exchangealgebra 0.5.3.2 → 0.6.0.0
raw patch · 287 files changed
+43331/−31541 lines, 287 filesdep +processdep −non-negativedep ~hashable
Dependencies added: process
Dependencies removed: non-negative
Dependency ranges changed: hashable
Files
- ChangeLog.md +666/−0
- README.md +192/−113
- docs/EDINET-TAXONOMY-NOTICE.md +21/−0
- docs/admission-api-design-notes.md +163/−0
- docs/migration-0.5-derived-metrics.md +27/−14
- exchangealgebra.cabal +194/−37
- src/ExchangeAlgebra.hs +360/−67
- src/ExchangeAlgebra/Accounting.hs +52/−45
- src/ExchangeAlgebra/Accounting/Account.hs +105/−0
- src/ExchangeAlgebra/Accounting/Account/Aliases.hs +217/−0
- src/ExchangeAlgebra/Accounting/Account/Classification.hs +138/−0
- src/ExchangeAlgebra/Accounting/Account/PostingPolicy.hs +77/−0
- src/ExchangeAlgebra/Accounting/Account/Registry.hs +4063/−0
- src/ExchangeAlgebra/Accounting/Account/Title.hs +573/−0
- src/ExchangeAlgebra/Accounting/Adjustment.hs +1021/−0
- src/ExchangeAlgebra/Accounting/Catalog.hs +536/−0
- src/ExchangeAlgebra/Accounting/Closing.hs +314/−0
- src/ExchangeAlgebra/Accounting/Consolidation.hs +526/−0
- src/ExchangeAlgebra/Accounting/Entries.hs +624/−0
- src/ExchangeAlgebra/Accounting/Equivalence.hs +271/−0
- src/ExchangeAlgebra/Accounting/Exchange.hs +552/−0
- src/ExchangeAlgebra/Accounting/PostingPolicy.hs +4/−76
- src/ExchangeAlgebra/Accounting/Statements.hs +82/−0
- src/ExchangeAlgebra/Accounting/Statements/Group.hs +408/−0
- src/ExchangeAlgebra/Accounting/Statements/Metric.hs +198/−0
- src/ExchangeAlgebra/Accounting/Statements/Presentation.hs +986/−0
- src/ExchangeAlgebra/Accounting/Transaction.hs +87/−0
- src/ExchangeAlgebra/Accounting/Transfer.hs +532/−0
- src/ExchangeAlgebra/Accounting/TrialBalance.hs +40/−0
- src/ExchangeAlgebra/Accounting/TrialBalance/Accum.hs +67/−0
- src/ExchangeAlgebra/Accounting/TrialBalance/Balance.hs +96/−0
- src/ExchangeAlgebra/Accounting/TrialBalance/Validation.hs +343/−0
- src/ExchangeAlgebra/Algebra.hs +27/−18
- src/ExchangeAlgebra/Algebra/Base.hs +37/−522
- src/ExchangeAlgebra/Algebra/Base/Account/JcciAliases.hs +3/−215
- src/ExchangeAlgebra/Algebra/Base/Account/Registry.hs +8/−4020
- src/ExchangeAlgebra/Algebra/Base/Account/Types.hs +6/−137
- src/ExchangeAlgebra/Algebra/Base/Element.hs +9/−842
- src/ExchangeAlgebra/Algebra/Base/Representation.hs +289/−0
- src/ExchangeAlgebra/Algebra/Core.hs +50/−0
- src/ExchangeAlgebra/Algebra/Core/Representation.hs +1921/−0
- src/ExchangeAlgebra/Algebra/Element.hs +33/−0
- src/ExchangeAlgebra/Algebra/Element/Representation.hs +522/−0
- src/ExchangeAlgebra/Algebra/Exact.hs +0/−608
- src/ExchangeAlgebra/Algebra/Internal.hs +55/−2150
- src/ExchangeAlgebra/Algebra/Posting.hs +227/−0
- src/ExchangeAlgebra/Algebra/Readout/Net.hs +0/−51
- src/ExchangeAlgebra/Algebra/Transfer.hs +22/−697
- src/ExchangeAlgebra/Algebra/Transfer/Closing.hs +0/−39
- src/ExchangeAlgebra/Algebra/Transfer/Representation.hs +292/−0
- src/ExchangeAlgebra/Algebra/Transfer/Rule.hs +48/−422
- src/ExchangeAlgebra/Algebra/Value.hs +494/−0
- src/ExchangeAlgebra/Algebra/Value/Accum.hs +293/−0
- src/ExchangeAlgebra/Algebra/Value/Class.hs +361/−0
- src/ExchangeAlgebra/Assist.hs +3/−278
- src/ExchangeAlgebra/Assist/Descriptions.hs +2/−2
- src/ExchangeAlgebra/Bookkeeping.hs +3/−579
- src/ExchangeAlgebra/Consolidation/Worksheet.hs +5/−472
- src/ExchangeAlgebra/Convert.hs +6/−209
- src/ExchangeAlgebra/Convert/Checked.hs +8/−547
- src/ExchangeAlgebra/Convert/Csv.hs +3/−161
- src/ExchangeAlgebra/Foundation.hs +0/−138
- src/ExchangeAlgebra/IO/Input.hs +43/−0
- src/ExchangeAlgebra/IO/Input/Admission.hs +214/−0
- src/ExchangeAlgebra/IO/Input/Admission/Diagnostic.hs +59/−0
- src/ExchangeAlgebra/IO/Input/Admission/Engine.hs +89/−0
- src/ExchangeAlgebra/IO/Input/Admission/Evidence.hs +90/−0
- src/ExchangeAlgebra/IO/Input/Admission/Representation.hs +18/−0
- src/ExchangeAlgebra/IO/Input/Assist.hs +289/−0
- src/ExchangeAlgebra/IO/Input/Checked.hs +583/−0
- src/ExchangeAlgebra/IO/Input/Conversion.hs +225/−0
- src/ExchangeAlgebra/IO/Input/Csv.hs +178/−0
- src/ExchangeAlgebra/IO/Output.hs +21/−0
- src/ExchangeAlgebra/IO/Output/Csv.hs +32/−0
- src/ExchangeAlgebra/IO/Output/Statements.hs +775/−0
- src/ExchangeAlgebra/Journal.hs +53/−924
- src/ExchangeAlgebra/Journal/Core.hs +83/−0
- src/ExchangeAlgebra/Journal/Core/Representation.hs +1070/−0
- src/ExchangeAlgebra/Journal/Exact.hs +0/−224
- src/ExchangeAlgebra/Journal/Transfer.hs +21/−176
- src/ExchangeAlgebra/Journal/Transfer/Rule.hs +251/−58
- src/ExchangeAlgebra/Optimize.hs +5/−90
- src/ExchangeAlgebra/Optimize/Annealing.hs +5/−144
- src/ExchangeAlgebra/Optimize/GA.hs +5/−284
- src/ExchangeAlgebra/Posting.hs +6/−197
- src/ExchangeAlgebra/Render/Bookkeeping.hs +9/−51
- src/ExchangeAlgebra/Render/Csv.hs +5/−4
- src/ExchangeAlgebra/Render/Simulation.hs +4/−76
- src/ExchangeAlgebra/Reporting/Group.hs +3/−393
- src/ExchangeAlgebra/Reporting/Metric.hs +3/−169
- src/ExchangeAlgebra/Reporting/Presentation.hs +3/−763
- src/ExchangeAlgebra/Simulate.hs +33/−89
- src/ExchangeAlgebra/Simulate/Analysis.hs +4/−12
- src/ExchangeAlgebra/Simulate/Engine.hs +6/−1
- src/ExchangeAlgebra/Simulate/Lite.hs +15/−6
- src/ExchangeAlgebra/Simulate/Network.hs +13/−1229
- src/ExchangeAlgebra/Simulate/Policy.hs +10/−28
- src/ExchangeAlgebra/Simulate/Random.hs +5/−0
- src/ExchangeAlgebra/Simulate/Spill.hs +16/−9
- src/ExchangeAlgebra/Simulate/Visualize.hs +38/−25
- src/ExchangeAlgebra/Simulation/Analysis.hs +135/−0
- src/ExchangeAlgebra/Simulation/Array.hs +23/−0
- src/ExchangeAlgebra/Simulation/Network.hs +99/−0
- src/ExchangeAlgebra/Simulation/Network/Csv.hs +86/−0
- src/ExchangeAlgebra/Simulation/Network/Flows.hs +156/−0
- src/ExchangeAlgebra/Simulation/Network/Journal.hs +44/−0
- src/ExchangeAlgebra/Simulation/Network/Representation.hs +858/−0
- src/ExchangeAlgebra/Simulation/Optimize.hs +113/−0
- src/ExchangeAlgebra/Simulation/Optimize/Annealing.hs +154/−0
- src/ExchangeAlgebra/Simulation/Optimize/GA.hs +293/−0
- src/ExchangeAlgebra/Simulation/Output.hs +146/−0
- src/ExchangeAlgebra/TrialBalance/Balance.hs +3/−84
- src/ExchangeAlgebra/TrialBalance/Validation.hs +3/−424
- src/ExchangeAlgebra/Value.hs +16/−128
- src/ExchangeAlgebra/Write.hs +0/−1044
- test/Accounting/AdjustmentSpec.hs +455/−0
- test/Accounting/RegistrySpec.hs +3119/−0
- test/Admission/Spec.hs +156/−0
- test/Algebra/AccumSpec.hs +507/−0
- test/Algebra/CoreSpec.hs +642/−0
- test/Algebra/ExactReadoutSpec.hs +372/−0
- test/Algebra/ExactSumSpec.hs +200/−180
- test/Algebra/ProjWildcardSpec.hs +14/−12
- test/Golden/AdmissionBaseline.hs +116/−0
- test/Golden/ExampleNumbers/RippleAnalysis.hs +72/−0
- test/Golden/ExampleNumbers/RippleFixture.hs +42/−0
- test/Golden/ReadoutBaseline.hs +395/−0
- test/Golden/WireFormat.hs +371/−0
- test/Golden/WriteRows.hs +8/−27
- test/IO/InputOutputSpec.hs +830/−0
- test/Journal/CarrySpec.hs +115/−8
- test/Journal/JournalSpec.hs +510/−0
- test/Journal/NormalFormSpec.hs +204/−0
- test/Journal/SideTotalsSpec.hs +226/−0
- test/Posting/NoNumPosted.hs +12/−5
- test/Posting/PostingSpec.hs +32/−21
- test/Posting/SettleSpec.hs +10/−1
- test/Simulate/SimulateSpec.hs +1353/−0
- test/Simulation/NetworkSpec.hs +306/−0
- test/Simulation/OptimizeSpec.hs +130/−0
- test/Spec.hs +50/−7130
- test/Support.hs +192/−0
- test/Surface/Accounting.hs +6/−14
- test/Surface/Algebra/Exact.hs +0/−36
- test/Surface/Algebra/Readout/Net.hs +0/−22
- test/Surface/Foundation.hs +0/−173
- test/Surface/Journal/Exact.hs +0/−41
- test/Surface/Render/Bookkeeping.hs +2/−16
- test/SurfaceMain.hs +7/−6
- test/Transfer/BalanceSpec.hs +516/−0
- test/Transfer/RuleSpec.hs +172/−33
- test/Value/MoneyParseSpec.hs +203/−0
- test/compat-clients/CompatInstance.hs +107/−0
- test/compat-clients/InstanceMain.hs +6/−0
- test/compat-clients/MixedPath.hs +112/−0
- test/compat-clients/NewPath.hs +156/−0
- test/compat-clients/OldPath.hs +39/−0
- test/compat-clients/README.md +21/−0
- test/compat-clients/check.sh +27/−0
- test/compile-fail/AdmissionFailureConstructor.hs +10/−0
- test/compile-fail/AlgConstructor.hs +7/−0
- test/compile-fail/AlgHiddenConstructor.hs +3/−0
- test/compile-fail/AlgRecordUpdate.hs +7/−0
- test/compile-fail/Coerce.hs +12/−0
- test/compile-fail/Constructor.hs +10/−0
- test/compile-fail/DeprecatedTransfer.hs +9/−0
- test/compile-fail/HiddenModule.hs +11/−0
- test/compile-fail/PublicClient.hs +34/−0
- test/compile-fail/PublicExplicitImportClient.hs +22/−0
- test/compile-fail/RawDerivation.hs +11/−0
- test/compile-fail/RecordUpdate.hs +11/−0
- test/compile-fail/check.sh +87/−0
- test/doctests.hs +41/−11
- test/fixtures/account-algebra-behavior/presentation.tsv +244/−0
- test/fixtures/account-algebra-behavior/projection-membership.tsv +244/−0
- test/fixtures/account-algebra-behavior/semantics.tsv +244/−0
- test/fixtures/account-semantics-050/README.md +3/−6
- test/fixtures/account-semantics-050/account-info.tsv +4/−1
- test/fixtures/account-semantics-050/consumer-inventory.txt +0/−48
- test/fixtures/account-semantics-050/metadata.tsv +3/−0
- test/fixtures/account-semantics-050/suggest.tsv +182/−163
- test/fixtures/admission-baseline-p1/README.md +19/−0
- test/fixtures/admission-baseline-p1/equivalence.tsv +14/−0
- test/fixtures/example-numbers-p1/README.md +36/−0
- test/fixtures/example-numbers-p1/ripple-analysis.tsv +244/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.PostingPolicy.txt +7/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.txt +359/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.JcciAliases.txt +1/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Registry.txt +23/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Types.txt +46/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Element.txt +277/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.txt +54/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Internal.txt +127/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.Rule.txt +36/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.txt +18/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.txt +128/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.Descriptions.txt +1/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.txt +16/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Bookkeeping.txt +19/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Consolidation.Worksheet.txt +73/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Checked.txt +40/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Csv.txt +5/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.txt +13/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.IO.Input.Admission.txt +19/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.Rule.txt +13/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.txt +12/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.txt +113/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.Annealing.txt +10/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.GA.txt +14/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.txt +8/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Posting.txt +13/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Bookkeeping.txt +12/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Csv.txt +2/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Simulation.txt +5/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Group.txt +36/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Metric.txt +22/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Presentation.txt +111/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Analysis.txt +2/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Engine.txt +49/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Lite.txt +37/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Network.txt +64/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Policy.txt +16/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Random.txt +3/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Spill.txt +37/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Visualize.txt +15/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.txt +89/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Balance.txt +12/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Validation.txt +35/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.Value.txt +13/−0
- test/fixtures/export-surface-p2/ExchangeAlgebra.txt +675/−0
- test/fixtures/export-surface-p2/README.md +51/−0
- test/fixtures/export-surface-p2/counts.tsv +46/−0
- test/fixtures/export-surface-p2/orphans.txt +19/−0
- test/fixtures/post-land2/README.md +0/−9
- test/fixtures/post-land2/suggest.tsv +0/−969
- test/fixtures/post-vocab/account-info.tsv +4/−1
- test/fixtures/post-vocab/ordinals.tsv +4/−1
- test/fixtures/post-vocab/semantics.tsv +3/−0
- test/fixtures/post-vocab/suggest.tsv +183/−164
- test/fixtures/pre-account-semantics-050/README.md +0/−33
- test/fixtures/pre-account-semantics-050/account-info.tsv +0/−233
- test/fixtures/pre-account-semantics-050/consumer-inventory.txt +0/−41
- test/fixtures/pre-account-semantics-050/presentation.tsv +0/−233
- test/fixtures/pre-account-semantics-050/projection-membership.tsv +0/−233
- test/fixtures/pre-account-semantics-050/semantics.tsv +0/−233
- test/fixtures/pre-land1/account-info.tsv +0/−117
- test/fixtures/pre-land1/account-semantics.tsv +0/−117
- test/fixtures/pre-land1/alias-resolution.tsv +0/−704
- test/fixtures/pre-land1/suggest.tsv +0/−972
- test/fixtures/pre-land2/presentation.txt +0/−117
- test/fixtures/pre-vland2/semantics.tsv +0/−117
- test/fixtures/pre-vocab/finalstock.tsv +0/−117
- test/fixtures/readout-baseline-p1/README.md +46/−0
- test/fixtures/readout-baseline-p1/alg-decimal-f-dec2.tsv +58/−0
- test/fixtures/readout-baseline-p1/alg-decimal-f-int.tsv +55/−0
- test/fixtures/readout-baseline-p1/alg-double-f-cancel.tsv +52/−0
- test/fixtures/readout-baseline-p1/alg-double-f-dec2.tsv +62/−0
- test/fixtures/readout-baseline-p1/alg-double-f-exp.tsv +45/−0
- test/fixtures/readout-baseline-p1/alg-double-f-int.tsv +55/−0
- test/fixtures/readout-baseline-p1/alg-double-f-sub.tsv +93/−0
- test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-dec2.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-int.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-double-f-cancel.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-double-f-dec2.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-double-f-exp.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-double-f-int.tsv +12/−0
- test/fixtures/readout-baseline-p1/alg-keyed-double-f-sub.tsv +12/−0
- test/fixtures/readout-baseline-p1/journal-decimal-f-dec2.tsv +29/−0
- test/fixtures/readout-baseline-p1/journal-decimal-f-int.tsv +30/−0
- test/fixtures/readout-baseline-p1/journal-double-f-cancel.tsv +52/−0
- test/fixtures/readout-baseline-p1/journal-double-f-dec2.tsv +57/−0
- test/fixtures/readout-baseline-p1/journal-double-f-exp.tsv +47/−0
- test/fixtures/readout-baseline-p1/journal-double-f-int.tsv +54/−0
- test/fixtures/readout-baseline-p1/journal-double-f-sub.tsv +88/−0
- test/fixtures/registry-aliases/alias-resolution.tsv +704/−0
- test/fixtures/statement-presentation/presentation.txt +66/−0
- test/fixtures/wire-format-p2/README.md +35/−0
- test/fixtures/wire-format-p2/decode-boundaries.tsv +25/−0
- test/fixtures/wire-format-p2/wire-format.tsv +288/−0
- test/fixtures/write-rows-0510/README.md +4/−9
- test/fixtures/write-rows-0510/ebex6-compound-trial-balance.tsv +26/−26
- test/fixtures/write-rows-0510/ebex7-compound-trial-balance.tsv +14/−14
- test/fixtures/write-rows-0510/ebex8-compound-trial-balance.tsv +10/−10
- test/fixtures/write-rows-0510/ebex9-bs.tsv +0/−12
- test/fixtures/write-rows-0510/ebex9-pl.tsv +0/−14
- tools/check-export-surface.py +180/−0
- tools/check-orphans.py +84/−0
ChangeLog.md view
@@ -1,5 +1,671 @@ # Changelog for ExchangeAlgebra +## 0.6.0.0 - 2026-11-06++### Highlights++- Transfers and closing return additional entries, preserving original postings.+- The public API has five layers: Algebra, Journal, Accounting, IO, and+ Simulation, with explicit root exports and compatibility modules for moved paths.+- Ordinary readouts accumulate exactly and round once; separate `*Exact`+ functions are removed, and custom `HatVal` instances implement the accumulator.+- Human-readable text in the affected public APIs uses `Text`.+- Plotting is optional: the `visualize` flag is off by default.+- Simulation modules are split by prefix. `Simulation.*` holds trade networks,+ matrix analysis, optimization, and result output; `Simulate.*` holds the+ runners (classic engine, Lite, ledger policies, spill files, plotting).+ Version 0.7 is planned to move simulation into a separate package and to+ remove the classic engine, Lite, and ledger policies without a compatibility+ module. These modules emit no deprecation warning in 0.6. See the README+ "Planned changes to simulation modules" section.++For migration steps, see the README "Migrating to 0.6.0.0" section.++### Added++- `ReversalOfAllowanceForBonuses` (賞与引当金戻入) is a new revenue account title,+ used when the estimate for bonuses is below the allowance balance. Its ordinal+ follows `PurchaseAllowances`; the wildcard `AccountTitle` shifts by one.++- `ExchangeAlgebra.Accounting.Adjustment` provides 15 named closing adjustments+ for grade-3 bookkeeping, with explicit rounding rules. Account-wide amounts+ are read from ledger balances, and contract or asset amounts from a selected+ source transaction; months, residual values, rates, and period-end counts are+ arguments. Depreciation schedules book the whole remainder in the final period.+ The catalog is unchanged.++- `Adjustment` adds 11 grade-2 closing adjustments: units-of-production+ depreciation, amortized cost, three allowance replenishments, retirement+ benefits, two securities valuations, inventory losses, foreign currency+ translation, and deferred tax assets. `TaxExceedsDifference` rejects rounded+ tax above the securities valuation difference.++- `ExchangeAlgebra.Accounting.Transfer` constructs additional accounting+ entries from exact balances per complete base. Each cancellation and+ destination have the same amount. Rows apply in the order written, including+ destinations generated by earlier rows. The destination keeps Hat/Not when+ the accounts have the same home side and reverses it otherwise. `Relabel`+ moves the whole balance through `splitAccum`; `Portion n d` rounds the exact+ balance times `n/d` once. `transferEntriesWith` selects the rounding rule.+ `transferEntriesBy` groups journal notes by their target note. When a transfer+ sits under a different note, combine the original and transfer notes with+ `toAlg` before reading base balances; include both in closing and carrying.++- `HasTermAxis(..)`, including `TermOf` and `termOf`, is available from+ `ExchangeAlgebra.Journal.Core`. `ExchangeAlgebra.Simulate.Policy` re-exports+ the same class and instances.++- `Journal.postFromNetBy` (also in `Journal.Core`) groups unrounded base+ residuals and calls the journal callback once per key after rounding. The+ callback can return postings on multiple notes. Bases containing nonfinite+ inputs retain their original postings and do not reach the callback.++- `HatVal` now provides `Accum` with `emptyAccum`, `addAccum`, `mergeAccum`,+ `negateAccum`, `signAccum`, `roundAccum`, `splitAccum`, and `exactAccum`.+ Finite inputs accumulate exactly, including sums beyond the floating-point+ range, and round once when read. `sumValues` uses this accumulator.+ `RationalAccum`, `rationalAddAccum`, and `rationalRoundAccum` support custom+ finite value types.+- `Rounding` (`Nearest` or `ToMultipleOf`) controls `roundRational` and+ `roundScaled`. Floating-point types accept only `Nearest`; `MoneyDecimal`+ accepts `Nearest` only for exact decimals of at most 255 places, or rounds+ to an explicit positive unit with ties to even.++- A `MoneyDouble` DET-2 test checks exact ledger and norm equality between+ `Sequential` and `ParChunk` widths 1, 2, 3, and 8 across four terms, with+ random fractional postings and a second stage that posts the snapshot norm.+- Two contra account titles, `SalesAllowances` (売上値引) and+ `PurchaseAllowances` (仕入値引), with their Japanese names accepted by the+ parser. The parser also accepts 荷造運賃 for `DeliveryExpenses` and 器具備品+ for `Fixtures`; these two are registry aliases (`asAliases`), because+ `jcciAliases` returns only the names in the frozen JCCI 2022 table. Net sales+ and net purchases deduct the respective allowances alongside rebates.+ Binary encoding uses the fixed account codes described under Changed.+- `ExchangeAlgebra.IO.Input.Admission` checks a journal written by an+ untrusted author, such as a language model, one transaction (note) at a+ time. `admit` accepts the journal unchanged as `Admitted` when every posting+ has a positive value, a `Hat` or `Not` side, and an account that some+ process may post, and when each transaction's debit (`decL`) and credit+ (`decR`) totals agree exactly. Otherwise it returns every violation+ (`AdmissionFailure`, read with `failureErrors`). `admit` is polymorphic in+ the note and base and uses `MoneyDecimal` values. `checkEvidence` compares an+ admitted journal, after a selection chosen by the caller, with a journal of+ expected elements from independent records. Each expected posting is a+ one-way query; mismatches are `EvidenceMissing`, `EvidenceDiffers`, or+ `EvidenceAmbiguous`. Neither function aggregates postings or applies `bar`.+ Admission does not post adjusting or closing entries, restrict accounts+ beyond the unpostable ones, or compare with a reference answer. Derive+ statements from `admittedJournal` with `Presentation.present`, and inspect+ accounting findings with `trialBalanceFindings`. `TxKey`, `EntityId`, `PeriodId`, and `TxId`+ (also in `ExchangeAlgebra.Accounting.Transaction`) are a ready-made note+ type. The separate `ExchangeAlgebra.Accounting.Equivalence` module compares+ original postings or transaction-local and selected-account net+ observations.+- `ExchangeAlgebra.IO.Output.Statements.financialStatementRows` and+ `writeFinancialStatements` write the chosen sections of presented financial+ statements as CSV rows (`section`, `account`, `label`, `row`, `side`,+ `amount`), in the order of the sections given and by account within a+ section. Each account is one line with its net balance. Choosing the revenue+ or expense section also writes the statement subtotals, including the+ period-result line; `PeriodResultMissing` reports a statement without one+ (for example, one presented after closing).+- `ExchangeAlgebra.Accounting.Equivalence.closingDifferences` and+ `isClosingEquivalent` compare two closings by transaction key: accounts other+ than the designated retained-earnings account by their exact total per debit+ or credit side, and retained earnings by its exact credit-minus-debit net.+ The result does not depend on how the same amounts are split across lines.+ Blank keys, HatNot postings, accounts without a debit or credit side, and+ negative amounts are reported as `ClosingDifference` diagnostics before any+ comparison. `isEquivalentUpTo` is unchanged.+- `ExchangeAlgebra.Value.moneyDecimalFromText` and+ `moneyDecimalFromScientific` convert a JSON-number text or a `Scientific`+ to `MoneyDecimal` exactly, without a binary floating-point step. The written+ decimal places are kept; nothing is rounded. Negative nonzero values,+ exponents beyond 10000 in magnitude, and scales above 255 places are rejected+ with the closed `MoneyParseError`, in a fixed priority.+- `ExchangeAlgebra.Journal.Transfer.Rule.sideTotalsBy` returns, for each key+ derived from a posting's `BasePart`, the Not and Hat totals before+ cancellation (`SideTotals`, read by `sideTotal`). Each side is the exact sum+ rounded once to nearest-even `Double`; a rounded total that stays finite+ succeeds even when the exact sum exceeds the largest finite value. HatNot,+ non-finite, and negative postings, and totals that round to infinity, return+ the closed `CarryError` with a fixed priority. Existing functions are unchanged.++### Changed++- `Presentation.present` takes the trial-balance stage and the trial balance+ directly (`ReportingContext v -> TrialBalanceStage -> Alg v (HatBase+ AccountTitles) -> Either (NonEmpty (PresentationIssue v)) (FinancialStatements+ v)`) and no longer stops on accounting findings such as reverse balances,+ remaining suspense balances, or unclosed nominal balances; inspect those with+ `trialBalanceFindings`. A legitimate reverse balance (for example a deficit+ in retained earnings) stays in its section on the side of the balance. It+ fails only when it cannot build a correct statement: an invalid value or a+ wildcard base (`InvalidStatementValue`, `WildcardStatementBase`), debit and+ credit totals that differ before any presentation step+ (`UnbalancedStatementInput`), a period result that cannot be computed+ (`PeriodResultUnavailable`), or the existing presentation issues. Before+ closing, the statements always include the period-result subtotal.+ `ValidationFindingBlocks` is removed.+ - Numeric: unchanged for statements that were accepted before.+ - Acceptance: trial balances with accounting findings are now presented.+ Three instructions that were accepted before are now rejected, so that+ the period-result subtotal always equals the exact sum of the revenue and+ expense balances: a relabel, materiality netting, or contra netting that+ moves a balance between a profit-and-loss title and a balance-sheet title+ (`ConflictingPresentationInstruction`); a maturity allocation or a+ maturity-sensitive title that is not an asset or a liability (also+ `ConflictingPresentationInstruction`); and a caller's `SubtotalDefinition`+ for `PeriodResultMetric` (`InvalidSubtotalDefinition`; the subtotal is now+ built in). Keep such instructions within one side, and drop the custom+ period-result subtotal.+ - Postings: unchanged; no posting is added for the period result.+- `periodResultOf` takes the trial-balance stage and the trial balance, and+ reports a wildcard account as `WildcardMetricAccount`.+- Journals now have no zero-valued notes in either layer. `map`,+ `projWithBase`, `projWithNoteBase`, `mkJournal`, `fromMap`, and Binary decoding+ drop zero notes. `mkJournal` and `fromMap` change from O(1) to O(n), evaluating+ each algebra to WHNF; maps without zero notes are reused without rebuilding.+ Code inspecting `toMap` keys no longer sees notes emptied by projection.+ `replaceNotes` no longer deletes a right-hand note when the left input has a+ zero note: use `filterWithNote` to remove the note instead.+ - Numeric: unchanged for direct operation results.+ - Acceptance: unchanged; old bytes containing zero notes still decode.+ - Postings: unchanged for direct results, except zero-valued underflow+ postings; compositions using zero-note replacement for deletion change.+ - Diagnostics: unchanged.+ - Serialization: changed; zero notes are no longer written.+- Algebra scalar multiplication `(.*)` drops postings whose products underflow+ to zero, preserving the remaining posting sequences and projection indices.++- The umbrella modules `ExchangeAlgebra` and `ExchangeAlgebra.Accounting`+ export the accounting transfer's `(.->)`, `(|%)`, `Relabel`, and+ `transferEntries`. Import `ExchangeAlgebra.Algebra.Transfer` qualified for+ generic per-posting `Relabel` and `transferEntries` alongside these names.+ For this transfer change: numeric unchanged; acceptance unchanged;+ postings unchanged; diagnostics changed (deprecation warnings can fail+ builds using `-Werror`); serialization unchanged. These five observations+ refer to existing functions; the umbrella name changes are source incompatible.++- `.@` rejects a base with side `HatNot`, including when the value is zero.+ `HatNot` remains available in query patterns.+- Public modules expose `:@` as a one-way record pattern synonym. Pattern+ matching and imports of `_val` and `_hatBase` still work; construction and+ record updates no longer compile. Migrate `v :@ b` to `v .@ b`.+ Tests that deliberately construct invalid values use `Single` from+ `ExchangeAlgebra.Algebra.Internal`, which provides unchecked construction+ outside the PVP guarantee.+ For this construction change: numeric unchanged; acceptance changed;+ postings unchanged; diagnostics unchanged; serialization unchanged.++- `closingEntries` closes each eligible base's exact net without a rounding+ residue. `Double` closing values can change; finite carry and side-total+ results retain their previous bits where the previous implementation succeeded.+- `closingEntries` accepts side sums beyond the value type's range when their+ net can be split into finite values. `carryEntries` and `carryBefore` now+ support any `HatVal`, including `MoneyDecimal`, and accept overflowing side+ sums when the once-rounded net is finite.+- `closingEntries` can emit multiple reversal and retained-earnings pairs per+ base to represent the exact net. Keep these pairs separate to preserve closure.+- `carryEntries` and `carryBefore` return `CarryError` instead of `ExactSumError`.+ Replace matches on `SumOutOfRange` with `ResultOutOfRange`, `NonFiniteInput`+ with `NonFiniteValue`, and `NegativeInput` with `NegativeValue`; nonzero+ HatNot postings now return `WildcardSide`.+- Serialization formats are unchanged by the closing and carry changes.++- Binary decoding of `Alg` and `Journal` rejects negative or nonfinite+ posting values, `HatNot` postings, repeated base keys, and repeated notes.+ Zero postings are removed with the remaining sequence order preserved;+ zero-valued journal notes are retained. `Binary (Alg v b)` now requires+ `HatVal v`; add that constraint to polymorphic serialization code.+- Decode diagnostics identify invalid values and duplicates. `Alg` failures+ begin with `Alg:`.+- `AccountTitles` uses fixed two-byte big-endian codes with the high bit set;+ the wildcard `AccountTitle` uses `0xFFFF`. Bytes containing `AccountTitles`+ written by 0.5.x or earlier develop revisions cannot be read: unmarked+ codes fail to decode. Export those values with their original library+ version and encode them again with this version. The `Alg` and `Journal`+ wire layouts (tags, counts, and order) are unchanged. Values containing no+ account bytes, such as `Zero` and an empty `Journal`, do not identify the+ encoding version.+- These serialization changes leave numeric results and valid postings+ unchanged.++- `(.|)` now returns an empty `Journal` for a zero algebra element, consistent+ with append and summation. It no longer retains a note for `Zero`.++- Posting readouts (`bar`, `norm`, `balance`, `diffRL`, `compress`, grouped+ balances, net projections, and `postFromNetBy`) now accumulate the supplied+ values exactly and round each output once. Finite intermediate sums can+ exceed the value type's range without losing later cancellation. `Pair`+ ordering compares exact net amounts, then the stored sequences.+- `Double` and `MoneyDouble` readouts use the exact binary values supplied,+ not their decimal spellings. Migration examples:+ 1. `0.1 + 0.2 - 0.3` leaves a `2^-55` residual instead of canceling.+ 2. A genuine difference of `0.5` at scale `1e12` remains visible.+ 3. `norm (x .+ y)` can differ from `norm x + norm y`, which adds rounded values.+ 4. Net projections merge unrounded base residuals; `norm . bar . proj`+ rounds each base first and can give a different result. Likewise,+ `norm (bar x) <= norm x` holds before rounding, but can fail after+ separately rounding base residuals.+ 5. `compress` rounds sides independently: `Not 2^53, Not 1, Hat 2^53`+ has net `1`, but applying `bar` after `compress` gives zero.+ 6. Equality of rounded side totals does not establish exact balance.+ Validation code must compare the original postings before rounding.+ 7. Grouping separate bases with `Not T, Not 1` and `Hat T`, where `T = 2^53`,+ gives signed balance `1` but net pair `(T,T)`. Their difference equals the+ signed balance before rounding, or for exact sums and subtraction, not for+ rounded `Double` or `MoneyDouble` components.+ 8. Base residuals `(T+1,1)` and `(T,1)` have the same rounded `bar` output,+ but `postFromNetBy` grouping both bases passes `T+2` and `T`, respectively,+ to the callback. Its factorization uses unrounded exact base residuals.+- Use `barWithin defaultTolerance` for the former sequential sum and tolerance+ formula. Summation order follows the representation, so bitwise identity with+ 0.5.x is not guaranteed. `balanceWithin` and `diffRLWithin` likewise retain+ sequential totals and an explicit tolerance. Other readouts have no tolerance.+- `compress` omits zero postings on an empty side and returns `Zero` for a+ zero scalar. These representation changes are visible through `Eq`, `Show`,+ and `Binary`.+- Nonfinite inputs follow IEEE accumulator states: `norm`, signed balances,+ and net projections return NaN or signed infinity; `compress` rounds each+ side separately. `netPairMapBy` puts infinite residuals on the winning side,+ or NaN on both sides. `balance` returns `False`; `diffRL` returns the direction+ and positive infinity, or `(Side, NaN)`. `bar` and `postFromNetBy` retain the+ original postings on nonfinite bases. `Pair` ordering classifies residuals as+ negative infinity, finite, positive infinity, then NaN; finite residuals are+ compared exactly before the structural tie-break.+- Accounting validation now compares exact posting sums before rounding:+ checked entries and text certification, trial balances (including reciprocal+ accounts), and consolidation source and adjustment checks. With+ `T = 9007199254740992 :: Double`, debit postings `[T, 1, 1]` against credit+ `[T + 2]` now pass, although sequential addition can reject them. Debit+ `[T, 1]` against credit `[T]` now fails, although rounded totals are equal.+ Exactness refers to the supplied binary values, not their decimal spelling.+- Account balances, period results, statement transformations and subtotals,+ and trial-balance writer totals use exact accumulators and round each amount+ once. Presentation checks use the values before rounding; displayed rows+ need not add up to the displayed subtotal. Caller-supplied scalar linkage+ comparisons in consolidation retain their existing meaning.+- `relativeTo` takes `Side -> (Side, v) -> RelativeAmount v`. Replace+ `relativeTo Debit (debitGross, creditGross)` with+ `relativeTo Debit (diffRL originalPostings)` so the net difference is taken+ before gross totals are rounded. When only scalar totals remain, determine+ their direction and difference explicitly; the lost posting precision cannot+ be recovered.+- Writers (`bsRows`, `plRows`, compound trial balances, worksheets, and+ post-closing trial balances), `presentGroups`, and `relativeTo` no longer+ suppress small differences with a tolerance; for example, a `plRows` total+ formerly displayed as `0.0` can now be `1.0e-300`. Balance-sheet groups use+ exact period-end balances per full base; small valuation-account residuals+ formerly suppressed by tolerance can now create a group.+- Removed `exactBalanced` from checked input and its compatibility export.+ Use `balance` for the exact posting-balance predicate in 0.6. Removed the+ internal `netGross`; writers read net balances from their accumulators.+- Removed the `NN.Double` instances of `HatVal`, `Nearly`, `ExactSum`, and+ the orphan `RealFloat` instance, along with `rounding` and the `non-negative`+ dependency. Use `MoneyDouble` or `Double` for values. Replace `rounding` with+ `ceilingRound` for ceiling rounding or `bankersRound` for ties-to-even rounding.+- Custom `HatVal` instances implement `valueError :: n -> Maybe ValueError`+ instead of `isErrorValue`. Use `NonFiniteValue` for NaN and infinities,+ `NegativeValue` for finite negative values, and `Nothing` for valid values.+ `isErrorValue` is now a function derived from `valueError`. Rejections by+ `.@` include the reason.+ Custom `HatVal` instances must also define `Accum`, all eight accumulator+ methods, and `roundRational`; there are no defaults, and a missing method+ fails at run time. For finite exact value types, follow the complete example+ in the Haddock for `RationalAccum`, built from `rationalAddAccum` and+ `rationalRoundAccum`, and satisfy laws A3, A5', A6, and T1-T3.+- `Posted v` and `Posting v b` use the caller's value type. `posted` checks+ `valueError` before the instance's `postingUpperBound`: `Double` and+ `MoneyDouble` use `Just (2^900)`, while `MoneyDecimal` has no upper bound.+ `PostedError` now uses `InvalidValue ValueError` and `AboveBound`;+ `postedUpperBound` is removed. Annotate literals, for example+ `posted (1 :: Double)`. Downstream adapters can define+ `type Posting b = EA.Posting Double b` once and route imports through that+ adapter. The value parameter has a nominal role, so `coerce` cannot bypass+ validation. `Posted Double` retains its existing binary encoding.++### Deprecated (module moves; definitions and behavior unchanged)++- `ExchangeAlgebra.Algebra.Transfer.Rule` is deprecated in 0.6.0.0. Import+ `ExchangeAlgebra.Algebra.Transfer` for generic coordinate transfers and+ `ExchangeAlgebra.Accounting.Closing` for closing and settlement operations.+ The individual deprecations of the scaling and rule-construction helpers+ remain in effect.++- `relabel`, `scaleBy`, `divideBy`, `MulBy`, and `DivBy` from the per-posting+ transfer API now warn. Use `from .-> to |% Relabel` for an accounting+ transfer, or `TransferRule from to Relabel` from+ `ExchangeAlgebra.Algebra.Transfer` for coordinate rewriting.+ Use `Portion n d` to move an equal share on both sides. For unit conversion,+ use `mapPosting` and retain the quantity ledger. For example, with a+ positive price `p`, convert one product's quantities to yen while keeping+ Hat/Not:++ ```haskell+ type ConversionBase = HatBase (AccountTitles, Text, CountUnit)++ toYen :: Double -> Alg Double ConversionBase -> Alg Double ConversionBase+ toYen p = mapPosting convert+ where+ convert :: Double -> ConversionBase -> (Double, ConversionBase)+ convert value posting = case base posting of+ (Products, productId, Amount) ->+ (value * p, merge (hat posting) (Products, productId, Yen))+ _ -> (value, posting)+ ```++ Import `ExchangeAlgebra` and `Data.Text (Text)` for this example.+ Use `ExchangeAlgebra.Journal.Core.map (toYen p)` for journals. The executable+ sample `test/api-samples/Conversion.hs` selects prices by product and also+ shows conversion back using positive prices.+- The per-posting `ExchangeAlgebra.Journal.Transfer.Rule.transferEntries`+ now warns; use `transferEntriesBy` for transfers from grouped exact balances.+- New module paths for the element, value, and account vocabulary. The old+ modules re-export exactly the same names and still build; they carry a+ module-level `DEPRECATED` pointing at the new path:+ - `ExchangeAlgebra.Algebra.Base.Element` → `ExchangeAlgebra.Algebra.Element`+ (also exports the new `matchesQuery`).+ - `ExchangeAlgebra.Value` → `ExchangeAlgebra.Algebra.Value`.+ - `ExchangeAlgebra.Algebra.Base.Account.Types`,+ `ExchangeAlgebra.Algebra.Base.Account.Registry`,+ `ExchangeAlgebra.Algebra.Base.Account.JcciAliases`, and+ `ExchangeAlgebra.Accounting.PostingPolicy` →+ `ExchangeAlgebra.Accounting.Account`.+- The accounting export changes in `Algebra.Base`, `Algebra.Internal`,+ `Algebra`, `Journal`, and the root module are listed under+ **Breaking changes without a compatibility shim**.+- `ExchangeAlgebra.Algebra.Core` (the generic `Alg` operations) and+ `ExchangeAlgebra.Algebra.Posting` are new public entry points.+ `ExchangeAlgebra.Posting` → `ExchangeAlgebra.Algebra.Posting`.+ Removed projection names and their replacements are listed under **Removed**.+- `ExchangeAlgebra.Journal.Core` is the generic Journal entry point. It does+ not export the removed `insert`, `projWithBaseNorm`, or `projWithNoteNorm`;+ see **Removed** for their replacements.+- `ExchangeAlgebra.Accounting.Exchange` owns `ExBaseClass`, `AccountBase`,+ `Exchange` and all their instances (including `Exchange Alg` and+ `Exchange (Journal n)`). Default methods resolve to the same functions.+- `ExchangeAlgebra.Accounting.Closing` owns the closing types and+ `closingEntries` / `settleEntries`. `ExchangeAlgebra.Algebra.Transfer.Rule`+ re-exports both the generic transfer names and the closing names as a+ deprecated compatibility module.+- Accounting reports: `ExchangeAlgebra.Bookkeeping` →+ `ExchangeAlgebra.Accounting.Entries`; `ExchangeAlgebra.TrialBalance.Balance` /+ `.Validation` → `ExchangeAlgebra.Accounting.TrialBalance`;+ `ExchangeAlgebra.Reporting.Group` / `.Metric` / `.Presentation` →+ `ExchangeAlgebra.Accounting.Statements`; `ExchangeAlgebra.Consolidation.Worksheet`+ → `ExchangeAlgebra.Accounting.Consolidation` (which does not re-export+ `PeriodResult` or `AccountBalance`; import them from `Accounting.Statements`+ and `Accounting.TrialBalance`).+- Input and output: `ExchangeAlgebra.Convert` and `ExchangeAlgebra.Convert.Checked`+ → `ExchangeAlgebra.IO.Input` (`exactBalanced` is removed; use `balance`),+ `ExchangeAlgebra.Convert.Csv` → `ExchangeAlgebra.IO.Input.Csv`, `ExchangeAlgebra.Assist` →+ `ExchangeAlgebra.IO.Input.Assist`, `ExchangeAlgebra.Assist.Descriptions` →+ `ExchangeAlgebra.Accounting.Account`, `ExchangeAlgebra.Render.Csv` /+ `ExchangeAlgebra.Render.Bookkeeping` → `ExchangeAlgebra.IO.Output.Csv` /+ `ExchangeAlgebra.IO.Output.Statements`.+- Simulation: `ExchangeAlgebra.Simulate.Network` → `ExchangeAlgebra.Simulation.Network`+ (industrial flows in `.Flows`, CSV readers in `.Csv`, `sigmaEdges` in+ `.Journal`); `ExchangeAlgebra.Simulate.Analysis` →+ `ExchangeAlgebra.Simulation.Analysis`; `ExchangeAlgebra.Optimize` /+ `.Annealing` / `.GA` → `ExchangeAlgebra.Simulation.Optimize` / ...;+ `ExchangeAlgebra.Render.Simulation` keeps re-exporting its names (the output+ functions are also on `ExchangeAlgebra.Simulation.Output`). The classic+ engine, `Policy`, `Spill`, `Random`, `Lite`, and `Visualize` keep their+ `Simulate.*` names.++### Breaking changes without a compatibility shim++- `ExchangeAlgebra.Algebra`, `ExchangeAlgebra.Algebra.Base`,+ `ExchangeAlgebra.Algebra.Internal`, and `ExchangeAlgebra.Journal` no longer+ re-export accounting names. Add imports from these owners; the values and+ types are unchanged:+ - `ExchangeAlgebra.Accounting.Account`: `AccountTitles(..)`; classification+ types `AccountDivision`, `Side`, `ClosingRule`, `FixedCurrent`, `AccountRole`,+ `PostingCapability`, `DivisionSemantics`, `HomeSideSemantics`, and+ `ReportingEligibility`; registry types `AccountSpec`, `AccountSemantics`+ and functions `accountAliases`, `accountSpec`, `accountSemantics`,+ `accountSpecMap`, `concreteAccountTitles`, `classifyAccountContra`,+ `accountDescriptions`; and `PIMO(..)`, `switchSide`, `defaultSide`,+ `classifyAccountDivision`, `pimoFromDivision`, `pimoFlip`.+ - `ExchangeAlgebra.Accounting.Exchange`: `ExBaseClass(..)`, `AccountBase(..)`,+ `Exchange(..)` (`decL`, `decR`, `decP`, `decM`, `balance`, `diffRL`,+ `balanceWithin`, `diffRLWithin`) and the account projections `projCredit`,+ `projDebit`, `projByAccountTitle`, `projCurrentAssets`, `projFixedAssets`,+ `projDeferredAssets`, `projCurrentLiability`, `projFixedLiability`,+ `projCapitalStock`, `projContraAssets`, `projContra`.+ Replace qualified uses such as `EA.balance` with the corresponding name from+ a qualified `Accounting.Exchange` import. `import ExchangeAlgebra` still+ supplies all these accounting names.+- `ExchangeAlgebra.Algebra.Transfer` and `ExchangeAlgebra.Journal.Transfer`+ replace their contents under the same module names. See the opening+ paragraph and per-name migrations in **Removed**.+- `ExchangeAlgebra` now uses an explicit list of exported names. Account+ vocabulary, exchange operations, transfers, closing, bookkeeping output,+ and value types remain available. `writeTermIO`, `writeIOMatrix`,+ `restoreJournalFromBinarySpill`, and `restoreJournalFromBinarySpillChecked`+ require the imports listed in **Removed**. The root now also exports+ trial-balance balances and findings (`AccountBalance(..)`, `accountBalances`,+ `balanceFor`, `balanceSide`, `balanceAmount`, `TrialBalanceStage(..)`,+ `TrialBalanceInput(..)`, `ReclassificationRule(..)`, `TBFinding(..)`,+ `trialBalanceFindings`), financial-statement presentation+ (`ReportingContext(..)`, `ReportingScope(..)`, `jcciSecondGradeContext`,+ `FinancialStatements(..)`, `StatementSection(..)`, `StatementLine(..)`,+ `PresentationIssue(..)`, `present`), and output (`StatementRowsError(..)`,+ `financialStatementRows`, `writeFinancialStatements`).++- Public API that carries human-readable text uses `Text` instead of `String`:+ scenario names in `runScenarios` and `runScenariosWithSpill` (the list, the+ callback argument, and the `Map` key), stage names in `stageFor`, `stage`, and+ `stageName`, the payload of `mkSpillOptions` and `defaultSpillWriter`,+ `spillErrorMessage`, `Title` and `Label` in `Simulate.Visualize` (and so+ `TimeSeries`, `TimeSerieses`, `GridColumns`, and `GridMatrix`), and the `Left`+ of `parseEdgeCsv`, `parseCoefCsv`, `readEdgeCsv`, and `readCoefCsv`. There are+ no `String` variants. Add `OverloadedStrings` or `Data.Text.pack` at call+ sites, and `Data.Text.unpack` where a scenario name becomes a path.+ `FilePath`, `FileName`, `showValue`, `renderSpillReadError`, and+ `instance Note String` keep `String`. The text spill file format and all+ message contents are unchanged.++- The Cabal flag `visualize` is now off by default, so the package builds+ without Cairo and Pango. `ExchangeAlgebra.Simulate.Visualize` is available+ only with `--flag exchangealgebra:visualize`, or with `visualize: true` under+ `flags` in `stack.yaml`.++The same module names are used by the new contents, so compatibility shims+cannot retain the old export surfaces under those names.++### Removed++In 0.6.0.0, `ExchangeAlgebra.Algebra.Transfer` and+`ExchangeAlgebra.Journal.Transfer` keep their module names but replace their+contents with generic coordinate transfers and note-aware carry operations,+respectively. The old functions rewrote postings; the replacement transfer+and closing functions return additional entries to add with `(.+)`, preserving+the original postings.++- `ExchangeAlgebra.Foundation` is removed. Import `ExchangeAlgebra.Algebra`;+ it provides all the former foundation names.+- The public `nearlyEqScaled` helper is removed; it has no callers in `src`.+ Use `isNearlyNum` for public value comparisons or `Tolerance` with the+ operations that accept an explicit tolerance.+- `writeTermIO` is removed from `ExchangeAlgebra`. Import it from+ `ExchangeAlgebra.Simulation.Output`.+- `writeIOMatrix` is removed from `ExchangeAlgebra`. Import it from+ `ExchangeAlgebra.Simulation.Output`.+- `restoreJournalFromBinarySpill` is removed from `ExchangeAlgebra`. Import+ it from `ExchangeAlgebra.Simulate.Spill`.+- `restoreJournalFromBinarySpillChecked` is removed from `ExchangeAlgebra`.+ Import it from `ExchangeAlgebra.Simulate.Spill`.++- `TransTable` is removed. Use `Accounting.Transfer.TransferTable` for+ accounting transfers or `Algebra.Transfer.TransferRules` for coordinates.+- `TransTableParts` is removed. Write accounting rows with+ `Accounting.Transfer`'s `from .-> to |% Relabel`.+- `Size` is removed with the old table representation; it has no replacement.+- `isNullTable` is removed. Inspect `Accounting.Transfer.tableRows` or+ `Algebra.Transfer.rulesToList` when checking for an empty rule set.+- `table` is removed. Use `Accounting.Transfer.mkTransferTable` or+ `Algebra.Transfer.mkTransferRules` to validate rows.+- The old `(.->)` in both Transfer modules is removed. Use+ `Accounting.Transfer`'s `(.->)` with bases without Hat/Not.+- The old `(|%)` in both Transfer modules is removed. Use+ `Accounting.Transfer`'s `from .-> to |% Relabel` to construct a row.+- `transfer` is removed from both Transfer modules. Use+ `Accounting.Transfer.transferEntries`, or `transferEntriesBy` for journals.+ For unit conversion, use `Algebra.Core.mapPosting` instead.+- `createTransfer` is removed from both Transfer modules. Build a table with+ `Accounting.Transfer.mkTransferTable`, then apply `transferEntries` or+ `transferEntriesBy`; use `mapPosting` for unit conversion.+- `incomeSummaryAccount` is removed from both Transfer modules. Use+ `Accounting.Closing.closingEntries`, or accounting transfer rows through+ `IncomeSummary` when the intermediate stage is needed.+- `netIncomeTransfer` is removed from both Transfer modules. Use+ `Accounting.Closing.closingEntries` for closing, or an accounting transfer+ row from `IncomeSummary` to `RetainedEarnings` for staged closing.+- `grossProfitTransfer` is removed from both Transfer modules. Use+ account-specific `Accounting.Transfer` rows; `examples/basic/fixedPriceIO.hs`+ demonstrates staged closing.+- `ordinaryProfitTransfer` is removed from both Transfer modules. Use+ account-specific `Accounting.Transfer` rows as in `fixedPriceIO.hs`.+- `retainedEarningTransfer` is removed from both Transfer modules. Use an+ `Accounting.Transfer` row from `OrdinaryProfit` to `RetainedEarnings`.+- `Algebra.Transfer.finalStockTransferStep` is removed. Use+ `Accounting.Closing.closingEntries` for additional closing entries.+- `finalStockTransfer` is removed from both Transfer modules. Use+ `Accounting.Closing.closingEntries`; for journals, pass `toAlg journal`,+ attach the closing note with `(.|)`, and add the result to the journal.+ Use `Accounting.Closing.closingSide` to observe closing classification.+- `Journal.Transfer.finalStockTransferAggregated` is removed. Call+ `Accounting.Closing.closingEntries (toAlg journal)`, attach a note with+ `(.|)`, and aggregate explicitly with `bar` where needed.+- `Journal.Transfer.Rule.closingEntries` is removed. Call+ `Accounting.Closing.closingEntries (toAlg j)` and attach the closing note+ to the returned entries with `(.|)`.++- The standard CGE example (`cge`) is removed; it will move to the+ general-equilibrium repository.+- The `sim2` example (`simulateEx2`) is removed. `fixed-price-io-spill`+ (formerly `sim2fast`) runs the same 200-company model with spill-to-disk on+ `MoneyDouble`; use `MoneyDecimal` in your own model when the ledger must be+ exact. Tag `v0.5.3.0` still builds `sim2`. The examples directory now keeps+ only general examples, and each research study publishes its models in a+ separate repository (see `examples/README.md`).+- The simulation examples are renamed after the model they run, a fixed-price+ input-output economy: `sim1` (`simulateEx1.hs`) is now `fixed-price-io`+ (`fixedPriceIO.hs`), and `sim2fast` (`simulateEx2Fast.hs`) is now+ `fixed-price-io-spill` (`fixedPriceIOSpill.hs`). Their outputs move to+ `examples/basic/result/{csv,fig}/fixedPriceIO/` and+ `examples/basic/result/csv/fixedPriceIOSpill/`, and the plotting script is+ now `examples/basic/visualize_fixedPriceIO.py`.+- `ExchangeAlgebra.Write` is removed. Import CSV serialization (`writeCSV`,+ `csvTranspose`) from `ExchangeAlgebra.IO.Output.Csv`; journals, account+ ledgers, trial balances, and worksheets from+ `ExchangeAlgebra.IO.Output.Statements`; `writeTermIO` and `writeIOMatrix`+ from `ExchangeAlgebra.Simulation.Output`; and spill restoration from+ `ExchangeAlgebra.Simulate.Spill`.+- `bsRows` is removed; it settled the ledger internally. Add `closingEntries`+ to the ledger, then use `present` with `AfterClosing`, then+ `financialStatementRows` from `ExchangeAlgebra.IO.Output.Statements`.+- `writeBS` is removed. Use `present` with `AfterClosing`, then+ `writeFinancialStatements` from `ExchangeAlgebra.IO.Output.Statements`.+- `plRows` is removed; it dropped reversals of the same title. Use `present`+ with `BeforeClosing`, then+ `financialStatementRows`. Pass the whole trial balance before selecting+ revenue and expense sections for rendering.+- `writePL` is removed. Use `present` with `BeforeClosing`, then+ `writeFinancialStatements`.+- `balanceOf t` is removed. Use `diffRL . projByAccountTitle t`.+- `tshow` is private. Use `Data.Text.pack . show` in caller code.+- `toSameLength` is private. Pad caller-owned text columns in caller code.+- `industrialEx1` (`industrial`) moved to the SICE paper repository.+- `invoiceEx1` (`invoice`) moved to the SICE paper repository.+- The `cge-lite` executable and its calibration, model, solver, and LHR test+ suites moved to general-equilibrium.+- `ripple`, `rippleWithStock`, and `rippleWithStockMultiSeeds` moved to the+ comp-econ-asabm research repository.+- `marketEx1`, `marketEx1d`, `market-policy-test`, and the RCR reproduction+ package moved to the scaling paper repository. The pre-move sources for all+ five research example families are preserved in tag+ [archive/research-examples-pre-p4](https://github.com/yakagika/ExchangeAlgebra/tree/archive/research-examples-pre-p4/examples).+- The trial-balance policy gate is removed in 0.6.0.0:+ `ReciprocalPolicy(..)`, `TemporaryBalancePolicy(..)`,+ `TrialBalancePolicy(..)`, `strictTrialBalancePolicy`,+ `standaloneTrialBalancePolicy`, `findingBlocksPresentation`,+ `ValidatedTrialBalance`, `validateTrialBalance`, `validatedTrialBalance`,+ `validatedFindings`, `validatedPolicy`, `validatedStage`, and+ `validatedMaturityRequiredTitles`. Pass the trial balance and its stage+ directly to `present`, and obtain accounting findings separately with+ `trialBalanceFindings`. Add every title in a `MaturityEvidenceRequired`+ rule to `_maturitySensitiveTitles`. Findings and their order are unchanged.+- `ExchangeAlgebra.Algebra.Exact` and `ExchangeAlgebra.Journal.Exact` are removed.+ Their successors below return values directly, without `Either ExactSumError`.+- `normExact` is removed; use the existing `norm` method on algebras or journals.+- `barExact` is removed; use the existing `bar` method on algebras or journals.+- `projNetNormExact` is removed; use `Algebra.projNetNorm` or+ `Journal.projWithBaseNetNorm`.+- `balanceMapByExact` is removed; use `Algebra.balanceMapBy`, composed with `toAlg`+ for journals. The result contains signed values rather than `(Ordering, magnitude)`.+- `netPairMapByExact` is removed; use `Algebra.netPairMapBy`, composed with `toAlg`+ for journals.+- `postFromNetByExact` is removed; use `postFromNetBy` on algebras or journals.+- `diffRLExact` is removed; use the existing `diffRL` method on algebras or journals.+- `balanceExact` is removed; use the existing `balance` method on algebras or journals.+- `accountBalancesExact` is removed; use+ `Accounting.TrialBalance.Balance.accountBalances`, composed with `toAlg` for journals.+- `projWithBaseNetNormExact` is removed; use `Journal.projWithBaseNetNorm`.+- `projWithNoteBaseNetNormExact` is removed; use `Journal.projWithNoteBaseNetNorm`.+- `ExactSum` and its methods are removed; use the existing `HatVal` accumulator.+ Subtract states with `mergeAccum left (negateAccum right)`. Use `signAccum`+ for the direction, `negateAccum` for a negative state's magnitude, and+ `roundAccum` to round once; this also replaces `netAccumState`.+- `ExactSumError` is removed. Posting construction rejects negative and non-finite+ inputs; readouts retain the 0.6 accumulator contract, including IEEE overflow+ for floating results.+- `sumExact` is removed; use `sumValues`. `sumValues` does not reject negative+ inputs, which `sumExact` reported as `NegativeInput`, so callers that add+ values outside postings must check the sign themselves.+- `netAccum` is removed; subtract `HatVal` states as above and round with `roundAccum`.+- `ExchangeAlgebra.Algebra.Readout.Net` is removed. Import its retained functions+ from `ExchangeAlgebra.Algebra` or `ExchangeAlgebra.Journal`.+- `projNorm` is removed; use `projNetNorm`.+- `Journal.insert` is removed; use `Journal.replaceNotes` for whole-note replacement.+- `Journal.projWithBaseNorm` is removed; use `Journal.projWithBaseNetNorm`.+- `Journal.projWithNoteNorm` is removed; use `Journal.projWithNoteBaseNetNorm`.++- The research evaluation tooling in `examples/audit-eval` (task generators,+ runners, checkers, and oracles) moved to a separate repository.++### Fixed++- `compoundTrialBalanceRows` now puts a debit balance in the debit-balance+ column and a credit balance in the credit-balance column. Previously each+ balance appeared in the opposite column; the total columns were already+ correct and are unchanged.+- Algebra projections (`proj` and the projections built on it) no longer keep+ a base whose selected side is empty. Previously, projecting a multi-posting+ element onto the side that a base does not have left that base with both+ sides empty, so `isZero` could return `False` for a projection that selected+ nothing, and `Journal.projWithBase` kept the emptied note. Netting results+ such as `projNetNorm` are unchanged.++### Documentation++- The root module example now debits cash and credits sales as described, and+ builds postings with `.@`.+- `Simulation.Analysis` documents the current preconditions: the fixed origin+ `(1,1)`, no pivot exchange in `inverse`, that `inverse` overwrites its+ argument while `leontiefInverse` copies it, and that `rippleEffect` takes the+ Leontief inverse and fills one column.+- Runnable Haddock examples now cover `Accounting.Statements.Presentation`,+ `Accounting.TrialBalance.Validation`, and `Accounting.Consolidation`.+ The `Journal.Transfer` examples are+ now part of the doctest run.+- `IO.Input.Admission` has a walkthrough with runnable examples: write a+ journal, add an adjusting entry and the closing entries, `admit`, read the+ failures, `checkEvidence` against a bank record, and present the statements.+ The walkthrough uses `present` and `trialBalanceFindings`.+ ## 0.5.3.2 - 2026-10-30 ### Fixed
README.md view
@@ -12,7 +12,7 @@ - Book: <https://www.springer.com/gp/book/9784431209850> - Paper: <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>-- Haddock: [haddock/index.html](https://htmlpreview.github.io/?https://raw.githubusercontent.com/yakagika/ExchangeAlgebra/master/haddock/index.html)+- Haddock: <https://hackage.haskell.org/package/exchangealgebra> ## Installation @@ -21,7 +21,7 @@ ```yaml # stack.yaml extra-deps:- - exchangealgebra-0.5.2.0+ - exchangealgebra-0.6.0.0 ``` ```yaml@@ -42,12 +42,13 @@ Requirements: - GHC 9.10 (tested with Stackage `lts-24.4`) - Cabal 3.0 or later-- `Chart` / `Chart-cairo` transitively require the Cairo / Pango / Freetype system libraries- (on macOS: `brew install cairo pango`). If you cannot or do not want to- install them, build without the plotting module: `stack build --flag- exchangealgebra:-visualize` (Cabal flag `visualize`, default on). This drops- `ExchangeAlgebra.Simulate.Visualize` and the Chart dependencies; the bundled- `ripple*`, `sim*` and `cge` examples import that module and need the default+- The plotting module `ExchangeAlgebra.Simulate.Visualize` is off by default.+ To use it, install the Cairo / Pango / Freetype system libraries (on macOS:+ `brew install cairo pango`) and enable `visualize` with+ `stack build --flag exchangealgebra:visualize` or set `visualize: true`+ under `flags` → `exchangealgebra` in `stack.yaml`. The `fixed-price-io` and+ `fixed-price-io-spill` examples import this module; `examples/stack.yaml`+ enables the flag. ## How to consume this package @@ -68,7 +69,7 @@ git clone https://github.com/yakagika/ExchangeAlgebra.git cd ExchangeAlgebra stack build-stack exec -- sim1 # or ebex1, ripple, cge, ...+stack exec -- fixed-price-io # or ebex1, ebex2, ... ``` See the [examples directory](https://github.com/yakagika/ExchangeAlgebra/tree/master/examples)@@ -93,29 +94,30 @@ ## Module Overview -The 44 public modules (including the `visualize`-gated module) are organized-into seven layers. Entries marked **0.5.1.0 / Git only** are not yet published-on Hackage; use a Git pin, or keep importing the existing defining module.+The following entry points describe the 0.6 API. The layers are Algebra, Journal, Accounting, IO, and Simulation. ### Umbrella The recommended entry point collects the common single-period bookkeeping API. -- `ExchangeAlgebra` — re-exports `Algebra`, `Algebra.Transfer`, `Value`, and `Write`.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Foundation` — re-exports the- Definitions 1-6 algebraic foundation without accounting vocabulary.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Accounting` — re-exports the+- `ExchangeAlgebra` — exports named selections of generic algebra, account+ vocabulary and exchange operations, transfers and closing, trial-balance+ balances and findings, financial-statement presentation, CSV and bookkeeping+ output, and value types.+- `ExchangeAlgebra.Accounting` — re-exports the Definitions 7-9 accounting basis, exchange decomposition, and transfer core. ### Account vocabulary These modules define basis elements and the canonical account metadata vocabulary. -- `ExchangeAlgebra.Algebra.Base.Element` — defines wildcard-aware basis components and the built-in account-title vocabulary.-- `ExchangeAlgebra.Algebra.Base.Account.Types` — defines account metadata types shared by the basis and registry.-- `ExchangeAlgebra.Algebra.Base.Account.JcciAliases` — supplies the generated JCCI 2022 account-name alias overlay.-- `ExchangeAlgebra.Algebra.Base.Account.Registry` — is the exhaustive source of canonical account classification, semantics, and descriptions.-- `ExchangeAlgebra.Algebra.Base` — defines basis classes, Hat/Not bases, account divisions, and basis display helpers.+- `ExchangeAlgebra.Accounting.Account` — defines account titles, classifications,+ registry metadata, posting eligibility, and PIMO direction helpers.+- `ExchangeAlgebra.Accounting.Exchange` — defines `ExBaseClass`, `AccountBase`,+ `Exchange`, debit/credit decomposition, balance checks, and account projections.+- `ExchangeAlgebra.Algebra.Element` — defines generic wildcard-aware basis components.+- `ExchangeAlgebra.Algebra.Base` — defines generic basis classes, Hat/Not bases,+ and basis display helpers. ### Core algebra @@ -125,10 +127,9 @@ - `ExchangeAlgebra.Algebra.Internal` — exposes the representation behind `Alg` for advanced use (all constructors, cache fields, rebuild helpers), but carries no stability guarantee.-- `ExchangeAlgebra.Algebra.Transfer` — rewrites existing algebra balances through transfer tables and closing transfers.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Algebra.Readout.Net` — collects lossy,- netted read-outs from algebras and journals.-- `ExchangeAlgebra.Value` — provides fast typed and exact non-negative value types for `Alg` and `Journal`.+- `ExchangeAlgebra.Algebra.Transfer` — generic coordinate rules that return+ additional entries to add to the original algebra.+- `ExchangeAlgebra.Algebra.Value` — provides fast typed and exact non-negative value types for `Alg` and `Journal`. ### Wildcards: queries versus ledger entries @@ -143,86 +144,124 @@ or `p = e`, on each axis. A concrete query does not select a ledger `#`: ```haskell-import ExchangeAlgebra.Algebra+import ExchangeAlgebra ledger = 10 .@ Not :< (Cash, (.#)) :: Alg Double (HatBase (AccountTitles, CountUnit))-norm (proj [Not :< (Cash, Yen)] ledger) -- 0.0-norm (proj [Not :< (Cash, (.#))] ledger) -- 10.0+concreteAmount = norm (proj [Not :< (Cash, Yen)] ledger) -- 0.0+wildcardAmount = norm (proj [Not :< (Cash, (.#))] ledger) -- 10.0 ``` Canonical ordering treats `#` as an ordinary value. The `.==` operator remains-symmetric; use projection or `ExchangeAlgebra.Algebra.Transfer.Rule` for one-way+symmetric; use projection or `ExchangeAlgebra.Algebra.Transfer` for one-way matching against ledger entries. -### Journal and simulation+### Journal -These modules attach notes to postings and run classic, Lite, network, policy-driven, and visual simulation workflows.+These modules attach notes to postings. - `ExchangeAlgebra.Journal` — defines metadata-bearing `Journal` values, indexed projections, and journal aggregation.-- `ExchangeAlgebra.Journal.Transfer` — specializes the algebra transfer API to `Journal`.-- `ExchangeAlgebra.Simulate` — provides the state-space engine, classic front-end, spill support, ripple utilities, and scenario execution.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Simulate.Engine` — re-exports the+- `ExchangeAlgebra.Journal.Transfer` — carries entries across notes and reads+ side totals. Use `Accounting.Transfer.transferEntriesBy` for accounting transfers.++### Simulation: `Simulation.*` and `Simulate.*`++Simulation code lives under two module prefixes with different futures.++- `ExchangeAlgebra.Simulation.*` holds components that a model uses besides+ the runner: trade networks, matrix analysis, optimization, and CSV output of+ results. These modules are kept after 0.6.+- `ExchangeAlgebra.Simulate.*` holds the simulation runners and their support:+ the classic state-space engine, the Lite front-end, ledger policies, spill+ files, random helpers, and Chart plotting.++Components (`Simulation.*`):++- `ExchangeAlgebra.Simulation.Network` — separates sparse trade-network topology from input coefficients;+ `.Flows`, `.Csv`, and `.Journal` generate industrial flows, read network CSV+ files, and sum flows into a `Journal`.+- `ExchangeAlgebra.Simulation.Analysis` — computes a Leontief inverse and ripple effects.+- `ExchangeAlgebra.Simulation.Output` — writes simulation results as CSV+ without Chart. `writeFuncResults` and `writeFuncResultsWithContext` take the+ classic engine's `StateTime`.+- `ExchangeAlgebra.Simulation.Optimize` — see **Optimization** below.++Runners (`Simulate.*`):++- `ExchangeAlgebra.Simulate` — provides the classic state-space engine, spill support, and scenario execution, and re-exports the ripple utilities.+- `ExchangeAlgebra.Simulate.Engine` — re-exports the classic state-space protocol and runners without spill configuration or analysis helpers.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Simulate.Analysis` — re-exports- Leontief-inverse and ripple-effect analysis.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Simulate.Random` — re-exports- simulation random-number helpers.-- `ExchangeAlgebra.Simulate.Spill` — provides spill chunk writers, checked readers, and ledger restoration shared by simulation and reporting layers.-- `ExchangeAlgebra.Simulate.Policy` — declares retention, spill, and compaction policy for long simulations. - `ExchangeAlgebra.Simulate.Lite` — provides the product-HKD, BSP front-end with declarative field rules and stages.-- `ExchangeAlgebra.Simulate.Network` — separates sparse trade-network topology from input coefficients and industrial flows.+- `ExchangeAlgebra.Simulate.Policy` — declares retention, spill, and compaction policy for long simulations.+- `ExchangeAlgebra.Simulate.Spill` — provides spill chunk writers, checked readers, and ledger restoration shared by simulation and reporting layers.+- `ExchangeAlgebra.Simulate.Random` — re-exports+ simulation random-number helpers. - `ExchangeAlgebra.Simulate.Visualize` — renders simulation grids and time series with Chart/Cairo. -### Which simulation API to use+### Planned changes to simulation modules -`ExchangeAlgebra.Simulate.Lite` is the canonical front-end for new simulations:-it provides `Generic`-derived world records, term-boundary field rules, stages,-and `runLite` / `runLiteWithPolicy`. `ExchangeAlgebra.Simulate.Engine` is the-lower-level classic state-space API (`StateTime`, `Updatable`, `UpdatePattern`,-and the `runSimulation` family), retained for existing models and-reproducibility. New models should normally use Lite; import Engine when-maintaining or directly driving the classic protocol.+The next major version (0.7) is planned to move simulation into a separate+package, `exchangealgebra-simulation`, whose runner is a new model evaluator.+The plan affects the 0.6 modules as follows: +- `Simulation.*` and `Simulate.Spill` move to the new package. The writers+ that take `StateTime` (`writeFuncResults`, `writeFuncResultsWithContext`)+ are redesigned for the evaluator or removed.+- The classic engine (`Simulate`, `Simulate.Engine`, `Simulate.Random`),+ `Simulate.Lite`, and `Simulate.Policy` are removed and not moved. Their+ successor is the model evaluator.+- `Simulate.Visualize` either moves to the new package behind Chart or is+ removed; the choice is made in 0.7.++The 0.6 modules do not emit deprecation warnings, because the successor package+has not been released. Changes to this plan will be recorded in the changelog.++### Which simulation runner to use in 0.6++`ExchangeAlgebra.Simulate.Lite` provides `Generic`-derived world records,+term-boundary field rules, stages, and `runLite` / `runLiteWithPolicy`.+`ExchangeAlgebra.Simulate.Engine` is the lower-level classic state-space API+(`StateTime`, `Updatable`, `UpdatePattern`, and the `runSimulation` family),+retained for existing models and reproducibility. Both runners are planned for+removal in 0.7 (see above). For a new model built on 0.6, Lite needs less code;+keep model logic separate from runner code so that it can be moved to the+successor evaluator.+ ### Bookkeeping and reporting These modules build adjustments, validate reporting boundaries, and produce statements and output. -- `ExchangeAlgebra.Bookkeeping` — builds period-end adjustment postings from explicit external amounts.-- `ExchangeAlgebra.Write` — formats bookkeeping reports and journals as CSV and restores binary spill files.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Render.Csv` — re-exports CSV+- `ExchangeAlgebra.Accounting.Entries` — builds period-end adjustment postings from explicit external amounts.+- `ExchangeAlgebra.IO.Output.Statements` — writes journals, account ledgers, trial balances, worksheets, and financial statements as CSV.+- `ExchangeAlgebra.IO.Output.Csv` — provides CSV serialization independently of document layouts.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Render.Bookkeeping` — re-exports- bookkeeping row builders and writers.-- **0.5.1.0 / Git only** `ExchangeAlgebra.Render.Simulation` — re-exports- simulation result writers.-- `ExchangeAlgebra.Reporting.Group` — defines presentation groups and contra-account netting policy.-- `ExchangeAlgebra.TrialBalance.Balance` — the shared `AccountBalance` type and debit-then-credit netting primitives.-- `ExchangeAlgebra.TrialBalance.Validation` — reports trial-balance findings and gates reporting with explicit policies.-- `ExchangeAlgebra.Reporting.Metric` — derives typed, read-only metrics without inserting posting coordinates.-- `ExchangeAlgebra.Reporting.Presentation` — transforms validated trial balances into auditable JGAAP presentation.-- `ExchangeAlgebra.Consolidation.Worksheet` — validates consolidation adjustments and preserves worksheet provenance.+- `ExchangeAlgebra.Accounting.Statements.Group` — defines presentation groups and contra-account netting policy.+- `ExchangeAlgebra.Accounting.TrialBalance.Balance` — the shared `AccountBalance` type and debit-then-credit netting primitives.+- `ExchangeAlgebra.Accounting.TrialBalance.Validation` — reports trial-balance findings and reclassification instructions.+- `ExchangeAlgebra.Accounting.Statements.Metric` — derives typed, read-only metrics without inserting posting coordinates.+- `ExchangeAlgebra.Accounting.Statements.Presentation` — presents a trial balance and its stage as JGAAP financial statements.+- `ExchangeAlgebra.Accounting.Consolidation` — validates consolidation adjustments and preserves worksheet provenance. ### Conversion and assistance These modules convert external postings safely and expose deterministic account-selection guidance. -- `ExchangeAlgebra.Convert` — converts pure side, account-name, and amount data to and from algebra terms.-- `ExchangeAlgebra.Convert.Csv` — reads a fixed journal CSV schema into normalized external postings.-- `ExchangeAlgebra.Accounting.PostingPolicy` — which coordinates may be posted in which processing context (`ProcessingContext`, `postingAllowedIn`).-- `ExchangeAlgebra.Convert.Checked` — validates externally generated entries before constructing journal values.-- `ExchangeAlgebra.Assist` — provides LLM-facing account metadata, suggestions, and validation explanations.-- `ExchangeAlgebra.Assist.Descriptions` — preserves the compatibility projection of canonical account descriptions.+- `ExchangeAlgebra.IO.Input` — converts side, account-name, and amount data,+ and validates externally generated entries before constructing journals.+- `ExchangeAlgebra.IO.Input.Csv` — reads a fixed journal CSV schema into normalized external postings.+- `ExchangeAlgebra.IO.Input.Assist` — provides LLM-facing account metadata, suggestions, and validation explanations. ### Optimization These modules define a common solver interface and two concrete stochastic strategies. -- `ExchangeAlgebra.Optimize` — defines the strategy-agnostic, monadic `Solver` interface.-- `ExchangeAlgebra.Optimize.Annealing` — implements simulated annealing over arbitrary candidate types.-- `ExchangeAlgebra.Optimize.GA` — implements a real-coded genetic algorithm over numeric vectors.+- `ExchangeAlgebra.Simulation.Optimize` — defines the strategy-agnostic, monadic `Solver` interface.+- `ExchangeAlgebra.Simulation.Optimize.Annealing` — implements simulated annealing over arbitrary candidate types.+- `ExchangeAlgebra.Simulation.Optimize.GA` — implements a real-coded genetic algorithm over numeric vectors. -Dependencies point downwards in this list; spill writing, checked reading and ledger restoration live in `Simulate.Spill`, which `Simulate`, `Write` and `Simulate.Policy` all import (the historical `Write` → `Simulate` and `Policy` → `Write` edges are gone).+Spill writing, checked reading, and ledger restoration live in `Simulate.Spill`.+Import `Simulation.Output` for simulation writers; these functions are separate+from the root bookkeeping API. Importing both `ExchangeAlgebra` and `ExchangeAlgebra.Journal` unqualified causes name collisions on `sigma`, `fromList`, `map`, `filter`, and friends. See the recommended import@@ -231,7 +270,7 @@ ## Choosing a value type The value parameter `v` in `Alg v b` / `Journal n v b` is selectable. Pick per-workload. The bare `Double` is the default; `ExchangeAlgebra.Value` adds two+workload. The bare `Double` is the default; `ExchangeAlgebra.Algebra.Value` adds two `newtype` money types — `MoneyDouble` (fast, FP) and `MoneyDecimal` (exact). | | `Double` (default) | `MoneyDouble` (`…Value`) | `MoneyDecimal` (`…Value`) |@@ -247,18 +286,20 @@ | Typical use | numeric methods (Leontief inverse, optimization), quick ABM | ABM where you want a *typed* money value distinct from raw `Double` | audited ledgers, bookkeeping, anywhere a total must be reproducible | ```haskell-import ExchangeAlgebra.Value (MoneyDecimal) -- or MoneyDouble-type Ledger = Journal Term MoneyDecimal (HatBase AccountTitles)-entry = 10.5 :@ Hat:<Cash .+ 2 :@ Not:<Sales -- numeric literals work directly+import ExchangeAlgebra++entry :: Alg MoneyDecimal (HatBase AccountTitles)+entry = 10.5 .@ Not :< Cash .+ 10.5 .@ Not :< Sales ``` -**FP vs exact, made concrete (measured).** In the bundled `sim1`, the same+**FP vs exact, made concrete (measured).** In the bundled `fixed-price-io`, the same stock value matches both types where it lands on an exactly-representable result (`stock(t=1, c=6) = 30.0` under `MoneyDouble` *and* `MoneyDecimal`), but diverges in the last ULP once FP rounding accumulates over terms (`stock(t=100, c=6)`: `767.960563480499` under `MoneyDouble` vs `767.9605634804993` under `MoneyDecimal`). The cost of that exactness is real and-was measured: in `sim2` (all-pairs purchases over a spill-to-disk ledger),+was measured: in the 200-company model (all-pairs purchases over a spill-to-disk+ledger; `fixed-price-io-spill`, and `sim2`, its `MoneyDecimal` version removed in 0.6.0.0), `MoneyDecimal` ran **~5–7× slower** in wall-clock and used **~15% more memory** than `MoneyDouble` (consistent at `EA_LASTC`/`EA_LASTTERM` of 40/30 and 80/50 — e.g. 23.3 s → 4.4 s and 1.42 GB → 1.19 GB at 80/50). The boxed `Integer`-mantissa@@ -268,16 +309,15 @@ subtraction is *signed*, so the negative intermediates that arise inside `bar` / `(.-)` are fine. (`Number.NonNegative.Double` is **not** a usable value type for this reason: its `(-)` *errors* on a negative result, which the algebra's-netting produces constantly. Its `HatVal` instance is **deprecated since 0.5.0.0-and will be removed in 0.6** — use `MoneyDouble` for a non-negative-by-convention+netting produces constantly. Its `HatVal` instance was deprecated in 0.5.0.0+and **removed in 0.6** — use `MoneyDouble` for a non-negative-by-convention FP money type instead.) **Boundary pattern (simulations).** Keep ABM parameters, input coefficients and random draws as `Double`, and convert (`realToFrac`) only where a value *enters the ledger*; convert reported stocks/profits back to `Double` for visualization. The ledger arithmetic in between is then exact. The bundled bookkeeping and-simulation examples follow this pattern; the numeric-method examples-(`ripple/*`, `CGE`) stay `Double` because they are inherently floating-point.+simulation examples follow this pattern. **Large scale: precision × memory trade-off.** Retaining every posting is `O(n)` regardless of value type; `MoneyDecimal` adds a constant factor (~2.5× per value box)@@ -292,6 +332,41 @@ `norm` / `bar` / balance. (`Integer` is intentionally not offered — it cannot represent the fractional relative prices the ABM work depends on.) +### Migrating to 0.6.0.0++`0.6.0.0` is a major (breaking) release. Update imports, transfer composition,+value instances, and plotting configuration as described here. See the+[changelog](https://hackage.haskell.org/package/exchangealgebra/changelog) for the full per-name migration tables.++- **Transfers add entries.** Transfer and closing functions return additional+ entries without rewriting the original postings. Add the returned entries+ with `(.+)`. `ExchangeAlgebra.Algebra.Transfer` and+ `ExchangeAlgebra.Journal.Transfer` replace their contents under the same+ module names; see **Removed** in the changelog.+- **Root exports are explicit.** `ExchangeAlgebra` has an explicit export+ list. Import names such as `writeTermIO` from their owning modules; see+ **Breaking changes without a compatibility shim** in the changelog.+- **Accounting names moved.** `ExchangeAlgebra.Algebra`,+ `ExchangeAlgebra.Algebra.Base`, `ExchangeAlgebra.Algebra.Internal`, and+ `ExchangeAlgebra.Journal` no longer re-export accounting names. Import their+ accounting owners, or use `import ExchangeAlgebra`, which still provides+ all these accounting names.+- **Module moves.** Deprecated compatibility modules remain in 0.6 and are+ removed in 0.7.0.0. See **Deprecated** in the changelog for the new paths.+- **Exact variants are merged.** The `*Exact` functions and+ `ExchangeAlgebra.Algebra.Exact` and `ExchangeAlgebra.Journal.Exact` modules+ are removed. Ordinary functions provide the same readouts; for example,+ replace `normExact` with `norm`, which returns the value directly.+- **Text instead of String.** Public API fields carrying human-readable text+ now use `Text`. Enable `OverloadedStrings` or pass text with `Data.Text.pack`;+ see the changelog for the affected fields and the exceptions.+- **Plotting is opt-in.** The `visualize` flag is off by default. Enable it to+ use `ExchangeAlgebra.Simulate.Visualize` as described in **Installation**.+- **Custom `HatVal` instances.** Implement `valueError`, `Accum`, all eight+ accumulator methods, and `roundRational`. Missing methods fail at run time.+ See the **Custom `HatVal` instances implement** item in the changelog for+ the signatures, helper functions, and laws.+ ### Migrating to 0.5.0.0 `0.5.0.0` is a major (breaking) release. Most users need no changes — `Double`@@ -307,14 +382,12 @@ `fromList`-built journal byte-for-byte, the same-(note,base) sequence order may differ. Switch such ledgers to `MoneyDecimal` for order-independent results, or compare via `norm` / `bar` / `balanceBy` rather than raw structure.-- **Abstract `Alg` / `Journal` / `TransTable`**: pattern-match `Alg` on `Zero`+- **Abstract `Alg` / `Journal`**: pattern-match `Alg` on `Zero` and `(:@)` only and build values with `(.@)`, `(.+)`, `fromList`, `mkJournal`,- `(.|)` and `table`. Code that constructed `Liner` directly must import+ and `(.|)`. Code that constructed `Liner` directly must import `ExchangeAlgebra.Algebra.Internal` (the exposed `Alg` representation module,- which carries no stability guarantee). The `Journal` and `TransTable`- constructors are not exported anywhere:- rebuild such values with `mkJournal` / `(.|)` / `fromList` and `table` /- `(.->)` / `(|%)`.+ which carries no stability guarantee). Rebuild journals with `mkJournal`,+ `(.|)`, or `fromList`. - **Spill files are validated**: `readBinarySpillFile` and `restoreJournalFromBinarySpill` now raise on a truncated, stale or gapped file instead of merging a partial ledger; use the `...Checked` variants for an@@ -325,32 +398,39 @@ ## Recommended import patterns -`Alg`, `Journal` and `TransTable` are abstract in 0.5.0.0: construct them with-`(.@)`, `(.+)`, `fromList`, `mkJournal`, `(.|)` and `table`, and match `Alg` on-`Zero` / `(:@)` only. The multi-posting representation and its cache fields live-in the exposed `ExchangeAlgebra.Algebra.Internal` module, which carries no-stability guarantee.+`Alg` and `Journal` are abstract: construct them with `(.@)`, `(.+)`,+`fromList`, `mkJournal`, and `(.|)`, and match `Alg` on `Zero` / `(:@)` only.+The multi-posting representation and its cache fields live in the exposed+`ExchangeAlgebra.Algebra.Internal` module, which carries no stability guarantee. ### Simple single-period bookkeeping ```haskell-import ExchangeAlgebra -- Algebra-layer umbrella+import ExchangeAlgebra +main :: IO () main = do- let e = 100 :@ Hat :< Cash .+ 100 :@ Not :< Sales- putStr (showBS e)+ let entry = 100 .@ Not :< Cash .+ 100 .@ Not :< Sales+ :: Alg MoneyDecimal (HatBase AccountTitles)+ context = jcciSecondGradeContext Standalone+ case present context BeforeClosing entry of+ Left issues -> print issues+ Right statements ->+ print (financialStatementRows [CurrentAssetsSection, RevenueSection] statements) ``` ### Journal-based work (multi-period, notes, simulation) ```haskell-import ExchangeAlgebra.Journal -- pulls in the type classes and the Journal API+import ExchangeAlgebra.Journal+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange import qualified ExchangeAlgebra.Algebra as EA import qualified ExchangeAlgebra.Journal as EJ-import qualified ExchangeAlgebra.Journal.Transfer as EJT+import qualified ExchangeAlgebra.Accounting.Transfer as Transfer import qualified ExchangeAlgebra.Simulate as ES import ExchangeAlgebra.Simulate -- unqualified, for StateSpace, Updatable, etc.-import ExchangeAlgebra.Write -- writeBS / writeIOMatrix and friends+import ExchangeAlgebra.Simulation.Output -- writeTermIO / writeIOMatrix ``` Even in Journal-centric code you will frequently reach into the Algebra layer (for the `Alg`@@ -359,8 +439,8 @@ ### Modules to import directly (not re-exported by the umbrellas) -`ExchangeAlgebra.Bookkeeping`, `ExchangeAlgebra.Simulate.Lite`,-`ExchangeAlgebra.Simulate.Network`, and `ExchangeAlgebra.Simulate.Policy` are **by design+`ExchangeAlgebra.Accounting.Entries`, `ExchangeAlgebra.Simulate.Lite`,+`ExchangeAlgebra.Simulation.Network`, and `ExchangeAlgebra.Simulate.Policy` are **by design imported directly** rather than re-exported from the `ExchangeAlgebra` umbrella. They introduce names that would otherwise collide with the Algebra layer (or with one another), so the package keeps them off the umbrella and expects an explicit (usually qualified)@@ -368,7 +448,7 @@ ```haskell import ExchangeAlgebra-import qualified ExchangeAlgebra.Bookkeeping as BK+import qualified ExchangeAlgebra.Accounting.Entries as BK import qualified ExchangeAlgebra.Simulate.Lite as Lite ``` @@ -391,7 +471,8 @@ main = do _world <- ES.runSimulationWithSpill opts gen env- -- restore spilled chunks later with ES.readBinarySpillFile / restoreJournalFromBinarySpill+ -- restore with ES.readBinarySpillFile, or import+ -- restoreJournalFromBinarySpill from ExchangeAlgebra.Simulate.Spill pure () ``` @@ -400,7 +481,7 @@ - `ES.SpillDeletePolicy` bounds resident memory: `KeepRecentTerms n` keeps a sliding window, `DeleteSpilledChunk` evicts each chunk right after it is written, `NoDelete` keeps everything. - Restore spilled data with `ES.readBinarySpillFile` (binary format) or the- `restoreJournalFromBinarySpill` helper.+ `restoreJournalFromBinarySpill` helper from `ExchangeAlgebra.Simulate.Spill`. For policy-driven long runs, `ExchangeAlgebra.Simulate.Policy` keeps `FullAudit` as the default to preserve the full audit trail, but a declared `LedgerPolicy` can opt into@@ -413,7 +494,7 @@ `LedgerPolicy` record-syntax example. A runnable end-to-end example (multi-scenario run with binary spill, `KeepRecentTerms`, and-restore) is `examples/basic/simulateEx2.hs` (the `sim2` executable).+restore) is `examples/basic/fixedPriceIOSpill.hs` (the `fixed-price-io-spill` executable). ## A note on visualization @@ -433,7 +514,7 @@ ```haskell -- Haskell side: write the simulation outputs to CSV-import ExchangeAlgebra.Write (writeIOMatrix)+import ExchangeAlgebra.Simulation.Output (writeIOMatrix) import qualified ExchangeAlgebra.Simulate.Visualize as ESV -- qualified access to writeFuncResults etc. @@ -477,15 +558,13 @@ ```bash stack build stack exec -- ebex1 # Introductory bookkeeping example-stack exec -- sim1 # 100-term simulation (+ Python visualization)-stack exec -- sim2 # spill-to-disk simulation (constant memory, binary spill + restore)-stack exec -- ripple # 10-agent ripple-effect simulation-stack exec -- cge # CGE model+stack exec -- fixed-price-io # 100-term simulation (+ Python visualization)+stack exec -- fixed-price-io-spill # spill-to-disk simulation (constant memory, binary spill + restore) ``` ## Documentation -- Haddock: <https://htmlpreview.github.io/?https://raw.githubusercontent.com/yakagika/ExchangeAlgebra/master/haddock/index.html>+- Haddock: <https://hackage.haskell.org/package/exchangealgebra> - A tutorial / guided walkthrough is planned, based on an upcoming paper. ## License@@ -501,8 +580,8 @@ DOI: [10.21203/rs.3.rs-8485050/v1](https://doi.org/10.21203/rs.3.rs-8485050/v1) The ripple-effect simulations reported in this preprint are driven by- `ExchangeAlgebra.Simulate` and the ripple example family under- [`examples/deterministic/ripple/`](https://github.com/yakagika/ExchangeAlgebra/tree/master/examples/deterministic/ripple).+ `ExchangeAlgebra.Simulate` and the ripple examples preserved in tag+ [archive/research-examples-pre-p4](https://github.com/yakagika/ExchangeAlgebra/tree/archive/research-examples-pre-p4/examples). If you use this library in academic work, please cite the preprint above. A `CITATION.cff` file at the repository root provides BibTeX and
+ docs/EDINET-TAXONOMY-NOTICE.md view
@@ -0,0 +1,21 @@+# EDINETタクソノミ由来の英語表示名++V-Land 2で追加した日商簿記2級商業簿記の勘定科目について,+`AccountSpec.asNameEn`の第一根拠に金融庁EDINETタクソノミ2026年版+「勘定科目リスト」の「一般商工業」タブを使用しています.++- 公表ページ: https://www.fsa.go.jp/search/20251111.html+- 勘定科目リスト: https://www.fsa.go.jp/search/20251111/1f_AccountList.xlsx+- 利用時の著作権等に関する注意事項:+ https://www.fsa.go.jp/search/EDINET_Taxonomy_Legal_Statement.html++出典: 金融庁 EDINETタクソノミ2026年版「勘定科目リスト」一般商工業.+© Copyright 2014 Financial Services Agency, The Japanese Government.++原典Excelは本repositoryへ再配布していません. 日商簿記科目との直接一致がない表示名は,+EDINET語彙からの派生または本プロジェクトによる補完であり, 金融庁の標準ラベルそのものでは+ありません. 行単位の出所・裁定はaudit-harness repositoryの+`data/edinet/2026-general-industry-name-map.tsv`を正本とします.++Haskellの`AccountTitles` constructorは本library内部の一意な識別子です.+EDINET element IDと同じ綴りを持つ場合でも, 両者の同一性や公式な対応関係を意味しません.
+ docs/admission-api-design-notes.md view
@@ -0,0 +1,163 @@+# 受入 API の設計ノート++この文書は `ExchangeAlgebra.IO.Input.Admission` の実装判断を記録する開発者向けの設計ノートである.+受入は, 信頼できない書き手 (言語モデルなど) が書いた仕訳帳について, 会計的な整合性を取引ごとに確かめる.+仕訳の経済的な意味, 勘定の選び方の適否, 演習の正解との一致は確かめない.++## 範囲++受入が持つのは次の 2 つだけである.++| 関数 | 問い | 結果 |+|---|---|---|+| `admit` | 仕訳帳の各取引 (note) が, それ単独で会計的に整合しているか | `Either (AdmissionFailure n b) (Admitted n b)` |+| `checkEvidence` | 受入済みの仕訳帳が, 信頼側の記録 (証憑) と一致するか | `Either (NonEmpty (EvidenceMismatch m b)) ()` |++受入は仕訳を作らない. 決算整理と締めの仕訳は, 書き手が会計の関数+(`ExchangeAlgebra.Accounting.Adjustment`, `closingEntries` など) で計算して仕訳帳に含める.+期首の残高のように信頼側が与える事実も仕訳帳に含めて渡し, 信頼側の記録との一致を `checkEvidence` で確かめる.+試算表と諸表は, 受入の後に `admittedJournal` から既存の関数+(`Presentation.present`, `ExchangeAlgebra.IO.Output.Statements` の CSV 出力) で作る.+会計的な所見 (逆残高, 仮勘定の残りなど) は `trialBalanceFindings` が返し, 帳票の作成は所見で止まらない.++使ってよい勘定の一覧, 特定の勘定への直接の記帳の禁止, 損益と純資産の間の振替の禁止のような規則は,+実験や道具の側の規則であり library には置かない. 例えば売上を利益剰余金へ直接振り替える提出も受け入れられ,+それを見つけるのは正解との照合である.++## 型++`admit` は note `n` と基底 `b` について多相で, 値は `MoneyDecimal` に固定する.++```haskell+admit :: (Note n, ExBaseClass b)+ => Journal n MoneyDecimal b+ -> Either (AdmissionFailure n b) (Admitted n b)+```++値を固定するのは, 金額の厳密な和と, 証憑との値の等号比較を, 有限の 10 進数で行うためである.+`TxKey = TxKey EntityId PeriodId TxId` は既定の note の型で, 会社・期間・取引を分ける.+他の `Note` の型も使える.++`Admitted` と `AdmissionFailure` の構築子は非公開で, `admit` だけが作る.+`Admitted` は「取引ごとの整合性を確かめた仕訳帳」であることを型で表す印で,+`checkEvidence` と利用者の関数が引数の型で検査済みを要求できるようにする.+中身は `admittedJournal` で取り出し, 既存の関数にそのまま渡せる. 印のほかに機構は持たない.+どちらの型にも, 中身から値を作り直す instance (`Generic`, `Read`, `Binary` など) を実装しない.+保証は `unsafeCoerce` と内部 module を使わないことを前提とする. EA の module は Safe Haskell ではない.++## admit の検査++各 note の各 posting について次を確かめる.++| 検査 | 違反 | 理由 |+|---|---|---|+| 値が正 | `NonPositiveAmount n b v` | posting の値は非負で, 0 の項は台帳に置かない |+| 側が `Hat` か `Not` | `WildcardSide n b` | 台帳の項の Hat / Not は借方か貸方かを決める値である. wildcard の `HatNot` は問い合わせにだけ使う |+| 勘定が記帳できる | `NotPostableAccount n b` | どの処理でも記帳できない勘定 (wildcard の `AccountTitle`) を拒む |++さらに, `WildcardSide` と `NotPostableAccount` の無い note について, 借方の合計と貸方の合計が厳密に等しいことを確かめる+(`Imbalanced n debit credit`). 借方・貸方は `decL` (Debit) と `decR` (Credit) で決め, Hat / Not の符号では決めない.+現金売上 `100 .@ Not :< Cash .+ 100 .@ Not :< Sales` は, 借方が Cash, 貸方が Sales で釣り合う.+和は厳密に累算し, 報告する値だけを 1 回丸める.+`HatNot` の項に `decL` を掛けると例外になり, wildcard の勘定の区分を問うても例外になるので,+これらを含む note では貸借を比べない.++`(.@)` は `HatNot` と負の値と有限でない値を拒むので, 公開 API で作った仕訳帳では値と側の検査は通常は落ちない.+それでも検査を残すのは, 費用が小さく, 内部の構築子で作った値も同じ型で届くからである.++違反は全部集めて返し, 最初の 1 件で止めない. 並びは note の昇順, note の中は posting の基底の昇順で,+`Imbalanced` はその note の最後に置く. 成功したときは仕訳帳を変えずに `Admitted` に入れる.+note を足しも消しもせず, 同じ基底の項をまとめず, `bar` を掛けない.++検査しないもの:++- 取引の key の重複. 同じ note の元を足すと仕訳帳の中で 1 つの元にまとまるので, 仕訳帳には重複を区別する情報が残らない.+ 重複は仕訳帳を作る前の変換 (JSON の読み込みなど) で確かめる. 既存の `checkedJournalIn` も仕訳帳を作る前に `DuplicateTxId` を検出する.+- 空の取引. 仕訳帳の正規形は値 0 の note を持たない.+- 処理によって変わる記帳の権限 (締めでだけ記帳できる勘定など). 受入は処理を知らない.+- 取引をまたぐ残高, 締めの有無, 証憑との一致 (`checkEvidence` で確かめる), 正解との一致.++法則:++- `admit j == Right a` ならば `admittedJournal a == j`.+- note が重ならない `j1`, `j2` について, `admit j1` と `admit j2` が成功すれば `admit (j1 .+ j2)` も成功する.+- `admit Zero` は成功する.++## checkEvidence の照合++```haskell+checkEvidence+ :: (Note m, HatBaseClass b)+ => Admitted n b+ -> (Journal n MoneyDecimal b -> Journal m MoneyDecimal b)+ -> Journal m MoneyDecimal b+ -> Either (NonEmpty (EvidenceMismatch m b)) ()+```++証憑 (銀行の明細, 現金の実査, 信頼側が与える期首の残高など) は, 期待する元の仕訳帳で渡す.+まず受入済みの仕訳帳に選び方 `f` を掛ける. `f` は射影, 振替, `bar`, note の付け替えなど,+利用者が要るだけ選んでまとめる関数である. `checkEvidence` 自身は集約せず, `bar` を呼ばない.++期待する posting 1 つ (note `m`, 基底 `b`, 値 `v`) を, `f` の結果への問い合わせとして片方向に照合する.+期待の元が pattern, `f` の結果の元が台帳の entry である.++- note: `plank` の note は全ての note に当たる. それ以外の note は自分自身にだけ当たる.+- 基底: 問い合わせの向きで照合する. 期待の基底の wildcard の軸 (勘定の `AccountTitle` など) は, その軸のどの値にも当たる.+ 対称の `.==` は使わない.+- 側: 期待の基底の側は `Hat` か `Not` で書く (`(.@)` は `HatNot` を拒む).+ 1 つの基底の両側を 1 つの正味の値として比べたいときは, `f` で `bar` を掛け, 結果の側を書く.++当たる posting がちょうど 1 つで値が厳密に等しければ一致である. それ以外は次の不一致になる.++| 当たる posting | 結果 |+|---|---|+| 0 個 | `EvidenceMissing m b v` |+| 1 個で値が違う | `EvidenceDiffers m b expected found` |+| 2 個以上 | `EvidenceAmbiguous m b values` (`f` で 1 つにまとめる. 同じ基底の両側なら `bar`, 別の勘定なら振替) |++posting は 1 つずつ数える. 同じ基底の seq の各項は別の posting で, 1 つにしたければ `f` で `bar` を掛ける.+期待側に同じ note と基底の posting が複数あれば, それぞれを独立の期待として照合する.+どの期待にも当たらない `f` の結果の元は無視する. 証憑は「あるべきもの」を述べ, 仕訳帳の全体を述べない.+全ての期待を照合し, 不一致は期待の note と基底の昇順で全部返す.++例: `f` の結果が `"sale"` の現金 100 だけのとき, 期待が `"sale"` の現金 100 なら一致,+`plank` の note の現金 100 でも一致, `"rent"` の現金 100 なら `EvidenceMissing` になる.++法則:++- `checkEvidence a f Zero == Right ()`.+- `e` の各 posting が, 上の片方向の照合で `admittedJournal a` のちょうど 1 つの posting に当たり, 値が等しければ+ `checkEvidence a id e == Right ()`. 前提の「ちょうど 1 つ」は外せない. 例えば `plank` の note の現金 100 と+ `"sale"` の現金 200 を持つ仕訳帳で, 現金だけを射影した元を期待にすると, `plank` の期待が 2 つに当たり+ `EvidenceAmbiguous` になる.+- 期待ごとの検査なので, `checkEvidence a f (e1 .+ e2)` が成功するのは `checkEvidence a f e1` と `checkEvidence a f e2` が+ ともに成功するときに限る.++## module の配置++| module | 公開 | 内容 |+|---|---|---|+| `ExchangeAlgebra.IO.Input.Admission` | 公開 | 入口. 取引の key, `admit`, `checkEvidence` と診断の型 |+| `...Admission.Diagnostic` | 非公開 | `AdmissionError`, `AdmissionFailure`, `EvidenceMismatch` |+| `...Admission.Representation` | 非公開 | `Admitted` と `admittedJournal` |+| `...Admission.Engine` | 非公開 | `admit` |+| `...Admission.Evidence` | 非公開 | `checkEvidence` |+| `ExchangeAlgebra.Accounting.Transaction` | 公開 | `TxKey`, `EntityId`, `PeriodId`, `TxId`, `Entry`, `isBlankKey` |+| `ExchangeAlgebra.Accounting.Equivalence` | 公開 | 取引の map の同値判定 (受入とは独立) |++構築子を置く非公開 module は, `package.yaml` と生成される `exchangealgebra.cabal` の両方で library の `other-modules` に登録する.++## 同値判定++`ExchangeAlgebra.Accounting.Equivalence` は受入とは独立した関数で, 受入していない `Entry` の map にも使える.+`isEquivalentUpTo` は全ての方式でキー集合の一致を要求し, 会社・期間・取引の境界を保つ.+posting の比較は `(Hat, AccountTitles, MoneyDecimal)` を sort した多重集合で行い, `Alg` の `Eq` や仕訳帳全体の `bar` で代用しない.++| 方式 | 正規化の範囲 |+|---|---|+| `PostingMultiset` | 行の順序だけを無視する |+| `NetWithinTransaction` | 各取引の `Alg` を個別に `bar` する |+| `NetAccountsInTransactions` | 指定した取引の指定した勘定だけ `bar` し, 他は厳密に比べる |++`closingDifferences` と `isClosingEquivalent` は 2 つの締めを取引の key ごとに比べる.+利益剰余金以外の勘定は借方・貸方の側ごとの厳密な合計で, 利益剰余金は貸方から借方を引いた厳密な正味で比べる.
docs/migration-0.5-derived-metrics.md view
@@ -1,5 +1,9 @@ # Derived metrics in 0.5.0.0 +This document describes derived metrics in 0.5.0.0 and uses the 0.6 module+names for current reporting APIs. For migration to 0.6, see+[Migrating to 0.6.0.0](../README.md#migrating-to-0600).+ ## Summary `NetIncome`, `NetLoss`, `GrossProfit`, and `OrdinaryProfit` remain@@ -10,8 +14,8 @@ titles. They are not canonical financial-statement lines. New reporting code should-use `ExchangeAlgebra.Reporting.Metric` and-`ExchangeAlgebra.Reporting.Presentation`:+use `ExchangeAlgebra.Accounting.Statements.Metric` and+`ExchangeAlgebra.Accounting.Statements.Presentation`: - `PeriodResultMetric` is one identity. Profit, loss, and break-even are value directions (`PeriodProfit`, `PeriodLoss`, and `PeriodBreakEven`), not three@@ -26,7 +30,7 @@ ## Legacy transfer boundary -The following functions remain for compatibility:+In 0.5.0.0, the following functions remain for compatibility: - `incomeSummaryAccount` and `netIncomeTransfer`. - `grossProfitTransfer`, `ordinaryProfitTransfer`, and@@ -39,25 +43,31 @@ not cover the full JCCI chart. They must not be used as JGAAP gross-profit or ordinary-profit definitions. -The output of `incomeSummaryAccount` contains a `NetIncome` or `NetLoss`+In 0.5.0.0, the output of `incomeSummaryAccount` contains a `NetIncome` or `NetLoss` balancing coordinate. It is an intermediate closing state and cannot be sent-directly to financial-statement presentation. Finish the legacy closing-pipeline, or derive the result from the before-closing trial balance with-`periodResultOf`.+directly to financial-statement presentation. In 0.6.0.0, these legacy transfer+functions are removed. Add the entries returned by+`ExchangeAlgebra.Accounting.Closing.closingEntries` to the original ledger+with `(.+)` to close it. To read the period result without closing, use+`periodResultOf BeforeClosing trialBalance` (from+`ExchangeAlgebra.Accounting.Statements.Metric`, with `BeforeClosing` from+`ExchangeAlgebra.Accounting.TrialBalance`). The 0.6 API receives the stage and+trial balance directly. Inspect `trialBalanceFindings` separately for accounting+findings. ## Checked and unchecked input -`EngineGeneratedOnly` is enforced by `ExchangeAlgebra.Convert.Checked`.-Use its `OrdinaryJournal` context for external or LLM-originated postings.+`EngineGeneratedOnly` is enforced by `ExchangeAlgebra.IO.Input`.+Use `checkedEntry` or `checkedJournal`, which apply the `OrdinaryJournal` context, for external or LLM-originated postings. -The legacy unchecked paths, including `ExchangeAlgebra.Convert.Csv`,+The legacy unchecked paths, including `ExchangeAlgebra.IO.Input.Csv`, `journalFromSides`, and direct algebra construction, intentionally remain unchecked in 0.5.0.0. They can still construct the four legacy coordinates. This is compatibility behavior, not authorization for ordinary posting. ## Serialization -The retained ordinals and Word16 big-endian encodings are:+The 0.5.0.0 ordinals and Word16 big-endian encodings are: | Constructor | Ordinal | Bytes | |---|---:|---:|@@ -67,6 +77,9 @@ | `NetLoss` | 64 | `0040` | | `IncomeSummary` | 216 | `00d8` | -The test suite pins these values directly. Physical removal of the four legacy-derived coordinates is deferred to a separately planned major-version-migration, no earlier than 0.6.0.0.+All four derived coordinates remain `AccountTitles` constructors in 0.6.0.0.+The 0.6.0.0 encoding uses fixed two-byte big-endian codes with the high bit set:+`NetIncome` is `8031`, `GrossProfit` is `8036`, `OrdinaryProfit` is `8037`,+`NetLoss` is `8040`, and `IncomeSummary` is `80d8`. The 0.5.x bytes are not+accepted by the 0.6 decoder; export them with their original library version+and encode them again with 0.6. See the changelog's serialization changes.
exchangealgebra.cabal view
@@ -5,9 +5,9 @@ -- see: https://github.com/sol/hpack name: exchangealgebra-version: 0.5.3.2+version: 0.6.0.0 synopsis: Exchange Algebra for bookkeeping and economic simulation-description: Please see the README on GitHub at <https://github.com/yakagika/ExchangeAlgebra#readme>+description: Exchange Algebra is an algebraic description of bookkeeping developed by Hiroshi Deguchi. This package represents bookkeeping entries as elements of a redundant algebra, where increases and decreases are expressed by Hat and Not bases rather than by sign. It provides journals with notes, accounting operations such as closing, transfers, trial balances, and financial statements, CSV input and output, and a state-space framework for economic simulation. See the README for a guide and the changelog for migration notes. category: Accounting, Finance, Math homepage: https://github.com/yakagika/ExchangeAlgebra#readme bug-reports: https://github.com/yakagika/ExchangeAlgebra/issues@@ -21,74 +21,177 @@ README.md ChangeLog.md docs/migration-0.5-derived-metrics.md+ docs/EDINET-TAXONOMY-NOTICE.md+ test/fixtures/account-algebra-behavior/presentation.tsv+ test/fixtures/account-algebra-behavior/projection-membership.tsv+ test/fixtures/account-algebra-behavior/semantics.tsv test/fixtures/account-semantics-050/account-info.tsv- test/fixtures/account-semantics-050/consumer-inventory.txt test/fixtures/account-semantics-050/metadata.tsv test/fixtures/account-semantics-050/README.md test/fixtures/account-semantics-050/suggest.tsv+ test/fixtures/admission-baseline-p1/equivalence.tsv+ test/fixtures/admission-baseline-p1/README.md test/fixtures/consolidation-worksheet-050/minimal.tsv test/fixtures/consolidation-worksheet-050/README.md+ test/fixtures/example-numbers-p1/README.md+ test/fixtures/example-numbers-p1/ripple-analysis.tsv+ test/fixtures/export-surface-p2/counts.tsv+ test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.PostingPolicy.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.JcciAliases.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Registry.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Types.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Element.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Internal.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.Rule.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.Descriptions.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Bookkeeping.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Consolidation.Worksheet.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Checked.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Csv.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.IO.Input.Admission.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.Rule.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.Annealing.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.GA.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Posting.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Bookkeeping.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Csv.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Simulation.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Group.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Metric.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Presentation.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Analysis.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Engine.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Lite.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Network.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Policy.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Random.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Spill.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Visualize.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Balance.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Validation.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.txt+ test/fixtures/export-surface-p2/ExchangeAlgebra.Value.txt+ test/fixtures/export-surface-p2/orphans.txt+ test/fixtures/export-surface-p2/README.md test/fixtures/jcci-2022/queries.tsv test/fixtures/jcci-2022/README.md test/fixtures/jcci-2022/source.tsv- test/fixtures/post-land2/README.md- test/fixtures/post-land2/suggest.tsv test/fixtures/post-vocab/account-info.tsv test/fixtures/post-vocab/ordinals.tsv test/fixtures/post-vocab/README.md test/fixtures/post-vocab/semantics.tsv test/fixtures/post-vocab/suggest.tsv- test/fixtures/pre-account-semantics-050/account-info.tsv- test/fixtures/pre-account-semantics-050/consumer-inventory.txt- test/fixtures/pre-account-semantics-050/presentation.tsv- test/fixtures/pre-account-semantics-050/projection-membership.tsv- test/fixtures/pre-account-semantics-050/README.md- test/fixtures/pre-account-semantics-050/semantics.tsv- test/fixtures/pre-land1/account-info.tsv- test/fixtures/pre-land1/account-semantics.tsv- test/fixtures/pre-land1/alias-resolution.tsv- test/fixtures/pre-land1/suggest.tsv- test/fixtures/pre-land2/presentation.txt test/fixtures/pre-vland2/ordinals.tsv- test/fixtures/pre-vland2/semantics.tsv- test/fixtures/pre-vocab/finalstock.tsv+ test/fixtures/readout-baseline-p1/alg-decimal-f-dec2.tsv+ test/fixtures/readout-baseline-p1/alg-decimal-f-int.tsv+ test/fixtures/readout-baseline-p1/alg-double-f-cancel.tsv+ test/fixtures/readout-baseline-p1/alg-double-f-dec2.tsv+ test/fixtures/readout-baseline-p1/alg-double-f-exp.tsv+ test/fixtures/readout-baseline-p1/alg-double-f-int.tsv+ test/fixtures/readout-baseline-p1/alg-double-f-sub.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-dec2.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-int.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-cancel.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-dec2.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-exp.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-int.tsv+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-sub.tsv+ test/fixtures/readout-baseline-p1/journal-decimal-f-dec2.tsv+ test/fixtures/readout-baseline-p1/journal-decimal-f-int.tsv+ test/fixtures/readout-baseline-p1/journal-double-f-cancel.tsv+ test/fixtures/readout-baseline-p1/journal-double-f-dec2.tsv+ test/fixtures/readout-baseline-p1/journal-double-f-exp.tsv+ test/fixtures/readout-baseline-p1/journal-double-f-int.tsv+ test/fixtures/readout-baseline-p1/journal-double-f-sub.tsv+ test/fixtures/readout-baseline-p1/README.md+ test/fixtures/registry-aliases/alias-resolution.tsv+ test/fixtures/statement-presentation/presentation.txt+ test/fixtures/wire-format-p2/decode-boundaries.tsv+ test/fixtures/wire-format-p2/README.md+ test/fixtures/wire-format-p2/wire-format.tsv test/fixtures/write-rows-0510/ebex6-compound-trial-balance.tsv test/fixtures/write-rows-0510/ebex7-compound-trial-balance.tsv test/fixtures/write-rows-0510/ebex8-account-ledger-journal.tsv test/fixtures/write-rows-0510/ebex8-account-ledger.tsv test/fixtures/write-rows-0510/ebex8-compound-trial-balance.tsv test/fixtures/write-rows-0510/ebex8-journal.tsv- test/fixtures/write-rows-0510/ebex9-bs.tsv- test/fixtures/write-rows-0510/ebex9-pl.tsv test/fixtures/write-rows-0510/ebex9-post-closing-trial-balance.tsv test/fixtures/write-rows-0510/ebex9-worksheet.tsv test/fixtures/write-rows-0510/README.md+ test/compile-fail/AdmissionFailureConstructor.hs+ test/compile-fail/AlgConstructor.hs+ test/compile-fail/AlgHiddenConstructor.hs+ test/compile-fail/AlgRecordUpdate.hs+ test/compile-fail/Coerce.hs+ test/compile-fail/Constructor.hs+ test/compile-fail/DeprecatedTransfer.hs+ test/compile-fail/HiddenModule.hs+ test/compile-fail/PublicClient.hs+ test/compile-fail/PublicExplicitImportClient.hs+ test/compile-fail/RawDerivation.hs+ test/compile-fail/RecordUpdate.hs+ test/compile-fail/check.sh+ test/compat-clients/CompatInstance.hs+ test/compat-clients/InstanceMain.hs+ test/compat-clients/MixedPath.hs+ test/compat-clients/NewPath.hs+ test/compat-clients/OldPath.hs+ test/compat-clients/check.sh+ test/compat-clients/README.md+ tools/check-export-surface.py+ tools/check-orphans.py+ docs/admission-api-design-notes.md source-repository head type: git location: https://github.com/yakagika/ExchangeAlgebra flag visualize- description: Build ExchangeAlgebra.Simulate.Visualize, which needs Chart, Chart-cairo and a cairo / pango toolchain. Disable with --flag exchangealgebra:-visualize to use the accounting algebra without plotting.+ description: Build ExchangeAlgebra.Simulate.Visualize, which needs Chart, Chart-cairo and a cairo / pango toolchain. Enable with --flag exchangealgebra:visualize to write PNG charts. manual: True- default: True+ default: False library exposed-modules: ExchangeAlgebra ExchangeAlgebra.Accounting+ ExchangeAlgebra.Accounting.Account+ ExchangeAlgebra.Accounting.Closing+ ExchangeAlgebra.Accounting.Consolidation+ ExchangeAlgebra.Accounting.Entries+ ExchangeAlgebra.Accounting.Exchange ExchangeAlgebra.Accounting.PostingPolicy+ ExchangeAlgebra.Accounting.Statements+ ExchangeAlgebra.Accounting.Statements.Group+ ExchangeAlgebra.Accounting.Statements.Metric+ ExchangeAlgebra.Accounting.Statements.Presentation+ ExchangeAlgebra.Accounting.TrialBalance+ ExchangeAlgebra.Accounting.TrialBalance.Balance+ ExchangeAlgebra.Accounting.TrialBalance.Validation+ ExchangeAlgebra.Accounting.Transfer ExchangeAlgebra.Algebra ExchangeAlgebra.Algebra.Base ExchangeAlgebra.Algebra.Base.Account.JcciAliases ExchangeAlgebra.Algebra.Base.Account.Registry ExchangeAlgebra.Algebra.Base.Account.Types ExchangeAlgebra.Algebra.Base.Element- ExchangeAlgebra.Algebra.Exact+ ExchangeAlgebra.Algebra.Core+ ExchangeAlgebra.Algebra.Element ExchangeAlgebra.Algebra.Internal- ExchangeAlgebra.Algebra.Readout.Net+ ExchangeAlgebra.Algebra.Posting ExchangeAlgebra.Algebra.Transfer ExchangeAlgebra.Algebra.Transfer.Rule+ ExchangeAlgebra.Algebra.Value ExchangeAlgebra.Assist ExchangeAlgebra.Assist.Descriptions ExchangeAlgebra.Bookkeeping@@ -96,9 +199,18 @@ ExchangeAlgebra.Convert ExchangeAlgebra.Convert.Checked ExchangeAlgebra.Convert.Csv- ExchangeAlgebra.Foundation+ ExchangeAlgebra.IO.Input+ ExchangeAlgebra.IO.Input.Csv+ ExchangeAlgebra.IO.Input.Assist+ ExchangeAlgebra.IO.Output+ ExchangeAlgebra.IO.Output.Csv+ ExchangeAlgebra.IO.Output.Statements+ ExchangeAlgebra.IO.Input.Admission+ ExchangeAlgebra.Accounting.Transaction+ ExchangeAlgebra.Accounting.Adjustment+ ExchangeAlgebra.Accounting.Equivalence ExchangeAlgebra.Journal- ExchangeAlgebra.Journal.Exact+ ExchangeAlgebra.Journal.Core ExchangeAlgebra.Journal.Transfer ExchangeAlgebra.Journal.Transfer.Rule ExchangeAlgebra.Optimize@@ -113,18 +225,47 @@ ExchangeAlgebra.Reporting.Presentation ExchangeAlgebra.Simulate ExchangeAlgebra.Simulate.Analysis+ ExchangeAlgebra.Simulation.Analysis+ ExchangeAlgebra.Simulation.Optimize+ ExchangeAlgebra.Simulation.Optimize.Annealing+ ExchangeAlgebra.Simulation.Optimize.GA+ ExchangeAlgebra.Simulation.Output ExchangeAlgebra.Simulate.Engine ExchangeAlgebra.Simulate.Lite ExchangeAlgebra.Simulate.Network+ ExchangeAlgebra.Simulation.Network+ ExchangeAlgebra.Simulation.Network.Flows+ ExchangeAlgebra.Simulation.Network.Csv+ ExchangeAlgebra.Simulation.Network.Journal ExchangeAlgebra.Simulate.Policy ExchangeAlgebra.Simulate.Random ExchangeAlgebra.Simulate.Spill ExchangeAlgebra.TrialBalance.Balance ExchangeAlgebra.TrialBalance.Validation ExchangeAlgebra.Value- ExchangeAlgebra.Write other-modules:- ExchangeAlgebra.Algebra.Transfer.Closing+ ExchangeAlgebra.Accounting.TrialBalance.Accum+ ExchangeAlgebra.Accounting.Catalog+ ExchangeAlgebra.Algebra.Element.Representation+ ExchangeAlgebra.Algebra.Base.Representation+ ExchangeAlgebra.Algebra.Value.Class+ ExchangeAlgebra.Algebra.Value.Accum+ ExchangeAlgebra.Algebra.Core.Representation+ ExchangeAlgebra.Journal.Core.Representation+ ExchangeAlgebra.Accounting.Account.Title+ ExchangeAlgebra.Accounting.Account.Classification+ ExchangeAlgebra.Accounting.Account.Aliases+ ExchangeAlgebra.Accounting.Account.Registry+ ExchangeAlgebra.Accounting.Account.PostingPolicy+ ExchangeAlgebra.Algebra.Transfer.Representation+ ExchangeAlgebra.Simulation.Network.Representation+ ExchangeAlgebra.Simulation.Array+ ExchangeAlgebra.IO.Input.Conversion+ ExchangeAlgebra.IO.Input.Checked+ ExchangeAlgebra.IO.Input.Admission.Engine+ ExchangeAlgebra.IO.Input.Admission.Evidence+ ExchangeAlgebra.IO.Input.Admission.Representation+ ExchangeAlgebra.IO.Input.Admission.Diagnostic hs-source-dirs: src ghc-options: -feager-blackholing@@ -138,7 +279,6 @@ , containers >=0.6 && <0.8 , deepseq >=1.4 && <1.6 , hashable >=1.4 && <1.6- , non-negative >=0.1.2 && <0.2 , numeric-prelude >=0.4.4 && <0.5 , parallel ==3.2.* , random ==1.2.*@@ -175,7 +315,6 @@ , doctest , exchangealgebra , hashable >=1.4 && <1.6- , non-negative >=0.1.2 && <0.2 , numeric-prelude >=0.4.4 && <0.5 , parallel ==3.2.* , random ==1.2.*@@ -193,11 +332,8 @@ type: exitcode-stdio-1.0 main-is: SurfaceMain.hs other-modules:+ CompatInstance Surface.Accounting- Surface.Algebra.Exact- Surface.Algebra.Readout.Net- Surface.Foundation- Surface.Journal.Exact Surface.Render.Bookkeeping Surface.Render.Csv Surface.Render.Simulation@@ -206,6 +342,7 @@ Surface.Simulate.Random hs-source-dirs: test+ test/compat-clients ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wunused-imports build-depends: Decimal ==0.5.*@@ -217,10 +354,10 @@ , containers >=0.6 && <0.8 , deepseq >=1.4 && <1.6 , exchangealgebra- , hashable >=1.4 && <1.6- , non-negative >=0.1.2 && <0.2+ , hashable , numeric-prelude >=0.4.4 && <0.5 , parallel ==3.2.*+ , process , random ==1.2.* , scientific >=0.3.7 && <0.4 , text >=2.0 && <2.2@@ -236,14 +373,35 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ Support+ Algebra.ExactReadoutSpec+ Algebra.AccumSpec+ Algebra.CoreSpec+ Journal.JournalSpec+ Journal.NormalFormSpec+ Accounting.AdjustmentSpec+ Accounting.RegistrySpec+ IO.InputOutputSpec+ Simulate.SimulateSpec+ Simulation.NetworkSpec+ Simulation.OptimizeSpec Algebra.ExactSumSpec+ Admission.Spec Algebra.ProjWildcardSpec Golden.WriteRows+ Golden.ReadoutBaseline+ Golden.WireFormat+ Golden.AdmissionBaseline+ Golden.ExampleNumbers.RippleAnalysis+ Golden.ExampleNumbers.RippleFixture Journal.CarrySpec+ Journal.SideTotalsSpec Posting.NoNumPosted Posting.PostingSpec Posting.SettleSpec Transfer.RuleSpec+ Transfer.BalanceSpec+ Value.MoneyParseSpec hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N@@ -261,7 +419,6 @@ , exchangealgebra , hashable >=1.4 && <1.6 , mtl ==2.3.*- , non-negative >=0.1.2 && <0.2 , numeric-prelude >=0.4.4 && <0.5 , parallel ==3.2.* , random ==1.2.*
src/ExchangeAlgebra.hs view
@@ -18,81 +18,68 @@ == Quick start - This top-level module is the Algebra-layer umbrella: it re-exports- "ExchangeAlgebra.Algebra", "ExchangeAlgebra.Algebra.Transfer",- "ExchangeAlgebra.Algebra.Transfer.Rule",- "ExchangeAlgebra.Write", and "ExchangeAlgebra.Value". It is the- recommended entry point for simple single-period bookkeeping:+ This umbrella exports named selections of the generic Algebra API,+ account titles, classifications and registry metadata, accounting exchange+ operations, transfers and closing, trial-balance balances and findings,+ financial-statement presentation, CSV and bookkeeping output, and value types.+ Start with this module for single-period bookkeeping. The transfer names+ '.->', '|%', 'Relabel' and 'transferEntries' belong to+ "ExchangeAlgebra.Accounting.Transfer"; import generic coordinate rules from+ "ExchangeAlgebra.Algebra.Transfer" qualified. > import ExchangeAlgebra >- > -- A minimal exchange: 100 units of cash debited, 100 credited to sales.+ > -- A minimal exchange: cash increases by 100 (debit, 'decL') and+ > -- sales increase by 100 (credit, 'decR'). Build postings with '.@'. > entry :: Alg Double (HatBase AccountTitles)- > entry = 100 :@ Hat :< Cash .+ 100 :@ Not :< Sales+ > entry = 100 .@ Not :< Cash .+ 100 .@ Not :< Sales == Choosing the value type (@v@ in @Alg v b@) * 'Prelude.Double' — fast IEEE-754; fine for simulations and quick work.- * 'ExchangeAlgebra.Value.MoneyDouble' — same speed, but a dedicated money+ * t'ExchangeAlgebra.Algebra.Value.MoneyDouble' — same speed, but a dedicated money newtype so a ledger value cannot be confused with a raw coefficient.- * t'ExchangeAlgebra.Value.MoneyDecimal' — exact decimal; construction-order+ * t'ExchangeAlgebra.Algebra.Value.MoneyDecimal' — exact decimal; construction-order independent totals. Use for audited\/deterministic ledgers.- * @Number.NonNegative.Double@ (@NN.Double@) — __deprecated__ since- 0.5.0.0: its @(-)@ errors on negative intermediates and the instance- will be removed in 0.6. Migrate to 'ExchangeAlgebra.Value.MoneyDouble'- (or bare 'Prelude.Double'). For multi-period simulation or metadata-aware journals (notes, axes),- switch to the Journal layer. The two umbrellas export overlapping- names (@sigma@, @fromList@, @map@, @filter@, …), so Journal-centric- code should use qualified imports for the Algebra layer:-- > import ExchangeAlgebra.Journal -- umbrella + type classes- > import qualified ExchangeAlgebra.Algebra as EA- > import qualified ExchangeAlgebra.Journal as EJ- > import qualified ExchangeAlgebra.Journal.Transfer as EJT-- == Modules to import directly (not re-exported here)-- This umbrella intentionally re-exports only the Algebra-layer modules- listed above. The following modules are __designed to be imported- directly__ and are deliberately /not/ re-exported from @ExchangeAlgebra@,- because they introduce names that would collide with the Algebra layer- (or with each other) and are better used under a qualified import:+ import "ExchangeAlgebra.Journal" qualified to avoid overlapping names+ such as @sigma@, @fromList@, @map@ and @filter@: - * "ExchangeAlgebra.Bookkeeping" — double-entry adjusting\/closing entries.- * "ExchangeAlgebra.Consolidation.Worksheet" — atomic consolidation- adjustments, source provenance, and cross-statement linkage validation.- * "ExchangeAlgebra.TrialBalance.Validation" — trial-balance findings,- reclassification instructions, and the policy-controlled reporting gate.- * "ExchangeAlgebra.Reporting.Metric" — typed, read-only derived metrics- that do not add account-basis coordinates.- * "ExchangeAlgebra.Reporting.Group" — reusable gross, deduction, and net- presentation groups for contra accounts.- * "ExchangeAlgebra.Reporting.Presentation" — context-sensitive JGAAP- transformation from validated trial balances to financial statements.- * "ExchangeAlgebra.Simulate" — the classic simulation engine. /(Removed- from this umbrella in 0.5.0.0: it exports very generic names —- @copy@, @modify@, @update@, @initialize@, @normal@, … — that polluted- the bookkeeping namespace.)/- * "ExchangeAlgebra.Simulate.Lite" — the lightweight simulation front-end.- * "ExchangeAlgebra.Simulate.Network" — trade-network generators.- * "ExchangeAlgebra.Simulate.Policy" — ledger\/spill policy configuration.- * "ExchangeAlgebra.Optimize" — pluggable optimization solvers- ("ExchangeAlgebra.Optimize.Annealing" \/ "ExchangeAlgebra.Optimize.GA");- generic names (@optimize@, @Config@, ...) best used qualified.+ > {-# LANGUAGE OverloadedStrings #-}+ > import ExchangeAlgebra+ > import Data.Text (Text)+ > import qualified ExchangeAlgebra.Journal as Journal+ >+ > journal :: Journal.Journal Text Double (HatBase AccountTitles)+ > journal = (100 .@ Not :< Cash .+ 100 .@ Not :< Sales) Journal..| "sale" - Import them explicitly when you need them, e.g.:+ == Modules to import directly - > import ExchangeAlgebra- > import qualified ExchangeAlgebra.Bookkeeping as BK- > import ExchangeAlgebra.Simulate -- simulation engine- > import qualified ExchangeAlgebra.Simulate.Lite as Lite+ * "ExchangeAlgebra.Journal" — note-bearing postings and journal operations.+ * "ExchangeAlgebra.Simulation.Output" — @writeTermIO@ and @writeIOMatrix@.+ * "ExchangeAlgebra.Simulate.Spill" — spill writers, checked readers,+ @restoreJournalFromBinarySpill@ and @restoreJournalFromBinarySpillChecked@.+ * "ExchangeAlgebra.Accounting.Statements.Presentation" — detailed presentation+ configuration beyond t'ReportingContext', t'ReportingScope' and+ 'jcciSecondGradeContext'.+ * "ExchangeAlgebra.Accounting.Statements.Group" and+ "ExchangeAlgebra.Accounting.Statements.Metric" — presentation groups,+ subtotal definitions and derived metrics.+ * "ExchangeAlgebra.Accounting.Entries" — adjusting and closing entries.+ * "ExchangeAlgebra.Accounting.Consolidation" — consolidation adjustments+ and provenance validation.+ * "ExchangeAlgebra.IO.Input" and "ExchangeAlgebra.IO.Input.Admission" —+ external input conversion and admission checks.+ * "ExchangeAlgebra.Simulate.Engine", "ExchangeAlgebra.Simulate.Lite",+ "ExchangeAlgebra.Simulate.Policy" and "ExchangeAlgebra.Simulation.Network" —+ simulation protocols, ledger policies and trade networks.+ * "ExchangeAlgebra.Simulation.Optimize" — optimization solvers. == Full examples - Runnable examples (elementary bookkeeping, ripple-effect simulations,- a CGE model) live in the repository, not on Hackage:+ Runnable examples (elementary bookkeeping and fixed-price simulations) live+ in the repository, not on Hackage: <https://github.com/yakagika/ExchangeAlgebra/tree/master/examples> @@ -103,14 +90,320 @@ module ExchangeAlgebra- ( module ExchangeAlgebra.Algebra- , module ExchangeAlgebra.Algebra.Transfer- , module ExchangeAlgebra.Algebra.Transfer.Rule- , module ExchangeAlgebra.Write- , module ExchangeAlgebra.Value ) where+ ( -- * Algebra+ Hat(..)+ , HatBase(..)+ , HatBaseClass(..)+ , BaseClass(..)+ , BaseForSingleHat(..)+ , customError+ , Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic+ , Nearly(..)+ , isNearlyNum+ , Redundant(..)+ , HatVal(..)+ , ValueError(..)+ , isErrorValue+ , Rounding(..)+ , Tolerance(..)+ , defaultTolerance+ , sumValues+ , roundScaled+ , Alg(Zero, (:@), _val, _hatBase)+ , isZero+ , (.@)+ , (<@)+ , vals+ , bases+ , fromList+ , toList+ , foldEntries+ , sigma+ , sigma2When+ , sigmaFromMap+ , toASCList+ , map+ , mapPosting+ , mapMaybePosting+ , mapBasePart+ , extendBy+ , filter+ , proj+ , projNetNorm+ , balanceBy+ , balanceMapBy+ , netPairMapBy+ , foldEntriesToMap+ , decBy+ , postFromNetBy+ , unionsMerge+ -- * Accounts+ , AccountTitles(..)+ , AccountDivision(..)+ , Side(..)+ , ClosingRule(..)+ , FixedCurrent(..)+ , AccountRole(..)+ , PostingCapability(..)+ , DivisionSemantics(..)+ , HomeSideSemantics(..)+ , ReportingEligibility(..)+ , AccountSpec(..)+ , AccountSemantics(..)+ , accountAliases+ , accountSpec+ , accountSemantics+ , accountSpecMap+ , concreteAccountTitles+ , classifyAccountContra+ , accountDescriptions+ , PIMO(..)+ , switchSide+ , defaultSide+ , classifyAccountDivision+ , pimoFromDivision+ , pimoFlip+ , ExBaseClass(..)+ , AccountBase(..)+ , Exchange(..)+ , projCredit+ , projDebit+ , projByAccountTitle+ , projCurrentAssets+ , projFixedAssets+ , projDeferredAssets+ , projCurrentLiability+ , projFixedLiability+ , projCapitalStock+ , projContraAssets+ , projContra+ -- * Transfers and closing+ , TransferScale(MulBy, DivBy)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , collapseEntries+ , collapseNetEntries+ , TransferShare(..)+ , TransferEnds+ , TransferRow+ , TransferTable+ , (.->)+ , (|%)+ , mkTransferTable+ , tableRows+ , TransferTableError(..)+ , TransferError(..)+ , transferEntries+ , transferEntriesWith+ , TransferJournalError(..)+ , transferEntriesBy+ , transferEntriesByWith+ , ClosingSide(..)+ , closingSide+ , closingEntries+ , SettleRule+ , retainedEarningsRule+ , SettlementBatch+ , SignedNet+ , SettleError(..)+ , settleEntries+ , settlementSteps+ -- * Trial balance and statements+ , AccountBalance(..)+ , accountBalances+ , balanceFor+ , balanceSide+ , balanceAmount+ , TrialBalanceStage(..)+ , TrialBalanceInput(..)+ , ReclassificationRule(..)+ , TBFinding(..)+ , trialBalanceFindings+ , ReportingContext(..)+ , ReportingScope(..)+ , jcciSecondGradeContext+ , FinancialStatements(..)+ , StatementSection(..)+ , StatementLine(..)+ , PresentationIssue(..)+ , present+ -- * Output+ , writeCSV+ , csvTranspose+ , journalRows+ , writeJournal+ , accountLedgerRows+ , writeAccountOf+ , accountLedgerRowsJournal+ , writeAccountOfJournal+ , compoundTrialBalanceRows+ , writeCompoundTrialBalance+ , worksheetRows+ , writeWorksheet+ , postClosingTrialBalanceRows+ , writePostClosingTrialBalance+ , StatementRowsError(..)+ , financialStatementRows+ , writeFinancialStatements+ -- * Values+ , MoneyDecimal(..)+ , toDecimal+ , bankersRound+ , ceilingRound+ , MoneyParseError(..)+ , moneyDecimalFromText+ , moneyDecimalFromScientific+ , MoneyDouble(..)+ , toDouble+ , RationalAccum(..)+ , rationalAddAccum+ , rationalRoundAccum+ ) where -import ExchangeAlgebra.Algebra.Transfer.Rule-import ExchangeAlgebra.Algebra-import ExchangeAlgebra.Algebra.Transfer-import ExchangeAlgebra.Write-import ExchangeAlgebra.Value -- MoneyDouble / MoneyDecimal value types++import Prelude ()+import ExchangeAlgebra.Algebra+import ExchangeAlgebra.Accounting.Account+ ( AccountTitles(..)+ , AccountDivision(..)+ , Side(..)+ , ClosingRule(..)+ , FixedCurrent(..)+ , AccountRole(..)+ , PostingCapability(..)+ , DivisionSemantics(..)+ , HomeSideSemantics(..)+ , ReportingEligibility(..)+ , AccountSpec(..)+ , AccountSemantics(..)+ , accountAliases+ , accountSpec+ , accountSemantics+ , accountSpecMap+ , concreteAccountTitles+ , classifyAccountContra+ , accountDescriptions+ , PIMO(..)+ , switchSide+ , defaultSide+ , classifyAccountDivision+ , pimoFromDivision+ , pimoFlip+ )+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass(..)+ , AccountBase(..)+ , Exchange(..)+ , projCredit+ , projDebit+ , projByAccountTitle+ , projCurrentAssets+ , projFixedAssets+ , projDeferredAssets+ , projCurrentLiability+ , projFixedLiability+ , projCapitalStock+ , projContraAssets+ , projContra+ )+-- Generic per-posting rules. The accounting transfer below owns Relabel and+-- transferEntries in this module, so they are not imported from here.+import ExchangeAlgebra.Algebra.Transfer.Representation+ ( TransferScale(MulBy, DivBy)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , collapseEntries+ , collapseNetEntries+ )+import ExchangeAlgebra.Accounting.Transfer+import ExchangeAlgebra.Accounting.Closing+ ( ClosingSide(..)+ , closingSide+ , closingEntries+ , SettleRule+ , retainedEarningsRule+ , SettlementBatch+ , SignedNet+ , SettleError(..)+ , settleEntries+ , settlementSteps+ )+import ExchangeAlgebra.Accounting.TrialBalance.Balance+ ( AccountBalance(..)+ , accountBalances+ , balanceFor+ , balanceSide+ , balanceAmount+ )+import ExchangeAlgebra.Accounting.TrialBalance.Validation+ ( TrialBalanceStage(..)+ , TrialBalanceInput(..)+ , ReclassificationRule(..)+ , TBFinding(..)+ , trialBalanceFindings+ )+import ExchangeAlgebra.Accounting.Statements.Presentation+ ( ReportingContext(..)+ , ReportingScope(..)+ , jcciSecondGradeContext+ , FinancialStatements(..)+ , StatementSection(..)+ , StatementLine(..)+ , PresentationIssue(..)+ , present+ )+import ExchangeAlgebra.IO.Output.Csv (writeCSV, csvTranspose)+import ExchangeAlgebra.IO.Output.Statements+ ( journalRows+ , writeJournal+ , accountLedgerRows+ , writeAccountOf+ , accountLedgerRowsJournal+ , writeAccountOfJournal+ , compoundTrialBalanceRows+ , writeCompoundTrialBalance+ , worksheetRows+ , writeWorksheet+ , postClosingTrialBalanceRows+ , writePostClosingTrialBalance+ , StatementRowsError(..)+ , financialStatementRows+ , writeFinancialStatements+ )+import ExchangeAlgebra.Algebra.Value+ ( MoneyDecimal(..)+ , toDecimal+ , bankersRound+ , ceilingRound+ , MoneyParseError(..)+ , moneyDecimalFromText+ , moneyDecimalFromScientific+ , MoneyDouble(..)+ , toDouble+ , RationalAccum(..)+ , rationalAddAccum+ , rationalRoundAccum+ )
src/ExchangeAlgebra/Accounting.hs view
@@ -5,32 +5,30 @@ Released under the OWL license - The accounting layer on top of "ExchangeAlgebra.Foundation": the names+ The accounting layer on top of "ExchangeAlgebra.Algebra": the names that realize Definitions 7-9 of Akagi (2026), Appendix A. * Definition 7 (exchange base class): account titles, account divisions, sides, and 'ExBaseClass', whose 'whichSide' places a posting on its- home side for 'Not' and on the opposite side for v'Hat' (the L\/R+ home side for 'ExchangeAlgebra.Algebra.Base.Not' and on the opposite side for+ v'ExchangeAlgebra.Algebra.Base.Hat' (the L\/R convention follows Deguchi & Nakano 1986, Definition 2.16: 'decL' is the debit part, 'decR' the credit part). * Definition 8 (exchange algebra): the 'Exchange' decomposition ('decL' \/ 'decR' \/ 'decP' \/ 'decM') and the account-aware projections.- * Definition 9 (transfer): the legacy 'TransTable' triple and 'transfer',- plus data-defined rules and additive 'transferEntries' from- "ExchangeAlgebra.Algebra.Transfer.Rule".+ * Definition 9 (transfer): the accounting transfer of+ "ExchangeAlgebra.Accounting.Transfer" (tables written with '.->' and+ '|%', additive 'transferEntries' from exact balances), plus coordinate+ rules and closing entries. Import generic per-posting @transferEntries@+ and @Relabel@ from "ExchangeAlgebra.Algebra.Transfer" qualified. - Everything is re-exported item by item from- "ExchangeAlgebra.Algebra.Base.Element", "ExchangeAlgebra.Algebra.Base.Account.Types",- "ExchangeAlgebra.Algebra.Base", "ExchangeAlgebra.Algebra.Internal" and- "ExchangeAlgebra.Algebra.Transfer" and- "ExchangeAlgebra.Algebra.Transfer.Rule"; nothing is defined here. The umbrella- is the Definition 7-9 core only: the account registry- ("ExchangeAlgebra.Algebra.Base.Account.Registry"), the posting policy- ("ExchangeAlgebra.Accounting.PostingPolicy"), trial-balance readouts- ("ExchangeAlgebra.TrialBalance.Balance") and the named closing transfers- of "ExchangeAlgebra.Algebra.Transfer" keep their own modules.+ This umbrella re-exports account vocabulary, exchange operations,+ coordinate rules, closing entries, and accounting transfers. It defines+ no operations. The account registry ("ExchangeAlgebra.Accounting.Account"),+ trial-balance readouts ("ExchangeAlgebra.Accounting.TrialBalance.Balance"),+ and statement modules are imported separately. - >>> import ExchangeAlgebra.Foundation+ >>> import ExchangeAlgebra.Algebra >>> import ExchangeAlgebra.Accounting >>> let x = 100 .@ Not :< Cash .+ 100 .@ Not :< Sales :: Alg Double (HatBase AccountTitles) >>> (norm (decL x), norm (decR x), balance x)@@ -64,37 +62,30 @@ , projCapitalStock , projContraAssets , projContra- -- * Definition 9: transfer- , TransTable- , TransTableParts- , Size- , isNullTable- , table- , (.->)- , (|%)- , createTransfer- , transfer+ -- * Definition 9: coordinate rules and closing , module ExchangeAlgebra.Algebra.Transfer.Rule+ -- * Definition 9: accounting transfer+ , module ExchangeAlgebra.Accounting.Transfer ) where -import ExchangeAlgebra.Algebra.Base.Element (AccountTitles(..))-import ExchangeAlgebra.Algebra.Base.Account.Types+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Account.Classification ( AccountDivision(..) , Side(..) , FixedCurrent(..) )-import ExchangeAlgebra.Algebra.Base+import ExchangeAlgebra.Accounting.Account ( PIMO(..)- , ExBaseClass(..)- , AccountBase(..) , switchSide , defaultSide , classifyAccountDivision , pimoFromDivision , pimoFlip )-import ExchangeAlgebra.Algebra.Internal- ( Exchange(..)+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass(..)+ , AccountBase(..)+ , Exchange(..) , projCredit , projDebit , projByAccountTitle@@ -107,16 +98,32 @@ , projContraAssets , projContra )-import ExchangeAlgebra.Algebra.Transfer- ( TransTable- , TransTableParts- , Size- , isNullTable- , table- , (.->)- , (|%)- , createTransfer- , transfer- )+import ExchangeAlgebra.Accounting.Transfer -import ExchangeAlgebra.Algebra.Transfer.Rule+import ExchangeAlgebra.Algebra.Transfer.Representation as ExchangeAlgebra.Algebra.Transfer.Rule+ ( TransferScale(MulBy, DivBy)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , collapseEntries+ , collapseNetEntries+ )++import ExchangeAlgebra.Accounting.Closing as ExchangeAlgebra.Algebra.Transfer.Rule+ ( ClosingSide(..)+ , closingSide+ , closingEntries+ , SettleRule+ , retainedEarningsRule+ , SettlementBatch+ , SignedNet+ , SettleError(..)+ , settleEntries+ , settlementSteps+ )
+ src/ExchangeAlgebra/Accounting/Account.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}++-- | Account title metadata and direction helpers for exchange bases (Definition 7).+module ExchangeAlgebra.Accounting.Account+ ( AccountTitles(..)+ , AccountDivision(..), Side(..), ClosingRule(..), FixedCurrent(..)+ , AccountRole(..), PostingCapability(..), DivisionSemantics(..)+ , HomeSideSemantics(..), ReportingEligibility(..)+ , AccountSpec(..), AccountSemantics(..), accountAliases, accountSpec+ , accountSemantics, accountSpecMap, concreteAccountTitles+ , classifyAccountContra, accountDescriptions, jcciAliases+ , ProcessingContext(..), postingAllowedIn, postingCapabilityFor+ , PIMO(..), switchSide, defaultSide, classifyAccountDivision+ , pimoFromDivision, pimoFlip+ ) where++import ExchangeAlgebra.Algebra.Base.Representation (customError)+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Account.Classification+import ExchangeAlgebra.Accounting.Account.Registry+import ExchangeAlgebra.Accounting.Account.Aliases (jcciAliases)+import ExchangeAlgebra.Accounting.Account.PostingPolicy+import GHC.Stack (HasCallStack)+import Control.DeepSeq (NFData(..))++-- | Reverse the credit/debit side. Swaps Credit and Debit.+-- The wildcard Side is returned unchanged.+--+-- Complexity: O(1)+{-# INLINE switchSide #-}+switchSide :: Side -> Side+switchSide Credit = Debit+switchSide Debit = Credit+switchSide Side = Side++-- | Default (home) side of an account division before any contra reversal:+-- Assets\/Cost are debit-normal, Liability\/Equity\/Revenue are credit-normal.+-- The actual home side of a base is this, reversed when+-- 'ExchangeAlgebra.Accounting.Exchange.isContra' holds+-- (contract: @isContra b == (homeSide of b \/= defaultSide (whatDiv b))@).+--+-- Complexity: O(1)+{-# INLINE defaultSide #-}+defaultSide :: AccountDivision -> Side+defaultSide Assets = Debit+defaultSide Cost = Debit+defaultSide Liability = Credit+defaultSide Equity = Credit+defaultSide Revenue = Credit++-- | Classify an account title into an account division (Assets/Equity/Liability/Cost/Revenue).+--+-- Complexity: O(1)+{-# INLINE classifyAccountDivision #-}+classifyAccountDivision :: HasCallStack => AccountTitles -> AccountDivision+classifyAccountDivision AccountTitle = customError "this is wildcard AccountTitle"+classifyAccountDivision title =+ case accountSpec title of+ Just spec -> asDivision spec+ Nothing -> customError "this is wildcard AccountTitle"++-- | PIMO direction. In Proposition 5.3.8 (Deguchi 2004, pp.89-91) PS, IN,+-- MS and OUT mean __plus stock, input, minus stock and output__ —+-- directions of exchange, not statement labels. The allowed exchange pairs+-- are exactly PS ⇔ IN, PS ⇔ MS, OUT ⇔ IN, OUT ⇔ MS (the+-- t'ExchangeAlgebra.Accounting.Exchange.AccountBase'+-- instance below). The earlier Haddock glossed these as "Product Stock \/+-- Income \/ Money Stock \/ Outflow"; that was naming drift from the+-- original and is kept only as a mnemonic.+data PIMO = PS -- ^ plus stock (stock increase; non-contra Assets)+ | IN -- ^ input (flow in; Revenue)+ | MS -- ^ minus stock (stock decrease; Liability\/Equity and contra assets)+ | OUT -- ^ output (flow out; Cost)+ deriving (Ord, Show, Eq)++instance NFData PIMO where+ rnf value = value `seq` ()++-- | The division-to-PIMO map of the standard interpretation (the @g@ of+-- Proposition 5.3.8 restricted to non-contra accounts): Assets are plus+-- stock, Liability\/Equity are minus stock, Cost is output, Revenue is+-- input. Contra accounts flip this via 'pimoFlip' (see+-- 'ExchangeAlgebra.Accounting.Exchange.whatPIMO').+--+-- Complexity: O(1)+{-# INLINE pimoFromDivision #-}+pimoFromDivision :: AccountDivision -> PIMO+pimoFromDivision Assets = PS+pimoFromDivision Equity = MS+pimoFromDivision Liability = MS+pimoFromDivision Cost = OUT+pimoFromDivision Revenue = IN++-- | Direction flip used for contra accounts: PS ↔ MS, IN ↔ OUT.+-- Self-inverse, and it preserves the exchange relation:+-- @x \<=\> y@ implies @pimoFlip x \<=\> pimoFlip y@.+--+-- Complexity: O(1)+{-# INLINE pimoFlip #-}+pimoFlip :: PIMO -> PIMO+pimoFlip PS = MS+pimoFlip MS = PS+pimoFlip IN = OUT+pimoFlip OUT = IN
+ src/ExchangeAlgebra/Accounting/Account/Aliases.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-deprecations #-}++-- Generated by tools/gen-jcci-aliases.py. Do not edit by hand.+module ExchangeAlgebra.Accounting.Account.Aliases+ ( jcciAliases ) where++import Data.Text (Text)+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))++-- | Return the JCCI account-name aliases for a title.+jcciAliases :: AccountTitles -> [Text]+jcciAliases AccountsPayable = ["〇〇商店", "買掛金"]+jcciAliases AccountsReceivable = ["売掛金", "〇〇商店"]+jcciAliases AccruedConsumptionTax = ["未払金", "未払消費税"]+jcciAliases AccruedCorporateIncomeTaxes = ["未払法人税等"]+jcciAliases AccruedExpenses = ["未払費用", "未払利息"]+jcciAliases AccruedRevenue = ["未収収益", "未収家賃"]+jcciAliases AccumulatedDepreciation = ["建物減価償却累計額", "減価償却累計額", "備品減価償却累計額", "車両運搬具減価償却累計額", "車両減価償却累計額"]+jcciAliases AdditionalIncomeTaxesForPriorPeriods = ["追徴法人税等"]+jcciAliases AdvancesPaid = ["前払金", "前渡金"]+jcciAliases AdvancesReceived = ["前受金", "契約負債"]+jcciAliases AdvertisingExpense = ["広告宣伝費", "広告費", "広告料", "宣伝費"]+jcciAliases AffiliateStocks = ["関係会社株式", "関連会社株式"]+jcciAliases AllowanceForBonuses = ["賞与引当金", "役員賞与引当金"]+jcciAliases AllowanceForDoubtfulAccounts = ["貸倒引当金"]+jcciAliases AllowanceForProductWarranties = ["商品保証引当金", "製品保証引当金"]+jcciAliases AllowanceForRepairs = ["修繕引当金", "特別修繕引当金"]+jcciAliases AllowanceForRetirementBenefits = ["退職給付引当金"]+jcciAliases AmortizationOfGoodwill = ["のれん償却"]+jcciAliases AmortizationOfPatents = ["特許権償却"]+jcciAliases AmortizationOfSoftware = ["ソフトウェア償却"]+jcciAliases AvailableForSaleSecurities = ["投資有価証券", "その他有価証券"]+jcciAliases BadDebtLoss = ["貸倒損失"]+jcciAliases BankOverdraft = ["借入金", "当座借越"]+jcciAliases Bonuses = ["賞与", "役員賞与"]+jcciAliases BonusesPayable = ["未払賞与", "未払役員賞与", "未払賞与金"]+jcciAliases BranchCurrentAccount = ["支店"]+jcciAliases Building = ["建物"]+jcciAliases BusinessCommencementExpenses = ["開業費"]+jcciAliases BusinessTrip = ["旅費交通費", "旅費", "交通費"]+jcciAliases CapitalStock = ["資本金"]+jcciAliases CapitalSurplus = ["資本剰余金"]+jcciAliases Cash = ["現金"]+jcciAliases CashOverShort = ["現金過不足"]+jcciAliases CommunicationExpenses = ["通信費"]+jcciAliases Commutation = ["通信費"]+jcciAliases ConstructionFundReserve = ["新築積立金"]+jcciAliases ConstructionInProgress = ["建設仮勘定", "建設仮", "建設前渡金", "建設仮払金"]+jcciAliases ConsumptionTaxPaid = ["仮払金", "仮払消費税"]+jcciAliases ConsumptionTaxReceived = ["仮受金", "仮受消費税"]+jcciAliases ConsumptionTaxRefundReceivable = ["未収還付消費税", "未収消費税"]+jcciAliases ContraAccountForGuaranteeObligations = ["保証債務見返"]+jcciAliases ContractAssets = ["契約資産"]+jcciAliases CorporateIncomeTaxes = ["法人税、住民税及び事業税", "法人税等"]+jcciAliases CostOfServices = ["役務原価", "営業費用"]+jcciAliases CreditCardReceivable = ["クレジット売掛金"]+jcciAliases CurrentDeposits = ["当座預金", "当座預金○○銀行"]+jcciAliases DeferredTaxAssets = ["繰延税金資産"]+jcciAliases DeferredTaxLiabilities = ["繰延税金負債"]+jcciAliases DeliveryExpenses = ["発送費", "支払運賃", "発送運賃"]+jcciAliases Deposits = ["普通預金", "銀行預金", "普通預金○○銀行"]+jcciAliases DepositsInSpecialAccounts = ["銀行預金", "別段預金"]+jcciAliases DepositsReceived = ["預り金", "従業員預り金", "所得税預り金", "住民税預り金", "社会保険料預り金"]+jcciAliases DepositsReceivedFromOfficers = ["役員預り金"]+jcciAliases Depreciation = ["減価償却費", "建物減価償却費", "備品減価償却費"]+jcciAliases DevelopmentExpenses = ["開発費"]+jcciAliases DishonoredNotesReceivable = ["不渡手形"]+jcciAliases DividendEqualizationReserve = ["配当平均積立金"]+jcciAliases DividendsReceived = ["受取配当金"]+jcciAliases EarnedSurplus = ["利益剰余金"]+jcciAliases ElectronicallyRecordedObligations = ["電子記録債務"]+jcciAliases ElectronicallyRecordedReceivable = ["電子記録債権"]+jcciAliases Fixtures = ["備品"]+jcciAliases ForeignExchangeGains = ["為替差損益", "為替差益"]+jcciAliases ForeignExchangeLosses = ["為替差損益", "為替差損"]+jcciAliases GainOnBargainPurchase = ["負ののれん発生益"]+jcciAliases GainOnConstructionGrants = ["工事負担金受贈益"]+jcciAliases GainOnDonationOfFixedAssets = ["固定資産受贈益"]+jcciAliases GainOnNationalSubsidies = ["国庫補助金受贈益"]+jcciAliases GainOnSalesOfFixedAssets = ["固定資産売却益", "備品売却益", "土地売却益", "建物売却益"]+jcciAliases GainOnSalesOfInvestmentSecurities = ["投資有価証券売却益", "その他有価証券売却益"]+jcciAliases GainOnSalesOfSecurities = ["有価証券売却益", "有価証券売買益", "有価証券運用益"]+jcciAliases GainOnValuationOfSecurities = ["有価証券運用益", "有価証券評価益", "有価証券評価損益", "有価証券運用損益"]+jcciAliases GeneralReserve = ["別途積立金"]+jcciAliases GiftCertificatesReceived = ["受取商品券"]+jcciAliases Goodwill = ["のれん"]+jcciAliases GuaranteeDepositsReceived = ["預り保証金", "受入保証金"]+jcciAliases GuaranteeObligations = ["保証債務"]+jcciAliases HeadOfficeCurrentAccount = ["本店"]+jcciAliases HeldToMaturityBonds = ["満期保有目的債券", "投資有価証券"]+jcciAliases IncomeSummary = ["損益"]+jcciAliases IncomeTaxesAdjustment = ["法人税等調整額"]+jcciAliases IncomeTaxesRefundReceivable = ["未収還付法人税等"]+jcciAliases IncorporationExpenses = ["創立費"]+jcciAliases InsuranceExpense = ["保険料", "支払保険料", "火災保険料"]+jcciAliases InsuranceGain = ["保険差益"]+jcciAliases InterestEarned = ["受取利息"]+jcciAliases InterestExpense = ["支払利息"]+jcciAliases InterestOnSecurities = ["有価証券利息", "受取社債利息"]+jcciAliases InventoryShrinkageLoss = ["棚卸減耗損", "棚卸減耗費"]+jcciAliases InvestmentSecurities = ["投資有価証券"]+jcciAliases Land = ["土地"]+jcciAliases LandRentPaid = ["地代家賃", "支払賃借料", "支払不動産賃借料", "支払地代"]+jcciAliases LandRentReceived = ["受取地代"]+jcciAliases LeaseExpenses = ["支払リース料"]+jcciAliases LeaseObligations = ["リース債務"]+jcciAliases LeasedAssets = ["リース資産"]+jcciAliases LeaseholdRights = ["借地権"]+jcciAliases LegalCapitalSurplus = ["資本準備金", "株式払込剰余金"]+jcciAliases LegalRetainedEarnings = ["利益準備金"]+jcciAliases LoansPayable = ["借入金", "銀行借入金", "役員借入金"]+jcciAliases LoansReceivable = ["貸付金", "従業員貸付金", "役員貸付金"]+jcciAliases LongTermLoansPayable = ["長期借入金"]+jcciAliases LongTermLoansReceivable = ["長期貸付金"]+jcciAliases LongTermOtherPayables = ["長期未払金"]+jcciAliases LongTermPrepaidExpenses = ["長期前払費用"]+jcciAliases LossOnFire = ["火災損失", "災害損失"]+jcciAliases LossOnReductionOfFixedAssets = ["固定資産圧縮損", "備品圧縮損", "建物圧縮損", "機械装置圧縮損"]+jcciAliases LossOnRetirementOfFixedAssets = ["固定資産除却損", "固定資産廃棄損", "備品除却損", "建物除却損", "除却損"]+jcciAliases LossOnSalesOfElectronicallyRecordedReceivables = ["電子記録債権売却損"]+jcciAliases LossOnSalesOfFixedAssets = ["固定資産売却損", "備品売却損", "建物売却損", "土地売却損"]+jcciAliases LossOnSalesOfInvestmentSecurities = ["投資有価証券売却損", "その他有価証券売却損"]+jcciAliases LossOnSalesOfNotesReceivable = ["手形売却損"]+jcciAliases LossOnSalesOfReceivables = ["債権売却損"]+jcciAliases LossOnSalesOfSecurities = ["有価証券売却損", "有価証券売買損", "有価証券運用損"]+jcciAliases LossOnValuationOfMerchandise = ["商品評価損", "棚卸評価損"]+jcciAliases LossOnValuationOfSecurities = ["有価証券運用損", "有価証券評価損", "有価証券評価損益", "有価証券運用損益"]+jcciAliases Machinery = ["機械装置", "機械"]+jcciAliases MaintenanceExpenses = ["保守費", "維持費", "支払メンテナンス料"]+jcciAliases MembershipFees = ["諸会費"]+jcciAliases MerchandiseInventory = ["繰越商品"]+jcciAliases MiscellaneousExpenses = ["雑費"]+jcciAliases MiscellaneousIncome = ["雑益", "雑収入", "雑収益"]+jcciAliases MiscellaneousLoss = ["雑損", "雑損失"]+jcciAliases NetDefinedBenefitAsset = ["退職給付に係る資産"]+jcciAliases NetDefinedBenefitLiability = ["退職給付に係る負債"]+jcciAliases NetIncomeAttributableToNCI = ["非支配株主に帰属する当期純利益"]+jcciAliases NetLossAttributableToNCI = ["非支配株主に帰属する当期純損失"]+jcciAliases NonControllingInterests = ["非支配株主持分"]+jcciAliases NonOperatingElectronicallyRecordedObligations = ["営業外電子記録債務"]+jcciAliases NonOperatingElectronicallyRecordedReceivable = ["営業外電子記録債権"]+jcciAliases NonOperatingNotesPayable = ["営業外支払手形"]+jcciAliases NonOperatingNotesReceivable = ["営業外受取手形"]+jcciAliases NotesLoansPayable = ["借入金", "手形借入金"]+jcciAliases NotesLoansReceivable = ["貸付金", "手形貸付金"]+jcciAliases NotesPayable = ["支払手形"]+jcciAliases NotesReceivable = ["受取手形"]+jcciAliases OperatingExpenses = ["営業費用"]+jcciAliases OperatingRevenue = ["営業収益"]+jcciAliases OtherCapitalSurplus = ["その他資本剰余金"]+jcciAliases OtherPayables = ["未払金"]+jcciAliases OtherReceivables = ["未収入金", "未収金"]+jcciAliases Patent = ["特許権"]+jcciAliases PaymentFees = ["支払手数料", "販売手数料"]+jcciAliases PaymentsOnBehalf = ["立替金", "従業員立替金"]+jcciAliases PettyCash = ["小口現金"]+jcciAliases PrepaidCorporateIncomeTaxes = ["仮払金", "仮払法人税等"]+jcciAliases PrepaidExpenses = ["前払費用", "前払保険料"]+jcciAliases PrepaidPensionCost = ["前払年金費用"]+jcciAliases Products = ["商品"]+jcciAliases PropertyTaxPayable = ["未払固定資産税"]+jcciAliases ProvisionForBonuses = ["賞与引当金繰入", "役員賞与引当金繰入"]+jcciAliases ProvisionForDoubtfulAccounts = ["貸倒引当金繰入", "貸倒引当金繰入額"]+jcciAliases ProvisionForProductWarranties = ["商品保証引当金繰入", "製品保証引当金繰入"]+jcciAliases ProvisionForRepairs = ["修繕引当金繰入", "特別修繕引当金繰入"]+jcciAliases PurchaseRebates = ["仕入割戻"]+jcciAliases Purchases = ["仕入"]+jcciAliases ReceiptFee = ["受取手数料"]+jcciAliases RecoveryOfBadDebts = ["償却債権取立益"]+jcciAliases RefundLiabilities = ["返金負債"]+jcciAliases RefundOfIncomeTaxes = ["還付法人税等"]+jcciAliases RentExpense = ["支払家賃", "地代家賃", "支払賃借料", "支払不動産賃借料"]+jcciAliases RentalIncome = ["受取家賃"]+jcciAliases RepairFundReserve = ["修繕積立金"]+jcciAliases RepairsExpense = ["修繕費", "支払修繕料", "修理費"]+jcciAliases ResearchAndDevelopmentExpenses = ["研究開発費"]+jcciAliases RetainedEarnings = ["繰越利益剰余金"]+jcciAliases RetirementBenefitExpenses = ["退職給付費用", "退職給付引当金繰入", "退職給付引当損"]+jcciAliases ReversalOfAllowanceForDoubtfulAccounts = ["貸倒引当金戻入", "貸倒引当金戻入益"]+jcciAliases ReversalOfAllowanceForProductWarranties = ["商品保証引当金戻入", "製品保証引当金戻入"]+jcciAliases ReversalOfAllowanceForRepairs = ["修繕引当金戻入"]+jcciAliases Sales = ["売上"]+jcciAliases SalesCost = ["売上原価"]+jcciAliases SalesRebates = ["売上割戻"]+jcciAliases Securities = ["有価証券"]+jcciAliases SecurityDepositsPaid = ["差入保証金"]+jcciAliases ServiceRevenue = ["役務収益", "営業収益"]+jcciAliases ShortTermLoansPayable = ["短期借入金"]+jcciAliases ShortTermLoansReceivable = ["短期貸付金"]+jcciAliases Software = ["ソフトウェア"]+jcciAliases SoftwareInProgress = ["ソフトウェア仮勘定"]+jcciAliases StatutoryWelfareExpenses = ["法定福利費", "社会保険料"]+jcciAliases StockIssuanceCosts = ["株式交付費"]+jcciAliases StockSubscriptionDeposits = ["株式申込証拠金", "申込証拠金", "新株式申込証拠金"]+jcciAliases StorageExpenses = ["保管費", "保管料", "倉庫料"]+jcciAliases Structures = ["構築物"]+jcciAliases SubsidiaryStocks = ["子会社株式", "関係会社株式"]+jcciAliases SuppliesExpenses = ["消耗品費", "事務用消耗品費"]+jcciAliases SuppliesOnHand = ["貯蔵品"]+jcciAliases SuspenseAccount = ["未決算", "火災未決算", "保険未決算"]+jcciAliases SuspensePayments = ["仮払金"]+jcciAliases SuspenseReceipts = ["仮受金"]+jcciAliases TaxesExpense = ["租税公課", "公租公課", "固定資産税", "印紙税"]+jcciAliases TimeDeposits = ["銀行預金", "定期預金", "定期預金○○銀行"]+jcciAliases ToolsAndInstruments = ["工具器具"]+jcciAliases Trademark = ["商標権"]+jcciAliases TradingSecurities = ["売買目的有価証券", "有価証券"]+jcciAliases UnearnedRevenue = ["前受収益", "前受地代"]+jcciAliases UnpaidDividends = ["未払配当金", "未払株主配当金"]+jcciAliases UtilitiesExpense = ["水道光熱費", "光熱水費"]+jcciAliases ValuationDifferenceOnOtherSecurities = ["その他有価証券評価差額金"]+jcciAliases Vehicle = ["車両運搬具", "車両", "運搬具"]+jcciAliases WageExpenditure = ["給料", "給料手当", "賃金給料"]+jcciAliases WelfareExpenses = ["福利厚生費"]+jcciAliases WorkInProcess = ["仕掛品"]+jcciAliases _ = []
+ src/ExchangeAlgebra/Accounting/Account/Classification.hs view
@@ -0,0 +1,138 @@+{- |+Module : ExchangeAlgebra.Accounting.Account.Classification+Description : Account metadata types shared by Base and the account registry.+-}+module ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..)+ , Side(..)+ , ClosingRule(..)+ , FixedCurrent(..)+ , AccountRole(..)+ , PostingCapability(..)+ , DivisionSemantics(..)+ , HomeSideSemantics(..)+ , ReportingEligibility(..)+ ) where++import Control.DeepSeq (NFData(..))++-- | Account division (financial-statement classification). The+-- 'ExchangeAlgebra.Accounting.Exchange.AccountBase' correspondence instance+-- lives in "ExchangeAlgebra.Accounting.Exchange" (the class's home module).+data AccountDivision = Assets -- ^ Assets+ | Equity -- ^ Equity+ | Liability -- ^ Liability+ | Cost -- ^ Cost+ | Revenue -- ^ Revenue+ deriving (Ord, Show, Eq)++instance NFData AccountDivision where+ rnf value = value `seq` ()++-- | Credit/debit distinction. v'Side' is the wildcard used by legacy APIs.+data Side = Credit -- ^ Credit side.+ | Debit -- ^ Debit side.+ | Side -- ^ Wildcard.+ deriving (Ord, Show, Eq)++instance NFData Side where+ rnf value = value `seq` ()++-- | Registry-level policy for automatic closing entries.+--+-- 'CloseByDivision' derives the transfer side from 'AccountDivision'.+-- 'NoClose' is an explicit override. Future policies may add explicit+-- keep/flip constructors without returning to an account-title case split.+data ClosingRule = CloseByDivision -- ^ Close Cost/Revenue accounts according to their division.+ | NoClose -- ^ Do not generate an automatic closing entry.+ deriving (Show, Eq)++instance NFData ClosingRule where+ rnf value = value `seq` ()++-- | Fixed/Current distinction. Used for classifying account titles as fixed or current.+data FixedCurrent = Fixed -- ^ Fixed+ | Current -- ^ Current+ | Other -- ^ Other (expenses, revenues, etc.)+ deriving (Show, Eq)++instance NFData FixedCurrent where+ rnf value = value `seq` ()++-- | Accounting role of an account-basis coordinate. Roles are not assumed to+-- be mutually exclusive; see+-- 'ExchangeAlgebra.Accounting.Account.Registry.AccountSemantics' in the+-- account registry.+data AccountRole+ = OrdinaryAccount+ | ContraAccount+ | ReciprocalAccount+ | SuspenseOrClearingAccount+ | ClosingDevice+ | AttributionAccount+ | PeriodResult+ | ReportingSubtotal+ deriving (Show, Eq)++instance NFData AccountRole where+ rnf value = value `seq` ()++-- | Context in which an account title may be used as a posting coordinate.+-- Enforcement is introduced by the checked-conversion API in a later land;+-- this type is the canonical metadata used by that gate.+data PostingCapability+ = OrdinaryPosting+ | ClosingOnly+ | ConsolidationOnly+ | EngineGeneratedOnly+ | NotPostable+ deriving (Show, Eq)++instance NFData PostingCapability where+ rnf value = value `seq` ()++-- | Meaning of the legacy five-way 'AccountDivision' value.+--+-- This separates a genuine statement classification from a bookkeeping+-- control class or an internal direction encoding. The wrapped legacy value+-- remains available for 0.4.x-compatible algebraic behavior.+data DivisionSemantics+ = StatementDivision AccountDivision+ | BookkeepingControlClass AccountDivision+ | DirectionEncoding AccountDivision+ | NoStatementDivision+ deriving (Show, Eq)++instance NFData DivisionSemantics where+ rnf value = case value of+ StatementDivision division -> rnf division+ BookkeepingControlClass division -> rnf division+ DirectionEncoding division -> rnf division+ NoStatementDivision -> ()++-- | Semantic status of an account's normal posting side.+data HomeSideSemantics+ = FixedHomeSide Side+ | ContextDependentHomeSide+ | NoFixedHomeSide+ | NoPostingSide+ deriving (Show, Eq)++instance NFData HomeSideSemantics where+ rnf value = case value of+ FixedHomeSide side -> rnf side+ ContextDependentHomeSide -> ()+ NoFixedHomeSide -> ()+ NoPostingSide -> ()++-- | Coarse reporting eligibility. Actual presentation remains a function of+-- reporting context and policy and is implemented in a later land.+data ReportingEligibility+ = StatementEligible+ | ContextualPresentation+ | DerivedPresentation+ | NotPresented+ deriving (Show, Eq)++instance NFData ReportingEligibility where+ rnf value = value `seq` ()
+ src/ExchangeAlgebra/Accounting/Account/PostingPolicy.hs view
@@ -0,0 +1,77 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.Account.PostingPolicy+Description : Accounting-domain posting authority for processing contexts.++Accounting-domain posting authority: which coordinates may be posted in which+processing context. Input adapters ("ExchangeAlgebra.Convert.Checked") and the+consolidation worksheet ("ExchangeAlgebra.Accounting.Consolidation") consume+this; it does not depend on either.++The policy has two inputs. The 'PostingCapability' of an account title is+canonical registry metadata ('accountSemantics'); the 'ProcessingContext' names+the boundary at which a posting is admitted. 'postingAllowedIn' is the closed+gate that relates the two, and 'postingCapabilityFor' is the total lookup that+maps the wildcard 'AccountTitle' (outside the metadata domain) to 'NotPostable'.+-}+module ExchangeAlgebra.Accounting.Account.PostingPolicy+ ( ProcessingContext(..)+ , postingAllowedIn+ , postingCapabilityFor+ ) where++import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSemantics(asemPostingCapability)+ , accountSemantics+ )+import ExchangeAlgebra.Accounting.Account.Classification (PostingCapability(..))++-- | Processing boundary at which generated postings are admitted.+--+-- Each non-ordinary context adds exactly one capability to+-- 'OrdinaryPosting'. This keeps closing, consolidation, and engine authority+-- separate instead of introducing one privileged "internal" bypass.+data ProcessingContext+ = OrdinaryJournal+ | ClosingProcess+ | ConsolidationWorksheet+ | EngineComputation+ deriving (Show, Eq)++-- | Whether a capability is admitted at a processing boundary.+postingAllowedIn :: ProcessingContext -> PostingCapability -> Bool+postingAllowedIn OrdinaryJournal capability = case capability of+ OrdinaryPosting -> True+ ClosingOnly -> False+ ConsolidationOnly -> False+ EngineGeneratedOnly -> False+ NotPostable -> False+postingAllowedIn ClosingProcess capability = case capability of+ OrdinaryPosting -> True+ ClosingOnly -> True+ ConsolidationOnly -> False+ EngineGeneratedOnly -> False+ NotPostable -> False+postingAllowedIn ConsolidationWorksheet capability = case capability of+ OrdinaryPosting -> True+ ClosingOnly -> False+ ConsolidationOnly -> True+ EngineGeneratedOnly -> False+ NotPostable -> False+postingAllowedIn EngineComputation capability = case capability of+ OrdinaryPosting -> True+ ClosingOnly -> False+ ConsolidationOnly -> False+ EngineGeneratedOnly -> True+ NotPostable -> False++-- | Posting capability of an account title, total over 'AccountTitles'.+--+-- Concrete titles report their registry capability+-- ('asemPostingCapability'); the wildcard 'AccountTitle' has no registry+-- semantics and is therefore 'NotPostable'.+postingCapabilityFor :: AccountTitles -> PostingCapability+postingCapabilityFor title =+ maybe NotPostable asemPostingCapability (accountSemantics title)
+ src/ExchangeAlgebra/Accounting/Account/Registry.hs view
@@ -0,0 +1,4063 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}+-- The exhaustive registry intentionally retains the deprecated Commutation+-- constructor so that legacy behavior remains byte-for-byte stable.+{-# OPTIONS_GHC -Wno-deprecations #-}++{- |+Module : ExchangeAlgebra.Accounting.Account.Registry+Description : Canonical metadata registry for concrete account titles.++The exhaustive 'accountSpec' case is the single source of truth for account+classification, fixed/current status, and bilingual descriptions. 'accountAliases'+combines its base aliases with the frozen JCCI 2022 standard/permitted-name overlay.+-}+module ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSpec(..)+ , AccountSemantics(..)+ , accountAliases+ , accountSpec+ , accountSemantics+ , accountSpecMap+ , concreteAccountTitles+ , classifyAccountContra+ , accountDescriptions+ ) where++import qualified Data.List as L+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import Data.Maybe (mapMaybe)+import Data.Text (Text)++import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..), AccountRole(..), ClosingRule(..)+ , DivisionSemantics(..), FixedCurrent(..), HomeSideSemantics(..)+ , PostingCapability(..), ReportingEligibility(..), Side(..) )+import ExchangeAlgebra.Accounting.Account.Aliases (jcciAliases)+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))++-- | All metadata attached to one concrete account title.+data AccountSpec = AccountSpec+ { asDivision :: AccountDivision+ , asClosing :: ClosingRule+ -- ^ Automatic closing policy. 'NetIncome' and 'NetLoss' are explicit+ -- 'NoClose' overrides: their division describes the P\/L presentation+ -- side, so division-derived closing would reverse their intended side.+ -- GrossProfit and OrdinaryProfit retain CloseByDivision for staged+ -- closing. Statement metrics live in+ -- "ExchangeAlgebra.Accounting.Statements.Metric".+ , asIsContra :: Bool+ , asFixedCurrent :: FixedCurrent+ , asNameEn :: Text+ , asNameJa :: Text+ -- | Annotation-free Japanese display label (清書和文表示名) used for+ -- financial-statement lines+ -- ('ExchangeAlgebra.Accounting.Statements.Presentation.presentationLabel') and LLM-facing+ -- names ('ExchangeAlgebra.Assist.safeNameJa'). Unlike 'asNameJa' it never+ -- carries notes, legacy-name+ -- remarks, or constructor identifiers; the JCCI level-2 A-column sweep in+ -- the test suite pins it against the official standard names.+ , asLabelJa :: Text+ , asDescription :: Text+ -- | Registry-local aliases only. Use 'accountAliases' when constructing a+ -- parser or UI: that function also includes the frozen JCCI overlay.+ , asAliases :: [Text]+ } deriving (Show, Eq)++-- | Processing and reporting semantics attached to a concrete account title.+--+-- This record deliberately does not replace 'AccountSpec.asDivision'. The+-- legacy division remains the exchange-algebra direction input, while this+-- layer states whether that value is a financial-statement classification,+-- a bookkeeping control class, or only an internal direction encoding.+data AccountSemantics = AccountSemantics+ { asemRoles :: [AccountRole]+ , asemPostingCapability :: PostingCapability+ , asemDivisionSemantics :: DivisionSemantics+ , asemHomeSideSemantics :: HomeSideSemantics+ , asemReportingEligibility :: ReportingEligibility+ } deriving (Show, Eq)++-- | All concrete account titles in their stable Enum order.+concreteAccountTitles :: [AccountTitles]+concreteAccountTitles = filter (/= AccountTitle) [minBound .. maxBound]++-- | Look up processing and reporting semantics for a concrete account title.+-- The wildcard 'AccountTitle' is outside the metadata domain and returns+-- 'Nothing' explicitly.+accountSemantics :: AccountTitles -> Maybe AccountSemantics+accountSemantics title = do+ spec <- accountSpec title+ pure AccountSemantics+ { asemRoles = rolesFor title spec+ , asemPostingCapability = postingFor title+ , asemDivisionSemantics = divisionFor title spec+ , asemHomeSideSemantics = homeSideFor title spec+ , asemReportingEligibility = reportingFor title+ }++rolesFor :: AccountTitles -> AccountSpec -> [AccountRole]+rolesFor title _ = case title of+ Cash -> [OrdinaryAccount]+ Deposits -> [OrdinaryAccount]+ CurrentDeposits -> [OrdinaryAccount]+ Securities -> [OrdinaryAccount]+ InvestmentSecurities -> [OrdinaryAccount]+ InvestmentInAssociate -> [OrdinaryAccount]+ LongTermNationalBonds -> [OrdinaryAccount]+ ShortTermNationalBonds -> [OrdinaryAccount]+ Products -> [OrdinaryAccount]+ Machinery -> [OrdinaryAccount]+ Building -> [OrdinaryAccount]+ Vehicle -> [OrdinaryAccount]+ StockInvestment -> [OrdinaryAccount]+ EquipmentInvestment -> [OrdinaryAccount]+ LongTermLoansReceivable -> [OrdinaryAccount]+ AccountsReceivable -> [OrdinaryAccount]+ ShortTermLoansReceivable -> [OrdinaryAccount]+ ReserveDepositReceivable -> [OrdinaryAccount]+ Gold -> [OrdinaryAccount]+ GovernmentService -> [OrdinaryAccount]+ CapitalStock -> [OrdinaryAccount]+ RetainedEarnings -> [OrdinaryAccount]+ LongTermLoansPayable -> [OrdinaryAccount]+ ShortTermLoansPayable -> [OrdinaryAccount]+ LoansPayable -> [OrdinaryAccount]+ ReserveForDepreciation -> [OrdinaryAccount]+ DepositPayable -> [OrdinaryAccount]+ LongTermNationalBondsPayable -> [OrdinaryAccount]+ ShortTermNationalBondsPayable -> [OrdinaryAccount]+ ReserveDepositPayable -> [OrdinaryAccount]+ CentralBankNotePayable -> [OrdinaryAccount]+ Depreciation -> [OrdinaryAccount]+ AmortizationExpense -> [OrdinaryAccount]+ SalesCost -> [OrdinaryAccount]+ BusinessTrip -> [OrdinaryAccount]+ Commutation -> [OrdinaryAccount]+ UtilitiesExpense -> [OrdinaryAccount]+ RentExpense -> [OrdinaryAccount]+ AdvertisingExpense -> [OrdinaryAccount]+ DeliveryExpenses -> [OrdinaryAccount]+ SuppliesExpenses -> [OrdinaryAccount]+ MiscellaneousExpenses -> [OrdinaryAccount]+ EntertainmentExpenses -> [OrdinaryAccount]+ MeetingExpenses -> [OrdinaryAccount]+ NewspaperBooksExpenses -> [OrdinaryAccount]+ WageExpenditure -> [OrdinaryAccount]+ InterestExpense -> [OrdinaryAccount]+ TaxesExpense -> [OrdinaryAccount]+ ConsumptionExpenditure -> [OrdinaryAccount]+ SubsidyExpense -> [OrdinaryAccount]+ CentralBankPaymentExpense -> [OrdinaryAccount]+ Purchases -> [OrdinaryAccount]+ NetIncome -> [PeriodResult]+ ValueAdded -> [OrdinaryAccount]+ SubsidyIncome -> [OrdinaryAccount]+ NationalBondInterestEarned -> [OrdinaryAccount]+ DepositInterestEarned -> [OrdinaryAccount]+ GrossProfit -> [ReportingSubtotal]+ OrdinaryProfit -> [ReportingSubtotal]+ InterestEarned -> [OrdinaryAccount]+ ReceiptFee -> [OrdinaryAccount]+ RentalIncome -> [OrdinaryAccount]+ WageEarned -> [OrdinaryAccount]+ TaxesRevenue -> [OrdinaryAccount]+ CentralBankPaymentIncome -> [OrdinaryAccount]+ Sales -> [OrdinaryAccount]+ EquityInEarningsOfInvestee -> [OrdinaryAccount]+ NetLoss -> [PeriodResult]+ PettyCash -> [OrdinaryAccount]+ NotesReceivable -> [OrdinaryAccount]+ ElectronicallyRecordedReceivable -> [OrdinaryAccount]+ CreditCardReceivable -> [OrdinaryAccount]+ NotesLoansReceivable -> [OrdinaryAccount]+ MerchandiseInventory -> [OrdinaryAccount]+ GoodsInTransit -> [OrdinaryAccount]+ AdvancesPaid -> [OrdinaryAccount]+ PrepaidExpenses -> [OrdinaryAccount]+ AccruedRevenue -> [OrdinaryAccount]+ OtherReceivables -> [OrdinaryAccount]+ PaymentsOnBehalf -> [OrdinaryAccount]+ SuspensePayments -> [SuspenseOrClearingAccount]+ ConsumptionTaxPaid -> [OrdinaryAccount]+ PrepaidCorporateIncomeTaxes -> [OrdinaryAccount]+ Land -> [OrdinaryAccount]+ Fixtures -> [OrdinaryAccount]+ Patent -> [OrdinaryAccount]+ Trademark -> [OrdinaryAccount]+ Software -> [OrdinaryAccount]+ CashOverShort -> [SuspenseOrClearingAccount]+ AccountsPayable -> [OrdinaryAccount]+ NotesPayable -> [OrdinaryAccount]+ ElectronicallyRecordedObligations -> [OrdinaryAccount]+ NotesLoansPayable -> [OrdinaryAccount]+ BankOverdraft -> [OrdinaryAccount]+ AdvancesReceived -> [OrdinaryAccount]+ UnearnedRevenue -> [OrdinaryAccount]+ AccruedExpenses -> [OrdinaryAccount]+ OtherPayables -> [OrdinaryAccount]+ DepositsReceived -> [OrdinaryAccount]+ SuspenseReceipts -> [SuspenseOrClearingAccount]+ ConsumptionTaxReceived -> [OrdinaryAccount]+ AccruedConsumptionTax -> [OrdinaryAccount]+ AccruedCorporateIncomeTaxes -> [OrdinaryAccount]+ UnpaidDividends -> [OrdinaryAccount]+ AllowanceForDoubtfulAccounts -> [OrdinaryAccount, ContraAccount]+ AccumulatedDepreciation -> [OrdinaryAccount, ContraAccount]+ LegalRetainedEarnings -> [OrdinaryAccount]+ CumulativeTranslationAdjustment -> [OrdinaryAccount]+ ProvisionForDoubtfulAccounts -> [OrdinaryAccount]+ BadDebtLoss -> [OrdinaryAccount]+ LossOnSalesOfFixedAssets -> [OrdinaryAccount]+ LossOnSalesOfNotesReceivable -> [OrdinaryAccount]+ PaymentFees -> [OrdinaryAccount]+ MiscellaneousLoss -> [OrdinaryAccount]+ CorporateIncomeTaxes -> [OrdinaryAccount]+ CommunicationExpenses -> [OrdinaryAccount]+ GainOnSalesOfFixedAssets -> [OrdinaryAccount]+ RecoveryOfBadDebts -> [OrdinaryAccount]+ MiscellaneousIncome -> [OrdinaryAccount]+ ReversalOfAllowanceForDoubtfulAccounts -> [OrdinaryAccount]+ TimeDeposits -> [OrdinaryAccount]+ LoansReceivable -> [OrdinaryAccount]+ GiftCertificatesReceived -> [OrdinaryAccount]+ SecurityDepositsPaid -> [OrdinaryAccount]+ SuppliesOnHand -> [OrdinaryAccount]+ RawMaterials -> [OrdinaryAccount]+ ContractAssets -> [OrdinaryAccount]+ IncomeTaxesRefundReceivable -> [OrdinaryAccount]+ WorkInProcess -> [OrdinaryAccount]+ DeferredTaxAssets -> [OrdinaryAccount]+ LeasedAssets -> [OrdinaryAccount]+ ToolsAndInstruments -> [OrdinaryAccount]+ ConstructionInProgress -> [OrdinaryAccount]+ Goodwill -> [OrdinaryAccount]+ SoftwareInProgress -> [OrdinaryAccount]+ LongTermPrepaidExpenses -> [OrdinaryAccount]+ DishonoredNotesReceivable -> [OrdinaryAccount]+ PrepaidPensionCost -> [OrdinaryAccount]+ NetDefinedBenefitAsset -> [OrdinaryAccount]+ DepositsInSpecialAccounts -> [OrdinaryAccount]+ Structures -> [OrdinaryAccount]+ LeaseholdRights -> [OrdinaryAccount]+ NonOperatingNotesReceivable -> [OrdinaryAccount]+ NonOperatingElectronicallyRecordedReceivable -> [OrdinaryAccount]+ RefundLiabilities -> [OrdinaryAccount]+ NonOperatingNotesPayable -> [OrdinaryAccount]+ NonOperatingElectronicallyRecordedObligations -> [OrdinaryAccount]+ BonusesPayable -> [OrdinaryAccount]+ AllowanceForRepairs -> [OrdinaryAccount]+ AllowanceForProductWarranties -> [OrdinaryAccount]+ AllowanceForBonuses -> [OrdinaryAccount]+ DeferredTaxLiabilities -> [OrdinaryAccount]+ LeaseObligations -> [OrdinaryAccount]+ GuaranteeDepositsReceived -> [OrdinaryAccount]+ AllowanceForRetirementBenefits -> [OrdinaryAccount]+ LongTermOtherPayables -> [OrdinaryAccount]+ NetDefinedBenefitLiability -> [OrdinaryAccount]+ StockSubscriptionDeposits -> [OrdinaryAccount]+ LegalCapitalSurplus -> [OrdinaryAccount]+ OtherCapitalSurplus -> [OrdinaryAccount]+ DividendEqualizationReserve -> [OrdinaryAccount]+ RepairFundReserve -> [OrdinaryAccount]+ ConstructionFundReserve -> [OrdinaryAccount]+ GeneralReserve -> [OrdinaryAccount]+ ValuationDifferenceOnOtherSecurities -> [OrdinaryAccount]+ NonControllingInterests -> [AttributionAccount]+ CapitalSurplus -> [OrdinaryAccount]+ EarnedSurplus -> [OrdinaryAccount]+ ServiceRevenue -> [OrdinaryAccount]+ OperatingRevenue -> [OrdinaryAccount]+ GainOnSalesOfSecurities -> [OrdinaryAccount]+ GainOnValuationOfSecurities -> [OrdinaryAccount]+ DividendsReceived -> [OrdinaryAccount]+ InterestOnSecurities -> [OrdinaryAccount]+ GainOnSalesOfInvestmentSecurities -> [OrdinaryAccount]+ InsuranceGain -> [OrdinaryAccount]+ GainOnBargainPurchase -> [OrdinaryAccount]+ ReversalOfAllowanceForRepairs -> [OrdinaryAccount]+ ReversalOfAllowanceForBonuses -> [OrdinaryAccount]+ ReversalOfAllowanceForProductWarranties -> [OrdinaryAccount]+ GainOnDonationOfFixedAssets -> [OrdinaryAccount]+ GainOnNationalSubsidies -> [OrdinaryAccount]+ GainOnConstructionGrants -> [OrdinaryAccount]+ LandRentReceived -> [OrdinaryAccount]+ SalesRebates -> [OrdinaryAccount, ContraAccount]+ SalesAllowances -> [OrdinaryAccount, ContraAccount]+ CostOfServices -> [OrdinaryAccount]+ OperatingExpenses -> [OrdinaryAccount]+ InventoryShrinkageLoss -> [OrdinaryAccount]+ LossOnValuationOfMerchandise -> [OrdinaryAccount]+ Bonuses -> [OrdinaryAccount]+ RetirementBenefitExpenses -> [OrdinaryAccount]+ ProvisionForRepairs -> [OrdinaryAccount]+ ProvisionForBonuses -> [OrdinaryAccount]+ ProvisionForProductWarranties -> [OrdinaryAccount]+ ResearchAndDevelopmentExpenses -> [OrdinaryAccount]+ AmortizationOfGoodwill -> [OrdinaryAccount]+ AmortizationOfSoftware -> [OrdinaryAccount]+ AmortizationOfPatents -> [OrdinaryAccount]+ LeaseExpenses -> [OrdinaryAccount]+ IncorporationExpenses -> [OrdinaryAccount]+ StockIssuanceCosts -> [OrdinaryAccount]+ BusinessCommencementExpenses -> [OrdinaryAccount]+ DevelopmentExpenses -> [OrdinaryAccount]+ LossOnSalesOfElectronicallyRecordedReceivables -> [OrdinaryAccount]+ LossOnSalesOfReceivables -> [OrdinaryAccount]+ LossOnSalesOfSecurities -> [OrdinaryAccount]+ LossOnValuationOfSecurities -> [OrdinaryAccount]+ LossOnSalesOfInvestmentSecurities -> [OrdinaryAccount]+ LossOnFire -> [OrdinaryAccount]+ LossOnRetirementOfFixedAssets -> [OrdinaryAccount]+ LossOnReductionOfFixedAssets -> [OrdinaryAccount]+ AdditionalIncomeTaxesForPriorPeriods -> [OrdinaryAccount]+ RefundOfIncomeTaxes -> [OrdinaryAccount, ContraAccount]+ PurchaseRebates -> [OrdinaryAccount, ContraAccount]+ PurchaseAllowances -> [OrdinaryAccount, ContraAccount]+ WelfareExpenses -> [OrdinaryAccount]+ MaintenanceExpenses -> [OrdinaryAccount]+ StatutoryWelfareExpenses -> [OrdinaryAccount]+ LandRentPaid -> [OrdinaryAccount]+ InsuranceExpense -> [OrdinaryAccount]+ RepairsExpense -> [OrdinaryAccount]+ StorageExpenses -> [OrdinaryAccount]+ MembershipFees -> [OrdinaryAccount]+ IncomeSummary -> [ClosingDevice]+ SuspenseAccount -> [SuspenseOrClearingAccount]+ ForeignExchangeGains -> [OrdinaryAccount]+ ForeignExchangeLosses -> [OrdinaryAccount]+ ContraAccountForGuaranteeObligations -> [OrdinaryAccount]+ GuaranteeObligations -> [OrdinaryAccount]+ IncomeTaxesAdjustment -> [OrdinaryAccount]+ BranchCurrentAccount -> [ReciprocalAccount]+ HeadOfficeCurrentAccount -> [ReciprocalAccount]+ NetIncomeAttributableToNCI -> [AttributionAccount, PeriodResult]+ NetLossAttributableToNCI -> [AttributionAccount, PeriodResult]+ TradingSecurities -> [OrdinaryAccount]+ HeldToMaturityBonds -> [OrdinaryAccount]+ SubsidiaryStocks -> [OrdinaryAccount]+ AffiliateStocks -> [OrdinaryAccount]+ AvailableForSaleSecurities -> [OrdinaryAccount]+ ConsumptionTaxRefundReceivable -> [OrdinaryAccount]+ PropertyTaxPayable -> [OrdinaryAccount]+ DepositsReceivedFromOfficers -> [OrdinaryAccount]+ AccountTitle -> [OrdinaryAccount]++postingFor :: AccountTitles -> PostingCapability+postingFor title = case title of+ Cash -> OrdinaryPosting+ Deposits -> OrdinaryPosting+ CurrentDeposits -> OrdinaryPosting+ Securities -> OrdinaryPosting+ InvestmentSecurities -> OrdinaryPosting+ InvestmentInAssociate -> OrdinaryPosting+ LongTermNationalBonds -> OrdinaryPosting+ ShortTermNationalBonds -> OrdinaryPosting+ Products -> OrdinaryPosting+ Machinery -> OrdinaryPosting+ Building -> OrdinaryPosting+ Vehicle -> OrdinaryPosting+ StockInvestment -> OrdinaryPosting+ EquipmentInvestment -> OrdinaryPosting+ LongTermLoansReceivable -> OrdinaryPosting+ AccountsReceivable -> OrdinaryPosting+ ShortTermLoansReceivable -> OrdinaryPosting+ ReserveDepositReceivable -> OrdinaryPosting+ Gold -> OrdinaryPosting+ GovernmentService -> OrdinaryPosting+ CapitalStock -> OrdinaryPosting+ RetainedEarnings -> OrdinaryPosting+ LongTermLoansPayable -> OrdinaryPosting+ ShortTermLoansPayable -> OrdinaryPosting+ LoansPayable -> OrdinaryPosting+ ReserveForDepreciation -> OrdinaryPosting+ DepositPayable -> OrdinaryPosting+ LongTermNationalBondsPayable -> OrdinaryPosting+ ShortTermNationalBondsPayable -> OrdinaryPosting+ ReserveDepositPayable -> OrdinaryPosting+ CentralBankNotePayable -> OrdinaryPosting+ Depreciation -> OrdinaryPosting+ AmortizationExpense -> OrdinaryPosting+ SalesCost -> OrdinaryPosting+ BusinessTrip -> OrdinaryPosting+ Commutation -> OrdinaryPosting+ UtilitiesExpense -> OrdinaryPosting+ RentExpense -> OrdinaryPosting+ AdvertisingExpense -> OrdinaryPosting+ DeliveryExpenses -> OrdinaryPosting+ SuppliesExpenses -> OrdinaryPosting+ MiscellaneousExpenses -> OrdinaryPosting+ EntertainmentExpenses -> OrdinaryPosting+ MeetingExpenses -> OrdinaryPosting+ NewspaperBooksExpenses -> OrdinaryPosting+ WageExpenditure -> OrdinaryPosting+ InterestExpense -> OrdinaryPosting+ TaxesExpense -> OrdinaryPosting+ ConsumptionExpenditure -> OrdinaryPosting+ SubsidyExpense -> OrdinaryPosting+ CentralBankPaymentExpense -> OrdinaryPosting+ Purchases -> OrdinaryPosting+ NetIncome -> EngineGeneratedOnly+ ValueAdded -> OrdinaryPosting+ SubsidyIncome -> OrdinaryPosting+ NationalBondInterestEarned -> OrdinaryPosting+ DepositInterestEarned -> OrdinaryPosting+ GrossProfit -> EngineGeneratedOnly+ OrdinaryProfit -> EngineGeneratedOnly+ InterestEarned -> OrdinaryPosting+ ReceiptFee -> OrdinaryPosting+ RentalIncome -> OrdinaryPosting+ WageEarned -> OrdinaryPosting+ TaxesRevenue -> OrdinaryPosting+ CentralBankPaymentIncome -> OrdinaryPosting+ Sales -> OrdinaryPosting+ EquityInEarningsOfInvestee -> ConsolidationOnly+ NetLoss -> EngineGeneratedOnly+ PettyCash -> OrdinaryPosting+ NotesReceivable -> OrdinaryPosting+ ElectronicallyRecordedReceivable -> OrdinaryPosting+ CreditCardReceivable -> OrdinaryPosting+ NotesLoansReceivable -> OrdinaryPosting+ MerchandiseInventory -> OrdinaryPosting+ GoodsInTransit -> OrdinaryPosting+ AdvancesPaid -> OrdinaryPosting+ PrepaidExpenses -> OrdinaryPosting+ AccruedRevenue -> OrdinaryPosting+ OtherReceivables -> OrdinaryPosting+ PaymentsOnBehalf -> OrdinaryPosting+ SuspensePayments -> OrdinaryPosting+ ConsumptionTaxPaid -> OrdinaryPosting+ PrepaidCorporateIncomeTaxes -> OrdinaryPosting+ Land -> OrdinaryPosting+ Fixtures -> OrdinaryPosting+ Patent -> OrdinaryPosting+ Trademark -> OrdinaryPosting+ Software -> OrdinaryPosting+ CashOverShort -> OrdinaryPosting+ AccountsPayable -> OrdinaryPosting+ NotesPayable -> OrdinaryPosting+ ElectronicallyRecordedObligations -> OrdinaryPosting+ NotesLoansPayable -> OrdinaryPosting+ BankOverdraft -> OrdinaryPosting+ AdvancesReceived -> OrdinaryPosting+ UnearnedRevenue -> OrdinaryPosting+ AccruedExpenses -> OrdinaryPosting+ OtherPayables -> OrdinaryPosting+ DepositsReceived -> OrdinaryPosting+ SuspenseReceipts -> OrdinaryPosting+ ConsumptionTaxReceived -> OrdinaryPosting+ AccruedConsumptionTax -> OrdinaryPosting+ AccruedCorporateIncomeTaxes -> OrdinaryPosting+ UnpaidDividends -> OrdinaryPosting+ AllowanceForDoubtfulAccounts -> OrdinaryPosting+ AccumulatedDepreciation -> OrdinaryPosting+ LegalRetainedEarnings -> OrdinaryPosting+ CumulativeTranslationAdjustment -> ConsolidationOnly+ ProvisionForDoubtfulAccounts -> OrdinaryPosting+ BadDebtLoss -> OrdinaryPosting+ LossOnSalesOfFixedAssets -> OrdinaryPosting+ LossOnSalesOfNotesReceivable -> OrdinaryPosting+ PaymentFees -> OrdinaryPosting+ MiscellaneousLoss -> OrdinaryPosting+ CorporateIncomeTaxes -> OrdinaryPosting+ CommunicationExpenses -> OrdinaryPosting+ GainOnSalesOfFixedAssets -> OrdinaryPosting+ RecoveryOfBadDebts -> OrdinaryPosting+ MiscellaneousIncome -> OrdinaryPosting+ ReversalOfAllowanceForDoubtfulAccounts -> OrdinaryPosting+ TimeDeposits -> OrdinaryPosting+ LoansReceivable -> OrdinaryPosting+ GiftCertificatesReceived -> OrdinaryPosting+ SecurityDepositsPaid -> OrdinaryPosting+ SuppliesOnHand -> OrdinaryPosting+ RawMaterials -> OrdinaryPosting+ ContractAssets -> OrdinaryPosting+ IncomeTaxesRefundReceivable -> OrdinaryPosting+ WorkInProcess -> OrdinaryPosting+ DeferredTaxAssets -> OrdinaryPosting+ LeasedAssets -> OrdinaryPosting+ ToolsAndInstruments -> OrdinaryPosting+ ConstructionInProgress -> OrdinaryPosting+ Goodwill -> OrdinaryPosting+ SoftwareInProgress -> OrdinaryPosting+ LongTermPrepaidExpenses -> OrdinaryPosting+ DishonoredNotesReceivable -> OrdinaryPosting+ PrepaidPensionCost -> OrdinaryPosting+ NetDefinedBenefitAsset -> OrdinaryPosting+ DepositsInSpecialAccounts -> OrdinaryPosting+ Structures -> OrdinaryPosting+ LeaseholdRights -> OrdinaryPosting+ NonOperatingNotesReceivable -> OrdinaryPosting+ NonOperatingElectronicallyRecordedReceivable -> OrdinaryPosting+ RefundLiabilities -> OrdinaryPosting+ NonOperatingNotesPayable -> OrdinaryPosting+ NonOperatingElectronicallyRecordedObligations -> OrdinaryPosting+ BonusesPayable -> OrdinaryPosting+ AllowanceForRepairs -> OrdinaryPosting+ AllowanceForProductWarranties -> OrdinaryPosting+ AllowanceForBonuses -> OrdinaryPosting+ DeferredTaxLiabilities -> OrdinaryPosting+ LeaseObligations -> OrdinaryPosting+ GuaranteeDepositsReceived -> OrdinaryPosting+ AllowanceForRetirementBenefits -> OrdinaryPosting+ LongTermOtherPayables -> OrdinaryPosting+ NetDefinedBenefitLiability -> OrdinaryPosting+ StockSubscriptionDeposits -> OrdinaryPosting+ LegalCapitalSurplus -> OrdinaryPosting+ OtherCapitalSurplus -> OrdinaryPosting+ DividendEqualizationReserve -> OrdinaryPosting+ RepairFundReserve -> OrdinaryPosting+ ConstructionFundReserve -> OrdinaryPosting+ GeneralReserve -> OrdinaryPosting+ ValuationDifferenceOnOtherSecurities -> OrdinaryPosting+ NonControllingInterests -> ConsolidationOnly+ CapitalSurplus -> OrdinaryPosting+ EarnedSurplus -> OrdinaryPosting+ ServiceRevenue -> OrdinaryPosting+ OperatingRevenue -> OrdinaryPosting+ GainOnSalesOfSecurities -> OrdinaryPosting+ GainOnValuationOfSecurities -> OrdinaryPosting+ DividendsReceived -> OrdinaryPosting+ InterestOnSecurities -> OrdinaryPosting+ GainOnSalesOfInvestmentSecurities -> OrdinaryPosting+ InsuranceGain -> OrdinaryPosting+ GainOnBargainPurchase -> OrdinaryPosting+ ReversalOfAllowanceForRepairs -> OrdinaryPosting+ ReversalOfAllowanceForBonuses -> OrdinaryPosting+ ReversalOfAllowanceForProductWarranties -> OrdinaryPosting+ GainOnDonationOfFixedAssets -> OrdinaryPosting+ GainOnNationalSubsidies -> OrdinaryPosting+ GainOnConstructionGrants -> OrdinaryPosting+ LandRentReceived -> OrdinaryPosting+ SalesRebates -> OrdinaryPosting+ SalesAllowances -> OrdinaryPosting+ CostOfServices -> OrdinaryPosting+ OperatingExpenses -> OrdinaryPosting+ InventoryShrinkageLoss -> OrdinaryPosting+ LossOnValuationOfMerchandise -> OrdinaryPosting+ Bonuses -> OrdinaryPosting+ RetirementBenefitExpenses -> OrdinaryPosting+ ProvisionForRepairs -> OrdinaryPosting+ ProvisionForBonuses -> OrdinaryPosting+ ProvisionForProductWarranties -> OrdinaryPosting+ ResearchAndDevelopmentExpenses -> OrdinaryPosting+ AmortizationOfGoodwill -> OrdinaryPosting+ AmortizationOfSoftware -> OrdinaryPosting+ AmortizationOfPatents -> OrdinaryPosting+ LeaseExpenses -> OrdinaryPosting+ IncorporationExpenses -> OrdinaryPosting+ StockIssuanceCosts -> OrdinaryPosting+ BusinessCommencementExpenses -> OrdinaryPosting+ DevelopmentExpenses -> OrdinaryPosting+ LossOnSalesOfElectronicallyRecordedReceivables -> OrdinaryPosting+ LossOnSalesOfReceivables -> OrdinaryPosting+ LossOnSalesOfSecurities -> OrdinaryPosting+ LossOnValuationOfSecurities -> OrdinaryPosting+ LossOnSalesOfInvestmentSecurities -> OrdinaryPosting+ LossOnFire -> OrdinaryPosting+ LossOnRetirementOfFixedAssets -> OrdinaryPosting+ LossOnReductionOfFixedAssets -> OrdinaryPosting+ AdditionalIncomeTaxesForPriorPeriods -> OrdinaryPosting+ RefundOfIncomeTaxes -> OrdinaryPosting+ PurchaseRebates -> OrdinaryPosting+ PurchaseAllowances -> OrdinaryPosting+ WelfareExpenses -> OrdinaryPosting+ MaintenanceExpenses -> OrdinaryPosting+ StatutoryWelfareExpenses -> OrdinaryPosting+ LandRentPaid -> OrdinaryPosting+ InsuranceExpense -> OrdinaryPosting+ RepairsExpense -> OrdinaryPosting+ StorageExpenses -> OrdinaryPosting+ MembershipFees -> OrdinaryPosting+ IncomeSummary -> ClosingOnly+ SuspenseAccount -> OrdinaryPosting+ ForeignExchangeGains -> OrdinaryPosting+ ForeignExchangeLosses -> OrdinaryPosting+ ContraAccountForGuaranteeObligations -> OrdinaryPosting+ GuaranteeObligations -> OrdinaryPosting+ IncomeTaxesAdjustment -> OrdinaryPosting+ BranchCurrentAccount -> OrdinaryPosting+ HeadOfficeCurrentAccount -> OrdinaryPosting+ NetIncomeAttributableToNCI -> ConsolidationOnly+ NetLossAttributableToNCI -> ConsolidationOnly+ TradingSecurities -> OrdinaryPosting+ HeldToMaturityBonds -> OrdinaryPosting+ SubsidiaryStocks -> OrdinaryPosting+ AffiliateStocks -> OrdinaryPosting+ AvailableForSaleSecurities -> OrdinaryPosting+ ConsumptionTaxRefundReceivable -> OrdinaryPosting+ PropertyTaxPayable -> OrdinaryPosting+ DepositsReceivedFromOfficers -> OrdinaryPosting+ AccountTitle -> NotPostable++divisionFor :: AccountTitles -> AccountSpec -> DivisionSemantics+divisionFor title spec = case title of+ Cash -> StatementDivision (asDivision spec)+ Deposits -> StatementDivision (asDivision spec)+ CurrentDeposits -> StatementDivision (asDivision spec)+ Securities -> StatementDivision (asDivision spec)+ InvestmentSecurities -> StatementDivision (asDivision spec)+ InvestmentInAssociate -> StatementDivision (asDivision spec)+ LongTermNationalBonds -> StatementDivision (asDivision spec)+ ShortTermNationalBonds -> StatementDivision (asDivision spec)+ Products -> StatementDivision (asDivision spec)+ Machinery -> StatementDivision (asDivision spec)+ Building -> StatementDivision (asDivision spec)+ Vehicle -> StatementDivision (asDivision spec)+ StockInvestment -> StatementDivision (asDivision spec)+ EquipmentInvestment -> StatementDivision (asDivision spec)+ LongTermLoansReceivable -> StatementDivision (asDivision spec)+ AccountsReceivable -> StatementDivision (asDivision spec)+ ShortTermLoansReceivable -> StatementDivision (asDivision spec)+ ReserveDepositReceivable -> StatementDivision (asDivision spec)+ Gold -> StatementDivision (asDivision spec)+ GovernmentService -> StatementDivision (asDivision spec)+ CapitalStock -> StatementDivision (asDivision spec)+ RetainedEarnings -> StatementDivision (asDivision spec)+ LongTermLoansPayable -> StatementDivision (asDivision spec)+ ShortTermLoansPayable -> StatementDivision (asDivision spec)+ LoansPayable -> StatementDivision (asDivision spec)+ ReserveForDepreciation -> StatementDivision (asDivision spec)+ DepositPayable -> StatementDivision (asDivision spec)+ LongTermNationalBondsPayable -> StatementDivision (asDivision spec)+ ShortTermNationalBondsPayable -> StatementDivision (asDivision spec)+ ReserveDepositPayable -> StatementDivision (asDivision spec)+ CentralBankNotePayable -> StatementDivision (asDivision spec)+ Depreciation -> StatementDivision (asDivision spec)+ AmortizationExpense -> StatementDivision (asDivision spec)+ SalesCost -> StatementDivision (asDivision spec)+ BusinessTrip -> StatementDivision (asDivision spec)+ Commutation -> StatementDivision (asDivision spec)+ UtilitiesExpense -> StatementDivision (asDivision spec)+ RentExpense -> StatementDivision (asDivision spec)+ AdvertisingExpense -> StatementDivision (asDivision spec)+ DeliveryExpenses -> StatementDivision (asDivision spec)+ SuppliesExpenses -> StatementDivision (asDivision spec)+ MiscellaneousExpenses -> StatementDivision (asDivision spec)+ EntertainmentExpenses -> StatementDivision (asDivision spec)+ MeetingExpenses -> StatementDivision (asDivision spec)+ NewspaperBooksExpenses -> StatementDivision (asDivision spec)+ WageExpenditure -> StatementDivision (asDivision spec)+ InterestExpense -> StatementDivision (asDivision spec)+ TaxesExpense -> StatementDivision (asDivision spec)+ ConsumptionExpenditure -> StatementDivision (asDivision spec)+ SubsidyExpense -> StatementDivision (asDivision spec)+ CentralBankPaymentExpense -> StatementDivision (asDivision spec)+ Purchases -> StatementDivision (asDivision spec)+ NetIncome -> DirectionEncoding (asDivision spec)+ ValueAdded -> StatementDivision (asDivision spec)+ SubsidyIncome -> StatementDivision (asDivision spec)+ NationalBondInterestEarned -> StatementDivision (asDivision spec)+ DepositInterestEarned -> StatementDivision (asDivision spec)+ GrossProfit -> DirectionEncoding (asDivision spec)+ OrdinaryProfit -> DirectionEncoding (asDivision spec)+ InterestEarned -> StatementDivision (asDivision spec)+ ReceiptFee -> StatementDivision (asDivision spec)+ RentalIncome -> StatementDivision (asDivision spec)+ WageEarned -> StatementDivision (asDivision spec)+ TaxesRevenue -> StatementDivision (asDivision spec)+ CentralBankPaymentIncome -> StatementDivision (asDivision spec)+ Sales -> StatementDivision (asDivision spec)+ EquityInEarningsOfInvestee -> StatementDivision (asDivision spec)+ NetLoss -> DirectionEncoding (asDivision spec)+ PettyCash -> StatementDivision (asDivision spec)+ NotesReceivable -> StatementDivision (asDivision spec)+ ElectronicallyRecordedReceivable -> StatementDivision (asDivision spec)+ CreditCardReceivable -> StatementDivision (asDivision spec)+ NotesLoansReceivable -> StatementDivision (asDivision spec)+ MerchandiseInventory -> StatementDivision (asDivision spec)+ GoodsInTransit -> StatementDivision (asDivision spec)+ AdvancesPaid -> StatementDivision (asDivision spec)+ PrepaidExpenses -> StatementDivision (asDivision spec)+ AccruedRevenue -> StatementDivision (asDivision spec)+ OtherReceivables -> StatementDivision (asDivision spec)+ PaymentsOnBehalf -> StatementDivision (asDivision spec)+ SuspensePayments -> BookkeepingControlClass (asDivision spec)+ ConsumptionTaxPaid -> StatementDivision (asDivision spec)+ PrepaidCorporateIncomeTaxes -> StatementDivision (asDivision spec)+ Land -> StatementDivision (asDivision spec)+ Fixtures -> StatementDivision (asDivision spec)+ Patent -> StatementDivision (asDivision spec)+ Trademark -> StatementDivision (asDivision spec)+ Software -> StatementDivision (asDivision spec)+ CashOverShort -> BookkeepingControlClass (asDivision spec)+ AccountsPayable -> StatementDivision (asDivision spec)+ NotesPayable -> StatementDivision (asDivision spec)+ ElectronicallyRecordedObligations -> StatementDivision (asDivision spec)+ NotesLoansPayable -> StatementDivision (asDivision spec)+ BankOverdraft -> StatementDivision (asDivision spec)+ AdvancesReceived -> StatementDivision (asDivision spec)+ UnearnedRevenue -> StatementDivision (asDivision spec)+ AccruedExpenses -> StatementDivision (asDivision spec)+ OtherPayables -> StatementDivision (asDivision spec)+ DepositsReceived -> StatementDivision (asDivision spec)+ SuspenseReceipts -> BookkeepingControlClass (asDivision spec)+ ConsumptionTaxReceived -> StatementDivision (asDivision spec)+ AccruedConsumptionTax -> StatementDivision (asDivision spec)+ AccruedCorporateIncomeTaxes -> StatementDivision (asDivision spec)+ UnpaidDividends -> StatementDivision (asDivision spec)+ AllowanceForDoubtfulAccounts -> StatementDivision (asDivision spec)+ AccumulatedDepreciation -> StatementDivision (asDivision spec)+ LegalRetainedEarnings -> StatementDivision (asDivision spec)+ CumulativeTranslationAdjustment -> StatementDivision (asDivision spec)+ ProvisionForDoubtfulAccounts -> StatementDivision (asDivision spec)+ BadDebtLoss -> StatementDivision (asDivision spec)+ LossOnSalesOfFixedAssets -> StatementDivision (asDivision spec)+ LossOnSalesOfNotesReceivable -> StatementDivision (asDivision spec)+ PaymentFees -> StatementDivision (asDivision spec)+ MiscellaneousLoss -> StatementDivision (asDivision spec)+ CorporateIncomeTaxes -> StatementDivision (asDivision spec)+ CommunicationExpenses -> StatementDivision (asDivision spec)+ GainOnSalesOfFixedAssets -> StatementDivision (asDivision spec)+ RecoveryOfBadDebts -> StatementDivision (asDivision spec)+ MiscellaneousIncome -> StatementDivision (asDivision spec)+ ReversalOfAllowanceForDoubtfulAccounts -> StatementDivision (asDivision spec)+ TimeDeposits -> StatementDivision (asDivision spec)+ LoansReceivable -> StatementDivision (asDivision spec)+ GiftCertificatesReceived -> StatementDivision (asDivision spec)+ SecurityDepositsPaid -> StatementDivision (asDivision spec)+ SuppliesOnHand -> StatementDivision (asDivision spec)+ RawMaterials -> StatementDivision (asDivision spec)+ ContractAssets -> StatementDivision (asDivision spec)+ IncomeTaxesRefundReceivable -> StatementDivision (asDivision spec)+ WorkInProcess -> StatementDivision (asDivision spec)+ DeferredTaxAssets -> StatementDivision (asDivision spec)+ LeasedAssets -> StatementDivision (asDivision spec)+ ToolsAndInstruments -> StatementDivision (asDivision spec)+ ConstructionInProgress -> StatementDivision (asDivision spec)+ Goodwill -> StatementDivision (asDivision spec)+ SoftwareInProgress -> StatementDivision (asDivision spec)+ LongTermPrepaidExpenses -> StatementDivision (asDivision spec)+ DishonoredNotesReceivable -> StatementDivision (asDivision spec)+ PrepaidPensionCost -> StatementDivision (asDivision spec)+ NetDefinedBenefitAsset -> StatementDivision (asDivision spec)+ DepositsInSpecialAccounts -> StatementDivision (asDivision spec)+ Structures -> StatementDivision (asDivision spec)+ LeaseholdRights -> StatementDivision (asDivision spec)+ NonOperatingNotesReceivable -> StatementDivision (asDivision spec)+ NonOperatingElectronicallyRecordedReceivable -> StatementDivision (asDivision spec)+ RefundLiabilities -> StatementDivision (asDivision spec)+ NonOperatingNotesPayable -> StatementDivision (asDivision spec)+ NonOperatingElectronicallyRecordedObligations -> StatementDivision (asDivision spec)+ BonusesPayable -> StatementDivision (asDivision spec)+ AllowanceForRepairs -> StatementDivision (asDivision spec)+ AllowanceForProductWarranties -> StatementDivision (asDivision spec)+ AllowanceForBonuses -> StatementDivision (asDivision spec)+ DeferredTaxLiabilities -> StatementDivision (asDivision spec)+ LeaseObligations -> StatementDivision (asDivision spec)+ GuaranteeDepositsReceived -> StatementDivision (asDivision spec)+ AllowanceForRetirementBenefits -> StatementDivision (asDivision spec)+ LongTermOtherPayables -> StatementDivision (asDivision spec)+ NetDefinedBenefitLiability -> StatementDivision (asDivision spec)+ StockSubscriptionDeposits -> StatementDivision (asDivision spec)+ LegalCapitalSurplus -> StatementDivision (asDivision spec)+ OtherCapitalSurplus -> StatementDivision (asDivision spec)+ DividendEqualizationReserve -> StatementDivision (asDivision spec)+ RepairFundReserve -> StatementDivision (asDivision spec)+ ConstructionFundReserve -> StatementDivision (asDivision spec)+ GeneralReserve -> StatementDivision (asDivision spec)+ ValuationDifferenceOnOtherSecurities -> StatementDivision (asDivision spec)+ NonControllingInterests -> StatementDivision (asDivision spec)+ CapitalSurplus -> StatementDivision (asDivision spec)+ EarnedSurplus -> StatementDivision (asDivision spec)+ ServiceRevenue -> StatementDivision (asDivision spec)+ OperatingRevenue -> StatementDivision (asDivision spec)+ GainOnSalesOfSecurities -> StatementDivision (asDivision spec)+ GainOnValuationOfSecurities -> StatementDivision (asDivision spec)+ DividendsReceived -> StatementDivision (asDivision spec)+ InterestOnSecurities -> StatementDivision (asDivision spec)+ GainOnSalesOfInvestmentSecurities -> StatementDivision (asDivision spec)+ InsuranceGain -> StatementDivision (asDivision spec)+ GainOnBargainPurchase -> StatementDivision (asDivision spec)+ ReversalOfAllowanceForRepairs -> StatementDivision (asDivision spec)+ ReversalOfAllowanceForBonuses -> StatementDivision (asDivision spec)+ ReversalOfAllowanceForProductWarranties -> StatementDivision (asDivision spec)+ GainOnDonationOfFixedAssets -> StatementDivision (asDivision spec)+ GainOnNationalSubsidies -> StatementDivision (asDivision spec)+ GainOnConstructionGrants -> StatementDivision (asDivision spec)+ LandRentReceived -> StatementDivision (asDivision spec)+ SalesRebates -> StatementDivision (asDivision spec)+ SalesAllowances -> StatementDivision (asDivision spec)+ CostOfServices -> StatementDivision (asDivision spec)+ OperatingExpenses -> StatementDivision (asDivision spec)+ InventoryShrinkageLoss -> StatementDivision (asDivision spec)+ LossOnValuationOfMerchandise -> StatementDivision (asDivision spec)+ Bonuses -> StatementDivision (asDivision spec)+ RetirementBenefitExpenses -> StatementDivision (asDivision spec)+ ProvisionForRepairs -> StatementDivision (asDivision spec)+ ProvisionForBonuses -> StatementDivision (asDivision spec)+ ProvisionForProductWarranties -> StatementDivision (asDivision spec)+ ResearchAndDevelopmentExpenses -> StatementDivision (asDivision spec)+ AmortizationOfGoodwill -> StatementDivision (asDivision spec)+ AmortizationOfSoftware -> StatementDivision (asDivision spec)+ AmortizationOfPatents -> StatementDivision (asDivision spec)+ LeaseExpenses -> StatementDivision (asDivision spec)+ IncorporationExpenses -> StatementDivision (asDivision spec)+ StockIssuanceCosts -> StatementDivision (asDivision spec)+ BusinessCommencementExpenses -> StatementDivision (asDivision spec)+ DevelopmentExpenses -> StatementDivision (asDivision spec)+ LossOnSalesOfElectronicallyRecordedReceivables -> StatementDivision (asDivision spec)+ LossOnSalesOfReceivables -> StatementDivision (asDivision spec)+ LossOnSalesOfSecurities -> StatementDivision (asDivision spec)+ LossOnValuationOfSecurities -> StatementDivision (asDivision spec)+ LossOnSalesOfInvestmentSecurities -> StatementDivision (asDivision spec)+ LossOnFire -> StatementDivision (asDivision spec)+ LossOnRetirementOfFixedAssets -> StatementDivision (asDivision spec)+ LossOnReductionOfFixedAssets -> StatementDivision (asDivision spec)+ AdditionalIncomeTaxesForPriorPeriods -> StatementDivision (asDivision spec)+ RefundOfIncomeTaxes -> StatementDivision (asDivision spec)+ PurchaseRebates -> StatementDivision (asDivision spec)+ PurchaseAllowances -> StatementDivision (asDivision spec)+ WelfareExpenses -> StatementDivision (asDivision spec)+ MaintenanceExpenses -> StatementDivision (asDivision spec)+ StatutoryWelfareExpenses -> StatementDivision (asDivision spec)+ LandRentPaid -> StatementDivision (asDivision spec)+ InsuranceExpense -> StatementDivision (asDivision spec)+ RepairsExpense -> StatementDivision (asDivision spec)+ StorageExpenses -> StatementDivision (asDivision spec)+ MembershipFees -> StatementDivision (asDivision spec)+ IncomeSummary -> DirectionEncoding (asDivision spec)+ SuspenseAccount -> BookkeepingControlClass (asDivision spec)+ ForeignExchangeGains -> StatementDivision (asDivision spec)+ ForeignExchangeLosses -> StatementDivision (asDivision spec)+ ContraAccountForGuaranteeObligations -> StatementDivision (asDivision spec)+ GuaranteeObligations -> StatementDivision (asDivision spec)+ IncomeTaxesAdjustment -> StatementDivision (asDivision spec)+ BranchCurrentAccount -> BookkeepingControlClass (asDivision spec)+ HeadOfficeCurrentAccount -> BookkeepingControlClass (asDivision spec)+ NetIncomeAttributableToNCI -> DirectionEncoding (asDivision spec)+ NetLossAttributableToNCI -> DirectionEncoding (asDivision spec)+ TradingSecurities -> StatementDivision (asDivision spec)+ HeldToMaturityBonds -> StatementDivision (asDivision spec)+ SubsidiaryStocks -> StatementDivision (asDivision spec)+ AffiliateStocks -> StatementDivision (asDivision spec)+ AvailableForSaleSecurities -> StatementDivision (asDivision spec)+ ConsumptionTaxRefundReceivable -> StatementDivision (asDivision spec)+ PropertyTaxPayable -> StatementDivision (asDivision spec)+ DepositsReceivedFromOfficers -> StatementDivision (asDivision spec)+ AccountTitle -> NoStatementDivision++homeSideFor :: AccountTitles -> AccountSpec -> HomeSideSemantics+homeSideFor title spec = case title of+ Cash -> FixedHomeSide (legacyHomeSide spec)+ Deposits -> FixedHomeSide (legacyHomeSide spec)+ CurrentDeposits -> FixedHomeSide (legacyHomeSide spec)+ Securities -> FixedHomeSide (legacyHomeSide spec)+ InvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)+ InvestmentInAssociate -> FixedHomeSide (legacyHomeSide spec)+ LongTermNationalBonds -> FixedHomeSide (legacyHomeSide spec)+ ShortTermNationalBonds -> FixedHomeSide (legacyHomeSide spec)+ Products -> FixedHomeSide (legacyHomeSide spec)+ Machinery -> FixedHomeSide (legacyHomeSide spec)+ Building -> FixedHomeSide (legacyHomeSide spec)+ Vehicle -> FixedHomeSide (legacyHomeSide spec)+ StockInvestment -> FixedHomeSide (legacyHomeSide spec)+ EquipmentInvestment -> FixedHomeSide (legacyHomeSide spec)+ LongTermLoansReceivable -> FixedHomeSide (legacyHomeSide spec)+ AccountsReceivable -> FixedHomeSide (legacyHomeSide spec)+ ShortTermLoansReceivable -> FixedHomeSide (legacyHomeSide spec)+ ReserveDepositReceivable -> FixedHomeSide (legacyHomeSide spec)+ Gold -> FixedHomeSide (legacyHomeSide spec)+ GovernmentService -> FixedHomeSide (legacyHomeSide spec)+ CapitalStock -> FixedHomeSide (legacyHomeSide spec)+ RetainedEarnings -> FixedHomeSide (legacyHomeSide spec)+ LongTermLoansPayable -> FixedHomeSide (legacyHomeSide spec)+ ShortTermLoansPayable -> FixedHomeSide (legacyHomeSide spec)+ LoansPayable -> FixedHomeSide (legacyHomeSide spec)+ ReserveForDepreciation -> FixedHomeSide (legacyHomeSide spec)+ DepositPayable -> FixedHomeSide (legacyHomeSide spec)+ LongTermNationalBondsPayable -> FixedHomeSide (legacyHomeSide spec)+ ShortTermNationalBondsPayable -> FixedHomeSide (legacyHomeSide spec)+ ReserveDepositPayable -> FixedHomeSide (legacyHomeSide spec)+ CentralBankNotePayable -> FixedHomeSide (legacyHomeSide spec)+ Depreciation -> FixedHomeSide (legacyHomeSide spec)+ AmortizationExpense -> FixedHomeSide (legacyHomeSide spec)+ SalesCost -> FixedHomeSide (legacyHomeSide spec)+ BusinessTrip -> FixedHomeSide (legacyHomeSide spec)+ Commutation -> FixedHomeSide (legacyHomeSide spec)+ UtilitiesExpense -> FixedHomeSide (legacyHomeSide spec)+ RentExpense -> FixedHomeSide (legacyHomeSide spec)+ AdvertisingExpense -> FixedHomeSide (legacyHomeSide spec)+ DeliveryExpenses -> FixedHomeSide (legacyHomeSide spec)+ SuppliesExpenses -> FixedHomeSide (legacyHomeSide spec)+ MiscellaneousExpenses -> FixedHomeSide (legacyHomeSide spec)+ EntertainmentExpenses -> FixedHomeSide (legacyHomeSide spec)+ MeetingExpenses -> FixedHomeSide (legacyHomeSide spec)+ NewspaperBooksExpenses -> FixedHomeSide (legacyHomeSide spec)+ WageExpenditure -> FixedHomeSide (legacyHomeSide spec)+ InterestExpense -> FixedHomeSide (legacyHomeSide spec)+ TaxesExpense -> FixedHomeSide (legacyHomeSide spec)+ ConsumptionExpenditure -> FixedHomeSide (legacyHomeSide spec)+ SubsidyExpense -> FixedHomeSide (legacyHomeSide spec)+ CentralBankPaymentExpense -> FixedHomeSide (legacyHomeSide spec)+ Purchases -> FixedHomeSide (legacyHomeSide spec)+ NetIncome -> FixedHomeSide (legacyHomeSide spec)+ ValueAdded -> FixedHomeSide (legacyHomeSide spec)+ SubsidyIncome -> FixedHomeSide (legacyHomeSide spec)+ NationalBondInterestEarned -> FixedHomeSide (legacyHomeSide spec)+ DepositInterestEarned -> FixedHomeSide (legacyHomeSide spec)+ GrossProfit -> FixedHomeSide (legacyHomeSide spec)+ OrdinaryProfit -> FixedHomeSide (legacyHomeSide spec)+ InterestEarned -> FixedHomeSide (legacyHomeSide spec)+ ReceiptFee -> FixedHomeSide (legacyHomeSide spec)+ RentalIncome -> FixedHomeSide (legacyHomeSide spec)+ WageEarned -> FixedHomeSide (legacyHomeSide spec)+ TaxesRevenue -> FixedHomeSide (legacyHomeSide spec)+ CentralBankPaymentIncome -> FixedHomeSide (legacyHomeSide spec)+ Sales -> FixedHomeSide (legacyHomeSide spec)+ EquityInEarningsOfInvestee -> FixedHomeSide (legacyHomeSide spec)+ NetLoss -> FixedHomeSide (legacyHomeSide spec)+ PettyCash -> FixedHomeSide (legacyHomeSide spec)+ NotesReceivable -> FixedHomeSide (legacyHomeSide spec)+ ElectronicallyRecordedReceivable -> FixedHomeSide (legacyHomeSide spec)+ CreditCardReceivable -> FixedHomeSide (legacyHomeSide spec)+ NotesLoansReceivable -> FixedHomeSide (legacyHomeSide spec)+ MerchandiseInventory -> FixedHomeSide (legacyHomeSide spec)+ GoodsInTransit -> FixedHomeSide (legacyHomeSide spec)+ AdvancesPaid -> FixedHomeSide (legacyHomeSide spec)+ PrepaidExpenses -> FixedHomeSide (legacyHomeSide spec)+ AccruedRevenue -> FixedHomeSide (legacyHomeSide spec)+ OtherReceivables -> FixedHomeSide (legacyHomeSide spec)+ PaymentsOnBehalf -> FixedHomeSide (legacyHomeSide spec)+ SuspensePayments -> FixedHomeSide (legacyHomeSide spec)+ ConsumptionTaxPaid -> FixedHomeSide (legacyHomeSide spec)+ PrepaidCorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)+ Land -> FixedHomeSide (legacyHomeSide spec)+ Fixtures -> FixedHomeSide (legacyHomeSide spec)+ Patent -> FixedHomeSide (legacyHomeSide spec)+ Trademark -> FixedHomeSide (legacyHomeSide spec)+ Software -> FixedHomeSide (legacyHomeSide spec)+ CashOverShort -> ContextDependentHomeSide+ AccountsPayable -> FixedHomeSide (legacyHomeSide spec)+ NotesPayable -> FixedHomeSide (legacyHomeSide spec)+ ElectronicallyRecordedObligations -> FixedHomeSide (legacyHomeSide spec)+ NotesLoansPayable -> FixedHomeSide (legacyHomeSide spec)+ BankOverdraft -> FixedHomeSide (legacyHomeSide spec)+ AdvancesReceived -> FixedHomeSide (legacyHomeSide spec)+ UnearnedRevenue -> FixedHomeSide (legacyHomeSide spec)+ AccruedExpenses -> FixedHomeSide (legacyHomeSide spec)+ OtherPayables -> FixedHomeSide (legacyHomeSide spec)+ DepositsReceived -> FixedHomeSide (legacyHomeSide spec)+ SuspenseReceipts -> FixedHomeSide (legacyHomeSide spec)+ ConsumptionTaxReceived -> FixedHomeSide (legacyHomeSide spec)+ AccruedConsumptionTax -> FixedHomeSide (legacyHomeSide spec)+ AccruedCorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)+ UnpaidDividends -> FixedHomeSide (legacyHomeSide spec)+ AllowanceForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)+ AccumulatedDepreciation -> FixedHomeSide (legacyHomeSide spec)+ LegalRetainedEarnings -> FixedHomeSide (legacyHomeSide spec)+ CumulativeTranslationAdjustment -> FixedHomeSide (legacyHomeSide spec)+ ProvisionForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)+ BadDebtLoss -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfNotesReceivable -> FixedHomeSide (legacyHomeSide spec)+ PaymentFees -> FixedHomeSide (legacyHomeSide spec)+ MiscellaneousLoss -> FixedHomeSide (legacyHomeSide spec)+ CorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)+ CommunicationExpenses -> FixedHomeSide (legacyHomeSide spec)+ GainOnSalesOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)+ RecoveryOfBadDebts -> FixedHomeSide (legacyHomeSide spec)+ MiscellaneousIncome -> FixedHomeSide (legacyHomeSide spec)+ ReversalOfAllowanceForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)+ TimeDeposits -> FixedHomeSide (legacyHomeSide spec)+ LoansReceivable -> FixedHomeSide (legacyHomeSide spec)+ GiftCertificatesReceived -> FixedHomeSide (legacyHomeSide spec)+ SecurityDepositsPaid -> FixedHomeSide (legacyHomeSide spec)+ SuppliesOnHand -> FixedHomeSide (legacyHomeSide spec)+ RawMaterials -> FixedHomeSide (legacyHomeSide spec)+ ContractAssets -> FixedHomeSide (legacyHomeSide spec)+ IncomeTaxesRefundReceivable -> FixedHomeSide (legacyHomeSide spec)+ WorkInProcess -> FixedHomeSide (legacyHomeSide spec)+ DeferredTaxAssets -> FixedHomeSide (legacyHomeSide spec)+ LeasedAssets -> FixedHomeSide (legacyHomeSide spec)+ ToolsAndInstruments -> FixedHomeSide (legacyHomeSide spec)+ ConstructionInProgress -> FixedHomeSide (legacyHomeSide spec)+ Goodwill -> FixedHomeSide (legacyHomeSide spec)+ SoftwareInProgress -> FixedHomeSide (legacyHomeSide spec)+ LongTermPrepaidExpenses -> FixedHomeSide (legacyHomeSide spec)+ DishonoredNotesReceivable -> FixedHomeSide (legacyHomeSide spec)+ PrepaidPensionCost -> FixedHomeSide (legacyHomeSide spec)+ NetDefinedBenefitAsset -> FixedHomeSide (legacyHomeSide spec)+ DepositsInSpecialAccounts -> FixedHomeSide (legacyHomeSide spec)+ Structures -> FixedHomeSide (legacyHomeSide spec)+ LeaseholdRights -> FixedHomeSide (legacyHomeSide spec)+ NonOperatingNotesReceivable -> FixedHomeSide (legacyHomeSide spec)+ NonOperatingElectronicallyRecordedReceivable -> FixedHomeSide (legacyHomeSide spec)+ RefundLiabilities -> FixedHomeSide (legacyHomeSide spec)+ NonOperatingNotesPayable -> FixedHomeSide (legacyHomeSide spec)+ NonOperatingElectronicallyRecordedObligations -> FixedHomeSide (legacyHomeSide spec)+ BonusesPayable -> FixedHomeSide (legacyHomeSide spec)+ AllowanceForRepairs -> FixedHomeSide (legacyHomeSide spec)+ AllowanceForProductWarranties -> FixedHomeSide (legacyHomeSide spec)+ AllowanceForBonuses -> FixedHomeSide (legacyHomeSide spec)+ DeferredTaxLiabilities -> FixedHomeSide (legacyHomeSide spec)+ LeaseObligations -> FixedHomeSide (legacyHomeSide spec)+ GuaranteeDepositsReceived -> FixedHomeSide (legacyHomeSide spec)+ AllowanceForRetirementBenefits -> FixedHomeSide (legacyHomeSide spec)+ LongTermOtherPayables -> FixedHomeSide (legacyHomeSide spec)+ NetDefinedBenefitLiability -> FixedHomeSide (legacyHomeSide spec)+ StockSubscriptionDeposits -> FixedHomeSide (legacyHomeSide spec)+ LegalCapitalSurplus -> FixedHomeSide (legacyHomeSide spec)+ OtherCapitalSurplus -> FixedHomeSide (legacyHomeSide spec)+ DividendEqualizationReserve -> FixedHomeSide (legacyHomeSide spec)+ RepairFundReserve -> FixedHomeSide (legacyHomeSide spec)+ ConstructionFundReserve -> FixedHomeSide (legacyHomeSide spec)+ GeneralReserve -> FixedHomeSide (legacyHomeSide spec)+ ValuationDifferenceOnOtherSecurities -> FixedHomeSide (legacyHomeSide spec)+ NonControllingInterests -> FixedHomeSide (legacyHomeSide spec)+ CapitalSurplus -> FixedHomeSide (legacyHomeSide spec)+ EarnedSurplus -> FixedHomeSide (legacyHomeSide spec)+ ServiceRevenue -> FixedHomeSide (legacyHomeSide spec)+ OperatingRevenue -> FixedHomeSide (legacyHomeSide spec)+ GainOnSalesOfSecurities -> FixedHomeSide (legacyHomeSide spec)+ GainOnValuationOfSecurities -> FixedHomeSide (legacyHomeSide spec)+ DividendsReceived -> FixedHomeSide (legacyHomeSide spec)+ InterestOnSecurities -> FixedHomeSide (legacyHomeSide spec)+ GainOnSalesOfInvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)+ InsuranceGain -> FixedHomeSide (legacyHomeSide spec)+ GainOnBargainPurchase -> FixedHomeSide (legacyHomeSide spec)+ ReversalOfAllowanceForRepairs -> FixedHomeSide (legacyHomeSide spec)+ ReversalOfAllowanceForBonuses -> FixedHomeSide (legacyHomeSide spec)+ ReversalOfAllowanceForProductWarranties -> FixedHomeSide (legacyHomeSide spec)+ GainOnDonationOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)+ GainOnNationalSubsidies -> FixedHomeSide (legacyHomeSide spec)+ GainOnConstructionGrants -> FixedHomeSide (legacyHomeSide spec)+ LandRentReceived -> FixedHomeSide (legacyHomeSide spec)+ SalesRebates -> FixedHomeSide (legacyHomeSide spec)+ SalesAllowances -> FixedHomeSide (legacyHomeSide spec)+ CostOfServices -> FixedHomeSide (legacyHomeSide spec)+ OperatingExpenses -> FixedHomeSide (legacyHomeSide spec)+ InventoryShrinkageLoss -> FixedHomeSide (legacyHomeSide spec)+ LossOnValuationOfMerchandise -> FixedHomeSide (legacyHomeSide spec)+ Bonuses -> FixedHomeSide (legacyHomeSide spec)+ RetirementBenefitExpenses -> FixedHomeSide (legacyHomeSide spec)+ ProvisionForRepairs -> FixedHomeSide (legacyHomeSide spec)+ ProvisionForBonuses -> FixedHomeSide (legacyHomeSide spec)+ ProvisionForProductWarranties -> FixedHomeSide (legacyHomeSide spec)+ ResearchAndDevelopmentExpenses -> FixedHomeSide (legacyHomeSide spec)+ AmortizationOfGoodwill -> FixedHomeSide (legacyHomeSide spec)+ AmortizationOfSoftware -> FixedHomeSide (legacyHomeSide spec)+ AmortizationOfPatents -> FixedHomeSide (legacyHomeSide spec)+ LeaseExpenses -> FixedHomeSide (legacyHomeSide spec)+ IncorporationExpenses -> FixedHomeSide (legacyHomeSide spec)+ StockIssuanceCosts -> FixedHomeSide (legacyHomeSide spec)+ BusinessCommencementExpenses -> FixedHomeSide (legacyHomeSide spec)+ DevelopmentExpenses -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfElectronicallyRecordedReceivables -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfReceivables -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfSecurities -> FixedHomeSide (legacyHomeSide spec)+ LossOnValuationOfSecurities -> FixedHomeSide (legacyHomeSide spec)+ LossOnSalesOfInvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)+ LossOnFire -> FixedHomeSide (legacyHomeSide spec)+ LossOnRetirementOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)+ LossOnReductionOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)+ AdditionalIncomeTaxesForPriorPeriods -> FixedHomeSide (legacyHomeSide spec)+ RefundOfIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)+ PurchaseRebates -> FixedHomeSide (legacyHomeSide spec)+ PurchaseAllowances -> FixedHomeSide (legacyHomeSide spec)+ WelfareExpenses -> FixedHomeSide (legacyHomeSide spec)+ MaintenanceExpenses -> FixedHomeSide (legacyHomeSide spec)+ StatutoryWelfareExpenses -> FixedHomeSide (legacyHomeSide spec)+ LandRentPaid -> FixedHomeSide (legacyHomeSide spec)+ InsuranceExpense -> FixedHomeSide (legacyHomeSide spec)+ RepairsExpense -> FixedHomeSide (legacyHomeSide spec)+ StorageExpenses -> FixedHomeSide (legacyHomeSide spec)+ MembershipFees -> FixedHomeSide (legacyHomeSide spec)+ IncomeSummary -> ContextDependentHomeSide+ SuspenseAccount -> ContextDependentHomeSide+ ForeignExchangeGains -> FixedHomeSide (legacyHomeSide spec)+ ForeignExchangeLosses -> FixedHomeSide (legacyHomeSide spec)+ ContraAccountForGuaranteeObligations -> FixedHomeSide (legacyHomeSide spec)+ GuaranteeObligations -> FixedHomeSide (legacyHomeSide spec)+ IncomeTaxesAdjustment -> FixedHomeSide (legacyHomeSide spec)+ BranchCurrentAccount -> FixedHomeSide (legacyHomeSide spec)+ HeadOfficeCurrentAccount -> FixedHomeSide (legacyHomeSide spec)+ NetIncomeAttributableToNCI -> FixedHomeSide (legacyHomeSide spec)+ NetLossAttributableToNCI -> FixedHomeSide (legacyHomeSide spec)+ TradingSecurities -> FixedHomeSide (legacyHomeSide spec)+ HeldToMaturityBonds -> FixedHomeSide (legacyHomeSide spec)+ SubsidiaryStocks -> FixedHomeSide (legacyHomeSide spec)+ AffiliateStocks -> FixedHomeSide (legacyHomeSide spec)+ AvailableForSaleSecurities -> FixedHomeSide (legacyHomeSide spec)+ ConsumptionTaxRefundReceivable -> FixedHomeSide (legacyHomeSide spec)+ PropertyTaxPayable -> FixedHomeSide (legacyHomeSide spec)+ DepositsReceivedFromOfficers -> FixedHomeSide (legacyHomeSide spec)+ AccountTitle -> NoPostingSide++legacyHomeSide :: AccountSpec -> Side+legacyHomeSide spec+ | asIsContra spec = reverseSide (divisionSide (asDivision spec))+ | otherwise = divisionSide (asDivision spec)++divisionSide :: AccountDivision -> Side+divisionSide Assets = Debit+divisionSide Cost = Debit+divisionSide Equity = Credit+divisionSide Liability = Credit+divisionSide Revenue = Credit++reverseSide :: Side -> Side+reverseSide Debit = Credit+reverseSide Credit = Debit+reverseSide Side = Side++reportingFor :: AccountTitles -> ReportingEligibility+reportingFor title = case title of+ Cash -> StatementEligible+ Deposits -> StatementEligible+ CurrentDeposits -> StatementEligible+ Securities -> StatementEligible+ InvestmentSecurities -> StatementEligible+ InvestmentInAssociate -> StatementEligible+ LongTermNationalBonds -> StatementEligible+ ShortTermNationalBonds -> StatementEligible+ Products -> StatementEligible+ Machinery -> StatementEligible+ Building -> StatementEligible+ Vehicle -> StatementEligible+ StockInvestment -> StatementEligible+ EquipmentInvestment -> StatementEligible+ LongTermLoansReceivable -> StatementEligible+ AccountsReceivable -> StatementEligible+ ShortTermLoansReceivable -> StatementEligible+ ReserveDepositReceivable -> StatementEligible+ Gold -> StatementEligible+ GovernmentService -> StatementEligible+ CapitalStock -> StatementEligible+ RetainedEarnings -> StatementEligible+ LongTermLoansPayable -> StatementEligible+ ShortTermLoansPayable -> StatementEligible+ LoansPayable -> StatementEligible+ ReserveForDepreciation -> StatementEligible+ DepositPayable -> StatementEligible+ LongTermNationalBondsPayable -> StatementEligible+ ShortTermNationalBondsPayable -> StatementEligible+ ReserveDepositPayable -> StatementEligible+ CentralBankNotePayable -> StatementEligible+ Depreciation -> StatementEligible+ AmortizationExpense -> StatementEligible+ SalesCost -> StatementEligible+ BusinessTrip -> StatementEligible+ Commutation -> StatementEligible+ UtilitiesExpense -> StatementEligible+ RentExpense -> StatementEligible+ AdvertisingExpense -> StatementEligible+ DeliveryExpenses -> StatementEligible+ SuppliesExpenses -> StatementEligible+ MiscellaneousExpenses -> StatementEligible+ EntertainmentExpenses -> StatementEligible+ MeetingExpenses -> StatementEligible+ NewspaperBooksExpenses -> StatementEligible+ WageExpenditure -> StatementEligible+ InterestExpense -> StatementEligible+ TaxesExpense -> StatementEligible+ ConsumptionExpenditure -> StatementEligible+ SubsidyExpense -> StatementEligible+ CentralBankPaymentExpense -> StatementEligible+ Purchases -> StatementEligible+ NetIncome -> DerivedPresentation+ ValueAdded -> StatementEligible+ SubsidyIncome -> StatementEligible+ NationalBondInterestEarned -> StatementEligible+ DepositInterestEarned -> StatementEligible+ GrossProfit -> DerivedPresentation+ OrdinaryProfit -> DerivedPresentation+ InterestEarned -> StatementEligible+ ReceiptFee -> StatementEligible+ RentalIncome -> StatementEligible+ WageEarned -> StatementEligible+ TaxesRevenue -> StatementEligible+ CentralBankPaymentIncome -> StatementEligible+ Sales -> StatementEligible+ EquityInEarningsOfInvestee -> ContextualPresentation+ NetLoss -> DerivedPresentation+ PettyCash -> StatementEligible+ NotesReceivable -> StatementEligible+ ElectronicallyRecordedReceivable -> StatementEligible+ CreditCardReceivable -> StatementEligible+ NotesLoansReceivable -> StatementEligible+ MerchandiseInventory -> StatementEligible+ GoodsInTransit -> StatementEligible+ AdvancesPaid -> StatementEligible+ PrepaidExpenses -> StatementEligible+ AccruedRevenue -> StatementEligible+ OtherReceivables -> StatementEligible+ PaymentsOnBehalf -> StatementEligible+ SuspensePayments -> ContextualPresentation+ ConsumptionTaxPaid -> StatementEligible+ PrepaidCorporateIncomeTaxes -> StatementEligible+ Land -> StatementEligible+ Fixtures -> StatementEligible+ Patent -> StatementEligible+ Trademark -> StatementEligible+ Software -> StatementEligible+ CashOverShort -> NotPresented+ AccountsPayable -> StatementEligible+ NotesPayable -> StatementEligible+ ElectronicallyRecordedObligations -> StatementEligible+ NotesLoansPayable -> StatementEligible+ BankOverdraft -> StatementEligible+ AdvancesReceived -> StatementEligible+ UnearnedRevenue -> StatementEligible+ AccruedExpenses -> StatementEligible+ OtherPayables -> StatementEligible+ DepositsReceived -> StatementEligible+ SuspenseReceipts -> ContextualPresentation+ ConsumptionTaxReceived -> StatementEligible+ AccruedConsumptionTax -> StatementEligible+ AccruedCorporateIncomeTaxes -> StatementEligible+ UnpaidDividends -> StatementEligible+ AllowanceForDoubtfulAccounts -> StatementEligible+ AccumulatedDepreciation -> StatementEligible+ LegalRetainedEarnings -> StatementEligible+ CumulativeTranslationAdjustment -> ContextualPresentation+ ProvisionForDoubtfulAccounts -> StatementEligible+ BadDebtLoss -> StatementEligible+ LossOnSalesOfFixedAssets -> StatementEligible+ LossOnSalesOfNotesReceivable -> StatementEligible+ PaymentFees -> StatementEligible+ MiscellaneousLoss -> StatementEligible+ CorporateIncomeTaxes -> StatementEligible+ CommunicationExpenses -> StatementEligible+ GainOnSalesOfFixedAssets -> StatementEligible+ RecoveryOfBadDebts -> StatementEligible+ MiscellaneousIncome -> StatementEligible+ ReversalOfAllowanceForDoubtfulAccounts -> StatementEligible+ TimeDeposits -> StatementEligible+ LoansReceivable -> StatementEligible+ GiftCertificatesReceived -> StatementEligible+ SecurityDepositsPaid -> StatementEligible+ SuppliesOnHand -> StatementEligible+ RawMaterials -> StatementEligible+ ContractAssets -> StatementEligible+ IncomeTaxesRefundReceivable -> StatementEligible+ WorkInProcess -> StatementEligible+ DeferredTaxAssets -> StatementEligible+ LeasedAssets -> StatementEligible+ ToolsAndInstruments -> StatementEligible+ ConstructionInProgress -> StatementEligible+ Goodwill -> StatementEligible+ SoftwareInProgress -> StatementEligible+ LongTermPrepaidExpenses -> StatementEligible+ DishonoredNotesReceivable -> StatementEligible+ PrepaidPensionCost -> StatementEligible+ NetDefinedBenefitAsset -> StatementEligible+ DepositsInSpecialAccounts -> StatementEligible+ Structures -> StatementEligible+ LeaseholdRights -> StatementEligible+ NonOperatingNotesReceivable -> StatementEligible+ NonOperatingElectronicallyRecordedReceivable -> StatementEligible+ RefundLiabilities -> StatementEligible+ NonOperatingNotesPayable -> StatementEligible+ NonOperatingElectronicallyRecordedObligations -> StatementEligible+ BonusesPayable -> StatementEligible+ AllowanceForRepairs -> StatementEligible+ AllowanceForProductWarranties -> StatementEligible+ AllowanceForBonuses -> StatementEligible+ DeferredTaxLiabilities -> StatementEligible+ LeaseObligations -> StatementEligible+ GuaranteeDepositsReceived -> StatementEligible+ AllowanceForRetirementBenefits -> StatementEligible+ LongTermOtherPayables -> StatementEligible+ NetDefinedBenefitLiability -> StatementEligible+ StockSubscriptionDeposits -> StatementEligible+ LegalCapitalSurplus -> StatementEligible+ OtherCapitalSurplus -> StatementEligible+ DividendEqualizationReserve -> StatementEligible+ RepairFundReserve -> StatementEligible+ ConstructionFundReserve -> StatementEligible+ GeneralReserve -> StatementEligible+ ValuationDifferenceOnOtherSecurities -> StatementEligible+ NonControllingInterests -> ContextualPresentation+ CapitalSurplus -> StatementEligible+ EarnedSurplus -> StatementEligible+ ServiceRevenue -> StatementEligible+ OperatingRevenue -> StatementEligible+ GainOnSalesOfSecurities -> StatementEligible+ GainOnValuationOfSecurities -> StatementEligible+ DividendsReceived -> StatementEligible+ InterestOnSecurities -> StatementEligible+ GainOnSalesOfInvestmentSecurities -> StatementEligible+ InsuranceGain -> StatementEligible+ GainOnBargainPurchase -> StatementEligible+ ReversalOfAllowanceForRepairs -> StatementEligible+ ReversalOfAllowanceForBonuses -> StatementEligible+ ReversalOfAllowanceForProductWarranties -> StatementEligible+ GainOnDonationOfFixedAssets -> StatementEligible+ GainOnNationalSubsidies -> StatementEligible+ GainOnConstructionGrants -> StatementEligible+ LandRentReceived -> StatementEligible+ SalesRebates -> StatementEligible+ SalesAllowances -> StatementEligible+ CostOfServices -> StatementEligible+ OperatingExpenses -> StatementEligible+ InventoryShrinkageLoss -> StatementEligible+ LossOnValuationOfMerchandise -> StatementEligible+ Bonuses -> StatementEligible+ RetirementBenefitExpenses -> StatementEligible+ ProvisionForRepairs -> StatementEligible+ ProvisionForBonuses -> StatementEligible+ ProvisionForProductWarranties -> StatementEligible+ ResearchAndDevelopmentExpenses -> StatementEligible+ AmortizationOfGoodwill -> StatementEligible+ AmortizationOfSoftware -> StatementEligible+ AmortizationOfPatents -> StatementEligible+ LeaseExpenses -> StatementEligible+ IncorporationExpenses -> StatementEligible+ StockIssuanceCosts -> StatementEligible+ BusinessCommencementExpenses -> StatementEligible+ DevelopmentExpenses -> StatementEligible+ LossOnSalesOfElectronicallyRecordedReceivables -> StatementEligible+ LossOnSalesOfReceivables -> StatementEligible+ LossOnSalesOfSecurities -> StatementEligible+ LossOnValuationOfSecurities -> StatementEligible+ LossOnSalesOfInvestmentSecurities -> StatementEligible+ LossOnFire -> StatementEligible+ LossOnRetirementOfFixedAssets -> StatementEligible+ LossOnReductionOfFixedAssets -> StatementEligible+ AdditionalIncomeTaxesForPriorPeriods -> StatementEligible+ RefundOfIncomeTaxes -> StatementEligible+ PurchaseRebates -> StatementEligible+ PurchaseAllowances -> StatementEligible+ WelfareExpenses -> StatementEligible+ MaintenanceExpenses -> StatementEligible+ StatutoryWelfareExpenses -> StatementEligible+ LandRentPaid -> StatementEligible+ InsuranceExpense -> StatementEligible+ RepairsExpense -> StatementEligible+ StorageExpenses -> StatementEligible+ MembershipFees -> StatementEligible+ IncomeSummary -> NotPresented+ SuspenseAccount -> ContextualPresentation+ ForeignExchangeGains -> StatementEligible+ ForeignExchangeLosses -> StatementEligible+ ContraAccountForGuaranteeObligations -> StatementEligible+ GuaranteeObligations -> StatementEligible+ IncomeTaxesAdjustment -> StatementEligible+ BranchCurrentAccount -> ContextualPresentation+ HeadOfficeCurrentAccount -> ContextualPresentation+ NetIncomeAttributableToNCI -> ContextualPresentation+ NetLossAttributableToNCI -> ContextualPresentation+ TradingSecurities -> StatementEligible+ HeldToMaturityBonds -> StatementEligible+ SubsidiaryStocks -> StatementEligible+ AffiliateStocks -> StatementEligible+ AvailableForSaleSecurities -> StatementEligible+ ConsumptionTaxRefundReceivable -> StatementEligible+ PropertyTaxPayable -> StatementEligible+ DepositsReceivedFromOfficers -> StatementEligible+ AccountTitle -> NotPresented++-- | Every accepted non-canonical alias for an account. The JCCI overlay is+-- generated from the frozen 2022 standard/permitted account-name fixture;+-- shared permitted names intentionally remain shared so parsing reports an+-- explicit ambiguity instead of silently choosing one account.+accountAliases :: AccountTitles -> [Text]+accountAliases title = L.nub (baseAliases <> jcciAliases title)+ where+ baseAliases = maybe [] asAliases (accountSpec title)++-- | Look up metadata for an account title. The wildcard has no metadata.+--+-- Complexity: O(1)+{-# INLINE accountSpec #-}+accountSpec :: AccountTitles -> Maybe AccountSpec+accountSpec Cash = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Cash"+ , asNameJa = "現金"+ , asLabelJa = "現金"+ , asDescription = "Asset: Cash (現金)"+ , asAliases = ["現金"]+ }+accountSpec Deposits = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Savings deposits"+ , asNameJa = "普通預金"+ , asLabelJa = "普通預金"+ , asDescription = "Asset: Savings deposits (普通預金)"+ , asAliases = ["普通預金"]+ }+accountSpec CurrentDeposits = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Current deposits"+ , asNameJa = "当座預金"+ , asLabelJa = "当座預金"+ , asDescription = "Asset: Current deposits (当座預金)"+ , asAliases = ["当座預金"]+ }+accountSpec Securities = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Securities"+ , asNameJa = "有価証券"+ , asLabelJa = "有価証券"+ , asDescription = "Asset: Securities (有価証券)"+ , asAliases = ["有価証券"]+ }+accountSpec InvestmentSecurities = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Investment securities"+ , asNameJa = "投資有価証券"+ , asLabelJa = "投資有価証券"+ , asDescription = "Asset: Investment securities (投資有価証券)"+ , asAliases = ["投資有価証券"]+ }+accountSpec InvestmentInAssociate = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Investment in associate"+ , asNameJa = "関係会社株式"+ , asLabelJa = "関係会社株式"+ , asDescription = "Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定)."+ , asAliases = []+ }+accountSpec LongTermNationalBonds = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term national bonds"+ , asNameJa = "長期国債"+ , asLabelJa = "長期国債"+ , asDescription = "Asset: Long-term national bonds (長期国債)"+ , asAliases = ["長期国債"]+ }+accountSpec ShortTermNationalBonds = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Short-term national bonds"+ , asNameJa = "短期国債"+ , asLabelJa = "短期国債"+ , asDescription = "Asset: Short-term national bonds (短期国債)"+ , asAliases = ["短期国債"]+ }+accountSpec Products = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Products"+ , asNameJa = "商品"+ , asLabelJa = "商品"+ , asDescription = "Asset: Products (商品)。分記法用。3 分法 (仕入\\/売上\\/繰越商品) では 'MerchandiseInventory' を使う"+ , asAliases = ["商品"]+ }+accountSpec Machinery = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Machinery and equipment"+ , asNameJa = "機械装置"+ , asLabelJa = "機械装置"+ , asDescription = "Asset: Machinery and equipment (機械装置)"+ , asAliases = ["機械装置"]+ }+accountSpec Building = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Real estate"+ , asNameJa = "建物"+ , asLabelJa = "建物"+ , asDescription = "Asset: Real estate (建物)"+ , asAliases = ["建物"]+ }+accountSpec Vehicle = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Vehicles"+ , asNameJa = "車両運搬具"+ , asLabelJa = "車両運搬具"+ , asDescription = "Asset: Vehicles (車両運搬具)"+ , asAliases = ["車両運搬具"]+ }+accountSpec StockInvestment = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Stock investment"+ , asNameJa = "株式投資"+ , asLabelJa = "株式投資"+ , asDescription = "Asset: Stock investment (株式投資)"+ , asAliases = ["株式投資"]+ }+accountSpec EquipmentInvestment = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Equipment investment"+ , asNameJa = "設備投資"+ , asLabelJa = "設備投資"+ , asDescription = "Asset: Equipment investment (設備投資)"+ , asAliases = ["設備投資"]+ }+accountSpec LongTermLoansReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term loans receivable"+ , asNameJa = "長期貸付金"+ , asLabelJa = "長期貸付金"+ , asDescription = "Asset: Long-term loans receivable (長期貸付金)"+ , asAliases = ["長期貸付金"]+ }+accountSpec AccountsReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accounts receivable"+ , asNameJa = "売掛金"+ , asLabelJa = "売掛金"+ , asDescription = "Asset: Accounts receivable (売掛金)"+ , asAliases = ["売掛金", "a/r", "accounts receivable"]+ }+accountSpec ShortTermLoansReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Short-term loans receivable"+ , asNameJa = "短期貸付金"+ , asLabelJa = "短期貸付金"+ , asDescription = "Asset: Short-term loans receivable (短期貸付金)"+ , asAliases = ["短期貸付金"]+ }+accountSpec ReserveDepositReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Reserve deposits, asset side"+ , asNameJa = "準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\\/マクロ系"+ , asLabelJa = "準備預金"+ , asDescription = "Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\\/マクロ系)"+ , asAliases = ["準備預金"]+ }+accountSpec Gold = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Gold"+ , asNameJa = "金"+ , asLabelJa = "金"+ , asDescription = "Asset: Gold (金)"+ , asAliases = ["金"]+ }+accountSpec GovernmentService = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Government service"+ , asNameJa = "政府サービス。SNA\\/マクロ系"+ , asLabelJa = "政府サービス"+ , asDescription = "Asset: Government service (政府サービス。SNA\\/マクロ系)"+ , asAliases = ["政府サービス"]+ }+accountSpec CapitalStock = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Capital stock"+ , asNameJa = "資本金"+ , asLabelJa = "資本金"+ , asDescription = "Equity: Capital stock (資本金)"+ , asAliases = ["資本金"]+ }+accountSpec RetainedEarnings = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Retained earnings"+ , asNameJa = "繰越利益剰余金"+ , asLabelJa = "繰越利益剰余金"+ , asDescription = "Equity: Retained earnings (繰越利益剰余金)"+ , asAliases = ["繰越利益剰余金"]+ }+accountSpec LongTermLoansPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term loans payable"+ , asNameJa = "長期借入金"+ , asLabelJa = "長期借入金"+ , asDescription = "Liability: Long-term loans payable (長期借入金)"+ , asAliases = ["長期借入金"]+ }+accountSpec ShortTermLoansPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Short-term loans payable"+ , asNameJa = "短期借入金"+ , asLabelJa = "短期借入金"+ , asDescription = "Liability: Short-term loans payable (短期借入金)"+ , asAliases = ["短期借入金"]+ }+accountSpec LoansPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Loans payable"+ , asNameJa = "借入金"+ , asLabelJa = "借入金"+ , asDescription = "Liability: Loans payable (借入金)"+ , asAliases = ["借入金"]+ }+accountSpec ReserveForDepreciation = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Reserve for depreciation"+ , asNameJa = "減価償却引当金 — SNA\\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額"+ , asLabelJa = "減価償却引当金"+ , asDescription = "Liability: Reserve for depreciation (減価償却引当金 — SNA\\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)"+ , asAliases = ["減価償却引当金"]+ }+accountSpec DepositPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Deposits accepted"+ , asNameJa = "受入預金 — 銀行側の負債としての預金。SNA\\/マクロ系。従業員等からの預り金は 'DepositsReceived'"+ , asLabelJa = "受入預金"+ , asDescription = "Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\\/マクロ系。従業員等からの預り金は 'DepositsReceived')"+ , asAliases = ["受入預金"]+ }+accountSpec LongTermNationalBondsPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term national bonds payable"+ , asNameJa = "長期国債, 発行側"+ , asLabelJa = "長期国債"+ , asDescription = "Liability: Long-term national bonds payable (長期国債, 発行側)"+ , asAliases = ["長期国債"]+ }+accountSpec ShortTermNationalBondsPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Short-term national bonds payable"+ , asNameJa = "短期国債, 発行側"+ , asLabelJa = "短期国債"+ , asDescription = "Liability: Short-term national bonds payable (短期国債, 発行側)"+ , asAliases = ["短期国債"]+ }+accountSpec ReserveDepositPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Reserve deposits, liability side"+ , asNameJa = "準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\\/マクロ系"+ , asLabelJa = "準備預金"+ , asDescription = "Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)"+ , asAliases = ["準備預金"]+ }+accountSpec CentralBankNotePayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Central bank notes"+ , asNameJa = "発行銀行券。SNA\\/マクロ系"+ , asLabelJa = "発行銀行券"+ , asDescription = "Liability: Central bank notes (発行銀行券。SNA\\/マクロ系)"+ , asAliases = ["発行銀行券"]+ }+accountSpec Depreciation = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Depreciation"+ , asNameJa = "減価償却費"+ , asLabelJa = "減価償却費"+ , asDescription = "Expense: Depreciation (減価償却費)"+ , asAliases = ["減価償却費"]+ }+accountSpec AmortizationExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Amortization expense for intangibles"+ , asNameJa = "無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費"+ , asLabelJa = "無形固定資産償却費"+ , asDescription = "Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)"+ , asAliases = []+ }+accountSpec SalesCost = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Cost of sales"+ , asNameJa = "売上原価"+ , asLabelJa = "売上原価"+ , asDescription = "Expense: Cost of sales (売上原価)"+ , asAliases = ["売上原価", "cogs", "cost of sales"]+ }+accountSpec BusinessTrip = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Travel and transportation"+ , asNameJa = "旅費交通費"+ , asLabelJa = "旅費交通費"+ , asDescription = "Expense: Travel and transportation (旅費交通費)"+ , asAliases = ["旅費交通費"]+ }+accountSpec Commutation = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Communication"+ , asNameJa = "通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う"+ , asLabelJa = "通信費"+ , asDescription = "Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)"+ , asAliases = ["通信費"]+ }+accountSpec UtilitiesExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Utilities"+ , asNameJa = "水道光熱費"+ , asLabelJa = "水道光熱費"+ , asDescription = "Expense: Utilities (水道光熱費)"+ , asAliases = ["水道光熱費"]+ }+accountSpec RentExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Rent"+ , asNameJa = "支払家賃"+ , asLabelJa = "支払家賃"+ , asDescription = "Expense: Rent (支払家賃)"+ , asAliases = ["支払家賃"]+ }+accountSpec AdvertisingExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Advertising"+ , asNameJa = "広告宣伝費"+ , asLabelJa = "広告宣伝費"+ , asDescription = "Expense: Advertising (広告宣伝費)"+ , asAliases = ["広告宣伝費"]+ }+accountSpec DeliveryExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Delivery"+ , asNameJa = "発送費"+ , asLabelJa = "発送費"+ , asDescription = "Expense: Delivery (発送費)"+ , asAliases = ["発送費", "荷造運賃"]+ }+accountSpec SuppliesExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Supplies"+ , asNameJa = "消耗品費"+ , asLabelJa = "消耗品費"+ , asDescription = "Expense: Supplies (消耗品費)"+ , asAliases = ["消耗品費"]+ }+accountSpec MiscellaneousExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Miscellaneous"+ , asNameJa = "雑費"+ , asLabelJa = "雑費"+ , asDescription = "Expense: Miscellaneous (雑費)"+ , asAliases = ["雑費"]+ }+accountSpec EntertainmentExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Entertainment expenses"+ , asNameJa = "交際費"+ , asLabelJa = "交際費"+ , asDescription = "Expense: Entertainment expenses (交際費)"+ , asAliases = ["交際費", "接待交際費"]+ }+accountSpec MeetingExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Meeting expenses"+ , asNameJa = "会議費"+ , asLabelJa = "会議費"+ , asDescription = "Expense: Meeting expenses (会議費)"+ , asAliases = ["会議費"]+ }+accountSpec NewspaperBooksExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Newspapers and books"+ , asNameJa = "新聞図書費"+ , asLabelJa = "新聞図書費"+ , asDescription = "Expense: Newspapers and books (新聞図書費)"+ , asAliases = ["新聞図書費"]+ }+accountSpec WageExpenditure = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Wages"+ , asNameJa = "給料"+ , asLabelJa = "給料"+ , asDescription = "Expense: Wages (給料)"+ , asAliases = ["給料"]+ }+accountSpec InterestExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Interest expense"+ , asNameJa = "支払利息"+ , asLabelJa = "支払利息"+ , asDescription = "Expense: Interest expense (支払利息)"+ , asAliases = ["支払利息"]+ }+accountSpec TaxesExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Taxes"+ , asNameJa = "租税公課"+ , asLabelJa = "租税公課"+ , asDescription = "Expense: Taxes (租税公課)"+ , asAliases = ["租税公課"]+ }+accountSpec ConsumptionExpenditure = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Consumption expenditure"+ , asNameJa = "消費支出。SNA\\/マクロ系"+ , asLabelJa = "消費支出"+ , asDescription = "Expense: Consumption expenditure (消費支出。SNA\\/マクロ系)"+ , asAliases = ["消費支出"]+ }+accountSpec SubsidyExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Subsidy expenditure"+ , asNameJa = "補助金支出。SNA\\/マクロ系"+ , asLabelJa = "補助金支出"+ , asDescription = "Expense: Subsidy expenditure (補助金支出。SNA\\/マクロ系)"+ , asAliases = ["補助金支出"]+ }+accountSpec CentralBankPaymentExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Central bank payment to treasury"+ , asNameJa = "国庫納付金支出。SNA\\/マクロ系"+ , asLabelJa = "国庫納付金支出"+ , asDescription = "Expense: Central bank payment to treasury (国庫納付金支出。SNA\\/マクロ系)"+ , asAliases = ["国庫納付金支出"]+ }+accountSpec Purchases = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Purchases"+ , asNameJa = "仕入"+ , asLabelJa = "仕入"+ , asDescription = "Expense: Purchases (仕入)"+ , asAliases = ["仕入"]+ }+accountSpec NetIncome = Just AccountSpec+ { asDivision = Cost+ , asClosing = NoClose+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Net income"+ , asNameJa = "当期純利益 — 決算振替用。借方側に立つため Expense 区分"+ , asLabelJa = "当期純利益"+ , asDescription = "Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)"+ , asAliases = ["当期純利益"]+ }+accountSpec ValueAdded = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Value added"+ , asNameJa = "付加価値。SNA\\/マクロ系"+ , asLabelJa = "付加価値"+ , asDescription = "Revenue: Value added (付加価値。SNA\\/マクロ系)"+ , asAliases = ["付加価値"]+ }+accountSpec SubsidyIncome = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Subsidy income"+ , asNameJa = "補助金収入。SNA\\/マクロ系"+ , asLabelJa = "補助金収入"+ , asDescription = "Revenue: Subsidy income (補助金収入。SNA\\/マクロ系)"+ , asAliases = ["補助金収入"]+ }+accountSpec NationalBondInterestEarned = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "National bond interest earned"+ , asNameJa = "国債利息収入"+ , asLabelJa = "国債利息収入"+ , asDescription = "Revenue: National bond interest earned (国債利息収入)"+ , asAliases = ["国債利息収入"]+ }+accountSpec DepositInterestEarned = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Deposit interest earned"+ , asNameJa = "預金利息収入"+ , asLabelJa = "預金利息収入"+ , asDescription = "Revenue: Deposit interest earned (預金利息収入)"+ , asAliases = ["預金利息収入"]+ }+accountSpec GrossProfit = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gross profit"+ , asNameJa = "売上総利益 — 決算振替用"+ , asLabelJa = "売上総利益"+ , asDescription = "Revenue: Gross profit (売上総利益 — 決算振替用)"+ , asAliases = ["売上総利益"]+ }+accountSpec OrdinaryProfit = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Ordinary profit"+ , asNameJa = "経常利益 — 決算振替用"+ , asLabelJa = "経常利益"+ , asDescription = "Revenue: Ordinary profit (経常利益 — 決算振替用)"+ , asAliases = ["経常利益"]+ }+accountSpec InterestEarned = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Interest earned"+ , asNameJa = "受取利息"+ , asLabelJa = "受取利息"+ , asDescription = "Revenue: Interest earned (受取利息)"+ , asAliases = ["受取利息"]+ }+accountSpec ReceiptFee = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Receipt fee"+ , asNameJa = "受取手数料。支払側は 'PaymentFees'"+ , asLabelJa = "受取手数料"+ , asDescription = "Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')"+ , asAliases = ["受取手数料"]+ }+accountSpec RentalIncome = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Rental income"+ , asNameJa = "受取家賃"+ , asLabelJa = "受取家賃"+ , asDescription = "Revenue: Rental income (受取家賃)"+ , asAliases = ["受取家賃"]+ }+accountSpec WageEarned = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Wage income"+ , asNameJa = "賃金収入。SNA\\/マクロ系"+ , asLabelJa = "賃金収入"+ , asDescription = "Revenue: Wage income (賃金収入。SNA\\/マクロ系)"+ , asAliases = ["賃金収入"]+ }+accountSpec TaxesRevenue = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Tax revenue"+ , asNameJa = "租税収入。SNA\\/マクロ系"+ , asLabelJa = "租税収入"+ , asDescription = "Revenue: Tax revenue (租税収入。SNA\\/マクロ系)"+ , asAliases = ["租税収入"]+ }+accountSpec CentralBankPaymentIncome = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Central bank payment to treasury"+ , asNameJa = "国庫納付金収入。SNA\\/マクロ系"+ , asLabelJa = "国庫納付金収入"+ , asDescription = "Revenue: Central bank payment to treasury (国庫納付金収入。SNA\\/マクロ系)"+ , asAliases = ["国庫納付金収入"]+ }+accountSpec Sales = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Sales"+ , asNameJa = "売上"+ , asLabelJa = "売上"+ , asDescription = "Revenue: Sales (売上)"+ , asAliases = ["売上"]+ }+accountSpec EquityInEarningsOfInvestee = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Equity in earnings of investee"+ , asNameJa = "持分法による投資利益"+ , asLabelJa = "持分法による投資利益"+ , asDescription = "Revenue: Equity in earnings of investee (持分法による投資利益). Recognized under the equity method."+ , asAliases = []+ }+accountSpec NetLoss = Just AccountSpec+ { asDivision = Revenue+ , asClosing = NoClose+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Net loss"+ , asNameJa = "当期純損失 — 決算振替用。貸方側に立つため Revenue 区分"+ , asLabelJa = "当期純損失"+ , asDescription = "Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)"+ , asAliases = ["当期純損失"]+ }+accountSpec PettyCash = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Petty cash"+ , asNameJa = "小口現金"+ , asLabelJa = "小口現金"+ , asDescription = "Asset: Petty cash (小口現金)"+ , asAliases = ["小口現金"]+ }+accountSpec NotesReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Notes receivable"+ , asNameJa = "受取手形"+ , asLabelJa = "受取手形"+ , asDescription = "Asset: Notes receivable (受取手形)"+ , asAliases = ["受取手形"]+ }+accountSpec ElectronicallyRecordedReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Electronically recorded monetary claims"+ , asNameJa = "電子記録債権"+ , asLabelJa = "電子記録債権"+ , asDescription = "Asset: Electronically recorded monetary claims (電子記録債権)"+ , asAliases = ["電子記録債権"]+ }+accountSpec CreditCardReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Credit card receivable"+ , asNameJa = "クレジット売掛金"+ , asLabelJa = "クレジット売掛金"+ , asDescription = "Asset: Credit card receivable (クレジット売掛金)"+ , asAliases = ["クレジット売掛金"]+ }+accountSpec NotesLoansReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Loans receivable on notes"+ , asNameJa = "手形貸付金"+ , asLabelJa = "手形貸付金"+ , asDescription = "Asset: Loans receivable on notes (手形貸付金)"+ , asAliases = ["手形貸付金"]+ }+accountSpec MerchandiseInventory = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Merchandise inventory"+ , asNameJa = "繰越商品"+ , asLabelJa = "繰越商品"+ , asDescription = "Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\\/Sales\\/MerchandiseInventory). For the perpetual\\/specific-identification method (分記法) use 'Products' instead."+ , asAliases = ["繰越商品"]+ }+accountSpec GoodsInTransit = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Goods in transit"+ , asNameJa = "未着品"+ , asLabelJa = "未着品"+ , asDescription = "Assets: Goods in transit (未着品)"+ , asAliases = ["未着品"]+ }+accountSpec AdvancesPaid = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Advances paid"+ , asNameJa = "前払金"+ , asLabelJa = "前払金"+ , asDescription = "Asset: Advances paid (前払金)"+ , asAliases = ["前払金"]+ }+accountSpec PrepaidExpenses = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Prepaid expenses"+ , asNameJa = "前払費用"+ , asLabelJa = "前払費用"+ , asDescription = "Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)"+ , asAliases = ["前払費用"]+ }+accountSpec AccruedRevenue = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accrued revenue"+ , asNameJa = "未収収益"+ , asLabelJa = "未収収益"+ , asDescription = "Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)"+ , asAliases = ["未収収益"]+ }+accountSpec OtherReceivables = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Other receivables"+ , asNameJa = "未収入金"+ , asLabelJa = "未収入金"+ , asDescription = "Asset: Other receivables (未収入金)"+ , asAliases = ["未収入金"]+ }+accountSpec PaymentsOnBehalf = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Payments made on behalf"+ , asNameJa = "立替金"+ , asLabelJa = "立替金"+ , asDescription = "Asset: Payments made on behalf (立替金)"+ , asAliases = ["立替金"]+ }+accountSpec SuspensePayments = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Suspense payments"+ , asNameJa = "仮払金"+ , asLabelJa = "仮払金"+ , asDescription = "Asset: Suspense payments (仮払金)"+ , asAliases = ["仮払金"]+ }+accountSpec ConsumptionTaxPaid = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Consumption tax paid"+ , asNameJa = "仮払消費税"+ , asLabelJa = "仮払消費税"+ , asDescription = "Asset: Consumption tax paid (仮払消費税)"+ , asAliases = ["仮払消費税"]+ }+accountSpec PrepaidCorporateIncomeTaxes = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Prepaid corporate income taxes"+ , asNameJa = "仮払法人税等"+ , asLabelJa = "仮払法人税等"+ , asDescription = "Asset: Prepaid corporate income taxes (仮払法人税等)"+ , asAliases = ["仮払法人税等"]+ }+accountSpec Land = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Land"+ , asNameJa = "土地"+ , asLabelJa = "土地"+ , asDescription = "Asset: Land (土地)"+ , asAliases = ["土地"]+ }+accountSpec Fixtures = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Fixtures and equipment"+ , asNameJa = "備品"+ , asLabelJa = "備品"+ , asDescription = "Asset: Fixtures and equipment (備品)"+ , asAliases = ["備品", "器具備品"]+ }+accountSpec Patent = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Patent"+ , asNameJa = "特許権"+ , asLabelJa = "特許権"+ , asDescription = "Asset: Patent (特許権)"+ , asAliases = ["特許権"]+ }+accountSpec Trademark = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Trademark"+ , asNameJa = "商標権"+ , asLabelJa = "商標権"+ , asDescription = "Asset: Trademark (商標権)"+ , asAliases = ["商標権"]+ }+accountSpec Software = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Software"+ , asNameJa = "ソフトウェア"+ , asLabelJa = "ソフトウェア"+ , asDescription = "Asset: Software (ソフトウェア)"+ , asAliases = ["ソフトウェア"]+ }+accountSpec CashOverShort = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Cash over and short"+ , asNameJa = "現金過不足"+ , asLabelJa = "現金過不足"+ , asDescription = "Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\\/MiscellaneousLoss"+ , asAliases = ["現金過不足"]+ }+accountSpec AccountsPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accounts payable"+ , asNameJa = "買掛金"+ , asLabelJa = "買掛金"+ , asDescription = "Liability: Accounts payable (買掛金)"+ , asAliases = ["買掛金", "a/p", "accounts payable"]+ }+accountSpec NotesPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Notes payable"+ , asNameJa = "支払手形"+ , asLabelJa = "支払手形"+ , asDescription = "Liability: Notes payable (支払手形)"+ , asAliases = ["支払手形"]+ }+accountSpec ElectronicallyRecordedObligations = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Electronically recorded monetary obligations"+ , asNameJa = "電子記録債務"+ , asLabelJa = "電子記録債務"+ , asDescription = "Liability: Electronically recorded monetary obligations (電子記録債務)"+ , asAliases = ["電子記録債務"]+ }+accountSpec NotesLoansPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Loans payable on notes"+ , asNameJa = "手形借入金"+ , asLabelJa = "手形借入金"+ , asDescription = "Liability: Loans payable on notes (手形借入金)"+ , asAliases = ["手形借入金"]+ }+accountSpec BankOverdraft = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Bank overdraft"+ , asNameJa = "当座借越"+ , asLabelJa = "当座借越"+ , asDescription = "Liability: Bank overdraft (当座借越)"+ , asAliases = ["当座借越"]+ }+accountSpec AdvancesReceived = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Advances received"+ , asNameJa = "前受金"+ , asLabelJa = "前受金"+ , asDescription = "Liability: Advances received (前受金)"+ , asAliases = ["前受金"]+ }+accountSpec UnearnedRevenue = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Unearned revenue"+ , asNameJa = "前受収益"+ , asLabelJa = "前受収益"+ , asDescription = "Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)"+ , asAliases = ["前受収益"]+ }+accountSpec AccruedExpenses = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accrued expenses"+ , asNameJa = "未払費用"+ , asLabelJa = "未払費用"+ , asDescription = "Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)"+ , asAliases = ["未払費用"]+ }+accountSpec OtherPayables = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Other payables"+ , asNameJa = "未払金"+ , asLabelJa = "未払金"+ , asDescription = "Liability: Other payables (未払金)"+ , asAliases = ["未払金"]+ }+accountSpec DepositsReceived = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Deposits received"+ , asNameJa = "預り金"+ , asLabelJa = "預り金"+ , asDescription = "Liability: Deposits received (預り金)"+ , asAliases = ["預り金"]+ }+accountSpec SuspenseReceipts = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Suspense receipts"+ , asNameJa = "仮受金"+ , asLabelJa = "仮受金"+ , asDescription = "Liability: Suspense receipts (仮受金)"+ , asAliases = ["仮受金"]+ }+accountSpec ConsumptionTaxReceived = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Consumption tax received"+ , asNameJa = "仮受消費税"+ , asLabelJa = "仮受消費税"+ , asDescription = "Liability: Consumption tax received (仮受消費税)"+ , asAliases = ["仮受消費税"]+ }+accountSpec AccruedConsumptionTax = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accrued (unpaid) consumption tax"+ , asNameJa = "未払消費税"+ , asLabelJa = "未払消費税"+ , asDescription = "Liability: Accrued (unpaid) consumption tax (未払消費税)"+ , asAliases = ["未払消費税"]+ }+accountSpec AccruedCorporateIncomeTaxes = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accrued (unpaid) corporate income taxes"+ , asNameJa = "未払法人税等"+ , asLabelJa = "未払法人税等"+ , asDescription = "Liability: Accrued (unpaid) corporate income taxes (未払法人税等)"+ , asAliases = ["未払法人税等"]+ }+accountSpec UnpaidDividends = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Unpaid dividends"+ , asNameJa = "未払配当金"+ , asLabelJa = "未払配当金"+ , asDescription = "Liability: Unpaid dividends (未払配当金)"+ , asAliases = ["未払配当金"]+ }+accountSpec AllowanceForDoubtfulAccounts = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Current+ , asNameEn = "Allowance for doubtful accounts"+ , asNameJa = "貸倒引当金"+ , asLabelJa = "貸倒引当金"+ , asDescription = "Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\\/Not structure is intact. B\\/S deduction (net) presentation is the ExchangeAlgebra.IO.Output module's job."+ , asAliases = ["貸倒引当金"]+ }+accountSpec AccumulatedDepreciation = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Fixed+ , asNameEn = "Accumulated depreciation"+ , asNameJa = "減価償却累計額"+ , asLabelJa = "減価償却累計額"+ , asDescription = "Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\\/macro-accounting name."+ , asAliases = ["減価償却累計額"]+ }+accountSpec LegalRetainedEarnings = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Legal (appropriated) retained earnings reserve"+ , asNameJa = "利益準備金"+ , asLabelJa = "利益準備金"+ , asDescription = "Equity: Legal (appropriated) retained earnings reserve (利益準備金)"+ , asAliases = ["利益準備金"]+ }+accountSpec CumulativeTranslationAdjustment = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Cumulative translation adjustment"+ , asNameJa = "為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目"+ , asLabelJa = "為替換算調整勘定"+ , asDescription = "Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)"+ , asAliases = []+ }+accountSpec ProvisionForDoubtfulAccounts = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Provision for doubtful accounts"+ , asNameJa = "貸倒引当金繰入"+ , asLabelJa = "貸倒引当金繰入"+ , asDescription = "Cost: Provision for doubtful accounts (貸倒引当金繰入)"+ , asAliases = ["貸倒引当金繰入"]+ }+accountSpec BadDebtLoss = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Bad debt loss"+ , asNameJa = "貸倒損失"+ , asLabelJa = "貸倒損失"+ , asDescription = "Cost: Bad debt loss (貸倒損失)"+ , asAliases = ["貸倒損失"]+ }+accountSpec LossOnSalesOfFixedAssets = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sales of fixed assets"+ , asNameJa = "固定資産売却損"+ , asLabelJa = "固定資産売却損"+ , asDescription = "Cost: Loss on sales of fixed assets (固定資産売却損)"+ , asAliases = ["固定資産売却損"]+ }+accountSpec LossOnSalesOfNotesReceivable = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sales of notes receivable"+ , asNameJa = "手形売却損"+ , asLabelJa = "手形売却損"+ , asDescription = "Cost: Loss on sales of notes receivable (手形売却損)"+ , asAliases = ["手形売却損"]+ }+accountSpec PaymentFees = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Payment fees / fees paid"+ , asNameJa = "支払手数料"+ , asLabelJa = "支払手数料"+ , asDescription = "Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'"+ , asAliases = ["支払手数料"]+ }+accountSpec MiscellaneousLoss = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Miscellaneous loss"+ , asNameJa = "雑損"+ , asLabelJa = "雑損"+ , asDescription = "Cost: Miscellaneous loss (雑損)"+ , asAliases = ["雑損"]+ }+accountSpec CorporateIncomeTaxes = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Corporate income taxes"+ , asNameJa = "法人税等"+ , asLabelJa = "法人税等"+ , asDescription = "Cost: Corporate income taxes (法人税等)"+ , asAliases = ["法人税等"]+ }+accountSpec CommunicationExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Communication expenses"+ , asNameJa = "通信費"+ , asLabelJa = "通信費"+ , asDescription = "Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also \"Communication\"); 'Commutation' is retained for backward compatibility."+ , asAliases = ["通信費"]+ }+accountSpec GainOnSalesOfFixedAssets = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on sales of fixed assets"+ , asNameJa = "固定資産売却益"+ , asLabelJa = "固定資産売却益"+ , asDescription = "Revenue: Gain on sales of fixed assets (固定資産売却益)"+ , asAliases = ["固定資産売却益"]+ }+accountSpec RecoveryOfBadDebts = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Recovery of bad debts written off"+ , asNameJa = "償却債権取立益"+ , asLabelJa = "償却債権取立益"+ , asDescription = "Revenue: Recovery of bad debts written off (償却債権取立益)"+ , asAliases = ["償却債権取立益"]+ }+accountSpec MiscellaneousIncome = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Miscellaneous income"+ , asNameJa = "雑益"+ , asLabelJa = "雑益"+ , asDescription = "Revenue: Miscellaneous income (雑益)"+ , asAliases = ["雑益"]+ }+accountSpec ReversalOfAllowanceForDoubtfulAccounts = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reversal of allowance for doubtful accounts"+ , asNameJa = "貸倒引当金戻入"+ , asLabelJa = "貸倒引当金戻入"+ , asDescription = "Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released)."+ , asAliases = ["貸倒引当金戻入"]+ }+accountSpec TimeDeposits = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Time deposits"+ , asNameJa = "定期預金"+ , asLabelJa = "定期預金"+ , asDescription = "Assets: Time deposits (定期預金)"+ , asAliases = ["定期預金"]+ }+accountSpec LoansReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Loans receivable"+ , asNameJa = "貸付金"+ , asLabelJa = "貸付金"+ , asDescription = "Assets: Loans receivable (貸付金)"+ , asAliases = ["貸付金"]+ }+accountSpec GiftCertificatesReceived = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Gift certificates received"+ , asNameJa = "受取商品券"+ , asLabelJa = "受取商品券"+ , asDescription = "Assets: Gift certificates received (受取商品券)"+ , asAliases = ["受取商品券"]+ }+accountSpec SecurityDepositsPaid = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Guarantee deposits"+ , asNameJa = "差入保証金"+ , asLabelJa = "差入保証金"+ , asDescription = "Assets: Guarantee deposits (差入保証金)"+ , asAliases = ["差入保証金"]+ }+accountSpec SuppliesOnHand = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Supplies"+ , asNameJa = "貯蔵品"+ , asLabelJa = "貯蔵品"+ , asDescription = "Assets: Supplies (貯蔵品)"+ , asAliases = ["貯蔵品"]+ }+accountSpec RawMaterials = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Raw materials"+ , asNameJa = "原材料"+ , asLabelJa = "原材料"+ , asDescription = "Assets: Raw materials (原材料)"+ , asAliases = ["原材料", "材料"]+ }+accountSpec ContractAssets = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Contract assets"+ , asNameJa = "契約資産"+ , asLabelJa = "契約資産"+ , asDescription = "Assets: Contract assets (契約資産)"+ , asAliases = ["契約資産"]+ }+accountSpec IncomeTaxesRefundReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Income taxes refund receivable"+ , asNameJa = "未収還付法人税等"+ , asLabelJa = "未収還付法人税等"+ , asDescription = "Assets: Income taxes refund receivable (未収還付法人税等)"+ , asAliases = ["未収還付法人税等"]+ }+accountSpec WorkInProcess = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Work in process"+ , asNameJa = "仕掛品"+ , asLabelJa = "仕掛品"+ , asDescription = "Assets: Work in process (仕掛品)"+ , asAliases = ["仕掛品"]+ }+accountSpec DeferredTaxAssets = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Deferred tax assets"+ , asNameJa = "繰延税金資産"+ , asLabelJa = "繰延税金資産"+ , asDescription = "Assets: Deferred tax assets (繰延税金資産)"+ , asAliases = ["繰延税金資産"]+ }+accountSpec LeasedAssets = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Leased assets"+ , asNameJa = "リース資産"+ , asLabelJa = "リース資産"+ , asDescription = "Assets: Leased assets (リース資産)"+ , asAliases = ["リース資産"]+ }+accountSpec ToolsAndInstruments = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Tools and instruments"+ , asNameJa = "工具器具"+ , asLabelJa = "工具器具"+ , asDescription = "Assets: Tools and instruments (工具器具)"+ , asAliases = ["工具器具"]+ }+accountSpec ConstructionInProgress = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Construction in progress"+ , asNameJa = "建設仮勘定"+ , asLabelJa = "建設仮勘定"+ , asDescription = "Assets: Construction in progress (建設仮勘定)"+ , asAliases = ["建設仮勘定"]+ }+accountSpec Goodwill = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Goodwill"+ , asNameJa = "のれん"+ , asLabelJa = "のれん"+ , asDescription = "Assets: Goodwill (のれん)"+ , asAliases = ["のれん"]+ }+accountSpec SoftwareInProgress = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Software in progress"+ , asNameJa = "ソフトウェア仮勘定"+ , asLabelJa = "ソフトウェア仮勘定"+ , asDescription = "Assets: Software in progress (ソフトウェア仮勘定)"+ , asAliases = ["ソフトウェア仮勘定"]+ }+accountSpec LongTermPrepaidExpenses = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term prepaid expenses"+ , asNameJa = "長期前払費用"+ , asLabelJa = "長期前払費用"+ , asDescription = "Assets: Long-term prepaid expenses (長期前払費用)"+ , asAliases = ["長期前払費用"]+ }+accountSpec DishonoredNotesReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Dishonored notes receivable"+ , asNameJa = "不渡手形"+ , asLabelJa = "不渡手形"+ , asDescription = "Assets: Dishonored notes receivable (不渡手形)"+ , asAliases = ["不渡手形"]+ }+accountSpec PrepaidPensionCost = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Prepaid pension costs"+ , asNameJa = "前払年金費用"+ , asLabelJa = "前払年金費用"+ , asDescription = "Assets: Prepaid pension costs (前払年金費用)"+ , asAliases = ["前払年金費用"]+ }+accountSpec NetDefinedBenefitAsset = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Retirement benefit asset"+ , asNameJa = "退職給付に係る資産"+ , asLabelJa = "退職給付に係る資産"+ , asDescription = "Assets: Retirement benefit asset (退職給付に係る資産)"+ , asAliases = ["退職給付に係る資産"]+ }+accountSpec DepositsInSpecialAccounts = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Deposits in special accounts"+ , asNameJa = "別段預金"+ , asLabelJa = "別段預金"+ , asDescription = "Assets: Deposits in special accounts (別段預金)"+ , asAliases = ["別段預金"]+ }+accountSpec Structures = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Structures"+ , asNameJa = "構築物"+ , asLabelJa = "構築物"+ , asDescription = "Assets: Structures (構築物)"+ , asAliases = ["構築物"]+ }+accountSpec LeaseholdRights = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Leasehold interests in land"+ , asNameJa = "借地権"+ , asLabelJa = "借地権"+ , asDescription = "Assets: Leasehold interests in land (借地権)"+ , asAliases = ["借地権"]+ }+accountSpec NonOperatingNotesReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Non-operating notes receivable"+ , asNameJa = "営業外受取手形"+ , asLabelJa = "営業外受取手形"+ , asDescription = "Assets: Non-operating notes receivable (営業外受取手形)"+ , asAliases = ["営業外受取手形"]+ }+accountSpec NonOperatingElectronicallyRecordedReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Electronically recorded monetary claims - non-operating"+ , asNameJa = "営業外電子記録債権"+ , asLabelJa = "営業外電子記録債権"+ , asDescription = "Assets: Electronically recorded monetary claims - non-operating (営業外電子記録債権)"+ , asAliases = ["営業外電子記録債権"]+ }+accountSpec RefundLiabilities = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Refund liabilities"+ , asNameJa = "返金負債"+ , asLabelJa = "返金負債"+ , asDescription = "Liability: Refund liabilities (返金負債)"+ , asAliases = ["返金負債"]+ }+accountSpec NonOperatingNotesPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Non-operating notes payable"+ , asNameJa = "営業外支払手形"+ , asLabelJa = "営業外支払手形"+ , asDescription = "Liability: Non-operating notes payable (営業外支払手形)"+ , asAliases = ["営業外支払手形"]+ }+accountSpec NonOperatingElectronicallyRecordedObligations = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Electronically recorded obligations - non-operating"+ , asNameJa = "営業外電子記録債務"+ , asLabelJa = "営業外電子記録債務"+ , asDescription = "Liability: Electronically recorded obligations - non-operating (営業外電子記録債務)"+ , asAliases = ["営業外電子記録債務"]+ }+accountSpec BonusesPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Accrued bonuses"+ , asNameJa = "未払賞与"+ , asLabelJa = "未払賞与"+ , asDescription = "Liability: Accrued bonuses (未払賞与)"+ , asAliases = ["未払賞与"]+ }+accountSpec AllowanceForRepairs = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Provision for repairs"+ , asNameJa = "修繕引当金"+ , asLabelJa = "修繕引当金"+ , asDescription = "Liability: Provision for repairs (修繕引当金)"+ , asAliases = ["修繕引当金"]+ }+accountSpec AllowanceForProductWarranties = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Provision for warranties"+ , asNameJa = "商品保証引当金"+ , asLabelJa = "商品保証引当金"+ , asDescription = "Liability: Provision for warranties (商品保証引当金)"+ , asAliases = ["商品保証引当金"]+ }+accountSpec AllowanceForBonuses = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Provision for bonuses"+ , asNameJa = "賞与引当金"+ , asLabelJa = "賞与引当金"+ , asDescription = "Liability: Provision for bonuses (賞与引当金)"+ , asAliases = ["賞与引当金"]+ }+accountSpec DeferredTaxLiabilities = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Deferred tax liabilities"+ , asNameJa = "繰延税金負債"+ , asLabelJa = "繰延税金負債"+ , asDescription = "Liability: Deferred tax liabilities (繰延税金負債)"+ , asAliases = ["繰延税金負債"]+ }+accountSpec LeaseObligations = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Lease liabilities"+ , asNameJa = "リース債務"+ , asLabelJa = "リース債務"+ , asDescription = "Liability: Lease liabilities (リース債務)"+ , asAliases = ["リース債務"]+ }+accountSpec GuaranteeDepositsReceived = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Guarantee deposits received"+ , asNameJa = "預り保証金"+ , asLabelJa = "預り保証金"+ , asDescription = "Liability: Guarantee deposits received (預り保証金)"+ , asAliases = ["預り保証金"]+ }+accountSpec AllowanceForRetirementBenefits = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Provision for retirement benefits"+ , asNameJa = "退職給付引当金"+ , asLabelJa = "退職給付引当金"+ , asDescription = "Liability: Provision for retirement benefits (退職給付引当金)"+ , asAliases = ["退職給付引当金"]+ }+accountSpec LongTermOtherPayables = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Long-term accounts payable - other"+ , asNameJa = "長期未払金"+ , asLabelJa = "長期未払金"+ , asDescription = "Liability: Long-term accounts payable - other (長期未払金)"+ , asAliases = ["長期未払金"]+ }+accountSpec NetDefinedBenefitLiability = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Retirement benefit liability"+ , asNameJa = "退職給付に係る負債"+ , asLabelJa = "退職給付に係る負債"+ , asDescription = "Liability: Retirement benefit liability (退職給付に係る負債)"+ , asAliases = ["退職給付に係る負債"]+ }+accountSpec StockSubscriptionDeposits = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Deposits for subscriptions of shares"+ , asNameJa = "株式申込証拠金"+ , asLabelJa = "株式申込証拠金"+ , asDescription = "Equity: Deposits for subscriptions of shares (株式申込証拠金)"+ , asAliases = ["株式申込証拠金"]+ }+accountSpec LegalCapitalSurplus = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Legal capital surplus"+ , asNameJa = "資本準備金"+ , asLabelJa = "資本準備金"+ , asDescription = "Equity: Legal capital surplus (資本準備金)"+ , asAliases = ["資本準備金"]+ }+accountSpec OtherCapitalSurplus = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Other capital surplus"+ , asNameJa = "その他資本剰余金"+ , asLabelJa = "その他資本剰余金"+ , asDescription = "Equity: Other capital surplus (その他資本剰余金)"+ , asAliases = ["その他資本剰余金"]+ }+accountSpec DividendEqualizationReserve = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reserve for dividend equalization"+ , asNameJa = "配当平均積立金"+ , asLabelJa = "配当平均積立金"+ , asDescription = "Equity: Reserve for dividend equalization (配当平均積立金)"+ , asAliases = ["配当平均積立金"]+ }+accountSpec RepairFundReserve = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reserve for repairs"+ , asNameJa = "修繕積立金"+ , asLabelJa = "修繕積立金"+ , asDescription = "Equity: Reserve for repairs (修繕積立金)"+ , asAliases = ["修繕積立金"]+ }+accountSpec ConstructionFundReserve = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reserve for new construction"+ , asNameJa = "新築積立金"+ , asLabelJa = "新築積立金"+ , asDescription = "Equity: Reserve for new construction (新築積立金)"+ , asAliases = ["新築積立金"]+ }+accountSpec GeneralReserve = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "General reserve"+ , asNameJa = "別途積立金"+ , asLabelJa = "別途積立金"+ , asDescription = "Equity: General reserve (別途積立金)"+ , asAliases = ["別途積立金"]+ }+accountSpec ValuationDifferenceOnOtherSecurities = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Valuation difference on available-for-sale securities"+ , asNameJa = "その他有価証券評価差額金"+ , asLabelJa = "その他有価証券評価差額金"+ , asDescription = "Equity: Valuation difference on available-for-sale securities (その他有価証券評価差額金)"+ , asAliases = ["その他有価証券評価差額金"]+ }+accountSpec NonControllingInterests = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Non-controlling interests"+ , asNameJa = "非支配株主持分"+ , asLabelJa = "非支配株主持分"+ , asDescription = "Equity: Non-controlling interests (非支配株主持分)"+ , asAliases = ["非支配株主持分"]+ }+accountSpec CapitalSurplus = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Capital surplus"+ , asNameJa = "資本剰余金"+ , asLabelJa = "資本剰余金"+ , asDescription = "Equity: Capital surplus (資本剰余金)"+ , asAliases = ["資本剰余金"]+ }+accountSpec EarnedSurplus = Just AccountSpec+ { asDivision = Equity+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Retained earnings"+ , asNameJa = "利益剰余金"+ , asLabelJa = "利益剰余金"+ , asDescription = "Equity: Retained earnings (利益剰余金)"+ , asAliases = ["利益剰余金"]+ }+accountSpec ServiceRevenue = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Service revenue"+ , asNameJa = "役務収益"+ , asLabelJa = "役務収益"+ , asDescription = "Revenue: Service revenue (役務収益)"+ , asAliases = ["役務収益"]+ }+accountSpec OperatingRevenue = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Operating revenue"+ , asNameJa = "営業収益"+ , asLabelJa = "営業収益"+ , asDescription = "Revenue: Operating revenue (営業収益)"+ , asAliases = ["営業収益"]+ }+accountSpec GainOnSalesOfSecurities = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on sale of securities"+ , asNameJa = "有価証券売却益"+ , asLabelJa = "有価証券売却益"+ , asDescription = "Revenue: Gain on sale of securities (有価証券売却益)"+ , asAliases = ["有価証券売却益"]+ }+accountSpec GainOnValuationOfSecurities = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on valuation of securities"+ , asNameJa = "有価証券評価益"+ , asLabelJa = "有価証券評価益"+ , asDescription = "Revenue: Gain on valuation of securities (有価証券評価益)"+ , asAliases = ["有価証券評価益"]+ }+accountSpec DividendsReceived = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Dividend income"+ , asNameJa = "受取配当金"+ , asLabelJa = "受取配当金"+ , asDescription = "Revenue: Dividend income (受取配当金)"+ , asAliases = ["受取配当金"]+ }+accountSpec InterestOnSecurities = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Interest on securities"+ , asNameJa = "有価証券利息"+ , asLabelJa = "有価証券利息"+ , asDescription = "Revenue: Interest on securities (有価証券利息)"+ , asAliases = ["有価証券利息"]+ }+accountSpec GainOnSalesOfInvestmentSecurities = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on sale of investment securities"+ , asNameJa = "投資有価証券売却益"+ , asLabelJa = "投資有価証券売却益"+ , asDescription = "Revenue: Gain on sale of investment securities (投資有価証券売却益)"+ , asAliases = ["投資有価証券売却益"]+ }+accountSpec InsuranceGain = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on insurance claims"+ , asNameJa = "保険差益"+ , asLabelJa = "保険差益"+ , asDescription = "Revenue: Gain on insurance claims (保険差益)"+ , asAliases = ["保険差益"]+ }+accountSpec GainOnBargainPurchase = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on bargain purchase"+ , asNameJa = "負ののれん発生益"+ , asLabelJa = "負ののれん発生益"+ , asDescription = "Revenue: Gain on bargain purchase (負ののれん発生益)"+ , asAliases = ["負ののれん発生益"]+ }+accountSpec ReversalOfAllowanceForRepairs = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reversal of provision for repairs"+ , asNameJa = "修繕引当金戻入"+ , asLabelJa = "修繕引当金戻入"+ , asDescription = "Revenue: Reversal of provision for repairs (修繕引当金戻入)"+ , asAliases = ["修繕引当金戻入"]+ }+accountSpec ReversalOfAllowanceForBonuses = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reversal of provision for bonuses"+ , asNameJa = "賞与引当金戻入"+ , asLabelJa = "賞与引当金戻入"+ , asDescription = "Revenue: Reversal of provision for bonuses (賞与引当金戻入)"+ , asAliases = ["賞与引当金戻入"]+ }+accountSpec ReversalOfAllowanceForProductWarranties = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Reversal of provision for warranties"+ , asNameJa = "商品保証引当金戻入"+ , asLabelJa = "商品保証引当金戻入"+ , asDescription = "Revenue: Reversal of provision for warranties (商品保証引当金戻入)"+ , asAliases = ["商品保証引当金戻入"]+ }+accountSpec GainOnDonationOfFixedAssets = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on receipt of donated non-current assets"+ , asNameJa = "固定資産受贈益"+ , asLabelJa = "固定資産受贈益"+ , asDescription = "Revenue: Gain on receipt of donated non-current assets (固定資産受贈益)"+ , asAliases = ["固定資産受贈益"]+ }+accountSpec GainOnNationalSubsidies = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on receipt of national subsidies"+ , asNameJa = "国庫補助金受贈益"+ , asLabelJa = "国庫補助金受贈益"+ , asDescription = "Revenue: Gain on receipt of national subsidies (国庫補助金受贈益)"+ , asAliases = ["国庫補助金受贈益"]+ }+accountSpec GainOnConstructionGrants = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Gain on contribution received for construction"+ , asNameJa = "工事負担金受贈益"+ , asLabelJa = "工事負担金受贈益"+ , asDescription = "Revenue: Gain on contribution received for construction (工事負担金受贈益)"+ , asAliases = ["工事負担金受贈益"]+ }+accountSpec LandRentReceived = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Rental income from land"+ , asNameJa = "受取地代"+ , asLabelJa = "受取地代"+ , asDescription = "Revenue: Rental income from land (受取地代)"+ , asAliases = ["受取地代"]+ }+accountSpec SalesRebates = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Other+ , asNameEn = "Sales rebates"+ , asNameJa = "売上割戻"+ , asLabelJa = "売上割戻"+ , asDescription = "Revenue: Sales rebates (売上割戻)"+ , asAliases = ["売上割戻"]+ }+accountSpec SalesAllowances = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Other+ , asNameEn = "Sales allowances"+ , asNameJa = "売上値引"+ , asLabelJa = "売上値引"+ , asDescription = "Revenue: Sales allowances (売上値引)"+ , asAliases = ["売上値引"]+ }+accountSpec CostOfServices = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Cost of services"+ , asNameJa = "役務原価"+ , asLabelJa = "役務原価"+ , asDescription = "Cost: Cost of services (役務原価)"+ , asAliases = ["役務原価"]+ }+accountSpec OperatingExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Operating expenses"+ , asNameJa = "営業費用"+ , asLabelJa = "営業費用"+ , asDescription = "Cost: Operating expenses (営業費用)"+ , asAliases = ["営業費用"]+ }+accountSpec InventoryShrinkageLoss = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Inventory shrinkage loss"+ , asNameJa = "棚卸減耗損"+ , asLabelJa = "棚卸減耗損"+ , asDescription = "Cost: Inventory shrinkage loss (棚卸減耗損)"+ , asAliases = ["棚卸減耗損"]+ }+accountSpec LossOnValuationOfMerchandise = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Valuation loss on goods"+ , asNameJa = "商品評価損"+ , asLabelJa = "商品評価損"+ , asDescription = "Cost: Valuation loss on goods (商品評価損)"+ , asAliases = ["商品評価損"]+ }+accountSpec Bonuses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Bonuses"+ , asNameJa = "賞与"+ , asLabelJa = "賞与"+ , asDescription = "Cost: Bonuses (賞与)"+ , asAliases = ["賞与"]+ }+accountSpec RetirementBenefitExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Retirement benefit expenses"+ , asNameJa = "退職給付費用"+ , asLabelJa = "退職給付費用"+ , asDescription = "Cost: Retirement benefit expenses (退職給付費用)"+ , asAliases = ["退職給付費用"]+ }+accountSpec ProvisionForRepairs = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Provision for repairs"+ , asNameJa = "修繕引当金繰入"+ , asLabelJa = "修繕引当金繰入"+ , asDescription = "Cost: Provision for repairs (修繕引当金繰入)"+ , asAliases = ["修繕引当金繰入"]+ }+accountSpec ProvisionForBonuses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Provision for bonuses"+ , asNameJa = "賞与引当金繰入"+ , asLabelJa = "賞与引当金繰入"+ , asDescription = "Cost: Provision for bonuses (賞与引当金繰入)"+ , asAliases = ["賞与引当金繰入"]+ }+accountSpec ProvisionForProductWarranties = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Provision for warranties"+ , asNameJa = "商品保証引当金繰入"+ , asLabelJa = "商品保証引当金繰入"+ , asDescription = "Cost: Provision for warranties (商品保証引当金繰入)"+ , asAliases = ["商品保証引当金繰入"]+ }+accountSpec ResearchAndDevelopmentExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Research and development expenses"+ , asNameJa = "研究開発費"+ , asLabelJa = "研究開発費"+ , asDescription = "Cost: Research and development expenses (研究開発費)"+ , asAliases = ["研究開発費"]+ }+accountSpec AmortizationOfGoodwill = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Amortization of goodwill"+ , asNameJa = "のれん償却"+ , asLabelJa = "のれん償却"+ , asDescription = "Cost: Amortization of goodwill (のれん償却)"+ , asAliases = ["のれん償却"]+ }+accountSpec AmortizationOfSoftware = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Amortization of software"+ , asNameJa = "ソフトウェア償却"+ , asLabelJa = "ソフトウェア償却"+ , asDescription = "Cost: Amortization of software (ソフトウェア償却)"+ , asAliases = ["ソフトウェア償却"]+ }+accountSpec AmortizationOfPatents = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Amortization of patent rights"+ , asNameJa = "特許権償却"+ , asLabelJa = "特許権償却"+ , asDescription = "Cost: Amortization of patent rights (特許権償却)"+ , asAliases = ["特許権償却"]+ }+accountSpec LeaseExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Lease expenses"+ , asNameJa = "支払リース料"+ , asLabelJa = "支払リース料"+ , asDescription = "Cost: Lease expenses (支払リース料)"+ , asAliases = ["支払リース料"]+ }+accountSpec IncorporationExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Organization expenses"+ , asNameJa = "創立費"+ , asLabelJa = "創立費"+ , asDescription = "Cost: Organization expenses (創立費)"+ , asAliases = ["創立費"]+ }+accountSpec StockIssuanceCosts = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Share issuance costs"+ , asNameJa = "株式交付費"+ , asLabelJa = "株式交付費"+ , asDescription = "Cost: Share issuance costs (株式交付費)"+ , asAliases = ["株式交付費"]+ }+accountSpec BusinessCommencementExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Business commencement expenses"+ , asNameJa = "開業費"+ , asLabelJa = "開業費"+ , asDescription = "Cost: Business commencement expenses (開業費)"+ , asAliases = ["開業費"]+ }+accountSpec DevelopmentExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Development expenses"+ , asNameJa = "開発費"+ , asLabelJa = "開発費"+ , asDescription = "Cost: Development expenses (開発費)"+ , asAliases = ["開発費"]+ }+accountSpec LossOnSalesOfElectronicallyRecordedReceivables = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sale of electronically recorded monetary claims"+ , asNameJa = "電子記録債権売却損"+ , asLabelJa = "電子記録債権売却損"+ , asDescription = "Cost: Loss on sale of electronically recorded monetary claims (電子記録債権売却損)"+ , asAliases = ["電子記録債権売却損"]+ }+accountSpec LossOnSalesOfReceivables = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sale of receivables"+ , asNameJa = "債権売却損"+ , asLabelJa = "債権売却損"+ , asDescription = "Cost: Loss on sale of receivables (債権売却損)"+ , asAliases = ["債権売却損"]+ }+accountSpec LossOnSalesOfSecurities = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sale of securities"+ , asNameJa = "有価証券売却損"+ , asLabelJa = "有価証券売却損"+ , asDescription = "Cost: Loss on sale of securities (有価証券売却損)"+ , asAliases = ["有価証券売却損"]+ }+accountSpec LossOnValuationOfSecurities = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on valuation of securities"+ , asNameJa = "有価証券評価損"+ , asLabelJa = "有価証券評価損"+ , asDescription = "Cost: Loss on valuation of securities (有価証券評価損)"+ , asAliases = ["有価証券評価損"]+ }+accountSpec LossOnSalesOfInvestmentSecurities = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on sale of investment securities"+ , asNameJa = "投資有価証券売却損"+ , asLabelJa = "投資有価証券売却損"+ , asDescription = "Cost: Loss on sale of investment securities (投資有価証券売却損)"+ , asAliases = ["投資有価証券売却損"]+ }+accountSpec LossOnFire = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on fire"+ , asNameJa = "火災損失"+ , asLabelJa = "火災損失"+ , asDescription = "Cost: Loss on fire (火災損失)"+ , asAliases = ["火災損失"]+ }+accountSpec LossOnRetirementOfFixedAssets = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on retirement of non-current assets"+ , asNameJa = "固定資産除却損"+ , asLabelJa = "固定資産除却損"+ , asDescription = "Cost: Loss on retirement of non-current assets (固定資産除却損)"+ , asAliases = ["固定資産除却損"]+ }+accountSpec LossOnReductionOfFixedAssets = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss on tax purpose reduction entry of non-current assets"+ , asNameJa = "固定資産圧縮損"+ , asLabelJa = "固定資産圧縮損"+ , asDescription = "Cost: Loss on tax purpose reduction entry of non-current assets (固定資産圧縮損)"+ , asAliases = ["固定資産圧縮損"]+ }+accountSpec AdditionalIncomeTaxesForPriorPeriods = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Additional income taxes for prior periods"+ , asNameJa = "追徴法人税等"+ , asLabelJa = "追徴法人税等"+ , asDescription = "Cost: Additional income taxes for prior periods (追徴法人税等)"+ , asAliases = ["追徴法人税等"]+ }+accountSpec RefundOfIncomeTaxes = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Other+ , asNameEn = "Refund of income taxes"+ , asNameJa = "還付法人税等"+ , asLabelJa = "還付法人税等"+ , asDescription = "Cost: Refund of income taxes (還付法人税等)"+ , asAliases = ["還付法人税等"]+ }+accountSpec PurchaseRebates = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Other+ , asNameEn = "Purchase rebates"+ , asNameJa = "仕入割戻"+ , asLabelJa = "仕入割戻"+ , asDescription = "Cost: Purchase rebates (仕入割戻)"+ , asAliases = ["仕入割戻"]+ }+accountSpec PurchaseAllowances = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = True+ , asFixedCurrent = Other+ , asNameEn = "Purchase allowances"+ , asNameJa = "仕入値引"+ , asLabelJa = "仕入値引"+ , asDescription = "Cost: Purchase allowances (仕入値引)"+ , asAliases = ["仕入値引"]+ }+accountSpec WelfareExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Welfare expenses"+ , asNameJa = "福利厚生費"+ , asLabelJa = "福利厚生費"+ , asDescription = "Cost: Welfare expenses (福利厚生費)"+ , asAliases = ["福利厚生費"]+ }+accountSpec MaintenanceExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Maintenance expenses"+ , asNameJa = "保守費"+ , asLabelJa = "保守費"+ , asDescription = "Cost: Maintenance expenses (保守費)"+ , asAliases = ["保守費"]+ }+accountSpec StatutoryWelfareExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Legal welfare expenses"+ , asNameJa = "法定福利費"+ , asLabelJa = "法定福利費"+ , asDescription = "Cost: Legal welfare expenses (法定福利費)"+ , asAliases = ["法定福利費"]+ }+accountSpec LandRentPaid = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Rent expenses on land"+ , asNameJa = "支払地代"+ , asLabelJa = "支払地代"+ , asDescription = "Cost: Rent expenses on land (支払地代)"+ , asAliases = ["支払地代"]+ }+accountSpec InsuranceExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Insurance expenses"+ , asNameJa = "保険料"+ , asLabelJa = "保険料"+ , asDescription = "Cost: Insurance expenses (保険料)"+ , asAliases = ["保険料"]+ }+accountSpec RepairsExpense = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Repair expenses"+ , asNameJa = "修繕費"+ , asLabelJa = "修繕費"+ , asDescription = "Cost: Repair expenses (修繕費)"+ , asAliases = ["修繕費"]+ }+accountSpec StorageExpenses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Storage costs"+ , asNameJa = "保管費"+ , asLabelJa = "保管費"+ , asDescription = "Cost: Storage costs (保管費)"+ , asAliases = ["保管費"]+ }+accountSpec MembershipFees = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Membership fees"+ , asNameJa = "諸会費"+ , asLabelJa = "諸会費"+ , asDescription = "Cost: Membership fees (諸会費)"+ , asAliases = ["諸会費"]+ }+accountSpec IncomeSummary = Just AccountSpec+ { asDivision = Assets+ , asClosing = NoClose+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Income summary"+ , asNameJa = "損益"+ , asLabelJa = "損益"+ , asDescription = "Bookkeeping device: Income summary (損益). Assets/NoClose is a technical debit-side placeholder; it is not a balance-sheet asset and is not automatically closed."+ , asAliases = ["損益"]+ }+accountSpec SuspenseAccount = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Suspense account"+ , asNameJa = "未決算"+ , asLabelJa = "未決算"+ , asDescription = "Assets: Suspense account (未決算)"+ , asAliases = ["未決算"]+ }+accountSpec ForeignExchangeGains = Just AccountSpec+ { asDivision = Revenue+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Foreign exchange gains"+ , asNameJa = "為替差益"+ , asLabelJa = "為替差益"+ , asDescription = "Revenue: Foreign exchange gains (為替差益)"+ , asAliases = ["為替差益"]+ }+accountSpec ForeignExchangeLosses = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Foreign exchange losses"+ , asNameJa = "為替差損"+ , asLabelJa = "為替差損"+ , asDescription = "Cost: Foreign exchange losses (為替差損)"+ , asAliases = ["為替差損"]+ }+accountSpec ContraAccountForGuaranteeObligations = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Contra account for guarantee obligations"+ , asNameJa = "保証債務見返"+ , asLabelJa = "保証債務見返"+ , asDescription = "Assets: Contra account for guarantee obligations (保証債務見返)"+ , asAliases = ["保証債務見返"]+ }+accountSpec GuaranteeObligations = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Guarantee obligations"+ , asNameJa = "保証債務"+ , asLabelJa = "保証債務"+ , asDescription = "Liability: Guarantee obligations (保証債務)"+ , asAliases = ["保証債務"]+ }+accountSpec IncomeTaxesAdjustment = Just AccountSpec+ { asDivision = Cost+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Income taxes - deferred"+ , asNameJa = "法人税等調整額"+ , asLabelJa = "法人税等調整額"+ , asDescription = "Cost: Income taxes - deferred (法人税等調整額)"+ , asAliases = ["法人税等調整額"]+ }+accountSpec BranchCurrentAccount = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Branch current account"+ , asNameJa = "支店"+ , asLabelJa = "支店"+ , asDescription = "Assets: Branch current account (支店)"+ , asAliases = ["支店"]+ }+accountSpec HeadOfficeCurrentAccount = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Head office current account"+ , asNameJa = "本店"+ , asLabelJa = "本店"+ , asDescription = "Liability: Head office current account (本店), the credit-balance reciprocal account in branch books."+ , asAliases = ["本店"]+ }+accountSpec NetIncomeAttributableToNCI = Just AccountSpec+ { asDivision = Cost+ , asClosing = NoClose+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Profit attributable to non-controlling interests"+ , asNameJa = "非支配株主に帰属する当期純利益"+ , asLabelJa = "非支配株主に帰属する当期純利益"+ , asDescription = "Cost: Profit attributable to non-controlling interests (非支配株主に帰属する当期純利益). NoClose: consolidation procedures transfer it separately to non-controlling interests."+ , asAliases = ["非支配株主に帰属する当期純利益"]+ }+accountSpec NetLossAttributableToNCI = Just AccountSpec+ { asDivision = Revenue+ , asClosing = NoClose+ , asIsContra = False+ , asFixedCurrent = Other+ , asNameEn = "Loss attributable to non-controlling interests"+ , asNameJa = "非支配株主に帰属する当期純損失"+ , asLabelJa = "非支配株主に帰属する当期純損失"+ , asDescription = "Revenue: Loss attributable to non-controlling interests (非支配株主に帰属する当期純損失). NoClose: consolidation procedures transfer it separately to non-controlling interests."+ , asAliases = ["非支配株主に帰属する当期純損失"]+ }+accountSpec TradingSecurities = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Trading securities"+ , asNameJa = "売買目的有価証券"+ , asLabelJa = "売買目的有価証券"+ , asDescription = "Assets: Trading securities (売買目的有価証券)"+ , asAliases = ["売買目的有価証券"]+ }+accountSpec HeldToMaturityBonds = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Held-to-maturity bonds"+ , asNameJa = "満期保有目的債券"+ , asLabelJa = "満期保有目的債券"+ , asDescription = "Assets: Held-to-maturity bonds (満期保有目的債券)"+ , asAliases = ["満期保有目的債券"]+ }+accountSpec SubsidiaryStocks = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Shares of subsidiaries"+ , asNameJa = "子会社株式"+ , asLabelJa = "子会社株式"+ , asDescription = "Assets: Shares of subsidiaries (子会社株式)"+ , asAliases = ["子会社株式"]+ }+accountSpec AffiliateStocks = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Shares of associates"+ , asNameJa = "関連会社株式"+ , asLabelJa = "関連会社株式"+ , asDescription = "Assets: Shares of associates (関連会社株式)"+ , asAliases = ["関連会社株式"]+ }+accountSpec AvailableForSaleSecurities = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Fixed+ , asNameEn = "Available-for-sale securities"+ , asNameJa = "その他有価証券"+ , asLabelJa = "その他有価証券"+ , asDescription = "Assets: Available-for-sale securities (その他有価証券)"+ , asAliases = ["その他有価証券"]+ }+accountSpec ConsumptionTaxRefundReceivable = Just AccountSpec+ { asDivision = Assets+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Consumption tax refund receivable"+ , asNameJa = "未収還付消費税"+ , asLabelJa = "未収還付消費税"+ , asDescription = "Asset: Consumption tax refund receivable (未収還付消費税)"+ , asAliases = ["未収還付消費税", "未収消費税"]+ }+accountSpec PropertyTaxPayable = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Property tax payable"+ , asNameJa = "未払固定資産税"+ , asLabelJa = "未払固定資産税"+ , asDescription = "Liability: Property tax payable (未払固定資産税)"+ , asAliases = ["未払固定資産税"]+ }+accountSpec DepositsReceivedFromOfficers = Just AccountSpec+ { asDivision = Liability+ , asClosing = CloseByDivision+ , asIsContra = False+ , asFixedCurrent = Current+ , asNameEn = "Deposits received from officers"+ , asNameJa = "役員預り金"+ , asLabelJa = "役員預り金"+ , asDescription = "Liability: Deposits received from officers (役員預り金)"+ , asAliases = ["役員預り金"]+ }+accountSpec AccountTitle = Nothing++-- | Derived strict map view. The exhaustive function above remains canonical.+accountSpecMap :: Map AccountTitles AccountSpec+accountSpecMap = M.fromList+ [ (title, spec)+ | title <- concreteAccountTitles+ , Just spec <- [accountSpec title]+ ]++-- | Whether an account is a contra account.+--+-- The result is projected from 'AccountSpec.asIsContra'; the wildcard has no+-- specification and therefore returns 'False'. Complexity: O(1)+{-# INLINE classifyAccountContra #-}+classifyAccountContra :: AccountTitles -> Bool+classifyAccountContra title = maybe False asIsContra (accountSpec title)++-- | Compatibility projection used by the assistance API.+accountDescriptions :: [(AccountTitles, Text, Text, Text)]+accountDescriptions = mapMaybe project concreteAccountTitles+ where+ project title = do+ spec <- accountSpec title+ pure (title, asNameEn spec, asNameJa spec, asDescription spec)
+ src/ExchangeAlgebra/Accounting/Account/Title.hs view
@@ -0,0 +1,573 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE DeriveGeneric #-}++module ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..)) where++import ExchangeAlgebra.Algebra.Element.Representation (Element(..))+import ExchangeAlgebra.Algebra.Base.Representation (BaseClass(..))+import GHC.Generics (Generic)+import Data.Hashable (Hashable(..))+import Control.DeepSeq (NFData(..))+import Data.Word (Word16)+import qualified Data.Map.Strict as Map+import qualified Data.Binary as Binary+import qualified Data.Binary.Get as BinaryGet+import qualified Data.Binary.Put as BinaryPut++-- ** Account Titles++-- | Account titles used as bookkeeping basis coordinates.+data AccountTitles = Cash -- ^ Asset: Cash (現金)+ | Deposits -- ^ Asset: Savings deposits (普通預金)+ | CurrentDeposits -- ^ Asset: Current deposits (当座預金)+ | Securities -- ^ Asset: Securities (有価証券)+ | InvestmentSecurities -- ^ Asset: Investment securities (投資有価証券)+ | InvestmentInAssociate -- ^ Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定).+ | LongTermNationalBonds -- ^ Asset: Long-term national bonds (長期国債)+ | ShortTermNationalBonds -- ^ Asset: Short-term national bonds (短期国債)+ | Products -- ^ Asset: Products (商品)。分記法用。3 分法 (仕入\/売上\/繰越商品) では 'MerchandiseInventory' を使う+ | Machinery -- ^ Asset: Machinery and equipment (機械装置)+ | Building -- ^ Asset: Real estate (建物)+ | Vehicle -- ^ Asset: Vehicles (車両運搬具)+ | StockInvestment -- ^ Asset: Stock investment (株式投資)+ | EquipmentInvestment -- ^ Asset: Equipment investment (設備投資)+ | LongTermLoansReceivable -- ^ Asset: Long-term loans receivable (長期貸付金)+ | AccountsReceivable -- ^ Asset: Accounts receivable (売掛金)+ | ShortTermLoansReceivable -- ^ Asset: Short-term loans receivable (短期貸付金)+ | ReserveDepositReceivable -- ^ Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系)+ | Gold -- ^ Asset: Gold (金)+ | GovernmentService -- ^ Asset: Government service (政府サービス。SNA\/マクロ系)+ | CapitalStock -- ^ Equity: Capital stock (資本金)+ | RetainedEarnings -- ^ Equity: Retained earnings (繰越利益剰余金)+ | LongTermLoansPayable -- ^ Liability: Long-term loans payable (長期借入金)+ | ShortTermLoansPayable -- ^ Liability: Short-term loans payable (短期借入金)+ | LoansPayable -- ^ Liability: Loans payable (借入金)+ | ReserveForDepreciation -- ^ Liability: Reserve for depreciation (減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)+ | DepositPayable -- ^ Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived')+ | LongTermNationalBondsPayable -- ^ Liability: Long-term national bonds payable (長期国債, 発行側)+ | ShortTermNationalBondsPayable -- ^ Liability: Short-term national bonds payable (短期国債, 発行側)+ | ReserveDepositPayable -- ^ Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)+ | CentralBankNotePayable -- ^ Liability: Central bank notes (発行銀行券。SNA\/マクロ系)+ | Depreciation -- ^ Expense: Depreciation (減価償却費)+ | AmortizationExpense -- ^ Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)+ | SalesCost -- ^ Expense: Cost of sales (売上原価)+ | BusinessTrip -- ^ Expense: Travel and transportation (旅費交通費)+ | Commutation -- ^ Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)+ | UtilitiesExpense -- ^ Expense: Utilities (水道光熱費)+ | RentExpense -- ^ Expense: Rent (支払家賃)+ | AdvertisingExpense -- ^ Expense: Advertising (広告宣伝費)+ | DeliveryExpenses -- ^ Expense: Delivery (発送費)+ | SuppliesExpenses -- ^ Expense: Supplies (消耗品費)+ | MiscellaneousExpenses -- ^ Expense: Miscellaneous (雑費)+ | WageExpenditure -- ^ Expense: Wages (給料)+ | InterestExpense -- ^ Expense: Interest expense (支払利息)+ | TaxesExpense -- ^ Expense: Taxes (租税公課)+ | ConsumptionExpenditure -- ^ Expense: Consumption expenditure (消費支出。SNA\/マクロ系)+ | SubsidyExpense -- ^ Expense: Subsidy expenditure (補助金支出。SNA\/マクロ系)+ | CentralBankPaymentExpense -- ^ Expense: Central bank payment to treasury (国庫納付金支出。SNA\/マクロ系)+ | Purchases -- ^ Expense: Purchases (仕入)+ | NetIncome -- ^ Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)+ | ValueAdded -- ^ Revenue: Value added (付加価値。SNA\/マクロ系)+ | SubsidyIncome -- ^ Revenue: Subsidy income (補助金収入。SNA\/マクロ系)+ | NationalBondInterestEarned -- ^ Revenue: National bond interest earned (国債利息収入)+ | DepositInterestEarned -- ^ Revenue: Deposit interest earned (預金利息収入)+ | GrossProfit -- ^ Revenue: Gross profit (売上総利益 — 決算振替用)+ | OrdinaryProfit -- ^ Revenue: Ordinary profit (経常利益 — 決算振替用)+ | InterestEarned -- ^ Revenue: Interest earned (受取利息)+ | ReceiptFee -- ^ Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')+ | RentalIncome -- ^ Revenue: Rental income (受取家賃)+ | WageEarned -- ^ Revenue: Wage income (賃金収入。SNA\/マクロ系)+ | TaxesRevenue -- ^ Revenue: Tax revenue (租税収入。SNA\/マクロ系)+ | CentralBankPaymentIncome -- ^ Revenue: Central bank payment to treasury (国庫納付金収入。SNA\/マクロ系)+ | Sales -- ^ Revenue: Sales (売上)+ | EquityInEarningsOfInvestee -- ^ Revenue: Equity in earnings of investee+ -- (持分法による投資利益). Recognized under the equity method.+ | NetLoss -- ^ Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)+ -- Elementary bookkeeping (日商簿記 3 級水準) additions.+ -- Appended before the 'AccountTitle' wildcard so that only the+ -- wildcard's Enum/Binary ordinal shifts (existing serialized+ -- bases remain compatible).+ --+ -- Assets (資産)+ | PettyCash -- ^ Asset: Petty cash (小口現金)+ | NotesReceivable -- ^ Asset: Notes receivable (受取手形)+ | ElectronicallyRecordedReceivable -- ^ Asset: Electronically recorded monetary claims (電子記録債権)+ | CreditCardReceivable -- ^ Asset: Credit card receivable (クレジット売掛金)+ | NotesLoansReceivable -- ^ Asset: Loans receivable on notes (手形貸付金)+ | MerchandiseInventory -- ^ Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\/Sales\/MerchandiseInventory). For the perpetual\/specific-identification method (分記法) use 'Products' instead.+ | AdvancesPaid -- ^ Asset: Advances paid (前払金)+ | PrepaidExpenses -- ^ Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)+ | AccruedRevenue -- ^ Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)+ | OtherReceivables -- ^ Asset: Other receivables (未収入金)+ | PaymentsOnBehalf -- ^ Asset: Payments made on behalf (立替金)+ | SuspensePayments -- ^ Asset: Suspense payments (仮払金)+ | ConsumptionTaxPaid -- ^ Asset: Consumption tax paid (仮払消費税)+ | PrepaidCorporateIncomeTaxes -- ^ Asset: Prepaid corporate income taxes (仮払法人税等)+ | Land -- ^ Asset: Land (土地)+ | Fixtures -- ^ Asset: Fixtures and equipment (備品)+ | Patent -- ^ Asset: Patent (特許権)+ | Trademark -- ^ Asset: Trademark (商標権)+ | Software -- ^ Asset: Software (ソフトウェア)+ | CashOverShort -- ^ Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\/MiscellaneousLoss+ -- Liability (負債)+ | AccountsPayable -- ^ Liability: Accounts payable (買掛金)+ | NotesPayable -- ^ Liability: Notes payable (支払手形)+ | ElectronicallyRecordedObligations -- ^ Liability: Electronically recorded monetary obligations (電子記録債務)+ | NotesLoansPayable -- ^ Liability: Loans payable on notes (手形借入金)+ | BankOverdraft -- ^ Liability: Bank overdraft (当座借越)+ | AdvancesReceived -- ^ Liability: Advances received (前受金)+ | UnearnedRevenue -- ^ Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)+ | AccruedExpenses -- ^ Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)+ | OtherPayables -- ^ Liability: Other payables (未払金)+ | DepositsReceived -- ^ Liability: Deposits received (預り金)+ | SuspenseReceipts -- ^ Liability: Suspense receipts (仮受金)+ | ConsumptionTaxReceived -- ^ Liability: Consumption tax received (仮受消費税)+ | AccruedConsumptionTax -- ^ Liability: Accrued (unpaid) consumption tax (未払消費税)+ | AccruedCorporateIncomeTaxes -- ^ Liability: Accrued (unpaid) corporate income taxes (未払法人税等)+ | UnpaidDividends -- ^ Liability: Unpaid dividends (未払配当金)+ | AllowanceForDoubtfulAccounts -- ^ Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Division is Assets with @isContra = True@ (Definition 7 amendment, 0.5.0.0); home side stays Credit, values stay non-negative, Hat\/Not structure intact. B\/S deduction (net) presentation is the ExchangeAlgebra.IO.Output module's job.+ | AccumulatedDepreciation -- ^ Asset (contra): Accumulated depreciation (減価償却累計額), valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Division is Assets with @isContra = True@ for the same reason as 'AllowanceForDoubtfulAccounts'. This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name.+ -- Equity (資本)+ | LegalRetainedEarnings -- ^ Equity: Legal (appropriated) retained earnings reserve (利益準備金)+ | CumulativeTranslationAdjustment -- ^ Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)+ -- Cost (費用)+ | ProvisionForDoubtfulAccounts -- ^ Cost: Provision for doubtful accounts (貸倒引当金繰入)+ | BadDebtLoss -- ^ Cost: Bad debt loss (貸倒損失)+ | LossOnSalesOfFixedAssets -- ^ Cost: Loss on sales of fixed assets (固定資産売却損)+ | LossOnSalesOfNotesReceivable -- ^ Cost: Loss on sales of notes receivable (手形売却損)+ | PaymentFees -- ^ Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'+ | MiscellaneousLoss -- ^ Cost: Miscellaneous loss (雑損)+ | CorporateIncomeTaxes -- ^ Cost: Corporate income taxes (法人税等)+ | CommunicationExpenses -- ^ Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also "Communication"); 'Commutation' is retained for backward compatibility.+ -- Revenue (収益)+ | GainOnSalesOfFixedAssets -- ^ Revenue: Gain on sales of fixed assets (固定資産売却益)+ | RecoveryOfBadDebts -- ^ Revenue: Recovery of bad debts written off (償却債権取立益)+ | MiscellaneousIncome -- ^ Revenue: Miscellaneous income (雑益)+ | ReversalOfAllowanceForDoubtfulAccounts -- ^ Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released).+ -- V-Land 2: JCCI grade-2 commercial-bookkeeping vocabulary.+ | TimeDeposits -- ^ Assets: Time deposits (定期預金)+ | LoansReceivable -- ^ Assets: Loans receivable (貸付金)+ | GiftCertificatesReceived -- ^ Assets: Gift certificates received (受取商品券)+ | SecurityDepositsPaid -- ^ Assets: Guarantee deposits (差入保証金)+ | SuppliesOnHand -- ^ Assets: Supplies (貯蔵品)+ | ContractAssets -- ^ Assets: Contract assets (契約資産)+ | IncomeTaxesRefundReceivable -- ^ Assets: Income taxes refund receivable (未収還付法人税等)+ | WorkInProcess -- ^ Assets: Work in process (仕掛品)+ | DeferredTaxAssets -- ^ Assets: Deferred tax assets (繰延税金資産)+ | LeasedAssets -- ^ Assets: Leased assets (リース資産)+ | ToolsAndInstruments -- ^ Assets: Tools and instruments (工具器具)+ | ConstructionInProgress -- ^ Assets: Construction in progress (建設仮勘定)+ | Goodwill -- ^ Assets: Goodwill (のれん)+ | SoftwareInProgress -- ^ Assets: Software in progress (ソフトウェア仮勘定)+ | LongTermPrepaidExpenses -- ^ Assets: Long-term prepaid expenses (長期前払費用)+ | DishonoredNotesReceivable -- ^ Assets: Dishonored notes receivable (不渡手形)+ | PrepaidPensionCost -- ^ Assets: Prepaid pension costs (前払年金費用)+ | NetDefinedBenefitAsset -- ^ Assets: Retirement benefit asset (退職給付に係る資産)+ | DepositsInSpecialAccounts -- ^ Assets: Deposits in special accounts (別段預金)+ | Structures -- ^ Assets: Structures (構築物)+ | LeaseholdRights -- ^ Assets: Leasehold interests in land (借地権)+ | NonOperatingNotesReceivable -- ^ Assets: Non-operating notes receivable (営業外受取手形)+ | NonOperatingElectronicallyRecordedReceivable -- ^ Assets: Electronically recorded monetary claims - non-operating (営業外電子記録債権)+ | RefundLiabilities -- ^ Liability: Refund liabilities (返金負債)+ | NonOperatingNotesPayable -- ^ Liability: Non-operating notes payable (営業外支払手形)+ | NonOperatingElectronicallyRecordedObligations -- ^ Liability: Electronically recorded obligations - non-operating (営業外電子記録債務)+ | BonusesPayable -- ^ Liability: Accrued bonuses (未払賞与)+ | AllowanceForRepairs -- ^ Liability: Provision for repairs (修繕引当金)+ | AllowanceForProductWarranties -- ^ Liability: Provision for warranties (商品保証引当金)+ | AllowanceForBonuses -- ^ Liability: Provision for bonuses (賞与引当金)+ | DeferredTaxLiabilities -- ^ Liability: Deferred tax liabilities (繰延税金負債)+ | LeaseObligations -- ^ Liability: Lease liabilities (リース債務)+ | GuaranteeDepositsReceived -- ^ Liability: Guarantee deposits received (預り保証金)+ | AllowanceForRetirementBenefits -- ^ Liability: Provision for retirement benefits (退職給付引当金)+ | LongTermOtherPayables -- ^ Liability: Long-term accounts payable - other (長期未払金)+ | NetDefinedBenefitLiability -- ^ Liability: Retirement benefit liability (退職給付に係る負債)+ | StockSubscriptionDeposits -- ^ Equity: Deposits for subscriptions of shares (株式申込証拠金)+ | LegalCapitalSurplus -- ^ Equity: Legal capital surplus (資本準備金)+ | OtherCapitalSurplus -- ^ Equity: Other capital surplus (その他資本剰余金)+ | DividendEqualizationReserve -- ^ Equity: Reserve for dividend equalization (配当平均積立金)+ | RepairFundReserve -- ^ Equity: Reserve for repairs (修繕積立金)+ | ConstructionFundReserve -- ^ Equity: Reserve for new construction (新築積立金)+ | GeneralReserve -- ^ Equity: General reserve (別途積立金)+ | ValuationDifferenceOnOtherSecurities -- ^ Equity: Valuation difference on available-for-sale securities (その他有価証券評価差額金)+ | NonControllingInterests -- ^ Equity: Non-controlling interests (非支配株主持分)+ | CapitalSurplus -- ^ Equity: Capital surplus (資本剰余金)+ | EarnedSurplus -- ^ Equity: Retained earnings (利益剰余金)+ | ServiceRevenue -- ^ Revenue: Service revenue (役務収益)+ | OperatingRevenue -- ^ Revenue: Operating revenue (営業収益)+ | GainOnSalesOfSecurities -- ^ Revenue: Gain on sale of securities (有価証券売却益)+ | GainOnValuationOfSecurities -- ^ Revenue: Gain on valuation of securities (有価証券評価益)+ | DividendsReceived -- ^ Revenue: Dividend income (受取配当金)+ | InterestOnSecurities -- ^ Revenue: Interest on securities (有価証券利息)+ | GainOnSalesOfInvestmentSecurities -- ^ Revenue: Gain on sale of investment securities (投資有価証券売却益)+ | InsuranceGain -- ^ Revenue: Gain on insurance claims (保険差益)+ | GainOnBargainPurchase -- ^ Revenue: Gain on bargain purchase (負ののれん発生益)+ | ReversalOfAllowanceForRepairs -- ^ Revenue: Reversal of provision for repairs (修繕引当金戻入)+ | ReversalOfAllowanceForProductWarranties -- ^ Revenue: Reversal of provision for warranties (商品保証引当金戻入)+ | GainOnDonationOfFixedAssets -- ^ Revenue: Gain on receipt of donated non-current assets (固定資産受贈益)+ | GainOnNationalSubsidies -- ^ Revenue: Gain on receipt of national subsidies (国庫補助金受贈益)+ | GainOnConstructionGrants -- ^ Revenue: Gain on contribution received for construction (工事負担金受贈益)+ | LandRentReceived -- ^ Revenue: Rental income from land (受取地代)+ | SalesRebates -- ^ Revenue: Sales rebates (売上割戻)+ | CostOfServices -- ^ Cost: Cost of services (役務原価)+ | OperatingExpenses -- ^ Cost: Operating expenses (営業費用)+ | InventoryShrinkageLoss -- ^ Cost: Inventory shrinkage loss (棚卸減耗損)+ | LossOnValuationOfMerchandise -- ^ Cost: Valuation loss on goods (商品評価損)+ | Bonuses -- ^ Cost: Bonuses (賞与)+ | RetirementBenefitExpenses -- ^ Cost: Retirement benefit expenses (退職給付費用)+ | ProvisionForRepairs -- ^ Cost: Provision for repairs (修繕引当金繰入)+ | ProvisionForBonuses -- ^ Cost: Provision for bonuses (賞与引当金繰入)+ | ProvisionForProductWarranties -- ^ Cost: Provision for warranties (商品保証引当金繰入)+ | ResearchAndDevelopmentExpenses -- ^ Cost: Research and development expenses (研究開発費)+ | AmortizationOfGoodwill -- ^ Cost: Amortization of goodwill (のれん償却)+ | AmortizationOfSoftware -- ^ Cost: Amortization of software (ソフトウェア償却)+ | AmortizationOfPatents -- ^ Cost: Amortization of patent rights (特許権償却)+ | LeaseExpenses -- ^ Cost: Lease expenses (支払リース料)+ | IncorporationExpenses -- ^ Cost: Organization expenses (創立費)+ | StockIssuanceCosts -- ^ Cost: Share issuance costs (株式交付費)+ | BusinessCommencementExpenses -- ^ Cost: Business commencement expenses (開業費)+ | DevelopmentExpenses -- ^ Cost: Development expenses (開発費)+ | LossOnSalesOfElectronicallyRecordedReceivables -- ^ Cost: Loss on sale of electronically recorded monetary claims (電子記録債権売却損)+ | LossOnSalesOfReceivables -- ^ Cost: Loss on sale of receivables (債権売却損)+ | LossOnSalesOfSecurities -- ^ Cost: Loss on sale of securities (有価証券売却損)+ | LossOnValuationOfSecurities -- ^ Cost: Loss on valuation of securities (有価証券評価損)+ | LossOnSalesOfInvestmentSecurities -- ^ Cost: Loss on sale of investment securities (投資有価証券売却損)+ | LossOnFire -- ^ Cost: Loss on fire (火災損失)+ | LossOnRetirementOfFixedAssets -- ^ Cost: Loss on retirement of non-current assets (固定資産除却損)+ | LossOnReductionOfFixedAssets -- ^ Cost: Loss on tax purpose reduction entry of non-current assets (固定資産圧縮損)+ | AdditionalIncomeTaxesForPriorPeriods -- ^ Cost: Additional income taxes for prior periods (追徴法人税等)+ | RefundOfIncomeTaxes -- ^ Cost: Refund of income taxes (還付法人税等)+ | PurchaseRebates -- ^ Cost: Purchase rebates (仕入割戻)+ | WelfareExpenses -- ^ Cost: Welfare expenses (福利厚生費)+ | MaintenanceExpenses -- ^ Cost: Maintenance expenses (保守費)+ | StatutoryWelfareExpenses -- ^ Cost: Legal welfare expenses (法定福利費)+ | LandRentPaid -- ^ Cost: Rent expenses on land (支払地代)+ | InsuranceExpense -- ^ Cost: Insurance expenses (保険料)+ | RepairsExpense -- ^ Cost: Repair expenses (修繕費)+ | StorageExpenses -- ^ Cost: Storage costs (保管費)+ | MembershipFees -- ^ Cost: Membership fees (諸会費)+ | IncomeSummary -- ^ Assets: Income summary (損益)+ | SuspenseAccount -- ^ Assets: Suspense account (未決算)+ | ForeignExchangeGains -- ^ Revenue: Foreign exchange gains (為替差益)+ | ForeignExchangeLosses -- ^ Cost: Foreign exchange losses (為替差損)+ | ContraAccountForGuaranteeObligations -- ^ Assets: Contra account for guarantee obligations (保証債務見返)+ | GuaranteeObligations -- ^ Liability: Guarantee obligations (保証債務)+ | IncomeTaxesAdjustment -- ^ Cost: Income taxes - deferred (法人税等調整額)+ | BranchCurrentAccount -- ^ Assets: Branch current account (支店)+ | HeadOfficeCurrentAccount -- ^ Assets: Head office current account (本店)+ | NetIncomeAttributableToNCI -- ^ Cost: Profit attributable to non-controlling interests (非支配株主に帰属する当期純利益)+ | NetLossAttributableToNCI -- ^ Revenue: Loss attributable to non-controlling interests (非支配株主に帰属する当期純損失)+ | TradingSecurities -- ^ Assets: Trading securities (売買目的有価証券)+ | HeldToMaturityBonds -- ^ Assets: Held-to-maturity bonds (満期保有目的債券)+ | SubsidiaryStocks -- ^ Assets: Shares of subsidiaries (子会社株式)+ | AffiliateStocks -- ^ Assets: Shares of associates (関連会社株式)+ | AvailableForSaleSecurities -- ^ Assets: Available-for-sale securities (その他有価証券)+ | ConsumptionTaxRefundReceivable -- ^ Assets: Consumption tax refund receivable (未収還付消費税)+ | PropertyTaxPayable -- ^ Liability: Property tax payable (未払固定資産税)+ | DepositsReceivedFromOfficers -- ^ Liability: Deposits received from officers (役員預り金)+ | EntertainmentExpenses -- ^ Cost: Entertainment expenses (交際費)+ | MeetingExpenses -- ^ Cost: Meeting expenses (会議費)+ | NewspaperBooksExpenses -- ^ Cost: Newspapers and books (新聞図書費)+ | RawMaterials -- ^ Assets: Raw materials (原材料)+ | GoodsInTransit -- ^ Assets: Goods in transit (未着品)+ | SalesAllowances -- ^ Revenue: Sales allowances (売上値引)+ | PurchaseAllowances -- ^ Cost: Purchase allowances (仕入値引)+ | ReversalOfAllowanceForBonuses+ -- ^ Revenue: Reversal of provision for bonuses (賞与引当金戻入)+ | AccountTitle -- ^ Wildcard (ワイルドカード — 任意の科目にマッチ。projWithBase 等の問い合わせ用で, 実 posting には使わない)+ deriving (Show, Ord, Eq, Enum, Generic, Bounded)++-- Commutation の実用法は examples の文脈から「通信費」であることが確認済+-- (2026-06-11 調査)。Enum/Binary 序数の安定のため削除はせず, 新名称への移行を促す。+{-# DEPRECATED Commutation "通信費 (communication expenses) — use 'CommunicationExpenses' instead" #-}++instance NFData AccountTitles++instance Hashable AccountTitles where+ {-# INLINE hashWithSalt #-}+ hashWithSalt salt x = hashWithSalt salt (fromEnum x)++-- | Serialize a title as a two-byte big-endian code: @0x8000 + n@, where+-- @n@ is the title's fixed number from 'accountCode', or @0xFFFF@ for the+-- wildcard 'AccountTitle'. The high bit marks the 0.6 encoding: bytes written+-- by 0.5.x (the declaration index, always below @0x8000@) and unknown codes+-- fail to decode, so a title added later never shifts an existing code.+instance Binary.Binary AccountTitles where+ {-# INLINE put #-}+ put = BinaryPut.putWord16be . accountCode+ {-# INLINE get #-}+ get = do+ code <- BinaryGet.getWord16be+ case Map.lookup code accountTitlesByCode of+ Just title -> pure title+ Nothing -> fail ("AccountTitles: unknown or unmarked code " ++ show code)++-- | The fixed wire code of each title. The case is total and written out, so+-- adding a title without a code fails to compile. The numbers are the+-- declaration indices of develop at this change, frozen; a new title+-- takes the next unused number wherever it is declared. Decoding uses the+-- inverse table built from this function, not a second hand-written table.+accountCode :: AccountTitles -> Word16+accountCode title = case title of+ Cash -> 0x8000+ Deposits -> 0x8001+ CurrentDeposits -> 0x8002+ Securities -> 0x8003+ InvestmentSecurities -> 0x8004+ InvestmentInAssociate -> 0x8005+ LongTermNationalBonds -> 0x8006+ ShortTermNationalBonds -> 0x8007+ Products -> 0x8008+ Machinery -> 0x8009+ Building -> 0x800A+ Vehicle -> 0x800B+ StockInvestment -> 0x800C+ EquipmentInvestment -> 0x800D+ LongTermLoansReceivable -> 0x800E+ AccountsReceivable -> 0x800F+ ShortTermLoansReceivable -> 0x8010+ ReserveDepositReceivable -> 0x8011+ Gold -> 0x8012+ GovernmentService -> 0x8013+ CapitalStock -> 0x8014+ RetainedEarnings -> 0x8015+ LongTermLoansPayable -> 0x8016+ ShortTermLoansPayable -> 0x8017+ LoansPayable -> 0x8018+ ReserveForDepreciation -> 0x8019+ DepositPayable -> 0x801A+ LongTermNationalBondsPayable -> 0x801B+ ShortTermNationalBondsPayable -> 0x801C+ ReserveDepositPayable -> 0x801D+ CentralBankNotePayable -> 0x801E+ Depreciation -> 0x801F+ AmortizationExpense -> 0x8020+ SalesCost -> 0x8021+ BusinessTrip -> 0x8022+ Commutation -> 0x8023+ UtilitiesExpense -> 0x8024+ RentExpense -> 0x8025+ AdvertisingExpense -> 0x8026+ DeliveryExpenses -> 0x8027+ SuppliesExpenses -> 0x8028+ MiscellaneousExpenses -> 0x8029+ WageExpenditure -> 0x802A+ InterestExpense -> 0x802B+ TaxesExpense -> 0x802C+ ConsumptionExpenditure -> 0x802D+ SubsidyExpense -> 0x802E+ CentralBankPaymentExpense -> 0x802F+ Purchases -> 0x8030+ NetIncome -> 0x8031+ ValueAdded -> 0x8032+ SubsidyIncome -> 0x8033+ NationalBondInterestEarned -> 0x8034+ DepositInterestEarned -> 0x8035+ GrossProfit -> 0x8036+ OrdinaryProfit -> 0x8037+ InterestEarned -> 0x8038+ ReceiptFee -> 0x8039+ RentalIncome -> 0x803A+ WageEarned -> 0x803B+ TaxesRevenue -> 0x803C+ CentralBankPaymentIncome -> 0x803D+ Sales -> 0x803E+ EquityInEarningsOfInvestee -> 0x803F+ NetLoss -> 0x8040+ PettyCash -> 0x8041+ NotesReceivable -> 0x8042+ ElectronicallyRecordedReceivable -> 0x8043+ CreditCardReceivable -> 0x8044+ NotesLoansReceivable -> 0x8045+ MerchandiseInventory -> 0x8046+ AdvancesPaid -> 0x8047+ PrepaidExpenses -> 0x8048+ AccruedRevenue -> 0x8049+ OtherReceivables -> 0x804A+ PaymentsOnBehalf -> 0x804B+ SuspensePayments -> 0x804C+ ConsumptionTaxPaid -> 0x804D+ PrepaidCorporateIncomeTaxes -> 0x804E+ Land -> 0x804F+ Fixtures -> 0x8050+ Patent -> 0x8051+ Trademark -> 0x8052+ Software -> 0x8053+ CashOverShort -> 0x8054+ AccountsPayable -> 0x8055+ NotesPayable -> 0x8056+ ElectronicallyRecordedObligations -> 0x8057+ NotesLoansPayable -> 0x8058+ BankOverdraft -> 0x8059+ AdvancesReceived -> 0x805A+ UnearnedRevenue -> 0x805B+ AccruedExpenses -> 0x805C+ OtherPayables -> 0x805D+ DepositsReceived -> 0x805E+ SuspenseReceipts -> 0x805F+ ConsumptionTaxReceived -> 0x8060+ AccruedConsumptionTax -> 0x8061+ AccruedCorporateIncomeTaxes -> 0x8062+ UnpaidDividends -> 0x8063+ AllowanceForDoubtfulAccounts -> 0x8064+ AccumulatedDepreciation -> 0x8065+ LegalRetainedEarnings -> 0x8066+ CumulativeTranslationAdjustment -> 0x8067+ ProvisionForDoubtfulAccounts -> 0x8068+ BadDebtLoss -> 0x8069+ LossOnSalesOfFixedAssets -> 0x806A+ LossOnSalesOfNotesReceivable -> 0x806B+ PaymentFees -> 0x806C+ MiscellaneousLoss -> 0x806D+ CorporateIncomeTaxes -> 0x806E+ CommunicationExpenses -> 0x806F+ GainOnSalesOfFixedAssets -> 0x8070+ RecoveryOfBadDebts -> 0x8071+ MiscellaneousIncome -> 0x8072+ ReversalOfAllowanceForDoubtfulAccounts -> 0x8073+ TimeDeposits -> 0x8074+ LoansReceivable -> 0x8075+ GiftCertificatesReceived -> 0x8076+ SecurityDepositsPaid -> 0x8077+ SuppliesOnHand -> 0x8078+ ContractAssets -> 0x8079+ IncomeTaxesRefundReceivable -> 0x807A+ WorkInProcess -> 0x807B+ DeferredTaxAssets -> 0x807C+ LeasedAssets -> 0x807D+ ToolsAndInstruments -> 0x807E+ ConstructionInProgress -> 0x807F+ Goodwill -> 0x8080+ SoftwareInProgress -> 0x8081+ LongTermPrepaidExpenses -> 0x8082+ DishonoredNotesReceivable -> 0x8083+ PrepaidPensionCost -> 0x8084+ NetDefinedBenefitAsset -> 0x8085+ DepositsInSpecialAccounts -> 0x8086+ Structures -> 0x8087+ LeaseholdRights -> 0x8088+ NonOperatingNotesReceivable -> 0x8089+ NonOperatingElectronicallyRecordedReceivable -> 0x808A+ RefundLiabilities -> 0x808B+ NonOperatingNotesPayable -> 0x808C+ NonOperatingElectronicallyRecordedObligations -> 0x808D+ BonusesPayable -> 0x808E+ AllowanceForRepairs -> 0x808F+ AllowanceForProductWarranties -> 0x8090+ AllowanceForBonuses -> 0x8091+ DeferredTaxLiabilities -> 0x8092+ LeaseObligations -> 0x8093+ GuaranteeDepositsReceived -> 0x8094+ AllowanceForRetirementBenefits -> 0x8095+ LongTermOtherPayables -> 0x8096+ NetDefinedBenefitLiability -> 0x8097+ StockSubscriptionDeposits -> 0x8098+ LegalCapitalSurplus -> 0x8099+ OtherCapitalSurplus -> 0x809A+ DividendEqualizationReserve -> 0x809B+ RepairFundReserve -> 0x809C+ ConstructionFundReserve -> 0x809D+ GeneralReserve -> 0x809E+ ValuationDifferenceOnOtherSecurities -> 0x809F+ NonControllingInterests -> 0x80A0+ CapitalSurplus -> 0x80A1+ EarnedSurplus -> 0x80A2+ ServiceRevenue -> 0x80A3+ OperatingRevenue -> 0x80A4+ GainOnSalesOfSecurities -> 0x80A5+ GainOnValuationOfSecurities -> 0x80A6+ DividendsReceived -> 0x80A7+ InterestOnSecurities -> 0x80A8+ GainOnSalesOfInvestmentSecurities -> 0x80A9+ InsuranceGain -> 0x80AA+ GainOnBargainPurchase -> 0x80AB+ ReversalOfAllowanceForRepairs -> 0x80AC+ ReversalOfAllowanceForProductWarranties -> 0x80AD+ GainOnDonationOfFixedAssets -> 0x80AE+ GainOnNationalSubsidies -> 0x80AF+ GainOnConstructionGrants -> 0x80B0+ LandRentReceived -> 0x80B1+ SalesRebates -> 0x80B2+ CostOfServices -> 0x80B3+ OperatingExpenses -> 0x80B4+ InventoryShrinkageLoss -> 0x80B5+ LossOnValuationOfMerchandise -> 0x80B6+ Bonuses -> 0x80B7+ RetirementBenefitExpenses -> 0x80B8+ ProvisionForRepairs -> 0x80B9+ ProvisionForBonuses -> 0x80BA+ ProvisionForProductWarranties -> 0x80BB+ ResearchAndDevelopmentExpenses -> 0x80BC+ AmortizationOfGoodwill -> 0x80BD+ AmortizationOfSoftware -> 0x80BE+ AmortizationOfPatents -> 0x80BF+ LeaseExpenses -> 0x80C0+ IncorporationExpenses -> 0x80C1+ StockIssuanceCosts -> 0x80C2+ BusinessCommencementExpenses -> 0x80C3+ DevelopmentExpenses -> 0x80C4+ LossOnSalesOfElectronicallyRecordedReceivables -> 0x80C5+ LossOnSalesOfReceivables -> 0x80C6+ LossOnSalesOfSecurities -> 0x80C7+ LossOnValuationOfSecurities -> 0x80C8+ LossOnSalesOfInvestmentSecurities -> 0x80C9+ LossOnFire -> 0x80CA+ LossOnRetirementOfFixedAssets -> 0x80CB+ LossOnReductionOfFixedAssets -> 0x80CC+ AdditionalIncomeTaxesForPriorPeriods -> 0x80CD+ RefundOfIncomeTaxes -> 0x80CE+ PurchaseRebates -> 0x80CF+ WelfareExpenses -> 0x80D0+ MaintenanceExpenses -> 0x80D1+ StatutoryWelfareExpenses -> 0x80D2+ LandRentPaid -> 0x80D3+ InsuranceExpense -> 0x80D4+ RepairsExpense -> 0x80D5+ StorageExpenses -> 0x80D6+ MembershipFees -> 0x80D7+ IncomeSummary -> 0x80D8+ SuspenseAccount -> 0x80D9+ ForeignExchangeGains -> 0x80DA+ ForeignExchangeLosses -> 0x80DB+ ContraAccountForGuaranteeObligations -> 0x80DC+ GuaranteeObligations -> 0x80DD+ IncomeTaxesAdjustment -> 0x80DE+ BranchCurrentAccount -> 0x80DF+ HeadOfficeCurrentAccount -> 0x80E0+ NetIncomeAttributableToNCI -> 0x80E1+ NetLossAttributableToNCI -> 0x80E2+ TradingSecurities -> 0x80E3+ HeldToMaturityBonds -> 0x80E4+ SubsidiaryStocks -> 0x80E5+ AffiliateStocks -> 0x80E6+ AvailableForSaleSecurities -> 0x80E7+ ConsumptionTaxRefundReceivable -> 0x80E8+ PropertyTaxPayable -> 0x80E9+ DepositsReceivedFromOfficers -> 0x80EA+ EntertainmentExpenses -> 0x80EB+ MeetingExpenses -> 0x80EC+ NewspaperBooksExpenses -> 0x80ED+ RawMaterials -> 0x80EE+ GoodsInTransit -> 0x80EF+ SalesAllowances -> 0x80F0+ PurchaseAllowances -> 0x80F1+ ReversalOfAllowanceForBonuses -> 0x80F2+ AccountTitle -> 0xFFFF++-- | Inverse of the fixed codes, shared by every decode.+accountTitlesByCode :: Map.Map Word16 AccountTitles+accountTitlesByCode = Map.fromList [(accountCode title, title) | title <- [minBound .. maxBound]]++instance Element AccountTitles where+ {-# INLINE wildcard #-}+ wildcard = AccountTitle+++++instance BaseClass AccountTitles where+
+ src/ExchangeAlgebra/Accounting/Adjustment.hs view
@@ -0,0 +1,1021 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+ Module : ExchangeAlgebra.Accounting.Adjustment+ Copyright : (c) Kaya Akagi. 2018-2026+ Maintainer : yakagika@icloud.com++ Released under the OWL license++ == Named closing adjustments (決算整理)++ Each function computes one textbook closing adjustment of the JCCI grade-3+ or grade-2 range and returns only the entry to add. Values already in the books are+ read from the books; only values the books do not hold (months, residual+ value, rates, period-end counts) are arguments. No function calls @bar@ on+ its input or result.++ Two sources are read. A /ledger/ is the visible pre-adjustment ledger, read+ by account balance; it serves adjustments that concern a whole account,+ such as the allowance on receivables. A /source transaction/ is one+ transaction chosen by the caller, for example with @filterWithNote@; it+ serves adjustments per contract or asset, so two contracts on the same+ account are adjusted separately. A value whose transaction is outside the+ visible ledger, such as an asset acquired in an earlier period, is stated+ with 'StatedAmount'.++ Values are t'MoneyDecimal' and bases are @'HatBase' 'AccountTitles'@, the+ base of admission and the catalog. Multi-axis bases are outside 0.6.++ Amounts that need a division take an 'AdjustmentRounding'. There is no+ default: the problem statement or company policy chooses it.+ Depreciation follows a schedule: every period is rounded by the rule, and+ the final period books the whole remainder, so the schedule sums exactly+ to the depreciable amount (cost 100, residual 0, three years, round down:+ 33, 33, 34).+-}+module ExchangeAlgebra.Accounting.Adjustment+ ( -- * Rounding+ AdjustmentRounding(..)+ , RoundingDirection(..)+ , RoundingUnit+ , roundingUnit+ , roundingUnitValue+ , roundAmount+ -- * Rates and sources+ , BasisPoints+ , basisPoints+ , basisPointsValue+ , SourceAmount(..)+ , sourceAmount+ -- * Problems+ , AdjustmentProblem(..)+ -- * Cost of goods sold+ , cogsPeriodic+ , cogsThroughSalesCost+ -- * Depreciation+ , DepreciationMethod(..)+ , DepreciationTerms(..)+ , straightLineDepreciation+ , decliningBalanceDepreciation+ -- * Allowance for doubtful accounts+ , allowanceReplenishment+ , allowanceReset+ -- * Deferrals and accruals+ , prepaidExpense+ , unearnedRevenue+ , accruedInterestRevenue+ , accruedInterestExpense+ -- * Other grade-3 adjustments+ , suppliesOnHand+ , cashOverShortSettlement+ , overdraftReclassification+ , consumptionTaxSettlement+ , corporateTaxSettlement+ -- * Grade-2 depreciation and amortization+ , ProductionTerms(..)+ , unitsOfProductionDepreciation+ , AmortizationTerms(..)+ , amortizedCostAdjustment+ -- * Grade-2 allowances+ , bonusAllowanceReplenishment+ , repairAllowanceReplenishment+ , productWarrantyAllowanceReplenishment+ , retirementBenefitProvision+ -- * Securities valuation+ , tradingSecuritiesValuation+ , availableForSaleValuation+ -- * Inventory shrinkage and valuation+ , PhysicalInventory(..)+ , inventoryShrinkageAndValuation+ -- * Foreign currency translation and tax effects+ , foreignCurrencyTranslation+ , deferredTaxAssetAdjustment+ ) where++import Control.Monad (foldM, when)+import Data.List (nub)+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty+import Numeric.Natural (Natural)++import ExchangeAlgebra.Accounting.Account+ ( AccountDivision(..)+ , AccountTitles(..)+ , Side(..)+ , classifyAccountDivision+ , defaultSide+ , switchSide+ )+import ExchangeAlgebra.Accounting.Entries+ ( cogsAdjustmentEntries+ , depreciationIndirectEntry+ , depreciationDirectEntry+ , allowanceReplenishmentEntry+ , allowanceResetEntries+ , prepaidExpenseEntry+ , unearnedRevenueEntry+ , accruedRevenueEntry+ , accruedExpenseEntry+ , consumptionTaxSettlementEntry+ , corporateTaxSettlementEntries+ )+import ExchangeAlgebra.Accounting.Exchange+ ( Exchange(..)+ , ExBaseClass(..)+ , projByAccountTitle+ )+import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase(..))+import ExchangeAlgebra.Algebra.Core (Redundant(..), (.@), proj)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), Rounding(..))+import ExchangeAlgebra.Accounting.Catalog+ (BasisPoints, DepreciationMethod(..), basisPoints, basisPointsValue)+import ExchangeAlgebra.Accounting.Transaction (Entry)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++------------------------------------------------------------+-- Rounding+------------------------------------------------------------++-- | Direction toward a multiple of the rounding unit. Amounts are+-- nonnegative, so 'RoundDown' truncates.+data RoundingDirection+ = RoundDown -- ^ Toward zero (切捨).+ | RoundUp -- ^ Away from zero (切上).+ | RoundHalfUp -- ^ Nearest; a tie goes up (四捨五入).+ | RoundHalfEven -- ^ Nearest; a tie goes to the even multiple.+ deriving (Eq, Ord, Show, Enum, Bounded)++-- | A strictly positive rounding unit, such as 1 or 100. Construct it with+-- 'roundingUnit'.+newtype RoundingUnit = RoundingUnit MoneyDecimal+ deriving (Eq, Ord, Show)++-- | Check a rounding unit. A unit that is not strictly positive yields+-- 'NonPositiveRoundingUnit'.+roundingUnit :: MoneyDecimal -> Either AdjustmentProblem RoundingUnit+roundingUnit unit+ | unit > 0 = Right (RoundingUnit unit)+ | otherwise = Left (NonPositiveRoundingUnit unit)++-- | The checked unit.+roundingUnitValue :: RoundingUnit -> MoneyDecimal+roundingUnitValue (RoundingUnit unit) = unit++-- | How an adjustment turns an exact quotient into an amount.+data AdjustmentRounding+ = ExactOnly+ -- ^ Accept only quotients that are exact decimals of at most 255+ -- fractional places; otherwise fail with 'InexactAmount'.+ | RoundTo RoundingUnit RoundingDirection+ -- ^ Round to a multiple of the unit in the given direction.+ deriving (Eq, Show)++-- | Round a nonnegative exact amount by the rule. Fails with 'NegativeAmount'+-- when the amount is negative, and with 'InexactAmount' under 'ExactOnly'+-- when the amount is not exactly representable.+--+-- >>> let Right unit = roundingUnit 1+-- >>> let directions = [RoundDown, RoundUp, RoundHalfUp, RoundHalfEven]+-- >>> traverse (\direction -> roundAmount (RoundTo unit direction) (5 / 2)) directions+-- Right [2,3,3,2]+roundAmount :: AdjustmentRounding -> Rational -> Either AdjustmentProblem MoneyDecimal+roundAmount rule amount+ | amount < 0 = Left (NegativeAmount amount)+ | otherwise = case rule of+ ExactOnly -> maybe (Left (InexactAmount amount)) Right (roundRational Nearest amount)+ RoundTo (RoundingUnit unit) direction -> Right (fromInteger multiple * unit)+ where+ quotient :: Rational+ quotient = amount / toRational unit+ multiple :: Integer+ multiple = case direction of+ RoundDown -> floor quotient+ RoundUp -> ceiling quotient+ RoundHalfUp -> floor (quotient + 1 / 2)+ RoundHalfEven -> round quotient++------------------------------------------------------------+-- Sources+------------------------------------------------------------++-- | Where a contract or asset amount comes from.+data SourceAmount+ = AmountIn AccountTitles Entry+ -- ^ The net increase of the account in the source transaction: its+ -- @Not@ postings minus its @Hat@ postings. For example, the rent paid+ -- in @12000 .\@ Not :< RentExpense .+ 12000 .\@ Hat :< Cash@ is+ -- @AmountIn RentExpense@ of that transaction.+ | StatedAmount MoneyDecimal+ -- ^ An amount whose transaction is outside the visible ledger.+ deriving (Eq, Show)++-- | Read a source amount. Fails with 'NoSourceAmount' when the net increase+-- is not strictly positive, and with 'NonPositiveArgument' when a stated+-- amount is not strictly positive.+--+-- >>> sourceAmount (AmountIn RentExpense (12000 .@ Not :< RentExpense))+-- Right 12000+sourceAmount :: SourceAmount -> Either AdjustmentProblem MoneyDecimal+sourceAmount (AmountIn account transaction)+ | account == AccountTitle || amount <= 0 = Left (NoSourceAmount account)+ | otherwise = Right amount+ where+ amount :: MoneyDecimal+ amount = norm (proj [Not :< account] transaction) - norm (proj [Hat :< account] transaction)+sourceAmount (StatedAmount amount)+ | amount > 0 = Right amount+ | otherwise = Left (NonPositiveArgument amount)++------------------------------------------------------------+-- Problems+------------------------------------------------------------++-- | Reason an adjustment cannot be computed. The entry is never partial.+data AdjustmentProblem+ = NonPositiveRoundingUnit MoneyDecimal+ -- ^ The rounding unit is zero or negative.+ | NegativeAmount Rational+ -- ^ The amount given to 'roundAmount' is negative.+ | InexactAmount Rational+ -- ^ Under 'ExactOnly', the exact amount is not a t'MoneyDecimal'.+ | NonPositiveArgument MoneyDecimal+ -- ^ A stated amount, ending count, or tax total that must be positive.+ | NegativeArgument MoneyDecimal+ -- ^ A residual value or period-end count that must be nonnegative.+ | NoSourceAmount AccountTitles+ -- ^ The source transaction does not increase the account.+ | UnexpectedBalanceSide AccountTitles Side+ -- ^ The account's ledger balance is on the side where the adjustment+ -- cannot read it, for example inventory with a credit balance.+ | NotInDivision AccountTitles AccountDivision+ -- ^ The account is not a concrete account of the expected division.+ | ContraAccount AccountTitles+ -- ^ The account is a contra account of the expected division, such as+ -- 'AccumulatedDepreciation' for assets; the adjustment would post it on+ -- the wrong side.+ | EmptyMonths+ -- ^ A month count that must be positive is zero.+ | MonthsExceedTerm Natural Natural+ -- ^ Months of the share, then the months of the whole term.+ | ScheduleFinished Natural Natural+ -- ^ Progress already booked (months, or units for+ -- 'unitsOfProductionDepreciation'), then the whole term in the same+ -- unit; nothing remains.+ | ResidualNotBelowCost MoneyDecimal MoneyDecimal+ -- ^ Residual value, then cost; the depreciable amount is not positive.+ | ExceedsBalance AccountTitles MoneyDecimal MoneyDecimal+ -- ^ Account, requested amount, then the balance it may not exceed.+ | TaxExceedsDifference MoneyDecimal MoneyDecimal+ -- ^ Rounded tax, then the valuation difference it may not exceed; the+ -- rounding rule leaves no nonnegative remainder for the valuation+ -- difference account.+ deriving (Eq, Show)++-- | Require a concrete account of the division with its ordinary home side.+checkAccountDivision :: AccountDivision -> AccountTitles -> Either AdjustmentProblem ()+checkAccountDivision division account+ | account == AccountTitle || classifyAccountDivision account /= division =+ Left (NotInDivision account division)+ | whichSide (Not :< account) /= defaultSide division = Left (ContraAccount account)+ | otherwise = Right ()++-- | Read a concrete account's signed balance, positive on its home side.+-- No postings are canceled or aggregated in the ledger.+accountHomeBalance :: AccountTitles -> Entry -> MoneyDecimal+accountHomeBalance account ledger+ | whichSide (Not :< account) == Debit = debitBalance+ | otherwise = negate debitBalance+ where+ selected = projByAccountTitle account ledger+ debitBalance :: MoneyDecimal+ debitBalance = norm (decL selected) - norm (decR selected)++-- | Read a concrete account's nonnegative home-side balance, rejecting a+-- balance on the opposite side with that side in the problem.+readAccountBalance :: AccountTitles -> Entry -> Either AdjustmentProblem MoneyDecimal+readAccountBalance account ledger+ | balance < 0 = Left (UnexpectedBalanceSide account (switchSide homeSide))+ | otherwise = Right balance+ where+ homeSide = whichSide (Not :< account)+ balance :: MoneyDecimal+ balance = accountHomeBalance account ledger++------------------------------------------------------------+-- Cost of goods sold+------------------------------------------------------------++-- | Periodic three-account method (三分法): reverse the beginning inventory+-- read from the 'MerchandiseInventory' debit balance into 'Purchases', and+-- move the ending inventory (argument) from 'Purchases' back to inventory.+-- Fails when the inventory balance is on the credit side, or when the ending+-- inventory is negative.+cogsPeriodic :: Entry -> MoneyDecimal -> Either AdjustmentProblem Entry+cogsPeriodic ledger ending = do+ when (ending < 0) $ Left (NegativeArgument ending)+ beginning <- readAccountBalance MerchandiseInventory ledger+ pure (cogsAdjustmentEntries (:<) beginning ending)++-- | Cost of goods sold through 'SalesCost': move the beginning inventory and+-- the 'Purchases' balance, both read from the ledger, into 'SalesCost', and+-- the ending inventory (argument) from 'SalesCost' to inventory.+cogsThroughSalesCost :: Entry -> MoneyDecimal -> Either AdjustmentProblem Entry+cogsThroughSalesCost ledger ending = do+ when (ending < 0) $ Left (NegativeArgument ending)+ beginning <- readAccountBalance MerchandiseInventory ledger+ purchases <- readAccountBalance Purchases ledger+ pure $+ beginning .@ Not :< SalesCost+ .+ beginning .@ Hat :< MerchandiseInventory+ .+ purchases .@ Not :< SalesCost+ .+ purchases .@ Hat :< Purchases+ .+ ending .@ Not :< MerchandiseInventory+ .+ ending .@ Hat :< SalesCost++------------------------------------------------------------+-- Depreciation+------------------------------------------------------------++-- | Terms of one asset's depreciation schedule, in months. The schedule+-- starts at acquisition; each element of 'termsPriorPeriods' is the number+-- of months depreciated in one earlier period, oldest first, so the earlier+-- amounts are recomputed by the same rounding rule instead of being read+-- from the accumulated balance.+data DepreciationTerms = DepreciationTerms+ { termsResidual :: MoneyDecimal -- ^ Residual value (残存価額), nonnegative.+ , termsUsefulMonths :: Natural -- ^ Useful life (耐用月数), positive.+ , termsPriorPeriods :: [Natural] -- ^ Months depreciated in each earlier period.+ , termsPeriodMonths :: Natural -- ^ Months of the current period, positive.+ } deriving (Eq, Show)++-- | Recompute a validated schedule in order and return the last charge.+-- Nonfinal periods round the quotient and cap it at the remaining amount;+-- the period that reaches the total books the whole unrounded remainder.+scheduledAmount+ :: AdjustmentRounding+ -> MoneyDecimal+ -> Natural+ -> [Natural]+ -> (MoneyDecimal -> Natural -> Rational)+ -> Either AdjustmentProblem MoneyDecimal+scheduledAmount rule total useful periods periodQuotient = do+ (_, _, amount) <- foldM advancePeriod (0, 0, 0) periods+ pure amount+ where+ advancePeriod+ :: (Natural, MoneyDecimal, MoneyDecimal)+ -> Natural+ -> Either AdjustmentProblem (Natural, MoneyDecimal, MoneyDecimal)+ advancePeriod (used, booked, _) progress = do+ let remaining = total - booked+ amount <- case compare (used + progress) useful of+ LT -> min remaining <$> roundAmount rule (periodQuotient booked progress)+ _ -> Right remaining+ pure (used + progress, booked + amount, amount)++-- | Validate an asset schedule, recompute its rounded periods, and build the+-- current entry. The quotient function receives cost, prior charges, and months.+-- The final period books the unrounded remainder.+depreciationForPeriod+ :: (MoneyDecimal -> MoneyDecimal -> Natural -> Rational)+ -> AdjustmentRounding+ -> DepreciationMethod+ -> AccountTitles+ -> SourceAmount+ -> DepreciationTerms+ -> Either AdjustmentProblem Entry+depreciationForPeriod periodQuotient rule method account source terms = do+ checkAccountDivision Assets account+ cost <- sourceAmount source+ when (residual < 0) $ Left (NegativeArgument residual)+ when (residual >= cost) $ Left (ResidualNotBelowCost residual cost)+ when (useful == 0 || current == 0 || 0 `elem` prior) $ Left EmptyMonths+ when (elapsed >= useful) $ Left (ScheduleFinished elapsed useful)+ amount <- scheduledAmount rule (cost - residual) useful (prior ++ [current])+ (periodQuotient cost)+ pure $ case method of+ IndirectMethod -> depreciationIndirectEntry (:<) amount+ DirectMethod -> depreciationDirectEntry (:<) amount account+ where+ residual = termsResidual terms+ useful = termsUsefulMonths terms+ prior = termsPriorPeriods terms+ current = termsPeriodMonths terms+ elapsed :: Natural+ elapsed = sum prior++-- | Straight-line depreciation (定額法) for this period. The cost is read from+-- the acquisition transaction or stated. Each period's amount is+-- @(cost - residual) * months / usefulMonths@ rounded by the rule, capped at+-- the depreciable amount not yet booked; the period that reaches the useful+-- life books the remainder, and months beyond the useful life are not+-- depreciated. The asset must be an asset account whose home side is the+-- debit side; a contra account yields 'ContraAccount'. 'IndirectMethod' credits+-- 'AccumulatedDepreciation'; 'DirectMethod' credits the asset account.+-- Both methods compute from cost, never from the asset's later balance.+--+-- >>> let Right unit = roundingUnit 1+-- >>> let terms = DepreciationTerms 0 36 [12, 12] 12+-- >>> let depreciate = straightLineDepreciation (RoundTo unit RoundDown) IndirectMethod+-- >>> fmap (norm . decL) (depreciate Fixtures (StatedAmount 100) terms)+-- Right 34+straightLineDepreciation+ :: AdjustmentRounding+ -> DepreciationMethod+ -> AccountTitles -- ^ Asset account.+ -> SourceAmount -- ^ Acquisition cost.+ -> DepreciationTerms+ -> Either AdjustmentProblem Entry+straightLineDepreciation rule method account source terms =+ depreciationForPeriod periodQuotient rule method account source terms+ where+ periodQuotient :: MoneyDecimal -> MoneyDecimal -> Natural -> Rational+ periodQuotient cost _ months =+ toRational (cost - termsResidual terms) * toRational months+ / toRational (termsUsefulMonths terms)++-- | Declining-balance depreciation (定率法) for this period. Each period's+-- amount is the smaller of @bookValue * rate * months / 12@ rounded by the+-- rule and @bookValue - residual@, where the book value at the start of a+-- period is the cost less the earlier amounts of the same schedule. The rule+-- rounds only the first term; the cap is not rounded. As in+-- 'straightLineDepreciation', the period that reaches the useful life books+-- the remainder down to the residual value. The guarantee rate of the 200%+-- method is not applied.+decliningBalanceDepreciation+ :: AdjustmentRounding+ -> DepreciationMethod+ -> AccountTitles -- ^ Asset account.+ -> SourceAmount -- ^ Acquisition cost.+ -> BasisPoints -- ^ Annual rate.+ -> DepreciationTerms+ -> Either AdjustmentProblem Entry+decliningBalanceDepreciation rule method account source rate terms =+ depreciationForPeriod periodQuotient rule method account source terms+ where+ periodQuotient :: MoneyDecimal -> MoneyDecimal -> Natural -> Rational+ periodQuotient cost booked months =+ toRational (cost - booked) * toRational (basisPointsValue rate)+ * toRational months / 120000++------------------------------------------------------------+-- Allowance for doubtful accounts+------------------------------------------------------------++-- | Read unique receivables and the current allowance. Return the rounded+-- estimate capped at the unrounded receivable total, then the current allowance.+readAllowanceAmounts+ :: AdjustmentRounding+ -> BasisPoints+ -> NonEmpty AccountTitles+ -> Entry+ -> Either AdjustmentProblem (MoneyDecimal, MoneyDecimal)+readAllowanceAmounts rule rate accounts ledger = do+ balances <- traverse readReceivableBalance $ nub $ NonEmpty.toList accounts+ current <- readAccountBalance AllowanceForDoubtfulAccounts ledger+ let total :: MoneyDecimal+ total = sum balances+ estimate <- roundAmount rule (toRational total * toRational (basisPointsValue rate) / 10000)+ pure (min total estimate, current)+ where+ readReceivableBalance :: AccountTitles -> Either AdjustmentProblem MoneyDecimal+ readReceivableBalance account = do+ checkAccountDivision Assets account+ readAccountBalance account ledger++-- | Replenishment method (差額補充法): the estimate is the sum of the named+-- receivables' debit balances times the rate, rounded by the rule; the entry+-- books the difference from the current 'AllowanceForDoubtfulAccounts'+-- balance as a provision or a reversal. Each receivable must be an asset.+-- The rounded estimate is capped at the unrounded sum of receivable balances.+allowanceReplenishment+ :: AdjustmentRounding+ -> BasisPoints+ -> NonEmpty AccountTitles -- ^ Receivable accounts.+ -> Entry -- ^ Ledger.+ -> Either AdjustmentProblem Entry+allowanceReplenishment rule rate accounts ledger = do+ (estimate, current) <- readAllowanceAmounts rule rate accounts ledger+ pure (allowanceReplenishmentEntry (:<) estimate current)++-- | Reset method (洗替法): release the whole current allowance and book the+-- new estimate, computed as in 'allowanceReplenishment'.+-- The rounded estimate is capped at the unrounded sum of receivable balances.+allowanceReset+ :: AdjustmentRounding+ -> BasisPoints+ -> NonEmpty AccountTitles+ -> Entry+ -> Either AdjustmentProblem Entry+allowanceReset rule rate accounts ledger = do+ (estimate, current) <- readAllowanceAmounts rule rate accounts ledger+ pure (allowanceResetEntries (:<) estimate current)++------------------------------------------------------------+-- Deferrals and accruals+------------------------------------------------------------++-- | Defer the part of a paid expense that belongs to later periods+-- (前払費用). The deferred amount is @paid * deferredMonths / coveredMonths@+-- rounded by the rule; it moves from the expense account to+-- 'PrepaidExpenses'.+-- The rounded deferral is capped at the unrounded payment amount.+prepaidExpense+ :: AdjustmentRounding+ -> AccountTitles -- ^ Expense account.+ -> SourceAmount -- ^ Payment; @AmountIn@ normally names the expense account.+ -> Natural -- ^ Months that belong to later periods.+ -> Natural -- ^ Months the payment covers.+ -> Either AdjustmentProblem Entry+prepaidExpense rule account source deferred covered = do+ checkAccountDivision Cost account+ when (deferred == 0 || covered == 0) $ Left EmptyMonths+ when (deferred > covered) $ Left (MonthsExceedTerm deferred covered)+ paid <- sourceAmount source+ amount <- roundAmount rule (toRational paid * toRational deferred / toRational covered)+ pure (prepaidExpenseEntry (:<) (min paid amount) account)++-- | Defer the part of received revenue that belongs to later periods+-- (前受収益), as in 'prepaidExpense'; the amount moves from the revenue+-- account to 'UnearnedRevenue'.+-- The rounded deferral is capped at the unrounded receipt amount.+unearnedRevenue+ :: AdjustmentRounding+ -> AccountTitles -- ^ Revenue account.+ -> SourceAmount -- ^ Receipt; @AmountIn@ normally names the revenue account.+ -> Natural -- ^ Months that belong to later periods.+ -> Natural -- ^ Months the receipt covers.+ -> Either AdjustmentProblem Entry+unearnedRevenue rule account source deferred covered = do+ checkAccountDivision Revenue account+ when (deferred == 0 || covered == 0) $ Left EmptyMonths+ when (deferred > covered) $ Left (MonthsExceedTerm deferred covered)+ paid <- sourceAmount source+ amount <- roundAmount rule (toRational paid * toRational deferred / toRational covered)+ pure (unearnedRevenueEntry (:<) (min paid amount) account)++-- | Accrue interest earned but not yet received (未収利息): principal times+-- the annual rate times @months / 12@, rounded by the rule. When the+-- principal changes during the period, call once per interval with that+-- interval's principal stated.+accruedInterestRevenue+ :: AdjustmentRounding+ -> SourceAmount -- ^ Principal; @AmountIn@ names the loan receivable.+ -> BasisPoints -- ^ Annual rate.+ -> Natural -- ^ Months accrued.+ -> Either AdjustmentProblem Entry+accruedInterestRevenue rule source rate months = do+ case source of+ StatedAmount _ -> pure ()+ AmountIn account _ -> checkAccountDivision Assets account+ when (months == 0) $ Left EmptyMonths+ principal <- sourceAmount source+ amount <- roundAmount rule+ (toRational principal * toRational (basisPointsValue rate) * toRational months / 120000)+ pure (accruedRevenueEntry (:<) amount InterestEarned)++-- | Accrue interest incurred but not yet paid (未払利息), as in+-- 'accruedInterestRevenue'. @AmountIn@ names the loan payable; its net+-- increase in the borrowing transaction is the principal.+accruedInterestExpense+ :: AdjustmentRounding+ -> SourceAmount -- ^ Principal; @AmountIn@ names the loan payable.+ -> BasisPoints -- ^ Annual rate.+ -> Natural -- ^ Months accrued.+ -> Either AdjustmentProblem Entry+accruedInterestExpense rule source rate months = do+ case source of+ StatedAmount _ -> pure ()+ AmountIn account _ -> checkAccountDivision Liability account+ when (months == 0) $ Left EmptyMonths+ principal <- sourceAmount source+ amount <- roundAmount rule+ (toRational principal * toRational (basisPointsValue rate) * toRational months / 120000)+ pure (accruedExpenseEntry (:<) amount InterestExpense)++------------------------------------------------------------+-- Other grade-3 adjustments+------------------------------------------------------------++-- | Move unused supplies (argument) from 'SuppliesExpenses' to+-- 'SuppliesOnHand' (貯蔵品). Fails when the amount exceeds the expense's+-- debit balance.+suppliesOnHand :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+suppliesOnHand amount ledger = do+ when (amount < 0) $ Left (NegativeArgument amount)+ balance <- readAccountBalance SuppliesExpenses ledger+ when (amount > balance) $ Left (ExceedsBalance SuppliesExpenses amount balance)+ pure $+ amount .@ Not :< SuppliesOnHand+ .+ amount .@ Hat :< SuppliesExpenses++-- | Settle 'CashOverShort' (現金過不足): a debit balance (shortage) goes to+-- 'MiscellaneousLoss', a credit balance (overage) to 'MiscellaneousIncome'.+-- A zero balance yields the empty entry.+cashOverShortSettlement :: Entry -> Either AdjustmentProblem Entry+cashOverShortSettlement ledger+ | balance > 0 = Right $+ balance .@ Not :< MiscellaneousLoss+ .+ balance .@ Hat :< CashOverShort+ | balance < 0 = Right $+ negate balance .@ Not :< CashOverShort+ .+ negate balance .@ Not :< MiscellaneousIncome+ | otherwise = Right mempty+ where+ balance :: MoneyDecimal+ balance = accountHomeBalance CashOverShort ledger++-- | Reclassify a credit balance of a current-deposit account to+-- 'BankOverdraft' (当座借越). A debit or zero balance yields the empty+-- entry, the correct closing procedure. Each account is judged alone; call+-- once per account without netting accounts first.+overdraftReclassification :: AccountTitles -> Entry -> Either AdjustmentProblem Entry+overdraftReclassification account ledger = do+ checkAccountDivision Assets account+ let balance :: MoneyDecimal+ balance = negate (accountHomeBalance account ledger)+ case compare balance 0 of+ GT -> Right $+ balance .@ Not :< account+ .+ balance .@ Not :< BankOverdraft+ _ -> Right mempty++-- | Settle consumption tax under the tax-exclusive method (税抜方式): offset+-- 'ConsumptionTaxPaid' and 'ConsumptionTaxReceived', both read from the+-- ledger, and book the excess as 'AccruedConsumptionTax' or, when paid+-- exceeds received, as 'ConsumptionTaxRefundReceivable'.+consumptionTaxSettlement :: Entry -> Either AdjustmentProblem Entry+consumptionTaxSettlement ledger = do+ paid <- readAccountBalance ConsumptionTaxPaid ledger+ received <- readAccountBalance ConsumptionTaxReceived ledger+ case compare paid received of+ GT -> Right $+ received .@ Hat :< ConsumptionTaxReceived+ .+ (paid - received) .@ Not :< ConsumptionTaxRefundReceivable+ .+ paid .@ Hat :< ConsumptionTaxPaid+ _ -> Right (consumptionTaxSettlementEntry (:<) paid received)++-- | Book corporate income taxes (法人税等) for the stated total, clear the+-- interim payment read from 'PrepaidCorporateIncomeTaxes', and book the+-- remainder as 'AccruedCorporateIncomeTaxes'. Fails when the interim+-- payment exceeds the total.+corporateTaxSettlement :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+corporateTaxSettlement total ledger = do+ when (total <= 0) $ Left (NonPositiveArgument total)+ interim <- readAccountBalance PrepaidCorporateIncomeTaxes ledger+ when (interim > total) $ Left (ExceedsBalance PrepaidCorporateIncomeTaxes interim total)+ pure (corporateTaxSettlementEntries (:<) total interim)++------------------------------------------------------------+-- Grade-2 depreciation and amortization+------------------------------------------------------------++-- | Terms of the units-of-production method (生産高比例法). Quantities are+-- natural numbers; the allocation is a ratio, so a fractional quantity can be+-- stated in a finer unit (for example 0.1 km as 1 unit of 100 m).+data ProductionTerms = ProductionTerms+ { productionResidual :: MoneyDecimal+ -- ^ Residual value.+ , productionTotal :: Natural+ -- ^ Estimated total units over the asset's life.+ , productionPrior :: [Natural]+ -- ^ Units used in each earlier period, oldest first.+ , productionCurrent :: Natural+ -- ^ Units used in this period.+ }+ deriving (Eq, Show)++-- | Depreciation of one asset by the units-of-production method (生産高比例法),+-- recorded by the chosen method. The cost is read as in+-- 'straightLineDepreciation'. Each period books+-- @(cost - residual) * units / total@, rounded by the rule; the period in+-- which the cumulative units reach the total books the whole remainder, so+-- the book value stops at the residual value even when the units used exceed+-- the estimate. Zero units this period yields the empty entry. Fails with+-- 'NonPositiveArgument' when the total is zero and with 'ScheduleFinished'+-- when the earlier periods already reached the total.+unitsOfProductionDepreciation+ :: AdjustmentRounding+ -> DepreciationMethod+ -> AccountTitles+ -> SourceAmount+ -> ProductionTerms+ -> Either AdjustmentProblem Entry+unitsOfProductionDepreciation rule method account source terms = do+ checkAccountDivision Assets account+ cost <- sourceAmount source+ when (residual < 0) $ Left (NegativeArgument residual)+ when (residual >= cost) $ Left (ResidualNotBelowCost residual cost)+ when (total == 0) $ Left (NonPositiveArgument 0)+ when (elapsed >= total) $ Left (ScheduleFinished elapsed total)+ if current == 0+ then pure mempty+ else do+ amount <- scheduledAmount rule (cost - residual) total (prior ++ [current])+ (periodQuotient cost)+ pure $ case method of+ IndirectMethod -> amount .@ Not :< Depreciation+ .+ amount .@ Not :< AccumulatedDepreciation+ DirectMethod -> amount .@ Not :< Depreciation+ .+ amount .@ Hat :< account+ where+ residual = productionResidual terms+ total = productionTotal terms+ prior = productionPrior terms+ current = productionCurrent terms+ elapsed :: Natural+ elapsed = sum prior+ periodQuotient :: MoneyDecimal -> MoneyDecimal -> Natural -> Rational+ periodQuotient cost _ units =+ toRational (cost - residual) * toRational units / toRational total++-- | Term of a held-to-maturity bond under the amortized cost method+-- (償却原価法, straight-line).+data AmortizationTerms = AmortizationTerms+ { amortizationMonths :: Natural+ -- ^ Months from acquisition to maturity.+ , amortizationPriorPeriods :: [Natural]+ -- ^ Months amortized in each earlier period, oldest first.+ , amortizationPeriodMonths :: Natural+ -- ^ Months of this period.+ }+ deriving (Eq, Show)++-- | Amortize the difference between the face value and the acquisition cost+-- of one 'HeldToMaturityBonds' holding (償却原価法, straight-line) against+-- 'InterestOnSecurities'. The cost is read from the source amount; the face+-- value is an argument. Each period books+-- @|face - cost| * months / amortizationMonths@, rounded by the rule, and the+-- final period books the remainder, as in 'straightLineDepreciation'. A face+-- value above cost debits the bond and credits interest; below cost, the+-- reverse. Equal values yield the empty entry. Fails with+-- 'NonPositiveArgument' for a zero face value or zero term, 'EmptyMonths' for+-- a zero period, and 'ScheduleFinished' when the earlier periods already+-- covered the term.+amortizedCostAdjustment+ :: AdjustmentRounding+ -> SourceAmount+ -> MoneyDecimal+ -> AmortizationTerms+ -> Either AdjustmentProblem Entry+amortizedCostAdjustment rule source face terms = do+ cost <- sourceAmount source+ when (face <= 0) $ Left (NonPositiveArgument face)+ when (total == 0) $ Left (NonPositiveArgument 0)+ when (current == 0 || 0 `elem` prior) $ Left EmptyMonths+ when (elapsed >= total) $ Left (ScheduleFinished elapsed total)+ amount <- scheduledAmount rule (abs (face - cost)) total (prior ++ [current])+ (periodQuotient cost)+ pure $ case compare face cost of+ GT -> amount .@ Not :< HeldToMaturityBonds+ .+ amount .@ Not :< InterestOnSecurities+ LT -> amount .@ Hat :< InterestOnSecurities+ .+ amount .@ Hat :< HeldToMaturityBonds+ EQ -> mempty+ where+ total = amortizationMonths terms+ prior = amortizationPriorPeriods terms+ current = amortizationPeriodMonths terms+ elapsed :: Natural+ elapsed = sum prior+ periodQuotient :: MoneyDecimal -> MoneyDecimal -> Natural -> Rational+ periodQuotient cost _ months =+ toRational (abs (face - cost)) * toRational months / toRational total++------------------------------------------------------------+-- Grade-2 allowances+------------------------------------------------------------++-- | Move a credit-side allowance to the nonnegative estimate using the+-- named provision and reversal accounts, preserving the individual postings.+replenishAllowance+ :: AccountTitles+ -> AccountTitles+ -> AccountTitles+ -> MoneyDecimal+ -> Entry+ -> Either AdjustmentProblem Entry+replenishAllowance allowance provision reversal estimate ledger = do+ when (estimate < 0) $ Left (NegativeArgument estimate)+ current <- readAccountBalance allowance ledger+ pure $ case compare estimate current of+ GT -> (estimate - current) .@ Not :< provision+ .+ (estimate - current) .@ Not :< allowance+ LT -> (current - estimate) .@ Hat :< allowance+ .+ (current - estimate) .@ Not :< reversal+ EQ -> mempty++-- | Replenish 'AllowanceForBonuses' (賞与引当金) to the period-end estimate+-- read against its ledger balance (差額補充法). An estimate above the balance+-- debits 'ProvisionForBonuses'; below it, the excess is credited to+-- 'ReversalOfAllowanceForBonuses'. An equal estimate yields the empty entry.+-- Fails with 'NegativeArgument' for a negative estimate and+-- 'UnexpectedBalanceSide' for a debit balance.+bonusAllowanceReplenishment :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+bonusAllowanceReplenishment = replenishAllowance+ AllowanceForBonuses ProvisionForBonuses ReversalOfAllowanceForBonuses++-- | As 'bonusAllowanceReplenishment' for 'AllowanceForRepairs' (修繕引当金),+-- with 'ProvisionForRepairs' and 'ReversalOfAllowanceForRepairs'.+repairAllowanceReplenishment :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+repairAllowanceReplenishment = replenishAllowance+ AllowanceForRepairs ProvisionForRepairs ReversalOfAllowanceForRepairs++-- | As 'bonusAllowanceReplenishment' for 'AllowanceForProductWarranties'+-- (商品保証引当金), with 'ProvisionForProductWarranties' and+-- 'ReversalOfAllowanceForProductWarranties'.+productWarrantyAllowanceReplenishment :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+productWarrantyAllowanceReplenishment = replenishAllowance+ AllowanceForProductWarranties ProvisionForProductWarranties+ ReversalOfAllowanceForProductWarranties++-- | Book the stated provision for retirement benefits (退職給付引当金): debit+-- 'RetirementBenefitExpenses', credit 'AllowanceForRetirementBenefits'. The+-- amount is given; computing the obligation is outside grade 2. Fails with+-- 'NonPositiveArgument' unless the amount is positive.+retirementBenefitProvision :: MoneyDecimal -> Either AdjustmentProblem Entry+retirementBenefitProvision amount = do+ when (amount <= 0) $ Left (NonPositiveArgument amount)+ pure $ amount .@ Not :< RetirementBenefitExpenses+ .+ amount .@ Not :< AllowanceForRetirementBenefits++------------------------------------------------------------+-- Securities valuation+------------------------------------------------------------++-- | Revalue 'TradingSecurities' (売買目的有価証券) to the stated total fair+-- value. The book value is the ledger balance. A gain is credited to+-- 'GainOnValuationOfSecurities', a loss debited to+-- 'LossOnValuationOfSecurities'; equal values yield the empty entry. The+-- entry suits the 切放法; under the 洗替法 the caller reverses it at the start+-- of the next period with 'ExchangeAlgebra.Accounting.Entries.reversingEntry'.+-- Fails with 'NegativeArgument' for a negative fair value.+tradingSecuritiesValuation :: MoneyDecimal -> Entry -> Either AdjustmentProblem Entry+tradingSecuritiesValuation fair ledger = do+ when (fair < 0) $ Left (NegativeArgument fair)+ book <- readAccountBalance TradingSecurities ledger+ pure $ case compare fair book of+ GT -> (fair - book) .@ Not :< TradingSecurities+ .+ (fair - book) .@ Not :< GainOnValuationOfSecurities+ LT -> (book - fair) .@ Not :< LossOnValuationOfSecurities+ .+ (book - fair) .@ Hat :< TradingSecurities+ EQ -> mempty++-- | Revalue 'AvailableForSaleSecurities' (その他有価証券) to the stated total+-- fair value under the full net-assets method (全部純資産直入法). The book value+-- is the ledger balance, so the ledger must already contain the reversal of+-- the previous period's valuation. With no tax rate, the whole difference goes+-- to 'ValuationDifferenceOnOtherSecurities'. With a rate, the difference times+-- the rate is rounded once by the rule and booked to 'DeferredTaxLiabilities'+-- for a gain or 'DeferredTaxAssets' for a loss, and the rest to the valuation+-- difference; 'IncomeTaxesAdjustment' is not used. The rounding rule is used+-- only with a rate. Equal values yield the empty entry. Fails with+-- 'NegativeArgument' for a negative fair value and 'TaxExceedsDifference'+-- when the rounded tax exceeds the difference.+availableForSaleValuation+ :: AdjustmentRounding+ -> Maybe BasisPoints+ -> MoneyDecimal+ -> Entry+ -> Either AdjustmentProblem Entry+availableForSaleValuation rule rate fair ledger = do+ when (fair < 0) $ Left (NegativeArgument fair)+ book <- readAccountBalance AvailableForSaleSecurities ledger+ let difference = abs (fair - book)+ tax <- case rate of+ Nothing -> Right 0+ Just taxRate -> roundAmount rule+ (toRational difference * toRational (basisPointsValue taxRate) / 10000)+ when (tax > difference) $ Left (TaxExceedsDifference tax difference)+ let remainder = difference - tax+ pure $ case compare fair book of+ GT -> difference .@ Not :< AvailableForSaleSecurities+ .+ tax .@ Not :< DeferredTaxLiabilities+ .+ remainder .@ Not :< ValuationDifferenceOnOtherSecurities+ LT -> tax .@ Not :< DeferredTaxAssets+ .+ remainder .@ Hat :< ValuationDifferenceOnOtherSecurities+ .+ difference .@ Hat :< AvailableForSaleSecurities+ EQ -> mempty++------------------------------------------------------------+-- Inventory shrinkage and valuation+------------------------------------------------------------++-- | Period-end physical inventory of one item or valuation group.+data PhysicalInventory = PhysicalInventory+ { physicalAtCost :: MoneyDecimal+ -- ^ Physical quantity times unit cost (実地棚卸高, 原価).+ , physicalAtNetRealizableValue :: MoneyDecimal+ -- ^ Physical quantity times net realizable value (正味売却価額).+ }+ deriving (Eq, Show)++-- | Book inventory shrinkage (棚卸減耗損) and the write-down of merchandise+-- (商品評価損) against 'MerchandiseInventory'. The book inventory is the+-- ledger balance, so the ledger must already contain the cost-of-goods-sold+-- entries ('cogsPeriodic'), and the list must cover every item. Shrinkage is+-- the book inventory minus the total physical inventory at cost. The+-- write-down is computed per item: cost minus net realizable value when+-- positive, otherwise zero, so a gain on one item never offsets a loss on+-- another. Transferring either loss to cost of sales is left to+-- "ExchangeAlgebra.Accounting.Transfer". Fails with 'NegativeArgument' for a+-- negative amount and 'ExceedsBalance' when the physical inventory at cost+-- exceeds the book inventory.+inventoryShrinkageAndValuation+ :: NonEmpty PhysicalInventory+ -> Entry+ -> Either AdjustmentProblem Entry+inventoryShrinkageAndValuation items ledger = do+ losses <- traverse itemLoss items+ book <- readAccountBalance MerchandiseInventory ledger+ let physical = sum (fmap physicalAtCost items)+ shrinkage = book - physical+ valuation = sum losses+ when (physical > book) $ Left (ExceedsBalance MerchandiseInventory physical book)+ pure $ shrinkage .@ Not :< InventoryShrinkageLoss+ .+ valuation .@ Not :< LossOnValuationOfMerchandise+ .+ (shrinkage + valuation) .@ Hat :< MerchandiseInventory+ where+ itemLoss :: PhysicalInventory -> Either AdjustmentProblem MoneyDecimal+ itemLoss item = do+ let cost = physicalAtCost item+ net = physicalAtNetRealizableValue item+ when (cost < 0) $ Left (NegativeArgument cost)+ when (net < 0) $ Left (NegativeArgument net)+ pure (max 0 (cost - net))++------------------------------------------------------------+-- Foreign currency translation and tax effects+------------------------------------------------------------++-- | Translate one foreign-currency receivable or payable at the closing+-- rate (外貨建債権債務の換算). The yen book value is read from the source+-- amount; the foreign amount and the closing rate (yen per unit) are+-- arguments, and their product is rounded once by the rule. For an asset+-- account an increase is a gain ('ForeignExchangeGains') and a decrease a+-- loss ('ForeignExchangeLosses'); for a liability the reverse. Equal values+-- yield the empty entry. Fails with 'NotInDivision' unless the account is+-- an asset or liability, 'ContraAccount' for a contra account, and+-- 'NegativeArgument' for a negative amount or rate.+foreignCurrencyTranslation+ :: AdjustmentRounding+ -> AccountTitles+ -> SourceAmount+ -> MoneyDecimal+ -> MoneyDecimal+ -> Either AdjustmentProblem Entry+foreignCurrencyTranslation rule account source foreignAmount rate = do+ let division :: AccountDivision+ division = classifyAccountDivision account+ case account of+ AccountTitle -> checkAccountDivision Assets account+ _ -> case division of+ Liability -> checkAccountDivision Liability account+ _ -> checkAccountDivision Assets account+ when (foreignAmount < 0) $ Left (NegativeArgument foreignAmount)+ when (rate < 0) $ Left (NegativeArgument rate)+ book <- sourceAmount source+ translated <- roundAmount rule (toRational foreignAmount * toRational rate)+ let difference = abs (translated - book)+ onIncrease :: AccountTitles+ onIncrease+ | division == Assets = ForeignExchangeGains+ | otherwise = ForeignExchangeLosses+ onDecrease :: AccountTitles+ onDecrease+ | division == Assets = ForeignExchangeLosses+ | otherwise = ForeignExchangeGains+ pure $ case compare translated book of+ GT -> difference .@ Not :< account+ .+ difference .@ Not :< onIncrease+ LT -> difference .@ Not :< onDecrease+ .+ difference .@ Hat :< account+ EQ -> mempty++-- | Adjust 'DeferredTaxAssets' (繰延税金資産) for deductible temporary+-- differences against 'IncomeTaxesAdjustment' (法人税等調整額). The target is+-- the stated period-end total of the differences times the rate, rounded once+-- by the rule; the ledger balance of 'DeferredTaxAssets' is moved to the+-- target. Differences on available-for-sale securities are excluded: they are+-- booked by 'availableForSaleValuation' without 'IncomeTaxesAdjustment', so the+-- ledger must not contain this period's valuation of those securities.+-- Equal amounts yield the empty entry. Fails with 'NegativeArgument' for a+-- negative total and 'UnexpectedBalanceSide' for a credit balance.+deferredTaxAssetAdjustment+ :: AdjustmentRounding+ -> BasisPoints+ -> MoneyDecimal+ -> Entry+ -> Either AdjustmentProblem Entry+deferredTaxAssetAdjustment rule rate differences ledger = do+ when (differences < 0) $ Left (NegativeArgument differences)+ current <- readAccountBalance DeferredTaxAssets ledger+ target <- roundAmount rule+ (toRational differences * toRational (basisPointsValue rate) / 10000)+ pure $ case compare target current of+ GT -> (target - current) .@ Not :< DeferredTaxAssets+ .+ (target - current) .@ Hat :< IncomeTaxesAdjustment+ LT -> (current - target) .@ Not :< IncomeTaxesAdjustment+ .+ (current - target) .@ Hat :< DeferredTaxAssets+ EQ -> mempty
+ src/ExchangeAlgebra/Accounting/Catalog.hs view
@@ -0,0 +1,536 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Execute closed bookkeeping requests in the Accounting layer. This+-- package-private module uses accounts, transaction identifiers, and monetary+-- values from Accounting and Algebra. Admission supplies resolved references+-- and a visible ledger, then converts the closed failures at the IO boundary.+-- Read requests, failure types, parameter checks, and 'executeCatalog' in order.+module ExchangeAlgebra.Accounting.Catalog where++import Data.Decimal (DecimalRaw(..))+import Data.Hashable (Hashable)+import Data.List.NonEmpty (NonEmpty(..))+import GHC.Generics (Generic)+import qualified Data.List.NonEmpty as NonEmpty+import Data.Ratio (denominator, numerator)+import Data.Word (Word8)+import qualified Data.Set as Set+import qualified Data.Text as Text++import ExchangeAlgebra.Algebra.Base.Representation (HatBase((:<)), Hat(..))+import ExchangeAlgebra.Algebra.Core+ ( Alg(_hatBase)+ , Redundant((.+), bar, norm)+ , toList+ )+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass(..)+ , Exchange(decL, decR)+ , projByAccountTitle+ )+import ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSemantics(..)+ , AccountSpec(..)+ , accountSemantics+ , accountSpec+ , concreteAccountTitles+ )+import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..)+ , ClosingRule(..)+ , DivisionSemantics(..)+ , Side+ )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Algebra.Transfer.Representation (TransferApplyError(..))+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Accounting.Entries as Bookkeeping+import ExchangeAlgebra.Algebra.Value (MoneyDecimal(..))+import ExchangeAlgebra.Accounting.Transaction++-- * Requests++-- | Closed operation identities used to authorize generated transactions.+data CatalogOperationKind+ = CogsKind -- ^ Cost-of-goods adjustment.+ | DepreciationIndirectKind -- ^ Indirect depreciation.+ | DepreciationDirectKind -- ^ Direct depreciation.+ | AllowanceKind -- ^ Allowance replenishment.+ | AllowanceRateKind -- ^ Rate-based allowance.+ | AllowanceResetKind -- ^ Allowance reset.+ | PrepaidKind -- ^ Prepaid expense.+ | UnearnedKind -- ^ Unearned revenue.+ | AccruedRevenueKind -- ^ Accrued revenue.+ | AccruedExpenseKind -- ^ Accrued expense.+ | ReverseEntryKind -- ^ Reverse a source entry.+ | ConsumptionTaxKind -- ^ Consumption tax settlement.+ | CorporateInterimKind -- ^ Interim corporate tax.+ | CorporateSettlementKind -- ^ Corporate tax settlement.+ | EquityEarningsKind -- ^ Equity-method earnings.+ | EquityDividendKind -- ^ Equity-method dividend.+ | EquityEntriesKind -- ^ Combined equity-method entries.+ | EquityBalanceKind -- ^ Equity-method balance query.+ | PriorErrorKind -- ^ Prior-period error correction.+ | FinalStockKind+ -- ^ Closing pairs per base, with the legacy net; debit-total evidence can increase.+ | StraightLineKind -- ^ Straight-line depreciation.+ | ConsolidateKind -- ^ Consolidation balance check.+ deriving (Eq, Ord, Show, Generic)++instance Hashable CatalogOperationKind++-- | Company input parameterized by its references. Before resolution, the+-- entity label must match each key. Admission records resolved provenance.+data EntityInputOf r = EntityInput EntityId (NonEmpty r) -- ^ Entity and its source keys.+ deriving (Eq, Show, Functor, Foldable, Traversable)++-- | Allowance rate in basis points, from 0 through 10000 inclusive. The value+-- stays decimal, so a fractional rate such as 147.5 is accepted. Construct it+-- with 'basisPoints'; the public admission module does not export the+-- constructor, so an out-of-range rate cannot reach admission.+newtype BasisPoints = BasisPoints MoneyDecimal+ deriving (Eq, Ord, Show)++-- | Check a rate in basis points. A negative rate @r@ yields+-- @'NegativeParameter' 'RateParameter' r@ and a rate above 10000 yields+-- @'RateAboveLimit' r@. The accepted range is exactly @0 <= r <= 10000@.+-- A valid rate can still make 'AllowanceRateCall' fail during execution,+-- for example with 'InexactQuotient'.+basisPoints :: MoneyDecimal -> Either CatalogParameterProblem BasisPoints+basisPoints rate+ | rate < 0 = Left (NegativeParameter RateParameter rate)+ | rate > 10000 = Left (RateAboveLimit rate)+ | otherwise = Right (BasisPoints rate)++-- | The checked rate in basis points.+basisPointsValue :: BasisPoints -> MoneyDecimal+basisPointsValue (BasisPoints rate) = rate++-- | Closed bookkeeping requests. Amount parameters use the same currency+-- unit as raw postings; allowance rates use t'BasisPoints'.+-- Account parameters and amounts are validated before any builder executes.+data CatalogCallOf r+ = CogsCall MoneyDecimal MoneyDecimal -- ^ Beginning and ending inventory amounts, in that order.+ | DepreciationIndirectCall MoneyDecimal -- ^ Indirect depreciation amount.+ | DepreciationDirectCall MoneyDecimal AccountTitles+ -- ^ Direct depreciation amount and asset account.+ | AllowanceCall MoneyDecimal MoneyDecimal -- ^ Estimate and current allowance.+ | AllowanceRateCall BasisPoints -- ^ Rate in basis points.+ | AllowanceResetCall MoneyDecimal MoneyDecimal -- ^ Estimate and current allowance.+ | PrepaidCall MoneyDecimal AccountTitles -- ^ Amount and expense account.+ | UnearnedCall MoneyDecimal AccountTitles -- ^ Amount and revenue account.+ | AccruedRevenueCall MoneyDecimal AccountTitles -- ^ Amount and revenue account.+ | AccruedExpenseCall MoneyDecimal AccountTitles -- ^ Amount and expense account.+ | ReverseEntryCall r -- ^ Exact key of the entry to reverse.+ | ConsumptionTaxCall MoneyDecimal MoneyDecimal -- ^ Paid and received amounts.+ | CorporateInterimCall MoneyDecimal -- ^ Interim tax amount.+ | CorporateSettlementCall MoneyDecimal MoneyDecimal -- ^ Total tax, then interim payment.+ | EquityEarningsCall MoneyDecimal -- ^ Share of earnings.+ | EquityDividendCall MoneyDecimal -- ^ Dividend amount.+ | EquityEntriesCall MoneyDecimal MoneyDecimal -- ^ Earnings share and dividend.+ | EquityBalanceCall -- ^ Query the equity-method balance.+ | PriorErrorCall -- ^ Current amount, prior amount, expense, then asset.+ MoneyDecimal MoneyDecimal AccountTitles AccountTitles+ | FinalStockCall+ -- ^ Close the visible ledger with a reversal and retained-earnings posting per base.+ -- The net equals the legacy closing expression; debit-total evidence can increase.+ | StraightLineCall -- ^ Asset, annual amount, then depreciation method.+ AccountTitles MoneyDecimal DepreciationMethod+ | ConsolidateCall (NonEmpty (EntityInputOf r)) (NonEmpty r) -- ^ Entities and elimination keys.+ deriving (Eq, Show, Functor, Foldable, Traversable)++-- | How a depreciation charge reaches the asset. The direct method credits+-- the asset account; the indirect method credits accumulated depreciation.+data DepreciationMethod+ = DirectMethod -- ^ Credit the asset account itself.+ | IndirectMethod -- ^ Credit 'AccumulatedDepreciation'.+ deriving (Eq, Ord, Show)++-- | Unresolved catalog request. This alias does not introduce a distinct type.+type CatalogCall = CatalogCallOf TxKey++-- | Unresolved entity input. This alias does not introduce a distinct type.+type EntityInput = EntityInputOf TxKey++-- * Failures and comparisons++-- | Names of catalog call parameters, in the order of the call fields.+data CatalogParameter+ = AmountParameter -- ^ Main amount of the call.+ | BeginningInventoryParameter -- ^ Beginning inventory amount.+ | EndingInventoryParameter -- ^ Ending inventory amount.+ | EstimateParameter -- ^ Estimated allowance.+ | CurrentParameter -- ^ Current allowance or current amount.+ | PriorParameter -- ^ Prior-period amount.+ | RateParameter -- ^ Allowance rate in basis points.+ | PaidParameter -- ^ Consumption tax paid.+ | ReceivedParameter -- ^ Consumption tax received.+ | TotalParameter -- ^ Total corporate tax.+ | InterimParameter -- ^ Interim corporate tax payment.+ | ShareParameter -- ^ Share of earnings.+ | DividendParameter -- ^ Dividend amount.+ | AnnualParameter -- ^ Annual depreciation amount.+ | AssetAccountParameter -- ^ Asset account.+ | ExpenseAccountParameter -- ^ Expense account.+ | RevenueAccountParameter -- ^ Revenue account.+ | PriorAssetAccountParameter -- ^ Asset account of a prior-period correction.+ deriving (Eq, Ord, Show)++-- | Two amounts that must be equal: the expected amount and the actual amount.+data AmountMismatch = AmountMismatch+ { expectedAmount :: MoneyDecimal -- ^ Amount required by the source.+ , actualAmount :: MoneyDecimal -- ^ Amount found in the submission.+ }+ deriving (Eq, Show)++-- | An amount compared with a limit. The constructor that holds this record+-- decides whether the limit is a lower or an upper bound.+data AmountLimit = AmountLimit+ { limitAmount :: MoneyDecimal -- ^ Bound given by another parameter.+ , limitedAmount :: MoneyDecimal -- ^ Amount compared with the bound.+ }+ deriving (Eq, Show)++-- | Debit and credit totals of an entry that does not balance.+data DebitCreditTotals = DebitCreditTotals+ { debitTotal :: MoneyDecimal -- ^ Sum of the debit side.+ , creditTotal :: MoneyDecimal -- ^ Sum of the credit side.+ }+ deriving (Eq, Show)++-- | Invalid parameters of one catalog call, found before any builder runs.+-- One problem is reported per failed check, in the order of the call fields.+data CatalogParameterProblem+ = NonPositiveParameter CatalogParameter MoneyDecimal+ -- ^ Parameter must be positive.+ | NegativeParameter CatalogParameter MoneyDecimal+ -- ^ Parameter must be nonnegative.+ | AccountOutsideDivision CatalogParameter AccountTitles AccountDivision+ -- ^ Account is not a concrete account of the expected division.+ | RateAboveLimit MoneyDecimal+ -- ^ Allowance rate exceeds 10000 basis points. Reported by 'basisPoints'.+ | ReceivedBelowPaid AmountLimit+ -- ^ Received tax is below the paid tax, which is the lower bound.+ | InterimExceedsTotal AmountLimit+ -- ^ Interim tax exceeds the total tax, which is the upper bound.+ | BlankReferenceKey TxKey+ -- ^ Referenced transaction key is blank.+ | TooFewEntities Int+ -- ^ Consolidation names fewer than two distinct entities.+ | RepeatedEntity EntityId+ -- ^ First entity named twice in one consolidation.+ | RepeatedReference TxKey+ -- ^ First transaction key referenced twice in one call.+ deriving (Eq, Show)++-- | Failures of a catalog builder whose parameters were valid.+data CatalogExecutionProblem+ = InexactQuotient MoneyDecimal MoneyDecimal+ -- ^ Dividend and divisor of a quotient that is not exact.+ | ImbalancedConsolidation DebitCreditTotals+ -- ^ Consolidation entry does not balance.+ | AbnormalBalance AccountTitles+ -- ^ Account balance is on the side opposite to its normal side.+ | ClosingFailed (HatBase AccountTitles)+ -- ^ Base whose balance closing could not close. With the exact+ -- t'MoneyDecimal' value type this does not occur; it keeps the+ -- closing builder total.+ deriving (Eq, Show)++-- * Metadata and parameter checks++-- | Map a closed call to the registry's operation identity.+catalogKind :: CatalogCallOf r -> CatalogOperationKind+catalogKind call = case call of+ CogsCall _ _ -> CogsKind+ DepreciationIndirectCall _ -> DepreciationIndirectKind+ DepreciationDirectCall _ _ -> DepreciationDirectKind+ AllowanceCall _ _ -> AllowanceKind+ AllowanceRateCall _ -> AllowanceRateKind+ AllowanceResetCall _ _ -> AllowanceResetKind+ PrepaidCall _ _ -> PrepaidKind+ UnearnedCall _ _ -> UnearnedKind+ AccruedRevenueCall _ _ -> AccruedRevenueKind+ AccruedExpenseCall _ _ -> AccruedExpenseKind+ ReverseEntryCall _ -> ReverseEntryKind+ ConsumptionTaxCall _ _ -> ConsumptionTaxKind+ CorporateInterimCall _ -> CorporateInterimKind+ CorporateSettlementCall _ _ -> CorporateSettlementKind+ EquityEarningsCall _ -> EquityEarningsKind+ EquityDividendCall _ -> EquityDividendKind+ EquityEntriesCall _ _ -> EquityEntriesKind+ EquityBalanceCall -> EquityBalanceKind+ PriorErrorCall _ _ _ _ -> PriorErrorKind+ FinalStockCall -> FinalStockKind+ StraightLineCall _ _ _ -> StraightLineKind+ ConsolidateCall _ _ -> ConsolidateKind++-- | Choose the direct asset or indirect accumulated-depreciation account.+depreciationAccount :: DepreciationMethod -> AccountTitles -> AccountTitles+depreciationAccount DirectMethod account = account+depreciationAccount IndirectMethod _ = AccumulatedDepreciation++-- | Declare the only account titles a builder may return, using the resolved+-- source entry carried by 'ReverseEntryCall'.+allowedAccounts :: CatalogCallOf (TxKey, Entry) -> [AccountTitles]+allowedAccounts call = case call of+ CogsCall _ _ -> [Purchases, MerchandiseInventory]+ DepreciationIndirectCall _ -> [Depreciation, AccumulatedDepreciation]+ DepreciationDirectCall _ account -> [Depreciation, account]+ AllowanceCall _ _ -> allowanceAccounts+ AllowanceRateCall _ -> allowanceAccounts+ AllowanceResetCall _ _ -> allowanceAccounts+ PrepaidCall _ account -> [PrepaidExpenses, account]+ UnearnedCall _ account -> [UnearnedRevenue, account]+ AccruedRevenueCall _ revenue -> [AccruedRevenue, revenue]+ AccruedExpenseCall _ expense -> [AccruedExpenses, expense]+ ReverseEntryCall (_, source) -> map (getAccountTitle . _hatBase) (toList source)+ ConsumptionTaxCall _ _ ->+ [ConsumptionTaxPaid, ConsumptionTaxReceived, AccruedConsumptionTax]+ CorporateInterimCall _ -> [PrepaidCorporateIncomeTaxes, Cash]+ CorporateSettlementCall _ _ ->+ [CorporateIncomeTaxes, PrepaidCorporateIncomeTaxes, AccruedCorporateIncomeTaxes]+ EquityEarningsCall _ -> [InvestmentInAssociate, EquityInEarningsOfInvestee]+ EquityDividendCall _ -> [Cash, InvestmentInAssociate]+ EquityEntriesCall _ _ -> [Cash, InvestmentInAssociate, EquityInEarningsOfInvestee]+ EquityBalanceCall -> []+ PriorErrorCall _ _ expense asset -> [RetainedEarnings, expense, asset]+ FinalStockCall -> RetainedEarnings :+ [ account+ | account <- concreteAccountTitles+ , Just spec <- [accountSpec account]+ , asClosing spec == CloseByDivision+ , asDivision spec `elem` [Cost, Revenue]+ ]+ StraightLineCall account _ direct ->+ [Depreciation, depreciationAccount direct account]+ ConsolidateCall _ _ -> []+ where+ allowanceAccounts :: [AccountTitles]+ allowanceAccounts =+ [ AllowanceForDoubtfulAccounts+ , ProvisionForDoubtfulAccounts+ , ReversalOfAllowanceForDoubtfulAccounts+ ]++-- | Require a strictly positive posting or amount parameter.+positive :: CatalogParameter -> MoneyDecimal -> [CatalogParameterProblem]+positive name amount = [NonPositiveParameter name amount | amount <= 0]++-- | Require a nonnegative monetary parameter.+nonnegative :: CatalogParameter -> MoneyDecimal -> [CatalogParameterProblem]+nonnegative name amount = [NegativeParameter name amount | amount < 0]++-- | Require a canonical concrete account in the requested statement division.+accountRole :: AccountDivision -> CatalogParameter -> AccountTitles -> [CatalogParameterProblem]+accountRole division name account =+ [AccountOutsideDivision name account division |+ account `notElem` concreteAccountTitles+ || fmap asemDivisionSemantics (accountSemantics account)+ /= Just (StatementDivision division)]++-- | Reject blank reference coordinates at the typed boundary.+keyProblems :: TxKey -> [CatalogParameterProblem]+keyProblems key@(TxKey (EntityId entity) (PeriodId period) (TxId transaction)) =+ [BlankReferenceKey key |+ any Text.null [entity, period, transaction]]++-- | List parameter violations while retaining independent diagnostics.+parameterProblems :: CatalogCall -> [CatalogParameterProblem]+parameterProblems call = case call of+ CogsCall beginning ending -> nonnegative BeginningInventoryParameter beginning+ ++ nonnegative EndingInventoryParameter ending+ DepreciationIndirectCall amount -> positive AmountParameter amount+ DepreciationDirectCall amount asset -> positive AmountParameter amount+ ++ accountRole Assets AssetAccountParameter asset+ AllowanceCall estimate current -> nonnegative EstimateParameter estimate+ ++ nonnegative CurrentParameter current+ AllowanceRateCall _ -> [] -- 'basisPoints' checks the rate.+ AllowanceResetCall estimate current -> nonnegative EstimateParameter estimate+ ++ nonnegative CurrentParameter current+ PrepaidCall amount expense -> positive AmountParameter amount+ ++ accountRole Cost ExpenseAccountParameter expense+ UnearnedCall amount revenue -> positive AmountParameter amount+ ++ accountRole Revenue RevenueAccountParameter revenue+ AccruedRevenueCall amount revenue -> positive AmountParameter amount+ ++ accountRole Revenue RevenueAccountParameter revenue+ AccruedExpenseCall amount expense -> positive AmountParameter amount+ ++ accountRole Cost ExpenseAccountParameter expense+ ReverseEntryCall key -> keyProblems key+ ConsumptionTaxCall paid received -> nonnegative PaidParameter paid+ ++ nonnegative ReceivedParameter received+ ++ [ReceivedBelowPaid (AmountLimit paid received) | received < paid]+ CorporateInterimCall amount -> positive AmountParameter amount+ CorporateSettlementCall total interim -> nonnegative TotalParameter total+ ++ nonnegative InterimParameter interim+ ++ [InterimExceedsTotal (AmountLimit total interim) | interim > total]+ EquityEarningsCall share -> nonnegative ShareParameter share+ EquityDividendCall dividend -> nonnegative DividendParameter dividend+ EquityEntriesCall share dividend -> nonnegative ShareParameter share+ ++ nonnegative DividendParameter dividend+ EquityBalanceCall -> []+ PriorErrorCall current prior expense asset -> nonnegative CurrentParameter current+ ++ nonnegative PriorParameter prior+ ++ accountRole Cost ExpenseAccountParameter expense+ ++ accountRole Assets PriorAssetAccountParameter asset+ FinalStockCall -> []+ StraightLineCall asset annual _ -> accountRole Assets AssetAccountParameter asset+ ++ nonnegative AnnualParameter annual+ ConsolidateCall entities eliminations -> consolidationProblems entities eliminations++-- | Validate the distinct entities and references in a consolidation request.+consolidationProblems+ :: NonEmpty EntityInput+ -> NonEmpty TxKey+ -> [CatalogParameterProblem]+consolidationProblems entities eliminations =+ [TooFewEntities count | count < 2]+ ++ take 1 [RepeatedEntity entity+ | (seen, entity) <- zip (scanl (flip Set.insert) Set.empty labels) labels+ , Set.member entity seen]+ ++ take 1 [RepeatedReference key+ | (seen, key) <- zip (scanl (flip Set.insert) Set.empty allKeys) allKeys+ , Set.member key seen]+ ++ concatMap keyProblems allKeys+ where+ count :: Int+ count = Set.size (Set.fromList labels)+ labels :: [EntityId]+ labels = [entity | EntityInput entity _ <- NonEmpty.toList entities]+ sourceKeys :: [TxKey]+ sourceKeys = [key | EntityInput _ keys <- NonEmpty.toList entities+ , key <- NonEmpty.toList keys]+ allKeys :: [TxKey]+ allKeys = sourceKeys ++ NonEmpty.toList eliminations+++-- * Execution++-- | Convert a rational amount to a decimal without rounding, at most 255+-- fractional places. A nonterminating quotient yields a catalog error.+exactQuotient :: Rational -> Either CatalogExecutionProblem MoneyDecimal+exactQuotient rational = go 0 (numerator rational) (denominator rational)+ where+ failure :: Either CatalogExecutionProblem MoneyDecimal+ failure = Left (InexactQuotient (fromInteger (numerator rational))+ (fromInteger (denominator rational)))+ go :: Word8 -> Integer -> Integer -> Either CatalogExecutionProblem MoneyDecimal+ go places numeratorValue denominatorValue+ | denominatorValue == 1 =+ Right (MoneyDecimal (Decimal places numeratorValue))+ | places == 255 = failure+ | denominatorValue `mod` 2 == 0 =+ go (places + 1) (numeratorValue * 5) (denominatorValue `div` 2)+ | denominatorValue `mod` 5 == 0 =+ go (places + 1) (numeratorValue * 2) (denominatorValue `div` 5)+ | otherwise = failure++-- | Execute a validated call carrying its resolved references. The entry+-- argument is the visible cumulative ledger before execution.+-- 'FinalStockCall' uses 'Closing.closingEntries', retaining a reversal and+-- retained-earnings posting per eligible base without calling 'bar'. Its net+-- equals the legacy closing expression, but debit-total evidence can increase+-- because retained-earnings postings from different bases remain separate.+-- 'AllowanceRateCall' reads balances after 'bar'. 'ConsolidateCall' checks balance only+-- after applying 'bar' to the selected entries.+--+-- >>> :set -XOverloadedStrings+-- >>> import ExchangeAlgebra.Algebra.Core ((.@))+-- >>> let key = TxKey (EntityId "A") (PeriodId "2026") (TxId "source")+-- >>> let tiny = fromRational (1 / 10^200) :: MoneyDecimal+-- >>> let rate = fromRational (1 / 10^100) :: MoneyDecimal+-- >>> let ledger = tiny .@ Not :< AccountsReceivable+-- >>> let expected = Left (InexactQuotient 1 (fromInteger (10^304)))+-- >>> fmap (executeCatalog ledger . AllowanceRateCall) (basisPoints rate) == Right expected+-- True+-- >>> fmap (executeCatalog (1 .@ Hat :< AccountsReceivable) . AllowanceRateCall) (basisPoints 1)+-- Right (Left (AbnormalBalance AccountsReceivable))+-- >>> let entities = EntityInput (EntityId "A") ((key, 1 .@ Not :< Cash) :| []) :| []+-- >>> let call = ConsolidateCall entities ((key, mempty) :| [])+-- >>> executeCatalog mempty call+-- Left (ImbalancedConsolidation (DebitCreditTotals {debitTotal = 1, creditTotal = 0}))+executeCatalog+ :: Entry+ -> CatalogCallOf (TxKey, Entry)+ -> Either CatalogExecutionProblem (Entry, Maybe MoneyDecimal)+executeCatalog ledger call = case call of+ CogsCall beginning ending -> generated (Bookkeeping.cogsAdjustmentEntries mk beginning ending)+ DepreciationIndirectCall amount -> generated (Bookkeeping.depreciationIndirectEntry mk amount)+ DepreciationDirectCall amount asset ->+ generated (Bookkeeping.depreciationDirectEntry mk amount asset)+ AllowanceCall estimate current ->+ generated (Bookkeeping.allowanceReplenishmentEntry mk estimate current)+ AllowanceRateCall rate -> do+ receivables <- balance AccountsReceivable+ current <- balance AllowanceForDoubtfulAccounts+ estimate <- exactQuotient+ (toRational receivables * toRational (basisPointsValue rate) / 10000)+ generated (Bookkeeping.allowanceReplenishmentEntry mk estimate current)+ AllowanceResetCall estimate current ->+ generated (Bookkeeping.allowanceResetEntries mk estimate current)+ PrepaidCall amount expense -> generated (Bookkeeping.prepaidExpenseEntry mk amount expense)+ UnearnedCall amount revenue -> generated (Bookkeeping.unearnedRevenueEntry mk amount revenue)+ AccruedRevenueCall amount revenue ->+ generated (Bookkeeping.accruedRevenueEntry mk amount revenue)+ AccruedExpenseCall amount expense ->+ generated (Bookkeeping.accruedExpenseEntry mk amount expense)+ ReverseEntryCall (_, entry) -> generated (Bookkeeping.reversingEntry entry)+ ConsumptionTaxCall paid received ->+ generated (Bookkeeping.consumptionTaxSettlementEntry mk paid received)+ CorporateInterimCall amount -> generated (Bookkeeping.corporateTaxInterimEntry mk amount)+ CorporateSettlementCall total interim ->+ generated (Bookkeeping.corporateTaxSettlementEntries mk total interim)+ EquityEarningsCall share -> generated (Bookkeeping.equityMethodEarningsEntry mk share)+ EquityDividendCall dividend -> generated (Bookkeeping.equityMethodDividendEntry mk dividend)+ EquityEntriesCall share dividend ->+ generated (Bookkeeping.equityMethodEntries mk share dividend)+ EquityBalanceCall -> Right (mempty, Just (Bookkeeping.equityMethodBalance ledger))+ PriorErrorCall current prior expense asset ->+ generated (Bookkeeping.priorPeriodErrorCorrection mk current prior expense asset)+ FinalStockCall -> case Closing.closingEntries ledger of+ Right entries -> generated entries+ Left (NonFiniteBalance base) -> Left (ClosingFailed base)+ Left (NonFiniteResult _ _ base) -> Left (ClosingFailed base)+ StraightLineCall asset amount method -> generated (depreciationEntry method asset amount)+ ConsolidateCall entities eliminations ->+ let selected = map snd (consolidationKeys entities eliminations)+ consolidated = bar (foldr (.+) mempty selected)+ in checkedConsolidation consolidated+ where+ mk :: Hat -> AccountTitles -> HatBase AccountTitles+ mk = (:<)+ generated :: Entry -> Either CatalogExecutionProblem (Entry, Maybe MoneyDecimal)+ generated entry = Right (entry, Nothing)+ consolidationKeys+ :: NonEmpty (EntityInputOf (TxKey, Entry))+ -> NonEmpty (TxKey, Entry)+ -> [(TxKey, Entry)]+ consolidationKeys entities eliminations =+ [key | EntityInput _ keys <- NonEmpty.toList entities+ , key <- NonEmpty.toList keys]+ ++ NonEmpty.toList eliminations+ checkedConsolidation :: Entry -> Either CatalogExecutionProblem (Entry, Maybe MoneyDecimal)+ checkedConsolidation consolidated+ | norm (decL consolidated) == norm (decR consolidated) = Right (mempty, Nothing)+ | otherwise = Left (ImbalancedConsolidation+ (DebitCreditTotals (norm (decL consolidated)) (norm (decR consolidated))))+ balance :: AccountTitles -> Either CatalogExecutionProblem MoneyDecimal+ balance account =+ let net = bar (projByAccountTitle account ledger)+ normalSide = whichSide (Not :< account)+ in checkedBalance account normalSide net+ checkedBalance+ :: AccountTitles -> Side -> Entry -> Either CatalogExecutionProblem MoneyDecimal+ checkedBalance account normalSide net+ | all ((== normalSide) . whichSide . _hatBase) (toList net) = Right (norm net)+ | otherwise = Left (AbnormalBalance account)+ depreciationEntry :: DepreciationMethod -> AccountTitles -> MoneyDecimal -> Entry+ depreciationEntry DirectMethod asset amount =+ Bookkeeping.depreciationDirectEntry mk amount asset+ depreciationEntry IndirectMethod _ amount =+ Bookkeeping.depreciationIndirectEntry mk amount
+ src/ExchangeAlgebra/Accounting/Closing.hs view
@@ -0,0 +1,314 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Module : ExchangeAlgebra.Accounting.Closing+Description : Additional closing entries and ordered settlement pairs.++This Accounting layer selects closing accounts and constructs additional+entries using generic Algebra operations. Start with 'closingEntries' for+an existing ledger; use 'settleEntries' for signed nets already computed+by the caller. Both preserve the non-account coordinates of each source.++Import this module qualified alongside "ExchangeAlgebra.Algebra.Core":++> import qualified ExchangeAlgebra.Accounting.Closing as Closing+> import ExchangeAlgebra.Algebra.Core ((.+))++'closingEntries' returns only additional entries. For @Right entries =+Closing.closingEntries ledger@, add them with @ledger .+ entries@.+Supply only postings through the closing date, including earlier closing+entries. For a journal, first use @toAlg@ from "ExchangeAlgebra.Journal.Core",+then attach the closing note to the returned entries with @(.|)@.++The posting pairs implement Definition 9 and retain the original audit trail.++== Laws++The observation @obs@ is the map from complete bases (including Hat\/Not) to+summed values after @bar@; it does not observe the singleton versus composite+constructor or sequence order. Relative tolerance means a per-base difference+of at most @1e-9 * max (abs x) (abs y)@, treating absent bases as zero.++=== L2: closing++* Subject: 'closingEntries'.+* Preconditions: valid Hat\/Not postings; the ledger includes only entries+ through the closing date and the call returns @Right entries@.+* Relation: adding the entries closes each eligible complete base exactly.+ Each reversal has an equal retained-earnings posting on the side selected+ by 'closingSide'; non-closing bases and non-account coordinates are retained.+* Observation: exact net per complete base, including 'RetainedEarnings'.+* Tolerance: none; the 'HatVal' accumulator and 'splitAccum' preserve exact nets.+* Instances: 'Double' and @MoneyDecimal@ with 'ExBaseClass' bases.++=== L3: balance++* Subject: generated transfer and closing entries.+* Preconditions: valid postings, concrete accounts, @Relabel@ rules whose+ actual target and source have equal 'whichSide'; finite totals. The+ transfer or closing operation being observed returns @Right entries@.+* Relation: debit total equals credit total. Closing entries also satisfy+ this relation, because 'closingSide' follows the account's PIMO direction.+* Observation: @norm (decL entries)@ and @norm (decR entries)@.+* Tolerance: relative @1e-9@ for 'Double'; exact for @MoneyDecimal@.+* Instances: 'Double' and @MoneyDecimal@ with 'ExBaseClass' bases.++A relabel from @Not :< Cash@ to @Not :< Sales@ violates L3's side+precondition: its cancellation and destination are both credits.++=== L5: non-negativity++* Subject: generated transfer and closing entries.+* Preconditions: non-negative valid input values; the transfer or closing+ operation being observed returns @Right entries@.+* Relation: every generated value is greater than or equal to zero.+* Observation: posting values, before any @bar@.+* Tolerance: none.+* Instances: all lawful 'HatVal' and 'HatBaseClass' instances (closing also+ requires 'ExBaseClass'). Hat reversal is never numeric negation.++-}+module ExchangeAlgebra.Accounting.Closing ( ClosingSide(..)+ , closingSide+ , closingEntries+ , SettleRule+ , retainedEarningsRule+ , SettlementBatch+ , SignedNet+ , SettleError(..)+ , settleEntries+ , settlementSteps+ ) where++import Data.Binary (Binary(..))+import Data.Hashable (Hashable)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (mapMaybe)+import GHC.Generics (Generic)+import ExchangeAlgebra.Algebra.Core ( Alg(..)+ , Redundant((.+))+ , (.@)+ , foldEntries+ )+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass(..), Hat(..))+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(..))+import ExchangeAlgebra.Accounting.Account ( AccountTitles(..)+ , accountSpec+ , asClosing+ , ClosingRule(..)+ , classifyAccountDivision+ , classifyAccountContra+ , pimoFromDivision+ , pimoFlip+ , PIMO(..)+ )+import ExchangeAlgebra.Algebra.Transfer.Representation (TransferApplyError(..))++-- * Closing entries++-- | The retained-earnings side selected by a closing account's PIMO direction.+data ClosingSide+ = ClosingKeep -- ^ IN: retain Hat/Not (revenue or contra cost).+ | ClosingFlip -- ^ OUT: reverse Hat/Not (cost or contra revenue).+ deriving (Eq, Ord, Show, Enum, Bounded, Generic)++instance Binary ClosingSide++instance Hashable ClosingSide++-- | Select the retained-earnings direction for an account eligible for closing.+-- Registry NoClose accounts, including NetIncome and NetLoss, are excluded.+closingSide :: AccountTitles -> Maybe ClosingSide+closingSide title = case accountSpec title of+ Nothing -> Nothing+ Just spec -> case asClosing spec of+ NoClose -> Nothing+ CloseByDivision -> case direction of+ IN -> Just ClosingKeep+ OUT -> Just ClosingFlip+ _ -> Nothing+ where+ direction+ | classifyAccountContra title = pimoFlip ordinaryDirection+ | otherwise = ordinaryDirection+ ordinaryDirection = pimoFromDivision (classifyAccountDivision title)++-- | Generate closing entries that close each eligible base exactly.+-- Supply only postings through the closing date. For each base whose account+-- has a 'closingSide', the Hat and Not values are accumulated without+-- rounding ('HatVal' accumulator); the exact net (Not minus Hat) decides the+-- winning side. A zero net generates nothing. Otherwise 'splitAccum' splits+-- the exact magnitude into finite positive values @d1 >= d2 >= ... >= dk@+-- whose exact sum is the net, and each @di@ becomes one pair: the reversal of+-- the base and the posting to 'RetainedEarnings'. @k = 1@ whenever the net is+-- representable in the value type. The pairs are not compressed back into one+-- pair, so the exact net of every closed base after the entries is zero.+-- This folds the source sequences (audit detail) per base, but preserves+-- separate target postings from distinct source accounts. It does not call+-- @bar@. All non-account axes are retained, and no note is attached.+--+-- 'NonFiniteBalance' is returned when an eligible base has a posting value+-- for which 'valueError' is 'Just' (non-finite or negative), or when its net+-- cannot be split ('splitAccum' returns 'Nothing': the rounded net is not+-- finite). Side sums beyond the value type's range succeed when the exact+-- net is in range. The reported base is the first in ascending 'Ord' order+-- after normalizing Hat/Not to Not. No partial result is returned.+--+-- Laws (exact arithmetic; observed with 'exactAccum'): for every closed base+-- the exact net of @x .+ c@ is zero, where @Right c = closingEntries x@;+-- the entries of @c@ have equal exact debit and credit sums; and+-- @closingEntries (x .+ c) == Right Zero@.+-- Balance collection is O(s log b), for s postings and b eligible bases.+closingEntries :: forall v b. (HatVal v, ExBaseClass b)+ => Alg v b -> Either (TransferApplyError v b) (Alg v b)+closingEntries = Map.foldlWithKey' close (Right Zero) . foldEntries collect Map.empty+ where+ collect :: Map b (Bool, Accum v, Accum v) -> v -> b+ -> Map b (Bool, Accum v, Accum v)+ collect balances value source = case (hat source, closingSide (getAccountTitle source)) of+ (Hat, Just _) -> Map.insertWith addTotals (toNot source)+ (isErrorValue value, addAccum value emptyAccum, emptyAccum) balances+ (Not, Just _) -> Map.insertWith addTotals source+ (isErrorValue value, emptyAccum, addAccum value emptyAccum) balances+ _ -> balances++ addTotals :: (Bool, Accum v, Accum v) -> (Bool, Accum v, Accum v)+ -> (Bool, Accum v, Accum v)+ addTotals (invalid, hatValue, notValue) (failed, hatTotal, notTotal) =+ (invalid || failed, mergeAccum hatValue hatTotal, mergeAccum notValue notTotal)++ close :: Either (TransferApplyError v b) (Alg v b)+ -> b+ -> (Bool, Accum v, Accum v)+ -> Either (TransferApplyError v b) (Alg v b)+ close result source (invalid, hatTotal, notTotal) = do+ entries <- result+ additions <- netEntries source invalid (mergeAccum notTotal (negateAccum hatTotal))+ pure (entries .+ additions)++ netEntries :: b -> Bool -> Accum v -> Either (TransferApplyError v b) (Alg v b)+ netEntries source invalid amount+ | invalid = Left (NonFiniteBalance source)+ | otherwise = case signAccum amount of+ Just EQ -> Right Zero+ Just LT -> splitPairs source (toHat source) (negateAccum amount)+ Just GT -> splitPairs source source amount+ Nothing -> Left (NonFiniteBalance source)++ splitPairs :: b -> b -> Accum v -> Either (TransferApplyError v b) (Alg v b)+ splitPairs source balanceBase amount = case splitAccum amount of+ Nothing -> Left (NonFiniteBalance source)+ Just values -> case closingSide (getAccountTitle balanceBase) of+ Nothing -> Right Zero+ Just side -> Right $ mconcat+ [ closingPairBy (targetSide side) RetainedEarnings value balanceBase+ | value <- values ]++ targetSide :: ClosingSide -> b -> b+ targetSide side = case side of+ ClosingKeep -> id+ ClosingFlip -> revHat++-- | A closing rule containing only its destination account title.+-- The private constructor restricts destinations to accounts compatible with+-- the closing directions.+newtype SettleRule = SettleRule AccountTitles++-- | Close eligible accounts into 'RetainedEarnings', preserving all other axes.+retainedEarningsRule :: SettleRule+retainedEarningsRule = SettleRule RetainedEarnings++-- | Settlement pairs in strictly ascending source-base order.+-- A model returns only @Posting@ built with @entry@ and 'Monoid'. Settlement+-- magnitudes can exceed the @Posted@ bound, so this type has no conversion to+-- @Posting@ and no 'Semigroup' or 'Monoid' instance.+--+-- Record each pair separately in source-base order. Combining all pairs first+-- can change the order of additions to a shared destination. For example,+-- sequential increments @T, 1, -T@ with @T = 2^53@ give 0 in 'Double', whereas+-- adding @T, -T, 1@ gives 1.+newtype SettlementBatch b = SettlementBatch [(BasePart b, Alg Double b)]++-- | A signed Not-minus-Hat net. This is not a non-negative posting magnitude.+-- As a type synonym, it does not enforce finiteness or any numeric range.+type SignedNet = Double++-- | The first non-finite input net, identified by its complete base coordinates.+data SettleError b = NonFiniteNet (BasePart b)++deriving instance Eq (BasePart b) => Eq (SettleError b)+deriving instance Show (BasePart b) => Show (SettleError b)++-- | Construct one reversal and destination pair per eligible source base.+-- Input values are signed Not-minus-Hat nets. Zero nets, accounts without a+-- closing side, and destination bases produce no pair. Every pair has two+-- finite, non-negative magnitudes equal to the absolute input net; the source+-- reversal cancels that net exactly. Other base axes are preserved.+--+-- A non-finite input, including at an excluded key, returns 'Left' with the+-- first key in ascending order. Finite magnitudes above+-- 'ExchangeAlgebra.Algebra.Value.postingUpperBound' are accepted, without passing+-- through 'ExchangeAlgebra.Algebra.Posting.posted'. No implicit @bar@ or @compress@ is+-- applied. Complexity: O(b) for b input bases.+-- Law: subject: each generated settlement pair; preconditions: all nets finite.+-- Relation: each reversal cancels its source net. For each destination base,+-- its increment is the sum of @direction * net@ over the source bases, where+-- @direction@ is +1 for 'ClosingKeep' and -1 for 'ClosingFlip'.+-- Observation: sums of @decL@ and @decR@ lifted to Rational for each pair.+-- Tolerance: exact. Instances: 'ExBaseClass' bases with Double posting values.+settleEntries :: forall b. ExBaseClass b+ => SettleRule+ -> Map (BasePart b) SignedNet+ -> Either (SettleError b) (SettlementBatch b)+settleEntries (SettleRule destination) amounts+ = case mapMaybe nonFinite (Map.toAscList amounts) of+ first : _ -> Left (NonFiniteNet first)+ [] -> Right (SettlementBatch (mapMaybe close (Map.toAscList amounts)))+ where+ finite amount = not (isNaN amount || isInfinite amount)+ nonFinite (coordinates, amount)+ | finite amount = Nothing+ | otherwise = Just coordinates+ close (coordinates, amount)+ | amount == 0 = Nothing+ | coordinates == base (setAccountTitle source destination) = Nothing+ | otherwise = case closingSide (getAccountTitle source) of+ Nothing -> Nothing+ Just side -> Just+ (coordinates, closingPairBy (targetSide side) destination (abs amount) source)+ where+ source = merge (sourceSide amount) coordinates :: b+ sourceSide amount+ | amount < 0 = Hat+ | otherwise = Not+ targetSide side = case side of+ ClosingKeep -> id+ ClosingFlip -> revHat++-- | Read the pairs in strictly ascending source-base order, without constraints+-- on the base type. Record each pair before proceeding to the next source.+-- Complexity: O(1) to expose the list; O(b) to consume b pairs.+settlementSteps :: SettlementBatch b -> [(BasePart b, Alg Double b)]+settlementSteps (SettlementBatch steps) = steps++-- | Reverse a closing balance and post it to the given destination account.+-- The first argument transforms the source to the target side: @id@ retains+-- Hat/Not, and 'revHat' reverses it. The caller must classify the source,+-- and the value must satisfy the non-negative,+-- finite posting contract of '.@'.+closingPairBy :: (HatVal v, ExBaseClass b)+ => (b -> b)+ -> AccountTitles+ -> v+ -> b+ -> Alg v b+closingPairBy targetSide targetAccount value source+ = (value .@ revHat source)+ .+ (value .@ setAccountTitle (targetSide source) targetAccount)
+ src/ExchangeAlgebra/Accounting/Consolidation.hs view
@@ -0,0 +1,526 @@+{-# LANGUAGE FlexibleContexts #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.Consolidation+Description : Validation boundary for consolidation worksheets.++A consolidation worksheet is not an entity's journal. Its adjustments are+therefore accepted as exchange-algebra elements, while this module checks that+each adjustment is independently balanced before any aggregation takes place.+This prevents two malformed adjustments from canceling each other only at the+worksheet total.++The validated value retains source trial balances and adjustments separately.+'combinedWorksheet' is an explicit projection and uses redundant-algebra+addition, so same-base postings remain separate sequences until a caller asks+for normalization. The projection itself does not carry per-posting provenance;+use 'validatedSources' and 'validatedAdjustments' when that provenance is+required.++Validation uses exact equality, consistently with the checked-conversion+boundary. Prefer an exact value type such as @MoneyDecimal@ for deterministic+worksheet validation rather than a floating-point representation.++Combine two balanced source trial balances without worksheet adjustments.+The linkage records no period result or retained-earnings movement. Combining+the sources preserves both cash postings and both capital postings.++>>> :set -XOverloadedStrings+>>> import Data.Text (Text)+>>> import Data.List.NonEmpty (NonEmpty(..))+>>> import qualified ExchangeAlgebra.Algebra.Core as Algebra+>>> import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase(..))+>>> import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+>>> import ExchangeAlgebra.Accounting.Statements.Metric (PeriodResult(..))+>>> import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+>>> type TrialBalance = Algebra.Alg MoneyDecimal (HatBase AccountTitles)+>>> let cash amount = amount Algebra..@ (Not :< Cash)+>>> let equity amount = amount Algebra..@ (Not :< CapitalStock)+>>> let capital amount = cash amount Algebra..+ equity amount :: TrialBalance+>>> let periodResult = PeriodBreakEven :: PeriodResult MoneyDecimal+>>> let resultColumns = WorksheetLinkage periodResult periodResult periodResult+>>> let retainedColumns = resultColumns NoBalance 0 NoBalance NoBalance+>>> let linkage = retainedColumns NoBalance periodResult 0 NoBalance NoBalance+>>> let parent = TrialBalanceSource ("parent" :: Text) (capital 100)+>>> let subsidiary = TrialBalanceSource ("subsidiary" :: Text) (capital 200)+>>> let sources = parent :| [subsidiary]+>>> let input = WorksheetInput sources [] linkage :: WorksheetInput Text Text MoneyDecimal+>>> let accepted = validateConsolidationWorksheet input+>>> fmap (length . validatedSources) accepted+Right 2+>>> fmap (length . Algebra.toList . combinedWorksheet) accepted+Right 4+>>> fmap (Algebra.norm . combinedWorksheet) accepted == Right 600+True+-}+module ExchangeAlgebra.Accounting.Consolidation+ ( LinkField(..)+ , TrialBalanceSource(..)+ , WorksheetAdjustment(..)+ , WorksheetLinkage(..)+ , WorksheetInput(..)+ , WorksheetError(..)+ , ValidatedWorksheet+ , validateConsolidationWorksheet+ , validatedSources+ , validatedAdjustments+ , validatedLinkage+ , combinedWorksheet+ ) where++import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List as L+import qualified Data.Map.Strict as M++import ExchangeAlgebra.Accounting.Exchange (decL, decR)+import ExchangeAlgebra.Algebra.Core.Representation+ ( Alg+ , Redundant((.+)), foldEntries+ , bases+ , vals+ )+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Accounting.Account.PostingPolicy+ ( ProcessingContext(ConsolidationWorksheet)+ , postingAllowedIn+ , postingCapabilityFor+ )+import ExchangeAlgebra.Accounting.Account.Classification+ ( PostingCapability )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Algebra.Base.Representation+ ( Hat(..), HatBase((:<)) )+import ExchangeAlgebra.Accounting.Statements.Metric (PeriodResult(..))+import ExchangeAlgebra.Accounting.TrialBalance.Balance+ ( AccountBalance(..)+ , balanceAmount+ , balancePair+ )++-- | Named linkage fields, used when reporting an invalid non-negative amount.+data LinkField+ = ProfitOrLossNetIncome+ | ProfitOrLossNetIncomeAttributableToOwners+ | StatementOfChangesNetIncomeAttributableToOwners+ | OpeningRetainedEarnings+ | RetainedEarningsDividends+ | StatementOfChangesClosingRetainedEarnings+ | BalanceSheetRetainedEarnings+ | OpeningNonControllingInterests+ | NonControllingInterestsPeriodShare+ | NonControllingInterestsDividends+ | StatementOfChangesClosingNonControllingInterests+ | BalanceSheetNonControllingInterests+ deriving (Show, Eq, Ord)++-- | One entity trial balance and its provenance identifier. A source trial+-- balance is accepted as historical input and is therefore not restricted by+-- a processing-context posting-capability gate; it is still checked for+-- structural coordinates, valid values, and exact balance.+data TrialBalanceSource source v = TrialBalanceSource+ { _sourceId :: source+ , _sourceTrialBalance :: Alg v (HatBase AccountTitles)+ }+ deriving (Show)++-- | One atomic worksheet adjustment. The algebra element need not originate+-- from a journal entry, but it must be independently balanced and use only+-- coordinates admitted by the consolidation processing context.+data WorksheetAdjustment source adjustment v = WorksheetAdjustment+ { _adjustmentId :: adjustment+ , _adjustmentSourceIds :: NonEmpty source+ , _adjustmentElement :: Alg v (HatBase AccountTitles)+ }+ deriving (Show)++-- | Cross-column equations for the statement of profit or loss (P/L),+-- statement of changes in equity (S/S), and balance sheet (B/S).+--+-- The P/L total is checked against its owners-of-parent and NCI attribution,+-- and the owners amount is then linked to S/S. Retained earnings and NCI+-- balances keep credit/debit position structural, so accumulated deficits do+-- not require negative scalar values.+--+-- These values are caller-supplied worksheet-column facts. This Land 2b type+-- validates their equations but cannot derive them from the unlabelled 'Alg'+-- alone; anchoring statement columns to a reporting projection is a later+-- reporting-layer responsibility. The roll-forwards model period result and+-- dividends only; worksheets with OCI, ownership changes, reserve transfers,+-- or other equity movements require a later reporting-layer extension.+data WorksheetLinkage v = WorksheetLinkage+ { _profitOrLossNetIncome :: PeriodResult v+ , _profitOrLossNetIncomeAttributableToOwners :: PeriodResult v+ , _statementOfChangesNetIncomeAttributableToOwners :: PeriodResult v+ , _openingRetainedEarnings :: AccountBalance v+ , _retainedEarningsDividends :: v+ , _statementOfChangesClosingRetainedEarnings :: AccountBalance v+ , _balanceSheetRetainedEarnings :: AccountBalance v+ , _openingNonControllingInterests :: AccountBalance v+ , _nonControllingInterestsPeriodShare :: PeriodResult v+ , _nonControllingInterestsDividends :: v+ , _statementOfChangesClosingNonControllingInterests+ :: AccountBalance v+ , _balanceSheetNonControllingInterests :: AccountBalance v+ }+ deriving (Show, Eq)++-- | Unvalidated consolidation worksheet input.+data WorksheetInput source adjustment v = WorksheetInput+ { _worksheetSources :: NonEmpty (TrialBalanceSource source v)+ , _worksheetAdjustments :: [WorksheetAdjustment source adjustment v]+ , _worksheetLinkage :: WorksheetLinkage v+ }+ deriving (Show)++-- | Every rejected invariant is tied to a source, adjustment, or linkage.+data WorksheetError source adjustment v+ = DuplicateSourceId source+ | DuplicateAdjustmentId adjustment+ | DuplicateAdjustmentSource adjustment source+ | UnknownAdjustmentSource adjustment source+ | InvalidSourceValue source v+ | WildcardSourceAccount source+ | WildcardSourceSide source+ | UnbalancedSourceTrialBalance source v v+ | EmptyAdjustment adjustment+ | InvalidAdjustmentValue adjustment v+ | WildcardAdjustmentAccount adjustment+ | WildcardAdjustmentSide adjustment+ | AdjustmentPostingNotAllowed adjustment AccountTitles PostingCapability+ | UnbalancedAdjustment adjustment v v+ | InvalidLinkAmount LinkField v+ | NetIncomeAttributionMismatch v v+ | OwnersPeriodResultLinkMismatch (PeriodResult v) (PeriodResult v)+ | RetainedEarningsRollForwardMismatch v v+ | BalanceSheetRetainedEarningsMismatch+ (AccountBalance v) (AccountBalance v)+ | NonControllingInterestsRollForwardMismatch v v+ | BalanceSheetNonControllingInterestsMismatch+ (AccountBalance v) (AccountBalance v)+ deriving (Show, Eq)++-- | A worksheet whose provenance, atomic balance, processing capability, and+-- cross-column linkage have all passed validation. The constructor is hidden.+data ValidatedWorksheet source adjustment v = ValidatedWorksheet+ (NonEmpty (TrialBalanceSource source v))+ [WorksheetAdjustment source adjustment v]+ (WorksheetLinkage v)++-- | Validate a consolidation worksheet without normalizing any algebra term.+validateConsolidationWorksheet+ :: (HatVal v, Ord source, Ord adjustment)+ => WorksheetInput source adjustment v+ -> Either (NonEmpty (WorksheetError source adjustment v))+ (ValidatedWorksheet source adjustment v)+validateConsolidationWorksheet input =+ case errors of+ [] -> Right (ValidatedWorksheet sources adjustments linkage)+ e : es -> Left (e :| es)+ where+ sources = _worksheetSources input+ adjustments = _worksheetAdjustments input+ linkage = _worksheetLinkage input+ errors = duplicateIdErrors sources adjustments+ ++ concatMap validateSource (toListNE sources)+ ++ concatMap (validateAdjustment (sourceIds sources)) adjustments+ ++ validateWorksheetLinkage linkage++-- | Recover the source trial balances with their provenance intact.+validatedSources+ :: ValidatedWorksheet source adjustment v+ -> NonEmpty (TrialBalanceSource source v)+validatedSources (ValidatedWorksheet sources _ _) = sources++-- | Recover the atomic adjustments with their provenance intact.+validatedAdjustments+ :: ValidatedWorksheet source adjustment v+ -> [WorksheetAdjustment source adjustment v]+validatedAdjustments (ValidatedWorksheet _ adjustments _) = adjustments++-- | Recover the validated cross-column linkage values.+validatedLinkage+ :: ValidatedWorksheet source adjustment v+ -> WorksheetLinkage v+validatedLinkage (ValidatedWorksheet _ _ linkage) = linkage++-- | Explicitly combine source trial balances and atomic adjustments.+-- Redundant-algebra addition preserves same-base posting sequences. This is a+-- calculation projection; inspect the validated source and adjustment lists+-- when provenance is required.+combinedWorksheet+ :: (HatVal v)+ => ValidatedWorksheet source adjustment v+ -> Alg v (HatBase AccountTitles)+combinedWorksheet (ValidatedWorksheet sources adjustments _) =+ L.foldl' (.+) mempty+ (map _sourceTrialBalance (toListNE sources)+ ++ map _adjustmentElement adjustments)++duplicateIdErrors+ :: (Ord source, Ord adjustment)+ => NonEmpty (TrialBalanceSource source v)+ -> [WorksheetAdjustment source adjustment v]+ -> [WorksheetError source adjustment v]+duplicateIdErrors sources adjustments =+ [ DuplicateSourceId sourceId+ | (sourceId, count) <- M.toList sourceCounts+ , count > 1+ ]+ ++ [ DuplicateAdjustmentId adjustmentId+ | (adjustmentId, count) <- M.toList adjustmentCounts+ , count > 1+ ]+ where+ sourceCounts = M.fromListWith (+)+ [ (_sourceId source, 1 :: Int) | source <- toListNE sources ]+ adjustmentCounts = M.fromListWith (+)+ [ (_adjustmentId adjustment, 1 :: Int) | adjustment <- adjustments ]++sourceIds :: Ord source+ => NonEmpty (TrialBalanceSource source v)+ -> M.Map source ()+sourceIds = M.fromList . map (\source -> (_sourceId source, ())) . toListNE++validateSource+ :: HatVal v+ => TrialBalanceSource source v+ -> [WorksheetError source adjustment v]+validateSource source = structuralErrors ++ balanceErrors+ where+ sourceId = _sourceId source+ alg = _sourceTrialBalance source+ structuralErrors =+ [ InvalidSourceValue sourceId value+ | value <- vals alg+ , isErrorValue value+ ]+ ++ [ WildcardSourceAccount sourceId+ | _ :< AccountTitle <- bases alg+ ]+ ++ [ WildcardSourceSide sourceId+ | HatNot :< _ <- bases alg+ ]+ balanceErrors+ | not (null structuralErrors) = []+ | signAccum difference /= Just EQ = [UnbalancedSourceTrialBalance sourceId debit credit]+ | otherwise = []+ where+ (debitState, creditState) = sideTotals alg+ difference = mergeAccum debitState (negateAccum creditState)+ debit = roundAccum debitState+ credit = roundAccum creditState++validateAdjustment+ :: (HatVal v, Ord source)+ => M.Map source ()+ -> WorksheetAdjustment source adjustment v+ -> [WorksheetError source adjustment v]+validateAdjustment knownSources adjustment =+ provenanceErrors ++ structuralErrors ++ balanceErrors+ where+ adjustmentId = _adjustmentId adjustment+ alg = _adjustmentElement adjustment+ structuralErrors =+ [ EmptyAdjustment adjustmentId | null (vals alg) ]+ ++ [ InvalidAdjustmentValue adjustmentId value+ | value <- vals alg+ , isErrorValue value || not (value > 0)+ ]+ ++ [ WildcardAdjustmentAccount adjustmentId+ | _ :< AccountTitle <- bases alg+ ]+ ++ [ WildcardAdjustmentSide adjustmentId+ | HatNot :< _ <- bases alg+ ]+ ++ [ AdjustmentPostingNotAllowed adjustmentId account capability+ | _ :< account <- bases alg+ , account /= AccountTitle+ , let capability = postingCapabilityFor account+ , not (postingAllowedIn ConsolidationWorksheet capability)+ ]+ refs = toListNE (_adjustmentSourceIds adjustment)+ refCounts = M.fromListWith (+) [ (sourceId, 1 :: Int) | sourceId <- refs ]+ provenanceErrors =+ [ DuplicateAdjustmentSource adjustmentId sourceId+ | (sourceId, count) <- M.toList refCounts+ , count > 1+ ]+ ++ [ UnknownAdjustmentSource adjustmentId sourceId+ | sourceId <- M.keys refCounts+ , M.notMember sourceId knownSources+ ]+ balanceErrors+ | not (null structuralErrors) = []+ | signAccum difference /= Just EQ = [UnbalancedAdjustment adjustmentId debit credit]+ | otherwise = []+ where+ (debitState, creditState) = sideTotals alg+ difference = mergeAccum debitState (negateAccum creditState)+ debit = roundAccum debitState+ credit = roundAccum creditState++validateWorksheetLinkage+ :: HatVal v+ => WorksheetLinkage v+ -> [WorksheetError source adjustment v]+validateWorksheetLinkage linkage+ | not (null invalidAmounts) = invalidAmounts+ | otherwise = attributionErrors+ ++ ownersLinkErrors+ ++ retainedEarningsErrors+ ++ balanceSheetErrors+ ++ nciErrors+ where+ invalidAmounts =+ [ InvalidLinkAmount field value+ | (field, value) <- linkageAmounts linkage+ , isErrorValue value+ ]++ totalResult = _profitOrLossNetIncome linkage+ ownersPlResult = _profitOrLossNetIncomeAttributableToOwners linkage+ ownersSsResult = _statementOfChangesNetIncomeAttributableToOwners linkage+ nciResult = _nonControllingInterestsPeriodShare linkage++ (attributionLeft, attributionRight) = attributionSides+ totalResult ownersPlResult nciResult+ attributionErrors =+ [ NetIncomeAttributionMismatch attributionLeft attributionRight+ | attributionLeft /= attributionRight+ ]++ ownersLinkErrors =+ [ OwnersPeriodResultLinkMismatch ownersPlResult ownersSsResult+ | not (periodResultEquivalent ownersPlResult ownersSsResult)+ ]++ (retainedLeft, retainedRight) = rollForwardSides+ (_openingRetainedEarnings linkage)+ ownersSsResult+ (_retainedEarningsDividends linkage)+ (_statementOfChangesClosingRetainedEarnings linkage)+ retainedEarningsErrors =+ [ RetainedEarningsRollForwardMismatch retainedLeft retainedRight+ | retainedLeft /= retainedRight+ ]++ ssClosing = _statementOfChangesClosingRetainedEarnings linkage+ bsClosing = _balanceSheetRetainedEarnings linkage+ balanceSheetErrors =+ [ BalanceSheetRetainedEarningsMismatch ssClosing bsClosing+ | not (balancePositionEquivalent ssClosing bsClosing)+ ]++ (nciLeft, nciRight) = rollForwardSides+ (_openingNonControllingInterests linkage)+ nciResult+ (_nonControllingInterestsDividends linkage)+ (_statementOfChangesClosingNonControllingInterests linkage)+ nciErrors =+ [ NonControllingInterestsRollForwardMismatch nciLeft nciRight+ | nciLeft /= nciRight+ ]+ ++ [ BalanceSheetNonControllingInterestsMismatch+ ssClosingNci bsNci+ | not (balancePositionEquivalent ssClosingNci bsNci)+ ]+ ssClosingNci =+ _statementOfChangesClosingNonControllingInterests linkage+ bsNci = _balanceSheetNonControllingInterests linkage++linkageAmounts :: Num v => WorksheetLinkage v -> [(LinkField, v)]+linkageAmounts linkage =+ [ (ProfitOrLossNetIncome, periodAmount (_profitOrLossNetIncome linkage))+ , (ProfitOrLossNetIncomeAttributableToOwners,+ periodAmount (_profitOrLossNetIncomeAttributableToOwners linkage))+ , (StatementOfChangesNetIncomeAttributableToOwners,+ periodAmount (_statementOfChangesNetIncomeAttributableToOwners linkage))+ , (OpeningRetainedEarnings,+ balanceAmount (_openingRetainedEarnings linkage))+ , (RetainedEarningsDividends, _retainedEarningsDividends linkage)+ , (StatementOfChangesClosingRetainedEarnings,+ balanceAmount (_statementOfChangesClosingRetainedEarnings linkage))+ , (BalanceSheetRetainedEarnings,+ balanceAmount (_balanceSheetRetainedEarnings linkage))+ , (OpeningNonControllingInterests,+ balanceAmount (_openingNonControllingInterests linkage))+ , (NonControllingInterestsPeriodShare,+ periodAmount (_nonControllingInterestsPeriodShare linkage))+ , (NonControllingInterestsDividends,+ _nonControllingInterestsDividends linkage)+ , (StatementOfChangesClosingNonControllingInterests,+ balanceAmount+ (_statementOfChangesClosingNonControllingInterests linkage))+ , (BalanceSheetNonControllingInterests,+ balanceAmount (_balanceSheetNonControllingInterests linkage))+ ]++periodAmount :: Num v => PeriodResult v -> v+periodAmount (PeriodProfit value) = value+periodAmount (PeriodLoss value) = value+periodAmount PeriodBreakEven = 0++periodSides :: Num v => PeriodResult v -> (v, v)+periodSides (PeriodProfit value) = (value, 0)+periodSides (PeriodLoss value) = (0, value)+periodSides PeriodBreakEven = (0, 0)++-- Total result equals owners' attribution plus NCI attribution. Moving all+-- loss-side values across the equation avoids signed scalars.+attributionSides :: Num v+ => PeriodResult v+ -> PeriodResult v+ -> PeriodResult v+ -> (v, v)+attributionSides total owners nci =+ (totalProfit + ownersLoss + nciLoss,+ totalLoss + ownersProfit + nciProfit)+ where+ (totalProfit, totalLoss) = periodSides total+ (ownersProfit, ownersLoss) = periodSides owners+ (nciProfit, nciLoss) = periodSides nci++periodResultEquivalent :: (Eq v, Num v)+ => PeriodResult v -> PeriodResult v -> Bool+periodResultEquivalent left right =+ leftProfit + rightLoss == leftLoss + rightProfit+ where+ (leftProfit, leftLoss) = periodSides left+ (rightProfit, rightLoss) = periodSides right++balancePositionEquivalent :: (Eq v, Num v)+ => AccountBalance v -> AccountBalance v -> Bool+balancePositionEquivalent left right =+ leftCredit + rightDebit == leftDebit + rightCredit+ where+ (leftDebit, leftCredit) = balancePair left+ (rightDebit, rightCredit) = balancePair right++rollForwardSides :: Num v+ => AccountBalance v+ -> PeriodResult v+ -> v+ -> AccountBalance v+ -> (v, v)+rollForwardSides opening result dividends closing = case result of+ PeriodProfit amount ->+ (openingCredit + amount + closingDebit,+ openingDebit + dividends + closingCredit)+ PeriodLoss amount ->+ (openingCredit + closingDebit,+ openingDebit + amount + dividends + closingCredit)+ PeriodBreakEven ->+ (openingCredit + closingDebit,+ openingDebit + dividends + closingCredit)+ where+ (openingDebit, openingCredit) = balancePair opening+ (closingDebit, closingCredit) = balancePair closing++sideTotals :: HatVal v => Alg v (HatBase AccountTitles) -> (Accum v, Accum v)+sideTotals alg = (sumSide (decL alg), sumSide (decR alg))+ where+ sumSide = foldEntries (\total value _ -> addAccum value total) emptyAccum++toListNE :: NonEmpty a -> [a]+toListNE (x :| xs) = x : xs
+ src/ExchangeAlgebra/Accounting/Entries.hs view
@@ -0,0 +1,624 @@+{- |+ Module : ExchangeAlgebra.Accounting.Entries+ Copyright : (c) Kaya Akagi. 2018-2026+ Maintainer : yakagika@icloud.com++ Released under the OWL license++ Package for Exchange Algebra defined by Hiroshi Deguchi.++ Exchange Algebra is an algebraic description of bookkeeping systems.+ Details are below.++ <https://www.springer.com/gp/book/9784431209850>++ <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>++ == Closing-adjustment entry builders (決算整理仕訳)++ This module provides /posting builders/ (journal-entry templates) for the+ closing adjustments taught at the elementary-bookkeeping (日商簿記 3 級) level:+ cost of goods sold under the periodic (3-account) method, depreciation, the+ allowance for doubtful accounts, deferral\/accrual (経過勘定) entries, and the+ settlement of consumption\/corporate income tax.++ Unlike "ExchangeAlgebra.Algebra.Transfer" (which /relabels existing ledger+ balances/), most closing adjustments record /new postings whose amounts come+ from outside the ledger/ (period-end inventory, estimated allowance,+ depreciation expense, …). Each builder here therefore returns a fresh,+ balanced @'Alg' v b@ value.++ === Design++ * Every builder is constructed exclusively with the smart constructor+ @('.@')@, so a zero amount normalizes to @Zero@ and a negative\/non-finite+ amount is rejected by @('.@')@'s @error@ path. No implicit @bar@\/@compress@+ is performed — builders only /generate/ new postings.++ * The base polymorphism is absorbed by an injection function @'MkBase'@ that+ the caller supplies once (see below).++ * The Hat\/Not label of each line follows the /accounting meaning/ of that+ line: an account that /increases/ uses @Not@ (so it lands on its home side),+ an account that /decreases/ uses @Hat@ (the opposite side).+ 'ExchangeAlgebra.Accounting.Exchange.whichSide'+ then places each line on the correct debit\/credit side. Consequently every+ builder is debit-credit balanced: @'norm' ('decL' x) == 'norm' ('decR' x)@.+-}++{-# LANGUAGE FlexibleContexts #-}++module ExchangeAlgebra.Accounting.Entries+ ( -- * Base injection+ MkBase+ -- * Cost of goods sold (売上原価, 3 分法)+ , cogsAdjustmentEntries+ -- * Depreciation (減価償却)+ , depreciationIndirectEntry+ , depreciationDirectEntry+ -- * Allowance for doubtful accounts (貸倒引当金)+ , allowanceReplenishmentEntry+ , allowanceResetEntries+ -- * Deferral / accrual (経過勘定)+ , prepaidExpenseEntry+ , unearnedRevenueEntry+ , accruedRevenueEntry+ , accruedExpenseEntry+ , reversingEntry+ -- * Tax settlement (消費税・法人税等)+ , consumptionTaxSettlementEntry+ , corporateTaxInterimEntry+ , corporateTaxSettlementEntries+ -- * Equity method (持分法)+ , equityMethodEarningsEntry+ , equityMethodDividendEntry+ , equityMethodEntries+ , equityMethodBalance+ -- * Prior-period error correction (前期修正)+ , priorPeriodErrorCorrection+ ) where++import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass+ , Exchange(decL, decR)+ , projByAccountTitle+ )+import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase((:<)))+import ExchangeAlgebra.Algebra.Core.Representation+ ( Alg(..)+ , Redundant((.+), norm)+ , (.@)+ , (.^)+ , bar+ )+import ExchangeAlgebra.Algebra.Value.Class (HatVal)+-- @MoneyDecimal@ is used only by the Haddock doctest examples (which run in this+-- module's import scope), not the module body; keep it imported for the doctests.+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import GHC.Stack (HasCallStack)++-- | Injection that tells a builder how to wrap a pair of v'Hat' and+-- 'AccountTitles' into the concrete base @b@ in use. The caller supplies it once.+--+-- @+-- -- single-axis base (account title only):+-- mk1 :: MkBase (HatBase AccountTitles)+-- mk1 = (':<')+--+-- -- multi-axis base, filling the extra axes with fixed values:+-- mk4 :: MkBase (HatBase (AccountTitles, Name, CountUnit, Subject))+-- mk4 h t = h :< (t, \"\", Yen, \"\")+-- @+type MkBase b = Hat -> AccountTitles -> b++-- | (借) debit-meaning convenience: the account /increases/, so it carries 'Not'+-- and lands on its home side.+{-# INLINE up #-}+up :: (HatVal v, ExBaseClass b) => MkBase b -> v -> AccountTitles -> Alg v b+up mk v t = v .@ mk Not t++-- | The account /decreases/, so it carries v'Hat' (the opposite of its home side).+{-# INLINE down #-}+down :: (HatVal v, ExBaseClass b) => MkBase b -> v -> AccountTitles -> Alg v b+down mk v t = v .@ mk Hat t++------------------------------------------------------------------+-- * Cost of goods sold (売上原価, 3 分法)+------------------------------------------------------------------++-- | Cost-of-goods-sold adjustment under the periodic\/3-account method+-- (3 分法; lecture ch.10, 24), the four "しいくりくりしい" postings:+--+-- > (借) 仕入 beg (貸) 繰越商品 beg -- opening inventory+-- > (借) 繰越商品 end (貸) 仕入 end -- closing inventory+--+-- After this entry the @Purchases@ balance equals cost of goods sold and the+-- @MerchandiseInventory@ balance equals the closing inventory.+--+-- The result is debit-credit balanced (each side sums to @beg + end@):+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = cogsAdjustmentEntries mk 100 50 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decL e)+-- 150+--+-- Complexity: O(1)+cogsAdjustmentEntries :: (HatVal v, ExBaseClass b)+ => MkBase b -- ^ base injection+ -> v -- ^ beginning inventory (期首商品棚卸高)+ -> v -- ^ ending inventory (期末商品棚卸高)+ -> Alg v b+cogsAdjustmentEntries mk beg end =+ up mk beg Purchases -- (借) 仕入 期首+ .+ down mk beg MerchandiseInventory -- (貸) 繰越商品 期首+ .+ up mk end MerchandiseInventory -- (借) 繰越商品 期末+ .+ down mk end Purchases -- (貸) 仕入 期末++------------------------------------------------------------------+-- * Depreciation (減価償却)+------------------------------------------------------------------++-- | Indirect-method depreciation (間接法; lecture ch.18):+--+-- > (借) 減価償却費 amt (貸) 減価償却累計額 amt+--+-- 'AccumulatedDepreciation' is a credit-balance valuation account (評価勘定).+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = depreciationIndirectEntry mk 150 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+depreciationIndirectEntry :: (HatVal v, ExBaseClass b)+ => MkBase b -> v -> Alg v b+depreciationIndirectEntry mk amt =+ up mk amt Depreciation -- (借) 減価償却費+ .+ up mk amt AccumulatedDepreciation -- (貸) 減価償却累計額++-- | Direct-method depreciation (直接法; lecture ch.18):+--+-- > (借) 減価償却費 amt (貸) <asset> amt+--+-- The asset's carrying amount is reduced directly. @assetTitle@ is the fixed+-- asset being depreciated (e.g. 'Fixtures', 'Building', 'Vehicle').+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = depreciationDirectEntry mk 90 Fixtures :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+depreciationDirectEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ depreciation amount+ -> AccountTitles -- ^ asset being depreciated+ -> Alg v b+depreciationDirectEntry mk amt assetTitle =+ up mk amt Depreciation -- (借) 減価償却費+ .+ down mk amt assetTitle -- (貸) <asset>++------------------------------------------------------------------+-- * Allowance for doubtful accounts (貸倒引当金)+------------------------------------------------------------------++-- | Allowance for doubtful accounts under the /差額補充法/ (replenishment;+-- lecture ch.16). Only the difference between the estimate and the current+-- balance is recorded:+--+-- * @estimate > current@: provide the shortfall —+-- @(借) 貸倒引当金繰入 / (貸) 貸倒引当金@.+-- * @estimate < current@: release the excess —+-- @(借) 貸倒引当金 / (貸) 貸倒引当金戻入@ (uses 'ReversalOfAllowanceForDoubtfulAccounts').+-- * @estimate == current@: no entry (@Zero@).+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = allowanceReplenishmentEntry mk 1400 1000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decL e)+-- 400+-- >>> allowanceReplenishmentEntry mk 2000 2000 == (Zero :: Alg MoneyDecimal (HatBase AccountTitles))+-- True+--+-- Complexity: O(1)+allowanceReplenishmentEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ estimated allowance (当期末貸倒見積額)+ -> v -- ^ current allowance balance (貸倒引当金残高)+ -> Alg v b+allowanceReplenishmentEntry mk estimate current+ | estimate >= current =+ let diff = estimate - current+ in up mk diff ProvisionForDoubtfulAccounts -- (借) 貸倒引当金繰入+ .+ up mk diff AllowanceForDoubtfulAccounts -- (貸) 貸倒引当金+ | otherwise =+ let diff = current - estimate+ in down mk diff AllowanceForDoubtfulAccounts -- (借) 貸倒引当金+ .+ up mk diff ReversalOfAllowanceForDoubtfulAccounts -- (貸) 貸倒引当金戻入++-- | Allowance for doubtful accounts under the /洗替法/ (reset; lecture ch.16):+-- release the whole current balance, then provide the whole estimate.+--+-- > (借) 貸倒引当金 current (貸) 貸倒引当金戻入 current+-- > (借) 貸倒引当金繰入 estimate (貸) 貸倒引当金 estimate+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = allowanceResetEntries mk 2400 1000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decL e)+-- 3400+--+-- Complexity: O(1)+allowanceResetEntries :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ estimated allowance for this period+ -> v -- ^ current allowance balance to release+ -> Alg v b+allowanceResetEntries mk estimate current =+ down mk current AllowanceForDoubtfulAccounts -- (借) 貸倒引当金+ .+ up mk current ReversalOfAllowanceForDoubtfulAccounts -- (貸) 貸倒引当金戻入+ .+ up mk estimate ProvisionForDoubtfulAccounts -- (借) 貸倒引当金繰入+ .+ up mk estimate AllowanceForDoubtfulAccounts -- (貸) 貸倒引当金++------------------------------------------------------------------+-- * Deferral / accrual (経過勘定; lecture ch.21)+------------------------------------------------------------------++-- | Deferral of a prepaid expense (費用の前払; 前払費用):+--+-- > (借) 前払費用 amt (貸) <expense> amt+--+-- Moves the unexpired portion of @expenseTitle@ out to 'PrepaidExpenses'. The+-- opening reversing entry of the next period is @'reversingEntry'@ of this.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = prepaidExpenseEntry mk 4000 RentExpense :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+prepaidExpenseEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ prepaid amount+ -> AccountTitles -- ^ expense being deferred+ -> Alg v b+prepaidExpenseEntry mk amt expenseTitle =+ up mk amt PrepaidExpenses -- (借) 前払費用+ .+ down mk amt expenseTitle -- (貸) <expense>++-- | Deferral of unearned revenue (収益の前受; 前受収益):+--+-- > (借) <revenue> amt (貸) 前受収益 amt+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = unearnedRevenueEntry mk 4000 RentalIncome :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+unearnedRevenueEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ unearned amount+ -> AccountTitles -- ^ revenue being deferred+ -> Alg v b+unearnedRevenueEntry mk amt revenueTitle =+ down mk amt revenueTitle -- (借) <revenue>+ .+ up mk amt UnearnedRevenue -- (貸) 前受収益++-- | Accrual of accrued revenue (収益の未収; 未収収益):+--+-- > (借) 未収収益 amt (貸) <revenue> amt+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = accruedRevenueEntry mk 2500 InterestEarned :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+accruedRevenueEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ accrued amount+ -> AccountTitles -- ^ revenue being accrued+ -> Alg v b+accruedRevenueEntry mk amt revenueTitle =+ up mk amt AccruedRevenue -- (借) 未収収益+ .+ up mk amt revenueTitle -- (貸) <revenue>++-- | Accrual of an accrued expense (費用の未払; 未払費用):+--+-- > (借) <expense> amt (貸) 未払費用 amt+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = accruedExpenseEntry mk 2500 InterestExpense :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+accruedExpenseEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ accrued amount+ -> AccountTitles -- ^ expense being accrued+ -> Alg v b+accruedExpenseEntry mk amt expenseTitle =+ up mk amt expenseTitle -- (借) <expense>+ .+ up mk amt AccruedExpenses -- (貸) 未払費用++-- | Reversing\/canceling entry, a vocabulary alias for the Hat operation+-- @('.^')@ (lecture ch.20-21, 再振替仕訳\/訂正仕訳). This is the direct+-- application of /Hat involution/: flipping Hat\/Not on every posting turns an+-- entry into its exact reversal.+--+-- * Opening reversal (再振替仕訳): @reversingEntry deferral@ at the start of the+-- next period undoes a deferral\/accrual entry.+--+-- * Correction (訂正仕訳): a mistaken entry @wrong@ is corrected by+-- @reversingEntry wrong '.+' correct@. Because @('.+')@ keeps same-base+-- postings as an ordered /sequence/ (the redundancy), the wrong entry and its+-- reversal both remain in the algebra as an /audit trail/ of the correction;+-- @'bar'@ then nets them out to the corrected balance.+--+-- @reversingEntry@ is an involution (@reversingEntry . reversingEntry = id@) and+-- @'bar' (x '.+' reversingEntry x) = Zero@ (an entry plus its reversal cancels).+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let x = accruedExpenseEntry mk 2500 InterestExpense :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> reversingEntry (reversingEntry x) == x+-- True+-- >>> bar (x .+ reversingEntry x) == Zero+-- True+--+-- Complexity: O(1) for a singleton, O(n) for an+-- 'ExchangeAlgebra.Algebra.Internal.Liner' (n base keys).+{-# INLINE reversingEntry #-}+reversingEntry :: (Redundant a v b) => a v b -> a v b+reversingEntry = (.^)++------------------------------------------------------------------+-- * Tax settlement (消費税・法人税等; lecture ch.23)+------------------------------------------------------------------++-- | Consumption-tax settlement at closing (税抜処理方式; lecture ch.23): offset+-- consumption tax received against consumption tax paid and book the unpaid+-- balance.+--+-- > (借) 仮受消費税 received (貸) 仮払消費税 paid+-- > (貸) 未払消費税 received - paid+--+-- __Out of 3-級 scope:__ @received < paid@ (a tax refund, 還付) is rejected with+-- @error@. At the elementary level the consumption tax received always exceeds+-- the consumption tax paid.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = consumptionTaxSettlementEntry mk 1000 20000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (projByAccountTitle AccruedConsumptionTax e)+-- 19000+--+-- Complexity: O(1)+consumptionTaxSettlementEntry :: (HasCallStack, HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ consumption tax paid (仮払消費税)+ -> v -- ^ consumption tax received (仮受消費税)+ -> Alg v b+consumptionTaxSettlementEntry mk paid received+ | received < paid =+ error $ "consumptionTaxSettlementEntry: received (" ++ show received+ ++ ") < paid (" ++ show paid+ ++ "); a consumption-tax refund is out of 日商簿記 3 級 scope."+ | otherwise =+ let unpaid = received - paid+ in down mk received ConsumptionTaxReceived -- (借) 仮受消費税+ .+ down mk paid ConsumptionTaxPaid -- (貸) 仮払消費税+ .+ up mk unpaid AccruedConsumptionTax -- (貸) 未払消費税++-- | Interim (mid-year) payment of corporate income tax (中間納付; lecture ch.23):+--+-- > (借) 仮払法人税等 amt (貸) 現金 amt+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = corporateTaxInterimEntry mk 500000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+corporateTaxInterimEntry :: (HatVal v, ExBaseClass b)+ => MkBase b -> v -> Alg v b+corporateTaxInterimEntry mk amt =+ up mk amt PrepaidCorporateIncomeTaxes -- (借) 仮払法人税等+ .+ down mk amt Cash -- (貸) 現金++-- | Corporate income tax settlement at closing (lecture ch.23): book the total+-- tax expense, credit the interim payment already made, and recognize the+-- unpaid balance.+--+-- > (借) 法人税等 total (貸) 仮払法人税等 interim+-- > (貸) 未払法人税等 total - interim+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = corporateTaxSettlementEntries mk 800000 500000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (projByAccountTitle AccruedCorporateIncomeTaxes e)+-- 300000+--+-- Complexity: O(1)+corporateTaxSettlementEntries :: (HasCallStack, HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ total corporate income tax (法人税等)+ -> v -- ^ interim payment already made (仮払法人税等)+ -> Alg v b+corporateTaxSettlementEntries mk total interim+ -- Same guard style as 'consumptionTaxSettlementEntry': an over-prepayment+ -- (refund position) is out of 3-級 scope, so reject it with a clear error+ -- instead of letting the negative @unpaid@ hit @('.@')@'s generic one.+ | interim > total =+ error $ "corporateTaxSettlementEntries: interim (" ++ show interim+ ++ ") > total (" ++ show total+ ++ "); a corporate-tax refund (over-prepayment) is out of 日商簿記 3 級 scope."+ | otherwise =+ let unpaid = total - interim+ in up mk total CorporateIncomeTaxes -- (借) 法人税等+ .+ down mk interim PrepaidCorporateIncomeTaxes -- (貸) 仮払法人税等+ .+ up mk unpaid AccruedCorporateIncomeTaxes -- (貸) 未払法人税等++------------------------------------------------------------------+-- * Equity method (持分法; lecture T4b)+------------------------------------------------------------------++-- | Equity-method earnings accrual (持分法による投資利益の計上):+--+-- > (借) 関係会社株式 share (貸) 持分法による投資利益 share+--+-- @share@ is the investor's proportionate share of the investee's net income+-- (= investee NI × ownership %). The investment carrying amount increases by+-- @share@.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = equityMethodEarningsEntry mk 438000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decL e)+-- 438000+--+-- Complexity: O(1)+equityMethodEarningsEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ investor's share of investee NI (持分利益)+ -> Alg v b+equityMethodEarningsEntry mk share =+ up mk share InvestmentInAssociate -- (借) 関係会社株式+ .+ up mk share EquityInEarningsOfInvestee -- (貸) 持分法による投資利益++-- | Equity-method dividend received (受取配当による投資簿価の減額):+--+-- > (借) 現金 div (貸) 関係会社株式 div+--+-- Under the equity method a dividend received from the investee is /not/ income;+-- it reduces the carrying amount of the investment.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = equityMethodDividendEntry mk 800000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decL e)+-- 800000+--+-- Complexity: O(1)+equityMethodDividendEntry :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ dividend received (受取配当金)+ -> Alg v b+equityMethodDividendEntry mk div' =+ up mk div' Cash -- (借) 現金+ .+ down mk div' InvestmentInAssociate -- (貸) 関係会社株式++-- | Combined equity-method closing entries: record the earnings accrual+-- then the dividend reduction.+--+-- > (借) 関係会社株式 share (貸) 持分法による投資利益 share+-- > (借) 現金 div (貸) 関係会社株式 div+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = equityMethodEntries mk 438000 800000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+--+-- Complexity: O(1)+equityMethodEntries :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ investor's share of investee NI (持分利益)+ -> v -- ^ dividend received (受取配当金)+ -> Alg v b+equityMethodEntries mk share div' =+ equityMethodEarningsEntry mk share+ .+ equityMethodDividendEntry mk div'++-- | Equity-method carrying-amount balance (関係会社株式の簿価残高).+--+-- Projects all @'InvestmentInAssociate'@ postings from an accumulated @'Alg'@+-- and returns their @'norm'@ (= net carrying amount). The caller accumulates+-- the initial acquisition entry plus any 'equityMethodEarningsEntry' and+-- 'equityMethodDividendEntry' calls; this function reads the result without+-- recomputing it by hand — /correct-by-construction/.+--+-- Example (#18 anchor doctest):+-- acquisition cost 2,400,000 + share of NI 0.30 × 1,460,000 (= 438,000)+-- − dividend received 800,000 = carrying amount 2,038,000.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> -- initial acquisition: Dr 関係会社株式 2,400,000 / Cr Cash 2,400,000+-- >>> let acq = up mk 2400000 InvestmentInAssociate .+ down mk 2400000 Cash :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> let entries = equityMethodEntries mk 438000 800000 :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> let ledger = acq .+ entries+-- >>> equityMethodBalance ledger+-- 2038000+--+-- Complexity: O(s) (s = number of scalar entries in the accumulated algebra)+equityMethodBalance :: (HatVal v, ExBaseClass b) => Alg v b -> v+equityMethodBalance = norm . bar . projByAccountTitle InvestmentInAssociate++------------------------------------------------------------------+-- * Prior-period error correction (前期修正)+------------------------------------------------------------------++-- | Prior-period error correction (前期修正・誤謬訂正; #15 Briggs anchor).+--+-- Records the correction of an error discovered in a prior period:+-- the current-period portion goes to an expense account, while the+-- prior-period portion bypasses the income statement and is charged+-- directly to 'RetainedEarnings' (繰越利益剰余金) — conforming to+-- IAS 8 \/ ASC 250-10 retrospective-restatement treatment.+--+-- > (借) \<expense\> current (貸) \<asset\> current + prior+-- > (借) RetainedEarnings prior+--+-- Structural guarantee (thesis property): the prior-period amount is+-- /always/ routed to 'RetainedEarnings'; the caller cannot accidentally+-- book it to an income-statement account — the parameter type forces the+-- split.+--+-- 'RetainedEarnings' is a credit-balance equity account (@home = Credit@),+-- so a reduction (debit-side charge) is recorded with @down@ (@Hat@),+-- which places it on the debit side — correct-by-construction.+--+-- The credit leg (asset reduction) is collapsed into one posting+-- @down mk (current + prior) assetAcc@ because @'Num' v@ is available+-- from the @'HatVal'@ superclass, making value addition clean; the+-- two-line @.+@ alternative is equally valid under the seq-redundancy+-- principle but the single line is more readable here.+--+-- === Example (#15 anchor: patent 55,000 \/ 10 yr, discovered in 2028)+--+-- * Patent cost 55,000, useful life 10 years → annual amortization 5,500.+-- * Error discovered in 2028: 2 prior years (2026, 2027) were not amortized.+-- * Current-year amortization 5,500 → expense (@AmortizationExpense@).+-- * Prior 2 years 11,000 → directly to @RetainedEarnings@ (前期修正).+-- * Credit @Patent@ 16,500 total.+--+-- >>> let mk = (:<) :: MkBase (HatBase AccountTitles)+-- >>> let e = priorPeriodErrorCorrection mk 5500 11000 AmortizationExpense Patent :: Alg MoneyDecimal (HatBase AccountTitles)+-- >>> norm (decL e) == norm (decR e)+-- True+-- >>> norm (decR e)+-- 16500+-- >>> norm (decL e)+-- 16500+--+-- Complexity: O(1)+priorPeriodErrorCorrection :: (HatVal v, ExBaseClass b)+ => MkBase b+ -> v -- ^ current-period portion (当期費用; → @expenseAcc@)+ -> v -- ^ prior-period portion (前期修正; → 'RetainedEarnings')+ -> AccountTitles -- ^ current-period expense account (e.g. 'AmortizationExpense')+ -> AccountTitles -- ^ affected asset credited (e.g. 'Patent')+ -> Alg v b+priorPeriodErrorCorrection mk current prior expenseAcc assetAcc =+ up mk current expenseAcc -- (借) 当期費用 (expense 増, home=Debit → Not)+ .+ down mk prior RetainedEarnings -- (借) 繰越利益剰余金 (equity 減, home=Credit → Hat=debit側)+ .+ down mk (current + prior) assetAcc -- (貸) 資産 (合計 current+prior 分だけ減額)
+ src/ExchangeAlgebra/Accounting/Equivalence.hs view
@@ -0,0 +1,271 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Compare transaction maps in the Accounting layer under explicit posting+-- observations. This module uses algebra filtering and netting for callers of+-- the admission API; it does not admit or validate input. Read the policy first,+-- then use 'isEquivalentUpTo' to compare complete per-transaction maps.+--+-- Choose an observation before comparing entries.+--+-- +-----------------------------------+-----------------------++-- | Task | Function |+-- +===================================+=======================++-- | Compare a selected observation | 'isEquivalentUpTo' |+-- +-----------------------------------+-----------------------++-- | Inspect exact closing differences | 'closingDifferences' |+-- +-----------------------------------+-----------------------++-- | Test exact closing agreement | 'isClosingEquivalent' |+-- +-----------------------------------+-----------------------++--+-- > import qualified ExchangeAlgebra.Accounting.Equivalence as Equivalence+-- > import qualified ExchangeAlgebra.Accounting.Transaction as Transaction+--+-- Supply maps of t'TxKey' to 'Entry'. Comparison does not establish admission.+module ExchangeAlgebra.Accounting.Equivalence+ ( -- * Observation policy+ Equivalence(..)+ -- * Comparison+ , isEquivalentUpTo+ -- * Closing comparison+ , ClosingSource(..)+ , ClosingDifference(..)+ , closingDifferences+ , isClosingEquivalent+ ) where++import Data.List (sort)+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import qualified Data.Set as Set+import Data.Set (Set)++import ExchangeAlgebra.Algebra.Base.Representation (HatBase((:<)), Hat(..))+import ExchangeAlgebra.Algebra.Core+ ( Alg(_hatBase, _val)+ , Redundant(bar)+ , toList+ )+import qualified ExchangeAlgebra.Algebra.Core as Algebra+import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(whichSide), Exchange(diffRL))+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles)+import ExchangeAlgebra.Accounting.Account.Registry (accountSpec)+import ExchangeAlgebra.Accounting.Account.Classification (Side(..))+import ExchangeAlgebra.Accounting.Transaction (Entry, TxKey, isBlankKey)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++-- * Observation policy++-- | Select the transaction-level observation used for comparison.+-- 'NetAccountsInTransactions' nets only the named accounts in the named+-- transactions. 'Entry' has only an account-title base axis, so there are no+-- other base axes to retain when an account is selected. Both the transaction+-- keys and account titles use exact set membership; wildcard coordinates are+-- values, not query patterns. Unselected postings remain strict multisets.+data Equivalence+ = PostingMultiset -- ^ Compare every original posting as a multiset.+ | NetWithinTransaction -- ^ Apply 'bar' within each transaction.+ | NetAccountsInTransactions (Set TxKey) (Set AccountTitles)+ -- ^ Net only the exact selected keys and account titles.+ deriving (Eq, Show)++-- * Canonical observations++-- | Split strict and netted posting multisets so one cannot cancel the other.+data PostingObservation = PostingObservation PostingMultisetValue PostingMultisetValue+ deriving (Eq)++-- | Sorted Hat, concrete account-title coordinate, and exact amount triples.+type PostingMultisetValue = [(Hat, AccountTitles, MoneyDecimal)]++-- | Sort individual postings without using the representation-sensitive 'Alg' Eq.+-- 'toList' returns only atomic postings, so both selectors are defined here.+postings :: Entry -> PostingMultisetValue+postings = sort . map coordinate . toList+ where+ coordinate posting = case _hatBase posting of+ hat :< account -> (hat, account, _val posting)++-- | Test the account coordinate without asking the potentially partial side API.+isInAccounts :: Set AccountTitles -> Entry -> Bool+isInAccounts accounts posting = case _hatBase posting of+ _ :< account -> Set.member account accounts++-- | Detect a formal wildcard before applying the algebra's netting operation.+hasWildcard :: Entry -> Bool+hasWildcard = any isWildcard . toList+ where+ isWildcard posting = case _hatBase posting of+ HatNot :< _ -> True+ _ -> False++-- | Canonical observation for one entry under the selected scope.+observed :: Equivalence -> TxKey -> Entry -> PostingObservation+observed PostingMultiset _ entry =+ PostingObservation (postings entry) []+observed NetWithinTransaction _ entry+ | hasWildcard entry = PostingObservation (postings entry) []+ | otherwise = PostingObservation (postings (bar entry)) []+observed (NetAccountsInTransactions keys accounts) key entry+ | Set.notMember key keys = PostingObservation (postings entry) []+ | hasWildcard selected = PostingObservation (postings entry) []+ | otherwise = PostingObservation+ (postings remaining)+ (postings (bar selected))+ where+ selected = Algebra.filter (isInAccounts accounts) entry+ remaining = Algebra.filter (not . isInAccounts accounts) entry++-- * Comparison++-- | Compare two maps after requiring identical transaction-key sets, including+-- keys whose entries have no postings. This is an observation, not admission:+-- callers must separately establish valid provenance, roles, and balances.+--+-- Law (equivalence relation): For finite maps with total 'Entry' values, each+-- policy is reflexive, symmetric, and transitive. The observed value is a+-- canonical sorted posting multiset per transaction, with 'bar' applied only+-- in the policy's stated scope. Comparing the resulting t'MoneyDecimal' values+-- uses exact equality. 'bar' nets each account exactly over the postings and+-- drops a residual only when it is exactly zero, so net policies are exact+-- arithmetic netting. A single atomic posting is unchanged by 'bar', including+-- tiny amounts. These laws apply to this 'Entry' instance. PostingMultiset does+-- not perform any normalization.+-- A selected entry with a wildcard Hat uses strict posting comparison because+-- the algebra's netting operation is not defined for wildcard postings.+isEquivalentUpTo :: Equivalence -> Map TxKey Entry -> Map TxKey Entry -> Bool+isEquivalentUpTo policy left right =+ Map.keysSet left == Map.keysSet right+ && all sameEntry (Map.toList left)+ where+ sameEntry (key, entry) = case Map.lookup key right of+ Just other -> observed policy key entry == observed policy key other+ Nothing -> False++-- * Closing comparison++-- | The input containing an invalid closing posting or transaction key.+data ClosingSource+ = Candidate+ | Reference+ deriving (Eq, Ord, Show)++-- | One difference between a candidate and a reference closing entry.+-- Amount pairs are always in candidate, reference order. t'Side' values in+-- totals are debit or credit sides, not Hat or Not labels.+data ClosingDifference+ = TransactionOnlyInCandidate TxKey+ | TransactionOnlyInReference TxKey+ | BlankTransactionKey ClosingSource TxKey+ | WildcardPosting ClosingSource TxKey AccountTitles+ | UnclassifiedAccount ClosingSource TxKey AccountTitles+ | NegativeAmount ClosingSource TxKey AccountTitles+ | SideTotalDifference TxKey AccountTitles Side MoneyDecimal MoneyDecimal+ | RetainedEarningsDifference TxKey (Side, MoneyDecimal) (Side, MoneyDecimal)+ deriving (Eq, Show)++-- | Diagnostic kinds in their public reporting order.+data ClosingIssue+ = IssueWildcard+ | IssueUnclassified+ | IssueNegative+ deriving (Eq, Ord)++-- | Validate every posting before calling the partial 'whichSide' classifier.+-- Duplicate diagnostics for the same source, kind, and account are collapsed.+entryDiagnostics :: ClosingSource -> TxKey -> Entry -> [ClosingDifference]+entryDiagnostics source key entry =+ (if isBlankKey key then [BlankTransactionKey source key] else [])+ ++ map toDifference (Set.toAscList (Set.fromList postingErrors))+ where+ postingErrors = concatMap check (toList entry)+ check posting = case _hatBase posting of+ hat :< account ->+ [(IssueWildcard, account) | hat == HatNot]+ ++ [(IssueUnclassified, account) | accountSpec account == Nothing]+ ++ [(IssueNegative, account) | _val posting < 0]+ toDifference (issue, account) = case issue of+ IssueWildcard -> WildcardPosting source key account+ IssueUnclassified -> UnclassifiedAccount source key account+ IssueNegative -> NegativeAmount source key account++-- | Sum exact posting amounts by account and accounting side.+sideTotals :: Entry -> Map (AccountTitles, Side) MoneyDecimal+sideTotals entry = Map.fromListWith (+)+ [ ((account, whichSide base), _val posting)+ | posting <- toList entry+ , let base@(_ :< account) = _hatBase posting+ ]++-- | Return the exact retained-earnings credit-minus-debit amount as side and+-- magnitude. Zero always has the credit side, including an absent account.+retainedNet :: AccountTitles -> Map (AccountTitles, Side) MoneyDecimal+ -> (Side, MoneyDecimal)+retainedNet account totals+ | credit >= debit = (Credit, credit - debit)+ | otherwise = (Debit, debit - credit)+ where+ debit = Map.findWithDefault 0 (account, Debit) totals+ credit = Map.findWithDefault 0 (account, Credit) totals++-- | Compare valid entries without applying 'bar', 'diffRL', or a tolerance.+entryDifferences :: AccountTitles -> TxKey -> Entry -> Entry -> [ClosingDifference]+entryDifferences retained key candidate reference =+ sideDifferences ++ retainedDifference+ where+ candidateTotals = sideTotals candidate+ referenceTotals = sideTotals reference+ coordinates = Set.toAscList+ (Map.keysSet candidateTotals `Set.union` Map.keysSet referenceTotals)+ sideDifferences =+ [ SideTotalDifference key account side candidateAmount referenceAmount+ | (account, side) <- coordinates+ , account /= retained+ , let candidateAmount = Map.findWithDefault 0 (account, side) candidateTotals+ , let referenceAmount = Map.findWithDefault 0 (account, side) referenceTotals+ , candidateAmount /= referenceAmount+ ]+ candidateNet = retainedNet retained candidateTotals+ referenceNet = retainedNet retained referenceTotals+ retainedDifference =+ [ RetainedEarningsDifference key candidateNet referenceNet+ | candidateNet /= referenceNet+ ]++-- | Compare closings by transaction key. Every input posting is checked first:+-- blank keys, wildcard Hat labels, unclassified accounts, and negative amounts+-- produce diagnostics, and their keys are not compared. Valid keys present in+-- only one map produce a presence difference, even when the entry is+-- 'ExchangeAlgebra.Algebra.Core.Zero'.+-- Other accounts compare exact sums per account and side. Retained earnings+-- compares only its exact credit-minus-debit net, with credit for zero.+-- Results follow ascending keys. Within a key, diagnostics follow candidate+-- then reference, and blank key, wildcard, unclassified, negative order, with+-- accounts ascending within each kind. Comparison differences follow account+-- and t'Side' order (Credit before Debit), then retained earnings.+--+-- Laws (for finite, fully valid input): comparison is reflexive; splitting a+-- posting into same-side amounts with the same sum preserves the result; and+-- retained-earnings gross and net postings with equal net amounts agree.+-- All sums use exact t'MoneyDecimal' arithmetic. Complexity: O(p log p), where+-- p is the total number of postings and transaction keys.+closingDifferences :: AccountTitles -> Map TxKey Entry -> Map TxKey Entry+ -> [ClosingDifference]+closingDifferences retained candidate reference = concatMap compareKey keys+ where+ keys = Set.toAscList (Map.keysSet candidate `Set.union` Map.keysSet reference)+ compareKey key =+ let candidateEntry = Map.lookup key candidate+ referenceEntry = Map.lookup key reference+ diagnostics = maybe [] (entryDiagnostics Candidate key) candidateEntry+ ++ maybe [] (entryDiagnostics Reference key) referenceEntry+ in if not (null diagnostics) then diagnostics else case (candidateEntry, referenceEntry) of+ (Just left, Just right) -> entryDifferences retained key left right+ (Just _, Nothing) -> [TransactionOnlyInCandidate key]+ (Nothing, Just _) -> [TransactionOnlyInReference key]+ (Nothing, Nothing) -> []++-- | Return whether two valid closing maps have the same exact observation.+-- Invalid input is never equivalent, including when compared with itself.+isClosingEquivalent :: AccountTitles -> Map TxKey Entry -> Map TxKey Entry -> Bool+isClosingEquivalent retained candidate reference =+ null (closingDifferences retained candidate reference)
+ src/ExchangeAlgebra/Accounting/Exchange.hs view
@@ -0,0 +1,552 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ExistentialQuantification #-}++-- | Account classification and debit/credit operations for algebras and journals.+-- This Accounting layer uses generic Algebra and Journal representations and+-- supplies their accounting instances (Definitions 7-9). Start with the base+-- classes, then use the decomposition and account projection operations.+--+-- @+-- Question Operation+-- Read or change an account getAccountTitle, setAccountTitle+-- Classify a posting whatDiv, whatPIMO, whichSide+-- Select debit or credit entries decL, decR+-- Check debit/credit totals balance, diffRL+-- Select an account in an Alg projByAccountTitle+-- @+--+-- Import "ExchangeAlgebra.Accounting.Account" for account titles and+-- classifications, and this module for accounting operations. For generic+-- operations, import "ExchangeAlgebra.Algebra.Core" and qualified+-- "ExchangeAlgebra.Journal.Core". Accounting classes and projections are+-- exported here, separately from the generic Algebra and Journal APIs.+module ExchangeAlgebra.Accounting.Exchange ( ExBaseClass(..)+ , AccountBase(..)+ , Exchange(..)+ , projCredit+ , projDebit+ , projByAccountTitle+ , projCurrentAssets+ , projFixedAssets+ , projDeferredAssets+ , projCurrentLiability+ , projFixedLiability+ , projCapitalStock+ , projContraAssets+ , projContra+ ) where++import ExchangeAlgebra.Accounting.Account ( AccountTitles(..)+ , AccountDivision(..)+ , Side(..)+ , FixedCurrent(..)+ , PIMO(..)+ , AccountSpec(..)+ , classifyAccountDivision+ , classifyAccountContra+ , defaultSide+ , switchSide+ , pimoFromDivision+ , pimoFlip+ , accountSpec+ )+import ExchangeAlgebra.Algebra.Base.Representation ( HatBaseClass(..)+ , HatBase(..)+ , Hat(..)+ , customError+ )+import ExchangeAlgebra.Algebra.Element.Representation ( Name+ , Subject+ , CountUnit+ , matchesQuery+ )+import ExchangeAlgebra.Algebra.Core.Representation ( Alg(..)+ , pattern (:@)+ , _val+ , _hatBase+ , Redundant(..)+ , (.@)+ , filter+ )+import qualified ExchangeAlgebra.Algebra.Core.Representation as EA (Alg(..), _hatBase, filter, foldEntries, foldPairs, Pair(..))+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), Tolerance(..), isErrorValue)+import qualified Data.Foldable as Foldable+import ExchangeAlgebra.Algebra.Value (MoneyDouble, MoneyDecimal)+import ExchangeAlgebra.Journal.Core.Representation (Journal, Note, map, toAlg)+import Data.Time (Day, TimeOfDay)+import Prelude hiding (map, filter)++-- | BaseClass ⊃ HatBaseClass ⊃ ExBaseClass+--+-- Extended type class for bases that carry an account title.+-- Provides access to and modification of account titles, account divisions, PIMO classification,+-- credit/debit determination, and fixed/current classification.+class (HatBaseClass a) => ExBaseClass a where+ -- | Retrieve the account title from a base. Complexity: O(1)+ getAccountTitle :: a -> AccountTitles++ -- | Change the account title of a base. Complexity: O(1)+ setAccountTitle :: a -> AccountTitles -> a++ -- | Account title setter operator. An alias for @setAccountTitle@. Complexity: O(1)+ {-# INLINE (.~) #-}+ (.~) :: a -> AccountTitles -> a+ (.~) = setAccountTitle++ -- | Retrieve the account division (Assets/Equity/Liability/Cost/Revenue). Complexity: O(1)+ {-# INLINE whatDiv #-}+ whatDiv :: a -> AccountDivision+ whatDiv = classifyAccountDivision . getAccountTitle++ -- | Whether the account is a contra account (評価勘定等): its home side+ -- and PIMO direction are the reverse of its division's defaults.+ -- Delegates to the registry ('classifyAccountContra') exactly like+ -- 'whatDiv' delegates to 'classifyAccountDivision' — a constant default+ -- would disconnect the registry flag from every built-in instance.+ -- Contract: @isContra b == (homeSide of b \/= defaultSide (whatDiv b))@.+ -- Complexity: O(1)+ {-# INLINE isContra #-}+ isContra :: a -> Bool+ isContra = classifyAccountContra . getAccountTitle++ -- | Retrieve the PIMO direction (PS/IN/MS/OUT; see 'PIMO' for the+ -- original semantics). Derived from the division via 'pimoFromDivision',+ -- flipped by 'pimoFlip' for contra accounts — e.g. a contra asset is MS+ -- (minus stock), which is what makes the standard allowance entry+ -- OUT ⇔ MS legal under Proposition 5.3.8. Complexity: O(1)+ {-# INLINE whatPIMO #-}+ whatPIMO :: a -> PIMO+ whatPIMO x+ | isContra x = pimoFlip (pimoFromDivision (whatDiv x))+ | otherwise = pimoFromDivision (whatDiv x)++ -- | Determine whether a base belongs to the Credit or Debit side.+ -- The home side is 'defaultSide' of the division, reversed for contra+ -- accounts ('isContra'). Takes the Hat/Not reversal into account: an+ -- account sits on its home side under 'Not' and on the opposite side+ -- under v'Hat'. A 'HatNot' (wildcard) label is rejected with an error —+ -- same policy as 'isHat': stored postings are always Hat\/Not, so a+ -- wildcard here means a query-side value leaked into a posting-side+ -- computation (this function previously treated 'HatNot' silently as+ -- v'Hat'). Complexity: O(1)+ {-# INLINE whichSide #-}+ whichSide :: a -> Side+ whichSide x =+ let side0 = defaultSide (whatDiv x)+ side = if isContra x then switchSide side0 else side0+ in case hat x of+ Not -> side+ Hat -> switchSide side+ HatNot -> customError "whichSide: called on a HatNot (wildcard) base"++ -- | Retrieve the fixed/current classification.+ -- Returns Current, Fixed, or Other based on the account title.+ --+ -- Complexity: O(1)+ {-# INLINE fixedCurrent #-}+ fixedCurrent :: a -> FixedCurrent+ fixedCurrent b = maybe Other asFixedCurrent (accountSpec (getAccountTitle b))+++-- | Type class for determining correspondences between account divisions.+-- Tests whether two account divisions form a pair in double-entry bookkeeping+-- (e.g., Assets <=> Liability).+--+-- Complexity: O(1)+class AccountBase a where+ -- | Test whether two account divisions are in a corresponding relationship.+ (<=>) :: a -> a -> Bool++-- | Derived from the PIMO relation via 'pimoFromDivision', matching+-- Proposition 5.3.8 (Deguchi 2004). BREAKING (0.5.0.0): the previous+-- hand-enumerated instance omitted the pairs required by PS ⇔ IN and+-- OUT ⇔ IN — @Assets \<=\> Revenue@ (e.g. a cash sale) and+-- @Cost \<=\> Revenue@ are now 'True'. This division-level relation cannot+-- see contra reversal; exchange checks on bases must go through 'whatPIMO'.+instance AccountBase AccountDivision where+ a <=> b = pimoFromDivision a <=> pimoFromDivision b++instance AccountBase PIMO where+ PS <=> IN = True+ IN <=> PS = True+ PS <=> MS = True+ MS <=> PS = True+ IN <=> OUT = True+ OUT <=> IN = True+ MS <=> OUT = True+ OUT <=> MS = True+ _ <=> _ = False+++------------------------------------------------------------------+-- * Simple bases (can be extended as needed)+-- Tuples are used so that the same accessor functions can be shared.+-- This approach was chosen over the DuplicateRecordFields extension+-- because it has fewer restrictions and looks cleaner.+------------------------------------------------------------------++-- ** 1-element bases+-- *** Account title only (exchange algebra base)+instance ExBaseClass (HatBase AccountTitles) where+ getAccountTitle (_ :< a) = a+ setAccountTitle (h :< _) b = h :< b++-- *** Name only (redundant algebra base)+-- *** CountUnit only (redundant algebra base)+-- *** Day only (redundant algebra base)+-- *** TimeOfDay only (redundant algebra base)+-- ***+++-- ** 2-element bases++-- | Basic BaseClass with 2 elements++instance ExBaseClass (HatBase (AccountTitles, Day)) where+ getAccountTitle (_:< (a, _)) = a+ setAccountTitle (h:< (_, d)) b = h:< (b, d)++instance ExBaseClass (HatBase (AccountTitles, Name)) where+ getAccountTitle (_:< (a, _)) = a+ setAccountTitle (h:< (_, n)) b = h:< (b, n)++instance ExBaseClass (HatBase (CountUnit, AccountTitles)) where+ getAccountTitle (_:< (_, a)) = a+ setAccountTitle (h:< (u, _)) b = h:< (u, b)++-- ** 3-element bases+-- | Basic BaseClass with 3 elements+instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit)) where+ getAccountTitle (_:< (a, _, _)) = a+ setAccountTitle (h:< (_, n, c)) b = h:< (b, n, c)++-- ** 4-element bases+-- | Basic BaseClass with 4 elements+instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject)) where+ getAccountTitle (_:< (a, _, _, _)) = a+ setAccountTitle (h:< (_, n, c, s)) b = h:< (b, n, c, s)++-- ** 5-element bases+-- | Basic BaseClass with 5 elements+instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject, Day)) where+ getAccountTitle (_:< (a, _, _, _, _)) = a+ setAccountTitle (h:< (_, n, c, s, d)) b = h:< (b, n, c, s, d)+++-- ** 6-element bases+-- | Basic BaseClass with 6 elements+instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject, Day, TimeOfDay)) where+ getAccountTitle (_:< (a, _, _, _, _, _)) = a+ setAccountTitle (h:< (_, n, c, s, d, t)) b = h:< (b, n, c, s, d, t)++------------------------------------------------------------+-- ** Definition of Exchange Algebra+------------------------------------------------------------++-- | Type class for Exchange Algebra. In addition to Redundant Algebra, provides+-- the decomposition operators of Deguchi & Nakano (1986, Definition 2.16) and+-- balance checking. Following the original convention, __L = Left = Debit+-- (借方)__ and __R = Right = Credit (貸方)__: 'decL' extracts the debit side,+-- 'decR' the credit side. ('decP' \/ 'decM' split along the Hat\/Not label+-- instead of the debit\/credit side.)+class (Redundant a n b ) => Exchange a n b where+ -- | Extracts only the credit-side elements (R = Right = Credit, 貸方),+ -- i.e. those whose 'whichSide' is 'Credit'. Complexity: O(s)+ decR :: a n b -> a n b+ -- | Extracts only the debit-side elements (L = Left = Debit, 借方),+ -- i.e. those whose 'whichSide' is 'Debit'. Complexity: O(s)+ decL :: a n b -> a n b+ -- | Extracts only the Hat-side elements (the P-projection of the+ -- decomposition; @isHat@ holds). Complexity: O(s)+ decP :: a n b -> a n b+ -- | Extracts only the Not-side elements (the M-projection of the+ -- decomposition; @isHat@ does not hold). Complexity: O(s)+ decM :: a n b -> a n b+ -- | 'True' exactly when the exact debit sum equals the exact credit sum+ -- (compared before rounding, no tolerance). 'False' when a non-finite+ -- posting is present. (0.6.0.0: the 0.5.x check is 'balanceWithin'.)+ -- Complexity: O(s)+ balance :: a n b -> Bool+ -- | The exact debit-credit difference rounded once, with the winning side;+ -- @(Side, 0)@ only when the difference is exactly zero. (0.6.0.0: the 0.5.x+ -- result is 'diffRLWithin'.)+ -- Complexity: O(s)+ diffRL :: a n b -> (Side, n)+ -- | The 0.5.x 'balance': sequential side totals compared within the tolerance.+ -- Complexity: O(s)+ balanceWithin :: Tolerance n -> a n b -> Bool+ -- | The 0.5.x 'diffRL': sequential side totals, a zero difference within the tolerance.+ -- Complexity: O(s)+ diffRLWithin :: Tolerance n -> a n b -> (Side, n)+++instance (HatVal n, ExBaseClass b) => Exchange Alg n b where+ {-# SPECIALIZE instance ExBaseClass b => Exchange Alg Double b #-}+ {-# SPECIALIZE instance ExBaseClass b => Exchange Alg MoneyDouble b #-}+ {-# SPECIALIZE instance ExBaseClass b => Exchange Alg MoneyDecimal b #-}+ -- | filter Credit side+ decR xs = filter (\x -> x /= Zero && (whichSide . _hatBase) x == Credit) xs++ -- | filter Debit side+ decL xs = filter (\x -> x /= Zero && (whichSide . _hatBase) x == Debit) xs++ -- | filter Plus Stock+ decP xs = filter (\x -> x /= Zero && (isHat . _hatBase ) x) xs++ -- | filter Minus Stock+ decM xs = filter (\x -> x /= Zero && (not. isHat. _hatBase) x) xs++ -- | Compare the exact credit-minus-debit state before rounding.+ balance = (== Just EQ) . signAccum . accountState++ diffRL algebra = case signAccum state of+ Just EQ -> (Side, zeroValue)+ Just GT -> (Credit, roundAccum state)+ Just LT -> (Debit, roundAccum (negateAccum state))+ Nothing -> (Side, roundAccum state)+ where+ state = accountState algebra++ balanceWithin tolerance = (== Side) . fst . diffRLWithin tolerance+ diffRLWithin tolerance algebra+ | not (isErrorValue credit || isErrorValue debit)+ && abs (credit - debit) <= absTol tolerance+ + relTol tolerance * max (abs credit) (abs debit) = (Side, zeroValue)+ | credit > debit = (Credit, credit - debit)+ | otherwise = (Debit, debit - credit)+ where+ credit = sequentialNorm (decR algebra)+ debit = sequentialNorm (decL algebra)+ sequentialNorm = EA.foldPairs (\total _ (EA.Pair hats nots) ->+ total + Foldable.foldl' (+) 0 hats + Foldable.foldl' (+) 0 nots) 0++-- | Exact credit minus debit, with structural Side contributing nothing.+accountState :: forall n b. (HatVal n, ExBaseClass b) => Alg n b -> Accum n+{-# INLINE accountState #-}+accountState = EA.foldPairs collect emptyAccum+ where+ collect total basePart (EA.Pair hats nots) =+ mergeAccum total (mergeAccum (signed Hat hats) (signed Not nots))+ where+ signed side values = case whichSide (merge side basePart :: b) of+ Credit -> Foldable.foldl' (flip addAccum) emptyAccum values+ Debit -> negateAccum (Foldable.foldl' (flip addAccum) emptyAccum values)+ Side -> emptyAccum++------------------------------------------------------------------+-- | Projects only the credit-side elements. For 'Alg' this coincides with the+-- 'Exchange' class method 'decR' (R = Right = Credit, 貸方); the top-level name+-- makes the selected side explicit at call sites. (An earlier doc sentence+-- restricting this to non-'Enum' bases referred to long-removed 'Enum'-based+-- class defaults and no longer applies.)+--++-- Complexity: O(s) (s is the total number of scalar entries)+projCredit :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projCredit = filter (\x -> (whichSide . _hatBase) x == Credit)++-- | Projects only the debit-side elements. For 'Alg' this coincides with the+-- 'Exchange' class method 'decL' (L = Left = Debit, 借方); the top-level name+-- makes the selected side explicit at call sites. (An earlier doc sentence+-- restricting this to non-'Enum' bases referred to long-removed 'Enum'-based+-- class defaults and no longer applies.)+--+-- Complexity: O(s) (s is the total number of scalar entries)+projDebit :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projDebit = filter (\x -> (whichSide . _hatBase) x == Debit)++-- | Projects only the elements matching the specified account title.+--+-- The match is one-way, as in 'ExchangeAlgebra.Algebra.Core.proj': a wildcard query+-- title selects every element, while a concrete title does not select an element+-- whose ledger title is the wildcard.+--+-- Complexity: O(s) (s is the total number of scalar entries)+projByAccountTitle :: (HatVal n, ExBaseClass b) => AccountTitles -> Alg n b -> Alg n b+projByAccountTitle at alg = filter (f at) alg+ where+ f :: (HatVal n,ExBaseClass b) => AccountTitles -> Alg n b -> Bool+ f _ Zero = False+ f t x = matchesQuery t ((getAccountTitle ._hatBase) x)+-- | Projects only current assets.+-- Extracts asset items classified as current from the debit side.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the debit side):+-- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Current && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projCurrentAssets x) - norm ('bar' (contra x))@ where+-- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+projCurrentAssets :: ( HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projCurrentAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Current))+ . (filter (\x -> (whatDiv . _hatBase) x == Assets))+ . (filter (not . isContra . _hatBase))+ . projDebit++-- | Projects only fixed assets.+-- Extracts asset items classified as fixed from the debit side.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the debit side):+-- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Fixed && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projFixedAssets x) - norm ('bar' (contra x))@ where+-- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+projFixedAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projFixedAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Fixed))+ . (filter (\x -> (whatDiv . _hatBase) x == Assets))+ . (filter (not . isContra . _hatBase))+ . projDebit++-- | Projects only deferred assets.+-- Tax-specific deferred assets are presented under "investments and other assets" with appropriate items such as long-term prepaid expenses.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the debit side):+-- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Other && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projDeferredAssets x) - norm ('bar' (contra x))@ where+-- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+projDeferredAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projDeferredAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Other))+ . (filter (\x -> (whatDiv . _hatBase) x == Assets))+ . (filter (not . isContra . _hatBase))+ . projDebit++-- | Projects only current liabilities.+-- Extracts liability items classified as current from the credit side.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the credit side):+-- @whatDiv (_hatBase x) == Liability && fixedCurrent (_hatBase x) == Current && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projCurrentLiability x) - norm ('bar' (contra x))@ where+-- @contra@ selects the Liability-division entries of 'projContra' (the current+-- registry has no contra liability account, so gross and net coincide today).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+projCurrentLiability :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projCurrentLiability = (filter (\x -> (fixedCurrent . _hatBase) x == Current))+ . (filter (\x -> (whatDiv . _hatBase) x == Liability))+ . (filter (not . isContra . _hatBase))+ . projCredit++-- | Projects only fixed liabilities.+-- Extracts liability items classified as fixed from the credit side.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the credit side):+-- @whatDiv (_hatBase x) == Liability && fixedCurrent (_hatBase x) == Fixed && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projFixedLiability x) - norm ('bar' (contra x))@ where+-- @contra@ selects the Liability-division entries of 'projContra' (the current+-- registry has no contra liability account, so gross and net coincide today).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+projFixedLiability :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projFixedLiability = (filter (\x -> (fixedCurrent . _hatBase) x == Fixed))+ . (filter (\x -> (whatDiv . _hatBase) x == Liability))+ . (filter (not . isContra . _hatBase))+ . projCredit++-- | Projects only capital stock (equity).+-- Extracts items classified under the 'Equity' division from the credit side.+--+-- Selection predicate (over every scalar entry @x@ of the input, on the credit side):+-- @whatDiv (_hatBase x) == Equity && not (isContra (_hatBase x))@.+-- Contra accounts are excluded, so the result is the /gross/ figure of this+-- class; the net figure is @norm (projCapitalStock x) - norm ('bar' (contra x))@ where+-- @contra@ selects the Equity-division entries of 'projContra' (the current+-- registry has no contra equity account, so gross and net coincide today).+-- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).+--+-- Complexity: O(s) (s is the total number of scalar entries)+--+-- >>> type Test = Alg Double (HatBase AccountTitles)+-- >>> x = 100.@Not:<CapitalStock .+ 30.@Not:<Cash .+ 20.@Not:<RetainedEarnings :: Test+-- >>> norm (projCapitalStock x)+-- 120.0+projCapitalStock :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projCapitalStock = (filter (\x -> (whatDiv . _hatBase) x == Equity))+ . (filter (not . isContra . _hatBase))+ . projCredit++-- | Projects contra-asset entries (@whatDiv == Assets && isContra@, e.g.+-- 貸倒引当金\/減価償却累計額) — an /attribute/ selection, not a physical-side+-- one: both Hat and Not postings of the contra account are kept, and normal+-- assets' credit-side (Hat) postings are NOT included. The division+-- projections (@proj*Assets@\/@proj*Liability@\/'projCapitalStock')+-- exclude ALL contra accounts, so within the Assets division this projection+-- is the sole selector — no double counting when combining them. A net+-- figure is @gross - contra balance@, e.g.+-- @norm (projCurrentAssets x) - norm ('ExchangeAlgebra.Algebra.bar' (projContraAssets x))@+-- when the contra accounts hold normal (credit) balances; deduction\/netting+-- /presentation/ policy is the ExchangeAlgebra.IO.Output module's job (Land 3).+--+-- NOTE: this selects the Assets division only. In the current registry every+-- contra account is an asset, but the type class does not forbid contra+-- accounts in other divisions (e.g. a future treasury-stock contra equity) —+-- those are excluded from the division projections too and must be selected+-- with the generic 'projContra'. Consumers that need a+-- net asset figure combine the gross @proj*Assets@ family with this+-- projection themselves; deduction\/netting presentation policy is the+-- ExchangeAlgebra.IO.Output module's job (Land 3 of the Definition 7 amendment).+--+-- Complexity: O(s) (s is the total number of scalar entries)+--+-- >>> type Test = Alg Double (HatBase AccountTitles)+-- >>> x = 100.@Not:<AllowanceForDoubtfulAccounts .+ 20.@Hat:<AllowanceForDoubtfulAccounts .+ 30.@Not:<Cash .+ 10.@Hat:<Cash :: Test+-- >>> projContraAssets x+-- 20.00:@Hat:<AllowanceForDoubtfulAccounts .+ 100.00:@Not:<AllowanceForDoubtfulAccounts+projContraAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projContraAssets = filter+ (\x -> (whatDiv . _hatBase) x == Assets && (isContra . _hatBase) x)++-- | Projects ALL contra entries regardless of division — the exact+-- complement, w.r.t. contra-ness, of the six division projections (which all+-- exclude contra accounts). Use this when the chart may contain contra+-- accounts outside the Assets division; @'projContraAssets' = filter by+-- Assets ∘ projContra@.+--+-- Complexity: O(s) (s is the total number of scalar entries)+projContra :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b+projContra = filter (isContra . _hatBase)+++instance (Note n, HatVal v, ExBaseClass b) => Exchange (Journal n) v b where+ balanceWithin tolerance = balanceWithin tolerance . toAlg+ diffRLWithin tolerance = diffRLWithin tolerance . toAlg+ decR js = map (EA.filter (\x -> x /= EA.Zero && (whichSide . EA._hatBase) x == Credit)) js+ decL xs = map (EA.filter (\x -> x /= EA.Zero && (whichSide . EA._hatBase) x == Debit)) xs+ decP xs = map (EA.filter (\x -> x /= EA.Zero && (isHat . EA._hatBase) x)) xs+ decM xs = map (EA.filter (\x -> x /= EA.Zero && (not . isHat . EA._hatBase) x)) xs++ balance = balance . toAlg+ diffRL = diffRL . toAlg
src/ExchangeAlgebra/Accounting/PostingPolicy.hs view
@@ -1,77 +1,5 @@-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Accounting.PostingPolicy-Description : Accounting-domain posting authority for processing contexts.--Accounting-domain posting authority: which coordinates may be posted in which-processing context. Input adapters ("ExchangeAlgebra.Convert.Checked") and the-consolidation worksheet ("ExchangeAlgebra.Consolidation.Worksheet") consume-this; it does not depend on either.--The policy has two inputs. The 'PostingCapability' of an account title is-canonical registry metadata ('accountSemantics'); the 'ProcessingContext' names-the boundary at which a posting is admitted. 'postingAllowedIn' is the closed-gate that relates the two, and 'postingCapabilityFor' is the total lookup that-maps the wildcard 'AccountTitle' (outside the metadata domain) to 'NotPostable'.--}-module ExchangeAlgebra.Accounting.PostingPolicy- ( ProcessingContext(..)- , postingAllowedIn- , postingCapabilityFor- ) where--import ExchangeAlgebra.Algebra.Base (AccountTitles(..))-import ExchangeAlgebra.Algebra.Base.Account.Registry- ( AccountSemantics(asemPostingCapability)- , accountSemantics- )-import ExchangeAlgebra.Algebra.Base.Account.Types (PostingCapability(..))---- | Processing boundary at which generated postings are admitted.------ Each non-ordinary context adds exactly one capability to--- 'OrdinaryPosting'. This keeps closing, consolidation, and engine authority--- separate instead of introducing one privileged "internal" bypass.-data ProcessingContext- = OrdinaryJournal- | ClosingProcess- | ConsolidationWorksheet- | EngineComputation- deriving (Show, Eq)---- | Whether a capability is admitted at a processing boundary.-postingAllowedIn :: ProcessingContext -> PostingCapability -> Bool-postingAllowedIn OrdinaryJournal capability = case capability of- OrdinaryPosting -> True- ClosingOnly -> False- ConsolidationOnly -> False- EngineGeneratedOnly -> False- NotPostable -> False-postingAllowedIn ClosingProcess capability = case capability of- OrdinaryPosting -> True- ClosingOnly -> True- ConsolidationOnly -> False- EngineGeneratedOnly -> False- NotPostable -> False-postingAllowedIn ConsolidationWorksheet capability = case capability of- OrdinaryPosting -> True- ClosingOnly -> False- ConsolidationOnly -> True- EngineGeneratedOnly -> False- NotPostable -> False-postingAllowedIn EngineComputation capability = case capability of- OrdinaryPosting -> True- ClosingOnly -> False- ConsolidationOnly -> False- EngineGeneratedOnly -> True- NotPostable -> False+-- | Policies for admitting postings to bookkeeping accounts.+module ExchangeAlgebra.Accounting.PostingPolicy {-# DEPRECATED "Use ExchangeAlgebra.Accounting.Account instead." #-} + ( ProcessingContext(..), postingAllowedIn, postingCapabilityFor ) where --- | Posting capability of an account title, total over 'AccountTitles'.------ Concrete titles report their registry capability--- ('asemPostingCapability'); the wildcard 'AccountTitle' has no registry--- semantics and is therefore 'NotPostable'.-postingCapabilityFor :: AccountTitles -> PostingCapability-postingCapabilityFor title =- maybe NotPostable asemPostingCapability (accountSemantics title)+import ExchangeAlgebra.Accounting.Account.PostingPolicy
+ src/ExchangeAlgebra/Accounting/Statements.hs view
@@ -0,0 +1,82 @@+{- |+Module : ExchangeAlgebra.Accounting.Statements+Description : Group, derive, and present financial statement values.++Import this module for the complete statements API. Import a child module+when only grouping, metrics, or presentation is needed.+++--------------------------------+--------------------------------------------++| Task | Entry point |++================================+============================================++| Group gross and contra amounts | 'presentGroups' |++--------------------------------+--------------------------------------------++| Derive a period result | 'periodResultOf' |++--------------------------------+--------------------------------------------++| Present a trial balance | 'present' |++--------------------------------+--------------------------------------------+++'present' accepts a trial balance over account titles and its stage, and+returns presentation results with their audit events. It does not alter+bookkeeping coordinates and does not judge whether the trial balance is normal+for accounting purposes; inspect+'ExchangeAlgebra.Accounting.TrialBalance.Validation.trialBalanceFindings' for+that audit.++Statements receive one reporting scope. A ledger with several axes (entity,+period, department) is narrowed by the caller and mapped to account titles+with 'ExchangeAlgebra.Algebra.Core.mapMaybePosting' or+'ExchangeAlgebra.Algebra.Core.mapPosting' before presentation. The remaining+axes are summed, and no axis name appears in the output.+-}+module ExchangeAlgebra.Accounting.Statements+ ( PresentationGroup(..)+ , PresentationGroupDef(..)+ , defaultPresentationGrouping+ , presentationGroupOf+ , lookupGroupDef+ , groupNormalSide+ , groupingForDivisions+ , RelativeAmount(..)+ , relativeTo+ , addGross+ , GroupRowKind(..)+ , GroupRow(..)+ , GroupedPresentation(..)+ , presentGroups+ , MetricId+ , mkMetricId+ , metricIdText+ , DerivedMetric(..)+ , PeriodResult(..)+ , MetricError(..)+ , metricForLegacyTitle+ , legacyTitlesForMetric+ , periodResultOfAlg+ , periodResultOf+ , AccountingFramework(..)+ , ReportingScope(..)+ , PresentationProfile(..)+ , StatementSection(..)+ , StatementLine(..)+ , PresentationAllocation(..)+ , PresentationRelabel(..)+ , MaterialityTreatment(..)+ , MaterialityDecision(..)+ , ContraPresentationRule(..)+ , CustomMetricLabel(..)+ , SubtotalCoverage(..)+ , SubtotalDefinition(..)+ , StatementSubtotal(..)+ , ReportingContext(..)+ , jcciSecondGradeContext+ , PresentationAuditEvent(..)+ , PresentationIssue(..)+ , FinancialStatements(..)+ , presentationLabel+ , metricLabel+ , present+ ) where++import ExchangeAlgebra.Accounting.Statements.Group+import ExchangeAlgebra.Accounting.Statements.Metric+import ExchangeAlgebra.Accounting.Statements.Presentation
+ src/ExchangeAlgebra/Accounting/Statements/Group.hs view
@@ -0,0 +1,408 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.Statements.Group+Description : Presentation groups and the contra netting policy (Definition 7, Land 3).++A /presentation group/ is a statement block made of one or more __gross__+account titles and the __contra__ (評価勘定) titles that are deducted from+them, rendered as @gross lines → deduction lines → net line@. It is the+mechanism the Definition 7 contra amendment left open: Land 2 fixed the+classification (contra assets are @Assets@ with @isContra = True@) but kept a+display-compatibility shim so that the contra accounts still /appeared/ in the+liability column; this module supplies the real deduction presentation and+lets that shim be removed.++This module implements display grouping and deduction rules independently of+CSV writers and the financial-statement presentation pipeline. Callers can use+'presentGroups' to apply these rules in their own displays.++=== Value domain++Statement amounts are held as a @(debit gross, credit gross)@ pair and only+netted at the last moment, into a t'RelativeAmount' carrying a __non-negative__+magnitude plus a direction flag. Stored and rendered magnitudes therefore stay+in \(\mathbb{R}_0^+\), including for soft-invariant value types such as+@MoneyDecimal@. A minus sign exists only in rendered text, never in a stored+value of t'RelativeAmount'.++=== Block invariant++Within one block the exact amounts, before rounding, sum to the net line:++> Σ (gross rows) + Σ (subgroup rows) + Σ (deduction rows) = net row++A deduction row is stated /relative to the group's normal side/, so an+ordinary contra balance is already negative there and the sum above is a plain+addition, not a subtraction. A contra that exceeds its gross members simply+makes the net row negative (that is the @contra > parent@ edge case); a group+whose gross members are absent entirely nets to the negated deduction (the+@parent absent@ edge case). Both are handled by the same arithmetic. Each output is rounded once; the+sum of the displayed amounts need not equal the displayed net.+-}+module ExchangeAlgebra.Accounting.Statements.Group+ ( -- * Groups+ PresentationGroup(..)+ , PresentationGroupDef(..)+ , defaultPresentationGrouping+ , presentationGroupOf+ , lookupGroupDef+ , groupNormalSide+ , groupingForDivisions+ -- * Amounts+ , RelativeAmount(..)+ , relativeTo+ , addGross+ -- * Grouped rows+ , GroupRowKind(..)+ , GroupRow(..)+ , GroupedPresentation(..)+ , presentGroups+ ) where++import qualified Data.List as L+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import qualified Data.Set as S+import Data.Set (Set)+import Data.Text (Text)++import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Accounting.Account (defaultSide)+import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..), Side(..) )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))++------------------------------------------------------------------+-- Groups+------------------------------------------------------------------++-- | Identity of a statement presentation block. These are reporting+-- identities, never posting coordinates: they name a group of account titles,+-- not an account.+data PresentationGroup+ = TradeReceivablesGroup -- ^ 売上債権 − 貸倒引当金+ | DepreciableAssetsGroup -- ^ 償却性有形固定資産 − 減価償却累計額+ | NetSalesGroup -- ^ 総売上高 − 売上割戻 − 売上値引+ | NetPurchasesGroup -- ^ 総仕入高 − 仕入割戻 − 仕入値引+ | IncomeTaxesGroup -- ^ 法人税等 − 還付法人税等+ deriving (Show, Eq, Ord, Enum, Bounded)++-- | One block of a statement: the gross titles, the contra titles deducted+-- from them, and (optionally) the group this block rolls up into.+--+-- @pgGross@ and @pgDeductions@ are ordered; rows are emitted in that order so+-- that the output is deterministic. Every @pgDeductions@ member is expected to+-- be a registry contra account of the same division as @pgDivision@, and no+-- @pgGross@ member is; the test-suite pins both, together with the disjointness+-- of the membership sets and the fact that every registry contra account+-- belongs to exactly one group.+data PresentationGroupDef = PresentationGroupDef+ { pgKey :: PresentationGroup+ , pgDivision :: AccountDivision+ -- ^ Statement column the block belongs to. Fixes the group's normal+ -- side via 'groupNormalSide'.+ , pgLabel :: Text+ -- ^ Label of the net line (and of the carried subtotal when this block+ -- rolls up into another one).+ , pgGross :: [AccountTitles]+ , pgDeductions :: [AccountTitles]+ , pgParent :: Maybe PresentationGroup+ -- ^ When set, this block's net is carried into the named block as a+ -- 'SubgroupRow' and only the outermost block contributes to the column+ -- total. The default grouping is flat; nesting exists so that a caller+ -- can state a multi-level statement without a second netting rule.+ }+ deriving (Show, Eq)++-- | Normal (deduction-free) side of a group, i.e. the side its net line is+-- stated against.+groupNormalSide :: PresentationGroupDef -> Side+groupNormalSide = defaultSide . pgDivision++-- | Default display groups for callers using 'presentGroups'.+--+-- The membership lists are presentation policy, not algebra: they say which+-- gross titles a single undifferentiated valuation account is deducted from+-- under JGAAP's indirect method (間接控除法). Tests verify that each registry+-- contra account occurs exactly once among the default blocks' deductions.+-- A block can deduct multiple contra accounts: net sales deducts both sales+-- rebates and sales allowances. All blocks are flat — a nested default would+-- assert a subtotal structure that the chart does not yet carry.+defaultPresentationGrouping :: [PresentationGroupDef]+defaultPresentationGrouping =+ [ PresentationGroupDef+ { pgKey = TradeReceivablesGroup+ , pgDivision = Assets+ , pgLabel = "TradeReceivablesNet"+ , pgGross =+ [ NotesReceivable+ , AccountsReceivable+ , ElectronicallyRecordedReceivable+ , CreditCardReceivable+ ]+ , pgDeductions = [AllowanceForDoubtfulAccounts]+ , pgParent = Nothing+ }+ , PresentationGroupDef+ { pgKey = DepreciableAssetsGroup+ , pgDivision = Assets+ , pgLabel = "DepreciableAssetsNet"+ , pgGross =+ [ Building+ , Structures+ , Machinery+ , Vehicle+ , ToolsAndInstruments+ , Fixtures+ ]+ , pgDeductions = [AccumulatedDepreciation]+ , pgParent = Nothing+ }+ , PresentationGroupDef+ { pgKey = NetSalesGroup+ , pgDivision = Revenue+ , pgLabel = "NetSales"+ , pgGross = [Sales]+ , pgDeductions = [SalesRebates, SalesAllowances]+ , pgParent = Nothing+ }+ , PresentationGroupDef+ { pgKey = NetPurchasesGroup+ , pgDivision = Cost+ , pgLabel = "NetPurchases"+ , pgGross = [Purchases]+ , pgDeductions = [PurchaseRebates, PurchaseAllowances]+ , pgParent = Nothing+ }+ , PresentationGroupDef+ { pgKey = IncomeTaxesGroup+ , pgDivision = Cost+ , pgLabel = "IncomeTaxesNet"+ , pgGross = [CorporateIncomeTaxes, AdditionalIncomeTaxesForPriorPeriods]+ , pgDeductions = [RefundOfIncomeTaxes]+ , pgParent = Nothing+ }+ ]++-- | Block a title belongs to under 'defaultPresentationGrouping', if any.+presentationGroupOf :: AccountTitles -> Maybe PresentationGroup+presentationGroupOf title = fmap pgKey (L.find member defaultPresentationGrouping)+ where+ member def = title `elem` pgGross def || title `elem` pgDeductions def++-- | Look one block up in the grouping in force.+lookupGroupDef :: [PresentationGroupDef] -> PresentationGroup -> Maybe PresentationGroupDef+lookupGroupDef defs key = L.find ((== key) . pgKey) defs++-- | Restrict a grouping to the statement columns a writer renders. A balance+-- sheet must not net revenue accounts, and a profit and loss statement must+-- not net asset accounts.+groupingForDivisions :: [AccountDivision] -> [PresentationGroupDef] -> [PresentationGroupDef]+groupingForDivisions divisions = filter ((`elem` divisions) . pgDivision)++------------------------------------------------------------------+-- Amounts+------------------------------------------------------------------++-- | An amount stated relative to a group's normal side. @raMagnitude@ is+-- always non-negative; @raBelowZero@ says whether the amount sits on the+-- opposite side (a deduction, or a net that its deductions pushed past zero).+-- Callers render the direction — as a leading @-@, a triangle (△), or a move+-- to the other column — and never negate the value itself.+data RelativeAmount v = RelativeAmount+ { raBelowZero :: !Bool+ , raMagnitude :: !v+ }+ deriving (Show, Eq)++-- | Orient a directional net balance, such as the output of @diffRL@,+-- against a group's normal side. The magnitude is already netted; this+-- function never subtracts rounded gross totals or applies a tolerance.+relativeTo :: HatVal v => Side -> (Side, v) -> RelativeAmount v+relativeTo side (direction, magnitude) =+ RelativeAmount (direction /= Side && direction /= normal) magnitude+ where+ normal = case side of+ Side -> Debit+ _ -> side++-- | Accumulate @(debit gross, credit gross)@ pairs.+addGross :: Num v => (v, v) -> (v, v) -> (v, v)+addGross (leftDebit, leftCredit) (rightDebit, rightCredit) =+ (leftDebit + rightDebit, leftCredit + rightCredit)++------------------------------------------------------------------+-- Grouped rows+------------------------------------------------------------------++-- | What one row of a block states.+data GroupRowKind+ = GrossRow AccountTitles+ -- ^ A constituent account, before deduction.+ | SubgroupRow PresentationGroup+ -- ^ The net of a nested block, carried into this one.+ | DeductionRow AccountTitles+ -- ^ A contra account. Its amount is already stated relative to the+ -- group's normal side, so it is normally 'raBelowZero'.+ | NetRow PresentationGroup+ -- ^ The block's net, rounded after combining the exact member amounts.+ deriving (Show, Eq)++-- | A group row kind paired with its relative amount.+data GroupRow v = GroupRow+ { grKind :: GroupRowKind+ , grAmount :: RelativeAmount v+ }+ deriving (Show, Eq)++-- | Result of applying a grouping to a set of per-title balances.+data GroupedPresentation v = GroupedPresentation+ { gpBlocks :: [(PresentationGroupDef, [GroupRow v])]+ -- ^ Blocks in render order: nested blocks precede the block they roll+ -- up into, roots follow the order of the grouping list.+ , gpRootTotals :: Map AccountDivision (v, v)+ -- ^ Per-division @(debit, credit)@ contribution of the __outermost__+ -- blocks only. A caller adds this to the ungrouped part of the column;+ -- adding nested blocks as well would double count.+ , gpConsumed :: Set AccountTitles+ -- ^ Titles the blocks have taken over. The caller must not render these+ -- again through its ordinary per-entry path.+ }+ deriving (Show, Eq)++-- | Apply a grouping to per-title @(debit gross, credit gross)@ totals.+--+-- A block is /active/ when one of its own contra titles carries non-zero gross+-- activity, or when a nested block is active: a group with nothing to deduct is+-- not a group, and leaving it inactive is what keeps output identical to the+-- pre-amendment behavior for charts that contain no contra posting. Testing+-- gross activity rather than the net balance also keeps a fully offset contra+-- title inside its group instead of leaking one side into the ordinary rows.+--+-- An indeterminate (NaN) net is oriented as @(Side, NaN)@, like @diffRL@.+--+-- Complexity: O(g · m + g²) for @g@ blocks of @m@ members (the grouping is a+-- fixed, small table).+presentGroups+ :: HatVal v+ => [PresentationGroupDef]+ -> Map AccountTitles (v, v)+ -> GroupedPresentation v+presentGroups defs balances = GroupedPresentation+ { gpBlocks = blocks+ , gpRootTotals = rootTotals+ , gpConsumed = consumed+ }+ where+ grossOf title = M.findWithDefault (zeroValue, zeroValue) title balances+ hasBalance title = uncurry (/=) (grossOf title)+ hasActivity title = case M.lookup title balances of+ Nothing -> False+ Just (debit, credit) ->+ debit /= zeroValue+ || credit /= zeroValue++ memberTitles def = pgGross def ++ pgDeductions def++ -- Children of a block, in grouping order. 'pgParent' is followed with a+ -- visited set so that a malformed (cyclic) grouping is truncated instead+ -- of diverging.+ childrenOf visited key =+ [ def+ | def <- defs+ , pgParent def == Just key+ , not (S.member (pgKey def) visited)+ ]++ ownPair def = L.foldl' mergePair (emptyAccum, emptyAccum)+ [ (addAccum debit emptyAccum, addAccum credit emptyAccum)+ | title <- memberTitles def, let (debit, credit) = grossOf title ]++ mergePair (debit, credit) (otherDebit, otherCredit) =+ (mergeAccum debit otherDebit, mergeAccum credit otherCredit)++ subtreePair visited def = L.foldl' mergePair (ownPair def)+ (map (subtreePair visited') (activeChildrenOf visited' def))+ where+ visited' = S.insert (pgKey def) visited++ ownActive def = any hasActivity (pgDeductions def)++ isActive visited def = ownActive def+ || any (isActive visited') (childrenOf visited' (pgKey def))+ where+ visited' = S.insert (pgKey def) visited++ activeChildrenOf visited def =+ [ child+ | child <- childrenOf visited (pgKey def)+ , pgDivision child == pgDivision def+ , isActive visited child+ ]++ -- A block is a root when it has no usable parent, or when it is the stable+ -- representative of a malformed parent cycle. Cross-division parents are+ -- unusable because a carried subtotal and its root total must share a+ -- statement column.+ isRoot def = case pgParent def of+ Nothing -> True+ Just parent -> case lookupGroupDef defs parent of+ Nothing -> True+ Just parentDef+ | pgDivision parentDef /= pgDivision def -> True+ | otherwise -> case parentCycle def of+ Nothing -> False+ Just keys -> pgKey def == minimum keys++ parentCycle def = follow [] (pgKey def)+ where+ follow path key+ | key `elem` path = Just (dropWhile (/= key) path)+ | otherwise = case lookupGroupDef defs key >>= pgParent of+ Nothing -> Nothing+ Just parent -> follow (path ++ [key]) parent++ activeRoots = [def | def <- defs, isRoot def, isActive S.empty def]++ -- Depth-first: nested blocks are rendered before the block that carries+ -- their net, so a reader meets a subtotal only after its constituents.+ blocksOf visited def =+ concatMap (blocksOf visited') activeChildren+ ++ [(def, rowsOf visited' def activeChildren)]+ where+ visited' = S.insert (pgKey def) visited+ activeChildren = activeChildrenOf visited' def++ rowsOf visited def activeChildren =+ [ GroupRow (GrossRow title) (relativeScalar (grossOf title))+ | title <- pgGross def, hasBalance title ]+ +++ [ GroupRow (SubgroupRow (pgKey child)) (relative (subtreePair visited child))+ | child <- activeChildren ]+ +++ [ GroupRow (DeductionRow title) (relativeScalar (grossOf title))+ | title <- pgDeductions def, hasBalance title ]+ +++ [ GroupRow (NetRow (pgKey def)) (relative (subtreePair visited def)) ]+ where+ relativeScalar (debit, credit) = relative+ (addAccum debit emptyAccum, addAccum credit emptyAccum)+ relative (debit, credit) = case signAccum difference of+ Just EQ -> relativeTo normal (Side, zeroValue)+ Just GT -> relativeTo normal (Debit, roundAccum difference)+ Just LT -> relativeTo normal (Credit, roundAccum (negateAccum difference))+ Nothing -> relativeTo normal (Side, roundAccum difference)+ where+ normal = groupNormalSide def+ difference = mergeAccum debit (negateAccum credit)++ blocks = concatMap (blocksOf S.empty) activeRoots++ rootTotals = M.map (\(debit, credit) -> (roundAccum debit, roundAccum credit))+ (M.fromListWith mergePair+ [ (pgDivision def, subtreePair S.empty def) | def <- activeRoots ])++ consumed = S.fromList+ [ title | (def, _) <- blocks, title <- memberTitles def ]
+ src/ExchangeAlgebra/Accounting/Statements/Metric.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE FlexibleContexts #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.Statements.Metric+Description : Typed, read-only metrics derived from exchange-algebra coordinates.++Derived metrics are not posting coordinates. In particular, profit and loss+are two directions of one period-result identity, represented by+'PeriodProfit' and 'PeriodLoss' with non-negative values. The legacy+@AccountTitles@ constructors remain available for the established transfer+pipeline, but new reporting code should derive values without inserting those+coordinates.+-}+module ExchangeAlgebra.Accounting.Statements.Metric+ ( MetricId+ , mkMetricId+ , metricIdText+ , DerivedMetric(..)+ , PeriodResult(..)+ , MetricError(..)+ , metricForLegacyTitle+ , legacyTitlesForMetric+ , periodResultOfAlg+ , periodResultOf+ ) where++import qualified ExchangeAlgebra.Accounting.TrialBalance.Accum as Accum++import qualified Data.Map.Strict as M+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Algebra.Core.Representation (Alg, foldEntries)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..), DivisionSemantics(..), Side(..) )+import ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSemantics(asemDivisionSemantics), accountSemantics )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass(getAccountTitle, whichSide) )+import ExchangeAlgebra.Algebra.Base.Representation+ ( Hat(..), HatBase, HatBaseClass(hat) )+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as TB++-- | Stable identity for a caller-defined metric. Display labels are supplied+-- separately by a reporting context.+newtype MetricId = MetricId Text+ deriving (Show, Eq, Ord)++-- | Construct a non-blank custom metric identity.+mkMetricId :: Text -> Maybe MetricId+mkMetricId raw+ | T.null normalized = Nothing+ | otherwise = Just (MetricId normalized)+ where+ normalized = T.strip raw++-- | Return the text of a custom metric identity.+metricIdText :: MetricId -> Text+metricIdText (MetricId value) = value++-- | Identity of a value derived for reporting. Profit versus loss is carried+-- by the result value, not duplicated as two metric identities.+data DerivedMetric+ = PeriodResultMetric+ | GrossProfitMetric+ | OrdinaryProfitMetric+ | CustomMetric MetricId+ deriving (Show, Eq, Ord)++-- | A period result with structural direction and a non-negative amount.+data PeriodResult v+ = PeriodProfit v+ | PeriodLoss v+ | PeriodBreakEven+ deriving (Show, Eq)++-- | Reasons why a reporting metric cannot be derived.+data MetricError+ = MetricNotAvailableAtStage TB.TrialBalanceStage+ | ResidualDerivedCoordinate AccountTitles+ | WildcardMetricSide AccountTitles+ | WildcardMetricAccount+ -- ^ A posting carries the wildcard account title, so its statement+ -- division cannot be determined.+ | InvalidMetricValue AccountTitles+ deriving (Show, Eq)++-- | Map an engine-only legacy coordinate to its reporting identity.+metricForLegacyTitle :: AccountTitles -> Maybe DerivedMetric+metricForLegacyTitle title = case title of+ NetIncome -> Just PeriodResultMetric+ NetLoss -> Just PeriodResultMetric+ GrossProfit -> Just GrossProfitMetric+ OrdinaryProfit -> Just OrdinaryProfitMetric+ _ -> Nothing++-- | Legacy coordinates retained for one metric. This is a migration aid,+-- not a list of statement lines.+legacyTitlesForMetric :: DerivedMetric -> [AccountTitles]+legacyTitlesForMetric metric = case metric of+ PeriodResultMetric -> [NetIncome, NetLoss]+ GrossProfitMetric -> [GrossProfit]+ OrdinaryProfitMetric -> [OrdinaryProfit]+ CustomMetric _ -> []++-- | Derive period profit or loss without adding a balancing coordinate.+-- Only genuine statement-classified Cost and Revenue titles participate;+-- direction-encoding legacy coordinates are therefore excluded structurally.+-- Invalid values and wildcard sides are returned as explicit errors. For+-- externally sourced trial balances, prefer 'periodResultOf'.+periodResultOfAlg+ :: (HatVal v, ExBaseClass b)+ => Alg v b+ -> Either MetricError (PeriodResult v)+periodResultOfAlg alg+ | (_, base) : _ <- invalidEntries =+ Left (InvalidMetricValue (getAccountTitle base))+ | (_, base) : _ <- wildcardEntries =+ Left (WildcardMetricSide (getAccountTitle base))+ | signAccum result == Just EQ = Right PeriodBreakEven+ | signAccum result == Just GT = Right (PeriodProfit (roundAccum result))+ | otherwise = Right (PeriodLoss (roundAccum (negateAccum result)))+ where+ entries = foldEntries (\current value base -> (value, base) : current) [] alg+ invalidEntries = filter (isErrorValue . fst) entries+ wildcardEntries = filter ((== HatNot) . hat . snd) entries+ result = foldEntries collect emptyAccum alg+ collect totals value base+ | isStatementNominal (getAccountTitle base) =+ case whichSide base of+ Debit -> mergeAccum totals (negateAccum (addAccum value emptyAccum))+ Credit -> addAccum value totals+ Side -> totals+ | otherwise = totals++-- | Derive a period result from a before-closing trial balance.+-- The stage is stated by the caller; an after-closing trial balance has no+-- nominal balances left, so it is rejected instead of reporting break-even.+-- Invalid values, wildcard sides, and the wildcard account title are rejected+-- before any balance is accumulated, because accumulation determines the+-- debit or credit side of every posting.+-- A non-zero legacy derived coordinate is rejected to prevent a caller from+-- treating an already-derived balancing item as another nominal account.+-- Consolidation attribution coordinates do not participate, so the result is+-- the period result before attribution to owners and non-controlling interests.+-- No accounting finding is consulted; inspect+-- 'ExchangeAlgebra.Accounting.TrialBalance.Validation.trialBalanceFindings'+-- separately when the trial balance must also be audited.+periodResultOf+ :: HatVal v+ => TB.TrialBalanceStage+ -> Alg v (HatBase AccountTitles)+ -> Either MetricError (PeriodResult v)+periodResultOf stage alg+ | stage /= TB.BeforeClosing =+ Left (MetricNotAvailableAtStage stage)+ | issue : _ <- metricStructureErrors alg = Left issue+ | legacy : _ <- nonZeroLegacyTitles alg =+ Left (ResidualDerivedCoordinate legacy)+ | otherwise = periodResultOfAlg alg++isStatementNominal :: AccountTitles -> Bool+isStatementNominal title = case accountSemantics title of+ Just semantics -> case asemDivisionSemantics semantics of+ StatementDivision Cost -> True+ StatementDivision Revenue -> True+ _ -> False+ Nothing -> False++-- | Invalid values, wildcard sides, and the wildcard account title, found+-- without determining any posting side.+metricStructureErrors+ :: HatVal v+ => Alg v (HatBase AccountTitles)+ -> [MetricError]+metricStructureErrors alg =+ map (InvalidMetricValue . fst) invalidValues+ ++ map WildcardMetricSide wildcardSides+ ++ map (const WildcardMetricAccount) wildcardAccounts+ where+ (invalidValues, wildcardAccounts, wildcardSides) = Accum.structureErrors alg++nonZeroLegacyTitles+ :: (HatVal v, ExBaseClass b)+ => Alg v b+ -> [AccountTitles]+nonZeroLegacyTitles alg =+ [ title+ | (title, total) <- M.toList totals+ , signAccum total /= Just EQ+ ]+ where+ totals = M.filterWithKey (\title _ -> case metricForLegacyTitle title of+ Nothing -> False+ Just _ -> True) (Accum.accountAccums alg)
+ src/ExchangeAlgebra/Accounting/Statements/Presentation.hs view
@@ -0,0 +1,986 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.Statements.Presentation+Description : Context-sensitive JGAAP presentation from trial balances.++This module is the reporting boundary between a trial balance and presented+statements. 'present' computes statements whenever a correct statement can be+computed; it does not judge whether the trial balance is normal for+accounting purposes. Abnormal-side balances (for example a deficit in+retained earnings), temporary-account residuals, reciprocal-account+mismatches, and unclosed nominal balances are reported separately by+'TB.trialBalanceFindings', and the caller decides how to treat them.+Presentation fails only when no correct statement exists: an invalid value,+a wildcard account or side, a trial balance whose debits and credits differ,+a conflicting presentation instruction, a balance on a title that no+statement section can hold, debits and credits that do not reconcile after+presentation, or a period result that cannot be derived.+Bookkeeping coordinates remain unchanged; all eliminations, relabelings,+maturity allocations, netting decisions, and subtotals are recorded as+presentation audit events.++Present a cash contribution with the standalone reporting context.+Read amounts and sections without relying on the order of statement lines.++>>> import qualified ExchangeAlgebra.Algebra.Core as Algebra+>>> import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase(..))+>>> import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+>>> import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+>>> type TrialBalance = Algebra.Alg MoneyDecimal (HatBase AccountTitles)+>>> let cash = 100 Algebra..@ (Not :< Cash) :: TrialBalance+>>> let equity = 100 Algebra..@ (Not :< CapitalStock) :: TrialBalance+>>> let capital = cash Algebra..+ equity :: TrialBalance+>>> let context = jcciSecondGradeContext Standalone+>>> let statements = present context TB.BeforeClosing capital+>>> fmap (length . _statementLines) statements+Right 2+>>> fmap (sum . map _lineAmount . _statementLines) statements == Right 200+True+>>> let isCash line = _lineAccount line == Cash+>>> let isCurrent line = _lineSection line == CurrentAssetsSection+>>> let isCurrentCash line = isCash line && isCurrent line+>>> let hasCurrentCash = any isCurrentCash . _statementLines+>>> fmap hasCurrentCash statements+Right True+-}+module ExchangeAlgebra.Accounting.Statements.Presentation+ ( AccountingFramework(..)+ , ReportingScope(..)+ , PresentationProfile(..)+ , StatementSection(..)+ , StatementLine(..)+ , PresentationAllocation(..)+ , PresentationRelabel(..)+ , MaterialityTreatment(..)+ , MaterialityDecision(..)+ , ContraPresentationRule(..)+ , CustomMetricLabel(..)+ , SubtotalCoverage(..)+ , SubtotalDefinition(..)+ , StatementSubtotal(..)+ , ReportingContext(..)+ , jcciSecondGradeContext+ , PresentationAuditEvent(..)+ , PresentationIssue(..)+ , FinancialStatements(..)+ , presentationLabel+ , metricLabel+ , present+ ) where++import qualified ExchangeAlgebra.Accounting.TrialBalance.Accum as Accum++import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import qualified Data.Set as S+import Data.Set (Set)+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Algebra.Base.Representation (HatBase)+import ExchangeAlgebra.Algebra.Core.Representation (Alg)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountDivision(..)+ , AccountRole(..)+ , DivisionSemantics(..)+ , FixedCurrent(..)+ , ReportingEligibility(..)+ , Side(..)+ )+import ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSemantics(asemDivisionSemantics,+ asemReportingEligibility, asemRoles) )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import qualified ExchangeAlgebra.Accounting.Account.Registry as Registry+import ExchangeAlgebra.Accounting.Statements.Metric+ ( DerivedMetric(..)+ , MetricError+ , MetricId+ , PeriodResult(..)+ , periodResultOf+ )+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as TB+import ExchangeAlgebra.Accounting.TrialBalance.Balance+ ( balanceAmount+ , balanceFor+ , balanceSide+ )++-- | Land 4 deliberately supports JGAAP only.+data AccountingFramework = JGAAP+ deriving (Show, Eq)++-- | Whether the report covers standalone or combined accounts.+data ReportingScope = Standalone | Combined+ deriving (Show, Eq)++-- | Choice of statement labels and presentation profile.+data PresentationProfile+ = JcciSecondGradeReport+ | CanonicalJapanese+ | CanonicalEnglish+ deriving (Show, Eq)++-- | Section in which a statement line is displayed.+data StatementSection+ = CurrentAssetsSection+ | NoncurrentAssetsSection+ | CurrentLiabilitiesSection+ | NoncurrentLiabilitiesSection+ | EquitySection+ | RevenueSection+ | ExpenseSection+ | ContextualDebitSection+ | ContextualCreditSection+ deriving (Show, Eq)++-- | An account line with its label, section, side, amount, and deduction flag.+data StatementLine v = StatementLine+ { _lineAccount :: AccountTitles+ , _lineLabel :: Text+ , _lineSection :: StatementSection+ , _lineSide :: Side+ , _lineAmount :: v+ , _lineIsDeduction :: Bool+ }+ deriving (Show, Eq)++-- | Evidence-backed split of one title without creating new bookkeeping+-- coordinates. Current and non-current parts must sum exactly to its balance.+data PresentationAllocation v = PresentationAllocation+ { _allocationAccount :: AccountTitles+ , _allocationCurrent :: v+ , _allocationNoncurrent :: v+ , _allocationEvidence :: Text+ }+ deriving (Show, Eq)++-- | An account relabeling instruction with its rationale.+data PresentationRelabel = PresentationRelabel+ { _relabelSource :: AccountTitles+ , _relabelTarget :: AccountTitles+ , _relabelRationale :: Text+ }+ deriving (Show, Eq)++-- | Whether an account is presented gross, separately, or net against another.+data MaterialityTreatment+ = PresentGross+ | PresentSeparately+ | NetAgainst AccountTitles+ deriving (Show, Eq)++-- | An account materiality treatment with its rationale.+data MaterialityDecision = MaterialityDecision+ { _materialityAccount :: AccountTitles+ , _materialityTreatment :: MaterialityTreatment+ , _materialityRationale :: Text+ }+ deriving (Show, Eq)++-- | A separate or net presentation instruction for a contra account.+data ContraPresentationRule+ = PresentContraSeparately AccountTitles Text+ | NetContraAgainst AccountTitles AccountTitles Text+ deriving (Show, Eq)++-- | Profile-specific display labels for a caller-defined metric. The metric+-- identity remains separate from these labels.+data CustomMetricLabel = CustomMetricLabel+ { _customMetricIdentity :: MetricId+ , _customMetricLabelJapanese :: Text+ , _customMetricLabelEnglish :: Text+ }+ deriving (Show, Eq)++-- | How a subtotal handles absent account titles.+data SubtotalCoverage+ = RequireAllTitlesPresent+ | TreatAbsentAsZero+ deriving (Show, Eq)++-- | A subtotal is a typed reporting definition, never an account-basis title.+data SubtotalDefinition = SubtotalDefinition+ { _subtotalMetric :: DerivedMetric+ , _subtotalCreditTitles :: [AccountTitles]+ , _subtotalDebitTitles :: [AccountTitles]+ , _subtotalCoverage :: SubtotalCoverage+ }+ deriving (Show, Eq)++-- | A reporting metric with its label and account balance.+data StatementSubtotal v = StatementSubtotal+ { _statementSubtotalMetric :: DerivedMetric+ , _statementSubtotalLabel :: Text+ , _statementSubtotalBalance :: TB.AccountBalance v+ }+ deriving (Show, Eq)++-- | Framework, scope, profile, and instructions used to present a trial balance.+data ReportingContext v = ReportingContext+ { _reportingFramework :: AccountingFramework+ , _reportingScope :: ReportingScope+ , _presentationProfile :: PresentationProfile+ , _maturitySensitiveTitles :: Set AccountTitles+ , _presentationAllocations :: [PresentationAllocation v]+ , _presentationRelabels :: [PresentationRelabel]+ , _materialityDecisions :: [MaterialityDecision]+ , _contraPresentationRules :: [ContraPresentationRule]+ , _subtotalDefinitions :: [SubtotalDefinition]+ , _customMetricLabels :: [CustomMetricLabel]+ }+ deriving (Show, Eq)++-- | Construct a JGAAP context with the JCCI grade-2 presentation profile.+-- The caller explicitly chooses the reporting scope; it is not inferred from+-- transaction entities or periods. Maturity-sensitive titles start empty, as+-- do allocations, relabels, materiality decisions, contra rules, subtotal+-- definitions, and custom metric labels. Use record updates to supply the+-- instructions needed for the report. This starting context does not guarantee+-- that 'present' succeeds for every validated trial balance.+jcciSecondGradeContext :: ReportingScope -> ReportingContext v+jcciSecondGradeContext scope = ReportingContext+ { _reportingFramework = JGAAP+ , _reportingScope = scope+ , _presentationProfile = JcciSecondGradeReport+ , _maturitySensitiveTitles = S.empty+ , _presentationAllocations = []+ , _presentationRelabels = []+ , _materialityDecisions = []+ , _contraPresentationRules = []+ , _subtotalDefinitions = []+ , _customMetricLabels = []+ }++-- | A recorded presentation operation and its supporting values.+data PresentationAuditEvent v+ = ReciprocalAccountsEliminated+ (TB.AccountBalance v) (TB.AccountBalance v)+ | AccountRelabeled+ AccountTitles AccountTitles (TB.AccountBalance v) Text+ | BalanceAllocated AccountTitles v v Text+ | MaterialityApplied+ AccountTitles MaterialityTreatment (TB.AccountBalance v) Text+ | ContraPresentationApplied+ AccountTitles (Maybe AccountTitles) (TB.AccountBalance v) Text+ | SubtotalCalculated DerivedMetric (TB.AccountBalance v)+ | LabelOverridden AccountTitles Text Text+ deriving (Show, Eq)++-- | Reasons why no correct statement can be presented. Accounting+-- abnormalities that still allow a statement are not issues; they are+-- reported by 'TB.trialBalanceFindings'.+data PresentationIssue v+ = InvalidStatementValue AccountTitles+ -- ^ A posting value is invalid (for example NaN or infinite).+ | WildcardStatementBase+ -- ^ An account title or a Hat/Not side is a wildcard, so the debit or+ -- credit side cannot be determined.+ | UnbalancedStatementInput v v+ -- ^ Debit and credit totals of the trial balance before any+ -- presentation step (the per-title net balances summed on each side,+ -- as in a balance trial balance). Checked before reciprocal accounts are eliminated,+ -- so the elimination cannot hide an original imbalance.+ | PeriodResultUnavailable MetricError+ -- ^ The before-closing period result cannot be derived, so the mandatory+ -- period-result subtotal cannot be presented.+ | MissingPresentationAllocation AccountTitles+ | DuplicatePresentationAllocation AccountTitles+ | InvalidPresentationAllocation+ AccountTitles (TB.AccountBalance v) v v+ | BlankPresentationEvidence AccountTitles+ | UnexpectedPresentationAllocation AccountTitles+ | ConflictingPresentationInstruction AccountTitles+ | MissingPresentationAccount AccountTitles+ | BlankPresentationRationale AccountTitles+ | UnpresentableBalance AccountTitles (TB.AccountBalance v)+ | InvalidSubtotalDefinition DerivedMetric+ | DuplicateMetricIdentity DerivedMetric+ | UnlabelledCustomMetric MetricId+ | UnreconciledPresentation v v+ deriving (Show, Eq)++-- | Presented statement lines, subtotals, and the presentation audit.+data FinancialStatements v = FinancialStatements+ { _statementFramework :: AccountingFramework+ , _statementScope :: ReportingScope+ , _statementProfile :: PresentationProfile+ , _statementTrialBalanceStage :: TB.TrialBalanceStage+ , _statementLines :: [StatementLine v]+ , _statementSubtotals :: [StatementSubtotal v]+ , _presentationAudit :: [PresentationAuditEvent v]+ }+ deriving (Show, Eq)++-- | Profile-specific Japanese display-label overrides.+jcciSecondGradeLabelOverrides :: Map AccountTitles Text+jcciSecondGradeLabelOverrides = M.fromList+ [ (AdvancesReceived, T.pack "契約負債")+ ]++-- | Profile-specific display label. Japanese profiles use the registry's+-- cleaned statement label, with the JCCI grade-2 overrides applied first.+presentationLabel :: PresentationProfile -> AccountTitles -> Text+presentationLabel profile title = case Registry.accountSpec title of+ Nothing -> T.pack (show title)+ Just spec -> case profile of+ JcciSecondGradeReport -> M.findWithDefault+ (Registry.asLabelJa spec) title jcciSecondGradeLabelOverrides+ CanonicalJapanese -> Registry.asLabelJa spec+ CanonicalEnglish -> Registry.asNameEn spec++-- | Resolve a metric's display label independently of its stable identity.+-- Profit/loss wording follows the structural balance direction.+metricLabel+ :: ReportingContext v+ -> DerivedMetric+ -> TB.AccountBalance v+ -> Maybe Text+metricLabel context metric balance = case metric of+ PeriodResultMetric -> Just (builtinLabel+ "当期純損益" "当期純利益" "当期純損失"+ "Net result" "Net income" "Net loss")+ GrossProfitMetric -> Just (builtinLabel+ "売上総損益" "売上総利益" "売上総損失"+ "Gross result" "Gross profit" "Gross loss")+ OrdinaryProfitMetric -> Just (builtinLabel+ "経常損益" "経常利益" "経常損失"+ "Ordinary result" "Ordinary profit" "Ordinary loss")+ CustomMetric metricId -> customLabel metricId+ where+ english = _presentationProfile context == CanonicalEnglish+ builtinLabel neutralJa creditJa debitJa neutralEn creditEn debitEn =+ case (english, balance) of+ (False, TB.NoBalance) -> neutralJa+ (False, TB.CreditBalance _) -> creditJa+ (False, TB.DebitBalance _) -> debitJa+ (True, TB.NoBalance) -> neutralEn+ (True, TB.CreditBalance _) -> creditEn+ (True, TB.DebitBalance _) -> debitEn+ customLabel metricId = case filter+ ((== metricId) . _customMetricIdentity)+ (_customMetricLabels context) of+ [label] -> Just (if english+ then _customMetricLabelEnglish label+ else _customMetricLabelJapanese label)+ _ -> Nothing++-- | Prepare statements using exact posting balances for transformations,+-- allocations, subtotals, and reconciliation. Each displayed amount is rounded+-- once. The sum of displayed amounts is not guaranteed to equal its subtotal.+--+-- The trial balance is a single reporting scope over account titles: select+-- an entity, period, or department and map it to account titles before calling+-- this function. The stage is stated by the caller. A 'TB.BeforeClosing' trial+-- balance always receives a 'PeriodResultMetric' subtotal derived as+-- 'periodResultOf' derives it; a caller-supplied 'PeriodResultMetric'+-- definition is rejected with 'InvalidSubtotalDefinition'. An+-- 'TB.AfterClosing' trial balance receives no period-result subtotal.+-- Presentation instructions that move a balance between a profit-and-loss+-- title and a balance-sheet title (relabels, materiality netting, contra+-- netting, and maturity allocations) are rejected with+-- 'ConflictingPresentationInstruction', so the period-result subtotal always+-- equals the exact sum of the revenue and expense balances. A maturity+-- allocation or a maturity-sensitive title must be an asset or a liability.+--+-- Titles whose balance must be split between current and noncurrent are the+-- union of '_maturitySensitiveTitles' and the implicitly maturity-sensitive+-- titles. '_maturitySensitiveTitles' lists titles whose balances require a+-- maturity split.+--+-- Success does not mean that the trial balance is normal for accounting+-- purposes. A balance on the side opposite to its home side is presented in+-- its own section on the side it actually has; inspect+-- 'TB.trialBalanceFindings' to audit it.+present+ :: HatVal v+ => ReportingContext v+ -> TB.TrialBalanceStage+ -> Alg v (HatBase AccountTitles)+ -> Either (NonEmpty (PresentationIssue v)) (FinancialStatements v)+present context stage alg = case issues of+ issue : rest -> Left (issue :| rest)+ [] -> Right FinancialStatements+ { _statementFramework = framework+ , _statementScope = _reportingScope context+ , _statementProfile = _presentationProfile context+ , _statementTrialBalanceStage = stage+ , _statementLines = statementLines+ , _statementSubtotals = subtotals+ , _presentationAudit = eliminationAudit+ ++ relabelAudit ++ materialityAudit ++ contraAudit+ ++ allocationAudit ++ subtotalAudit ++ labelAudit+ }+ where+ framework = case _reportingFramework context of+ JGAAP -> JGAAP+ initialStates = Accum.accountAccums alg+ initial = M.map accumulatedBalance initialStates+ structuralIssues = case statementStructureIssues alg of+ [] -> inputBalanceIssues initialStates+ issues' -> issues'+ (eliminated, eliminationAudit) = eliminateReciprocals context initialStates+ (relabeled, relabelAudit) = applyRelabels context eliminated+ (materialized, materialityAudit) = applyMateriality context relabeled+ (transformed, contraAudit) = applyContraRules context materialized+ explicitRequired = _maturitySensitiveTitles context+ requiredMaturity = S.union explicitRequired+ (implicitMaturityTitles displayedBalances)+ displayedBalances = M.map accumulatedBalance transformed+ statementLines = renderLines context displayedBalances+ contextIssues = instructionIssues explicitRequired context initial+ ++ allocationIssues requiredMaturity context displayedBalances transformed+ ++ subtotalIssues context initial displayedBalances+ ++ coverageIssues displayedBalances+ ++ reconciliationIssues context transformed+ issues+ | not (null structuralIssues) = structuralIssues+ | otherwise = contextIssues ++ periodResultIssues+ (periodResultIssues, periodResultSubtotals) =+ periodResultSubtotal context stage alg+ allocationAudit =+ [ BalanceAllocated title current noncurrent evidence+ | PresentationAllocation title current noncurrent evidence+ <- _presentationAllocations context+ , balanceFor title displayedBalances /= TB.NoBalance+ ]+ subtotals = periodResultSubtotals +++ [ StatementSubtotal metric label balance+ | SubtotalDefinition metric credits debits _ <-+ _subtotalDefinitions context+ , let balance = subtotalBalance transformed credits debits+ , Just label <- [metricLabel context metric balance]+ ]+ subtotalAudit =+ [ SubtotalCalculated metric balance+ | StatementSubtotal metric _ balance <- subtotals+ ]+ labelAudit =+ [ LabelOverridden title canonical displayed+ | _presentationProfile context == JcciSecondGradeReport+ , (title, _) <- M.toList jcciSecondGradeLabelOverrides+ , title `elem` map _lineAccount statementLines+ , let canonical = presentationLabel CanonicalJapanese title+ , let displayed = presentationLabel JcciSecondGradeReport title+ , canonical /= displayed+ ]++eliminateReciprocals+ :: HatVal v => ReportingContext v+ -> Map AccountTitles (Accum v)+ -> (Map AccountTitles (Accum v), [PresentationAuditEvent v])+eliminateReciprocals context balances = case _reportingScope context of+ Standalone -> (balances, [])+ Combined ->+ ( M.delete BranchCurrentAccount+ (M.delete HeadOfficeCurrentAccount balances)+ , [ ReciprocalAccountsEliminated branch headOffice+ | branch /= TB.NoBalance || headOffice /= TB.NoBalance+ ]+ )+ where+ branch = accumulatedBalance (accumulatedFor BranchCurrentAccount balances)+ headOffice = accumulatedBalance (accumulatedFor HeadOfficeCurrentAccount balances)++applyRelabels+ :: HatVal v+ => ReportingContext v+ -> Map AccountTitles (Accum v)+ -> (Map AccountTitles (Accum v), [PresentationAuditEvent v])+applyRelabels context balances0 =+ Prelude.foldl applyOne (balances0, []) (_presentationRelabels context)+ where+ applyOne (balances, events) (PresentationRelabel source target rationale) =+ let sourceBalance = accumulatedBalance (accumulatedFor source balances)+ targetBalance = accumulatedFor target balances+ next = M.insert target (mergeAccum targetBalance (accumulatedFor source balances))+ (M.delete source balances)+ in (next, events +++ [AccountRelabeled source target sourceBalance rationale])++applyMateriality+ :: HatVal v+ => ReportingContext v+ -> Map AccountTitles (Accum v)+ -> (Map AccountTitles (Accum v), [PresentationAuditEvent v])+applyMateriality context balances0 =+ Prelude.foldl applyOne (balances0, []) (_materialityDecisions context)+ where+ applyOne (balances, events) decision =+ let source = _materialityAccount decision+ treatment = _materialityTreatment decision+ next = case treatment of+ PresentGross -> balances+ PresentSeparately -> balances+ NetAgainst target -> moveBalance source target balances+ event = MaterialityApplied source treatment+ (accumulatedBalance (accumulatedFor source balances))+ (_materialityRationale decision)+ in (next, events ++ [event])++applyContraRules+ :: HatVal v+ => ReportingContext v+ -> Map AccountTitles (Accum v)+ -> (Map AccountTitles (Accum v), [PresentationAuditEvent v])+applyContraRules context balances0 =+ Prelude.foldl applyOne (balances0, [])+ (_contraPresentationRules context)+ where+ applyOne (balances, events) rule = case rule of+ PresentContraSeparately source rationale ->+ (balances, events ++ [ContraPresentationApplied source Nothing+ (accumulatedBalance (accumulatedFor source balances)) rationale])+ NetContraAgainst source target rationale ->+ ( moveBalance source target balances+ , events ++ [ContraPresentationApplied source (Just target)+ (accumulatedBalance (accumulatedFor source balances)) rationale]+ )++moveBalance+ :: HatVal v+ => AccountTitles -> AccountTitles+ -> Map AccountTitles (Accum v)+ -> Map AccountTitles (Accum v)+moveBalance source target balances =+ if source == target+ then balances+ else M.insert target+ (mergeAccum (accumulatedFor target balances)+ (accumulatedFor source balances))+ (M.delete source balances)++allocationIssues+ :: HatVal v+ => Set AccountTitles+ -> ReportingContext v+ -> Map AccountTitles (TB.AccountBalance v)+ -> Map AccountTitles (Accum v)+ -> [PresentationIssue v]+allocationIssues required context balances states = concatMap checkTitle checkedTitles+ ++ [ DuplicatePresentationAllocation title+ | title <- duplicateTitles (map _allocationAccount allocations)+ ]+ ++ map UnexpectedPresentationAllocation+ (S.toList (allocationTitles `S.difference` required))+ where+ allocations = _presentationAllocations context+ allocationTitles = S.fromList (map _allocationAccount allocations)+ checkedTitles = S.toList (required `S.union` allocationTitles)+ checkTitle title+ | Just semantics <- Registry.accountSemantics title+ , StatementDivision division <- asemDivisionSemantics semantics+ , division `elem` [Assets, Liability] = checkBalance title+ | otherwise = [ConflictingPresentationInstruction title]+ checkBalance title = case balanceFor title balances of+ TB.NoBalance+ | title `S.member` allocationTitles ->+ [MissingPresentationAccount title]+ | otherwise -> []+ balance -> case filter ((== title) . _allocationAccount) allocations of+ [] -> [MissingPresentationAllocation title]+ [allocation]+ | T.null (T.strip (_allocationEvidence allocation)) ->+ [BlankPresentationEvidence title]+ | allocationMatches (accumulatedFor title states) allocation -> []+ | otherwise -> [InvalidPresentationAllocation title balance+ (_allocationCurrent allocation)+ (_allocationNoncurrent allocation)]+ _ -> []++allocationMatches+ :: HatVal v+ => Accum v -> PresentationAllocation v -> Bool+allocationMatches total allocation =+ _allocationCurrent allocation >= 0+ && _allocationNoncurrent allocation >= 0+ && signAccum (mergeAccum allocated (negateAccum magnitude)) == Just EQ+ where+ magnitude+ | signAccum total == Just LT = negateAccum total+ | otherwise = total+ allocated = addAccum (_allocationCurrent allocation)+ (addAccum (_allocationNoncurrent allocation) emptyAccum)++instructionIssues+ :: HatVal v+ => Set AccountTitles+ -> ReportingContext v+ -> Map AccountTitles (TB.AccountBalance v)+ -> [PresentationIssue v]+instructionIssues explicitRequired context balances =+ duplicateInstructionIssues+ ++ concatMap relabelIssue (_presentationRelabels context)+ ++ concatMap materialityIssue (_materialityDecisions context)+ ++ concatMap contraIssue (_contraPresentationRules context)+ where+ duplicateInstructionIssues =+ map ConflictingPresentationInstruction+ (duplicateTitles instructionSources)+ ++ [ ConflictingPresentationInstruction title+ | _reportingScope context == Combined+ , title <- [BranchCurrentAccount, HeadOfficeCurrentAccount]+ , title `elem` instructionSources+ ]+ instructionSources = concatMap relabelTitles (_presentationRelabels context)+ ++ concatMap materialityTitles (_materialityDecisions context)+ ++ concatMap contraTitles (_contraPresentationRules context)+ relabelIssue rule+ | T.null (T.strip (_relabelRationale rule)) =+ [BlankPresentationRationale (_relabelSource rule)]+ | balanceFor (_relabelSource rule) balances == TB.NoBalance =+ [MissingPresentationAccount (_relabelSource rule)]+ | _relabelSource rule `S.member` explicitRequired =+ [ConflictingPresentationInstruction (_relabelSource rule)]+ | isNominal (_relabelSource rule) /= isNominal (_relabelTarget rule) =+ [ConflictingPresentationInstruction (_relabelSource rule)]+ | not (presentableTarget (_relabelTarget rule)) =+ [ConflictingPresentationInstruction (_relabelTarget rule)]+ | otherwise = []+ materialityIssue decision+ | T.null (T.strip (_materialityRationale decision)) =+ [BlankPresentationRationale (_materialityAccount decision)]+ | balanceFor (_materialityAccount decision) balances == TB.NoBalance =+ [MissingPresentationAccount (_materialityAccount decision)]+ | otherwise = case _materialityTreatment decision of+ NetAgainst target+ | _materialityAccount decision `S.member` explicitRequired ->+ [ConflictingPresentationInstruction+ (_materialityAccount decision)]+ | target == _materialityAccount decision ->+ [ConflictingPresentationInstruction target]+ | isNominal (_materialityAccount decision) /= isNominal target ->+ [ConflictingPresentationInstruction (_materialityAccount decision)]+ | not (presentableTarget target) ->+ [ConflictingPresentationInstruction target]+ | M.notMember target balances ->+ [MissingPresentationAccount target]+ _ -> []+ contraIssue rule = case rule of+ PresentContraSeparately source rationale+ | T.null (T.strip rationale) ->+ [BlankPresentationRationale source]+ | balanceFor source balances == TB.NoBalance ->+ [MissingPresentationAccount source]+ | otherwise -> []+ NetContraAgainst source target rationale+ | T.null (T.strip rationale) ->+ [BlankPresentationRationale source]+ | balanceFor source balances == TB.NoBalance ->+ [MissingPresentationAccount source]+ | source `S.member` explicitRequired ->+ [ConflictingPresentationInstruction source]+ | source == target ->+ [ConflictingPresentationInstruction source]+ | isNominal source /= isNominal target ->+ [ConflictingPresentationInstruction source]+ | not (presentableTarget target) ->+ [ConflictingPresentationInstruction target]+ | M.notMember target balances ->+ [MissingPresentationAccount target]+ | otherwise -> []+ isNominal title = case Registry.accountSemantics title of+ Just semantics -> asemDivisionSemantics semantics `elem`+ [StatementDivision Revenue, StatementDivision Cost]+ Nothing -> False+ relabelTitles rule = [_relabelSource rule, _relabelTarget rule]+ materialityTitles decision = _materialityAccount decision : case+ _materialityTreatment decision of+ NetAgainst target -> [target]+ PresentGross -> []+ PresentSeparately -> []+ contraTitles (PresentContraSeparately source _) = [source]+ contraTitles (NetContraAgainst source target _) = [source, target]++duplicateTitles :: [AccountTitles] -> [AccountTitles]+duplicateTitles titles = S.toList+ (S.fromList [title | title <- titles, count title titles > 1])+ where+ count needle = length . filter (== needle)++presentableTarget :: AccountTitles -> Bool+presentableTarget title = case Registry.accountSemantics title of+ Just semantics -> asemReportingEligibility semantics `elem`+ [StatementEligible, ContextualPresentation]+ Nothing -> False++implicitMaturityTitles+ :: Map AccountTitles (TB.AccountBalance v) -> Set AccountTitles+implicitMaturityTitles balances = S.fromList+ [ title+ | (title, balance) <- M.toList balances+ , balanceSide balance /= Side+ , fixedCurrent title == Other+ , Just semantics <- [Registry.accountSemantics title]+ , StatementDivision division <- [asemDivisionSemantics semantics]+ , division `elem` [Assets, Liability]+ ]++-- | Invalid values and wildcard bases, which leave the debit or credit side+-- undetermined. When this list is non-empty, no other check is evaluated,+-- because the account accumulation relies on a determined side.+statementStructureIssues+ :: HatVal v+ => Alg v (HatBase AccountTitles)+ -> [PresentationIssue v]+statementStructureIssues alg =+ map (InvalidStatementValue . fst) invalidValues+ ++ map (const WildcardStatementBase) (wildcardAccounts ++ wildcardSides)+ where+ (invalidValues, wildcardAccounts, wildcardSides) = Accum.structureErrors alg++-- | Exact debit and credit totals of the trial balance before elimination,+-- relabeling, netting, or allocation; empty when they are equal.+inputBalanceIssues+ :: HatVal v+ => Map AccountTitles (Accum v)+ -> [PresentationIssue v]+inputBalanceIssues states =+ [ UnbalancedStatementInput (roundAccum debit) (roundAccum credit)+ | signAccum (mergeAccum debit (negateAccum credit)) /= Just EQ ]+ where+ (debit, credit) = M.foldl' collect (emptyAccum, emptyAccum) states+ collect (debits, credits) total = case signAccum total of+ Just GT -> (mergeAccum debits total, credits)+ Just LT -> (debits, mergeAccum credits (negateAccum total))+ _ -> (debits, credits)++-- | The mandatory period-result subtotal of a before-closing trial balance,+-- derived as 'periodResultOf' derives it, or the issue that prevents it. An+-- after-closing trial balance yields neither.+periodResultSubtotal+ :: HatVal v+ => ReportingContext v+ -> TB.TrialBalanceStage+ -> Alg v (HatBase AccountTitles)+ -> ([PresentationIssue v], [StatementSubtotal v])+periodResultSubtotal _ TB.AfterClosing _ = ([], [])+periodResultSubtotal context TB.BeforeClosing alg = case periodResultOf TB.BeforeClosing alg of+ Left issue -> ([PeriodResultUnavailable issue], [])+ Right result ->+ let balance = case result of+ PeriodProfit value -> TB.CreditBalance value+ PeriodLoss value -> TB.DebitBalance value+ PeriodBreakEven -> TB.NoBalance+ in ([], [StatementSubtotal PeriodResultMetric label balance+ | Just label <- [metricLabel context PeriodResultMetric balance]])++coverageIssues+ :: Map AccountTitles (TB.AccountBalance v) -> [PresentationIssue v]+coverageIssues balances =+ [ UnpresentableBalance title balance+ | (title, balance) <- M.toList balances+ , balanceSide balance /= Side+ , not (presentableTarget title)+ ]++reconciliationIssues+ :: HatVal v+ => ReportingContext v -> Map AccountTitles (Accum v) -> [PresentationIssue v]+reconciliationIssues context balances =+ [ UnreconciledPresentation (roundAccum debit) (roundAccum credit)+ | signAccum (mergeAccum debit (negateAccum credit)) /= Just EQ ]+ where+ (debit, credit) = M.foldlWithKey' collect (emptyAccum, emptyAccum) balances+ collect totals@(debits, credits) title total+ | not (hasAllocation title)+ && reportingSection title (accumulatedBalance total) == Nothing = totals+ | otherwise = case signAccum total of+ Just GT -> (mergeAccum debits (allocated title total), credits)+ Just LT -> (debits, mergeAccum credits (allocated title (negateAccum total)))+ _ -> (debits, credits)+ hasAllocation title = any ((== title) . _allocationAccount)+ (_presentationAllocations context)+ allocated title total = case filter ((== title) . _allocationAccount)+ (_presentationAllocations context) of+ allocation : _ -> addAccum (_allocationCurrent allocation)+ (addAccum (_allocationNoncurrent allocation) emptyAccum)+ [] -> total++subtotalIssues+ :: HatVal v+ => ReportingContext v+ -> Map AccountTitles (TB.AccountBalance v)+ -> Map AccountTitles (TB.AccountBalance v)+ -> [PresentationIssue v]+subtotalIssues context initial balances = duplicateDefinitionIssues+ ++ duplicateCustomLabelIssues+ ++ concatMap check definitions+ where+ definitions = _subtotalDefinitions context+ definitionMetrics = map _subtotalMetric definitions+ duplicateDefinitionIssues =+ [ DuplicateMetricIdentity metric+ | metric <- duplicateMetrics definitionMetrics+ ]+ customLabelIds = map _customMetricIdentity (_customMetricLabels context)+ duplicateCustomLabelIssues =+ [ DuplicateMetricIdentity (CustomMetric metricId)+ | metricId <- duplicateMetricIds customLabelIds+ ]+ check (SubtotalDefinition metric credits debits coverage)+ | metric == PeriodResultMetric = [InvalidSubtotalDefinition metric]+ | null credits && null debits = [InvalidSubtotalDefinition metric]+ | not (null (duplicateTitles (credits ++ debits))) =+ [InvalidSubtotalDefinition metric]+ | customMetricUnlabelled metric = [customMetricIssue metric]+ | coverage == RequireAllTitlesPresent+ && any (`M.notMember` balances) (credits ++ debits) =+ [InvalidSubtotalDefinition metric]+ | coverage == TreatAbsentAsZero+ && any removedNonZeroTitle (credits ++ debits) =+ [InvalidSubtotalDefinition metric]+ | any (wrongSide Credit coverage) credits =+ [InvalidSubtotalDefinition metric]+ | any (wrongSide Debit coverage) debits =+ [InvalidSubtotalDefinition metric]+ | otherwise = []+ removedNonZeroTitle title =+ balanceFor title initial /= TB.NoBalance && M.notMember title balances+ wrongSide expected coverage title = case balanceFor title balances of+ TB.NoBalance -> coverage == RequireAllTitlesPresent+ balance -> balanceSide balance /= expected+ customMetricUnlabelled metric = case metric of+ CustomMetric metricId -> case customLabels metricId of+ [label] -> T.null (T.strip (_customMetricLabelJapanese label))+ || T.null (T.strip (_customMetricLabelEnglish label))+ _ -> True+ _ -> False+ customMetricIssue metric = case metric of+ CustomMetric metricId -> UnlabelledCustomMetric metricId+ _ -> InvalidSubtotalDefinition metric+ customLabels metricId = filter+ ((== metricId) . _customMetricIdentity)+ (_customMetricLabels context)++duplicateMetrics :: [DerivedMetric] -> [DerivedMetric]+duplicateMetrics metrics = S.toList+ (S.fromList [metric | metric <- metrics, count metric metrics > 1])+ where+ count needle = length . filter (== needle)++duplicateMetricIds :: [MetricId] -> [MetricId]+duplicateMetricIds metricIds = S.toList+ (S.fromList [metricId | metricId <- metricIds, count metricId metricIds > 1])+ where+ count needle = length . filter (== needle)++renderLines+ :: (Eq v, Num v)+ => ReportingContext v+ -> Map AccountTitles (TB.AccountBalance v)+ -> [StatementLine v]+renderLines context balances = concatMap renderOne (M.toList balances)+ where+ renderOne (_, TB.NoBalance) = []+ renderOne (title, balance) = case allocationFor title of+ Just allocation -> allocatedLines context title balance allocation+ Nothing -> case reportingSection title balance of+ Nothing -> []+ Just section -> [lineFor context title section balance]+ allocationFor title = case filter ((== title) . _allocationAccount)+ (_presentationAllocations context) of+ allocation : _ -> Just allocation+ [] -> Nothing++allocatedLines+ :: (Eq v, Num v)+ => ReportingContext v -> AccountTitles -> TB.AccountBalance v+ -> PresentationAllocation v -> [StatementLine v]+allocatedLines context title balance allocation =+ [ StatementLine title (presentationLabel (_presentationProfile context) title)+ section side amount (isContraTitle title)+ | (section, amount) <-+ [ (currentSection title balance, _allocationCurrent allocation)+ , (noncurrentSection title balance, _allocationNoncurrent allocation)+ ]+ , amount /= 0+ ]+ where+ side = balanceSide balance++lineFor+ :: Num v => ReportingContext v -> AccountTitles -> StatementSection+ -> TB.AccountBalance v -> StatementLine v+lineFor context title section balance = StatementLine+ title (presentationLabel (_presentationProfile context) title)+ section (balanceSide balance) (balanceAmount balance) (isContraTitle title)++isContraTitle :: AccountTitles -> Bool+isContraTitle title = case Registry.accountSemantics title of+ Just semantics -> ContraAccount `elem` asemRoles semantics+ Nothing -> False++reportingSection+ :: AccountTitles -> TB.AccountBalance v -> Maybe StatementSection+reportingSection title balance = do+ semantics <- Registry.accountSemantics title+ case asemReportingEligibility semantics of+ DerivedPresentation -> Nothing+ NotPresented -> Nothing+ ContextualPresentation -> Just (contextualSection balance)+ StatementEligible -> case asemDivisionSemantics semantics of+ StatementDivision division -> Just (divisionSection title division)+ _ -> Nothing++divisionSection :: AccountTitles -> AccountDivision -> StatementSection+divisionSection title division = case division of+ Assets -> case fixedCurrent title of+ Fixed -> NoncurrentAssetsSection+ _ -> CurrentAssetsSection+ Liability -> case fixedCurrent title of+ Fixed -> NoncurrentLiabilitiesSection+ _ -> CurrentLiabilitiesSection+ Equity -> EquitySection+ Revenue -> RevenueSection+ Cost -> ExpenseSection++fixedCurrent :: AccountTitles -> FixedCurrent+fixedCurrent title = case Registry.accountSpec title of+ Just spec -> Registry.asFixedCurrent spec+ Nothing -> Other++contextualSection :: TB.AccountBalance v -> StatementSection+contextualSection (TB.DebitBalance _) = ContextualDebitSection+contextualSection (TB.CreditBalance _) = ContextualCreditSection+contextualSection TB.NoBalance = ContextualDebitSection++currentSection :: AccountTitles -> TB.AccountBalance v -> StatementSection+currentSection title balance = case Registry.accountSemantics title of+ Just semantics -> case asemDivisionSemantics semantics of+ StatementDivision Liability -> CurrentLiabilitiesSection+ StatementDivision Assets -> CurrentAssetsSection+ _ -> contextualSection balance+ Nothing -> contextualSection balance++noncurrentSection :: AccountTitles -> TB.AccountBalance v -> StatementSection+noncurrentSection title balance = case Registry.accountSemantics title of+ Just semantics -> case asemDivisionSemantics semantics of+ StatementDivision Liability -> NoncurrentLiabilitiesSection+ StatementDivision Assets -> NoncurrentAssetsSection+ _ -> contextualSection balance+ Nothing -> contextualSection balance++-- | Read one exact signed balance as a structural side and rounded magnitude.+accumulatedBalance :: HatVal v => Accum v -> TB.AccountBalance v+accumulatedBalance total = case Accum.readAccountAccum Credit total of+ (Side, _) -> TB.NoBalance+ (Debit, value) -> TB.DebitBalance value+ (Credit, value) -> TB.CreditBalance value++-- | An absent title contributes the empty exact sum.+accumulatedFor :: HatVal v => AccountTitles -> Map AccountTitles (Accum v) -> Accum v+accumulatedFor = M.findWithDefault emptyAccum++subtotalBalance+ :: HatVal v+ => Map AccountTitles (Accum v)+ -> [AccountTitles] -> [AccountTitles] -> TB.AccountBalance v+subtotalBalance balances creditTitles debitTitles = accumulatedBalance+ (foldr (mergeAccum . (`accumulatedFor` balances)) emptyAccum+ (debitTitles ++ creditTitles))
+ src/ExchangeAlgebra/Accounting/Transaction.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Exact transaction coordinates and posting entries in the Accounting layer.+-- These types use algebra values and Journal notes; admission and equivalence+-- share them without a dependency on input validation. Read the identifiers+-- before 'Entry' and 'isBlankKey'.+--+-- The following names identify transactions and their supporting records.+--+-- +-----------------------------+---------------------------------------------++-- | Task | Type or function |+-- +=============================+=============================================++-- | Identify a transaction | t'EntityId', t'PeriodId', t'TxId', t'TxKey' |+-- +-----------------------------+---------------------------------------------++-- | Keep original postings | 'Entry' |+-- +-----------------------------+---------------------------------------------++-- | Check blank key coordinates | 'isBlankKey' |+-- +-----------------------------+---------------------------------------------++--+-- > import qualified ExchangeAlgebra.Accounting.Transaction as Transaction+--+-- t'TxKey' supplies the note coordinates used by Definitions 10-12.+-- Constructing an identifier or an 'Entry' does not establish admission.+module ExchangeAlgebra.Accounting.Transaction+ ( EntityId(..)+ , PeriodId(..)+ , TxId(..)+ , TxKey(..)+ , Entry+ , isBlankKey+ ) where++import Data.Hashable (Hashable(..))+import Data.Text (Text)+import qualified Data.Text as Text++import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Algebra.Core (Alg)+import ExchangeAlgebra.Algebra.Base.Representation (HatBase)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Journal.Core (Note(..))++-- | Exact entity identity; admission rejects blank identifiers.+newtype EntityId = EntityId Text deriving (Eq, Ord, Show)++-- | Exact reporting-period identity; no implicit current period is assumed.+-- 'Ord' is the text order, not a calendar order: "2026-9" sorts after+-- "2026-10". Use it only for map keys; operations that need the order of+-- periods take the earlier and later period as explicit arguments.+newtype PeriodId = PeriodId Text deriving (Eq, Ord, Show)++-- | Transaction identity within one entity and period.+newtype TxId = TxId Text deriving (Eq, Ord, Show)++-- | Hash each identifier by its exact text, as t'TxKey' hashes its coordinates.+instance Hashable EntityId where+ hashWithSalt salt (EntityId text) = hashWithSalt salt text++instance Hashable PeriodId where+ hashWithSalt salt (PeriodId text) = hashWithSalt salt text++instance Hashable TxId where+ hashWithSalt salt (TxId text) = hashWithSalt salt text++-- | Exact transaction coordinate. Equality never performs wildcard matching.+data TxKey = TxKey EntityId PeriodId TxId -- ^ Entity, period, and transaction identity.+ deriving (Eq, Ord, Show)++-- | Hash the three exact text coordinates in their declared order.+instance Hashable TxKey where+ hashWithSalt salt (TxKey (EntityId entity) (PeriodId period) (TxId transaction)) =+ hashWithSalt salt (entity, period, transaction)++-- | Journal notes preserve transaction boundaries. The blank sentinel is+-- rejected by admission and is used only by the existing Journal interface.+instance Note TxKey where+ plank = TxKey (EntityId "") (PeriodId "") (TxId "")++-- | Concrete entry representation, retaining every original posting.+type Entry = Alg MoneyDecimal (HatBase AccountTitles)++-- | Reject empty or whitespace-only entity, period, or transaction identities.+isBlankKey :: TxKey -> Bool+isBlankKey (TxKey (EntityId entity) (PeriodId period) (TxId transaction)) =+ any (Text.null . Text.strip) [entity, period, transaction]+
+ src/ExchangeAlgebra/Accounting/Transfer.hs view
@@ -0,0 +1,532 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Module : ExchangeAlgebra.Accounting.Transfer+Description : Accounting transfers from exact balances (Definition 9).++An accounting transfer reads the exact balance of each selected base and+returns the additional entries that move it: a cancellation of the source+and a posting to the destination, of equal value. Bases here are complete+bases without Hat\/Not ('BasePart'); a balance is Not minus Hat of one base.+The destination keeps the balance on the same debit or credit side: when the+source and destination accounts have the same home side+(@whichSide (Not :< b)@), the destination keeps the balance's Hat\/Not;+otherwise it reverses it. Rules do not state Hat\/Not.++Write a table in the order the rows apply:++> import ExchangeAlgebra.Accounting.Transfer+>+> table = mkTransferTable+> [ (RentExpense, (.#)) .-> (PrepaidExpenses, (.#)) |% Portion 8 12 ]++Each row reads the balances left by the rows before it, so @[A .-> B |%+Relabel, B .-> C |% Relabel]@ moves A to C in one application. A table in+which a row can move a balance into the source of the same or an earlier row+is rejected ('BackwardFlow'); this includes @A .-> A@ and cycles.++'transferEntries' returns only the additional entries @d@; add them with+@x .+ d@. It does not call @bar@. The input ledger keeps every posting.++* 'Relabel' moves the whole exact balance. When the balance is not a value+ of the value type, it is split into several finite positive values+ ('splitAccum'), each one pair, so the source closes exactly also for+ 'Double'.+* @'Portion' n d@ (@0 < n <= d@) moves the exact balance times @n/d@, rounded+ once with the given 'Rounding' (@roundScaled@). Applying it twice moves a+ share of the remainder; it is not idempotent.++Reading the result of a journal transfer: base projections of a+"ExchangeAlgebra.Journal.Core" journal cancel only within a note. When the+transfer entries sit under a different note from the original postings,+combine the notes with @toAlg@ (or @gather@) before reading a balance. Pass+the transfer notes of the same range to closing and carrying, and apply a+transfer to the ledger after any per-period archiving.++Conversion between units (quantity to money) is not a transfer: use+'ExchangeAlgebra.Algebra.Core.mapPosting' (or @map@ of a journal) and keep the+ledger in quantities.++For generic per-posting coordinate rules, import+"ExchangeAlgebra.Algebra.Transfer" qualified. The umbrella modules export+the accounting transfer names defined here.++== Laws++Bases are flat @HatBase@ tuples with two or more coordinates and every+registry account; value types are 'Double' and @MoneyDecimal@. The exact+balance of a base is Not minus Hat read with 'exactAccum' as a 'Rational'.+@d = transferEntries t x@ with @Right d@ unless stated.++=== T1: balance++* Subject: 'transferEntries'.+* Preconditions: valid input; the call returns @Right d@.+* Relation: the exact debit and credit sums of @d@ are equal; the+ cancellation and destination of each pair have the same value.+* Observation: 'exactAccum' of @decL d@ and @decR d@, and of each pair.+* Tolerance: none. Instances: as above.++=== T2: shape++* Subject: 'transferEntries'.+* Preconditions: as T1.+* Relation: every posting of @d@ is finite and positive. A 'Relabel' row+ makes @k >= 1@ pairs per base with a nonzero balance (@k = 1@ when the+ balance is a value of the type); a 'Portion' row makes at most one pair per+ base, and none when the rounded amount is zero.+* Observation: postings of @d@ before any @bar@. Tolerance: none.++=== T3: closure++* Subject: a 'Relabel' row.+* Preconditions: as T1.+* Relation: after the row applies, the exact balance of every base that+ matches its source is zero.+* Observation: exact balances of @x .+ d@. Tolerance: none, also for 'Double'.++=== T4: idempotence++* Subject: tables whose rows are all 'Relabel'.+* Preconditions: as T1.+* Relation: @transferEntries t (x .+ d) == Right Zero@.+* Observation: 'Eq' of the result. Tolerance: none.++=== T5: agreement with rewriting++* Subject: 'Relabel' tables.+* Preconditions: as T1.+* Relation: the exact balances of @x .+ d@ equal those of @x@ rewritten by+ the table row by row, each posting keeping its debit or credit side.+* Observation: exact balance of every base. Posting sequences and counts are+ not compared. Tolerance: none.++=== T6: Hat\/Not of the destination++* Subject: each generated pair.+* Relation: the destination's Hat\/Not is the opposite of the cancellation's+ when the source and destination have the same home side, and equal to it+ otherwise.+* Observation: Hat\/Not of the pair. Instances: every pair of registry accounts.++=== T7: closing++* Subject: 'transferEntries' and 'ExchangeAlgebra.Accounting.Closing.closingEntries'.+* Preconditions: @t@ has one 'Relabel' row @(a, #, ...) .-> (RetainedEarnings,+ #, ...)@ for each account @a@ with a @closingSide@.+* Relation: @closingEntries x@ and @transferEntries t x@ have the same+ multiset of postings; for every such account the @closingSide@ decision+ agrees with the comparison of home sides.+* Observation: sorted postings. Tolerance: none.++=== T8: portion++* Subject: a @'Portion' n d@ row.+* Relation: the moved amount is @roundScaled rule (n % d) |s|@ for the exact+ balance @s@; @|s| - q@ remains on the source exactly.+* Observation: exact balances. Tolerance: none. With @MoneyDecimal@ a+ balance of 1 and @Portion 1 3@ fails with 'UnroundableAmount' under+ 'Nearest' and moves 0.33 under @ToMultipleOf 0.01@; a balance of 3 moves 1.++=== T9: order++* Subject: 'mkTransferTable'.+* Relation: @[B .-> C, A .-> B]@ is 'BackwardFlow'; @[A .-> B, B .-> C]@ is+ accepted and moves the balance of A to C. 'tableRows' returns the rows in+ the order written.++=== T10: failures++* Subject: 'transferEntries'.+* Relation: a NaN, negative or nonzero @HatNot@ posting on a selected base+ returns the corresponding 'Left'; permuting the postings does not change the+ result (the failure order below is total).++=== J1-J3: journals++* J1: the entries of 'transferEntriesBy' under target note @g@ equal+ @transferEntries t (toAlg (filterWithNote (\\n _ -> f n == g) j))@; other+ notes are empty.+* J2: if an input note @m@ is a target and @f m /= m@, the result is+ @Left (TransferNoteMoves m (f m))@; a target absent from the input (the+ next period) is accepted.+* J3: for all-'Relabel' tables and @f@ that maps every target to itself,+ @transferEntriesBy f t (j .+ r)@ is empty, where @Right r@ is the first result.+-}+module ExchangeAlgebra.Accounting.Transfer+ ( -- * Tables+ TransferShare(..)+ , TransferEnds+ , TransferRow+ , TransferTable+ , (.->)+ , (|%)+ , mkTransferTable+ , tableRows+ , TransferTableError(..)+ -- * Additional entries+ , TransferError(..)+ , transferEntries+ , transferEntriesWith+ -- * Journals+ , TransferJournalError(..)+ , transferEntriesBy+ , transferEntriesByWith+ ) where++import Data.List (find, inits)+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.HashMap.Strict as HashMap+import Data.Maybe (isNothing)+import Data.Ratio ((%))+import ExchangeAlgebra.Algebra.Core (Alg(..), Redundant((.+)), (.@), foldEntries)+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass(..), Hat(..))+import ExchangeAlgebra.Algebra.Element (ignoreWildcard)+import ExchangeAlgebra.Algebra.Transfer.Representation (matches, patternsOverlap)+import ExchangeAlgebra.Algebra.Value.Class+ ( HatVal(..)+ , ValueError(..)+ , Rounding(..)+ , roundScaled+ )+import ExchangeAlgebra.Accounting.Account (accountSpec)+import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(..))+import ExchangeAlgebra.Journal.Core (Journal, Note)+import qualified ExchangeAlgebra.Journal.Core as Journal++-- * Tables++-- | How much of a balance a row moves.+data TransferShare+ = Relabel+ -- ^ The whole exact balance.+ | Portion !Integer !Integer+ -- ^ @Portion n d@ moves @n/d@ of the exact balance, rounded once;+ -- @0 < n <= d@, checked by 'mkTransferTable'.+ deriving (Eq, Show)++-- | A source and a destination, written @from .-> to@. Both are complete bases+-- without Hat\/Not; a wildcard in the source selects any coordinate and a+-- wildcard in the destination keeps the source's coordinate.+data TransferEnds b = TransferEnds (BasePart b) (BasePart b)++deriving instance Eq (BasePart b) => Eq (TransferEnds b)++-- | One row of a table, written @from .-> to |% share@.+data TransferRow b = TransferRow (BasePart b) (BasePart b) TransferShare++deriving instance Eq (BasePart b) => Eq (TransferRow b)++instance Show (BasePart b) => Show (TransferRow b) where+ showsPrec d (TransferRow from to share) = showParen (d > 7)+ $ showsPrec 9 from . showString " .-> " . showsPrec 9 to+ . showString " |% " . showsPrec 8 share++-- | Validated rows in the order written. The constructor is private; build+-- tables with 'mkTransferTable'.+newtype TransferTable b = TransferTable [TransferRow b]++deriving instance Eq (BasePart b) => Eq (TransferTable b)+deriving instance Show (BasePart b) => Show (TransferTable b)++infixr 8 .->+infixr 7 |%++-- | Pair a source with a destination.+(.->) :: BasePart b -> BasePart b -> TransferEnds b+(.->) = TransferEnds++-- | Attach the share to move.+(|%) :: TransferEnds b -> TransferShare -> TransferRow b+(|%) (TransferEnds from to) = TransferRow from to++-- | A table that 'mkTransferTable' rejects. Checks run in the order of the+-- constructors; within each, rows are compared in the order written.+data TransferTableError b+ = InvalidPortion (TransferRow b)+ -- ^ The first row with @n <= 0@, @d <= 0@ or @n > d@.+ | OverlappingRows (TransferRow b) (TransferRow b)+ -- ^ The first pair of rows @(i, j)@, @i < j@, whose sources overlap+ -- (ordered by @j@, then @i@). Duplicates overlap.+ | BackwardFlow (TransferRow b) (TransferRow b)+ -- ^ The first pair @(i, j)@, @i <= j@, in the same order, where row @j@+ -- can move a balance into the source of row @i@: the destination+ -- pattern of row @j@ (its wildcards filled from its source pattern)+ -- overlaps the source of row @i@. @i == j@ is a row to itself.++deriving instance Eq (BasePart b) => Eq (TransferTableError b)+deriving instance Show (BasePart b) => Show (TransferTableError b)++-- | Validate a table and keep the rows in the order written.+-- Matching is one-way: a wildcard in the pattern matches any coordinate,+-- including a wildcard in the other base. Complexity: O(r^2) for r rows.+mkTransferTable :: forall b. HatBaseClass b+ => [TransferRow b]+ -> Either (TransferTableError b) (TransferTable b)+mkTransferTable rows = case find invalidPortion rows of+ Just row -> Left (InvalidPortion row)+ Nothing -> case [(first, second)+ | (before, second) <- zip (inits rows) rows+ , first@(TransferRow source _ _) <- before+ , let TransferRow other _ _ = second+ , patternsOverlap source other] of+ (first, second) : _ -> Left (OverlappingRows first second)+ [] -> case [(first, second)+ | (before, second@(TransferRow source target _)) <- zip (inits rows) rows+ , first@(TransferRow previous _ _) <- before ++ [second]+ , patternsOverlap previous (ignoreWildcard source target)] of+ (first, second) : _ -> Left (BackwardFlow first second)+ [] -> Right (TransferTable rows)+ where+ invalidPortion :: TransferRow b -> Bool+ invalidPortion (TransferRow _ _ Relabel) = False+ invalidPortion (TransferRow _ _ (Portion numerator denominator)) =+ numerator <= 0 || denominator <= 0 || numerator > denominator++-- | The rows in the order written. Complexity: O(1)+tableRows :: TransferTable b -> [TransferRow b]+tableRows (TransferTable rows) = rows++-- * Additional entries++-- | A ledger that 'transferEntries' cannot transfer. Only postings whose base+-- matches the source of some row are checked. The first failure is reported+-- in the order of the constructors; within a constructor, by ascending base+-- (Hat\/Not normalized to Not), and for 'InvalidPosting' on the same base a+-- non-finite value before a negative one. No partial result is returned.+data TransferError b+ = WildcardPosting b+ -- ^ A posting with a nonzero value and side @HatNot@.+ | InvalidPosting b ValueError+ -- ^ A non-finite or negative value.+ | UnclassifiedAccount b+ -- ^ A source or destination account without an account division, so+ -- its home side is unknown. Checked for every selected base (from the+ -- ledger or from an earlier row's destinations) and its destination,+ -- whatever the balance, so the result does not depend on the amounts.+ | UnroundableAmount (TransferRow b) b+ -- ^ The rounding rule cannot represent the moved amount, or the+ -- rounded amount is infinite. Reported for the first row in table+ -- order, then the first base.++deriving instance (Eq b, Eq (BasePart b)) => Eq (TransferError b)+deriving instance (Show b, Show (BasePart b)) => Show (TransferError b)++-- | 'transferEntriesWith' rounding 'Portion' rows to the nearest value.+transferEntries :: (HatVal v, ExBaseClass b)+ => TransferTable b -> Alg v b -> Either (TransferError b) (Alg v b)+transferEntries = transferEntriesWith Nearest++-- | Generate the additional entries of a table, rounding 'Portion' rows with+-- the given rule. The ledger is read once: per selected base, Hat and Not are+-- accumulated exactly, rows apply in table order, and each row's destinations+-- feed the balances of later rows. A base with a zero exact balance, and a+-- 'Portion' whose rounded amount is zero, add nothing. Wildcards in ledger+-- bases are values. No @bar@ is applied.+-- Complexity: O(s log b + r * b) for s postings, b selected bases, r rows.+transferEntriesWith :: forall v b. (HatVal v, ExBaseClass b)+ => Rounding v+ -> TransferTable b+ -> Alg v b+ -> Either (TransferError b) (Alg v b)+transferEntriesWith rounding (TransferTable rows) ledger =+ case Set.lookupMin wildcards of+ Just source -> Left (WildcardPosting source)+ Nothing -> case Map.lookupMin invalid of+ Just (source, failure) -> Left (InvalidPosting source failure)+ Nothing -> case process rows balances of+ (unclassified, failures, entries) -> case Set.lookupMin unclassified of+ Just source -> Left (UnclassifiedAccount source)+ Nothing -> case failures of+ failure : _ -> Left failure+ [] -> Right entries+ where+ -- Only selected input postings enter these exact, separate side totals.+ (_, wildcards, invalid, balances) =+ foldEntries collect (Map.empty, Set.empty, Map.empty, Map.empty) ledger++ collect :: (Map.Map b Bool, Set.Set b, Map.Map b ValueError, Map.Map b (Accum v, Accum v))+ -> v+ -> b+ -> (Map.Map b Bool, Set.Set b, Map.Map b ValueError, Map.Map b (Accum v, Accum v))+ collect (selection, wildcardBases, invalidBases, totals) value posting+ | not isSelected = (nextSelection, wildcardBases, invalidBases, totals)+ | otherwise = (nextSelection, nextWildcards, nextInvalid, nextTotals)+ where+ source = merge Not (base posting)+ isSelected :: Bool+ isSelected = Map.findWithDefault (any selects rows) source selection+ nextSelection :: Map.Map b Bool+ nextSelection = Map.insert source isSelected selection++ selects :: TransferRow b -> Bool+ selects (TransferRow patternBase _ _) = matches patternBase (base posting)++ nextWildcards :: Set.Set b+ nextWildcards+ | hat posting == HatNot && value /= zeroValue = Set.insert source wildcardBases+ | otherwise = wildcardBases++ nextInvalid :: Map.Map b ValueError+ nextInvalid = case valueError value of+ Nothing -> invalidBases+ Just failure -> Map.insertWith preferNonFinite source failure invalidBases++ nextTotals :: Map.Map b (Accum v, Accum v)+ nextTotals = case (hat posting, valueError value) of+ (Hat, Nothing) -> addTotal source (addAccum value emptyAccum, emptyAccum) totals+ (Not, Nothing) -> addTotal source (emptyAccum, addAccum value emptyAccum) totals+ (HatNot, Nothing) -> addTotal source (emptyAccum, emptyAccum) totals+ _ -> totals++ preferNonFinite :: ValueError -> ValueError -> ValueError+ preferNonFinite NonFiniteValue _ = NonFiniteValue+ preferNonFinite _ NonFiniteValue = NonFiniteValue+ preferNonFinite _ _ = NegativeValue++ addTotal :: b+ -> (Accum v, Accum v)+ -> Map.Map b (Accum v, Accum v)+ -> Map.Map b (Accum v, Accum v)+ addTotal = Map.insertWith mergeTotals++ mergeTotals :: (Accum v, Accum v) -> (Accum v, Accum v) -> (Accum v, Accum v)+ mergeTotals (firstHat, firstNot) (secondHat, secondNot) =+ (mergeAccum firstHat secondHat, mergeAccum firstNot secondNot)++ -- Continue after a rounding failure so an unclassified account anywhere+ -- in the table still has priority. No partial entries escape this function.+ process :: [TransferRow b]+ -> Map.Map b (Accum v, Accum v)+ -> (Set.Set b, [TransferError b], Alg v b)+ process [] _ = (Set.empty, [], Zero)+ process (row@(TransferRow patternBase targetPattern share) : remaining) totals =+ let selected :: [(b, (Accum v, Accum v))]+ selected = [(source, amounts)+ | (source, amounts) <- Map.toAscList totals+ , matches patternBase (base source)]+ (unclassified, failures, entries, nextTotals) =+ foldl' move (Set.empty, [], Zero, totals) selected+ (laterUnclassified, laterFailures, laterEntries) = process remaining nextTotals+ in ( Set.union unclassified laterUnclassified+ , failures ++ laterFailures+ , entries .+ laterEntries )+ where+ move :: (Set.Set b, [TransferError b], Alg v b, Map.Map b (Accum v, Accum v))+ -> (b, (Accum v, Accum v))+ -> (Set.Set b, [TransferError b], Alg v b, Map.Map b (Accum v, Accum v))+ move (unclassified, failures, entries, current) (source, (hatTotal, notTotal))+ | not (null unknown) =+ (Set.union unclassified (Set.fromList unknown), failures, entries, current)+ | otherwise = case signAccum net of+ Just EQ -> (unclassified, failures, entries, current)+ Just LT -> emit (toHat source) (negateAccum net)+ Just GT -> emit source net+ Nothing -> (unclassified, failures ++ [UnroundableAmount row source], entries,+ current)+ where+ target :: b+ target = merge Not (ignoreWildcard (base source) targetPattern)+ unknown :: [b]+ unknown = [postingBase | postingBase <- [source, target]+ , isNothing (accountSpec (getAccountTitle postingBase))]+ net :: Accum v+ net = mergeAccum notTotal (negateAccum hatTotal)++ emit :: b+ -> Accum v+ -> (Set.Set b, [TransferError b], Alg v b, Map.Map b (Accum v, Accum v))+ emit balanceBase amount = case movedValues of+ Nothing -> (unclassified, failures ++ [UnroundableAmount row source], entries,+ current)+ Just values ->+ let additions :: Alg v b+ additions = mconcat [ (value .@ revHat balanceBase)+ .+ (value .@ destination)+ | value <- values, value > zeroValue ]+ next :: Map.Map b (Accum v, Accum v)+ next = foldl' feed current values+ in (unclassified, failures, entries .+ additions, next)+ where+ destination :: b+ destination+ | whichSide source == whichSide target = merge (hat balanceBase) (base target)+ | otherwise = merge (hat (revHat balanceBase)) (base target)++ movedValues :: Maybe [v]+ movedValues = case share of+ Relabel -> splitAccum amount+ Portion numerator denominator ->+ fmap (: []) (roundScaled rounding (numerator % denominator) amount)++ feed :: Map.Map b (Accum v, Accum v) -> v -> Map.Map b (Accum v, Accum v)+ feed accumulated value+ | value == zeroValue || not (any selectsDestination remaining) = accumulated+ | hat destination == Hat =+ addTotal target (addAccum value emptyAccum, emptyAccum) accumulated+ | otherwise =+ addTotal target (emptyAccum, addAccum value emptyAccum) accumulated++ selectsDestination :: TransferRow b -> Bool+ selectsDestination (TransferRow nextPattern _ _) =+ matches nextPattern (base destination)++-- * Journals++-- | A journal that 'transferEntriesBy' cannot transfer.+data TransferJournalError n b+ = TransferFailed n (TransferError b)+ -- ^ The first failing target note in ascending order.+ | TransferNoteMoves n n+ -- ^ @(m, f m)@: an input note @m@ is a target but @f m /= m@. Checked+ -- for all notes, ascending, before any transfer is computed.++deriving instance (Eq n, Eq b, Eq (BasePart b)) => Eq (TransferJournalError n b)+deriving instance (Show n, Show b, Show (BasePart b)) => Show (TransferJournalError n b)++-- | 'transferEntriesByWith' rounding to the nearest value.+transferEntriesBy :: (Note n, HatVal v, ExBaseClass b)+ => (n -> n)+ -> TransferTable b+ -> Journal n v b+ -> Either (TransferJournalError n b) (Journal n v b)+transferEntriesBy = transferEntriesByWith Nearest++-- | Transfer per target note. @f@ maps each input note to the note that holds+-- its transfer entries. The notes with the same target are combined, the+-- table is applied once to their combined ledger (rounding once per target),+-- and the entries are placed under the target. Only the additional entries+-- are returned; add them with @j .+ r@. A target with no entries creates no+-- note. See the module header for reading balances across notes.+transferEntriesByWith :: forall n v b. (Note n, HatVal v, ExBaseClass b)+ => Rounding v+ -> (n -> n)+ -> TransferTable b+ -> Journal n v b+ -> Either (TransferJournalError n b) (Journal n v b)+transferEntriesByWith rounding targetOf table journal = case movingNotes of+ (note, target) : _ -> Left (TransferNoteMoves note target)+ [] -> fmap mconcat $ traverse transferGroup (Map.toAscList groups)+ where+ notes :: [(n, Alg v b)]+ notes = HashMap.toList (Journal.toMap journal)+ targets :: Set.Set n+ targets = Set.fromList [targetOf note | (note, _) <- notes]+ movingNotes :: [(n, n)]+ movingNotes = [(note, targetOf note)+ | note <- Set.toAscList (Set.fromList (map fst notes))+ , Set.member note targets+ , targetOf note /= note]+ groups :: Map.Map n (Journal n v b)+ groups = Map.fromListWith (.+)+ [(targetOf note, entries Journal..| note) | (note, entries) <- notes]++ transferGroup :: (n, Journal n v b) -> Either (TransferJournalError n b) (Journal n v b)+ transferGroup (target, group) = case transferEntriesWith rounding table (Journal.toAlg group) of+ Left failure -> Left (TransferFailed target failure)+ Right additions -> Right (additions Journal..| target)
+ src/ExchangeAlgebra/Accounting/TrialBalance.hs view
@@ -0,0 +1,40 @@+{- |+Module : ExchangeAlgebra.Accounting.TrialBalance+Description : Compute debit and credit account balances and audit findings.++Import this module for the complete trial-balance API. Import+"ExchangeAlgebra.Accounting.TrialBalance.Balance" for balance calculations or+"ExchangeAlgebra.Accounting.TrialBalance.Validation" for findings alone.+++--------------------------------------+-------------------------------------------++| Task | Entry point |++======================================+===========================================++| Aggregate account balances | 'accountBalances' |++--------------------------------------+-------------------------------------------++| Report accounting findings | 'trialBalanceFindings' |++--------------------------------------+-------------------------------------------+++'accountBalances' returns account balances directed by 'DebitBalance' or+'CreditBalance'. 'ExchangeAlgebra.Algebra.Core.netPairMapBy' instead returns+Not and Hat components. Use the account balance API when the direction means+Debit or Credit.+-}+module ExchangeAlgebra.Accounting.TrialBalance+ ( AccountBalance(..)+ , balancePair+ , addPair+ , netPair+ , combineBalances+ , balanceFor+ , balanceSide+ , balanceAmount+ , accountBalances+ , TrialBalanceStage(..)+ , ReclassificationRule(..)+ , TrialBalanceInput(..)+ , TBFinding(..)+ , trialBalanceFindings+ ) where++import ExchangeAlgebra.Accounting.TrialBalance.Balance+import ExchangeAlgebra.Accounting.TrialBalance.Validation
+ src/ExchangeAlgebra/Accounting/TrialBalance/Accum.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE FlexibleContexts #-}++-- | Internal exact account sums for trial-balance validation and reporting.+-- The Accounting layer shares the posting fold and directional readout here;+-- the public balance representation remains in TrialBalance.Balance.+module ExchangeAlgebra.Accounting.TrialBalance.Accum+ ( structureErrors+ , accountAccums+ , insertAccountAccum+ , readAccountAccum+ ) where++import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)++import ExchangeAlgebra.Algebra.Core.Representation (Alg, pattern (:@), foldEntries)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Accounting.Account.Classification (Side(..))+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass(getAccountTitle, whichSide) )++import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase(..))++-- | Inspect structure without determining posting sides. Lists retain the+-- validation fold's reverse traversal order, including a standalone zero term.+-- The result contains invalid values with titles, wildcard accounts, and+-- wildcard sides, respectively.+structureErrors+ :: HatVal v+ => Alg v (HatBase AccountTitles)+ -> ([(AccountTitles, v)], [AccountTitles], [AccountTitles])+structureErrors alg = case alg of+ value :@ base -> collect ([], [], []) value base+ _ -> foldEntries collect ([], [], []) alg+ where+ collect (invalids, accounts, sides) value (side :< title) =+ ( [(title, value) | isErrorValue value] ++ invalids+ , [title | title == AccountTitle] ++ accounts+ , [title | side == HatNot] ++ sides+ )++-- | Add one posting to its account's exact debit-minus-credit sum.+insertAccountAccum+ :: (HatVal v, ExBaseClass b)+ => Map AccountTitles (Accum v) -> v -> b -> Map AccountTitles (Accum v)+insertAccountAccum totals value base = M.insertWith mergeAccum (getAccountTitle base)+ (case whichSide base of+ Debit -> addAccum value emptyAccum+ Credit -> negateAccum (addAccum value emptyAccum)+ Side -> emptyAccum) totals++-- | Retain each account's exact signed sum until its consumer reads it.+accountAccums+ :: (HatVal v, ExBaseClass b)+ => Alg v b -> Map AccountTitles (Accum v)+accountAccums = foldEntries insertAccountAccum M.empty++-- | Determine direction before rounding a non-negative magnitude once.+-- A NaN uses the caller's reporting convention for its direction.+readAccountAccum :: HatVal v => Side -> Accum v -> (Side, v)+readAccountAccum nanSide total = case signAccum total of+ Just EQ -> (Side, zeroValue)+ Just GT -> (Debit, roundAccum total)+ Just LT -> (Credit, roundAccum (negateAccum total))+ Nothing -> (nanSide, roundAccum total)
+ src/ExchangeAlgebra/Accounting/TrialBalance/Balance.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE FlexibleContexts #-}++{- |+Module : ExchangeAlgebra.Accounting.TrialBalance.Balance+Description : Account-balance representation and netting for trial balances,+ presentation, and consolidation.++Account balances retain their debit or credit direction while keeping scalar+values non-negative. All pair-valued functions in this module use debit first+and credit second.+-}+module ExchangeAlgebra.Accounting.TrialBalance.Balance+ ( AccountBalance(..)+ , balancePair+ , addPair+ , netPair+ , combineBalances+ , balanceFor+ , balanceSide+ , balanceAmount+ , accountBalances+ ) where++import qualified ExchangeAlgebra.Accounting.TrialBalance.Accum as Accum++import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)++import ExchangeAlgebra.Algebra.Core.Representation (Alg)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Accounting.Account.Classification (Side(..))+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles)+import ExchangeAlgebra.Accounting.Exchange+ ( ExBaseClass )++-- | Net balance direction remains structural; values stay non-negative.+data AccountBalance v+ = NoBalance+ | DebitBalance v+ | CreditBalance v+ deriving (Show, Eq)++-- | Convert a balance to a pair ordered as debit, then credit.+balancePair :: Num v => AccountBalance v -> (v, v)+balancePair NoBalance = (0, 0)+balancePair (DebitBalance value) = (value, 0)+balancePair (CreditBalance value) = (0, value)++-- | Add two debit-credit pairs componentwise.+addPair :: Num v => (v, v) -> (v, v) -> (v, v)+addPair (leftDebit, leftCredit) (rightDebit, rightCredit) =+ (leftDebit + rightDebit, leftCredit + rightCredit)++-- | Net a debit-credit pair into one structural balance.+netPair :: (Ord v, Num v) => (v, v) -> AccountBalance v+netPair (debit, credit)+ | debit == credit = NoBalance+ | debit > credit = DebitBalance (debit - credit)+ | otherwise = CreditBalance (credit - debit)++-- | Combine two balances using the common netting rule.+combineBalances+ :: (Ord v, Num v)+ => AccountBalance v -> AccountBalance v -> AccountBalance v+combineBalances left right = netPair+ (addPair (balancePair left) (balancePair right))++-- | Look up an account balance, treating an absent account as balanced.+balanceFor :: AccountTitles -> Map AccountTitles (AccountBalance v)+ -> AccountBalance v+balanceFor title = M.findWithDefault NoBalance title++-- | Recover the structural side of a balance.+balanceSide :: AccountBalance v -> Side+balanceSide NoBalance = Side+balanceSide (DebitBalance _) = Debit+balanceSide (CreditBalance _) = Credit++-- | Recover the non-negative scalar amount of a balance.+balanceAmount :: Num v => AccountBalance v -> v+balanceAmount NoBalance = 0+balanceAmount (DebitBalance value) = value+balanceAmount (CreditBalance value) = value++-- | Net the exact posting sum of each account and round its magnitude once.+-- The direction is determined before rounding, without a tolerance.+accountBalances+ :: (HatVal v, ExBaseClass b)+ => Alg v b+ -> Map AccountTitles (AccountBalance v)+accountBalances = M.map finish . Accum.accountAccums+ where+ finish total = case Accum.readAccountAccum Credit total of+ (Side, _) -> NoBalance+ (Debit, value) -> DebitBalance value+ (Credit, value) -> CreditBalance value
+ src/ExchangeAlgebra/Accounting/TrialBalance/Validation.hs view
@@ -0,0 +1,343 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.Accounting.TrialBalance.Validation+Description : Accounting findings on trial balances, reported without blocking.++An exactly balanced trial balance can still be abnormal for accounting+purposes. 'trialBalanceFindings' reports reciprocal-account mismatches,+temporary-account residuals, closing-device residuals, abnormal-side balances,+and missing classification evidence independently of the double-entry balance+check. It is an audit function: it never blocks a computation, and the caller+(a person or a language model) decides how to treat each finding.++Computation and audit are separate. Financial statements are prepared by+'ExchangeAlgebra.Accounting.Statements.Presentation.present' directly from a+trial balance, without consulting these findings. The policy gate was removed+in 0.6.0.0; inspect findings with 'trialBalanceFindings'. No finding performs+a reclassification automatically. A caller must record and apply a transfer,+then inspect the resulting trial balance again.++Findings use exact equality. Prefer an exact value type such as+@MoneyDecimal@ for deterministic accounting checks.++Inspect a cash contribution. The findings do not close or reclassify the+supplied postings.++>>> import qualified Data.Map.Strict as Map+>>> import qualified Data.Set as Set+>>> import qualified ExchangeAlgebra.Algebra.Core as Algebra+>>> import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase(..))+>>> import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+>>> import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+>>> type TrialBalance = Algebra.Alg MoneyDecimal (HatBase AccountTitles)+>>> let cash = 100 Algebra..@ (Not :< Cash) :: TrialBalance+>>> let equity = 100 Algebra..@ (Not :< CapitalStock) :: TrialBalance+>>> let capital = cash Algebra..+ equity :: TrialBalance+>>> let input = TrialBalanceInput capital BeforeClosing Map.empty [] Set.empty+>>> trialBalanceFindings input+[]+-}+module ExchangeAlgebra.Accounting.TrialBalance.Validation+ ( TrialBalanceStage(..)+ , AccountBalance(..)+ , ReclassificationRule(..)+ , TrialBalanceInput(..)+ , TBFinding(..)+ , trialBalanceFindings+ ) where++import qualified ExchangeAlgebra.Accounting.TrialBalance.Accum as Accum++import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NE+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import qualified Data.Set as S+import Data.Set (Set)+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(whichSide))+import ExchangeAlgebra.Algebra.Core.Representation+ ( Alg, pattern (:@)+ , foldEntries+ )+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Accounting.Account.Classification+ ( AccountRole(..)+ , DivisionSemantics(..)+ , HomeSideSemantics(..)+ , Side(..)+ , AccountDivision(..)+ )+import ExchangeAlgebra.Accounting.Account.Registry+ ( AccountSemantics(asemDivisionSemantics,+ asemHomeSideSemantics, asemRoles)+ , accountSemantics, concreteAccountTitles )+import ExchangeAlgebra.Accounting.Account.Title (AccountTitles(..))+import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBase((:<)))+import ExchangeAlgebra.Accounting.TrialBalance.Balance+ ( AccountBalance(..)+ , balanceFor+ , balanceSide+ )++-- | Processing point of the supplied trial balance.+data TrialBalanceStage+ = BeforeClosing+ | AfterClosing+ deriving (Show, Eq)++-- | A rule identifies a condition and possible target titles. It supplies an+-- instruction, not an automatic mutation.+data ReclassificationRule+ = SideReclassificationRule+ AccountTitles+ Side+ (NonEmpty AccountTitles)+ | MaturityEvidenceRequired AccountTitles+ deriving (Show, Eq)++-- | Unvalidated trial-balance input and its external evidence.+data TrialBalanceInput v = TrialBalanceInput+ { _trialBalanceElement :: Alg v (HatBase AccountTitles)+ , _trialBalanceStage :: TrialBalanceStage+ , _temporaryBalanceExplanations :: Map AccountTitles Text+ , _reclassificationRules :: [ReclassificationRule]+ , _maturityEvidenceTitles :: Set AccountTitles+ }+ deriving (Show)++-- | Facts detected before financial-statement presentation.+data TBFinding v+ = InvalidTrialBalanceValue v+ | WildcardTrialBalanceAccount+ | WildcardTrialBalanceSide+ | UnbalancedTrialBalance v v+ | ReciprocalMismatch+ (AccountBalance v)+ (AccountBalance v)+ | StandaloneReciprocalBalance AccountTitles (AccountBalance v)+ | UnresolvedTemporaryBalance AccountTitles (AccountBalance v)+ | ExplainedTemporaryBalance AccountTitles (AccountBalance v) Text+ | BlankTemporaryExplanation AccountTitles (AccountBalance v)+ | ClosingDeviceResidual AccountTitles (AccountBalance v)+ | DerivedCoordinateResidual AccountTitles (AccountBalance v)+ | UnclosedNominalBalance AccountTitles (AccountBalance v)+ | UnexplainedAbnormalBalance+ AccountTitles Side (AccountBalance v)+ | AbnormalBalanceWithReclassificationRule+ AccountTitles (AccountBalance v) AccountTitles+ | AmbiguousReclassification+ AccountTitles (AccountBalance v) (NonEmpty AccountTitles)+ | MissingMaturityEvidence AccountTitles+ | InapplicableReclassificationRule ReclassificationRule+ deriving (Show, Eq)++-- | Detect every applicable finding. Structural errors suppress any operation+-- that would call the partial legacy @whichSide@ function on wildcard bases.+trialBalanceFindings :: HatVal v => TrialBalanceInput v -> [TBFinding v]+trialBalanceFindings input+ | not (null structuralFindings) = structuralFindings+ | otherwise = balanceFindings+ ++ reciprocalFindings totals balances+ ++ temporaryFindings input balances+ ++ closingFindings input balances+ ++ abnormalFindings input balances+ ++ maturityFindings input balances+ ++ ruleConfigurationFindings input+ where+ alg = _trialBalanceElement input+ (invalidValues, wildcardAccounts, wildcardSides) = Accum.structureErrors alg+ structuralFindings = map (InvalidTrialBalanceValue . snd) invalidValues+ ++ map (const WildcardTrialBalanceAccount) wildcardAccounts+ ++ map (const WildcardTrialBalanceSide) wildcardSides+ (debitState, creditState, totals) = case alg of+ value :@ base -> collect initial value base+ _ -> foldEntries collect initial alg+ initial = (emptyAccum, emptyAccum, M.empty)+ collect (!debits, !credits, !accounts) value base = case base of+ HatNot :< _ -> (debits, credits, accounts)+ _ :< AccountTitle -> (debits, credits, accounts)+ _ | value == zeroValue -> (debits, credits, accounts)+ | otherwise ->+ let !accounts' = Accum.insertAccountAccum accounts value base+ !debits' = case whichSide base of+ Debit -> addAccum value debits+ _ -> debits+ !credits' = case whichSide base of+ Credit -> addAccum value credits+ _ -> credits+ in (debits', credits', accounts')+ debit = roundAccum debitState+ credit = roundAccum creditState+ balanceFindings =+ [ UnbalancedTrialBalance debit credit+ | signAccum (mergeAccum debitState (negateAccum creditState)) /= Just EQ ]+ balances = M.map finish totals+ finish total = case Accum.readAccountAccum Credit total of+ (Side, _) -> NoBalance+ (Debit, value) -> DebitBalance value+ (Credit, value) -> CreditBalance value++reciprocalFindings+ :: HatVal v+ => Map AccountTitles (Accum v)+ -> Map AccountTitles (AccountBalance v)+ -> [TBFinding v]+reciprocalFindings totals balances =+ case (branch, headOffice) of+ (NoBalance, NoBalance) -> []+ (NoBalance, balance) ->+ [StandaloneReciprocalBalance HeadOfficeCurrentAccount balance]+ (balance, NoBalance) ->+ [StandaloneReciprocalBalance BranchCurrentAccount balance]+ _ | signAccum (mergeAccum+ (M.findWithDefault emptyAccum BranchCurrentAccount totals)+ (M.findWithDefault emptyAccum HeadOfficeCurrentAccount totals))+ == Just EQ -> []+ | otherwise -> [ReciprocalMismatch branch headOffice]+ where+ branch = balanceFor BranchCurrentAccount balances+ headOffice = balanceFor HeadOfficeCurrentAccount balances++temporaryFindings+ :: TrialBalanceInput v+ -> Map AccountTitles (AccountBalance v)+ -> [TBFinding v]+temporaryFindings input balances = concatMap finding temporaryTitles+ where+ temporaryTitles = case _trialBalanceStage input of+ BeforeClosing -> titlesWithRole SuspenseOrClearingAccount+ AfterClosing -> filter (/= CashOverShort)+ (titlesWithRole SuspenseOrClearingAccount)+ finding title = case balanceFor title balances of+ NoBalance -> []+ balance -> case M.lookup title (_temporaryBalanceExplanations input) of+ Nothing -> [UnresolvedTemporaryBalance title balance]+ Just explanation+ | T.null (T.strip explanation) ->+ [BlankTemporaryExplanation title balance]+ | otherwise ->+ [ExplainedTemporaryBalance title balance explanation]++closingFindings+ :: TrialBalanceInput v+ -> Map AccountTitles (AccountBalance v)+ -> [TBFinding v]+closingFindings input balances = case _trialBalanceStage input of+ BeforeClosing -> []+ AfterClosing ->+ concatMap residual (CashOverShort : titlesWithRole ClosingDevice)+ ++ concatMap derivedResidual+ (titlesWithRole PeriodResult ++ titlesWithRole ReportingSubtotal)+ ++ [ UnclosedNominalBalance title balance+ | (title, balance) <- M.toList balances+ , hasBalance balance+ , isNominal title+ ]+ where+ residual title = case balanceFor title balances of+ NoBalance -> []+ balance -> [ClosingDeviceResidual title balance]+ derivedResidual title = case balanceFor title balances of+ NoBalance -> []+ balance -> [DerivedCoordinateResidual title balance]+ isNominal title = case accountSemantics title of+ Just semantics -> case asemDivisionSemantics semantics of+ StatementDivision Cost -> True+ StatementDivision Revenue -> True+ _ -> False+ Nothing -> False+ hasBalance NoBalance = False+ hasBalance _ = True++abnormalFindings+ :: TrialBalanceInput v+ -> Map AccountTitles (AccountBalance v)+ -> [TBFinding v]+abnormalFindings input balances = concatMap finding (M.toList balances)+ where+ finding (_, NoBalance) = []+ finding (title, balance) = case expectedSide title of+ Nothing -> []+ Just expected+ | balanceSide balance == expected -> []+ | otherwise -> classifyRule title expected balance+ classifyRule title expected balance = case ruleTargets title (balanceSide balance)+ (_reclassificationRules input) of+ [] -> [UnexplainedAbnormalBalance+ title expected balance]+ [target] -> [AbnormalBalanceWithReclassificationRule+ title balance target]+ target : targets -> [AmbiguousReclassification+ title balance (target :| targets)]++maturityFindings+ :: Eq v+ => TrialBalanceInput v+ -> Map AccountTitles (AccountBalance v)+ -> [TBFinding v]+maturityFindings input balances =+ [ MissingMaturityEvidence title+ | MaturityEvidenceRequired title <- _reclassificationRules input+ , balanceFor title balances /= NoBalance+ , S.notMember title (_maturityEvidenceTitles input)+ ]++expectedSide :: AccountTitles -> Maybe Side+expectedSide title = do+ semantics <- accountSemantics title+ if OrdinaryAccount `elem` asemRoles semantics+ then case asemHomeSideSemantics semantics of+ FixedHomeSide side -> Just side+ ContextDependentHomeSide -> Nothing+ NoFixedHomeSide -> Nothing+ NoPostingSide -> Nothing+ else Nothing++ruleTargets+ :: AccountTitles+ -> Side+ -> [ReclassificationRule]+ -> [AccountTitles]+ruleTargets title side = deduplicate S.empty . concatMap targets+ where+ deduplicate _ [] = []+ deduplicate seen (candidate : candidates)+ | candidate `S.member` seen = deduplicate seen candidates+ | otherwise = candidate+ : deduplicate (S.insert candidate seen) candidates+ targets (SideReclassificationRule source trigger candidates)+ | source == title && trigger == side = NE.toList candidates+ | otherwise = []+ targets (MaturityEvidenceRequired _) = []++ruleConfigurationFindings :: TrialBalanceInput v -> [TBFinding v]+ruleConfigurationFindings input =+ [ InapplicableReclassificationRule rule+ | rule <- _reclassificationRules input+ , not (applicable rule)+ ]+ where+ applicable (MaturityEvidenceRequired title) = title /= AccountTitle+ applicable (SideReclassificationRule source trigger candidates) =+ source /= AccountTitle+ && trigger /= Side+ && expectedSide source /= Nothing+ && expectedSide source /= Just trigger+ && all (targetAccepts trigger) (NE.toList candidates)+ targetAccepts trigger target =+ target /= AccountTitle && expectedSide target == Just trigger++titlesWithRole :: AccountRole -> [AccountTitles]+titlesWithRole role =+ [ title+ | title <- concreteAccountTitles+ , Just semantics <- [accountSemantics title]+ , role `elem` asemRoles semantics+ ]
src/ExchangeAlgebra/Algebra.hs view
@@ -16,7 +16,8 @@ <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf> - 'Alg' is abstract here: 'Zero' and '(:@)' can be constructed and matched,+ 'Alg' is abstract here: 'Zero' can be constructed and matched, while+ '(:@)' only matches. Construct postings with '(.@)'. the multi-posting 'ExchangeAlgebra.Algebra.Internal.Liner' representation and its cache fields are reachable only through "ExchangeAlgebra.Algebra.Internal".@@ -24,13 +25,33 @@ -} module ExchangeAlgebra.Algebra- ( module ExchangeAlgebra.Algebra.Base+ ( customError+ , BaseClass(..)+ , HatBaseClass(..)+ , Hat(..)+ , BaseForSingleHat(..)+ , HatBase(..)+ , Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic , Nearly(..) , isNearlyNum- , nearlyEqScaled , Redundant(..)- , Exchange(..) , HatVal(..)+ , ValueError(..)+ , isErrorValue+ , Rounding(..)+ , Tolerance(..)+ , defaultTolerance+ , sumValues+ , roundScaled , Alg(Zero, (:@), _val, _hatBase) , isZero , (.@)@@ -51,29 +72,17 @@ , extendBy , filter , proj- , projCredit- , projDebit- , projByAccountTitle , projNetNorm- , projNorm , balanceBy , balanceMapBy , netPairMapBy , foldEntriesToMap , decBy , postFromNetBy- , projCurrentAssets- , projFixedAssets- , projDeferredAssets- , projCurrentLiability- , projFixedLiability- , projCapitalStock- , projContraAssets- , projContra- , rounding , unionsMerge ) where import Prelude hiding (map, filter)-import ExchangeAlgebra.Algebra.Internal+import ExchangeAlgebra.Algebra.Core.Representation+import ExchangeAlgebra.Algebra.Value.Class import ExchangeAlgebra.Algebra.Base
src/ExchangeAlgebra/Algebra/Base.hs view
@@ -27,526 +27,41 @@ {-# LANGUAGE DeriveGeneric #-} module ExchangeAlgebra.Algebra.Base- ( module ExchangeAlgebra.Algebra.Base- , module ExchangeAlgebra.Algebra.Base.Account.Registry- , module ExchangeAlgebra.Algebra.Base.Account.Types- , module ExchangeAlgebra.Algebra.Base.Element) where--import ExchangeAlgebra.Algebra.Base.Element-import ExchangeAlgebra.Algebra.Base.Account.Registry-import ExchangeAlgebra.Algebra.Base.Account.Types--import Data.Time (Day, TimeOfDay)-import GHC.Stack (HasCallStack, callStack, prettyCallStack)-import qualified Data.Binary as Binary-import Control.DeepSeq (NFData(..))--customError :: HasCallStack => String -> a-customError msg = error (msg ++ "\nCallStack:\n" ++ prettyCallStack callStack)----------------------------------------------------------------------- * Base conditions----------------------------------------------------------------------- ** Base--------------------------------------------------------------------{- | Base class definition.- Any type that is an instance of this class qualifies as a base.--}--class (Element a) => BaseClass a where- compareBase :: a -> a -> Ordering- compareBase = compareElement--instance (Element e1, Element e2)- => BaseClass (e1, e2) where--instance (Element e1, Element e2, Element e3)- => BaseClass (e1, e2, e3) where--instance (Element e1, Element e2, Element e3, Element e4)- => BaseClass (e1, e2, e3, e4) where--instance (Element e1, Element e2, Element e3, Element e4, Element e5)- => BaseClass (e1, e2, e3, e4, e5) where--instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6)- => BaseClass (e1, e2, e3, e4, e5, e6) where---- 7-tuple: 'Element'/'AxisDecompose' already provide 7-tuple instances; this--- closes the gap so every Element tuple arity is also usable as a base.-instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6, Element e7)- => BaseClass (e1, e2, e3, e4, e5, e6, e7) where------------------------------------------------------------------------ ** HatBase----------------------------------------------------------------------- | Type class for bases with a Hat component. Provides functionality to decompose and--- compose a base into its Hat part and BasePart. Manages the Hat (decrease) \/--- Not (increase) label at the base level in exchange algebra. Note that Hat\/Not--- is __not__ the debit\/credit distinction: the side of a posting is determined--- by the account division /together with/ this label (see 'whichSide' — an--- account sits on its home side when 'Not' and on the opposite side when v'Hat').-class (BaseClass a, BaseClass (BasePart a), AxisDecompose (BasePart a)) => HatBaseClass a where- -- | The type of the base part excluding the Hat.- type BasePart a- -- | Extract the base part excluding the Hat. Complexity: O(1)- base :: (BaseClass (BasePart a)) => a -> BasePart a- -- | Extract the Hat part. Complexity: O(1)- hat :: a -> Hat-- -- | Reconstruct a base from a Hat and a BasePart. Complexity: O(1)- merge :: Hat -> BasePart a -> a-- -- | Convert to the Hat side. Complexity: O(1)- toHat :: a -> a- -- | Convert to the Not side. Complexity: O(1)- toNot :: a -> a- -- | Reverse Hat/Not. Complexity: O(1)- revHat :: a -> a- -- | Test whether the base is Hat. Complexity: O(1)- isHat :: a -> Bool- -- | Test whether the base is Not. Complexity: O(1)- isNot :: a -> Bool-- -- | Compare bases with Hat. Defaults to 'compareBase'. Complexity: O(k)- compareHatBase :: a -> a -> Ordering- compareHatBase = compareBase----------------------------------------------------------------------- | Hat definition-data Hat = Hat- | Not- | HatNot- deriving (Enum, Eq, Ord, Show, Generic)--instance NFData Hat--instance Hashable Hat where-instance Binary.Binary Hat--instance Element Hat where- wildcard = HatNot-- {-# INLINE equal #-}- equal Hat Hat = True- equal Hat Not = False- equal Not Hat = False- equal Not Not = True- equal _ _ = True--instance BaseClass Hat where--data BaseForSingleHat = BaseForSingleHat- deriving (Eq,Ord,Generic)--instance NFData BaseForSingleHat--instance Show BaseForSingleHat where- show _ = ""--instance Hashable BaseForSingleHat where-instance Binary.Binary BaseForSingleHat--instance Element BaseForSingleHat where- wildcard = BaseForSingleHat- equal _ _ = True--instance BaseClass BaseForSingleHat where--instance HatBaseClass Hat where- type BasePart Hat = BaseForSingleHat- hat = id- base _ = BaseForSingleHat-- -- NB. 'merge'\/'revHat'\/'isHat' below match only @Hat@ and @Not@. The third- -- v'Hat' constructor @HatNot@ is the formalization-only wildcard state (the- -- paper convention is the 2-state Hat\/Not; see CLAUDE.md "HatNot wildcard").- -- These methods are never invoked on a @HatNot@ label by library code, so the- -- non-exhaustive @-Wincomplete-patterns@ here is by design (audited). Adding a- -- @HatNot@ case would change behavior (turn the pattern-match failure into a- -- different error), so it is intentionally left as-is rather than masked.- merge Hat _ = Hat- merge Not _ = Not-- {-# INLINE toHat #-}- toHat _ = Hat-- {-# INLINE toNot #-}- toNot _ = Not-- {-# INLINE revHat #-}- revHat Hat = Not- revHat Not = Hat-- {-# INLINE isHat #-}- isHat Hat = True- isHat Not = False-- {-# INLINE isNot #-}- isNot = not . isHat----------------------------------------------------------------------- | Base with Hat. Attaches a Hat (decrease) / Not (increase) label to a base--- element such as an account title. Use the constructor @(:<)@ as in @Hat :< Cash@.-data HatBase a where- (:<) :: (BaseClass a) => {_hat :: Hat, _base :: a } -> HatBase a--instance (BaseClass a, NFData a) => NFData (HatBase a) where- rnf (hatValue :< baseValue) = rnf hatValue `seq` rnf baseValue--instance (BaseClass a, Binary.Binary a) => Binary.Binary (HatBase a) where- put (h :< b) = Binary.put h >> Binary.put b- get = (:<) <$> Binary.get <*> Binary.get--instance Show (HatBase a) where- show (h :< b) = show h ++ ":<" ++ show b--instance Eq (HatBase a) where- {-# INLINE (==) #-}- (==) (h1 :< b1) (h2 :< b2) = h1 == h2 && b1 == b2- {-# INLINE (/=) #-}- (/=) x y = not (x == y)--instance Ord (HatBase a) where- {-# INLINE compare #-}- compare (h :< b) (h' :< b') =- case compare b b' of- EQ -> compare h h'- x -> x--instance (BaseClass a) => Hashable (HatBase a) where- hashWithSalt salt (h:<b) = salt `hashWithSalt` h- `hashWithSalt` b---- | Element (HatBase a)--- haveWildcard--- >>> haveWildcard (HatNot:<Amount :: HatBase CountUnit)--- True------ (.==)--- >>> Not:<(Cash, Yen) == Not:<(Cash,(.#))--- False------ >>> Not:<(Cash, Yen) .== Not:<(Cash,(.#))--- True------ compareElement--- >>> type Test = HatBase CountUnit--- >>> compareHatBase (Not:<Amount :: Test) (Not:<(.#) :: Test)--- EQ------ ignoreWildcard--- >>> ignoreWildcard (Not:<(Products,Yen)) (Hat:<(Products,Amount))--- Hat:<(Products,Amount)------ >>> ignoreWildcard (Not:<(Products,Yen)) (Hat:<(Products,(.#)))--- Hat:<(Products,Yen)------ >>> ignoreWildcard (Not:<(Cash,(.#))) (HatNot:<((.#),Amount))--- Not:<(Cash,Amount)---instance (BaseClass a) => Element (HatBase a) where- wildcard = HatNot :<wildcard-- haveWildcard (h:<b)- = isWildcard h- || haveWildcard b-- {-# INLINE equal #-}- equal (h1:<b1) (h2:<b2) = h1 .== h2 && b1 .== b2-- ignoreWildcard (h1:<b1) (h2:<b2)- = (ignoreWildcard h1 h2) :< (ignoreWildcard b1 b2)--- compareElement (h1:<b1) (h2:<b2)- = case compareElement b1 b2 of- EQ -> compareElement h1 h2- x -> x--instance (BaseClass a) => BaseClass (HatBase a) where--instance (BaseClass a, AxisDecompose a) => HatBaseClass (HatBase a) where- type BasePart (HatBase a) = a-- hat = _hat-- base = _base-- merge = (:<)-- {-# INLINE toHat #-}- toHat (_:<b) = Hat:<b-- {-# INLINE toNot #-}- toNot (_:<b) = Not:<b-- {-# INLINE revHat #-}- revHat (Hat :< b) = Not :< b- revHat (Not :< b) = Hat :< b-- {-# INLINE isHat #-}- isHat (Hat :< _) = True- isHat (Not :< _) = False- isHat (HatNot :< _) = customError "called HatNot"-- {-# INLINE isNot #-}- isNot = not . isHat----------------------------------------------------------------- * Define ExBase----------------------------------------------------------------- | Reverse the credit/debit side. Swaps Credit and Debit.--- The wildcard Side is returned unchanged.------ Complexity: O(1)-{-# INLINE switchSide #-}-switchSide :: Side -> Side-switchSide Credit = Debit-switchSide Debit = Credit-switchSide Side = Side---- | Default (home) side of an account division before any contra reversal:--- Assets\/Cost are debit-normal, Liability\/Equity\/Revenue are credit-normal.--- The actual home side of a base is this, reversed when 'isContra' holds--- (contract: @isContra b == (homeSide of b \/= defaultSide (whatDiv b))@).------ Complexity: O(1)-{-# INLINE defaultSide #-}-defaultSide :: AccountDivision -> Side-defaultSide Assets = Debit-defaultSide Cost = Debit-defaultSide Liability = Credit-defaultSide Equity = Credit-defaultSide Revenue = Credit---- | Classify an account title into an account division (Assets/Equity/Liability/Cost/Revenue).------ Complexity: O(1)-{-# INLINE classifyAccountDivision #-}-classifyAccountDivision :: HasCallStack => AccountTitles -> AccountDivision-classifyAccountDivision AccountTitle = customError "this is wildcard AccountTitle"-classifyAccountDivision title =- case accountSpec title of- Just spec -> asDivision spec- Nothing -> customError "this is wildcard AccountTitle"---- | BaseClass ⊃ HatBaseClass ⊃ ExBaseClass------ Extended type class for bases that carry an account title.--- Provides access to and modification of account titles, account divisions, PIMO classification,--- credit/debit determination, and fixed/current classification.-class (HatBaseClass a) => ExBaseClass a where- -- | Retrieve the account title from a base. Complexity: O(1)- getAccountTitle :: a -> AccountTitles-- -- | Change the account title of a base. Complexity: O(1)- setAccountTitle :: a -> AccountTitles -> a-- -- | Account title setter operator. An alias for @setAccountTitle@. Complexity: O(1)- {-# INLINE (.~) #-}- (.~) :: a -> AccountTitles -> a- (.~) = setAccountTitle-- -- | Retrieve the account division (Assets/Equity/Liability/Cost/Revenue). Complexity: O(1)- {-# INLINE whatDiv #-}- whatDiv :: a -> AccountDivision- whatDiv = classifyAccountDivision . getAccountTitle-- -- | Whether the account is a contra account (評価勘定等): its home side- -- and PIMO direction are the reverse of its division's defaults.- -- Delegates to the registry ('classifyAccountContra') exactly like- -- 'whatDiv' delegates to 'classifyAccountDivision' — a constant default- -- would disconnect the registry flag from every built-in instance.- -- Contract: @isContra b == (homeSide of b \/= defaultSide (whatDiv b))@.- -- Complexity: O(1)- {-# INLINE isContra #-}- isContra :: a -> Bool- isContra = classifyAccountContra . getAccountTitle-- -- | Retrieve the PIMO direction (PS/IN/MS/OUT; see 'PIMO' for the- -- original semantics). Derived from the division via 'pimoFromDivision',- -- flipped by 'pimoFlip' for contra accounts — e.g. a contra asset is MS- -- (minus stock), which is what makes the standard allowance entry- -- OUT ⇔ MS legal under Proposition 5.3.8. Complexity: O(1)- {-# INLINE whatPIMO #-}- whatPIMO :: a -> PIMO- whatPIMO x- | isContra x = pimoFlip (pimoFromDivision (whatDiv x))- | otherwise = pimoFromDivision (whatDiv x)-- -- | Determine whether a base belongs to the Credit or Debit side.- -- The home side is 'defaultSide' of the division, reversed for contra- -- accounts ('isContra'). Takes the Hat/Not reversal into account: an- -- account sits on its home side under 'Not' and on the opposite side- -- under v'Hat'. A 'HatNot' (wildcard) label is rejected with an error —- -- same policy as 'isHat': stored postings are always Hat\/Not, so a- -- wildcard here means a query-side value leaked into a posting-side- -- computation (this function previously treated 'HatNot' silently as- -- v'Hat'). Complexity: O(1)- {-# INLINE whichSide #-}- whichSide :: a -> Side- whichSide x =- let side0 = defaultSide (whatDiv x)- side = if isContra x then switchSide side0 else side0- in case hat x of- Not -> side- Hat -> switchSide side- HatNot -> customError "whichSide: called on a HatNot (wildcard) base"-- -- | Retrieve the fixed/current classification.- -- Returns Current, Fixed, or Other based on the account title.- --- -- Complexity: O(1)- {-# INLINE fixedCurrent #-}- fixedCurrent :: a -> FixedCurrent- fixedCurrent b = maybe Other asFixedCurrent (accountSpec (getAccountTitle b))----- | Type class for determining correspondences between account divisions.--- Tests whether two account divisions form a pair in double-entry bookkeeping--- (e.g., Assets <=> Liability).------ Complexity: O(1)-class AccountBase a where- -- | Test whether two account divisions are in a corresponding relationship.- (<=>) :: a -> a -> Bool---- | Derived from the PIMO relation via 'pimoFromDivision', matching--- Proposition 5.3.8 (Deguchi 2004). BREAKING (0.5.0.0): the previous--- hand-enumerated instance omitted the pairs required by PS ⇔ IN and--- OUT ⇔ IN — @Assets \<=\> Revenue@ (e.g. a cash sale) and--- @Cost \<=\> Revenue@ are now 'True'. This division-level relation cannot--- see contra reversal; exchange checks on bases must go through 'whatPIMO'.-instance AccountBase AccountDivision where- a <=> b = pimoFromDivision a <=> pimoFromDivision b---- | PIMO direction. In Proposition 5.3.8 (Deguchi 2004, pp.89-91) PS, IN,--- MS and OUT mean __plus stock, input, minus stock and output__ —--- directions of exchange, not statement labels. The allowed exchange pairs--- are exactly PS ⇔ IN, PS ⇔ MS, OUT ⇔ IN, OUT ⇔ MS (the 'AccountBase'--- instance below). The earlier Haddock glossed these as "Product Stock \/--- Income \/ Money Stock \/ Outflow"; that was naming drift from the--- original and is kept only as a mnemonic.-data PIMO = PS -- ^ plus stock (stock increase; non-contra Assets)- | IN -- ^ input (flow in; Revenue)- | MS -- ^ minus stock (stock decrease; Liability\/Equity and contra assets)- | OUT -- ^ output (flow out; Cost)- deriving (Ord, Show, Eq)--instance NFData PIMO where- rnf value = value `seq` ()---- | The division-to-PIMO map of the standard interpretation (the @g@ of--- Proposition 5.3.8 restricted to non-contra accounts): Assets are plus--- stock, Liability\/Equity are minus stock, Cost is output, Revenue is--- input. Contra accounts flip this via 'pimoFlip' (see 'whatPIMO').------ Complexity: O(1)-{-# INLINE pimoFromDivision #-}-pimoFromDivision :: AccountDivision -> PIMO-pimoFromDivision Assets = PS-pimoFromDivision Equity = MS-pimoFromDivision Liability = MS-pimoFromDivision Cost = OUT-pimoFromDivision Revenue = IN---- | Direction flip used for contra accounts: PS ↔ MS, IN ↔ OUT.--- Self-inverse, and it preserves the exchange relation:--- @x \<=\> y@ implies @pimoFlip x \<=\> pimoFlip y@.------ Complexity: O(1)-{-# INLINE pimoFlip #-}-pimoFlip :: PIMO -> PIMO-pimoFlip PS = MS-pimoFlip MS = PS-pimoFlip IN = OUT-pimoFlip OUT = IN--instance AccountBase PIMO where- PS <=> IN = True- IN <=> PS = True- PS <=> MS = True- MS <=> PS = True- IN <=> OUT = True- OUT <=> IN = True- MS <=> OUT = True- OUT <=> MS = True- _ <=> _ = False------------------------------------------------------------------------ * Simple bases (can be extended as needed)--- Tuples are used so that the same accessor functions can be shared.--- This approach was chosen over the DuplicateRecordFields extension--- because it has fewer restrictions and looks cleaner.----------------------------------------------------------------------- ** 1-element bases--- *** Account title only (exchange algebra base)-instance BaseClass AccountTitles where--instance ExBaseClass (HatBase AccountTitles) where- getAccountTitle (_ :< a) = a- setAccountTitle (h :< _) b = h :< b---- *** Name only (redundant algebra base)-instance BaseClass Name where---- *** CountUnit only (redundant algebra base)-instance BaseClass CountUnit where---- *** Day only (redundant algebra base)-instance BaseClass Day where---- *** TimeOfDay only (redundant algebra base)-instance BaseClass TimeOfDay where---- ***----- ** 2-element bases---- | Basic BaseClass with 2 elements--instance ExBaseClass (HatBase (AccountTitles, Day)) where- getAccountTitle (_:< (a, _)) = a- setAccountTitle (h:< (_, d)) b = h:< (b, d)--instance ExBaseClass (HatBase (AccountTitles, Name)) where- getAccountTitle (_:< (a, _)) = a- setAccountTitle (h:< (_, n)) b = h:< (b, n)--instance ExBaseClass (HatBase (CountUnit, AccountTitles)) where- getAccountTitle (_:< (_, a)) = a- setAccountTitle (h:< (u, _)) b = h:< (u, b)---- ** 3-element bases--- | Basic BaseClass with 3 elements-instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit)) where- getAccountTitle (_:< (a, _, _)) = a- setAccountTitle (h:< (_, n, c)) b = h:< (b, n, c)---- ** 4-element bases--- | Basic BaseClass with 4 elements-instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject)) where- getAccountTitle (_:< (a, _, _, _)) = a- setAccountTitle (h:< (_, n, c, s)) b = h:< (b, n, c, s)---- ** 5-element bases--- | Basic BaseClass with 5 elements-instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject, Day)) where- getAccountTitle (_:< (a, _, _, _, _)) = a- setAccountTitle (h:< (_, n, c, s, d)) b = h:< (b, n, c, s, d)-+ ( customError+ , BaseClass(..)+ , HatBaseClass(..)+ , Hat(..)+ , BaseForSingleHat(..)+ , HatBase(..)+ , Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic+ ) where --- ** 6-element bases--- | Basic BaseClass with 6 elements-instance ExBaseClass (HatBase (AccountTitles, Name, CountUnit, Subject, Day, TimeOfDay)) where- getAccountTitle (_:< (a, _, _, _, _, _)) = a- setAccountTitle (h:< (_, n, c, s, d, t)) b = h:< (b, n, c, s, d, t)+import ExchangeAlgebra.Algebra.Base.Representation+ ( customError+ , BaseClass(..)+ , HatBaseClass(..)+ , Hat(..)+ , BaseForSingleHat(..)+ , HatBase(..)+ )+import ExchangeAlgebra.Algebra.Element+ ( Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic+ )
src/ExchangeAlgebra/Algebra/Base/Account/JcciAliases.hs view
@@ -1,216 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wno-deprecations #-}---- Generated by tools/gen-jcci-aliases.py. Do not edit by hand.-module ExchangeAlgebra.Algebra.Base.Account.JcciAliases- ( jcciAliases ) where--import Data.Text (Text)-import ExchangeAlgebra.Algebra.Base.Element (AccountTitles(..))+-- | Compatibility export for JCCI account-name aliases.+module ExchangeAlgebra.Algebra.Base.Account.JcciAliases {-# DEPRECATED "Use ExchangeAlgebra.Accounting.Account instead." #-} (jcciAliases) where -jcciAliases :: AccountTitles -> [Text]-jcciAliases AccountsPayable = ["〇〇商店", "買掛金"]-jcciAliases AccountsReceivable = ["売掛金", "〇〇商店"]-jcciAliases AccruedConsumptionTax = ["未払金", "未払消費税"]-jcciAliases AccruedCorporateIncomeTaxes = ["未払法人税等"]-jcciAliases AccruedExpenses = ["未払費用", "未払利息"]-jcciAliases AccruedRevenue = ["未収収益", "未収家賃"]-jcciAliases AccumulatedDepreciation = ["建物減価償却累計額", "減価償却累計額", "備品減価償却累計額", "車両運搬具減価償却累計額", "車両減価償却累計額"]-jcciAliases AdditionalIncomeTaxesForPriorPeriods = ["追徴法人税等"]-jcciAliases AdvancesPaid = ["前払金", "前渡金"]-jcciAliases AdvancesReceived = ["前受金", "契約負債"]-jcciAliases AdvertisingExpense = ["広告宣伝費", "広告費", "広告料", "宣伝費"]-jcciAliases AffiliateStocks = ["関係会社株式", "関連会社株式"]-jcciAliases AllowanceForBonuses = ["賞与引当金", "役員賞与引当金"]-jcciAliases AllowanceForDoubtfulAccounts = ["貸倒引当金"]-jcciAliases AllowanceForProductWarranties = ["商品保証引当金", "製品保証引当金"]-jcciAliases AllowanceForRepairs = ["修繕引当金", "特別修繕引当金"]-jcciAliases AllowanceForRetirementBenefits = ["退職給付引当金"]-jcciAliases AmortizationOfGoodwill = ["のれん償却"]-jcciAliases AmortizationOfPatents = ["特許権償却"]-jcciAliases AmortizationOfSoftware = ["ソフトウェア償却"]-jcciAliases AvailableForSaleSecurities = ["投資有価証券", "その他有価証券"]-jcciAliases BadDebtLoss = ["貸倒損失"]-jcciAliases BankOverdraft = ["借入金", "当座借越"]-jcciAliases Bonuses = ["賞与", "役員賞与"]-jcciAliases BonusesPayable = ["未払賞与", "未払役員賞与", "未払賞与金"]-jcciAliases BranchCurrentAccount = ["支店"]-jcciAliases Building = ["建物"]-jcciAliases BusinessCommencementExpenses = ["開業費"]-jcciAliases BusinessTrip = ["旅費交通費", "旅費", "交通費"]-jcciAliases CapitalStock = ["資本金"]-jcciAliases CapitalSurplus = ["資本剰余金"]-jcciAliases Cash = ["現金"]-jcciAliases CashOverShort = ["現金過不足"]-jcciAliases CommunicationExpenses = ["通信費"]-jcciAliases Commutation = ["通信費"]-jcciAliases ConstructionFundReserve = ["新築積立金"]-jcciAliases ConstructionInProgress = ["建設仮勘定", "建設仮", "建設前渡金", "建設仮払金"]-jcciAliases ConsumptionTaxPaid = ["仮払金", "仮払消費税"]-jcciAliases ConsumptionTaxReceived = ["仮受金", "仮受消費税"]-jcciAliases ConsumptionTaxRefundReceivable = ["未収還付消費税", "未収消費税"]-jcciAliases ContraAccountForGuaranteeObligations = ["保証債務見返"]-jcciAliases ContractAssets = ["契約資産"]-jcciAliases CorporateIncomeTaxes = ["法人税、住民税及び事業税", "法人税等"]-jcciAliases CostOfServices = ["役務原価", "営業費用"]-jcciAliases CreditCardReceivable = ["クレジット売掛金"]-jcciAliases CurrentDeposits = ["当座預金", "当座預金○○銀行"]-jcciAliases DeferredTaxAssets = ["繰延税金資産"]-jcciAliases DeferredTaxLiabilities = ["繰延税金負債"]-jcciAliases DeliveryExpenses = ["発送費", "支払運賃", "発送運賃"]-jcciAliases Deposits = ["普通預金", "銀行預金", "普通預金○○銀行"]-jcciAliases DepositsInSpecialAccounts = ["銀行預金", "別段預金"]-jcciAliases DepositsReceived = ["預り金", "従業員預り金", "所得税預り金", "住民税預り金", "社会保険料預り金"]-jcciAliases DepositsReceivedFromOfficers = ["役員預り金"]-jcciAliases Depreciation = ["減価償却費", "建物減価償却費", "備品減価償却費"]-jcciAliases DevelopmentExpenses = ["開発費"]-jcciAliases DishonoredNotesReceivable = ["不渡手形"]-jcciAliases DividendEqualizationReserve = ["配当平均積立金"]-jcciAliases DividendsReceived = ["受取配当金"]-jcciAliases EarnedSurplus = ["利益剰余金"]-jcciAliases ElectronicallyRecordedObligations = ["電子記録債務"]-jcciAliases ElectronicallyRecordedReceivable = ["電子記録債権"]-jcciAliases Fixtures = ["備品"]-jcciAliases ForeignExchangeGains = ["為替差損益", "為替差益"]-jcciAliases ForeignExchangeLosses = ["為替差損益", "為替差損"]-jcciAliases GainOnBargainPurchase = ["負ののれん発生益"]-jcciAliases GainOnConstructionGrants = ["工事負担金受贈益"]-jcciAliases GainOnDonationOfFixedAssets = ["固定資産受贈益"]-jcciAliases GainOnNationalSubsidies = ["国庫補助金受贈益"]-jcciAliases GainOnSalesOfFixedAssets = ["固定資産売却益", "備品売却益", "土地売却益", "建物売却益"]-jcciAliases GainOnSalesOfInvestmentSecurities = ["投資有価証券売却益", "その他有価証券売却益"]-jcciAliases GainOnSalesOfSecurities = ["有価証券売却益", "有価証券売買益", "有価証券運用益"]-jcciAliases GainOnValuationOfSecurities = ["有価証券運用益", "有価証券評価益", "有価証券評価損益", "有価証券運用損益"]-jcciAliases GeneralReserve = ["別途積立金"]-jcciAliases GiftCertificatesReceived = ["受取商品券"]-jcciAliases Goodwill = ["のれん"]-jcciAliases GuaranteeDepositsReceived = ["預り保証金", "受入保証金"]-jcciAliases GuaranteeObligations = ["保証債務"]-jcciAliases HeadOfficeCurrentAccount = ["本店"]-jcciAliases HeldToMaturityBonds = ["満期保有目的債券", "投資有価証券"]-jcciAliases IncomeSummary = ["損益"]-jcciAliases IncomeTaxesAdjustment = ["法人税等調整額"]-jcciAliases IncomeTaxesRefundReceivable = ["未収還付法人税等"]-jcciAliases IncorporationExpenses = ["創立費"]-jcciAliases InsuranceExpense = ["保険料", "支払保険料", "火災保険料"]-jcciAliases InsuranceGain = ["保険差益"]-jcciAliases InterestEarned = ["受取利息"]-jcciAliases InterestExpense = ["支払利息"]-jcciAliases InterestOnSecurities = ["有価証券利息", "受取社債利息"]-jcciAliases InventoryShrinkageLoss = ["棚卸減耗損", "棚卸減耗費"]-jcciAliases InvestmentSecurities = ["投資有価証券"]-jcciAliases Land = ["土地"]-jcciAliases LandRentPaid = ["地代家賃", "支払賃借料", "支払不動産賃借料", "支払地代"]-jcciAliases LandRentReceived = ["受取地代"]-jcciAliases LeaseExpenses = ["支払リース料"]-jcciAliases LeaseObligations = ["リース債務"]-jcciAliases LeasedAssets = ["リース資産"]-jcciAliases LeaseholdRights = ["借地権"]-jcciAliases LegalCapitalSurplus = ["資本準備金", "株式払込剰余金"]-jcciAliases LegalRetainedEarnings = ["利益準備金"]-jcciAliases LoansPayable = ["借入金", "銀行借入金", "役員借入金"]-jcciAliases LoansReceivable = ["貸付金", "従業員貸付金", "役員貸付金"]-jcciAliases LongTermLoansPayable = ["長期借入金"]-jcciAliases LongTermLoansReceivable = ["長期貸付金"]-jcciAliases LongTermOtherPayables = ["長期未払金"]-jcciAliases LongTermPrepaidExpenses = ["長期前払費用"]-jcciAliases LossOnFire = ["火災損失", "災害損失"]-jcciAliases LossOnReductionOfFixedAssets = ["固定資産圧縮損", "備品圧縮損", "建物圧縮損", "機械装置圧縮損"]-jcciAliases LossOnRetirementOfFixedAssets = ["固定資産除却損", "固定資産廃棄損", "備品除却損", "建物除却損", "除却損"]-jcciAliases LossOnSalesOfElectronicallyRecordedReceivables = ["電子記録債権売却損"]-jcciAliases LossOnSalesOfFixedAssets = ["固定資産売却損", "備品売却損", "建物売却損", "土地売却損"]-jcciAliases LossOnSalesOfInvestmentSecurities = ["投資有価証券売却損", "その他有価証券売却損"]-jcciAliases LossOnSalesOfNotesReceivable = ["手形売却損"]-jcciAliases LossOnSalesOfReceivables = ["債権売却損"]-jcciAliases LossOnSalesOfSecurities = ["有価証券売却損", "有価証券売買損", "有価証券運用損"]-jcciAliases LossOnValuationOfMerchandise = ["商品評価損", "棚卸評価損"]-jcciAliases LossOnValuationOfSecurities = ["有価証券運用損", "有価証券評価損", "有価証券評価損益", "有価証券運用損益"]-jcciAliases Machinery = ["機械装置", "機械"]-jcciAliases MaintenanceExpenses = ["保守費", "維持費", "支払メンテナンス料"]-jcciAliases MembershipFees = ["諸会費"]-jcciAliases MerchandiseInventory = ["繰越商品"]-jcciAliases MiscellaneousExpenses = ["雑費"]-jcciAliases MiscellaneousIncome = ["雑益", "雑収入", "雑収益"]-jcciAliases MiscellaneousLoss = ["雑損", "雑損失"]-jcciAliases NetDefinedBenefitAsset = ["退職給付に係る資産"]-jcciAliases NetDefinedBenefitLiability = ["退職給付に係る負債"]-jcciAliases NetIncomeAttributableToNCI = ["非支配株主に帰属する当期純利益"]-jcciAliases NetLossAttributableToNCI = ["非支配株主に帰属する当期純損失"]-jcciAliases NonControllingInterests = ["非支配株主持分"]-jcciAliases NonOperatingElectronicallyRecordedObligations = ["営業外電子記録債務"]-jcciAliases NonOperatingElectronicallyRecordedReceivable = ["営業外電子記録債権"]-jcciAliases NonOperatingNotesPayable = ["営業外支払手形"]-jcciAliases NonOperatingNotesReceivable = ["営業外受取手形"]-jcciAliases NotesLoansPayable = ["借入金", "手形借入金"]-jcciAliases NotesLoansReceivable = ["貸付金", "手形貸付金"]-jcciAliases NotesPayable = ["支払手形"]-jcciAliases NotesReceivable = ["受取手形"]-jcciAliases OperatingExpenses = ["営業費用"]-jcciAliases OperatingRevenue = ["営業収益"]-jcciAliases OtherCapitalSurplus = ["その他資本剰余金"]-jcciAliases OtherPayables = ["未払金"]-jcciAliases OtherReceivables = ["未収入金", "未収金"]-jcciAliases Patent = ["特許権"]-jcciAliases PaymentFees = ["支払手数料", "販売手数料"]-jcciAliases PaymentsOnBehalf = ["立替金", "従業員立替金"]-jcciAliases PettyCash = ["小口現金"]-jcciAliases PrepaidCorporateIncomeTaxes = ["仮払金", "仮払法人税等"]-jcciAliases PrepaidExpenses = ["前払費用", "前払保険料"]-jcciAliases PrepaidPensionCost = ["前払年金費用"]-jcciAliases Products = ["商品"]-jcciAliases PropertyTaxPayable = ["未払固定資産税"]-jcciAliases ProvisionForBonuses = ["賞与引当金繰入", "役員賞与引当金繰入"]-jcciAliases ProvisionForDoubtfulAccounts = ["貸倒引当金繰入", "貸倒引当金繰入額"]-jcciAliases ProvisionForProductWarranties = ["商品保証引当金繰入", "製品保証引当金繰入"]-jcciAliases ProvisionForRepairs = ["修繕引当金繰入", "特別修繕引当金繰入"]-jcciAliases PurchaseRebates = ["仕入割戻"]-jcciAliases Purchases = ["仕入"]-jcciAliases ReceiptFee = ["受取手数料"]-jcciAliases RecoveryOfBadDebts = ["償却債権取立益"]-jcciAliases RefundLiabilities = ["返金負債"]-jcciAliases RefundOfIncomeTaxes = ["還付法人税等"]-jcciAliases RentExpense = ["支払家賃", "地代家賃", "支払賃借料", "支払不動産賃借料"]-jcciAliases RentalIncome = ["受取家賃"]-jcciAliases RepairFundReserve = ["修繕積立金"]-jcciAliases RepairsExpense = ["修繕費", "支払修繕料", "修理費"]-jcciAliases ResearchAndDevelopmentExpenses = ["研究開発費"]-jcciAliases RetainedEarnings = ["繰越利益剰余金"]-jcciAliases RetirementBenefitExpenses = ["退職給付費用", "退職給付引当金繰入", "退職給付引当損"]-jcciAliases ReversalOfAllowanceForDoubtfulAccounts = ["貸倒引当金戻入", "貸倒引当金戻入益"]-jcciAliases ReversalOfAllowanceForProductWarranties = ["商品保証引当金戻入", "製品保証引当金戻入"]-jcciAliases ReversalOfAllowanceForRepairs = ["修繕引当金戻入"]-jcciAliases Sales = ["売上"]-jcciAliases SalesCost = ["売上原価"]-jcciAliases SalesRebates = ["売上割戻"]-jcciAliases Securities = ["有価証券"]-jcciAliases SecurityDepositsPaid = ["差入保証金"]-jcciAliases ServiceRevenue = ["役務収益", "営業収益"]-jcciAliases ShortTermLoansPayable = ["短期借入金"]-jcciAliases ShortTermLoansReceivable = ["短期貸付金"]-jcciAliases Software = ["ソフトウェア"]-jcciAliases SoftwareInProgress = ["ソフトウェア仮勘定"]-jcciAliases StatutoryWelfareExpenses = ["法定福利費", "社会保険料"]-jcciAliases StockIssuanceCosts = ["株式交付費"]-jcciAliases StockSubscriptionDeposits = ["株式申込証拠金", "申込証拠金", "新株式申込証拠金"]-jcciAliases StorageExpenses = ["保管費", "保管料", "倉庫料"]-jcciAliases Structures = ["構築物"]-jcciAliases SubsidiaryStocks = ["子会社株式", "関係会社株式"]-jcciAliases SuppliesExpenses = ["消耗品費", "事務用消耗品費"]-jcciAliases SuppliesOnHand = ["貯蔵品"]-jcciAliases SuspenseAccount = ["未決算", "火災未決算", "保険未決算"]-jcciAliases SuspensePayments = ["仮払金"]-jcciAliases SuspenseReceipts = ["仮受金"]-jcciAliases TaxesExpense = ["租税公課", "公租公課", "固定資産税", "印紙税"]-jcciAliases TimeDeposits = ["銀行預金", "定期預金", "定期預金○○銀行"]-jcciAliases ToolsAndInstruments = ["工具器具"]-jcciAliases Trademark = ["商標権"]-jcciAliases TradingSecurities = ["売買目的有価証券", "有価証券"]-jcciAliases UnearnedRevenue = ["前受収益", "前受地代"]-jcciAliases UnpaidDividends = ["未払配当金", "未払株主配当金"]-jcciAliases UtilitiesExpense = ["水道光熱費", "光熱水費"]-jcciAliases ValuationDifferenceOnOtherSecurities = ["その他有価証券評価差額金"]-jcciAliases Vehicle = ["車両運搬具", "車両", "運搬具"]-jcciAliases WageExpenditure = ["給料", "給料手当", "賃金給料"]-jcciAliases WelfareExpenses = ["福利厚生費"]-jcciAliases WorkInProcess = ["仕掛品"]-jcciAliases _ = []+import ExchangeAlgebra.Accounting.Account.Aliases (jcciAliases)
src/ExchangeAlgebra/Algebra/Base/Account/Registry.hs view
@@ -1,4020 +1,8 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--- The exhaustive registry intentionally retains the deprecated Commutation--- constructor so that legacy behavior remains byte-for-byte stable.-{-# OPTIONS_GHC -Wno-deprecations #-}--{- |-Module : ExchangeAlgebra.Algebra.Base.Account.Registry-Description : Canonical metadata registry for concrete account titles.--The exhaustive 'accountSpec' case is the single source of truth for account-classification, fixed/current status, and bilingual descriptions. 'accountAliases'-combines its base aliases with the frozen JCCI 2022 standard/permitted-name overlay.--}-module ExchangeAlgebra.Algebra.Base.Account.Registry- ( AccountSpec(..)- , AccountSemantics(..)- , accountAliases- , accountSpec- , accountSemantics- , accountSpecMap- , concreteAccountTitles- , classifyAccountContra- , accountDescriptions- ) where--import qualified Data.List as L-import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)-import Data.Maybe (mapMaybe)-import Data.Text (Text)--import ExchangeAlgebra.Algebra.Base.Account.Types- ( AccountDivision(..), AccountRole(..), ClosingRule(..)- , DivisionSemantics(..), FixedCurrent(..), HomeSideSemantics(..)- , PostingCapability(..), ReportingEligibility(..), Side(..) )-import ExchangeAlgebra.Algebra.Base.Account.JcciAliases (jcciAliases)-import ExchangeAlgebra.Algebra.Base.Element (AccountTitles(..))---- | All metadata attached to one concrete account title.-data AccountSpec = AccountSpec- { asDivision :: AccountDivision- , asClosing :: ClosingRule- -- ^ Automatic final-stock closing policy. 'NetIncome' and 'NetLoss' are- -- __permanent__ explicit 'NoClose' overrides (adjudicated 2026-08-11):- -- their division encodes the P\/L /presentation side/ (profit sits on- -- the debit side to balance the statement), so the division-derived- -- rule would invert the transfer sign. Their closing is owned by the- -- dedicated net-income transfer pipeline in- -- "ExchangeAlgebra.Algebra.Transfer" (correct signs, engine-inserted- -- balancing postings). GrossProfit and OrdinaryProfit retain- -- CloseByDivision for the historical SNA/simulation transfer pipeline.- -- That asymmetry is compatibility behavior, not a reporting rule;- -- statement metrics live in "ExchangeAlgebra.Reporting.Metric".- , asIsContra :: Bool- , asFixedCurrent :: FixedCurrent- , asNameEn :: Text- , asNameJa :: Text- -- | Annotation-free Japanese display label (清書和文表示名) used for- -- financial-statement lines- -- ('ExchangeAlgebra.Reporting.Presentation.presentationLabel') and LLM-facing- -- names ('ExchangeAlgebra.Assist.safeNameJa'). Unlike 'asNameJa' it never- -- carries notes, legacy-name- -- remarks, or constructor identifiers; the JCCI level-2 A-column sweep in- -- the test suite pins it against the official standard names.- , asLabelJa :: Text- , asDescription :: Text- -- | Registry-local aliases only. Use 'accountAliases' when constructing a- -- parser or UI: that function also includes the frozen JCCI overlay.- , asAliases :: [Text]- } deriving (Show, Eq)---- | Processing and reporting semantics attached to a concrete account title.------ This record deliberately does not replace 'AccountSpec.asDivision'. The--- legacy division remains the exchange-algebra direction input, while this--- layer states whether that value is a financial-statement classification,--- a bookkeeping control class, or only an internal direction encoding.-data AccountSemantics = AccountSemantics- { asemRoles :: [AccountRole]- , asemPostingCapability :: PostingCapability- , asemDivisionSemantics :: DivisionSemantics- , asemHomeSideSemantics :: HomeSideSemantics- , asemReportingEligibility :: ReportingEligibility- } deriving (Show, Eq)---- | All concrete account titles in their stable Enum order.-concreteAccountTitles :: [AccountTitles]-concreteAccountTitles = filter (/= AccountTitle) [minBound .. maxBound]---- | Look up processing and reporting semantics for a concrete account title.--- The wildcard 'AccountTitle' is outside the metadata domain and returns--- 'Nothing' explicitly.-accountSemantics :: AccountTitles -> Maybe AccountSemantics-accountSemantics title = do- spec <- accountSpec title- pure AccountSemantics- { asemRoles = rolesFor title spec- , asemPostingCapability = postingFor title- , asemDivisionSemantics = divisionFor title spec- , asemHomeSideSemantics = homeSideFor title spec- , asemReportingEligibility = reportingFor title- }--rolesFor :: AccountTitles -> AccountSpec -> [AccountRole]-rolesFor title _ = case title of- Cash -> [OrdinaryAccount]- Deposits -> [OrdinaryAccount]- CurrentDeposits -> [OrdinaryAccount]- Securities -> [OrdinaryAccount]- InvestmentSecurities -> [OrdinaryAccount]- InvestmentInAssociate -> [OrdinaryAccount]- LongTermNationalBonds -> [OrdinaryAccount]- ShortTermNationalBonds -> [OrdinaryAccount]- Products -> [OrdinaryAccount]- Machinery -> [OrdinaryAccount]- Building -> [OrdinaryAccount]- Vehicle -> [OrdinaryAccount]- StockInvestment -> [OrdinaryAccount]- EquipmentInvestment -> [OrdinaryAccount]- LongTermLoansReceivable -> [OrdinaryAccount]- AccountsReceivable -> [OrdinaryAccount]- ShortTermLoansReceivable -> [OrdinaryAccount]- ReserveDepositReceivable -> [OrdinaryAccount]- Gold -> [OrdinaryAccount]- GovernmentService -> [OrdinaryAccount]- CapitalStock -> [OrdinaryAccount]- RetainedEarnings -> [OrdinaryAccount]- LongTermLoansPayable -> [OrdinaryAccount]- ShortTermLoansPayable -> [OrdinaryAccount]- LoansPayable -> [OrdinaryAccount]- ReserveForDepreciation -> [OrdinaryAccount]- DepositPayable -> [OrdinaryAccount]- LongTermNationalBondsPayable -> [OrdinaryAccount]- ShortTermNationalBondsPayable -> [OrdinaryAccount]- ReserveDepositPayable -> [OrdinaryAccount]- CentralBankNotePayable -> [OrdinaryAccount]- Depreciation -> [OrdinaryAccount]- AmortizationExpense -> [OrdinaryAccount]- SalesCost -> [OrdinaryAccount]- BusinessTrip -> [OrdinaryAccount]- Commutation -> [OrdinaryAccount]- UtilitiesExpense -> [OrdinaryAccount]- RentExpense -> [OrdinaryAccount]- AdvertisingExpense -> [OrdinaryAccount]- DeliveryExpenses -> [OrdinaryAccount]- SuppliesExpenses -> [OrdinaryAccount]- MiscellaneousExpenses -> [OrdinaryAccount]- EntertainmentExpenses -> [OrdinaryAccount]- MeetingExpenses -> [OrdinaryAccount]- NewspaperBooksExpenses -> [OrdinaryAccount]- WageExpenditure -> [OrdinaryAccount]- InterestExpense -> [OrdinaryAccount]- TaxesExpense -> [OrdinaryAccount]- ConsumptionExpenditure -> [OrdinaryAccount]- SubsidyExpense -> [OrdinaryAccount]- CentralBankPaymentExpense -> [OrdinaryAccount]- Purchases -> [OrdinaryAccount]- NetIncome -> [PeriodResult]- ValueAdded -> [OrdinaryAccount]- SubsidyIncome -> [OrdinaryAccount]- NationalBondInterestEarned -> [OrdinaryAccount]- DepositInterestEarned -> [OrdinaryAccount]- GrossProfit -> [ReportingSubtotal]- OrdinaryProfit -> [ReportingSubtotal]- InterestEarned -> [OrdinaryAccount]- ReceiptFee -> [OrdinaryAccount]- RentalIncome -> [OrdinaryAccount]- WageEarned -> [OrdinaryAccount]- TaxesRevenue -> [OrdinaryAccount]- CentralBankPaymentIncome -> [OrdinaryAccount]- Sales -> [OrdinaryAccount]- EquityInEarningsOfInvestee -> [OrdinaryAccount]- NetLoss -> [PeriodResult]- PettyCash -> [OrdinaryAccount]- NotesReceivable -> [OrdinaryAccount]- ElectronicallyRecordedReceivable -> [OrdinaryAccount]- CreditCardReceivable -> [OrdinaryAccount]- NotesLoansReceivable -> [OrdinaryAccount]- MerchandiseInventory -> [OrdinaryAccount]- GoodsInTransit -> [OrdinaryAccount]- AdvancesPaid -> [OrdinaryAccount]- PrepaidExpenses -> [OrdinaryAccount]- AccruedRevenue -> [OrdinaryAccount]- OtherReceivables -> [OrdinaryAccount]- PaymentsOnBehalf -> [OrdinaryAccount]- SuspensePayments -> [SuspenseOrClearingAccount]- ConsumptionTaxPaid -> [OrdinaryAccount]- PrepaidCorporateIncomeTaxes -> [OrdinaryAccount]- Land -> [OrdinaryAccount]- Fixtures -> [OrdinaryAccount]- Patent -> [OrdinaryAccount]- Trademark -> [OrdinaryAccount]- Software -> [OrdinaryAccount]- CashOverShort -> [SuspenseOrClearingAccount]- AccountsPayable -> [OrdinaryAccount]- NotesPayable -> [OrdinaryAccount]- ElectronicallyRecordedObligations -> [OrdinaryAccount]- NotesLoansPayable -> [OrdinaryAccount]- BankOverdraft -> [OrdinaryAccount]- AdvancesReceived -> [OrdinaryAccount]- UnearnedRevenue -> [OrdinaryAccount]- AccruedExpenses -> [OrdinaryAccount]- OtherPayables -> [OrdinaryAccount]- DepositsReceived -> [OrdinaryAccount]- SuspenseReceipts -> [SuspenseOrClearingAccount]- ConsumptionTaxReceived -> [OrdinaryAccount]- AccruedConsumptionTax -> [OrdinaryAccount]- AccruedCorporateIncomeTaxes -> [OrdinaryAccount]- UnpaidDividends -> [OrdinaryAccount]- AllowanceForDoubtfulAccounts -> [OrdinaryAccount, ContraAccount]- AccumulatedDepreciation -> [OrdinaryAccount, ContraAccount]- LegalRetainedEarnings -> [OrdinaryAccount]- CumulativeTranslationAdjustment -> [OrdinaryAccount]- ProvisionForDoubtfulAccounts -> [OrdinaryAccount]- BadDebtLoss -> [OrdinaryAccount]- LossOnSalesOfFixedAssets -> [OrdinaryAccount]- LossOnSalesOfNotesReceivable -> [OrdinaryAccount]- PaymentFees -> [OrdinaryAccount]- MiscellaneousLoss -> [OrdinaryAccount]- CorporateIncomeTaxes -> [OrdinaryAccount]- CommunicationExpenses -> [OrdinaryAccount]- GainOnSalesOfFixedAssets -> [OrdinaryAccount]- RecoveryOfBadDebts -> [OrdinaryAccount]- MiscellaneousIncome -> [OrdinaryAccount]- ReversalOfAllowanceForDoubtfulAccounts -> [OrdinaryAccount]- TimeDeposits -> [OrdinaryAccount]- LoansReceivable -> [OrdinaryAccount]- GiftCertificatesReceived -> [OrdinaryAccount]- SecurityDepositsPaid -> [OrdinaryAccount]- SuppliesOnHand -> [OrdinaryAccount]- RawMaterials -> [OrdinaryAccount]- ContractAssets -> [OrdinaryAccount]- IncomeTaxesRefundReceivable -> [OrdinaryAccount]- WorkInProcess -> [OrdinaryAccount]- DeferredTaxAssets -> [OrdinaryAccount]- LeasedAssets -> [OrdinaryAccount]- ToolsAndInstruments -> [OrdinaryAccount]- ConstructionInProgress -> [OrdinaryAccount]- Goodwill -> [OrdinaryAccount]- SoftwareInProgress -> [OrdinaryAccount]- LongTermPrepaidExpenses -> [OrdinaryAccount]- DishonoredNotesReceivable -> [OrdinaryAccount]- PrepaidPensionCost -> [OrdinaryAccount]- NetDefinedBenefitAsset -> [OrdinaryAccount]- DepositsInSpecialAccounts -> [OrdinaryAccount]- Structures -> [OrdinaryAccount]- LeaseholdRights -> [OrdinaryAccount]- NonOperatingNotesReceivable -> [OrdinaryAccount]- NonOperatingElectronicallyRecordedReceivable -> [OrdinaryAccount]- RefundLiabilities -> [OrdinaryAccount]- NonOperatingNotesPayable -> [OrdinaryAccount]- NonOperatingElectronicallyRecordedObligations -> [OrdinaryAccount]- BonusesPayable -> [OrdinaryAccount]- AllowanceForRepairs -> [OrdinaryAccount]- AllowanceForProductWarranties -> [OrdinaryAccount]- AllowanceForBonuses -> [OrdinaryAccount]- DeferredTaxLiabilities -> [OrdinaryAccount]- LeaseObligations -> [OrdinaryAccount]- GuaranteeDepositsReceived -> [OrdinaryAccount]- AllowanceForRetirementBenefits -> [OrdinaryAccount]- LongTermOtherPayables -> [OrdinaryAccount]- NetDefinedBenefitLiability -> [OrdinaryAccount]- StockSubscriptionDeposits -> [OrdinaryAccount]- LegalCapitalSurplus -> [OrdinaryAccount]- OtherCapitalSurplus -> [OrdinaryAccount]- DividendEqualizationReserve -> [OrdinaryAccount]- RepairFundReserve -> [OrdinaryAccount]- ConstructionFundReserve -> [OrdinaryAccount]- GeneralReserve -> [OrdinaryAccount]- ValuationDifferenceOnOtherSecurities -> [OrdinaryAccount]- NonControllingInterests -> [AttributionAccount]- CapitalSurplus -> [OrdinaryAccount]- EarnedSurplus -> [OrdinaryAccount]- ServiceRevenue -> [OrdinaryAccount]- OperatingRevenue -> [OrdinaryAccount]- GainOnSalesOfSecurities -> [OrdinaryAccount]- GainOnValuationOfSecurities -> [OrdinaryAccount]- DividendsReceived -> [OrdinaryAccount]- InterestOnSecurities -> [OrdinaryAccount]- GainOnSalesOfInvestmentSecurities -> [OrdinaryAccount]- InsuranceGain -> [OrdinaryAccount]- GainOnBargainPurchase -> [OrdinaryAccount]- ReversalOfAllowanceForRepairs -> [OrdinaryAccount]- ReversalOfAllowanceForProductWarranties -> [OrdinaryAccount]- GainOnDonationOfFixedAssets -> [OrdinaryAccount]- GainOnNationalSubsidies -> [OrdinaryAccount]- GainOnConstructionGrants -> [OrdinaryAccount]- LandRentReceived -> [OrdinaryAccount]- SalesRebates -> [OrdinaryAccount, ContraAccount]- CostOfServices -> [OrdinaryAccount]- OperatingExpenses -> [OrdinaryAccount]- InventoryShrinkageLoss -> [OrdinaryAccount]- LossOnValuationOfMerchandise -> [OrdinaryAccount]- Bonuses -> [OrdinaryAccount]- RetirementBenefitExpenses -> [OrdinaryAccount]- ProvisionForRepairs -> [OrdinaryAccount]- ProvisionForBonuses -> [OrdinaryAccount]- ProvisionForProductWarranties -> [OrdinaryAccount]- ResearchAndDevelopmentExpenses -> [OrdinaryAccount]- AmortizationOfGoodwill -> [OrdinaryAccount]- AmortizationOfSoftware -> [OrdinaryAccount]- AmortizationOfPatents -> [OrdinaryAccount]- LeaseExpenses -> [OrdinaryAccount]- IncorporationExpenses -> [OrdinaryAccount]- StockIssuanceCosts -> [OrdinaryAccount]- BusinessCommencementExpenses -> [OrdinaryAccount]- DevelopmentExpenses -> [OrdinaryAccount]- LossOnSalesOfElectronicallyRecordedReceivables -> [OrdinaryAccount]- LossOnSalesOfReceivables -> [OrdinaryAccount]- LossOnSalesOfSecurities -> [OrdinaryAccount]- LossOnValuationOfSecurities -> [OrdinaryAccount]- LossOnSalesOfInvestmentSecurities -> [OrdinaryAccount]- LossOnFire -> [OrdinaryAccount]- LossOnRetirementOfFixedAssets -> [OrdinaryAccount]- LossOnReductionOfFixedAssets -> [OrdinaryAccount]- AdditionalIncomeTaxesForPriorPeriods -> [OrdinaryAccount]- RefundOfIncomeTaxes -> [OrdinaryAccount, ContraAccount]- PurchaseRebates -> [OrdinaryAccount, ContraAccount]- WelfareExpenses -> [OrdinaryAccount]- MaintenanceExpenses -> [OrdinaryAccount]- StatutoryWelfareExpenses -> [OrdinaryAccount]- LandRentPaid -> [OrdinaryAccount]- InsuranceExpense -> [OrdinaryAccount]- RepairsExpense -> [OrdinaryAccount]- StorageExpenses -> [OrdinaryAccount]- MembershipFees -> [OrdinaryAccount]- IncomeSummary -> [ClosingDevice]- SuspenseAccount -> [SuspenseOrClearingAccount]- ForeignExchangeGains -> [OrdinaryAccount]- ForeignExchangeLosses -> [OrdinaryAccount]- ContraAccountForGuaranteeObligations -> [OrdinaryAccount]- GuaranteeObligations -> [OrdinaryAccount]- IncomeTaxesAdjustment -> [OrdinaryAccount]- BranchCurrentAccount -> [ReciprocalAccount]- HeadOfficeCurrentAccount -> [ReciprocalAccount]- NetIncomeAttributableToNCI -> [AttributionAccount, PeriodResult]- NetLossAttributableToNCI -> [AttributionAccount, PeriodResult]- TradingSecurities -> [OrdinaryAccount]- HeldToMaturityBonds -> [OrdinaryAccount]- SubsidiaryStocks -> [OrdinaryAccount]- AffiliateStocks -> [OrdinaryAccount]- AvailableForSaleSecurities -> [OrdinaryAccount]- ConsumptionTaxRefundReceivable -> [OrdinaryAccount]- PropertyTaxPayable -> [OrdinaryAccount]- DepositsReceivedFromOfficers -> [OrdinaryAccount]- AccountTitle -> [OrdinaryAccount]--postingFor :: AccountTitles -> PostingCapability-postingFor title = case title of- Cash -> OrdinaryPosting- Deposits -> OrdinaryPosting- CurrentDeposits -> OrdinaryPosting- Securities -> OrdinaryPosting- InvestmentSecurities -> OrdinaryPosting- InvestmentInAssociate -> OrdinaryPosting- LongTermNationalBonds -> OrdinaryPosting- ShortTermNationalBonds -> OrdinaryPosting- Products -> OrdinaryPosting- Machinery -> OrdinaryPosting- Building -> OrdinaryPosting- Vehicle -> OrdinaryPosting- StockInvestment -> OrdinaryPosting- EquipmentInvestment -> OrdinaryPosting- LongTermLoansReceivable -> OrdinaryPosting- AccountsReceivable -> OrdinaryPosting- ShortTermLoansReceivable -> OrdinaryPosting- ReserveDepositReceivable -> OrdinaryPosting- Gold -> OrdinaryPosting- GovernmentService -> OrdinaryPosting- CapitalStock -> OrdinaryPosting- RetainedEarnings -> OrdinaryPosting- LongTermLoansPayable -> OrdinaryPosting- ShortTermLoansPayable -> OrdinaryPosting- LoansPayable -> OrdinaryPosting- ReserveForDepreciation -> OrdinaryPosting- DepositPayable -> OrdinaryPosting- LongTermNationalBondsPayable -> OrdinaryPosting- ShortTermNationalBondsPayable -> OrdinaryPosting- ReserveDepositPayable -> OrdinaryPosting- CentralBankNotePayable -> OrdinaryPosting- Depreciation -> OrdinaryPosting- AmortizationExpense -> OrdinaryPosting- SalesCost -> OrdinaryPosting- BusinessTrip -> OrdinaryPosting- Commutation -> OrdinaryPosting- UtilitiesExpense -> OrdinaryPosting- RentExpense -> OrdinaryPosting- AdvertisingExpense -> OrdinaryPosting- DeliveryExpenses -> OrdinaryPosting- SuppliesExpenses -> OrdinaryPosting- MiscellaneousExpenses -> OrdinaryPosting- EntertainmentExpenses -> OrdinaryPosting- MeetingExpenses -> OrdinaryPosting- NewspaperBooksExpenses -> OrdinaryPosting- WageExpenditure -> OrdinaryPosting- InterestExpense -> OrdinaryPosting- TaxesExpense -> OrdinaryPosting- ConsumptionExpenditure -> OrdinaryPosting- SubsidyExpense -> OrdinaryPosting- CentralBankPaymentExpense -> OrdinaryPosting- Purchases -> OrdinaryPosting- NetIncome -> EngineGeneratedOnly- ValueAdded -> OrdinaryPosting- SubsidyIncome -> OrdinaryPosting- NationalBondInterestEarned -> OrdinaryPosting- DepositInterestEarned -> OrdinaryPosting- GrossProfit -> EngineGeneratedOnly- OrdinaryProfit -> EngineGeneratedOnly- InterestEarned -> OrdinaryPosting- ReceiptFee -> OrdinaryPosting- RentalIncome -> OrdinaryPosting- WageEarned -> OrdinaryPosting- TaxesRevenue -> OrdinaryPosting- CentralBankPaymentIncome -> OrdinaryPosting- Sales -> OrdinaryPosting- EquityInEarningsOfInvestee -> ConsolidationOnly- NetLoss -> EngineGeneratedOnly- PettyCash -> OrdinaryPosting- NotesReceivable -> OrdinaryPosting- ElectronicallyRecordedReceivable -> OrdinaryPosting- CreditCardReceivable -> OrdinaryPosting- NotesLoansReceivable -> OrdinaryPosting- MerchandiseInventory -> OrdinaryPosting- GoodsInTransit -> OrdinaryPosting- AdvancesPaid -> OrdinaryPosting- PrepaidExpenses -> OrdinaryPosting- AccruedRevenue -> OrdinaryPosting- OtherReceivables -> OrdinaryPosting- PaymentsOnBehalf -> OrdinaryPosting- SuspensePayments -> OrdinaryPosting- ConsumptionTaxPaid -> OrdinaryPosting- PrepaidCorporateIncomeTaxes -> OrdinaryPosting- Land -> OrdinaryPosting- Fixtures -> OrdinaryPosting- Patent -> OrdinaryPosting- Trademark -> OrdinaryPosting- Software -> OrdinaryPosting- CashOverShort -> OrdinaryPosting- AccountsPayable -> OrdinaryPosting- NotesPayable -> OrdinaryPosting- ElectronicallyRecordedObligations -> OrdinaryPosting- NotesLoansPayable -> OrdinaryPosting- BankOverdraft -> OrdinaryPosting- AdvancesReceived -> OrdinaryPosting- UnearnedRevenue -> OrdinaryPosting- AccruedExpenses -> OrdinaryPosting- OtherPayables -> OrdinaryPosting- DepositsReceived -> OrdinaryPosting- SuspenseReceipts -> OrdinaryPosting- ConsumptionTaxReceived -> OrdinaryPosting- AccruedConsumptionTax -> OrdinaryPosting- AccruedCorporateIncomeTaxes -> OrdinaryPosting- UnpaidDividends -> OrdinaryPosting- AllowanceForDoubtfulAccounts -> OrdinaryPosting- AccumulatedDepreciation -> OrdinaryPosting- LegalRetainedEarnings -> OrdinaryPosting- CumulativeTranslationAdjustment -> ConsolidationOnly- ProvisionForDoubtfulAccounts -> OrdinaryPosting- BadDebtLoss -> OrdinaryPosting- LossOnSalesOfFixedAssets -> OrdinaryPosting- LossOnSalesOfNotesReceivable -> OrdinaryPosting- PaymentFees -> OrdinaryPosting- MiscellaneousLoss -> OrdinaryPosting- CorporateIncomeTaxes -> OrdinaryPosting- CommunicationExpenses -> OrdinaryPosting- GainOnSalesOfFixedAssets -> OrdinaryPosting- RecoveryOfBadDebts -> OrdinaryPosting- MiscellaneousIncome -> OrdinaryPosting- ReversalOfAllowanceForDoubtfulAccounts -> OrdinaryPosting- TimeDeposits -> OrdinaryPosting- LoansReceivable -> OrdinaryPosting- GiftCertificatesReceived -> OrdinaryPosting- SecurityDepositsPaid -> OrdinaryPosting- SuppliesOnHand -> OrdinaryPosting- RawMaterials -> OrdinaryPosting- ContractAssets -> OrdinaryPosting- IncomeTaxesRefundReceivable -> OrdinaryPosting- WorkInProcess -> OrdinaryPosting- DeferredTaxAssets -> OrdinaryPosting- LeasedAssets -> OrdinaryPosting- ToolsAndInstruments -> OrdinaryPosting- ConstructionInProgress -> OrdinaryPosting- Goodwill -> OrdinaryPosting- SoftwareInProgress -> OrdinaryPosting- LongTermPrepaidExpenses -> OrdinaryPosting- DishonoredNotesReceivable -> OrdinaryPosting- PrepaidPensionCost -> OrdinaryPosting- NetDefinedBenefitAsset -> OrdinaryPosting- DepositsInSpecialAccounts -> OrdinaryPosting- Structures -> OrdinaryPosting- LeaseholdRights -> OrdinaryPosting- NonOperatingNotesReceivable -> OrdinaryPosting- NonOperatingElectronicallyRecordedReceivable -> OrdinaryPosting- RefundLiabilities -> OrdinaryPosting- NonOperatingNotesPayable -> OrdinaryPosting- NonOperatingElectronicallyRecordedObligations -> OrdinaryPosting- BonusesPayable -> OrdinaryPosting- AllowanceForRepairs -> OrdinaryPosting- AllowanceForProductWarranties -> OrdinaryPosting- AllowanceForBonuses -> OrdinaryPosting- DeferredTaxLiabilities -> OrdinaryPosting- LeaseObligations -> OrdinaryPosting- GuaranteeDepositsReceived -> OrdinaryPosting- AllowanceForRetirementBenefits -> OrdinaryPosting- LongTermOtherPayables -> OrdinaryPosting- NetDefinedBenefitLiability -> OrdinaryPosting- StockSubscriptionDeposits -> OrdinaryPosting- LegalCapitalSurplus -> OrdinaryPosting- OtherCapitalSurplus -> OrdinaryPosting- DividendEqualizationReserve -> OrdinaryPosting- RepairFundReserve -> OrdinaryPosting- ConstructionFundReserve -> OrdinaryPosting- GeneralReserve -> OrdinaryPosting- ValuationDifferenceOnOtherSecurities -> OrdinaryPosting- NonControllingInterests -> ConsolidationOnly- CapitalSurplus -> OrdinaryPosting- EarnedSurplus -> OrdinaryPosting- ServiceRevenue -> OrdinaryPosting- OperatingRevenue -> OrdinaryPosting- GainOnSalesOfSecurities -> OrdinaryPosting- GainOnValuationOfSecurities -> OrdinaryPosting- DividendsReceived -> OrdinaryPosting- InterestOnSecurities -> OrdinaryPosting- GainOnSalesOfInvestmentSecurities -> OrdinaryPosting- InsuranceGain -> OrdinaryPosting- GainOnBargainPurchase -> OrdinaryPosting- ReversalOfAllowanceForRepairs -> OrdinaryPosting- ReversalOfAllowanceForProductWarranties -> OrdinaryPosting- GainOnDonationOfFixedAssets -> OrdinaryPosting- GainOnNationalSubsidies -> OrdinaryPosting- GainOnConstructionGrants -> OrdinaryPosting- LandRentReceived -> OrdinaryPosting- SalesRebates -> OrdinaryPosting- CostOfServices -> OrdinaryPosting- OperatingExpenses -> OrdinaryPosting- InventoryShrinkageLoss -> OrdinaryPosting- LossOnValuationOfMerchandise -> OrdinaryPosting- Bonuses -> OrdinaryPosting- RetirementBenefitExpenses -> OrdinaryPosting- ProvisionForRepairs -> OrdinaryPosting- ProvisionForBonuses -> OrdinaryPosting- ProvisionForProductWarranties -> OrdinaryPosting- ResearchAndDevelopmentExpenses -> OrdinaryPosting- AmortizationOfGoodwill -> OrdinaryPosting- AmortizationOfSoftware -> OrdinaryPosting- AmortizationOfPatents -> OrdinaryPosting- LeaseExpenses -> OrdinaryPosting- IncorporationExpenses -> OrdinaryPosting- StockIssuanceCosts -> OrdinaryPosting- BusinessCommencementExpenses -> OrdinaryPosting- DevelopmentExpenses -> OrdinaryPosting- LossOnSalesOfElectronicallyRecordedReceivables -> OrdinaryPosting- LossOnSalesOfReceivables -> OrdinaryPosting- LossOnSalesOfSecurities -> OrdinaryPosting- LossOnValuationOfSecurities -> OrdinaryPosting- LossOnSalesOfInvestmentSecurities -> OrdinaryPosting- LossOnFire -> OrdinaryPosting- LossOnRetirementOfFixedAssets -> OrdinaryPosting- LossOnReductionOfFixedAssets -> OrdinaryPosting- AdditionalIncomeTaxesForPriorPeriods -> OrdinaryPosting- RefundOfIncomeTaxes -> OrdinaryPosting- PurchaseRebates -> OrdinaryPosting- WelfareExpenses -> OrdinaryPosting- MaintenanceExpenses -> OrdinaryPosting- StatutoryWelfareExpenses -> OrdinaryPosting- LandRentPaid -> OrdinaryPosting- InsuranceExpense -> OrdinaryPosting- RepairsExpense -> OrdinaryPosting- StorageExpenses -> OrdinaryPosting- MembershipFees -> OrdinaryPosting- IncomeSummary -> ClosingOnly- SuspenseAccount -> OrdinaryPosting- ForeignExchangeGains -> OrdinaryPosting- ForeignExchangeLosses -> OrdinaryPosting- ContraAccountForGuaranteeObligations -> OrdinaryPosting- GuaranteeObligations -> OrdinaryPosting- IncomeTaxesAdjustment -> OrdinaryPosting- BranchCurrentAccount -> OrdinaryPosting- HeadOfficeCurrentAccount -> OrdinaryPosting- NetIncomeAttributableToNCI -> ConsolidationOnly- NetLossAttributableToNCI -> ConsolidationOnly- TradingSecurities -> OrdinaryPosting- HeldToMaturityBonds -> OrdinaryPosting- SubsidiaryStocks -> OrdinaryPosting- AffiliateStocks -> OrdinaryPosting- AvailableForSaleSecurities -> OrdinaryPosting- ConsumptionTaxRefundReceivable -> OrdinaryPosting- PropertyTaxPayable -> OrdinaryPosting- DepositsReceivedFromOfficers -> OrdinaryPosting- AccountTitle -> NotPostable--divisionFor :: AccountTitles -> AccountSpec -> DivisionSemantics-divisionFor title spec = case title of- Cash -> StatementDivision (asDivision spec)- Deposits -> StatementDivision (asDivision spec)- CurrentDeposits -> StatementDivision (asDivision spec)- Securities -> StatementDivision (asDivision spec)- InvestmentSecurities -> StatementDivision (asDivision spec)- InvestmentInAssociate -> StatementDivision (asDivision spec)- LongTermNationalBonds -> StatementDivision (asDivision spec)- ShortTermNationalBonds -> StatementDivision (asDivision spec)- Products -> StatementDivision (asDivision spec)- Machinery -> StatementDivision (asDivision spec)- Building -> StatementDivision (asDivision spec)- Vehicle -> StatementDivision (asDivision spec)- StockInvestment -> StatementDivision (asDivision spec)- EquipmentInvestment -> StatementDivision (asDivision spec)- LongTermLoansReceivable -> StatementDivision (asDivision spec)- AccountsReceivable -> StatementDivision (asDivision spec)- ShortTermLoansReceivable -> StatementDivision (asDivision spec)- ReserveDepositReceivable -> StatementDivision (asDivision spec)- Gold -> StatementDivision (asDivision spec)- GovernmentService -> StatementDivision (asDivision spec)- CapitalStock -> StatementDivision (asDivision spec)- RetainedEarnings -> StatementDivision (asDivision spec)- LongTermLoansPayable -> StatementDivision (asDivision spec)- ShortTermLoansPayable -> StatementDivision (asDivision spec)- LoansPayable -> StatementDivision (asDivision spec)- ReserveForDepreciation -> StatementDivision (asDivision spec)- DepositPayable -> StatementDivision (asDivision spec)- LongTermNationalBondsPayable -> StatementDivision (asDivision spec)- ShortTermNationalBondsPayable -> StatementDivision (asDivision spec)- ReserveDepositPayable -> StatementDivision (asDivision spec)- CentralBankNotePayable -> StatementDivision (asDivision spec)- Depreciation -> StatementDivision (asDivision spec)- AmortizationExpense -> StatementDivision (asDivision spec)- SalesCost -> StatementDivision (asDivision spec)- BusinessTrip -> StatementDivision (asDivision spec)- Commutation -> StatementDivision (asDivision spec)- UtilitiesExpense -> StatementDivision (asDivision spec)- RentExpense -> StatementDivision (asDivision spec)- AdvertisingExpense -> StatementDivision (asDivision spec)- DeliveryExpenses -> StatementDivision (asDivision spec)- SuppliesExpenses -> StatementDivision (asDivision spec)- MiscellaneousExpenses -> StatementDivision (asDivision spec)- EntertainmentExpenses -> StatementDivision (asDivision spec)- MeetingExpenses -> StatementDivision (asDivision spec)- NewspaperBooksExpenses -> StatementDivision (asDivision spec)- WageExpenditure -> StatementDivision (asDivision spec)- InterestExpense -> StatementDivision (asDivision spec)- TaxesExpense -> StatementDivision (asDivision spec)- ConsumptionExpenditure -> StatementDivision (asDivision spec)- SubsidyExpense -> StatementDivision (asDivision spec)- CentralBankPaymentExpense -> StatementDivision (asDivision spec)- Purchases -> StatementDivision (asDivision spec)- NetIncome -> DirectionEncoding (asDivision spec)- ValueAdded -> StatementDivision (asDivision spec)- SubsidyIncome -> StatementDivision (asDivision spec)- NationalBondInterestEarned -> StatementDivision (asDivision spec)- DepositInterestEarned -> StatementDivision (asDivision spec)- GrossProfit -> DirectionEncoding (asDivision spec)- OrdinaryProfit -> DirectionEncoding (asDivision spec)- InterestEarned -> StatementDivision (asDivision spec)- ReceiptFee -> StatementDivision (asDivision spec)- RentalIncome -> StatementDivision (asDivision spec)- WageEarned -> StatementDivision (asDivision spec)- TaxesRevenue -> StatementDivision (asDivision spec)- CentralBankPaymentIncome -> StatementDivision (asDivision spec)- Sales -> StatementDivision (asDivision spec)- EquityInEarningsOfInvestee -> StatementDivision (asDivision spec)- NetLoss -> DirectionEncoding (asDivision spec)- PettyCash -> StatementDivision (asDivision spec)- NotesReceivable -> StatementDivision (asDivision spec)- ElectronicallyRecordedReceivable -> StatementDivision (asDivision spec)- CreditCardReceivable -> StatementDivision (asDivision spec)- NotesLoansReceivable -> StatementDivision (asDivision spec)- MerchandiseInventory -> StatementDivision (asDivision spec)- GoodsInTransit -> StatementDivision (asDivision spec)- AdvancesPaid -> StatementDivision (asDivision spec)- PrepaidExpenses -> StatementDivision (asDivision spec)- AccruedRevenue -> StatementDivision (asDivision spec)- OtherReceivables -> StatementDivision (asDivision spec)- PaymentsOnBehalf -> StatementDivision (asDivision spec)- SuspensePayments -> BookkeepingControlClass (asDivision spec)- ConsumptionTaxPaid -> StatementDivision (asDivision spec)- PrepaidCorporateIncomeTaxes -> StatementDivision (asDivision spec)- Land -> StatementDivision (asDivision spec)- Fixtures -> StatementDivision (asDivision spec)- Patent -> StatementDivision (asDivision spec)- Trademark -> StatementDivision (asDivision spec)- Software -> StatementDivision (asDivision spec)- CashOverShort -> BookkeepingControlClass (asDivision spec)- AccountsPayable -> StatementDivision (asDivision spec)- NotesPayable -> StatementDivision (asDivision spec)- ElectronicallyRecordedObligations -> StatementDivision (asDivision spec)- NotesLoansPayable -> StatementDivision (asDivision spec)- BankOverdraft -> StatementDivision (asDivision spec)- AdvancesReceived -> StatementDivision (asDivision spec)- UnearnedRevenue -> StatementDivision (asDivision spec)- AccruedExpenses -> StatementDivision (asDivision spec)- OtherPayables -> StatementDivision (asDivision spec)- DepositsReceived -> StatementDivision (asDivision spec)- SuspenseReceipts -> BookkeepingControlClass (asDivision spec)- ConsumptionTaxReceived -> StatementDivision (asDivision spec)- AccruedConsumptionTax -> StatementDivision (asDivision spec)- AccruedCorporateIncomeTaxes -> StatementDivision (asDivision spec)- UnpaidDividends -> StatementDivision (asDivision spec)- AllowanceForDoubtfulAccounts -> StatementDivision (asDivision spec)- AccumulatedDepreciation -> StatementDivision (asDivision spec)- LegalRetainedEarnings -> StatementDivision (asDivision spec)- CumulativeTranslationAdjustment -> StatementDivision (asDivision spec)- ProvisionForDoubtfulAccounts -> StatementDivision (asDivision spec)- BadDebtLoss -> StatementDivision (asDivision spec)- LossOnSalesOfFixedAssets -> StatementDivision (asDivision spec)- LossOnSalesOfNotesReceivable -> StatementDivision (asDivision spec)- PaymentFees -> StatementDivision (asDivision spec)- MiscellaneousLoss -> StatementDivision (asDivision spec)- CorporateIncomeTaxes -> StatementDivision (asDivision spec)- CommunicationExpenses -> StatementDivision (asDivision spec)- GainOnSalesOfFixedAssets -> StatementDivision (asDivision spec)- RecoveryOfBadDebts -> StatementDivision (asDivision spec)- MiscellaneousIncome -> StatementDivision (asDivision spec)- ReversalOfAllowanceForDoubtfulAccounts -> StatementDivision (asDivision spec)- TimeDeposits -> StatementDivision (asDivision spec)- LoansReceivable -> StatementDivision (asDivision spec)- GiftCertificatesReceived -> StatementDivision (asDivision spec)- SecurityDepositsPaid -> StatementDivision (asDivision spec)- SuppliesOnHand -> StatementDivision (asDivision spec)- RawMaterials -> StatementDivision (asDivision spec)- ContractAssets -> StatementDivision (asDivision spec)- IncomeTaxesRefundReceivable -> StatementDivision (asDivision spec)- WorkInProcess -> StatementDivision (asDivision spec)- DeferredTaxAssets -> StatementDivision (asDivision spec)- LeasedAssets -> StatementDivision (asDivision spec)- ToolsAndInstruments -> StatementDivision (asDivision spec)- ConstructionInProgress -> StatementDivision (asDivision spec)- Goodwill -> StatementDivision (asDivision spec)- SoftwareInProgress -> StatementDivision (asDivision spec)- LongTermPrepaidExpenses -> StatementDivision (asDivision spec)- DishonoredNotesReceivable -> StatementDivision (asDivision spec)- PrepaidPensionCost -> StatementDivision (asDivision spec)- NetDefinedBenefitAsset -> StatementDivision (asDivision spec)- DepositsInSpecialAccounts -> StatementDivision (asDivision spec)- Structures -> StatementDivision (asDivision spec)- LeaseholdRights -> StatementDivision (asDivision spec)- NonOperatingNotesReceivable -> StatementDivision (asDivision spec)- NonOperatingElectronicallyRecordedReceivable -> StatementDivision (asDivision spec)- RefundLiabilities -> StatementDivision (asDivision spec)- NonOperatingNotesPayable -> StatementDivision (asDivision spec)- NonOperatingElectronicallyRecordedObligations -> StatementDivision (asDivision spec)- BonusesPayable -> StatementDivision (asDivision spec)- AllowanceForRepairs -> StatementDivision (asDivision spec)- AllowanceForProductWarranties -> StatementDivision (asDivision spec)- AllowanceForBonuses -> StatementDivision (asDivision spec)- DeferredTaxLiabilities -> StatementDivision (asDivision spec)- LeaseObligations -> StatementDivision (asDivision spec)- GuaranteeDepositsReceived -> StatementDivision (asDivision spec)- AllowanceForRetirementBenefits -> StatementDivision (asDivision spec)- LongTermOtherPayables -> StatementDivision (asDivision spec)- NetDefinedBenefitLiability -> StatementDivision (asDivision spec)- StockSubscriptionDeposits -> StatementDivision (asDivision spec)- LegalCapitalSurplus -> StatementDivision (asDivision spec)- OtherCapitalSurplus -> StatementDivision (asDivision spec)- DividendEqualizationReserve -> StatementDivision (asDivision spec)- RepairFundReserve -> StatementDivision (asDivision spec)- ConstructionFundReserve -> StatementDivision (asDivision spec)- GeneralReserve -> StatementDivision (asDivision spec)- ValuationDifferenceOnOtherSecurities -> StatementDivision (asDivision spec)- NonControllingInterests -> StatementDivision (asDivision spec)- CapitalSurplus -> StatementDivision (asDivision spec)- EarnedSurplus -> StatementDivision (asDivision spec)- ServiceRevenue -> StatementDivision (asDivision spec)- OperatingRevenue -> StatementDivision (asDivision spec)- GainOnSalesOfSecurities -> StatementDivision (asDivision spec)- GainOnValuationOfSecurities -> StatementDivision (asDivision spec)- DividendsReceived -> StatementDivision (asDivision spec)- InterestOnSecurities -> StatementDivision (asDivision spec)- GainOnSalesOfInvestmentSecurities -> StatementDivision (asDivision spec)- InsuranceGain -> StatementDivision (asDivision spec)- GainOnBargainPurchase -> StatementDivision (asDivision spec)- ReversalOfAllowanceForRepairs -> StatementDivision (asDivision spec)- ReversalOfAllowanceForProductWarranties -> StatementDivision (asDivision spec)- GainOnDonationOfFixedAssets -> StatementDivision (asDivision spec)- GainOnNationalSubsidies -> StatementDivision (asDivision spec)- GainOnConstructionGrants -> StatementDivision (asDivision spec)- LandRentReceived -> StatementDivision (asDivision spec)- SalesRebates -> StatementDivision (asDivision spec)- CostOfServices -> StatementDivision (asDivision spec)- OperatingExpenses -> StatementDivision (asDivision spec)- InventoryShrinkageLoss -> StatementDivision (asDivision spec)- LossOnValuationOfMerchandise -> StatementDivision (asDivision spec)- Bonuses -> StatementDivision (asDivision spec)- RetirementBenefitExpenses -> StatementDivision (asDivision spec)- ProvisionForRepairs -> StatementDivision (asDivision spec)- ProvisionForBonuses -> StatementDivision (asDivision spec)- ProvisionForProductWarranties -> StatementDivision (asDivision spec)- ResearchAndDevelopmentExpenses -> StatementDivision (asDivision spec)- AmortizationOfGoodwill -> StatementDivision (asDivision spec)- AmortizationOfSoftware -> StatementDivision (asDivision spec)- AmortizationOfPatents -> StatementDivision (asDivision spec)- LeaseExpenses -> StatementDivision (asDivision spec)- IncorporationExpenses -> StatementDivision (asDivision spec)- StockIssuanceCosts -> StatementDivision (asDivision spec)- BusinessCommencementExpenses -> StatementDivision (asDivision spec)- DevelopmentExpenses -> StatementDivision (asDivision spec)- LossOnSalesOfElectronicallyRecordedReceivables -> StatementDivision (asDivision spec)- LossOnSalesOfReceivables -> StatementDivision (asDivision spec)- LossOnSalesOfSecurities -> StatementDivision (asDivision spec)- LossOnValuationOfSecurities -> StatementDivision (asDivision spec)- LossOnSalesOfInvestmentSecurities -> StatementDivision (asDivision spec)- LossOnFire -> StatementDivision (asDivision spec)- LossOnRetirementOfFixedAssets -> StatementDivision (asDivision spec)- LossOnReductionOfFixedAssets -> StatementDivision (asDivision spec)- AdditionalIncomeTaxesForPriorPeriods -> StatementDivision (asDivision spec)- RefundOfIncomeTaxes -> StatementDivision (asDivision spec)- PurchaseRebates -> StatementDivision (asDivision spec)- WelfareExpenses -> StatementDivision (asDivision spec)- MaintenanceExpenses -> StatementDivision (asDivision spec)- StatutoryWelfareExpenses -> StatementDivision (asDivision spec)- LandRentPaid -> StatementDivision (asDivision spec)- InsuranceExpense -> StatementDivision (asDivision spec)- RepairsExpense -> StatementDivision (asDivision spec)- StorageExpenses -> StatementDivision (asDivision spec)- MembershipFees -> StatementDivision (asDivision spec)- IncomeSummary -> DirectionEncoding (asDivision spec)- SuspenseAccount -> BookkeepingControlClass (asDivision spec)- ForeignExchangeGains -> StatementDivision (asDivision spec)- ForeignExchangeLosses -> StatementDivision (asDivision spec)- ContraAccountForGuaranteeObligations -> StatementDivision (asDivision spec)- GuaranteeObligations -> StatementDivision (asDivision spec)- IncomeTaxesAdjustment -> StatementDivision (asDivision spec)- BranchCurrentAccount -> BookkeepingControlClass (asDivision spec)- HeadOfficeCurrentAccount -> BookkeepingControlClass (asDivision spec)- NetIncomeAttributableToNCI -> DirectionEncoding (asDivision spec)- NetLossAttributableToNCI -> DirectionEncoding (asDivision spec)- TradingSecurities -> StatementDivision (asDivision spec)- HeldToMaturityBonds -> StatementDivision (asDivision spec)- SubsidiaryStocks -> StatementDivision (asDivision spec)- AffiliateStocks -> StatementDivision (asDivision spec)- AvailableForSaleSecurities -> StatementDivision (asDivision spec)- ConsumptionTaxRefundReceivable -> StatementDivision (asDivision spec)- PropertyTaxPayable -> StatementDivision (asDivision spec)- DepositsReceivedFromOfficers -> StatementDivision (asDivision spec)- AccountTitle -> NoStatementDivision--homeSideFor :: AccountTitles -> AccountSpec -> HomeSideSemantics-homeSideFor title spec = case title of- Cash -> FixedHomeSide (legacyHomeSide spec)- Deposits -> FixedHomeSide (legacyHomeSide spec)- CurrentDeposits -> FixedHomeSide (legacyHomeSide spec)- Securities -> FixedHomeSide (legacyHomeSide spec)- InvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)- InvestmentInAssociate -> FixedHomeSide (legacyHomeSide spec)- LongTermNationalBonds -> FixedHomeSide (legacyHomeSide spec)- ShortTermNationalBonds -> FixedHomeSide (legacyHomeSide spec)- Products -> FixedHomeSide (legacyHomeSide spec)- Machinery -> FixedHomeSide (legacyHomeSide spec)- Building -> FixedHomeSide (legacyHomeSide spec)- Vehicle -> FixedHomeSide (legacyHomeSide spec)- StockInvestment -> FixedHomeSide (legacyHomeSide spec)- EquipmentInvestment -> FixedHomeSide (legacyHomeSide spec)- LongTermLoansReceivable -> FixedHomeSide (legacyHomeSide spec)- AccountsReceivable -> FixedHomeSide (legacyHomeSide spec)- ShortTermLoansReceivable -> FixedHomeSide (legacyHomeSide spec)- ReserveDepositReceivable -> FixedHomeSide (legacyHomeSide spec)- Gold -> FixedHomeSide (legacyHomeSide spec)- GovernmentService -> FixedHomeSide (legacyHomeSide spec)- CapitalStock -> FixedHomeSide (legacyHomeSide spec)- RetainedEarnings -> FixedHomeSide (legacyHomeSide spec)- LongTermLoansPayable -> FixedHomeSide (legacyHomeSide spec)- ShortTermLoansPayable -> FixedHomeSide (legacyHomeSide spec)- LoansPayable -> FixedHomeSide (legacyHomeSide spec)- ReserveForDepreciation -> FixedHomeSide (legacyHomeSide spec)- DepositPayable -> FixedHomeSide (legacyHomeSide spec)- LongTermNationalBondsPayable -> FixedHomeSide (legacyHomeSide spec)- ShortTermNationalBondsPayable -> FixedHomeSide (legacyHomeSide spec)- ReserveDepositPayable -> FixedHomeSide (legacyHomeSide spec)- CentralBankNotePayable -> FixedHomeSide (legacyHomeSide spec)- Depreciation -> FixedHomeSide (legacyHomeSide spec)- AmortizationExpense -> FixedHomeSide (legacyHomeSide spec)- SalesCost -> FixedHomeSide (legacyHomeSide spec)- BusinessTrip -> FixedHomeSide (legacyHomeSide spec)- Commutation -> FixedHomeSide (legacyHomeSide spec)- UtilitiesExpense -> FixedHomeSide (legacyHomeSide spec)- RentExpense -> FixedHomeSide (legacyHomeSide spec)- AdvertisingExpense -> FixedHomeSide (legacyHomeSide spec)- DeliveryExpenses -> FixedHomeSide (legacyHomeSide spec)- SuppliesExpenses -> FixedHomeSide (legacyHomeSide spec)- MiscellaneousExpenses -> FixedHomeSide (legacyHomeSide spec)- EntertainmentExpenses -> FixedHomeSide (legacyHomeSide spec)- MeetingExpenses -> FixedHomeSide (legacyHomeSide spec)- NewspaperBooksExpenses -> FixedHomeSide (legacyHomeSide spec)- WageExpenditure -> FixedHomeSide (legacyHomeSide spec)- InterestExpense -> FixedHomeSide (legacyHomeSide spec)- TaxesExpense -> FixedHomeSide (legacyHomeSide spec)- ConsumptionExpenditure -> FixedHomeSide (legacyHomeSide spec)- SubsidyExpense -> FixedHomeSide (legacyHomeSide spec)- CentralBankPaymentExpense -> FixedHomeSide (legacyHomeSide spec)- Purchases -> FixedHomeSide (legacyHomeSide spec)- NetIncome -> FixedHomeSide (legacyHomeSide spec)- ValueAdded -> FixedHomeSide (legacyHomeSide spec)- SubsidyIncome -> FixedHomeSide (legacyHomeSide spec)- NationalBondInterestEarned -> FixedHomeSide (legacyHomeSide spec)- DepositInterestEarned -> FixedHomeSide (legacyHomeSide spec)- GrossProfit -> FixedHomeSide (legacyHomeSide spec)- OrdinaryProfit -> FixedHomeSide (legacyHomeSide spec)- InterestEarned -> FixedHomeSide (legacyHomeSide spec)- ReceiptFee -> FixedHomeSide (legacyHomeSide spec)- RentalIncome -> FixedHomeSide (legacyHomeSide spec)- WageEarned -> FixedHomeSide (legacyHomeSide spec)- TaxesRevenue -> FixedHomeSide (legacyHomeSide spec)- CentralBankPaymentIncome -> FixedHomeSide (legacyHomeSide spec)- Sales -> FixedHomeSide (legacyHomeSide spec)- EquityInEarningsOfInvestee -> FixedHomeSide (legacyHomeSide spec)- NetLoss -> FixedHomeSide (legacyHomeSide spec)- PettyCash -> FixedHomeSide (legacyHomeSide spec)- NotesReceivable -> FixedHomeSide (legacyHomeSide spec)- ElectronicallyRecordedReceivable -> FixedHomeSide (legacyHomeSide spec)- CreditCardReceivable -> FixedHomeSide (legacyHomeSide spec)- NotesLoansReceivable -> FixedHomeSide (legacyHomeSide spec)- MerchandiseInventory -> FixedHomeSide (legacyHomeSide spec)- GoodsInTransit -> FixedHomeSide (legacyHomeSide spec)- AdvancesPaid -> FixedHomeSide (legacyHomeSide spec)- PrepaidExpenses -> FixedHomeSide (legacyHomeSide spec)- AccruedRevenue -> FixedHomeSide (legacyHomeSide spec)- OtherReceivables -> FixedHomeSide (legacyHomeSide spec)- PaymentsOnBehalf -> FixedHomeSide (legacyHomeSide spec)- SuspensePayments -> FixedHomeSide (legacyHomeSide spec)- ConsumptionTaxPaid -> FixedHomeSide (legacyHomeSide spec)- PrepaidCorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)- Land -> FixedHomeSide (legacyHomeSide spec)- Fixtures -> FixedHomeSide (legacyHomeSide spec)- Patent -> FixedHomeSide (legacyHomeSide spec)- Trademark -> FixedHomeSide (legacyHomeSide spec)- Software -> FixedHomeSide (legacyHomeSide spec)- CashOverShort -> ContextDependentHomeSide- AccountsPayable -> FixedHomeSide (legacyHomeSide spec)- NotesPayable -> FixedHomeSide (legacyHomeSide spec)- ElectronicallyRecordedObligations -> FixedHomeSide (legacyHomeSide spec)- NotesLoansPayable -> FixedHomeSide (legacyHomeSide spec)- BankOverdraft -> FixedHomeSide (legacyHomeSide spec)- AdvancesReceived -> FixedHomeSide (legacyHomeSide spec)- UnearnedRevenue -> FixedHomeSide (legacyHomeSide spec)- AccruedExpenses -> FixedHomeSide (legacyHomeSide spec)- OtherPayables -> FixedHomeSide (legacyHomeSide spec)- DepositsReceived -> FixedHomeSide (legacyHomeSide spec)- SuspenseReceipts -> FixedHomeSide (legacyHomeSide spec)- ConsumptionTaxReceived -> FixedHomeSide (legacyHomeSide spec)- AccruedConsumptionTax -> FixedHomeSide (legacyHomeSide spec)- AccruedCorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)- UnpaidDividends -> FixedHomeSide (legacyHomeSide spec)- AllowanceForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)- AccumulatedDepreciation -> FixedHomeSide (legacyHomeSide spec)- LegalRetainedEarnings -> FixedHomeSide (legacyHomeSide spec)- CumulativeTranslationAdjustment -> FixedHomeSide (legacyHomeSide spec)- ProvisionForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)- BadDebtLoss -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfNotesReceivable -> FixedHomeSide (legacyHomeSide spec)- PaymentFees -> FixedHomeSide (legacyHomeSide spec)- MiscellaneousLoss -> FixedHomeSide (legacyHomeSide spec)- CorporateIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)- CommunicationExpenses -> FixedHomeSide (legacyHomeSide spec)- GainOnSalesOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)- RecoveryOfBadDebts -> FixedHomeSide (legacyHomeSide spec)- MiscellaneousIncome -> FixedHomeSide (legacyHomeSide spec)- ReversalOfAllowanceForDoubtfulAccounts -> FixedHomeSide (legacyHomeSide spec)- TimeDeposits -> FixedHomeSide (legacyHomeSide spec)- LoansReceivable -> FixedHomeSide (legacyHomeSide spec)- GiftCertificatesReceived -> FixedHomeSide (legacyHomeSide spec)- SecurityDepositsPaid -> FixedHomeSide (legacyHomeSide spec)- SuppliesOnHand -> FixedHomeSide (legacyHomeSide spec)- RawMaterials -> FixedHomeSide (legacyHomeSide spec)- ContractAssets -> FixedHomeSide (legacyHomeSide spec)- IncomeTaxesRefundReceivable -> FixedHomeSide (legacyHomeSide spec)- WorkInProcess -> FixedHomeSide (legacyHomeSide spec)- DeferredTaxAssets -> FixedHomeSide (legacyHomeSide spec)- LeasedAssets -> FixedHomeSide (legacyHomeSide spec)- ToolsAndInstruments -> FixedHomeSide (legacyHomeSide spec)- ConstructionInProgress -> FixedHomeSide (legacyHomeSide spec)- Goodwill -> FixedHomeSide (legacyHomeSide spec)- SoftwareInProgress -> FixedHomeSide (legacyHomeSide spec)- LongTermPrepaidExpenses -> FixedHomeSide (legacyHomeSide spec)- DishonoredNotesReceivable -> FixedHomeSide (legacyHomeSide spec)- PrepaidPensionCost -> FixedHomeSide (legacyHomeSide spec)- NetDefinedBenefitAsset -> FixedHomeSide (legacyHomeSide spec)- DepositsInSpecialAccounts -> FixedHomeSide (legacyHomeSide spec)- Structures -> FixedHomeSide (legacyHomeSide spec)- LeaseholdRights -> FixedHomeSide (legacyHomeSide spec)- NonOperatingNotesReceivable -> FixedHomeSide (legacyHomeSide spec)- NonOperatingElectronicallyRecordedReceivable -> FixedHomeSide (legacyHomeSide spec)- RefundLiabilities -> FixedHomeSide (legacyHomeSide spec)- NonOperatingNotesPayable -> FixedHomeSide (legacyHomeSide spec)- NonOperatingElectronicallyRecordedObligations -> FixedHomeSide (legacyHomeSide spec)- BonusesPayable -> FixedHomeSide (legacyHomeSide spec)- AllowanceForRepairs -> FixedHomeSide (legacyHomeSide spec)- AllowanceForProductWarranties -> FixedHomeSide (legacyHomeSide spec)- AllowanceForBonuses -> FixedHomeSide (legacyHomeSide spec)- DeferredTaxLiabilities -> FixedHomeSide (legacyHomeSide spec)- LeaseObligations -> FixedHomeSide (legacyHomeSide spec)- GuaranteeDepositsReceived -> FixedHomeSide (legacyHomeSide spec)- AllowanceForRetirementBenefits -> FixedHomeSide (legacyHomeSide spec)- LongTermOtherPayables -> FixedHomeSide (legacyHomeSide spec)- NetDefinedBenefitLiability -> FixedHomeSide (legacyHomeSide spec)- StockSubscriptionDeposits -> FixedHomeSide (legacyHomeSide spec)- LegalCapitalSurplus -> FixedHomeSide (legacyHomeSide spec)- OtherCapitalSurplus -> FixedHomeSide (legacyHomeSide spec)- DividendEqualizationReserve -> FixedHomeSide (legacyHomeSide spec)- RepairFundReserve -> FixedHomeSide (legacyHomeSide spec)- ConstructionFundReserve -> FixedHomeSide (legacyHomeSide spec)- GeneralReserve -> FixedHomeSide (legacyHomeSide spec)- ValuationDifferenceOnOtherSecurities -> FixedHomeSide (legacyHomeSide spec)- NonControllingInterests -> FixedHomeSide (legacyHomeSide spec)- CapitalSurplus -> FixedHomeSide (legacyHomeSide spec)- EarnedSurplus -> FixedHomeSide (legacyHomeSide spec)- ServiceRevenue -> FixedHomeSide (legacyHomeSide spec)- OperatingRevenue -> FixedHomeSide (legacyHomeSide spec)- GainOnSalesOfSecurities -> FixedHomeSide (legacyHomeSide spec)- GainOnValuationOfSecurities -> FixedHomeSide (legacyHomeSide spec)- DividendsReceived -> FixedHomeSide (legacyHomeSide spec)- InterestOnSecurities -> FixedHomeSide (legacyHomeSide spec)- GainOnSalesOfInvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)- InsuranceGain -> FixedHomeSide (legacyHomeSide spec)- GainOnBargainPurchase -> FixedHomeSide (legacyHomeSide spec)- ReversalOfAllowanceForRepairs -> FixedHomeSide (legacyHomeSide spec)- ReversalOfAllowanceForProductWarranties -> FixedHomeSide (legacyHomeSide spec)- GainOnDonationOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)- GainOnNationalSubsidies -> FixedHomeSide (legacyHomeSide spec)- GainOnConstructionGrants -> FixedHomeSide (legacyHomeSide spec)- LandRentReceived -> FixedHomeSide (legacyHomeSide spec)- SalesRebates -> FixedHomeSide (legacyHomeSide spec)- CostOfServices -> FixedHomeSide (legacyHomeSide spec)- OperatingExpenses -> FixedHomeSide (legacyHomeSide spec)- InventoryShrinkageLoss -> FixedHomeSide (legacyHomeSide spec)- LossOnValuationOfMerchandise -> FixedHomeSide (legacyHomeSide spec)- Bonuses -> FixedHomeSide (legacyHomeSide spec)- RetirementBenefitExpenses -> FixedHomeSide (legacyHomeSide spec)- ProvisionForRepairs -> FixedHomeSide (legacyHomeSide spec)- ProvisionForBonuses -> FixedHomeSide (legacyHomeSide spec)- ProvisionForProductWarranties -> FixedHomeSide (legacyHomeSide spec)- ResearchAndDevelopmentExpenses -> FixedHomeSide (legacyHomeSide spec)- AmortizationOfGoodwill -> FixedHomeSide (legacyHomeSide spec)- AmortizationOfSoftware -> FixedHomeSide (legacyHomeSide spec)- AmortizationOfPatents -> FixedHomeSide (legacyHomeSide spec)- LeaseExpenses -> FixedHomeSide (legacyHomeSide spec)- IncorporationExpenses -> FixedHomeSide (legacyHomeSide spec)- StockIssuanceCosts -> FixedHomeSide (legacyHomeSide spec)- BusinessCommencementExpenses -> FixedHomeSide (legacyHomeSide spec)- DevelopmentExpenses -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfElectronicallyRecordedReceivables -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfReceivables -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfSecurities -> FixedHomeSide (legacyHomeSide spec)- LossOnValuationOfSecurities -> FixedHomeSide (legacyHomeSide spec)- LossOnSalesOfInvestmentSecurities -> FixedHomeSide (legacyHomeSide spec)- LossOnFire -> FixedHomeSide (legacyHomeSide spec)- LossOnRetirementOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)- LossOnReductionOfFixedAssets -> FixedHomeSide (legacyHomeSide spec)- AdditionalIncomeTaxesForPriorPeriods -> FixedHomeSide (legacyHomeSide spec)- RefundOfIncomeTaxes -> FixedHomeSide (legacyHomeSide spec)- PurchaseRebates -> FixedHomeSide (legacyHomeSide spec)- WelfareExpenses -> FixedHomeSide (legacyHomeSide spec)- MaintenanceExpenses -> FixedHomeSide (legacyHomeSide spec)- StatutoryWelfareExpenses -> FixedHomeSide (legacyHomeSide spec)- LandRentPaid -> FixedHomeSide (legacyHomeSide spec)- InsuranceExpense -> FixedHomeSide (legacyHomeSide spec)- RepairsExpense -> FixedHomeSide (legacyHomeSide spec)- StorageExpenses -> FixedHomeSide (legacyHomeSide spec)- MembershipFees -> FixedHomeSide (legacyHomeSide spec)- IncomeSummary -> ContextDependentHomeSide- SuspenseAccount -> ContextDependentHomeSide- ForeignExchangeGains -> FixedHomeSide (legacyHomeSide spec)- ForeignExchangeLosses -> FixedHomeSide (legacyHomeSide spec)- ContraAccountForGuaranteeObligations -> FixedHomeSide (legacyHomeSide spec)- GuaranteeObligations -> FixedHomeSide (legacyHomeSide spec)- IncomeTaxesAdjustment -> FixedHomeSide (legacyHomeSide spec)- BranchCurrentAccount -> FixedHomeSide (legacyHomeSide spec)- HeadOfficeCurrentAccount -> FixedHomeSide (legacyHomeSide spec)- NetIncomeAttributableToNCI -> FixedHomeSide (legacyHomeSide spec)- NetLossAttributableToNCI -> FixedHomeSide (legacyHomeSide spec)- TradingSecurities -> FixedHomeSide (legacyHomeSide spec)- HeldToMaturityBonds -> FixedHomeSide (legacyHomeSide spec)- SubsidiaryStocks -> FixedHomeSide (legacyHomeSide spec)- AffiliateStocks -> FixedHomeSide (legacyHomeSide spec)- AvailableForSaleSecurities -> FixedHomeSide (legacyHomeSide spec)- ConsumptionTaxRefundReceivable -> FixedHomeSide (legacyHomeSide spec)- PropertyTaxPayable -> FixedHomeSide (legacyHomeSide spec)- DepositsReceivedFromOfficers -> FixedHomeSide (legacyHomeSide spec)- AccountTitle -> NoPostingSide--legacyHomeSide :: AccountSpec -> Side-legacyHomeSide spec- | asIsContra spec = reverseSide (divisionSide (asDivision spec))- | otherwise = divisionSide (asDivision spec)--divisionSide :: AccountDivision -> Side-divisionSide Assets = Debit-divisionSide Cost = Debit-divisionSide Equity = Credit-divisionSide Liability = Credit-divisionSide Revenue = Credit--reverseSide :: Side -> Side-reverseSide Debit = Credit-reverseSide Credit = Debit-reverseSide Side = Side--reportingFor :: AccountTitles -> ReportingEligibility-reportingFor title = case title of- Cash -> StatementEligible- Deposits -> StatementEligible- CurrentDeposits -> StatementEligible- Securities -> StatementEligible- InvestmentSecurities -> StatementEligible- InvestmentInAssociate -> StatementEligible- LongTermNationalBonds -> StatementEligible- ShortTermNationalBonds -> StatementEligible- Products -> StatementEligible- Machinery -> StatementEligible- Building -> StatementEligible- Vehicle -> StatementEligible- StockInvestment -> StatementEligible- EquipmentInvestment -> StatementEligible- LongTermLoansReceivable -> StatementEligible- AccountsReceivable -> StatementEligible- ShortTermLoansReceivable -> StatementEligible- ReserveDepositReceivable -> StatementEligible- Gold -> StatementEligible- GovernmentService -> StatementEligible- CapitalStock -> StatementEligible- RetainedEarnings -> StatementEligible- LongTermLoansPayable -> StatementEligible- ShortTermLoansPayable -> StatementEligible- LoansPayable -> StatementEligible- ReserveForDepreciation -> StatementEligible- DepositPayable -> StatementEligible- LongTermNationalBondsPayable -> StatementEligible- ShortTermNationalBondsPayable -> StatementEligible- ReserveDepositPayable -> StatementEligible- CentralBankNotePayable -> StatementEligible- Depreciation -> StatementEligible- AmortizationExpense -> StatementEligible- SalesCost -> StatementEligible- BusinessTrip -> StatementEligible- Commutation -> StatementEligible- UtilitiesExpense -> StatementEligible- RentExpense -> StatementEligible- AdvertisingExpense -> StatementEligible- DeliveryExpenses -> StatementEligible- SuppliesExpenses -> StatementEligible- MiscellaneousExpenses -> StatementEligible- EntertainmentExpenses -> StatementEligible- MeetingExpenses -> StatementEligible- NewspaperBooksExpenses -> StatementEligible- WageExpenditure -> StatementEligible- InterestExpense -> StatementEligible- TaxesExpense -> StatementEligible- ConsumptionExpenditure -> StatementEligible- SubsidyExpense -> StatementEligible- CentralBankPaymentExpense -> StatementEligible- Purchases -> StatementEligible- NetIncome -> DerivedPresentation- ValueAdded -> StatementEligible- SubsidyIncome -> StatementEligible- NationalBondInterestEarned -> StatementEligible- DepositInterestEarned -> StatementEligible- GrossProfit -> DerivedPresentation- OrdinaryProfit -> DerivedPresentation- InterestEarned -> StatementEligible- ReceiptFee -> StatementEligible- RentalIncome -> StatementEligible- WageEarned -> StatementEligible- TaxesRevenue -> StatementEligible- CentralBankPaymentIncome -> StatementEligible- Sales -> StatementEligible- EquityInEarningsOfInvestee -> ContextualPresentation- NetLoss -> DerivedPresentation- PettyCash -> StatementEligible- NotesReceivable -> StatementEligible- ElectronicallyRecordedReceivable -> StatementEligible- CreditCardReceivable -> StatementEligible- NotesLoansReceivable -> StatementEligible- MerchandiseInventory -> StatementEligible- GoodsInTransit -> StatementEligible- AdvancesPaid -> StatementEligible- PrepaidExpenses -> StatementEligible- AccruedRevenue -> StatementEligible- OtherReceivables -> StatementEligible- PaymentsOnBehalf -> StatementEligible- SuspensePayments -> ContextualPresentation- ConsumptionTaxPaid -> StatementEligible- PrepaidCorporateIncomeTaxes -> StatementEligible- Land -> StatementEligible- Fixtures -> StatementEligible- Patent -> StatementEligible- Trademark -> StatementEligible- Software -> StatementEligible- CashOverShort -> NotPresented- AccountsPayable -> StatementEligible- NotesPayable -> StatementEligible- ElectronicallyRecordedObligations -> StatementEligible- NotesLoansPayable -> StatementEligible- BankOverdraft -> StatementEligible- AdvancesReceived -> StatementEligible- UnearnedRevenue -> StatementEligible- AccruedExpenses -> StatementEligible- OtherPayables -> StatementEligible- DepositsReceived -> StatementEligible- SuspenseReceipts -> ContextualPresentation- ConsumptionTaxReceived -> StatementEligible- AccruedConsumptionTax -> StatementEligible- AccruedCorporateIncomeTaxes -> StatementEligible- UnpaidDividends -> StatementEligible- AllowanceForDoubtfulAccounts -> StatementEligible- AccumulatedDepreciation -> StatementEligible- LegalRetainedEarnings -> StatementEligible- CumulativeTranslationAdjustment -> ContextualPresentation- ProvisionForDoubtfulAccounts -> StatementEligible- BadDebtLoss -> StatementEligible- LossOnSalesOfFixedAssets -> StatementEligible- LossOnSalesOfNotesReceivable -> StatementEligible- PaymentFees -> StatementEligible- MiscellaneousLoss -> StatementEligible- CorporateIncomeTaxes -> StatementEligible- CommunicationExpenses -> StatementEligible- GainOnSalesOfFixedAssets -> StatementEligible- RecoveryOfBadDebts -> StatementEligible- MiscellaneousIncome -> StatementEligible- ReversalOfAllowanceForDoubtfulAccounts -> StatementEligible- TimeDeposits -> StatementEligible- LoansReceivable -> StatementEligible- GiftCertificatesReceived -> StatementEligible- SecurityDepositsPaid -> StatementEligible- SuppliesOnHand -> StatementEligible- RawMaterials -> StatementEligible- ContractAssets -> StatementEligible- IncomeTaxesRefundReceivable -> StatementEligible- WorkInProcess -> StatementEligible- DeferredTaxAssets -> StatementEligible- LeasedAssets -> StatementEligible- ToolsAndInstruments -> StatementEligible- ConstructionInProgress -> StatementEligible- Goodwill -> StatementEligible- SoftwareInProgress -> StatementEligible- LongTermPrepaidExpenses -> StatementEligible- DishonoredNotesReceivable -> StatementEligible- PrepaidPensionCost -> StatementEligible- NetDefinedBenefitAsset -> StatementEligible- DepositsInSpecialAccounts -> StatementEligible- Structures -> StatementEligible- LeaseholdRights -> StatementEligible- NonOperatingNotesReceivable -> StatementEligible- NonOperatingElectronicallyRecordedReceivable -> StatementEligible- RefundLiabilities -> StatementEligible- NonOperatingNotesPayable -> StatementEligible- NonOperatingElectronicallyRecordedObligations -> StatementEligible- BonusesPayable -> StatementEligible- AllowanceForRepairs -> StatementEligible- AllowanceForProductWarranties -> StatementEligible- AllowanceForBonuses -> StatementEligible- DeferredTaxLiabilities -> StatementEligible- LeaseObligations -> StatementEligible- GuaranteeDepositsReceived -> StatementEligible- AllowanceForRetirementBenefits -> StatementEligible- LongTermOtherPayables -> StatementEligible- NetDefinedBenefitLiability -> StatementEligible- StockSubscriptionDeposits -> StatementEligible- LegalCapitalSurplus -> StatementEligible- OtherCapitalSurplus -> StatementEligible- DividendEqualizationReserve -> StatementEligible- RepairFundReserve -> StatementEligible- ConstructionFundReserve -> StatementEligible- GeneralReserve -> StatementEligible- ValuationDifferenceOnOtherSecurities -> StatementEligible- NonControllingInterests -> ContextualPresentation- CapitalSurplus -> StatementEligible- EarnedSurplus -> StatementEligible- ServiceRevenue -> StatementEligible- OperatingRevenue -> StatementEligible- GainOnSalesOfSecurities -> StatementEligible- GainOnValuationOfSecurities -> StatementEligible- DividendsReceived -> StatementEligible- InterestOnSecurities -> StatementEligible- GainOnSalesOfInvestmentSecurities -> StatementEligible- InsuranceGain -> StatementEligible- GainOnBargainPurchase -> StatementEligible- ReversalOfAllowanceForRepairs -> StatementEligible- ReversalOfAllowanceForProductWarranties -> StatementEligible- GainOnDonationOfFixedAssets -> StatementEligible- GainOnNationalSubsidies -> StatementEligible- GainOnConstructionGrants -> StatementEligible- LandRentReceived -> StatementEligible- SalesRebates -> StatementEligible- CostOfServices -> StatementEligible- OperatingExpenses -> StatementEligible- InventoryShrinkageLoss -> StatementEligible- LossOnValuationOfMerchandise -> StatementEligible- Bonuses -> StatementEligible- RetirementBenefitExpenses -> StatementEligible- ProvisionForRepairs -> StatementEligible- ProvisionForBonuses -> StatementEligible- ProvisionForProductWarranties -> StatementEligible- ResearchAndDevelopmentExpenses -> StatementEligible- AmortizationOfGoodwill -> StatementEligible- AmortizationOfSoftware -> StatementEligible- AmortizationOfPatents -> StatementEligible- LeaseExpenses -> StatementEligible- IncorporationExpenses -> StatementEligible- StockIssuanceCosts -> StatementEligible- BusinessCommencementExpenses -> StatementEligible- DevelopmentExpenses -> StatementEligible- LossOnSalesOfElectronicallyRecordedReceivables -> StatementEligible- LossOnSalesOfReceivables -> StatementEligible- LossOnSalesOfSecurities -> StatementEligible- LossOnValuationOfSecurities -> StatementEligible- LossOnSalesOfInvestmentSecurities -> StatementEligible- LossOnFire -> StatementEligible- LossOnRetirementOfFixedAssets -> StatementEligible- LossOnReductionOfFixedAssets -> StatementEligible- AdditionalIncomeTaxesForPriorPeriods -> StatementEligible- RefundOfIncomeTaxes -> StatementEligible- PurchaseRebates -> StatementEligible- WelfareExpenses -> StatementEligible- MaintenanceExpenses -> StatementEligible- StatutoryWelfareExpenses -> StatementEligible- LandRentPaid -> StatementEligible- InsuranceExpense -> StatementEligible- RepairsExpense -> StatementEligible- StorageExpenses -> StatementEligible- MembershipFees -> StatementEligible- IncomeSummary -> NotPresented- SuspenseAccount -> ContextualPresentation- ForeignExchangeGains -> StatementEligible- ForeignExchangeLosses -> StatementEligible- ContraAccountForGuaranteeObligations -> StatementEligible- GuaranteeObligations -> StatementEligible- IncomeTaxesAdjustment -> StatementEligible- BranchCurrentAccount -> ContextualPresentation- HeadOfficeCurrentAccount -> ContextualPresentation- NetIncomeAttributableToNCI -> ContextualPresentation- NetLossAttributableToNCI -> ContextualPresentation- TradingSecurities -> StatementEligible- HeldToMaturityBonds -> StatementEligible- SubsidiaryStocks -> StatementEligible- AffiliateStocks -> StatementEligible- AvailableForSaleSecurities -> StatementEligible- ConsumptionTaxRefundReceivable -> StatementEligible- PropertyTaxPayable -> StatementEligible- DepositsReceivedFromOfficers -> StatementEligible- AccountTitle -> NotPresented---- | Every accepted non-canonical alias for an account. The JCCI overlay is--- generated from the frozen 2022 standard/permitted account-name fixture;--- shared permitted names intentionally remain shared so parsing reports an--- explicit ambiguity instead of silently choosing one account.-accountAliases :: AccountTitles -> [Text]-accountAliases title = L.nub (baseAliases <> jcciAliases title)- where- baseAliases = maybe [] asAliases (accountSpec title)---- | Look up metadata for an account title. The wildcard has no metadata.------ Complexity: O(1)-{-# INLINE accountSpec #-}-accountSpec :: AccountTitles -> Maybe AccountSpec-accountSpec Cash = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Cash"- , asNameJa = "現金"- , asLabelJa = "現金"- , asDescription = "Asset: Cash (現金)"- , asAliases = ["現金"]- }-accountSpec Deposits = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Savings deposits"- , asNameJa = "普通預金"- , asLabelJa = "普通預金"- , asDescription = "Asset: Savings deposits (普通預金)"- , asAliases = ["普通預金"]- }-accountSpec CurrentDeposits = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Current deposits"- , asNameJa = "当座預金"- , asLabelJa = "当座預金"- , asDescription = "Asset: Current deposits (当座預金)"- , asAliases = ["当座預金"]- }-accountSpec Securities = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Securities"- , asNameJa = "有価証券"- , asLabelJa = "有価証券"- , asDescription = "Asset: Securities (有価証券)"- , asAliases = ["有価証券"]- }-accountSpec InvestmentSecurities = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Investment securities"- , asNameJa = "投資有価証券"- , asLabelJa = "投資有価証券"- , asDescription = "Asset: Investment securities (投資有価証券)"- , asAliases = ["投資有価証券"]- }-accountSpec InvestmentInAssociate = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Investment in associate"- , asNameJa = "関係会社株式"- , asLabelJa = "関係会社株式"- , asDescription = "Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定)."- , asAliases = []- }-accountSpec LongTermNationalBonds = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term national bonds"- , asNameJa = "長期国債"- , asLabelJa = "長期国債"- , asDescription = "Asset: Long-term national bonds (長期国債)"- , asAliases = ["長期国債"]- }-accountSpec ShortTermNationalBonds = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Short-term national bonds"- , asNameJa = "短期国債"- , asLabelJa = "短期国債"- , asDescription = "Asset: Short-term national bonds (短期国債)"- , asAliases = ["短期国債"]- }-accountSpec Products = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Products"- , asNameJa = "商品"- , asLabelJa = "商品"- , asDescription = "Asset: Products (商品)。分記法用。3 分法 (仕入\\/売上\\/繰越商品) では 'MerchandiseInventory' を使う"- , asAliases = ["商品"]- }-accountSpec Machinery = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Machinery and equipment"- , asNameJa = "機械装置"- , asLabelJa = "機械装置"- , asDescription = "Asset: Machinery and equipment (機械装置)"- , asAliases = ["機械装置"]- }-accountSpec Building = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Real estate"- , asNameJa = "建物"- , asLabelJa = "建物"- , asDescription = "Asset: Real estate (建物)"- , asAliases = ["建物"]- }-accountSpec Vehicle = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Vehicles"- , asNameJa = "車両運搬具"- , asLabelJa = "車両運搬具"- , asDescription = "Asset: Vehicles (車両運搬具)"- , asAliases = ["車両運搬具"]- }-accountSpec StockInvestment = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Stock investment"- , asNameJa = "株式投資"- , asLabelJa = "株式投資"- , asDescription = "Asset: Stock investment (株式投資)"- , asAliases = ["株式投資"]- }-accountSpec EquipmentInvestment = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Equipment investment"- , asNameJa = "設備投資"- , asLabelJa = "設備投資"- , asDescription = "Asset: Equipment investment (設備投資)"- , asAliases = ["設備投資"]- }-accountSpec LongTermLoansReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term loans receivable"- , asNameJa = "長期貸付金"- , asLabelJa = "長期貸付金"- , asDescription = "Asset: Long-term loans receivable (長期貸付金)"- , asAliases = ["長期貸付金"]- }-accountSpec AccountsReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accounts receivable"- , asNameJa = "売掛金"- , asLabelJa = "売掛金"- , asDescription = "Asset: Accounts receivable (売掛金)"- , asAliases = ["売掛金", "a/r", "accounts receivable"]- }-accountSpec ShortTermLoansReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Short-term loans receivable"- , asNameJa = "短期貸付金"- , asLabelJa = "短期貸付金"- , asDescription = "Asset: Short-term loans receivable (短期貸付金)"- , asAliases = ["短期貸付金"]- }-accountSpec ReserveDepositReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Reserve deposits, asset side"- , asNameJa = "準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\\/マクロ系"- , asLabelJa = "準備預金"- , asDescription = "Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\\/マクロ系)"- , asAliases = ["準備預金"]- }-accountSpec Gold = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Gold"- , asNameJa = "金"- , asLabelJa = "金"- , asDescription = "Asset: Gold (金)"- , asAliases = ["金"]- }-accountSpec GovernmentService = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Government service"- , asNameJa = "政府サービス。SNA\\/マクロ系"- , asLabelJa = "政府サービス"- , asDescription = "Asset: Government service (政府サービス。SNA\\/マクロ系)"- , asAliases = ["政府サービス"]- }-accountSpec CapitalStock = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Capital stock"- , asNameJa = "資本金"- , asLabelJa = "資本金"- , asDescription = "Equity: Capital stock (資本金)"- , asAliases = ["資本金"]- }-accountSpec RetainedEarnings = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Retained earnings"- , asNameJa = "繰越利益剰余金"- , asLabelJa = "繰越利益剰余金"- , asDescription = "Equity: Retained earnings (繰越利益剰余金)"- , asAliases = ["繰越利益剰余金"]- }-accountSpec LongTermLoansPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term loans payable"- , asNameJa = "長期借入金"- , asLabelJa = "長期借入金"- , asDescription = "Liability: Long-term loans payable (長期借入金)"- , asAliases = ["長期借入金"]- }-accountSpec ShortTermLoansPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Short-term loans payable"- , asNameJa = "短期借入金"- , asLabelJa = "短期借入金"- , asDescription = "Liability: Short-term loans payable (短期借入金)"- , asAliases = ["短期借入金"]- }-accountSpec LoansPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Loans payable"- , asNameJa = "借入金"- , asLabelJa = "借入金"- , asDescription = "Liability: Loans payable (借入金)"- , asAliases = ["借入金"]- }-accountSpec ReserveForDepreciation = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Reserve for depreciation"- , asNameJa = "減価償却引当金 — SNA\\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額"- , asLabelJa = "減価償却引当金"- , asDescription = "Liability: Reserve for depreciation (減価償却引当金 — SNA\\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)"- , asAliases = ["減価償却引当金"]- }-accountSpec DepositPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Deposits accepted"- , asNameJa = "受入預金 — 銀行側の負債としての預金。SNA\\/マクロ系。従業員等からの預り金は 'DepositsReceived'"- , asLabelJa = "受入預金"- , asDescription = "Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\\/マクロ系。従業員等からの預り金は 'DepositsReceived')"- , asAliases = ["受入預金"]- }-accountSpec LongTermNationalBondsPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term national bonds payable"- , asNameJa = "長期国債, 発行側"- , asLabelJa = "長期国債"- , asDescription = "Liability: Long-term national bonds payable (長期国債, 発行側)"- , asAliases = ["長期国債"]- }-accountSpec ShortTermNationalBondsPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Short-term national bonds payable"- , asNameJa = "短期国債, 発行側"- , asLabelJa = "短期国債"- , asDescription = "Liability: Short-term national bonds payable (短期国債, 発行側)"- , asAliases = ["短期国債"]- }-accountSpec ReserveDepositPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Reserve deposits, liability side"- , asNameJa = "準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\\/マクロ系"- , asLabelJa = "準備預金"- , asDescription = "Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)"- , asAliases = ["準備預金"]- }-accountSpec CentralBankNotePayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Central bank notes"- , asNameJa = "発行銀行券。SNA\\/マクロ系"- , asLabelJa = "発行銀行券"- , asDescription = "Liability: Central bank notes (発行銀行券。SNA\\/マクロ系)"- , asAliases = ["発行銀行券"]- }-accountSpec Depreciation = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Depreciation"- , asNameJa = "減価償却費"- , asLabelJa = "減価償却費"- , asDescription = "Expense: Depreciation (減価償却費)"- , asAliases = ["減価償却費"]- }-accountSpec AmortizationExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Amortization expense for intangibles"- , asNameJa = "無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費"- , asLabelJa = "無形固定資産償却費"- , asDescription = "Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)"- , asAliases = []- }-accountSpec SalesCost = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Cost of sales"- , asNameJa = "売上原価"- , asLabelJa = "売上原価"- , asDescription = "Expense: Cost of sales (売上原価)"- , asAliases = ["売上原価", "cogs", "cost of sales"]- }-accountSpec BusinessTrip = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Travel and transportation"- , asNameJa = "旅費交通費"- , asLabelJa = "旅費交通費"- , asDescription = "Expense: Travel and transportation (旅費交通費)"- , asAliases = ["旅費交通費"]- }-accountSpec Commutation = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Communication"- , asNameJa = "通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う"- , asLabelJa = "通信費"- , asDescription = "Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)"- , asAliases = ["通信費"]- }-accountSpec UtilitiesExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Utilities"- , asNameJa = "水道光熱費"- , asLabelJa = "水道光熱費"- , asDescription = "Expense: Utilities (水道光熱費)"- , asAliases = ["水道光熱費"]- }-accountSpec RentExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Rent"- , asNameJa = "支払家賃"- , asLabelJa = "支払家賃"- , asDescription = "Expense: Rent (支払家賃)"- , asAliases = ["支払家賃"]- }-accountSpec AdvertisingExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Advertising"- , asNameJa = "広告宣伝費"- , asLabelJa = "広告宣伝費"- , asDescription = "Expense: Advertising (広告宣伝費)"- , asAliases = ["広告宣伝費"]- }-accountSpec DeliveryExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Delivery"- , asNameJa = "発送費"- , asLabelJa = "発送費"- , asDescription = "Expense: Delivery (発送費)"- , asAliases = ["発送費"]- }-accountSpec SuppliesExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Supplies"- , asNameJa = "消耗品費"- , asLabelJa = "消耗品費"- , asDescription = "Expense: Supplies (消耗品費)"- , asAliases = ["消耗品費"]- }-accountSpec MiscellaneousExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Miscellaneous"- , asNameJa = "雑費"- , asLabelJa = "雑費"- , asDescription = "Expense: Miscellaneous (雑費)"- , asAliases = ["雑費"]- }-accountSpec EntertainmentExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Entertainment expenses"- , asNameJa = "交際費"- , asLabelJa = "交際費"- , asDescription = "Expense: Entertainment expenses (交際費)"- , asAliases = ["交際費", "接待交際費"]- }-accountSpec MeetingExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Meeting expenses"- , asNameJa = "会議費"- , asLabelJa = "会議費"- , asDescription = "Expense: Meeting expenses (会議費)"- , asAliases = ["会議費"]- }-accountSpec NewspaperBooksExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Newspapers and books"- , asNameJa = "新聞図書費"- , asLabelJa = "新聞図書費"- , asDescription = "Expense: Newspapers and books (新聞図書費)"- , asAliases = ["新聞図書費"]- }-accountSpec WageExpenditure = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Wages"- , asNameJa = "給料"- , asLabelJa = "給料"- , asDescription = "Expense: Wages (給料)"- , asAliases = ["給料"]- }-accountSpec InterestExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Interest expense"- , asNameJa = "支払利息"- , asLabelJa = "支払利息"- , asDescription = "Expense: Interest expense (支払利息)"- , asAliases = ["支払利息"]- }-accountSpec TaxesExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Taxes"- , asNameJa = "租税公課"- , asLabelJa = "租税公課"- , asDescription = "Expense: Taxes (租税公課)"- , asAliases = ["租税公課"]- }-accountSpec ConsumptionExpenditure = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Consumption expenditure"- , asNameJa = "消費支出。SNA\\/マクロ系"- , asLabelJa = "消費支出"- , asDescription = "Expense: Consumption expenditure (消費支出。SNA\\/マクロ系)"- , asAliases = ["消費支出"]- }-accountSpec SubsidyExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Subsidy expenditure"- , asNameJa = "補助金支出。SNA\\/マクロ系"- , asLabelJa = "補助金支出"- , asDescription = "Expense: Subsidy expenditure (補助金支出。SNA\\/マクロ系)"- , asAliases = ["補助金支出"]- }-accountSpec CentralBankPaymentExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Central bank payment to treasury"- , asNameJa = "国庫納付金支出。SNA\\/マクロ系"- , asLabelJa = "国庫納付金支出"- , asDescription = "Expense: Central bank payment to treasury (国庫納付金支出。SNA\\/マクロ系)"- , asAliases = ["国庫納付金支出"]- }-accountSpec Purchases = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Purchases"- , asNameJa = "仕入"- , asLabelJa = "仕入"- , asDescription = "Expense: Purchases (仕入)"- , asAliases = ["仕入"]- }-accountSpec NetIncome = Just AccountSpec- { asDivision = Cost- , asClosing = NoClose- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Net income"- , asNameJa = "当期純利益 — 決算振替用。借方側に立つため Expense 区分"- , asLabelJa = "当期純利益"- , asDescription = "Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)"- , asAliases = ["当期純利益"]- }-accountSpec ValueAdded = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Value added"- , asNameJa = "付加価値。SNA\\/マクロ系"- , asLabelJa = "付加価値"- , asDescription = "Revenue: Value added (付加価値。SNA\\/マクロ系)"- , asAliases = ["付加価値"]- }-accountSpec SubsidyIncome = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Subsidy income"- , asNameJa = "補助金収入。SNA\\/マクロ系"- , asLabelJa = "補助金収入"- , asDescription = "Revenue: Subsidy income (補助金収入。SNA\\/マクロ系)"- , asAliases = ["補助金収入"]- }-accountSpec NationalBondInterestEarned = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "National bond interest earned"- , asNameJa = "国債利息収入"- , asLabelJa = "国債利息収入"- , asDescription = "Revenue: National bond interest earned (国債利息収入)"- , asAliases = ["国債利息収入"]- }-accountSpec DepositInterestEarned = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Deposit interest earned"- , asNameJa = "預金利息収入"- , asLabelJa = "預金利息収入"- , asDescription = "Revenue: Deposit interest earned (預金利息収入)"- , asAliases = ["預金利息収入"]- }-accountSpec GrossProfit = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gross profit"- , asNameJa = "売上総利益 — 決算振替用"- , asLabelJa = "売上総利益"- , asDescription = "Revenue: Gross profit (売上総利益 — 決算振替用)"- , asAliases = ["売上総利益"]- }-accountSpec OrdinaryProfit = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Ordinary profit"- , asNameJa = "経常利益 — 決算振替用"- , asLabelJa = "経常利益"- , asDescription = "Revenue: Ordinary profit (経常利益 — 決算振替用)"- , asAliases = ["経常利益"]- }-accountSpec InterestEarned = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Interest earned"- , asNameJa = "受取利息"- , asLabelJa = "受取利息"- , asDescription = "Revenue: Interest earned (受取利息)"- , asAliases = ["受取利息"]- }-accountSpec ReceiptFee = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Receipt fee"- , asNameJa = "受取手数料。支払側は 'PaymentFees'"- , asLabelJa = "受取手数料"- , asDescription = "Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')"- , asAliases = ["受取手数料"]- }-accountSpec RentalIncome = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Rental income"- , asNameJa = "受取家賃"- , asLabelJa = "受取家賃"- , asDescription = "Revenue: Rental income (受取家賃)"- , asAliases = ["受取家賃"]- }-accountSpec WageEarned = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Wage income"- , asNameJa = "賃金収入。SNA\\/マクロ系"- , asLabelJa = "賃金収入"- , asDescription = "Revenue: Wage income (賃金収入。SNA\\/マクロ系)"- , asAliases = ["賃金収入"]- }-accountSpec TaxesRevenue = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Tax revenue"- , asNameJa = "租税収入。SNA\\/マクロ系"- , asLabelJa = "租税収入"- , asDescription = "Revenue: Tax revenue (租税収入。SNA\\/マクロ系)"- , asAliases = ["租税収入"]- }-accountSpec CentralBankPaymentIncome = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Central bank payment to treasury"- , asNameJa = "国庫納付金収入。SNA\\/マクロ系"- , asLabelJa = "国庫納付金収入"- , asDescription = "Revenue: Central bank payment to treasury (国庫納付金収入。SNA\\/マクロ系)"- , asAliases = ["国庫納付金収入"]- }-accountSpec Sales = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Sales"- , asNameJa = "売上"- , asLabelJa = "売上"- , asDescription = "Revenue: Sales (売上)"- , asAliases = ["売上"]- }-accountSpec EquityInEarningsOfInvestee = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Equity in earnings of investee"- , asNameJa = "持分法による投資利益"- , asLabelJa = "持分法による投資利益"- , asDescription = "Revenue: Equity in earnings of investee (持分法による投資利益). Recognized under the equity method."- , asAliases = []- }-accountSpec NetLoss = Just AccountSpec- { asDivision = Revenue- , asClosing = NoClose- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Net loss"- , asNameJa = "当期純損失 — 決算振替用。貸方側に立つため Revenue 区分"- , asLabelJa = "当期純損失"- , asDescription = "Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)"- , asAliases = ["当期純損失"]- }-accountSpec PettyCash = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Petty cash"- , asNameJa = "小口現金"- , asLabelJa = "小口現金"- , asDescription = "Asset: Petty cash (小口現金)"- , asAliases = ["小口現金"]- }-accountSpec NotesReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Notes receivable"- , asNameJa = "受取手形"- , asLabelJa = "受取手形"- , asDescription = "Asset: Notes receivable (受取手形)"- , asAliases = ["受取手形"]- }-accountSpec ElectronicallyRecordedReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Electronically recorded monetary claims"- , asNameJa = "電子記録債権"- , asLabelJa = "電子記録債権"- , asDescription = "Asset: Electronically recorded monetary claims (電子記録債権)"- , asAliases = ["電子記録債権"]- }-accountSpec CreditCardReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Credit card receivable"- , asNameJa = "クレジット売掛金"- , asLabelJa = "クレジット売掛金"- , asDescription = "Asset: Credit card receivable (クレジット売掛金)"- , asAliases = ["クレジット売掛金"]- }-accountSpec NotesLoansReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Loans receivable on notes"- , asNameJa = "手形貸付金"- , asLabelJa = "手形貸付金"- , asDescription = "Asset: Loans receivable on notes (手形貸付金)"- , asAliases = ["手形貸付金"]- }-accountSpec MerchandiseInventory = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Merchandise inventory"- , asNameJa = "繰越商品"- , asLabelJa = "繰越商品"- , asDescription = "Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\\/Sales\\/MerchandiseInventory). For the perpetual\\/specific-identification method (分記法) use 'Products' instead."- , asAliases = ["繰越商品"]- }-accountSpec GoodsInTransit = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Goods in transit"- , asNameJa = "未着品"- , asLabelJa = "未着品"- , asDescription = "Assets: Goods in transit (未着品)"- , asAliases = ["未着品"]- }-accountSpec AdvancesPaid = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Advances paid"- , asNameJa = "前払金"- , asLabelJa = "前払金"- , asDescription = "Asset: Advances paid (前払金)"- , asAliases = ["前払金"]- }-accountSpec PrepaidExpenses = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Prepaid expenses"- , asNameJa = "前払費用"- , asLabelJa = "前払費用"- , asDescription = "Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)"- , asAliases = ["前払費用"]- }-accountSpec AccruedRevenue = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accrued revenue"- , asNameJa = "未収収益"- , asLabelJa = "未収収益"- , asDescription = "Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)"- , asAliases = ["未収収益"]- }-accountSpec OtherReceivables = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Other receivables"- , asNameJa = "未収入金"- , asLabelJa = "未収入金"- , asDescription = "Asset: Other receivables (未収入金)"- , asAliases = ["未収入金"]- }-accountSpec PaymentsOnBehalf = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Payments made on behalf"- , asNameJa = "立替金"- , asLabelJa = "立替金"- , asDescription = "Asset: Payments made on behalf (立替金)"- , asAliases = ["立替金"]- }-accountSpec SuspensePayments = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Suspense payments"- , asNameJa = "仮払金"- , asLabelJa = "仮払金"- , asDescription = "Asset: Suspense payments (仮払金)"- , asAliases = ["仮払金"]- }-accountSpec ConsumptionTaxPaid = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Consumption tax paid"- , asNameJa = "仮払消費税"- , asLabelJa = "仮払消費税"- , asDescription = "Asset: Consumption tax paid (仮払消費税)"- , asAliases = ["仮払消費税"]- }-accountSpec PrepaidCorporateIncomeTaxes = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Prepaid corporate income taxes"- , asNameJa = "仮払法人税等"- , asLabelJa = "仮払法人税等"- , asDescription = "Asset: Prepaid corporate income taxes (仮払法人税等)"- , asAliases = ["仮払法人税等"]- }-accountSpec Land = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Land"- , asNameJa = "土地"- , asLabelJa = "土地"- , asDescription = "Asset: Land (土地)"- , asAliases = ["土地"]- }-accountSpec Fixtures = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Fixtures and equipment"- , asNameJa = "備品"- , asLabelJa = "備品"- , asDescription = "Asset: Fixtures and equipment (備品)"- , asAliases = ["備品"]- }-accountSpec Patent = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Patent"- , asNameJa = "特許権"- , asLabelJa = "特許権"- , asDescription = "Asset: Patent (特許権)"- , asAliases = ["特許権"]- }-accountSpec Trademark = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Trademark"- , asNameJa = "商標権"- , asLabelJa = "商標権"- , asDescription = "Asset: Trademark (商標権)"- , asAliases = ["商標権"]- }-accountSpec Software = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Software"- , asNameJa = "ソフトウェア"- , asLabelJa = "ソフトウェア"- , asDescription = "Asset: Software (ソフトウェア)"- , asAliases = ["ソフトウェア"]- }-accountSpec CashOverShort = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Cash over and short"- , asNameJa = "現金過不足"- , asLabelJa = "現金過不足"- , asDescription = "Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\\/MiscellaneousLoss"- , asAliases = ["現金過不足"]- }-accountSpec AccountsPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accounts payable"- , asNameJa = "買掛金"- , asLabelJa = "買掛金"- , asDescription = "Liability: Accounts payable (買掛金)"- , asAliases = ["買掛金", "a/p", "accounts payable"]- }-accountSpec NotesPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Notes payable"- , asNameJa = "支払手形"- , asLabelJa = "支払手形"- , asDescription = "Liability: Notes payable (支払手形)"- , asAliases = ["支払手形"]- }-accountSpec ElectronicallyRecordedObligations = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Electronically recorded monetary obligations"- , asNameJa = "電子記録債務"- , asLabelJa = "電子記録債務"- , asDescription = "Liability: Electronically recorded monetary obligations (電子記録債務)"- , asAliases = ["電子記録債務"]- }-accountSpec NotesLoansPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Loans payable on notes"- , asNameJa = "手形借入金"- , asLabelJa = "手形借入金"- , asDescription = "Liability: Loans payable on notes (手形借入金)"- , asAliases = ["手形借入金"]- }-accountSpec BankOverdraft = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Bank overdraft"- , asNameJa = "当座借越"- , asLabelJa = "当座借越"- , asDescription = "Liability: Bank overdraft (当座借越)"- , asAliases = ["当座借越"]- }-accountSpec AdvancesReceived = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Advances received"- , asNameJa = "前受金"- , asLabelJa = "前受金"- , asDescription = "Liability: Advances received (前受金)"- , asAliases = ["前受金"]- }-accountSpec UnearnedRevenue = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Unearned revenue"- , asNameJa = "前受収益"- , asLabelJa = "前受収益"- , asDescription = "Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)"- , asAliases = ["前受収益"]- }-accountSpec AccruedExpenses = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accrued expenses"- , asNameJa = "未払費用"- , asLabelJa = "未払費用"- , asDescription = "Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)"- , asAliases = ["未払費用"]- }-accountSpec OtherPayables = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Other payables"- , asNameJa = "未払金"- , asLabelJa = "未払金"- , asDescription = "Liability: Other payables (未払金)"- , asAliases = ["未払金"]- }-accountSpec DepositsReceived = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Deposits received"- , asNameJa = "預り金"- , asLabelJa = "預り金"- , asDescription = "Liability: Deposits received (預り金)"- , asAliases = ["預り金"]- }-accountSpec SuspenseReceipts = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Suspense receipts"- , asNameJa = "仮受金"- , asLabelJa = "仮受金"- , asDescription = "Liability: Suspense receipts (仮受金)"- , asAliases = ["仮受金"]- }-accountSpec ConsumptionTaxReceived = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Consumption tax received"- , asNameJa = "仮受消費税"- , asLabelJa = "仮受消費税"- , asDescription = "Liability: Consumption tax received (仮受消費税)"- , asAliases = ["仮受消費税"]- }-accountSpec AccruedConsumptionTax = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accrued (unpaid) consumption tax"- , asNameJa = "未払消費税"- , asLabelJa = "未払消費税"- , asDescription = "Liability: Accrued (unpaid) consumption tax (未払消費税)"- , asAliases = ["未払消費税"]- }-accountSpec AccruedCorporateIncomeTaxes = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accrued (unpaid) corporate income taxes"- , asNameJa = "未払法人税等"- , asLabelJa = "未払法人税等"- , asDescription = "Liability: Accrued (unpaid) corporate income taxes (未払法人税等)"- , asAliases = ["未払法人税等"]- }-accountSpec UnpaidDividends = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Unpaid dividends"- , asNameJa = "未払配当金"- , asLabelJa = "未払配当金"- , asDescription = "Liability: Unpaid dividends (未払配当金)"- , asAliases = ["未払配当金"]- }-accountSpec AllowanceForDoubtfulAccounts = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = True- , asFixedCurrent = Current- , asNameEn = "Allowance for doubtful accounts"- , asNameJa = "貸倒引当金"- , asLabelJa = "貸倒引当金"- , asDescription = "Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\\/Not structure is intact. B\\/S deduction (net) presentation is the Write side's job."- , asAliases = ["貸倒引当金"]- }-accountSpec AccumulatedDepreciation = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = True- , asFixedCurrent = Fixed- , asNameEn = "Accumulated depreciation"- , asNameJa = "減価償却累計額"- , asLabelJa = "減価償却累計額"- , asDescription = "Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\\/macro-accounting name."- , asAliases = ["減価償却累計額"]- }-accountSpec LegalRetainedEarnings = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Legal (appropriated) retained earnings reserve"- , asNameJa = "利益準備金"- , asLabelJa = "利益準備金"- , asDescription = "Equity: Legal (appropriated) retained earnings reserve (利益準備金)"- , asAliases = ["利益準備金"]- }-accountSpec CumulativeTranslationAdjustment = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Cumulative translation adjustment"- , asNameJa = "為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目"- , asLabelJa = "為替換算調整勘定"- , asDescription = "Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)"- , asAliases = []- }-accountSpec ProvisionForDoubtfulAccounts = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Provision for doubtful accounts"- , asNameJa = "貸倒引当金繰入"- , asLabelJa = "貸倒引当金繰入"- , asDescription = "Cost: Provision for doubtful accounts (貸倒引当金繰入)"- , asAliases = ["貸倒引当金繰入"]- }-accountSpec BadDebtLoss = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Bad debt loss"- , asNameJa = "貸倒損失"- , asLabelJa = "貸倒損失"- , asDescription = "Cost: Bad debt loss (貸倒損失)"- , asAliases = ["貸倒損失"]- }-accountSpec LossOnSalesOfFixedAssets = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sales of fixed assets"- , asNameJa = "固定資産売却損"- , asLabelJa = "固定資産売却損"- , asDescription = "Cost: Loss on sales of fixed assets (固定資産売却損)"- , asAliases = ["固定資産売却損"]- }-accountSpec LossOnSalesOfNotesReceivable = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sales of notes receivable"- , asNameJa = "手形売却損"- , asLabelJa = "手形売却損"- , asDescription = "Cost: Loss on sales of notes receivable (手形売却損)"- , asAliases = ["手形売却損"]- }-accountSpec PaymentFees = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Payment fees / fees paid"- , asNameJa = "支払手数料"- , asLabelJa = "支払手数料"- , asDescription = "Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'"- , asAliases = ["支払手数料"]- }-accountSpec MiscellaneousLoss = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Miscellaneous loss"- , asNameJa = "雑損"- , asLabelJa = "雑損"- , asDescription = "Cost: Miscellaneous loss (雑損)"- , asAliases = ["雑損"]- }-accountSpec CorporateIncomeTaxes = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Corporate income taxes"- , asNameJa = "法人税等"- , asLabelJa = "法人税等"- , asDescription = "Cost: Corporate income taxes (法人税等)"- , asAliases = ["法人税等"]- }-accountSpec CommunicationExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Communication expenses"- , asNameJa = "通信費"- , asLabelJa = "通信費"- , asDescription = "Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also \"Communication\"); 'Commutation' is retained for backward compatibility."- , asAliases = ["通信費"]- }-accountSpec GainOnSalesOfFixedAssets = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on sales of fixed assets"- , asNameJa = "固定資産売却益"- , asLabelJa = "固定資産売却益"- , asDescription = "Revenue: Gain on sales of fixed assets (固定資産売却益)"- , asAliases = ["固定資産売却益"]- }-accountSpec RecoveryOfBadDebts = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Recovery of bad debts written off"- , asNameJa = "償却債権取立益"- , asLabelJa = "償却債権取立益"- , asDescription = "Revenue: Recovery of bad debts written off (償却債権取立益)"- , asAliases = ["償却債権取立益"]- }-accountSpec MiscellaneousIncome = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Miscellaneous income"- , asNameJa = "雑益"- , asLabelJa = "雑益"- , asDescription = "Revenue: Miscellaneous income (雑益)"- , asAliases = ["雑益"]- }-accountSpec ReversalOfAllowanceForDoubtfulAccounts = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reversal of allowance for doubtful accounts"- , asNameJa = "貸倒引当金戻入"- , asLabelJa = "貸倒引当金戻入"- , asDescription = "Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released)."- , asAliases = ["貸倒引当金戻入"]- }-accountSpec TimeDeposits = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Time deposits"- , asNameJa = "定期預金"- , asLabelJa = "定期預金"- , asDescription = "Assets: Time deposits (定期預金)"- , asAliases = ["定期預金"]- }-accountSpec LoansReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Loans receivable"- , asNameJa = "貸付金"- , asLabelJa = "貸付金"- , asDescription = "Assets: Loans receivable (貸付金)"- , asAliases = ["貸付金"]- }-accountSpec GiftCertificatesReceived = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Gift certificates received"- , asNameJa = "受取商品券"- , asLabelJa = "受取商品券"- , asDescription = "Assets: Gift certificates received (受取商品券)"- , asAliases = ["受取商品券"]- }-accountSpec SecurityDepositsPaid = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Guarantee deposits"- , asNameJa = "差入保証金"- , asLabelJa = "差入保証金"- , asDescription = "Assets: Guarantee deposits (差入保証金)"- , asAliases = ["差入保証金"]- }-accountSpec SuppliesOnHand = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Supplies"- , asNameJa = "貯蔵品"- , asLabelJa = "貯蔵品"- , asDescription = "Assets: Supplies (貯蔵品)"- , asAliases = ["貯蔵品"]- }-accountSpec RawMaterials = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Raw materials"- , asNameJa = "原材料"- , asLabelJa = "原材料"- , asDescription = "Assets: Raw materials (原材料)"- , asAliases = ["原材料", "材料"]- }-accountSpec ContractAssets = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Contract assets"- , asNameJa = "契約資産"- , asLabelJa = "契約資産"- , asDescription = "Assets: Contract assets (契約資産)"- , asAliases = ["契約資産"]- }-accountSpec IncomeTaxesRefundReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Income taxes refund receivable"- , asNameJa = "未収還付法人税等"- , asLabelJa = "未収還付法人税等"- , asDescription = "Assets: Income taxes refund receivable (未収還付法人税等)"- , asAliases = ["未収還付法人税等"]- }-accountSpec WorkInProcess = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Work in process"- , asNameJa = "仕掛品"- , asLabelJa = "仕掛品"- , asDescription = "Assets: Work in process (仕掛品)"- , asAliases = ["仕掛品"]- }-accountSpec DeferredTaxAssets = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Deferred tax assets"- , asNameJa = "繰延税金資産"- , asLabelJa = "繰延税金資産"- , asDescription = "Assets: Deferred tax assets (繰延税金資産)"- , asAliases = ["繰延税金資産"]- }-accountSpec LeasedAssets = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Leased assets"- , asNameJa = "リース資産"- , asLabelJa = "リース資産"- , asDescription = "Assets: Leased assets (リース資産)"- , asAliases = ["リース資産"]- }-accountSpec ToolsAndInstruments = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Tools and instruments"- , asNameJa = "工具器具"- , asLabelJa = "工具器具"- , asDescription = "Assets: Tools and instruments (工具器具)"- , asAliases = ["工具器具"]- }-accountSpec ConstructionInProgress = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Construction in progress"- , asNameJa = "建設仮勘定"- , asLabelJa = "建設仮勘定"- , asDescription = "Assets: Construction in progress (建設仮勘定)"- , asAliases = ["建設仮勘定"]- }-accountSpec Goodwill = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Goodwill"- , asNameJa = "のれん"- , asLabelJa = "のれん"- , asDescription = "Assets: Goodwill (のれん)"- , asAliases = ["のれん"]- }-accountSpec SoftwareInProgress = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Software in progress"- , asNameJa = "ソフトウェア仮勘定"- , asLabelJa = "ソフトウェア仮勘定"- , asDescription = "Assets: Software in progress (ソフトウェア仮勘定)"- , asAliases = ["ソフトウェア仮勘定"]- }-accountSpec LongTermPrepaidExpenses = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term prepaid expenses"- , asNameJa = "長期前払費用"- , asLabelJa = "長期前払費用"- , asDescription = "Assets: Long-term prepaid expenses (長期前払費用)"- , asAliases = ["長期前払費用"]- }-accountSpec DishonoredNotesReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Dishonored notes receivable"- , asNameJa = "不渡手形"- , asLabelJa = "不渡手形"- , asDescription = "Assets: Dishonored notes receivable (不渡手形)"- , asAliases = ["不渡手形"]- }-accountSpec PrepaidPensionCost = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Prepaid pension costs"- , asNameJa = "前払年金費用"- , asLabelJa = "前払年金費用"- , asDescription = "Assets: Prepaid pension costs (前払年金費用)"- , asAliases = ["前払年金費用"]- }-accountSpec NetDefinedBenefitAsset = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Retirement benefit asset"- , asNameJa = "退職給付に係る資産"- , asLabelJa = "退職給付に係る資産"- , asDescription = "Assets: Retirement benefit asset (退職給付に係る資産)"- , asAliases = ["退職給付に係る資産"]- }-accountSpec DepositsInSpecialAccounts = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Deposits in special accounts"- , asNameJa = "別段預金"- , asLabelJa = "別段預金"- , asDescription = "Assets: Deposits in special accounts (別段預金)"- , asAliases = ["別段預金"]- }-accountSpec Structures = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Structures"- , asNameJa = "構築物"- , asLabelJa = "構築物"- , asDescription = "Assets: Structures (構築物)"- , asAliases = ["構築物"]- }-accountSpec LeaseholdRights = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Leasehold interests in land"- , asNameJa = "借地権"- , asLabelJa = "借地権"- , asDescription = "Assets: Leasehold interests in land (借地権)"- , asAliases = ["借地権"]- }-accountSpec NonOperatingNotesReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Non-operating notes receivable"- , asNameJa = "営業外受取手形"- , asLabelJa = "営業外受取手形"- , asDescription = "Assets: Non-operating notes receivable (営業外受取手形)"- , asAliases = ["営業外受取手形"]- }-accountSpec NonOperatingElectronicallyRecordedReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Electronically recorded monetary claims - non-operating"- , asNameJa = "営業外電子記録債権"- , asLabelJa = "営業外電子記録債権"- , asDescription = "Assets: Electronically recorded monetary claims - non-operating (営業外電子記録債権)"- , asAliases = ["営業外電子記録債権"]- }-accountSpec RefundLiabilities = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Refund liabilities"- , asNameJa = "返金負債"- , asLabelJa = "返金負債"- , asDescription = "Liability: Refund liabilities (返金負債)"- , asAliases = ["返金負債"]- }-accountSpec NonOperatingNotesPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Non-operating notes payable"- , asNameJa = "営業外支払手形"- , asLabelJa = "営業外支払手形"- , asDescription = "Liability: Non-operating notes payable (営業外支払手形)"- , asAliases = ["営業外支払手形"]- }-accountSpec NonOperatingElectronicallyRecordedObligations = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Electronically recorded obligations - non-operating"- , asNameJa = "営業外電子記録債務"- , asLabelJa = "営業外電子記録債務"- , asDescription = "Liability: Electronically recorded obligations - non-operating (営業外電子記録債務)"- , asAliases = ["営業外電子記録債務"]- }-accountSpec BonusesPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Accrued bonuses"- , asNameJa = "未払賞与"- , asLabelJa = "未払賞与"- , asDescription = "Liability: Accrued bonuses (未払賞与)"- , asAliases = ["未払賞与"]- }-accountSpec AllowanceForRepairs = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Provision for repairs"- , asNameJa = "修繕引当金"- , asLabelJa = "修繕引当金"- , asDescription = "Liability: Provision for repairs (修繕引当金)"- , asAliases = ["修繕引当金"]- }-accountSpec AllowanceForProductWarranties = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Provision for warranties"- , asNameJa = "商品保証引当金"- , asLabelJa = "商品保証引当金"- , asDescription = "Liability: Provision for warranties (商品保証引当金)"- , asAliases = ["商品保証引当金"]- }-accountSpec AllowanceForBonuses = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Provision for bonuses"- , asNameJa = "賞与引当金"- , asLabelJa = "賞与引当金"- , asDescription = "Liability: Provision for bonuses (賞与引当金)"- , asAliases = ["賞与引当金"]- }-accountSpec DeferredTaxLiabilities = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Deferred tax liabilities"- , asNameJa = "繰延税金負債"- , asLabelJa = "繰延税金負債"- , asDescription = "Liability: Deferred tax liabilities (繰延税金負債)"- , asAliases = ["繰延税金負債"]- }-accountSpec LeaseObligations = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Lease liabilities"- , asNameJa = "リース債務"- , asLabelJa = "リース債務"- , asDescription = "Liability: Lease liabilities (リース債務)"- , asAliases = ["リース債務"]- }-accountSpec GuaranteeDepositsReceived = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Guarantee deposits received"- , asNameJa = "預り保証金"- , asLabelJa = "預り保証金"- , asDescription = "Liability: Guarantee deposits received (預り保証金)"- , asAliases = ["預り保証金"]- }-accountSpec AllowanceForRetirementBenefits = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Provision for retirement benefits"- , asNameJa = "退職給付引当金"- , asLabelJa = "退職給付引当金"- , asDescription = "Liability: Provision for retirement benefits (退職給付引当金)"- , asAliases = ["退職給付引当金"]- }-accountSpec LongTermOtherPayables = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Long-term accounts payable - other"- , asNameJa = "長期未払金"- , asLabelJa = "長期未払金"- , asDescription = "Liability: Long-term accounts payable - other (長期未払金)"- , asAliases = ["長期未払金"]- }-accountSpec NetDefinedBenefitLiability = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Retirement benefit liability"- , asNameJa = "退職給付に係る負債"- , asLabelJa = "退職給付に係る負債"- , asDescription = "Liability: Retirement benefit liability (退職給付に係る負債)"- , asAliases = ["退職給付に係る負債"]- }-accountSpec StockSubscriptionDeposits = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Deposits for subscriptions of shares"- , asNameJa = "株式申込証拠金"- , asLabelJa = "株式申込証拠金"- , asDescription = "Equity: Deposits for subscriptions of shares (株式申込証拠金)"- , asAliases = ["株式申込証拠金"]- }-accountSpec LegalCapitalSurplus = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Legal capital surplus"- , asNameJa = "資本準備金"- , asLabelJa = "資本準備金"- , asDescription = "Equity: Legal capital surplus (資本準備金)"- , asAliases = ["資本準備金"]- }-accountSpec OtherCapitalSurplus = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Other capital surplus"- , asNameJa = "その他資本剰余金"- , asLabelJa = "その他資本剰余金"- , asDescription = "Equity: Other capital surplus (その他資本剰余金)"- , asAliases = ["その他資本剰余金"]- }-accountSpec DividendEqualizationReserve = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reserve for dividend equalization"- , asNameJa = "配当平均積立金"- , asLabelJa = "配当平均積立金"- , asDescription = "Equity: Reserve for dividend equalization (配当平均積立金)"- , asAliases = ["配当平均積立金"]- }-accountSpec RepairFundReserve = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reserve for repairs"- , asNameJa = "修繕積立金"- , asLabelJa = "修繕積立金"- , asDescription = "Equity: Reserve for repairs (修繕積立金)"- , asAliases = ["修繕積立金"]- }-accountSpec ConstructionFundReserve = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reserve for new construction"- , asNameJa = "新築積立金"- , asLabelJa = "新築積立金"- , asDescription = "Equity: Reserve for new construction (新築積立金)"- , asAliases = ["新築積立金"]- }-accountSpec GeneralReserve = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "General reserve"- , asNameJa = "別途積立金"- , asLabelJa = "別途積立金"- , asDescription = "Equity: General reserve (別途積立金)"- , asAliases = ["別途積立金"]- }-accountSpec ValuationDifferenceOnOtherSecurities = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Valuation difference on available-for-sale securities"- , asNameJa = "その他有価証券評価差額金"- , asLabelJa = "その他有価証券評価差額金"- , asDescription = "Equity: Valuation difference on available-for-sale securities (その他有価証券評価差額金)"- , asAliases = ["その他有価証券評価差額金"]- }-accountSpec NonControllingInterests = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Non-controlling interests"- , asNameJa = "非支配株主持分"- , asLabelJa = "非支配株主持分"- , asDescription = "Equity: Non-controlling interests (非支配株主持分)"- , asAliases = ["非支配株主持分"]- }-accountSpec CapitalSurplus = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Capital surplus"- , asNameJa = "資本剰余金"- , asLabelJa = "資本剰余金"- , asDescription = "Equity: Capital surplus (資本剰余金)"- , asAliases = ["資本剰余金"]- }-accountSpec EarnedSurplus = Just AccountSpec- { asDivision = Equity- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Retained earnings"- , asNameJa = "利益剰余金"- , asLabelJa = "利益剰余金"- , asDescription = "Equity: Retained earnings (利益剰余金)"- , asAliases = ["利益剰余金"]- }-accountSpec ServiceRevenue = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Service revenue"- , asNameJa = "役務収益"- , asLabelJa = "役務収益"- , asDescription = "Revenue: Service revenue (役務収益)"- , asAliases = ["役務収益"]- }-accountSpec OperatingRevenue = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Operating revenue"- , asNameJa = "営業収益"- , asLabelJa = "営業収益"- , asDescription = "Revenue: Operating revenue (営業収益)"- , asAliases = ["営業収益"]- }-accountSpec GainOnSalesOfSecurities = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on sale of securities"- , asNameJa = "有価証券売却益"- , asLabelJa = "有価証券売却益"- , asDescription = "Revenue: Gain on sale of securities (有価証券売却益)"- , asAliases = ["有価証券売却益"]- }-accountSpec GainOnValuationOfSecurities = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on valuation of securities"- , asNameJa = "有価証券評価益"- , asLabelJa = "有価証券評価益"- , asDescription = "Revenue: Gain on valuation of securities (有価証券評価益)"- , asAliases = ["有価証券評価益"]- }-accountSpec DividendsReceived = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Dividend income"- , asNameJa = "受取配当金"- , asLabelJa = "受取配当金"- , asDescription = "Revenue: Dividend income (受取配当金)"- , asAliases = ["受取配当金"]- }-accountSpec InterestOnSecurities = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Interest on securities"- , asNameJa = "有価証券利息"- , asLabelJa = "有価証券利息"- , asDescription = "Revenue: Interest on securities (有価証券利息)"- , asAliases = ["有価証券利息"]- }-accountSpec GainOnSalesOfInvestmentSecurities = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on sale of investment securities"- , asNameJa = "投資有価証券売却益"- , asLabelJa = "投資有価証券売却益"- , asDescription = "Revenue: Gain on sale of investment securities (投資有価証券売却益)"- , asAliases = ["投資有価証券売却益"]- }-accountSpec InsuranceGain = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on insurance claims"- , asNameJa = "保険差益"- , asLabelJa = "保険差益"- , asDescription = "Revenue: Gain on insurance claims (保険差益)"- , asAliases = ["保険差益"]- }-accountSpec GainOnBargainPurchase = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on bargain purchase"- , asNameJa = "負ののれん発生益"- , asLabelJa = "負ののれん発生益"- , asDescription = "Revenue: Gain on bargain purchase (負ののれん発生益)"- , asAliases = ["負ののれん発生益"]- }-accountSpec ReversalOfAllowanceForRepairs = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reversal of provision for repairs"- , asNameJa = "修繕引当金戻入"- , asLabelJa = "修繕引当金戻入"- , asDescription = "Revenue: Reversal of provision for repairs (修繕引当金戻入)"- , asAliases = ["修繕引当金戻入"]- }-accountSpec ReversalOfAllowanceForProductWarranties = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Reversal of provision for warranties"- , asNameJa = "商品保証引当金戻入"- , asLabelJa = "商品保証引当金戻入"- , asDescription = "Revenue: Reversal of provision for warranties (商品保証引当金戻入)"- , asAliases = ["商品保証引当金戻入"]- }-accountSpec GainOnDonationOfFixedAssets = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on receipt of donated non-current assets"- , asNameJa = "固定資産受贈益"- , asLabelJa = "固定資産受贈益"- , asDescription = "Revenue: Gain on receipt of donated non-current assets (固定資産受贈益)"- , asAliases = ["固定資産受贈益"]- }-accountSpec GainOnNationalSubsidies = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on receipt of national subsidies"- , asNameJa = "国庫補助金受贈益"- , asLabelJa = "国庫補助金受贈益"- , asDescription = "Revenue: Gain on receipt of national subsidies (国庫補助金受贈益)"- , asAliases = ["国庫補助金受贈益"]- }-accountSpec GainOnConstructionGrants = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Gain on contribution received for construction"- , asNameJa = "工事負担金受贈益"- , asLabelJa = "工事負担金受贈益"- , asDescription = "Revenue: Gain on contribution received for construction (工事負担金受贈益)"- , asAliases = ["工事負担金受贈益"]- }-accountSpec LandRentReceived = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Rental income from land"- , asNameJa = "受取地代"- , asLabelJa = "受取地代"- , asDescription = "Revenue: Rental income from land (受取地代)"- , asAliases = ["受取地代"]- }-accountSpec SalesRebates = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = True- , asFixedCurrent = Other- , asNameEn = "Sales rebates"- , asNameJa = "売上割戻"- , asLabelJa = "売上割戻"- , asDescription = "Revenue: Sales rebates (売上割戻)"- , asAliases = ["売上割戻"]- }-accountSpec CostOfServices = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Cost of services"- , asNameJa = "役務原価"- , asLabelJa = "役務原価"- , asDescription = "Cost: Cost of services (役務原価)"- , asAliases = ["役務原価"]- }-accountSpec OperatingExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Operating expenses"- , asNameJa = "営業費用"- , asLabelJa = "営業費用"- , asDescription = "Cost: Operating expenses (営業費用)"- , asAliases = ["営業費用"]- }-accountSpec InventoryShrinkageLoss = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Inventory shrinkage loss"- , asNameJa = "棚卸減耗損"- , asLabelJa = "棚卸減耗損"- , asDescription = "Cost: Inventory shrinkage loss (棚卸減耗損)"- , asAliases = ["棚卸減耗損"]- }-accountSpec LossOnValuationOfMerchandise = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Valuation loss on goods"- , asNameJa = "商品評価損"- , asLabelJa = "商品評価損"- , asDescription = "Cost: Valuation loss on goods (商品評価損)"- , asAliases = ["商品評価損"]- }-accountSpec Bonuses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Bonuses"- , asNameJa = "賞与"- , asLabelJa = "賞与"- , asDescription = "Cost: Bonuses (賞与)"- , asAliases = ["賞与"]- }-accountSpec RetirementBenefitExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Retirement benefit expenses"- , asNameJa = "退職給付費用"- , asLabelJa = "退職給付費用"- , asDescription = "Cost: Retirement benefit expenses (退職給付費用)"- , asAliases = ["退職給付費用"]- }-accountSpec ProvisionForRepairs = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Provision for repairs"- , asNameJa = "修繕引当金繰入"- , asLabelJa = "修繕引当金繰入"- , asDescription = "Cost: Provision for repairs (修繕引当金繰入)"- , asAliases = ["修繕引当金繰入"]- }-accountSpec ProvisionForBonuses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Provision for bonuses"- , asNameJa = "賞与引当金繰入"- , asLabelJa = "賞与引当金繰入"- , asDescription = "Cost: Provision for bonuses (賞与引当金繰入)"- , asAliases = ["賞与引当金繰入"]- }-accountSpec ProvisionForProductWarranties = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Provision for warranties"- , asNameJa = "商品保証引当金繰入"- , asLabelJa = "商品保証引当金繰入"- , asDescription = "Cost: Provision for warranties (商品保証引当金繰入)"- , asAliases = ["商品保証引当金繰入"]- }-accountSpec ResearchAndDevelopmentExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Research and development expenses"- , asNameJa = "研究開発費"- , asLabelJa = "研究開発費"- , asDescription = "Cost: Research and development expenses (研究開発費)"- , asAliases = ["研究開発費"]- }-accountSpec AmortizationOfGoodwill = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Amortization of goodwill"- , asNameJa = "のれん償却"- , asLabelJa = "のれん償却"- , asDescription = "Cost: Amortization of goodwill (のれん償却)"- , asAliases = ["のれん償却"]- }-accountSpec AmortizationOfSoftware = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Amortization of software"- , asNameJa = "ソフトウェア償却"- , asLabelJa = "ソフトウェア償却"- , asDescription = "Cost: Amortization of software (ソフトウェア償却)"- , asAliases = ["ソフトウェア償却"]- }-accountSpec AmortizationOfPatents = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Amortization of patent rights"- , asNameJa = "特許権償却"- , asLabelJa = "特許権償却"- , asDescription = "Cost: Amortization of patent rights (特許権償却)"- , asAliases = ["特許権償却"]- }-accountSpec LeaseExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Lease expenses"- , asNameJa = "支払リース料"- , asLabelJa = "支払リース料"- , asDescription = "Cost: Lease expenses (支払リース料)"- , asAliases = ["支払リース料"]- }-accountSpec IncorporationExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Organization expenses"- , asNameJa = "創立費"- , asLabelJa = "創立費"- , asDescription = "Cost: Organization expenses (創立費)"- , asAliases = ["創立費"]- }-accountSpec StockIssuanceCosts = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Share issuance costs"- , asNameJa = "株式交付費"- , asLabelJa = "株式交付費"- , asDescription = "Cost: Share issuance costs (株式交付費)"- , asAliases = ["株式交付費"]- }-accountSpec BusinessCommencementExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Business commencement expenses"- , asNameJa = "開業費"- , asLabelJa = "開業費"- , asDescription = "Cost: Business commencement expenses (開業費)"- , asAliases = ["開業費"]- }-accountSpec DevelopmentExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Development expenses"- , asNameJa = "開発費"- , asLabelJa = "開発費"- , asDescription = "Cost: Development expenses (開発費)"- , asAliases = ["開発費"]- }-accountSpec LossOnSalesOfElectronicallyRecordedReceivables = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sale of electronically recorded monetary claims"- , asNameJa = "電子記録債権売却損"- , asLabelJa = "電子記録債権売却損"- , asDescription = "Cost: Loss on sale of electronically recorded monetary claims (電子記録債権売却損)"- , asAliases = ["電子記録債権売却損"]- }-accountSpec LossOnSalesOfReceivables = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sale of receivables"- , asNameJa = "債権売却損"- , asLabelJa = "債権売却損"- , asDescription = "Cost: Loss on sale of receivables (債権売却損)"- , asAliases = ["債権売却損"]- }-accountSpec LossOnSalesOfSecurities = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sale of securities"- , asNameJa = "有価証券売却損"- , asLabelJa = "有価証券売却損"- , asDescription = "Cost: Loss on sale of securities (有価証券売却損)"- , asAliases = ["有価証券売却損"]- }-accountSpec LossOnValuationOfSecurities = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on valuation of securities"- , asNameJa = "有価証券評価損"- , asLabelJa = "有価証券評価損"- , asDescription = "Cost: Loss on valuation of securities (有価証券評価損)"- , asAliases = ["有価証券評価損"]- }-accountSpec LossOnSalesOfInvestmentSecurities = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on sale of investment securities"- , asNameJa = "投資有価証券売却損"- , asLabelJa = "投資有価証券売却損"- , asDescription = "Cost: Loss on sale of investment securities (投資有価証券売却損)"- , asAliases = ["投資有価証券売却損"]- }-accountSpec LossOnFire = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on fire"- , asNameJa = "火災損失"- , asLabelJa = "火災損失"- , asDescription = "Cost: Loss on fire (火災損失)"- , asAliases = ["火災損失"]- }-accountSpec LossOnRetirementOfFixedAssets = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on retirement of non-current assets"- , asNameJa = "固定資産除却損"- , asLabelJa = "固定資産除却損"- , asDescription = "Cost: Loss on retirement of non-current assets (固定資産除却損)"- , asAliases = ["固定資産除却損"]- }-accountSpec LossOnReductionOfFixedAssets = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss on tax purpose reduction entry of non-current assets"- , asNameJa = "固定資産圧縮損"- , asLabelJa = "固定資産圧縮損"- , asDescription = "Cost: Loss on tax purpose reduction entry of non-current assets (固定資産圧縮損)"- , asAliases = ["固定資産圧縮損"]- }-accountSpec AdditionalIncomeTaxesForPriorPeriods = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Additional income taxes for prior periods"- , asNameJa = "追徴法人税等"- , asLabelJa = "追徴法人税等"- , asDescription = "Cost: Additional income taxes for prior periods (追徴法人税等)"- , asAliases = ["追徴法人税等"]- }-accountSpec RefundOfIncomeTaxes = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = True- , asFixedCurrent = Other- , asNameEn = "Refund of income taxes"- , asNameJa = "還付法人税等"- , asLabelJa = "還付法人税等"- , asDescription = "Cost: Refund of income taxes (還付法人税等)"- , asAliases = ["還付法人税等"]- }-accountSpec PurchaseRebates = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = True- , asFixedCurrent = Other- , asNameEn = "Purchase rebates"- , asNameJa = "仕入割戻"- , asLabelJa = "仕入割戻"- , asDescription = "Cost: Purchase rebates (仕入割戻)"- , asAliases = ["仕入割戻"]- }-accountSpec WelfareExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Welfare expenses"- , asNameJa = "福利厚生費"- , asLabelJa = "福利厚生費"- , asDescription = "Cost: Welfare expenses (福利厚生費)"- , asAliases = ["福利厚生費"]- }-accountSpec MaintenanceExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Maintenance expenses"- , asNameJa = "保守費"- , asLabelJa = "保守費"- , asDescription = "Cost: Maintenance expenses (保守費)"- , asAliases = ["保守費"]- }-accountSpec StatutoryWelfareExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Legal welfare expenses"- , asNameJa = "法定福利費"- , asLabelJa = "法定福利費"- , asDescription = "Cost: Legal welfare expenses (法定福利費)"- , asAliases = ["法定福利費"]- }-accountSpec LandRentPaid = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Rent expenses on land"- , asNameJa = "支払地代"- , asLabelJa = "支払地代"- , asDescription = "Cost: Rent expenses on land (支払地代)"- , asAliases = ["支払地代"]- }-accountSpec InsuranceExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Insurance expenses"- , asNameJa = "保険料"- , asLabelJa = "保険料"- , asDescription = "Cost: Insurance expenses (保険料)"- , asAliases = ["保険料"]- }-accountSpec RepairsExpense = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Repair expenses"- , asNameJa = "修繕費"- , asLabelJa = "修繕費"- , asDescription = "Cost: Repair expenses (修繕費)"- , asAliases = ["修繕費"]- }-accountSpec StorageExpenses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Storage costs"- , asNameJa = "保管費"- , asLabelJa = "保管費"- , asDescription = "Cost: Storage costs (保管費)"- , asAliases = ["保管費"]- }-accountSpec MembershipFees = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Membership fees"- , asNameJa = "諸会費"- , asLabelJa = "諸会費"- , asDescription = "Cost: Membership fees (諸会費)"- , asAliases = ["諸会費"]- }-accountSpec IncomeSummary = Just AccountSpec- { asDivision = Assets- , asClosing = NoClose- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Income summary"- , asNameJa = "損益"- , asLabelJa = "損益"- , asDescription = "Bookkeeping device: Income summary (損益). Assets/NoClose is a technical debit-side placeholder; it is not a balance-sheet asset and is not automatically closed."- , asAliases = ["損益"]- }-accountSpec SuspenseAccount = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Suspense account"- , asNameJa = "未決算"- , asLabelJa = "未決算"- , asDescription = "Assets: Suspense account (未決算)"- , asAliases = ["未決算"]- }-accountSpec ForeignExchangeGains = Just AccountSpec- { asDivision = Revenue- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Foreign exchange gains"- , asNameJa = "為替差益"- , asLabelJa = "為替差益"- , asDescription = "Revenue: Foreign exchange gains (為替差益)"- , asAliases = ["為替差益"]- }-accountSpec ForeignExchangeLosses = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Foreign exchange losses"- , asNameJa = "為替差損"- , asLabelJa = "為替差損"- , asDescription = "Cost: Foreign exchange losses (為替差損)"- , asAliases = ["為替差損"]- }-accountSpec ContraAccountForGuaranteeObligations = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Contra account for guarantee obligations"- , asNameJa = "保証債務見返"- , asLabelJa = "保証債務見返"- , asDescription = "Assets: Contra account for guarantee obligations (保証債務見返)"- , asAliases = ["保証債務見返"]- }-accountSpec GuaranteeObligations = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Guarantee obligations"- , asNameJa = "保証債務"- , asLabelJa = "保証債務"- , asDescription = "Liability: Guarantee obligations (保証債務)"- , asAliases = ["保証債務"]- }-accountSpec IncomeTaxesAdjustment = Just AccountSpec- { asDivision = Cost- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Income taxes - deferred"- , asNameJa = "法人税等調整額"- , asLabelJa = "法人税等調整額"- , asDescription = "Cost: Income taxes - deferred (法人税等調整額)"- , asAliases = ["法人税等調整額"]- }-accountSpec BranchCurrentAccount = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Branch current account"- , asNameJa = "支店"- , asLabelJa = "支店"- , asDescription = "Assets: Branch current account (支店)"- , asAliases = ["支店"]- }-accountSpec HeadOfficeCurrentAccount = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Head office current account"- , asNameJa = "本店"- , asLabelJa = "本店"- , asDescription = "Liability: Head office current account (本店), the credit-balance reciprocal account in branch books."- , asAliases = ["本店"]- }-accountSpec NetIncomeAttributableToNCI = Just AccountSpec- { asDivision = Cost- , asClosing = NoClose- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Profit attributable to non-controlling interests"- , asNameJa = "非支配株主に帰属する当期純利益"- , asLabelJa = "非支配株主に帰属する当期純利益"- , asDescription = "Cost: Profit attributable to non-controlling interests (非支配株主に帰属する当期純利益). NoClose: consolidation procedures transfer it separately to non-controlling interests."- , asAliases = ["非支配株主に帰属する当期純利益"]- }-accountSpec NetLossAttributableToNCI = Just AccountSpec- { asDivision = Revenue- , asClosing = NoClose- , asIsContra = False- , asFixedCurrent = Other- , asNameEn = "Loss attributable to non-controlling interests"- , asNameJa = "非支配株主に帰属する当期純損失"- , asLabelJa = "非支配株主に帰属する当期純損失"- , asDescription = "Revenue: Loss attributable to non-controlling interests (非支配株主に帰属する当期純損失). NoClose: consolidation procedures transfer it separately to non-controlling interests."- , asAliases = ["非支配株主に帰属する当期純損失"]- }-accountSpec TradingSecurities = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Trading securities"- , asNameJa = "売買目的有価証券"- , asLabelJa = "売買目的有価証券"- , asDescription = "Assets: Trading securities (売買目的有価証券)"- , asAliases = ["売買目的有価証券"]- }-accountSpec HeldToMaturityBonds = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Held-to-maturity bonds"- , asNameJa = "満期保有目的債券"- , asLabelJa = "満期保有目的債券"- , asDescription = "Assets: Held-to-maturity bonds (満期保有目的債券)"- , asAliases = ["満期保有目的債券"]- }-accountSpec SubsidiaryStocks = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Shares of subsidiaries"- , asNameJa = "子会社株式"- , asLabelJa = "子会社株式"- , asDescription = "Assets: Shares of subsidiaries (子会社株式)"- , asAliases = ["子会社株式"]- }-accountSpec AffiliateStocks = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Shares of associates"- , asNameJa = "関連会社株式"- , asLabelJa = "関連会社株式"- , asDescription = "Assets: Shares of associates (関連会社株式)"- , asAliases = ["関連会社株式"]- }-accountSpec AvailableForSaleSecurities = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Fixed- , asNameEn = "Available-for-sale securities"- , asNameJa = "その他有価証券"- , asLabelJa = "その他有価証券"- , asDescription = "Assets: Available-for-sale securities (その他有価証券)"- , asAliases = ["その他有価証券"]- }-accountSpec ConsumptionTaxRefundReceivable = Just AccountSpec- { asDivision = Assets- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Consumption tax refund receivable"- , asNameJa = "未収還付消費税"- , asLabelJa = "未収還付消費税"- , asDescription = "Asset: Consumption tax refund receivable (未収還付消費税)"- , asAliases = ["未収還付消費税", "未収消費税"]- }-accountSpec PropertyTaxPayable = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Property tax payable"- , asNameJa = "未払固定資産税"- , asLabelJa = "未払固定資産税"- , asDescription = "Liability: Property tax payable (未払固定資産税)"- , asAliases = ["未払固定資産税"]- }-accountSpec DepositsReceivedFromOfficers = Just AccountSpec- { asDivision = Liability- , asClosing = CloseByDivision- , asIsContra = False- , asFixedCurrent = Current- , asNameEn = "Deposits received from officers"- , asNameJa = "役員預り金"- , asLabelJa = "役員預り金"- , asDescription = "Liability: Deposits received from officers (役員預り金)"- , asAliases = ["役員預り金"]- }-accountSpec AccountTitle = Nothing---- | Derived strict map view. The exhaustive function above remains canonical.-accountSpecMap :: Map AccountTitles AccountSpec-accountSpecMap = M.fromList- [ (title, spec)- | title <- concreteAccountTitles- , Just spec <- [accountSpec title]- ]---- | Whether an account is a contra account.------ The result is projected from 'AccountSpec.asIsContra'; the wildcard has no--- specification and therefore returns 'False'. Complexity: O(1)-{-# INLINE classifyAccountContra #-}-classifyAccountContra :: AccountTitles -> Bool-classifyAccountContra title = maybe False asIsContra (accountSpec title)---- | Compatibility projection used by the assistance API.-accountDescriptions :: [(AccountTitles, Text, Text, Text)]-accountDescriptions = mapMaybe project concreteAccountTitles- where- project title = do- spec <- accountSpec title- pure (title, asNameEn spec, asNameJa spec, asDescription spec)+-- | Compatibility exports for the account registry.+module ExchangeAlgebra.Algebra.Base.Account.Registry {-# DEPRECATED "Use ExchangeAlgebra.Accounting.Account instead." #-} + ( AccountSpec(..), AccountSemantics(..), accountAliases, accountSpec+ , accountSemantics, accountSpecMap, concreteAccountTitles+ , classifyAccountContra, accountDescriptions+ ) where++import ExchangeAlgebra.Accounting.Account.Registry
src/ExchangeAlgebra/Algebra/Base/Account/Types.hs view
@@ -1,139 +1,8 @@-{- |-Module : ExchangeAlgebra.Algebra.Base.Account.Types-Description : Account metadata types shared by Base and the account registry.--}-module ExchangeAlgebra.Algebra.Base.Account.Types- ( AccountDivision(..)- , Side(..)- , ClosingRule(..)- , FixedCurrent(..)- , AccountRole(..)- , PostingCapability(..)- , DivisionSemantics(..)- , HomeSideSemantics(..)- , ReportingEligibility(..)+-- | Compatibility exports for account classification types.+module ExchangeAlgebra.Algebra.Base.Account.Types {-# DEPRECATED "Use ExchangeAlgebra.Accounting.Account instead." #-} + ( AccountDivision(..), Side(..), ClosingRule(..), FixedCurrent(..)+ , AccountRole(..), PostingCapability(..), DivisionSemantics(..)+ , HomeSideSemantics(..), ReportingEligibility(..) ) where -import Control.DeepSeq (NFData(..))---- | Account division (financial-statement classification). The--- 'ExchangeAlgebra.Algebra.Base.AccountBase'--- correspondence instance lives in "ExchangeAlgebra.Algebra.Base" (the class's--- home module).-data AccountDivision = Assets -- ^ Assets- | Equity -- ^ Equity- | Liability -- ^ Liability- | Cost -- ^ Cost- | Revenue -- ^ Revenue- deriving (Ord, Show, Eq)--instance NFData AccountDivision where- rnf value = value `seq` ()---- | Credit/debit distinction. v'Side' is the wildcard used by legacy APIs.-data Side = Credit -- ^ Credit side.- | Debit -- ^ Debit side.- | Side -- ^ Wildcard.- deriving (Ord, Show, Eq)--instance NFData Side where- rnf value = value `seq` ()---- | Registry-level policy for automatic closing entries.------ 'CloseByDivision' derives the transfer side from 'AccountDivision'.--- 'NoClose' is an explicit override. Future policies may add explicit--- keep/flip constructors without returning to an account-title case split.-data ClosingRule = CloseByDivision -- ^ Close Cost/Revenue accounts according to their division.- | NoClose -- ^ Do not generate an automatic closing entry.- deriving (Show, Eq)--instance NFData ClosingRule where- rnf value = value `seq` ()---- | Fixed/Current distinction. Used for classifying account titles as fixed or current.-data FixedCurrent = Fixed -- ^ Fixed- | Current -- ^ Current- | Other -- ^ Other (expenses, revenues, etc.)- deriving (Show, Eq)--instance NFData FixedCurrent where- rnf value = value `seq` ()---- | Accounting role of an account-basis coordinate. Roles are not assumed to--- be mutually exclusive; see--- 'ExchangeAlgebra.Algebra.Base.Account.Registry.AccountSemantics' in the--- account registry.-data AccountRole- = OrdinaryAccount- | ContraAccount- | ReciprocalAccount- | SuspenseOrClearingAccount- | ClosingDevice- | AttributionAccount- | PeriodResult- | ReportingSubtotal- deriving (Show, Eq)--instance NFData AccountRole where- rnf value = value `seq` ()---- | Context in which an account title may be used as a posting coordinate.--- Enforcement is introduced by the checked-conversion API in a later land;--- this type is the canonical metadata used by that gate.-data PostingCapability- = OrdinaryPosting- | ClosingOnly- | ConsolidationOnly- | EngineGeneratedOnly- | NotPostable- deriving (Show, Eq)--instance NFData PostingCapability where- rnf value = value `seq` ()---- | Meaning of the legacy five-way 'AccountDivision' value.------ This separates a genuine statement classification from a bookkeeping--- control class or an internal direction encoding. The wrapped legacy value--- remains available for 0.4.x-compatible algebraic behavior.-data DivisionSemantics- = StatementDivision AccountDivision- | BookkeepingControlClass AccountDivision- | DirectionEncoding AccountDivision- | NoStatementDivision- deriving (Show, Eq)--instance NFData DivisionSemantics where- rnf value = case value of- StatementDivision division -> rnf division- BookkeepingControlClass division -> rnf division- DirectionEncoding division -> rnf division- NoStatementDivision -> ()---- | Semantic status of an account's normal posting side.-data HomeSideSemantics- = FixedHomeSide Side- | ContextDependentHomeSide- | NoFixedHomeSide- | NoPostingSide- deriving (Show, Eq)--instance NFData HomeSideSemantics where- rnf value = case value of- FixedHomeSide side -> rnf side- ContextDependentHomeSide -> ()- NoFixedHomeSide -> ()- NoPostingSide -> ()---- | Coarse reporting eligibility. Actual presentation remains a function of--- reporting context and policy and is implemented in a later land.-data ReportingEligibility- = StatementEligible- | ContextualPresentation- | DerivedPresentation- | NotPresented- deriving (Show, Eq)--instance NFData ReportingEligibility where- rnf value = value `seq` ()+import ExchangeAlgebra.Accounting.Account.Classification
src/ExchangeAlgebra/Algebra/Base/Element.hs view
@@ -1,843 +1,10 @@-{- |- Module : ExchangeAlgebra.Algebra.Base.Element- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Package for Exchange Algebra defined by Hiroshi Deguchi.-- Exchange Algebra is an algebraic description of bookkeeping system.- Details are below.-- <https://www.springer.com/gp/book/9784431209850>-- <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>-- == Extending with user-defined types-- To use your own type as a basis component, declare an 'Element' instance.- A single distinguished value must serve as the wildcard used by the- transfer engine and by projection operations. Derive 'NFData' when keys- containing this component must be fully evaluated:-- @- data Company = CompanyA | CompanyB | CompanyWildcard- deriving (Eq, Ord, Show, Generic, Hashable, NFData, Typeable)-- instance Element Company where- wildcard = CompanyWildcard- @-- == Import guidance-- User code rarely needs to import this module directly. The 'Element'- class and its associated symbols are re-exported from- "ExchangeAlgebra.Algebra.Base", and transitively from- "ExchangeAlgebra.Algebra", "ExchangeAlgebra.Journal", and the top-level- "ExchangeAlgebra". The module path of this file may be reorganized in- future versions; prefer importing from the higher-level modules.---}--{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE StrictData #-}-{-# LANGUAGE Strict #-}-{-# LANGUAGE DeriveGeneric #-}---- NB. Only the two names a user needs to define an 'Element' instance are--- re-exported ('Hashable' for the constraint/instance, 'Generic' for deriving).--- The former whole-module re-exports of "Data.Hashable" and "GHC.Generics"--- leaked their entire namespaces (hash, hashUsing, Rep, from/to, …) through--- Base -> Algebra -> the ExchangeAlgebra umbrella.-module ExchangeAlgebra.Algebra.Base.Element- ( module ExchangeAlgebra.Algebra.Base.Element- , Hashable(..)- , Generic) where--import qualified Data.Text as T-import Data.Text (Text)-import qualified Data.Time as Time-import Data.Time-import GHC.Generics (Generic)-import Data.Hashable-import Data.Typeable (Typeable, cast, typeOf)-import Control.DeepSeq (NFData(..))-import qualified Data.Binary as Binary-import qualified Data.Binary.Get as BinaryGet-import qualified Data.Binary.Put as BinaryPut----------------------------------------------------------------------- * Element (components of bases)----------------------------------------------------------------------- | Element Class: a type must be an instance of this class to serve as a component of a basis.------ Each component of a basis must be an instance of this type class.--- It provides wildcard-based pattern matching, enabling flexible basis--- specification in transfer transformations and projections.------ A wildcard in a ledger entry's base means that axis does not apply; it is--- one value, not a pattern. In a projection query or transfer source pattern,--- it matches any entry value, including a ledger wildcard. In a transfer target,--- it preserves the source coordinate. To collapse an axis, use a function such--- as @mapBasePart@ rather than a wildcard.------ For pattern @p@ and ledger entry @e@, @p matches e@ exactly when--- @p = wildcard@ or @p = e@, applied componentwise to tuple bases. A concrete--- query therefore does not select a ledger wildcard. Canonical ordering treats--- the wildcard as an ordinary value.-class (Eq a, Ord a, Show a, Hashable a, Typeable a) => Element a where-- -- | The wildcard value. Used for pattern matching in search, transfer transformation, etc.- --- -- Complexity: O(1)- wildcard :: a-- -- | Determines whether the element itself or any of its internal components contains a wildcard.- -- For tuple elements, returns True if any component is a wildcard.- --- -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)- {-# INLINE haveWildcard #-}- haveWildcard :: a -> Bool- haveWildcard = isWildcard-- -- | Determines whether the value is exactly the wildcard.- --- -- Complexity: O(1)- {-# INLINE isWildcard #-}- isWildcard :: a -> Bool- isWildcard a = a == wildcard-- -- | Wildcard-ignoring transformation.- -- If @after@ is a wildcard, returns @before@.- -- Used inside transfer to fill wildcard positions in the target basis with the original values.- --- -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)- {-# INLINE ignoreWildcard #-}- ignoreWildcard :: a -> a -> a- ignoreWildcard before after- | before == after = before- | isWildcard after = before- | otherwise = after-- -- | Wildcard-aware equality test.- -- Returns True if either operand is a wildcard.- --- -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)- {-# INLINE equal #-}- equal :: a -> a -> Bool- equal a b | isWildcard a = True- | isWildcard b = True- | otherwise = a == b-- -- | Equality operator that treats wildcards as equal.- -- Unlike '==', @(.==)@ matches tuples that partially contain wildcards.- -- This operator remains symmetric when either operand contains a wildcard;- -- its meaning stays unchanged in 0.5.x. Do not use it to match a query to- -- ledger entries: the @proj@ family matches one way since 0.5.1.0.- --- -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)- {-# INLINE (.==) #-}- (.==) :: a -> a -> Bool- (.==) a b = a == b || (haveWildcard a || haveWildcard b) && equal a b-- -- | Inequality operator that treats wildcards as equal. Negation of @(.==)@.- --- -- Complexity: O(k)- {-# INLINE (./=) #-}- (./=) :: a -> a -> Bool- (./=) a b = not (a .== b)-- -- | Wildcard-aware comparison.- -- Returns EQ if the two values are equal under @(.==)@.- --- -- Complexity: O(k)- {-# INLINE compareElement #-}- compareElement :: a -> a -> Ordering- compareElement x y- | x .== y = EQ- | otherwise = compare x y-- -- | Wildcard-aware less-than comparison.- --- -- Complexity: O(k)- (.<) :: a -> a -> Bool- (.<) x y = compareElement x y == LT-- -- | Wildcard-aware greater-than comparison.- --- -- Complexity: O(k)- (.>) :: a -> a -> Bool- (.>) x y = compareElement x y == GT-- -- | Wildcard-aware less-than-or-equal comparison.- --- -- Complexity: O(k)- (.<=) :: a -> a -> Bool- (.<=) x y = compareElement x y /= GT-- -- | Wildcard-aware greater-than-or-equal comparison.- --- -- Complexity: O(k)- (.>=) :: a -> a -> Bool- (.>=) x y = compareElement x y /= LT-- -- | Wildcard-aware maximum.- --- -- Complexity: O(k)- maxElement :: a -> a -> a- maxElement x y- | x .>= y = x- | otherwise = y-- -- | Wildcard-aware minimum.- --- -- Complexity: O(k)- minElement :: a -> a -> a- minElement x y- | x .<= y = x- | otherwise = y---- | An existential type that holds each axis of a basis with its type erased.--- Used to decompose multi-dimensional bases (tuples) into per-axis keys for indexing.-data AxisKey = forall a. Element a => AxisKey !a---- | Force the stored axis to weak head normal form without requiring 'NFData'--- from every user-defined 'Element'.-instance NFData AxisKey where- rnf (AxisKey axis) = axis `seq` ()--instance Eq AxisKey where- AxisKey x == AxisKey y = case cast y of- Nothing -> False- Just y' -> x == y'--instance Hashable AxisKey where- hashWithSalt salt (AxisKey x) = salt `hashWithSalt` (typeOf x) `hashWithSalt` x--{-# INLINE axisIsWildcard #-}-axisIsWildcard :: AxisKey -> Bool-axisIsWildcard (AxisKey x) = isWildcard x---- | A type class for decomposing a basis element into a list of per-axis t'AxisKey's.--- Overlapping instances are defined for tuple types so that each component--- is decomposed into a separate t'AxisKey'.------ Complexity: O(k) (k is the number of tuple components)-class (Element a) => AxisDecompose a where- toAxisKeys :: a -> [AxisKey]--instance {-# OVERLAPPABLE #-} Element a => AxisDecompose a where- {-# INLINE toAxisKeys #-}- toAxisKeys a = [AxisKey a]---- | Shorthand notation for the wildcard. An alias for @wildcard@.--- Write @(.#)@ when specifying patterns in projections and transfer transformations.------ Complexity: O(1)-{-# INLINE (.#) #-}-(.#) :: Element a => a-(.#) = wildcard--infix 4 .==-infix 4 ./=---------------------------------------------------------------------- * Elements----------------------------------------------------------------------- ** Account Titles--data AccountTitles = Cash -- ^ Asset: Cash (現金)- | Deposits -- ^ Asset: Savings deposits (普通預金)- | CurrentDeposits -- ^ Asset: Current deposits (当座預金)- | Securities -- ^ Asset: Securities (有価証券)- | InvestmentSecurities -- ^ Asset: Investment securities (投資有価証券)- | InvestmentInAssociate -- ^ Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定).- | LongTermNationalBonds -- ^ Asset: Long-term national bonds (長期国債)- | ShortTermNationalBonds -- ^ Asset: Short-term national bonds (短期国債)- | Products -- ^ Asset: Products (商品)。分記法用。3 分法 (仕入\/売上\/繰越商品) では 'MerchandiseInventory' を使う- | Machinery -- ^ Asset: Machinery and equipment (機械装置)- | Building -- ^ Asset: Real estate (建物)- | Vehicle -- ^ Asset: Vehicles (車両運搬具)- | StockInvestment -- ^ Asset: Stock investment (株式投資)- | EquipmentInvestment -- ^ Asset: Equipment investment (設備投資)- | LongTermLoansReceivable -- ^ Asset: Long-term loans receivable (長期貸付金)- | AccountsReceivable -- ^ Asset: Accounts receivable (売掛金)- | ShortTermLoansReceivable -- ^ Asset: Short-term loans receivable (短期貸付金)- | ReserveDepositReceivable -- ^ Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系)- | Gold -- ^ Asset: Gold (金)- | GovernmentService -- ^ Asset: Government service (政府サービス。SNA\/マクロ系)- | CapitalStock -- ^ Equity: Capital stock (資本金)- | RetainedEarnings -- ^ Equity: Retained earnings (繰越利益剰余金)- | LongTermLoansPayable -- ^ Liability: Long-term loans payable (長期借入金)- | ShortTermLoansPayable -- ^ Liability: Short-term loans payable (短期借入金)- | LoansPayable -- ^ Liability: Loans payable (借入金)- | ReserveForDepreciation -- ^ Liability: Reserve for depreciation (減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)- | DepositPayable -- ^ Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived')- | LongTermNationalBondsPayable -- ^ Liability: Long-term national bonds payable (長期国債, 発行側)- | ShortTermNationalBondsPayable -- ^ Liability: Short-term national bonds payable (短期国債, 発行側)- | ReserveDepositPayable -- ^ Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)- | CentralBankNotePayable -- ^ Liability: Central bank notes (発行銀行券。SNA\/マクロ系)- | Depreciation -- ^ Expense: Depreciation (減価償却費)- | AmortizationExpense -- ^ Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)- | SalesCost -- ^ Expense: Cost of sales (売上原価)- | BusinessTrip -- ^ Expense: Travel and transportation (旅費交通費)- | Commutation -- ^ Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)- | UtilitiesExpense -- ^ Expense: Utilities (水道光熱費)- | RentExpense -- ^ Expense: Rent (支払家賃)- | AdvertisingExpense -- ^ Expense: Advertising (広告宣伝費)- | DeliveryExpenses -- ^ Expense: Delivery (発送費)- | SuppliesExpenses -- ^ Expense: Supplies (消耗品費)- | MiscellaneousExpenses -- ^ Expense: Miscellaneous (雑費)- | WageExpenditure -- ^ Expense: Wages (給料)- | InterestExpense -- ^ Expense: Interest expense (支払利息)- | TaxesExpense -- ^ Expense: Taxes (租税公課)- | ConsumptionExpenditure -- ^ Expense: Consumption expenditure (消費支出。SNA\/マクロ系)- | SubsidyExpense -- ^ Expense: Subsidy expenditure (補助金支出。SNA\/マクロ系)- | CentralBankPaymentExpense -- ^ Expense: Central bank payment to treasury (国庫納付金支出。SNA\/マクロ系)- | Purchases -- ^ Expense: Purchases (仕入)- | NetIncome -- ^ Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)- | ValueAdded -- ^ Revenue: Value added (付加価値。SNA\/マクロ系)- | SubsidyIncome -- ^ Revenue: Subsidy income (補助金収入。SNA\/マクロ系)- | NationalBondInterestEarned -- ^ Revenue: National bond interest earned (国債利息収入)- | DepositInterestEarned -- ^ Revenue: Deposit interest earned (預金利息収入)- | GrossProfit -- ^ Revenue: Gross profit (売上総利益 — 決算振替用)- | OrdinaryProfit -- ^ Revenue: Ordinary profit (経常利益 — 決算振替用)- | InterestEarned -- ^ Revenue: Interest earned (受取利息)- | ReceiptFee -- ^ Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')- | RentalIncome -- ^ Revenue: Rental income (受取家賃)- | WageEarned -- ^ Revenue: Wage income (賃金収入。SNA\/マクロ系)- | TaxesRevenue -- ^ Revenue: Tax revenue (租税収入。SNA\/マクロ系)- | CentralBankPaymentIncome -- ^ Revenue: Central bank payment to treasury (国庫納付金収入。SNA\/マクロ系)- | Sales -- ^ Revenue: Sales (売上)- | EquityInEarningsOfInvestee -- ^ Revenue: Equity in earnings of investee- -- (持分法による投資利益). Recognized under the equity method.- | NetLoss -- ^ Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)- -- Elementary bookkeeping (日商簿記 3 級水準) additions.- -- Appended before the 'AccountTitle' wildcard so that only the- -- wildcard's Enum/Binary ordinal shifts (existing serialized- -- bases remain compatible).- --- -- Assets (資産)- | PettyCash -- ^ Asset: Petty cash (小口現金)- | NotesReceivable -- ^ Asset: Notes receivable (受取手形)- | ElectronicallyRecordedReceivable -- ^ Asset: Electronically recorded monetary claims (電子記録債権)- | CreditCardReceivable -- ^ Asset: Credit card receivable (クレジット売掛金)- | NotesLoansReceivable -- ^ Asset: Loans receivable on notes (手形貸付金)- | MerchandiseInventory -- ^ Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\/Sales\/MerchandiseInventory). For the perpetual\/specific-identification method (分記法) use 'Products' instead.- | AdvancesPaid -- ^ Asset: Advances paid (前払金)- | PrepaidExpenses -- ^ Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)- | AccruedRevenue -- ^ Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)- | OtherReceivables -- ^ Asset: Other receivables (未収入金)- | PaymentsOnBehalf -- ^ Asset: Payments made on behalf (立替金)- | SuspensePayments -- ^ Asset: Suspense payments (仮払金)- | ConsumptionTaxPaid -- ^ Asset: Consumption tax paid (仮払消費税)- | PrepaidCorporateIncomeTaxes -- ^ Asset: Prepaid corporate income taxes (仮払法人税等)- | Land -- ^ Asset: Land (土地)- | Fixtures -- ^ Asset: Fixtures and equipment (備品)- | Patent -- ^ Asset: Patent (特許権)- | Trademark -- ^ Asset: Trademark (商標権)- | Software -- ^ Asset: Software (ソフトウェア)- | CashOverShort -- ^ Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\/MiscellaneousLoss- -- Liability (負債)- | AccountsPayable -- ^ Liability: Accounts payable (買掛金)- | NotesPayable -- ^ Liability: Notes payable (支払手形)- | ElectronicallyRecordedObligations -- ^ Liability: Electronically recorded monetary obligations (電子記録債務)- | NotesLoansPayable -- ^ Liability: Loans payable on notes (手形借入金)- | BankOverdraft -- ^ Liability: Bank overdraft (当座借越)- | AdvancesReceived -- ^ Liability: Advances received (前受金)- | UnearnedRevenue -- ^ Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)- | AccruedExpenses -- ^ Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)- | OtherPayables -- ^ Liability: Other payables (未払金)- | DepositsReceived -- ^ Liability: Deposits received (預り金)- | SuspenseReceipts -- ^ Liability: Suspense receipts (仮受金)- | ConsumptionTaxReceived -- ^ Liability: Consumption tax received (仮受消費税)- | AccruedConsumptionTax -- ^ Liability: Accrued (unpaid) consumption tax (未払消費税)- | AccruedCorporateIncomeTaxes -- ^ Liability: Accrued (unpaid) corporate income taxes (未払法人税等)- | UnpaidDividends -- ^ Liability: Unpaid dividends (未払配当金)- | AllowanceForDoubtfulAccounts -- ^ Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Division is Assets with @isContra = True@ (Definition 7 amendment, 0.5.0.0); home side stays Credit, values stay non-negative, Hat\/Not structure intact. B\/S deduction (net) presentation is the Write side's job.- | AccumulatedDepreciation -- ^ Asset (contra): Accumulated depreciation (減価償却累計額), valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Division is Assets with @isContra = True@ for the same reason as 'AllowanceForDoubtfulAccounts'. This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name.- -- Equity (資本)- | LegalRetainedEarnings -- ^ Equity: Legal (appropriated) retained earnings reserve (利益準備金)- | CumulativeTranslationAdjustment -- ^ Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)- -- Cost (費用)- | ProvisionForDoubtfulAccounts -- ^ Cost: Provision for doubtful accounts (貸倒引当金繰入)- | BadDebtLoss -- ^ Cost: Bad debt loss (貸倒損失)- | LossOnSalesOfFixedAssets -- ^ Cost: Loss on sales of fixed assets (固定資産売却損)- | LossOnSalesOfNotesReceivable -- ^ Cost: Loss on sales of notes receivable (手形売却損)- | PaymentFees -- ^ Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'- | MiscellaneousLoss -- ^ Cost: Miscellaneous loss (雑損)- | CorporateIncomeTaxes -- ^ Cost: Corporate income taxes (法人税等)- | CommunicationExpenses -- ^ Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also "Communication"); 'Commutation' is retained for backward compatibility.- -- Revenue (収益)- | GainOnSalesOfFixedAssets -- ^ Revenue: Gain on sales of fixed assets (固定資産売却益)- | RecoveryOfBadDebts -- ^ Revenue: Recovery of bad debts written off (償却債権取立益)- | MiscellaneousIncome -- ^ Revenue: Miscellaneous income (雑益)- | ReversalOfAllowanceForDoubtfulAccounts -- ^ Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released).- -- V-Land 2: JCCI grade-2 commercial-bookkeeping vocabulary.- | TimeDeposits -- ^ Assets: Time deposits (定期預金)- | LoansReceivable -- ^ Assets: Loans receivable (貸付金)- | GiftCertificatesReceived -- ^ Assets: Gift certificates received (受取商品券)- | SecurityDepositsPaid -- ^ Assets: Guarantee deposits (差入保証金)- | SuppliesOnHand -- ^ Assets: Supplies (貯蔵品)- | ContractAssets -- ^ Assets: Contract assets (契約資産)- | IncomeTaxesRefundReceivable -- ^ Assets: Income taxes refund receivable (未収還付法人税等)- | WorkInProcess -- ^ Assets: Work in process (仕掛品)- | DeferredTaxAssets -- ^ Assets: Deferred tax assets (繰延税金資産)- | LeasedAssets -- ^ Assets: Leased assets (リース資産)- | ToolsAndInstruments -- ^ Assets: Tools and instruments (工具器具)- | ConstructionInProgress -- ^ Assets: Construction in progress (建設仮勘定)- | Goodwill -- ^ Assets: Goodwill (のれん)- | SoftwareInProgress -- ^ Assets: Software in progress (ソフトウェア仮勘定)- | LongTermPrepaidExpenses -- ^ Assets: Long-term prepaid expenses (長期前払費用)- | DishonoredNotesReceivable -- ^ Assets: Dishonored notes receivable (不渡手形)- | PrepaidPensionCost -- ^ Assets: Prepaid pension costs (前払年金費用)- | NetDefinedBenefitAsset -- ^ Assets: Retirement benefit asset (退職給付に係る資産)- | DepositsInSpecialAccounts -- ^ Assets: Deposits in special accounts (別段預金)- | Structures -- ^ Assets: Structures (構築物)- | LeaseholdRights -- ^ Assets: Leasehold interests in land (借地権)- | NonOperatingNotesReceivable -- ^ Assets: Non-operating notes receivable (営業外受取手形)- | NonOperatingElectronicallyRecordedReceivable -- ^ Assets: Electronically recorded monetary claims - non-operating (営業外電子記録債権)- | RefundLiabilities -- ^ Liability: Refund liabilities (返金負債)- | NonOperatingNotesPayable -- ^ Liability: Non-operating notes payable (営業外支払手形)- | NonOperatingElectronicallyRecordedObligations -- ^ Liability: Electronically recorded obligations - non-operating (営業外電子記録債務)- | BonusesPayable -- ^ Liability: Accrued bonuses (未払賞与)- | AllowanceForRepairs -- ^ Liability: Provision for repairs (修繕引当金)- | AllowanceForProductWarranties -- ^ Liability: Provision for warranties (商品保証引当金)- | AllowanceForBonuses -- ^ Liability: Provision for bonuses (賞与引当金)- | DeferredTaxLiabilities -- ^ Liability: Deferred tax liabilities (繰延税金負債)- | LeaseObligations -- ^ Liability: Lease liabilities (リース債務)- | GuaranteeDepositsReceived -- ^ Liability: Guarantee deposits received (預り保証金)- | AllowanceForRetirementBenefits -- ^ Liability: Provision for retirement benefits (退職給付引当金)- | LongTermOtherPayables -- ^ Liability: Long-term accounts payable - other (長期未払金)- | NetDefinedBenefitLiability -- ^ Liability: Retirement benefit liability (退職給付に係る負債)- | StockSubscriptionDeposits -- ^ Equity: Deposits for subscriptions of shares (株式申込証拠金)- | LegalCapitalSurplus -- ^ Equity: Legal capital surplus (資本準備金)- | OtherCapitalSurplus -- ^ Equity: Other capital surplus (その他資本剰余金)- | DividendEqualizationReserve -- ^ Equity: Reserve for dividend equalization (配当平均積立金)- | RepairFundReserve -- ^ Equity: Reserve for repairs (修繕積立金)- | ConstructionFundReserve -- ^ Equity: Reserve for new construction (新築積立金)- | GeneralReserve -- ^ Equity: General reserve (別途積立金)- | ValuationDifferenceOnOtherSecurities -- ^ Equity: Valuation difference on available-for-sale securities (その他有価証券評価差額金)- | NonControllingInterests -- ^ Equity: Non-controlling interests (非支配株主持分)- | CapitalSurplus -- ^ Equity: Capital surplus (資本剰余金)- | EarnedSurplus -- ^ Equity: Retained earnings (利益剰余金)- | ServiceRevenue -- ^ Revenue: Service revenue (役務収益)- | OperatingRevenue -- ^ Revenue: Operating revenue (営業収益)- | GainOnSalesOfSecurities -- ^ Revenue: Gain on sale of securities (有価証券売却益)- | GainOnValuationOfSecurities -- ^ Revenue: Gain on valuation of securities (有価証券評価益)- | DividendsReceived -- ^ Revenue: Dividend income (受取配当金)- | InterestOnSecurities -- ^ Revenue: Interest on securities (有価証券利息)- | GainOnSalesOfInvestmentSecurities -- ^ Revenue: Gain on sale of investment securities (投資有価証券売却益)- | InsuranceGain -- ^ Revenue: Gain on insurance claims (保険差益)- | GainOnBargainPurchase -- ^ Revenue: Gain on bargain purchase (負ののれん発生益)- | ReversalOfAllowanceForRepairs -- ^ Revenue: Reversal of provision for repairs (修繕引当金戻入)- | ReversalOfAllowanceForProductWarranties -- ^ Revenue: Reversal of provision for warranties (商品保証引当金戻入)- | GainOnDonationOfFixedAssets -- ^ Revenue: Gain on receipt of donated non-current assets (固定資産受贈益)- | GainOnNationalSubsidies -- ^ Revenue: Gain on receipt of national subsidies (国庫補助金受贈益)- | GainOnConstructionGrants -- ^ Revenue: Gain on contribution received for construction (工事負担金受贈益)- | LandRentReceived -- ^ Revenue: Rental income from land (受取地代)- | SalesRebates -- ^ Revenue: Sales rebates (売上割戻)- | CostOfServices -- ^ Cost: Cost of services (役務原価)- | OperatingExpenses -- ^ Cost: Operating expenses (営業費用)- | InventoryShrinkageLoss -- ^ Cost: Inventory shrinkage loss (棚卸減耗損)- | LossOnValuationOfMerchandise -- ^ Cost: Valuation loss on goods (商品評価損)- | Bonuses -- ^ Cost: Bonuses (賞与)- | RetirementBenefitExpenses -- ^ Cost: Retirement benefit expenses (退職給付費用)- | ProvisionForRepairs -- ^ Cost: Provision for repairs (修繕引当金繰入)- | ProvisionForBonuses -- ^ Cost: Provision for bonuses (賞与引当金繰入)- | ProvisionForProductWarranties -- ^ Cost: Provision for warranties (商品保証引当金繰入)- | ResearchAndDevelopmentExpenses -- ^ Cost: Research and development expenses (研究開発費)- | AmortizationOfGoodwill -- ^ Cost: Amortization of goodwill (のれん償却)- | AmortizationOfSoftware -- ^ Cost: Amortization of software (ソフトウェア償却)- | AmortizationOfPatents -- ^ Cost: Amortization of patent rights (特許権償却)- | LeaseExpenses -- ^ Cost: Lease expenses (支払リース料)- | IncorporationExpenses -- ^ Cost: Organization expenses (創立費)- | StockIssuanceCosts -- ^ Cost: Share issuance costs (株式交付費)- | BusinessCommencementExpenses -- ^ Cost: Business commencement expenses (開業費)- | DevelopmentExpenses -- ^ Cost: Development expenses (開発費)- | LossOnSalesOfElectronicallyRecordedReceivables -- ^ Cost: Loss on sale of electronically recorded monetary claims (電子記録債権売却損)- | LossOnSalesOfReceivables -- ^ Cost: Loss on sale of receivables (債権売却損)- | LossOnSalesOfSecurities -- ^ Cost: Loss on sale of securities (有価証券売却損)- | LossOnValuationOfSecurities -- ^ Cost: Loss on valuation of securities (有価証券評価損)- | LossOnSalesOfInvestmentSecurities -- ^ Cost: Loss on sale of investment securities (投資有価証券売却損)- | LossOnFire -- ^ Cost: Loss on fire (火災損失)- | LossOnRetirementOfFixedAssets -- ^ Cost: Loss on retirement of non-current assets (固定資産除却損)- | LossOnReductionOfFixedAssets -- ^ Cost: Loss on tax purpose reduction entry of non-current assets (固定資産圧縮損)- | AdditionalIncomeTaxesForPriorPeriods -- ^ Cost: Additional income taxes for prior periods (追徴法人税等)- | RefundOfIncomeTaxes -- ^ Cost: Refund of income taxes (還付法人税等)- | PurchaseRebates -- ^ Cost: Purchase rebates (仕入割戻)- | WelfareExpenses -- ^ Cost: Welfare expenses (福利厚生費)- | MaintenanceExpenses -- ^ Cost: Maintenance expenses (保守費)- | StatutoryWelfareExpenses -- ^ Cost: Legal welfare expenses (法定福利費)- | LandRentPaid -- ^ Cost: Rent expenses on land (支払地代)- | InsuranceExpense -- ^ Cost: Insurance expenses (保険料)- | RepairsExpense -- ^ Cost: Repair expenses (修繕費)- | StorageExpenses -- ^ Cost: Storage costs (保管費)- | MembershipFees -- ^ Cost: Membership fees (諸会費)- | IncomeSummary -- ^ Assets: Income summary (損益)- | SuspenseAccount -- ^ Assets: Suspense account (未決算)- | ForeignExchangeGains -- ^ Revenue: Foreign exchange gains (為替差益)- | ForeignExchangeLosses -- ^ Cost: Foreign exchange losses (為替差損)- | ContraAccountForGuaranteeObligations -- ^ Assets: Contra account for guarantee obligations (保証債務見返)- | GuaranteeObligations -- ^ Liability: Guarantee obligations (保証債務)- | IncomeTaxesAdjustment -- ^ Cost: Income taxes - deferred (法人税等調整額)- | BranchCurrentAccount -- ^ Assets: Branch current account (支店)- | HeadOfficeCurrentAccount -- ^ Assets: Head office current account (本店)- | NetIncomeAttributableToNCI -- ^ Cost: Profit attributable to non-controlling interests (非支配株主に帰属する当期純利益)- | NetLossAttributableToNCI -- ^ Revenue: Loss attributable to non-controlling interests (非支配株主に帰属する当期純損失)- | TradingSecurities -- ^ Assets: Trading securities (売買目的有価証券)- | HeldToMaturityBonds -- ^ Assets: Held-to-maturity bonds (満期保有目的債券)- | SubsidiaryStocks -- ^ Assets: Shares of subsidiaries (子会社株式)- | AffiliateStocks -- ^ Assets: Shares of associates (関連会社株式)- | AvailableForSaleSecurities -- ^ Assets: Available-for-sale securities (その他有価証券)- | ConsumptionTaxRefundReceivable -- ^ Assets: Consumption tax refund receivable (未収還付消費税)- | PropertyTaxPayable -- ^ Liability: Property tax payable (未払固定資産税)- | DepositsReceivedFromOfficers -- ^ Liability: Deposits received from officers (役員預り金)- | EntertainmentExpenses -- ^ Cost: Entertainment expenses (交際費)- | MeetingExpenses -- ^ Cost: Meeting expenses (会議費)- | NewspaperBooksExpenses -- ^ Cost: Newspapers and books (新聞図書費)- | RawMaterials -- ^ Assets: Raw materials (原材料)- | GoodsInTransit -- ^ Assets: Goods in transit (未着品)- | AccountTitle -- ^ Wildcard (ワイルドカード — 任意の科目にマッチ。projWithBase 等の問い合わせ用で, 実 posting には使わない)- deriving (Show, Ord, Eq, Enum, Generic, Bounded)---- Commutation の実用法は examples の文脈から「通信費」であることが確認済--- (2026-06-11 調査)。Enum/Binary 序数の安定のため削除はせず, 新名称への移行を促す。-{-# DEPRECATED Commutation "通信費 (communication expenses) — use 'CommunicationExpenses' instead" #-}--instance NFData AccountTitles--instance Hashable AccountTitles where- {-# INLINE hashWithSalt #-}- hashWithSalt salt x = hashWithSalt salt (fromEnum x)--instance Binary.Binary AccountTitles where- {-# INLINE put #-}- put = BinaryPut.putWord16be . fromIntegral . fromEnum- {-# INLINE get #-}- get = do- tag <- BinaryGet.getWord16be- let maxTag = fromIntegral (fromEnum (maxBound :: AccountTitles))- if tag <= maxTag- then pure (toEnum (fromIntegral tag))- else fail ("AccountTitles: invalid binary tag " ++ show tag)--instance Element AccountTitles where- {-# INLINE wildcard #-}- wildcard = AccountTitle------ | Name :: Name of an item-type Name = Text---- | Subject of an account title-type Subject = Text-instance Element Text where-- {-# INLINE wildcard #-}- wildcard = T.empty---- | Currency unit or physical quantity-data CountUnit = Yen- | Dollar- | Euro- | CNY- | Amount- | CountUnit- deriving (Show, Ord, Eq, Enum,Generic)--instance NFData CountUnit--instance Hashable CountUnit where- {-# INLINE hashWithSalt #-}- hashWithSalt salt x = hashWithSalt salt (fromEnum x)--instance Binary.Binary CountUnit where- {-# INLINE put #-}- put = Binary.putWord8 . fromIntegral . fromEnum- {-# INLINE get #-}- get = toEnum . fromIntegral <$> Binary.getWord8--instance Element CountUnit where-- {-# INLINE wildcard #-}- wildcard = CountUnit----- TimeOfDay internally holds hour, minute, and second (Pico), so each is hashed individually-instance Hashable TimeOfDay where- hashWithSalt salt (TimeOfDay hour minute sec) =- salt `hashWithSalt` hour `hashWithSalt` minute `hashWithSalt` sec---- Day internally holds an Integer in ModifiedJulianDay format, so that is used for hashing-instance Hashable Day where- hashWithSalt salt day = hashWithSalt salt (toModifiedJulianDay day)--instance Element TimeOfDay where- wildcard = Time.midnight--instance Element Day where- wildcard = ModifiedJulianDay 0--instance (Element a ,Element b)- => Element (a, b) where-- {-# INLINE wildcard #-}- wildcard = (wildcard, wildcard)-- {-# INLINE haveWildcard #-}- haveWildcard (a,b)- = haveWildcard a- || haveWildcard b-- {-# INLINE equal #-}- equal (a1, a2) (b1, b2)- = (a1 .== b1)- && (a2 .== b2)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2) (b1, b2)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2)-- {-# INLINE compareElement #-}- compareElement (a1, a2) (b1, b2)- = case compareElement a1 b1 of- EQ -> compareElement a2 b2- x -> x--instance {-# OVERLAPPING #-} (Element a, Element b)- => AxisDecompose (a, b) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b) = [AxisKey a, AxisKey b]--instance (Element a, Element b, Element c)- => Element (a, b, c) where-- {-# INLINE wildcard #-}- wildcard = ( wildcard- , wildcard- , wildcard)-- {-# INLINE haveWildcard #-}- haveWildcard (a,b,c)- = haveWildcard a- || haveWildcard b- || haveWildcard c--- {-# INLINE equal #-}- equal (a1, a2, a3) (b1, b2, b3)- = (a1 .== b1)- && (a2 .== b2)- && (a3 .== b3)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2, a3) (b1, b2, b3)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2- , ignoreWildcard a3 b3)-- {-# INLINE compareElement #-}- compareElement (a1, a2, a3) (b1, b2, b3)- = compareElement ((a1, a2), a3)- ((b1, b2), b3)--instance {-# OVERLAPPING #-} (Element a, Element b, Element c)- => AxisDecompose (a, b, c) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b, c) = [AxisKey a, AxisKey b, AxisKey c]---instance (Element a, Element b, Element c, Element d)- => Element (a, b, c, d) where-- {-# INLINE wildcard #-}- wildcard = ( wildcard- , wildcard- , wildcard- , wildcard)--- {-# INLINE haveWildcard #-}- haveWildcard (a,b,c,d)- = haveWildcard a- || haveWildcard b- || haveWildcard c- || haveWildcard d-- {-# INLINE equal #-}- equal (a1, a2, a3, a4) (b1, b2, b3, b4)- = (a1 .== b1)- && (a2 .== b2)- && (a3 .== b3)- && (a4 .== b4)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2, a3, a4) (b1, b2, b3, b4)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2- , ignoreWildcard a3 b3- , ignoreWildcard a4 b4)-- {-# INLINE compareElement #-}- compareElement (a1, a2, a3, a4) (b1, b2, b3, b4)- = compareElement ((a1, a2, a3), a4)- ((b1, b2, b3), b4)--instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d)- => AxisDecompose (a, b, c, d) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b, c, d) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d]---instance (Element a, Element b, Element c, Element d, Element e)- => Element (a, b, c, d, e) where-- {-# INLINE wildcard #-}- wildcard = ( wildcard- , wildcard- , wildcard- , wildcard- , wildcard)--- {-# INLINE haveWildcard #-}- haveWildcard (a,b,c,d,e)- = haveWildcard a- || haveWildcard b- || haveWildcard c- || haveWildcard d- || haveWildcard e-- {-# INLINE equal #-}- equal (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)- = (a1 .== b1)- && (a2 .== b2)- && (a3 .== b3)- && (a4 .== b4)- && (a5 .== b5)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2- , ignoreWildcard a3 b3- , ignoreWildcard a4 b4- , ignoreWildcard a5 b5)-- {-# INLINE compareElement #-}- compareElement (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)- = compareElement ((a1, a2, a3, a4), a5)- ((b1, b2, b3, b4), b5)--instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e)- => AxisDecompose (a, b, c, d, e) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b, c, d, e) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e]---instance (Element a, Element b, Element c, Element d, Element e, Element f)- => Element (a, b, c, d, e, f) where-- {-# INLINE wildcard #-}- wildcard = ( wildcard- , wildcard- , wildcard- , wildcard- , wildcard- , wildcard)-- {-# INLINE haveWildcard #-}- haveWildcard (a,b,c,d,e,f)- = haveWildcard a- || haveWildcard b- || haveWildcard c- || haveWildcard d- || haveWildcard e- || haveWildcard f-- {-# INLINE equal #-}- equal (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)- = (a1 .== b1)- && (a2 .== b2)- && (a3 .== b3)- && (a4 .== b4)- && (a5 .== b5)- && (a6 .== b6)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2- , ignoreWildcard a3 b3- , ignoreWildcard a4 b4- , ignoreWildcard a5 b5- , ignoreWildcard a6 b6)-- {-# INLINE compareElement #-}- compareElement (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)- = compareElement ((a1, a2, a3, a4, a5), a6)- ((b1, b2, b3, b4, b5), b6)--instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e, Element f)- => AxisDecompose (a, b, c, d, e, f) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b, c, d, e, f) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e, AxisKey f]---instance (Element a, Element b, Element c, Element d, Element e, Element f, Element g)- => Element (a, b, c, d, e, f, g) where- {-# INLINE wildcard #-}- wildcard = ( wildcard- , wildcard- , wildcard- , wildcard- , wildcard- , wildcard- , wildcard)-- {-# INLINE haveWildcard #-}- haveWildcard (a,b,c,d,e,f,g)- = haveWildcard a- || haveWildcard b- || haveWildcard c- || haveWildcard d- || haveWildcard e- || haveWildcard f- || haveWildcard g-- {-# INLINE equal #-}- equal (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)- = (a1 .== b1)- && (a2 .== b2)- && (a3 .== b3)- && (a4 .== b4)- && (a5 .== b5)- && (a6 .== b6)- && (a7 .== b7)-- {-# INLINE ignoreWildcard #-}- ignoreWildcard (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)- = ( ignoreWildcard a1 b1- , ignoreWildcard a2 b2- , ignoreWildcard a3 b3- , ignoreWildcard a4 b4- , ignoreWildcard a5 b5- , ignoreWildcard a6 b6- , ignoreWildcard a7 b7)-- {-# INLINE compareElement #-}- compareElement (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)- = compareElement ((a1, a2, a3, a4, a5, a6), a7)- ((b1, b2, b3, b4, b5, b6), b7)+-- | Import "ExchangeAlgebra.Algebra.Element" instead.+module ExchangeAlgebra.Algebra.Base.Element {-# DEPRECATED "Use ExchangeAlgebra.Algebra.Element instead." #-}+ ( Element(..), AxisKey(..), axisIsWildcard, AxisDecompose(..), (.#)+ , AccountTitles(..), Name, Subject, CountUnit(..), Hashable(..), Generic+ ) where -instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e, Element f, Element g)- => AxisDecompose (a, b, c, d, e, f, g) where- {-# INLINE toAxisKeys #-}- toAxisKeys (a, b, c, d, e, f, g) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e, AxisKey f, AxisKey g]+import ExchangeAlgebra.Algebra.Element.Representation+ ( Element(..), AxisKey(..), axisIsWildcard, AxisDecompose(..), (.#)+ , Name, Subject, CountUnit(..), Hashable(..), Generic )+import ExchangeAlgebra.Accounting.Account (AccountTitles(..))
+ src/ExchangeAlgebra/Algebra/Base/Representation.hs view
@@ -0,0 +1,289 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE DeriveGeneric #-}++-- | Generic bases and Hat labels for exchange algebra.+module ExchangeAlgebra.Algebra.Base.Representation where++import ExchangeAlgebra.Algebra.Element.Representation+import Data.Time (Day, TimeOfDay)+import GHC.Stack (HasCallStack, callStack, prettyCallStack)+import qualified Data.Binary as Binary+import Control.DeepSeq (NFData(..))++-- | Raise an error with the current call stack appended.+customError :: HasCallStack => String -> a+customError msg = error (msg ++ "\nCallStack:\n" ++ prettyCallStack callStack)++------------------------------------------------------------------+-- * Base conditions+------------------------------------------------------------------++-- ** Base+------------------------------------------------------------------+{- | Base class definition.+ Any type that is an instance of this class qualifies as a base.+-}++class (Element a) => BaseClass a where+ compareBase :: a -> a -> Ordering+ compareBase = compareElement++instance (Element e1, Element e2)+ => BaseClass (e1, e2) where++instance (Element e1, Element e2, Element e3)+ => BaseClass (e1, e2, e3) where++instance (Element e1, Element e2, Element e3, Element e4)+ => BaseClass (e1, e2, e3, e4) where++instance (Element e1, Element e2, Element e3, Element e4, Element e5)+ => BaseClass (e1, e2, e3, e4, e5) where++instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6)+ => BaseClass (e1, e2, e3, e4, e5, e6) where++-- 7-tuple: 'Element'/'AxisDecompose' already provide 7-tuple instances; this+-- closes the gap so every Element tuple arity is also usable as a base.+instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6, Element e7)+ => BaseClass (e1, e2, e3, e4, e5, e6, e7) where+++------------------------------------------------------------------+-- ** HatBase+------------------------------------------------------------------++-- | Type class for bases with a Hat component. Provides functionality to decompose and+-- compose a base into its Hat part and BasePart. Manages the Hat (decrease) \/+-- Not (increase) label at the base level in exchange algebra. Note that Hat\/Not+-- is __not__ the debit\/credit distinction: the side of a posting is determined+-- by the account division /together with/ this label (see+-- 'ExchangeAlgebra.Accounting.Exchange.whichSide' — an+-- account sits on its home side when 'Not' and on the opposite side when v'Hat').+class (BaseClass a, BaseClass (BasePart a), AxisDecompose (BasePart a)) => HatBaseClass a where+ -- | The type of the base part excluding the Hat.+ type BasePart a+ -- | Extract the base part excluding the Hat. Complexity: O(1)+ base :: (BaseClass (BasePart a)) => a -> BasePart a+ -- | Extract the Hat part. Complexity: O(1)+ hat :: a -> Hat++ -- | Reconstruct a base from a Hat and a BasePart. Complexity: O(1)+ merge :: Hat -> BasePart a -> a++ -- | Convert to the Hat side. Complexity: O(1)+ toHat :: a -> a+ -- | Convert to the Not side. Complexity: O(1)+ toNot :: a -> a+ -- | Reverse Hat/Not. Complexity: O(1)+ revHat :: a -> a+ -- | Test whether the base is Hat. Complexity: O(1)+ isHat :: a -> Bool+ -- | Test whether the base is Not. Complexity: O(1)+ isNot :: a -> Bool++ -- | Compare bases with Hat. Defaults to 'compareBase'. Complexity: O(k)+ compareHatBase :: a -> a -> Ordering+ compareHatBase = compareBase++------------------------------------------------------------------+-- | Hat definition+data Hat = Hat+ | Not+ | HatNot+ deriving (Enum, Eq, Ord, Show, Generic)++instance NFData Hat++instance Hashable Hat where+instance Binary.Binary Hat++instance Element Hat where+ wildcard = HatNot++ {-# INLINE equal #-}+ equal Hat Hat = True+ equal Hat Not = False+ equal Not Hat = False+ equal Not Not = True+ equal _ _ = True++instance BaseClass Hat where++-- | A one-constructor basis type whose only value is its own wildcard.+data BaseForSingleHat = BaseForSingleHat+ deriving (Eq,Ord,Generic)++instance NFData BaseForSingleHat++instance Show BaseForSingleHat where+ show _ = ""++instance Hashable BaseForSingleHat where+instance Binary.Binary BaseForSingleHat++instance Element BaseForSingleHat where+ wildcard = BaseForSingleHat+ equal _ _ = True++instance BaseClass BaseForSingleHat where++instance HatBaseClass Hat where+ type BasePart Hat = BaseForSingleHat+ hat = id+ base _ = BaseForSingleHat++ -- NB. 'merge'\/'revHat'\/'isHat' below match only @Hat@ and @Not@. The third+ -- v'Hat' constructor @HatNot@ is the formalization-only wildcard state (the+ -- paper convention is the 2-state Hat\/Not; see CLAUDE.md "HatNot wildcard").+ -- These methods are never invoked on a @HatNot@ label by library code, so the+ -- non-exhaustive @-Wincomplete-patterns@ here is by design (audited). Adding a+ -- @HatNot@ case would change behavior (turn the pattern-match failure into a+ -- different error), so it is intentionally left as-is rather than masked.+ merge Hat _ = Hat+ merge Not _ = Not++ {-# INLINE toHat #-}+ toHat _ = Hat++ {-# INLINE toNot #-}+ toNot _ = Not++ {-# INLINE revHat #-}+ revHat Hat = Not+ revHat Not = Hat++ {-# INLINE isHat #-}+ isHat Hat = True+ isHat Not = False++ {-# INLINE isNot #-}+ isNot = not . isHat+------------------------------------------------------------------++-- | Base with Hat. Attaches a Hat (decrease) / Not (increase) label to a base+-- element such as a currency unit. Use the constructor @(:<)@ as in @Hat :< Yen@.+data HatBase a where+ (:<) :: (BaseClass a) => {_hat :: Hat, _base :: a } -> HatBase a++instance (BaseClass a, NFData a) => NFData (HatBase a) where+ rnf (hatValue :< baseValue) = rnf hatValue `seq` rnf baseValue++instance (BaseClass a, Binary.Binary a) => Binary.Binary (HatBase a) where+ put (h :< b) = Binary.put h >> Binary.put b+ get = (:<) <$> Binary.get <*> Binary.get++instance Show (HatBase a) where+ show (h :< b) = show h ++ ":<" ++ show b++instance Eq (HatBase a) where+ {-# INLINE (==) #-}+ (==) (h1 :< b1) (h2 :< b2) = h1 == h2 && b1 == b2+ {-# INLINE (/=) #-}+ (/=) x y = not (x == y)++instance Ord (HatBase a) where+ {-# INLINE compare #-}+ compare (h :< b) (h' :< b') =+ case compare b b' of+ EQ -> compare h h'+ x -> x++instance (BaseClass a) => Hashable (HatBase a) where+ hashWithSalt salt (h:<b) = salt `hashWithSalt` h+ `hashWithSalt` b++-- | Element (HatBase a)+-- haveWildcard+-- >>> haveWildcard (HatNot:<Amount :: HatBase CountUnit)+-- True+--+-- (.==)+-- >>> Not:<(Yen, Dollar) == Not:<(Yen,(.#))+-- False+--+-- >>> Not:<(Yen, Dollar) .== Not:<(Yen,(.#))+-- True+--+-- compareElement+-- >>> type Test = HatBase CountUnit+-- >>> compareHatBase (Not:<Amount :: Test) (Not:<(.#) :: Test)+-- EQ+--+-- ignoreWildcard+-- >>> ignoreWildcard (Not:<(Yen,Dollar)) (Hat:<(Yen,Amount))+-- Hat:<(Yen,Amount)+--+-- >>> ignoreWildcard (Not:<(Yen,Dollar)) (Hat:<(Yen,(.#)))+-- Hat:<(Yen,Dollar)+--+-- >>> ignoreWildcard (Not:<(Yen,(.#))) (HatNot:<((.#),Amount))+-- Not:<(Yen,Amount)+++instance (BaseClass a) => Element (HatBase a) where+ wildcard = HatNot :<wildcard++ haveWildcard (h:<b)+ = isWildcard h+ || haveWildcard b++ {-# INLINE equal #-}+ equal (h1:<b1) (h2:<b2) = h1 .== h2 && b1 .== b2++ ignoreWildcard (h1:<b1) (h2:<b2)+ = (ignoreWildcard h1 h2) :< (ignoreWildcard b1 b2)+++ compareElement (h1:<b1) (h2:<b2)+ = case compareElement b1 b2 of+ EQ -> compareElement h1 h2+ x -> x++instance (BaseClass a) => BaseClass (HatBase a) where++instance (BaseClass a, AxisDecompose a) => HatBaseClass (HatBase a) where+ type BasePart (HatBase a) = a++ hat = _hat++ base = _base++ merge = (:<)++ {-# INLINE toHat #-}+ toHat (_:<b) = Hat:<b++ {-# INLINE toNot #-}+ toNot (_:<b) = Not:<b++ {-# INLINE revHat #-}+ revHat (Hat :< b) = Not :< b+ revHat (Not :< b) = Hat :< b++ {-# INLINE isHat #-}+ isHat (Hat :< _) = True+ isHat (Not :< _) = False+ isHat (HatNot :< _) = customError "called HatNot"++ {-# INLINE isNot #-}+ isNot = not . isHat+++instance BaseClass Name where++-- *** CountUnit only (redundant algebra base)+instance BaseClass CountUnit where++-- *** Day only (redundant algebra base)+instance BaseClass Day where++-- *** TimeOfDay only (redundant algebra base)+instance BaseClass TimeOfDay where
+ src/ExchangeAlgebra/Algebra/Core.hs view
@@ -0,0 +1,50 @@+{- | Generic exchange algebra operations.++Choose an operation by the question you need to answer:++@+Question Operation+Build a posting or sequence .@, .++Cancel opposing values bar+Select bases proj, projNetNorm+Measure values norm, balanceBy+@++Import "ExchangeAlgebra.Accounting.Account" alongside this module when a+base uses account titles, and "ExchangeAlgebra.Accounting.Exchange" for+accounting operations. "ExchangeAlgebra.Algebra.Internal" exposes the raw representation.+-}+module ExchangeAlgebra.Algebra.Core+ ( Redundant(..)+ , Alg(Zero, (:@), _val, _hatBase)+ , isZero+ , (.@)+ , (<@)+ , vals+ , bases+ , fromList+ , toList+ , foldEntries+ , sigma+ , sigma2When+ , sigmaFromMap+ , toASCList+ , map+ , mapPosting+ , mapMaybePosting+ , mapBasePart+ , extendBy+ , filter+ , proj+ , projNetNorm+ , balanceBy+ , balanceMapBy+ , netPairMapBy+ , foldEntriesToMap+ , decBy+ , postFromNetBy+ , unionsMerge+ ) where++import Prelude hiding (map, filter)+import ExchangeAlgebra.Algebra.Core.Representation
+ src/ExchangeAlgebra/Algebra/Core/Representation.hs view
@@ -0,0 +1,1921 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Generic exchange algebra representation and operations.+module ExchangeAlgebra.Algebra.Core.Representation where++import ExchangeAlgebra.Algebra.Base.Representation+import ExchangeAlgebra.Algebra.Element.Representation (Element(..), AxisDecompose(..), AxisKey(..), CountUnit(..), ( .# ), matchesQuery)+import ExchangeAlgebra.Algebra.Value (MoneyDouble, MoneyDecimal)+import ExchangeAlgebra.Algebra.Value.Class++import qualified Data.List as L (foldl', sort, sortOn, any)+import Prelude hiding (map, head, filter, tail, traverse, mapM)+import qualified Data.HashMap.Strict as Map+import qualified Data.IntMap.Strict as IntMap+import qualified Data.IntSet as IntSet+import qualified Data.Map.Strict as M+import qualified Data.Foldable as Foldable (foldl', foldr)+import qualified Data.Sequence as Seq+import Data.Sequence (Seq)+import qualified Data.Maybe as Maybe+import Control.DeepSeq+import GHC.Stack (HasCallStack)+import qualified Data.Binary as Binary++-- * Algebra+------------------------------------------------------------+------------------------------------------------------------------+-- ** Definition of Redundancy (subclassing this makes a redundant algebra)+------------------------------------------------------------------++-- | Type class for Redundant Algebra.+-- Provides fundamental exchange algebra operations: hat, bar, norm, scalar product, and compress.+--+-- Redundant ⊃ Exchange+--+-- hat calculation+-- >>> (.^) (10.@Not:<Yen .+ 10.@Hat:<Dollar)+-- 10.00:@Hat:<Yen .+ 10.00:@Not:<Dollar+--+-- bar calculation+-- >>> x = 10.@Not:<Yen .+ 10.@Hat:<Dollar+-- >>> y = 5.@Hat:<Yen .+ 5.@Not:<Dollar+-- >>> (.-) $ x .+ y+-- 5.00:@Not:<Yen .+ 5.00:@Hat:<Dollar+--+-- norm calculation+-- >>> norm $ 10.@Not:<Yen .+ 10.@Hat:<Dollar+-- 20.0+--+-- (.*) calculation+-- >>> (.*) 5 $ 10.@Not:<Yen .+ 10.@Hat:<Dollar+-- 50.00:@Not:<Yen .+ 50.00:@Hat:<Dollar+--+-- compress calculation+-- >>> compress $ 10.@Not:<Yen .+ 5.@Hat:<Yen .+ 3.@Not:<Yen+-- 5.00:@Hat:<Yen .+ 13.00:@Not:<Yen+--+-- == Redundant-algebra axioms (Akagi 2026, Appendix A, Definition 6)+--+-- The operations above satisfy the following /axioms/ (the paper states five;+-- they are verified as QuickCheck properties in @test\/Spec.hs@, see+-- @axiomProperties@). Axioms 4 and 5 require exact arithmetic, including+-- intermediate products and sums; floating-point readouts can round differently:+--+-- 1. Hat involution: @(.^) ((.^) x) = x@+-- 2. Scalar on an element: @a '.*' (v ':@' b) = (a*v) ':@' b@+-- 3. Scalar distribution: @a '.*' (x '.+' y) = (a '.*' x) '.+' (a '.*' y)@+-- 4. Norm homogeneity: @'norm' (a '.*' x) = a * 'norm' x@ (for @a >= 0@)+-- 5. Norm additivity: @'norm' (x '.+' y) = 'norm' x + 'norm' y@+--+-- For axiom 4 with Double, take @a = 10@ and two Not postings of 0.1 and+-- 0.2 on one base: @norm (a .* x)@ is 3, but @a * norm x@ is+-- 3.0000000000000004. For axiom 5, let @T = 2^53@, let @x@ contain+-- Not T and Not 1 on one base, and let @y@ contain Not 1 on another:+-- @norm (x .+ y)@ is T+2, but @norm x + norm y@ is T.+--+-- Derived lemmas (also property-tested): @'bar'@ idempotence+-- (@'bar' ('bar' x) = 'bar' x@), 'Zero' identity, and associativity of @('.+')@.+-- Note @('.+')@ accumulates same-base postings as an ordered sequence (the+-- /redundancy/), so 'Show' \/ 'Eq' \/ @Binary@ observe that order, as do the+-- sequential sums in 'barWithin'. For finite postings, 'norm', 'bar', and+-- balance are order-independent for every value type.+-- The order itself is furthermore /construction-path dependent/: the+-- pairwise-union path ('fromList'\/'mconcat') and the bulk-merge path+-- ('sigma'\/'unionsMerge') arrange the same multiset of postings differently.+-- Do not rely on @('==')@ to compare algebras built by different routes —+-- compare after 'compress'\/'bar', or use an exact value type and compare+-- the netted content.++class (HatVal n, HatBaseClass b, Monoid (a n b)) => Redundant a n b where+ -- | Hat operation. Flips Hat/Not on all elements.+ -- Complexity: O(1) for singleton, O(n) for Liner (n is the number of base keys)+ (.^) :: a n b -> a n b++ -- | Bar operation. For each full base, sums the Hat side and the Not side+ -- exactly in the value type's 'Accum', rounds the exact difference once+ -- (to nearest, ties to even) and keeps it on the winning side; the base+ -- disappears only when the exact sums are equal. The result does not depend+ -- on construction order. A base holding a non-finite posting is kept as is.+ -- (0.6.0.0: the 0.5.x sequential sum with a tolerance is 'barWithin'.)+ -- Complexity: O(n) (n is the number of base keys)+ (.-) :: a n b -> a n b++ -- | Alias for bar operation. Identical to @(.-)@.+ -- On an axis-preserving ledger, @norm . bar@ cancels only within each full+ -- base. Use 'balanceMapBy' or 'netPairMapBy' for net amounts by group.+ bar :: a n b -> a n b+ bar = (.-)++ -- | The 0.5.x 'bar': sequential side totals, and the two sides cancel when+ -- their difference is within the tolerance. Bits may differ from 0.5.x+ -- because the summation order follows the internal representation.+ -- @barWithin defaultTolerance@ reproduces the 0.5.x values.+ -- Complexity: O(n)+ barWithin :: Tolerance n -> a n b -> a n b++ -- | Aggregates values on the same base. Sums while preserving the Hat/Not distinction.+ -- Each side's exact sum is rounded once; the sides do not cancel. Because each+ -- side is rounded separately, the net amount of a base may change and cannot+ -- be recovered afterwards: with @T = 2^53@, @Not T, Not 1, Hat T@ has exact+ -- net 1, but 'compress' rounds the Not side to @T@, so a later 'bar' is zero.+ -- Complexity: O(n) (n is the number of base keys)+ compress :: a n b -> a n b++ -- | Addition of algebra elements. Alias for the Monoid @<>@ operation.+ -- Complexity: O(union cost)+ (.+) :: a n b -> a n b -> a n b++ -- | Scalar product. Multiplies all element values by a scalar.+ -- Complexity: O(1) for singleton, O(n) for Liner+ (.*) :: n -> a n b -> a n b++ -- | Norm. Sum of all element values (both Hat and Not sides), i.e. the+ -- homomorphism from the algebra into the value domain @n@ (Akagi 2026,+ -- Appendix A, Definition 6). On exact values it is /additive/:+ -- @norm (x '.+' y) = norm x + norm y@ (axiom 5), and /homogeneous/:+ -- @norm (a '.*' x) = a * norm x@ for @a >= 0@ (axiom 4). The implementation+ -- sums the postings exactly and rounds once, so for floating-point types the+ -- two sides of these equations can differ in the last bit (the right-hand+ -- side adds two rounded values). Because it sums both sides it does not cancel Hat+ -- against Not. Before rounding, @norm ('bar' x) <= norm x@ because bar+ -- discards the canceled part. This inequality also holds for exact value+ -- types. It need not hold after floating-point rounding: with T = 2^53,+ -- Not T and Not 3 on one base and Not 2 on another give norm x = T+4, whereas+ -- norm (bar x) = T+6.+ -- On an axis-preserving ledger, @norm ('bar' x)@ does not cancel across+ -- distinct axis values. Use 'balanceMapBy' or 'netPairMapBy' to read each+ -- group's net amount.+ --+ -- >>> norm (100.@Not:<Yen .+ 50.@Not:<Euro :: Alg Double (HatBase CountUnit))+ -- 150.0+ --+ -- Complexity: O(n) (n is the number of base keys)+ norm :: a n b -> n++ -- | Addition in an Applicative context.+ -- Complexity: O(union cost)+ {-# INLINE (<+) #-}+ (<+) :: (Applicative f) => f (a n b) -> f (a n b) -> f (a n b)+ (<+) x y = (.+) <$> x <*> y+++infixr 7 .^+infixr 2 .-+infixr 3 .++infixr 3 <+++------------------------------------------------------------+-- | Sequences of values on the Hat and Not sides of one basis.+data Pair v where+ Pair :: {_hatSide :: !(Seq v)+ ,_notSide :: !(Seq v)} -> Pair v+ deriving (Eq)++instance (Binary.Binary v) => Binary.Binary (Pair v) where+ {-# INLINABLE put #-}+ {-# INLINABLE get #-}+ put (Pair hs ns) = do+ Binary.put (Seq.length hs :: Int)+ Foldable.foldr (\x k -> Binary.put x >> k) (pure ()) hs+ Binary.put (Seq.length ns :: Int)+ Foldable.foldr (\x k -> Binary.put x >> k) (pure ()) ns+ get = do+ hsLen <- Binary.get :: Binary.Get Int+ hs <- go hsLen Seq.empty+ nsLen <- Binary.get :: Binary.Get Int+ ns <- go nsLen Seq.empty+ pure (Pair hs ns)+ where+ go :: Binary.Binary a => Int -> Seq a -> Binary.Get (Seq a)+ go n !acc+ | n <= 0 = pure acc+ | otherwise = do+ x <- Binary.get+ go (n - 1) (acc Seq.|> x)+++instance (HatVal v) => Ord (Pair v) where+ {-# INLINE compare #-}+ compare first@(Pair hs1 ns1) second@(Pair hs2 ns2) =+ compare (key first) (key second) <> compare (hs1, ns1) (hs2, ns2)+ where+ -- Hat minus Not; classification uses the state, never rounded overflow.+ key pair = case exactAccum state of+ Just value -> (1 :: Int, value)+ Nothing -> case signAccum state of+ Just LT -> (0, 0)+ Just GT -> (2, 0)+ _ -> (3, 0)+ where+ state = negateAccum (netPairState pair)++ (<) x y | compare x y == LT = True+ | otherwise = False++ (>) x y | compare x y == GT = True+ | otherwise = False++ (<=) x y | compare x y == LT = True+ | compare x y == EQ = True+ | otherwise = False++ (>=) x y | compare x y == GT = True+ | compare x y == EQ = True+ | otherwise = False++ max x y | x >= y = x+ | otherwise = y++ min x y | x <= y = x+ | otherwise = y++{-# INLINE nullPair #-}+-- | Complexity: O(1)+nullPair :: Pair v+nullPair = Pair Seq.empty Seq.empty++{-# INLINE pairAppend #-}+-- | Complexity: O(log(min(h1,h2)) + log(min(n1,n2)))+-- where h1/h2 and n1/n2 are the lengths of the appended 'Seq's on each side.+pairAppend :: Pair v -> Pair v -> Pair v+pairAppend (Pair x1 y1) (Pair x2 y2) =+ let !hs = x1 Seq.>< x2+ !ns = y1 Seq.>< y2+ in Pair hs ns++{-# INLINE pairUnion #-}+-- | Set-style merge of two single-base projection results.+--+-- Used by the multi-pattern 'proj'/'projNetNorm' paths where a query list is+-- treated as a /set/: when several queries select the same posting (duplicate+-- bases, or an exact base overlapping a wildcard query), the selected sides+-- come from the /same/ underlying t'Pair' in @_realg@, so the per-side+-- sequences are identical. Taking each side from whichever operand supplies it+-- (and keeping a single copy) therefore unions the selections without double+-- counting. Contrast @pairAppend@, which concatenates and would duplicate.+--+-- Each operand is a side-restricted projection ('choosePairByHat'), so at most+-- one of the two contributes a non-empty hat side and at most one a non-empty+-- not side for a given base; @pairUnion@ keeps the non-empty one per side.+--+-- Complexity: O(1).+pairUnion :: Pair v -> Pair v -> Pair v+pairUnion (Pair x1 y1) (Pair x2 y2) =+ let !hs = if Seq.null x1 then x2 else x1+ !ns = if Seq.null y1 then y2 else y1+ in Pair hs ns++-- | Algebra element. An element of exchange algebra consisting of a value-base pair.+-- Zero is the zero element, @(:@)@ is a singleton, and Liner is a HashMap-based multi-element representation.+--+-- __Invariants (do not hand-construct @Liner@).__ No posting has a zero value,+-- and no @Pair@ has both sides empty. The @Liner@ constructor carries+-- internal cache\/index fields (@_axisPosting@, @_idToBp@, @_allBpIds@) that are+-- /derived from/ @_realg@ and must stay consistent with it: @_axisPosting@,+-- @_idToBp@ and @_allBpIds@ must be exactly the axis index, id↔base map and id set+-- built from the keys of @_realg@ (as the internal @linerFromMap@ does). The+-- wildcard projection path (@projWildMap@, @filterByAxis@) reads those indices and+-- will return wrong answers (silently, not an exception) if they disagree with+-- @_realg@. The fields @_bpToId@ and @_nextBpId@ are currently unmaintained+-- /poison/ (reserved for a dormant scheme) — forcing them throws. Always build+-- values via 'fromList' or the smart constructor '(.@)' rather than applying+-- @Liner@ (or @Single@) directly.+--+-- __Construction boundary (0.6).__ The singleton constructor is the internal+-- 'Single'. Public modules export the one-way pattern synonym '(:@)' instead,+-- so outside "ExchangeAlgebra.Algebra.Internal" a singleton can be matched+-- (@v :@ b@, and the selectors '_val' and '_hatBase') but not built or+-- record-updated: build with '(.@)', which checks the value and the side.+data Alg v b where+ Zero :: Alg v b+ Single :: !v -> !b -> Alg v b+ Liner :: { _realg :: !(Map.HashMap (BasePart b) (Pair v))+ , _axisPosting :: ~(IntMap.IntMap (Map.HashMap AxisKey IntSet.IntSet))+ , _bpToId :: ~(Map.HashMap (BasePart b) Int)+ , _idToBp :: ~(IntMap.IntMap (BasePart b))+ , _nextBpId :: ~Int+ , _allBpIds :: ~IntSet.IntSet+ } -> Alg v b++-- | Serialize an algebra in the 0.5 wire layout (tag 0 = 'Zero', tag 1 =+-- one posting, tag 2 = the per-base map with each t'Pair' in sequence order).+--+-- Decoding is a construction boundary, like '.@': it fails on a value for+-- which 'valueError' is 'Just' (negative or non-finite), on a tag-1 posting+-- whose base side is 'HatNot', and on a repeated base key in tag 2. The checks+-- run before zero values are dropped, and a dropped key still counts as a+-- repeat. Zero values are then dropped as '.@' and '.+' drop them: a tag-1+-- zero decodes to 'Zero', zero entries leave their t'Pair' sequence with the+-- remaining order unchanged, an empty side or base disappears, and an empty+-- tag 2 decodes to 'Zero'. A standalone base value is not checked here, so a+-- 'HatNot' pattern still decodes. For every algebra built from Hat or Not+-- bases with valid values through the public constructors,+-- @decode . encode@ is the identity under structural equality, including the+-- order of each base's sequence.+instance forall v b. ( HatBaseClass b+ , HatVal v+ , Binary.Binary v+ , Binary.Binary b+ , Binary.Binary (BasePart b)+ ) => Binary.Binary (Alg v b) where+ {-# INLINABLE put #-}+ {-# INLINABLE get #-}+ put Zero = Binary.put (0 :: Int)+ put (v :@ b) = do+ Binary.put (1 :: Int)+ Binary.put v+ Binary.put b+ put (Liner m _ _ _ _ _) = do+ Binary.put (2 :: Int)+ Binary.put (Map.size m :: Int)+ Map.foldrWithKey+ (\bp p k -> Binary.put bp >> Binary.put p >> k)+ (pure ())+ m++ get = do+ tag <- Binary.get :: Binary.Get Int+ case tag of+ 0 -> pure Zero+ 1 -> do+ value <- Binary.get+ basis <- Binary.get+ case valueError value of+ Just message -> fail ("Alg: " ++ show message)+ Nothing+ | hat basis == HatNot -> fail "Alg: HatNot posting"+ | isZeroValue value -> pure Zero+ | otherwise -> pure (value `Single` basis)+ 2 -> do+ count <- Binary.get :: Binary.Get Int+ pairs <- go count Map.empty+ let nonempty = Map.filter+ (\(Pair hs ns) -> not (Seq.null hs && Seq.null ns)) pairs+ pure $ if Map.null nonempty then Zero else linerFromMap nonempty+ _ -> fail ("Alg: unknown tag " ++ show tag)+ where+ go :: Int+ -> Map.HashMap (BasePart b) (Pair v)+ -> Binary.Get (Map.HashMap (BasePart b) (Pair v))+ go remaining !acc+ | remaining <= 0 = pure acc+ | otherwise = do+ basis <- Binary.get+ Pair hs ns <- Binary.get+ if Map.member basis acc+ then fail "Alg: repeated base key"+ else do+ mapM_ (\value -> case valueError value of+ Just message -> fail ("Alg: " ++ show message)+ Nothing -> pure ()) (hs Seq.>< ns)+ let pair = Pair (Seq.filter (not . isZeroValue) hs)+ (Seq.filter (not . isZeroValue) ns)+ go (remaining - 1) (Map.insert basis pair acc)++type AxisPosting = IntMap.IntMap (Map.HashMap AxisKey IntSet.IntSet)++{-# INLINE emptyAxisPosting #-}+-- | Complexity: O(1)+emptyAxisPosting :: AxisPosting+emptyAxisPosting = IntMap.empty++{-# INLINE insertAxisPosting #-}+-- | Complexity: O(d * (hash-insert + intset-insert))+-- In practice this is near O(d), where d is the number of axes in the base part.+insertAxisPosting :: [AxisKey] -> Int -> AxisPosting -> AxisPosting+insertAxisPosting !keys !bpId !idx =+ snd $ L.foldl' step (0 :: Int, idx) keys+ where+ step (!axis, !acc) !k =+ let !axisMap = IntMap.findWithDefault Map.empty axis acc+ !ids0 = Map.lookupDefault IntSet.empty k axisMap+ !ids1 = IntSet.insert bpId ids0+ !axisMap' = Map.insert k ids1 axisMap+ !acc' = IntMap.insert axis axisMap' acc+ in (axis + 1, acc')++{-# INLINE queryAxisPosting #-}+-- | Complexity: O(d + intersection cost)+-- d is the number of axes; intersections are performed in ascending set-size order.+queryAxisPosting :: [AxisKey] -> AxisPosting -> IntSet.IntSet -> IntSet.IntSet+queryAxisPosting !keys !idx !allIds =+ case matchedSets of+ Left () -> IntSet.empty+ Right [] -> allIds+ -- 'L.sortOn' preserves length, so a non-empty 'xs' sorts to a non-empty+ -- list; matching @(x:rest)@ here (rather than on a lazy let-binding) makes+ -- exhaustiveness explicit and avoids the partial pattern warning.+ Right xs@(_:_) ->+ case L.sortOn IntSet.size xs of+ (x:rest) -> L.foldl' IntSet.intersection x rest+ [] -> allIds -- unreachable: xs is non-empty+ where+ matchedSets =+ L.foldl' collect (Right []) (zip [0 :: Int ..] keys)++ collect (Left ()) _ = Left ()+ collect (Right acc) (!axis, !k)+ | axisHasWildcard k = Right acc+ | otherwise =+ case IntMap.lookup axis idx of+ Nothing -> Left ()+ Just axisMap -> case Map.lookup k axisMap of+ Nothing -> Left ()+ Just ids -> Right (ids : acc)++ -- A component with a wildcard inside it (e.g. @("a", wildcard)@) must+ -- not be looked up as an exact key; the final 'matchesQuery' check selects.+ axisHasWildcard (AxisKey coordinate) = haveWildcard coordinate+ {-# INLINE axisHasWildcard #-}++{-# INLINE linerFromMap #-}+-- | Complexity: O(n * d * (hash-insert + intset-insert))+-- n is the number of distinct base keys in the map.+linerFromMap :: (HatBaseClass b)+ => Map.HashMap (BasePart b) (Pair v)+ -> Alg v b+-- | @_bpToId@ and @_nextBpId@ are reserved for the (currently dormant) P1a+-- incremental-id scheme and are not consumed by any read path. We therefore skip+-- building them and leave them as lazy poison: nothing forces them in normal+-- operation (the wildcard 'proj' path uses @idx@/@idToBp@/@allIds@ only). Forcing+-- either field is a bug, surfaced loudly here instead of silently returning a+-- stale value. Guarded by a poison-field regression test.+--+-- NOTE: the @error@ thunks are written inline in the constructor application (the+-- @Liner@ fields are lazy @~@) rather than as @where@-bindings, because this+-- module is compiled @{-\# LANGUAGE Strict \#-}@, under which a @where@-bound thunk+-- would be forced when 'linerFromMap' is evaluated.+linerFromMap m =+ Liner m idx+ (error "Liner internal: _bpToId is not maintained (reserved for P1a); do not force")+ idToBp+ (error "Liner internal: _nextBpId is not maintained (reserved for P1a); do not force")+ allIds+ where+ ~(idx, idToBp, allIds) =+ Map.foldlWithKey'+ (\(!idxAcc, !idToBpAcc, !allIdsAcc) bp _ ->+ let !bpId = IntMap.size idToBpAcc+ !idx' = insertAxisPosting (toAxisKeys bp) bpId idxAcc+ !idToBp' = IntMap.insert bpId bp idToBpAcc+ !allIds' = IntSet.insert bpId allIdsAcc+ in (idx', idToBp', allIds'))+ (emptyAxisPosting, IntMap.empty, IntSet.empty)+ m++-- | Tests whether the algebra element is zero (empty).+--+-- Complexity: O(1)+isZero :: Alg v b -> Bool+isZero Zero = True+isZero _ = False++{-# INLINE singleton #-}+-- | The checked singleton behind '(.@)'; see its contract.+--+-- Complexity: O(1)+singleton :: (HatVal v, HatBaseClass b) => v -> b -> Alg v b+singleton v b | hat b == HatNot = error $ "errorValue at (.@): HatNot side val: "+ ++ show v ++ show ":@" ++ show b+ | isZeroValue v = Zero+ | Just reason <- valueError v = error $ "errorValue at (.@): "+ ++ show reason ++ " val: " ++ show v+ ++ show ":@"+ ++ show b+ | otherwise = v `Single` b++{-# INLINE (.@) #-}+-- | Smart constructor that builds an algebra element from a value and a base.+-- This is the public construction boundary for a posting.+--+-- Contract (0.6): the side of @b@ must be v'Hat' or 'Not'. A base whose side is+-- the wildcard 'HatNot' is rejected with 'error', checked before the zero+-- short-cut (so @0 .@ HatNot :< Cash@ also fails): 'HatNot' is a query+-- pattern, not a side a posting can have. Then a zero value gives 'Zero', and+-- a value for which 'valueError' is 'Just' (negative or non-finite) is+-- rejected with 'error'. Otherwise the result is the singleton @v :@ b@.+-- Values supplied by callbacks that rebuild postings (for example+-- 'mapMaybePosting') are subject to the same precondition.+--+-- Complexity: O(1)+(.@) :: (HatVal n, HatBaseClass b) => n -> b -> Alg n b+(.@) v b = singleton v b++-- | Constructs an algebra element in an Applicative context. Lifted version of @(.@)@.+--+-- Complexity: O(1) + Applicative effects+(<@) :: (HatVal n, Applicative f, HatBaseClass b)+ => f n -> b -> f (Alg n b)+(<@) v b = (.@) <$> v <*> (pure b)+++-- | Match a singleton: @v :@ b@ is the posting of value @v@ on base @b@.+-- One-way: it matches and gives the record selectors '_val' and '_hatBase'+-- (partial, as before: they fail on 'Zero' and on a multi-posting value), but+-- it does not build. Build with '(.@)'. Library code that needs the unchecked+-- constructor uses the internal 'Single'.+pattern (:@) :: v -> b -> Alg v b+pattern (:@) {_val, _hatBase} <- Single _val _hatBase++{-# COMPLETE Zero, (:@), Liner #-}++infixr 6 :@+infixr 6 `Single`+infixr 6 .@+infixr 6 <@++-- NOTE: the former top-level @showV@ (which hard-wired @RealFloat@ via+-- @fromFloatDigits@) has been replaced by the per-instance 'showValue' method of+-- 'HatVal', so that exact value types can render themselves. The 'Show' instance+-- of 'Alg' below now calls 'showValue'. The @Double@/@MoneyDouble@ 'showValue'+-- implementations reproduce the old formatting byte-for-byte.++instance (HatVal v, HatBaseClass b) => Eq (Alg v b) where+ (==) Zero Zero = True+ (==) Zero _ = False+ (==) _ Zero = False+ (==) (v1:@b1) (v2:@b2) = (v1 == v2) && (b1 == b2)+ (==) (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = m1 == m2+ (==) _ _ = False+ (/=) x y = not (x == y)++instance (HatVal v, HatBaseClass b) => Ord (Alg v b) where+ {-# INLINE compare #-}+ compare Zero Zero = EQ+ compare Zero _ = LT+ compare _ Zero = GT++ compare (_:@_) (Liner _ _ _ _ _ _) = LT+ compare (Liner _ _ _ _ _ _) (_:@_) = GT+ compare (v1:@b1) (v2:@b2)+ | b1 == b2 = compare v1 v2+ | b1 > b2 = GT+ | otherwise = LT -- b1 < b2 (Ord is total; otherwise keeps it exhaustive)++ compare (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = compare m1 m2++ (<) x y | compare x y == LT = True+ | otherwise = False++ (>) x y | compare x y == GT = True+ | otherwise = False++ (<=) x y | compare x y == LT = True+ | compare x y == EQ = True+ | otherwise = False++ (>=) x y | compare x y == GT = True+ | compare x y == EQ = True+ | otherwise = False++ max x y | x >= y = x+ | otherwise = y++ min x y | x <= y = x+ | otherwise = y++instance (HatVal v, HatBaseClass b) => Show (Alg v b) where+ show Zero = "0"+ show (v:@b) = (showValue v) ++ ":@" ++ show b+ show xs = let ls = toASCList xs+ in go ls+ where+ go [] = "0"+ go [y] = show y+ go (y:ys) = show y ++ " .+ " ++ go ys+++instance NFData (Alg v b) where+ rnf Zero = Zero `seq` ()+ rnf (v:@b) = v `seq` b `seq` ()+ rnf (Liner m _ _ _ _ _) = Map.foldrWithKey (\k v acc -> k `seq` v `seq` acc) () m+------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------++instance (HatVal n, HatBaseClass b) => Semigroup (Alg n b) where+ {-# INLINE (<>) #-}+ -- | Associative law ;convert to right join+ (<>) = union++++-- | union two trees+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Hat:<Yen .+ 2.@Not:<Amount :: Test+-- >>> union x y+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Not:<Amount .+ 2.00:@Not:<Amount+{-# INLINE union #-}+-- | Complexity:+-- - singleton/singleton and singleton/liner cases: O(n * d * index-build)+-- - liner/liner case: O(n + m) for map union plus O((n+m) * d * index-build)+-- where n and m are distinct key counts on each side.+union :: (HatVal n, HatBaseClass b) => Alg n b -> Alg n b -> Alg n b+union Zero x = x+union x Zero = x+-- singletons+-- NOTE: a zero-valued singleton contributes nothing, so the result must keep the+-- /nonzero/ value on its OWN base. Earlier code returned @v2:@b1@ / @v1:@b2@,+-- relabeling the surviving value onto the zero posting's base — this preserved+-- 'norm' but silently moved the value to the wrong base, corrupting per-base+-- projection and making construction order observable (raw @(:@)@ on a sparsified+-- zero coefficient builds an explicit @0:@base@ singleton). Keep @v2:@b2@ / @v1:@b1@.+union (v1:@b1) (v2:@b2)+ | isZeroValue v1 = case isZeroValue v2 of+ True -> Zero+ False -> v2`Single`b2+ | isZeroValue v2 = v1`Single`b1+ | otherwise = insert b2 v2 (v1`Single`b1)+-- If one side is a singleton+union x (v:@b) = insert b v x+union (v:@b) x = insert b v x++-- In the case of multiple elements+union (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = linerFromMap (Map.unionWith pairAppend m1 m2)+++{-# INLINE insert #-}+-- | Complexity:+-- - into Zero or singleton: O(1) to O(d * index-build)+-- - into Liner: O(n * d * index-build) due to rebuilding 'linerFromMap'+-- where n is the number of distinct base keys after insertion.+insert :: (HatVal v,HatBaseClass b) => b -> v -> Alg v b -> Alg v b+insert _ v x | isZeroValue v = x+insert !b !v Zero = v .@ b+insert !b1 !v1 (v2:@b2) = case isHat b1 of+ True -> insert b2 v2+ $ linerFromMap+ $ Map.singleton (base b1)+ $ nullPair {_hatSide = Seq.singleton v1}+ False -> insert b2 v2+ $ linerFromMap+ $ Map.singleton (base b1)+ $ nullPair {_notSide = Seq.singleton v1}+insert !b !v (Liner m _ _ _ _ _) = case isHat b of+ True -> insertLiner (nullPair {_hatSide = Seq.singleton v})+ False -> insertLiner (nullPair {_notSide = Seq.singleton v})+ where+ !bp = base b+ insertLiner !pairToInsert =+ let !m' = Map.insertWith pairAppend bp pairToInsert m+ in linerFromMap m'++------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------++instance (HatVal n, HatBaseClass b) => Monoid (Alg n b) where+ -- Identity element+ mempty = Zero+ mappend = (<>)+ mconcat = unions++{-# INLINE unions #-}+-- | Complexity: O(sum of HashMap union costs over the fold)+-- For a long list this is typically the dominant construction cost.+unions :: (HatVal n, Foldable f, HatBaseClass b) => f (Alg n b) -> Alg n b+unions ts = Foldable.foldl' union Zero ts++{-# INLINE mergeAlgMap #-}+mergeAlgMap :: (HatVal n, HatBaseClass b)+ => Map.HashMap (BasePart b) (Pair n)+ -> Alg n b+ -> Map.HashMap (BasePart b) (Pair n)+mergeAlgMap !acc Zero = acc+mergeAlgMap !acc (v :@ b)+ | isZeroValue v = acc+ | otherwise =+ let !p = if isHat b+ then nullPair {_hatSide = Seq.singleton v}+ else nullPair {_notSide = Seq.singleton v}+ in Map.insertWith pairAppend (base b) p acc+mergeAlgMap !acc (Liner m _ _ _ _ _)+ | Map.null m = acc+ | otherwise = Map.unionWith pairAppend acc m++{-# INLINE mergeAlgMapIfNonZero #-}+mergeAlgMapIfNonZero :: (HatVal n, HatBaseClass b)+ => Map.HashMap (BasePart b) (Pair n)+ -> Alg n b+ -> Map.HashMap (BasePart b) (Pair n)+mergeAlgMapIfNonZero !acc Zero = acc+mergeAlgMapIfNonZero !acc alg@(v :@ _)+ | isZeroValue v = acc+ | otherwise = mergeAlgMap acc alg+mergeAlgMapIfNonZero !acc alg = mergeAlgMap acc alg++{-# INLINE unionsMerge #-}+-- | Merge multiple Algs by directly combining their internal HashMaps,+-- building the AxisPosting index only once at the end.+--+-- Produces the same /multiset/ of postings as @unions@\/'mconcat', but the+-- same-base sequence order differs (the bulk-merge accumulates a new same-base+-- singleton in front of the previously merged values, whereas the pairwise+-- union path interleaves differently). 'Eq'\/@Binary@ observe that order, and+-- 'Double' can observe it through the sequential sums in 'barWithin'; see the+-- characterization test @testSameBaseSeqOrderPathDependence@.+unionsMerge :: (HatVal n, Foldable f, HatBaseClass b) => f (Alg n b) -> Alg n b+unionsMerge ts =+ let !m = Foldable.foldl' mergeAlgMap Map.empty ts+ in mkAlgFromMap m++------------------------------------------------------------------+-- Redundant+------------------------------------------------------------------++instance (HatVal n, HatBaseClass b) => Redundant Alg n b where+ {-# SPECIALIZE instance HatBaseClass b => Redundant Alg Double b #-}+ {-# SPECIALIZE instance HatBaseClass b => Redundant Alg MoneyDouble b #-}+ {-# SPECIALIZE instance HatBaseClass b => Redundant Alg MoneyDecimal b #-}+ (.^) Zero = Zero+ (.^) (n:@ b) = n `Single` (revHat b)+ (.^) (Liner ms idx bpToId idToBp nextBpId allIds) = Liner+ (Map.map (\ (Pair hs ns) -> Pair ns hs) ms)+ idx+ bpToId+ idToBp+ nextBpId+ allIds++ (.+) = mappend++ _ .* Zero = Zero+ 0 .* _ = Zero+ -- The algebra is over non-negative values: reject a negative / non-finite+ -- scalar instead of silently producing out-of-domain (negative) postings.+ -- Products that underflow to zero are dropped without aggregating postings.+ x .* _ | isErrorValue x =+ error ("(.*): non-negative finite scalar required, got " ++ show x)+ x .* (v:@b)+ | isZeroValue productValue = Zero+ | otherwise = productValue `Single` b+ where+ productValue = x * v+ x .* (Liner ms idx bpToId idToBp nextBpId allIds)+ | any isZeroValue (vals scaled) = filter (not . isZeroValue . _val) scaled+ | otherwise = scaled+ where+ scaled = Liner+ (Map.map (\(Pair hs ns) -> Pair (fmap (x *) hs) (fmap (x *) ns)) ms)+ idx bpToId idToBp nextBpId allIds++ norm Zero = 0+ norm (v:@_) = roundAccum (addAccum v emptyAccum)+ norm (Liner ms _ _ _ _ _) = roundAccum $+ Map.foldl' (\total (Pair hs ns) ->+ Foldable.foldl' (flip addAccum)+ (Foldable.foldl' (flip addAccum) total hs) ns) emptyAccum ms++ {-# INLINE (.-) #-}+ (.-) Zero = Zero+ (.-) scalar@(v :@ _)+ | isZeroValue v = Zero+ | otherwise = scalar+ (.-) (Liner pairs ~idx ~bpToId ~idToBp ~nextBpId ~allIds)+ | Map.null reduced = Zero+ | Map.size reduced == Map.size pairs =+ Liner reduced idx bpToId idToBp nextBpId allIds+ | otherwise = linerFromMap reduced+ where+ reduced = Map.mapMaybe reduce pairs+ reduce pair = case signAccum state of+ Just EQ -> Nothing+ Just LT -> settle Hat (negateAccum state)+ _ -> settle Not state+ where+ state = netPairState pair+ settle side magnitude =+ let value = roundAccum magnitude+ in if isErrorValue value && hasNonfiniteState state+ then Just pair+ else finish side value+ finish side value+ | isZeroValue value = Nothing+ | side == Hat = Just (Pair (Seq.singleton value) Seq.empty)+ | otherwise = Just (Pair Seq.empty (Seq.singleton value))++ {-# INLINE barWithin #-}+ barWithin _ Zero = Zero+ barWithin _ (v:@b) = v`Single`b+ barWithin tolerance (Liner m _ _ _ _ _) = let !res = Map.mapMaybe f m+ in case null res of+ True -> Zero+ False -> linerFromMap res+ where+ {-# INLINE f #-}+ f p@(Pair hs ns) =+ let !h = Foldable.foldl' (+) 0 hs+ !n = Foldable.foldl' (+) 0 ns+ in case (not (isErrorValue h || isErrorValue n)+ && abs (h - n) <= absTol tolerance+ + relTol tolerance * max (abs h) (abs n)) of -- scale-aware tolerance (WI-11)+ True -> Nothing+ False -> case (Seq.length hs, Seq.length ns) of+ -- Already in canonical form: singleton on winning side, empty on other+ (1, 0) | h > n -> Just p+ (0, 1) | n > h -> Just p+ _ -> case compare h n of+ GT -> Just (Pair (Seq.singleton (h - n)) Seq.empty)+ _ -> Just (Pair Seq.empty (Seq.singleton (n - h)))++ {-# INLINE compress #-}+ compress Zero = Zero+ compress scalar@(v :@ _)+ | isZeroValue v = Zero+ | otherwise = scalar+ compress (Liner pairs ~idx ~bpToId ~idToBp ~nextBpId ~allIds)+ | Map.null reduced = Zero+ | Map.size reduced == Map.size pairs =+ Liner reduced idx bpToId idToBp nextBpId allIds+ | otherwise = linerFromMap reduced+ where+ reduced = Map.mapMaybe compressPair pairs+ compressPair (Pair hs ns) =+ let h = roundAccum (Foldable.foldl' (flip addAccum) emptyAccum hs)+ n = roundAccum (Foldable.foldl' (flip addAccum) emptyAccum ns)+ hats = if isZeroValue h then Seq.empty else Seq.singleton h+ nots = if isZeroValue n then Seq.empty else Seq.singleton n+ in if Seq.null hats && Seq.null nots then Nothing+ else Just (Pair hats nots)+++-- | Visit full-base pairs without rebuilding every posting's base.+foldPairs :: (HatVal n, HatBaseClass b)+ => (a -> BasePart b -> Pair n -> a) -> a -> Alg n b -> a+{-# INLINE foldPairs #-}+foldPairs _ initial Zero = initial+foldPairs collect initial (value :@ postingBase)+ | isZeroValue value = initial+ | isHat postingBase = collect initial (base postingBase)+ (Pair (Seq.singleton value) Seq.empty)+ | otherwise = collect initial (base postingBase)+ (Pair Seq.empty (Seq.singleton value))+foldPairs collect initial (Liner pairs _ _ _ _ _) = Map.foldlWithKey' collect initial pairs++-- | Exact Not-minus-Hat residual for one complete base.+netPairState :: HatVal n => Pair n -> Accum n+{-# INLINE netPairState #-}+netPairState (Pair hats nots) = mergeAccum+ (Foldable.foldl' (flip addAccum) emptyAccum nots)+ (negateAccum (Foldable.foldl' (flip addAccum) emptyAccum hats))++-- | Distinguish nonfinite inputs from finite overflow of the rounded output.+hasNonfiniteState :: HatVal n => Accum n -> Bool+{-# INLINE hasNonfiniteState #-}+hasNonfiniteState state = signAccum (mergeAccum state (negateAccum state)) /= Just EQ++-- | Group unrounded residuals for Alg and Journal callbacks. Nonfinite bases+-- are returned separately, unchanged, and never classified by the callback key.+postNetStates :: (HatVal n, HatBaseClass b, Ord k)+ => (b -> Maybe k) -> Alg n b -> (Alg n b, M.Map k (Accum n))+{-# INLINE postNetStates #-}+postNetStates keyOf algebra = (mkAlgFromMap retained, totals)+ where+ (!retained, !totals) = foldPairs collect (Map.empty, M.empty) algebra+ collect (!unchanged, !amounts) basePart pair =+ let state = netPairState pair+ select side magnitude = case keyOf (merge side basePart) of+ Nothing -> (unchanged, amounts)+ Just key -> let !updated = M.insertWith mergeAccum key magnitude amounts+ in (unchanged, updated)+ in if isErrorValue (abs (roundAccum state)) && hasNonfiniteState state+ then let !updated = Map.insert basePart pair unchanged in (updated, amounts)+ else case signAccum state of+ Just EQ -> (unchanged, amounts)+ Just LT -> select Hat (negateAccum state)+ _ -> select Not state++-- * Basic functions+------------------------------------------------------------------++-- | Returns all values contained in the algebra element as a list.+--+-- Complexity: O(s) (s is the total number of scalar entries)+vals :: (HatVal v, HatBaseClass b) => Alg v b -> [v]+vals Zero = []+vals (v:@_) = [v]+vals (Liner m _ _ _ _ _) =+ reverse $+ Map.foldl'+ (\acc (Pair hs ns) ->+ Foldable.foldl' (flip (:))+ (Foldable.foldl' (flip (:)) acc hs)+ ns+ )+ []+ m+++-- | Returns all bases contained in the algebra element as a list.+--+-- Complexity: O(s) (s is the total number of scalar entries)+bases :: (HatVal v, HatBaseClass b) => Alg v b -> [b]+bases Zero = []+bases (_:@b) = [b]+bases (Liner m _ _ _ _ _) = Map.foldlWithKey' f [] m+ where+ f :: (HatVal v, HatBaseClass b) => [b] -> BasePart b -> Pair v -> [b]+ f xs b (Pair {_hatSide = hs, _notSide = ns})+ = Foldable.foldl' (g Not b) (Foldable.foldl' (g Hat b) xs hs) ns++ g :: (HatVal v, HatBaseClass b) => Hat -> BasePart b -> [b] -> v -> [b]+ g h b ys _ = (merge h b):ys++{-# INLINE fromList #-}+-- | convert List to Alg n b+-- Complexity: O(sum of HashMap union costs), because this is implemented via 'mconcat'.+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> xs = [1.@Hat:<Yen,1.@Not:<Dollar, 2.@Hat:<Yen, 2.@Not:<Dollar] :: [Test]+-- >>> fromList xs+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Not:<Dollar .+ 2.00:@Not:<Dollar+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Hat:<Yen .+ 2.@Not:<Amount :: Test+-- >>> fromList [x,y]+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Not:<Amount .+ 2.00:@Not:<Amount++fromList ::(HatVal v, HatBaseClass b ) => [Alg v b] -> Alg v b+fromList = mconcat++++-- | Summation function that applies a function to each element of a list and sums the results.+-- Complexity: O(sum of HashMap union costs over produced elements).+--+-- Uses the bulk-merge path ('unionsMerge'); see there for the same-base+-- sequence-order caveat relative to 'fromList'\/'mconcat'.+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> sigma [1,2] (\x -> x.@Hat:<Yen)+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen++{-# INLINE sigma #-}+sigma :: (HatVal v, HatBaseClass b) => [a] -> (a -> Alg v b) -> Alg v b+sigma xs f = mkAlgFromMap $ L.foldl' step Map.empty xs+ where+ step !acc !x = mergeAlgMapIfNonZero acc (f x)++-- | Conditional summation over a double loop. For all combinations of two lists,+-- applies the function only to pairs that satisfy the condition and sums the results.+--+-- Complexity: O(|xs| * |ys| * union cost)+{-# INLINE sigma2When #-}+sigma2When :: (HatVal v, HatBaseClass b)+ => [a]+ -> [c]+ -> (a -> c -> Bool)+ -> (a -> c -> Alg v b)+ -> Alg v b+sigma2When xs ys cond f =+ mkAlgFromMap $ L.foldl' outer Map.empty xs+ where+ outer !acc !x = L.foldl' (inner x) acc ys+ inner !x !acc !y+ | cond x y = mergeAlgMapIfNonZero acc (f x y)+ | otherwise = acc++-- | Summation using keys and values from a Map. Skips entries with zero values.+--+-- Complexity: O(|map| * union cost)+{-# INLINE sigmaFromMap #-}+sigmaFromMap :: (HatVal v, HatBaseClass b, Ord k)+ => M.Map k v+ -> (k -> v -> Alg v b)+ -> Alg v b+sigmaFromMap kvs f =+ mkAlgFromMap $ M.foldlWithKey' step Map.empty kvs+ where+ step !acc !k !v+ | isZeroValue v = acc+ | otherwise = mergeAlgMapIfNonZero acc (f k v)++-- | Converts an algebra element to a list.+-- Complexity: O(s) (s is the total number of scalar entries)+--+-- >>> toList (10.@Hat:<(Yen) .+ 10.@Hat:<(Dollar) .+ Zero :: Alg Double (HatBase CountUnit))+-- [10.00:@Hat:<Dollar,10.00:@Hat:<Yen]+--+-- you need define type variables to use this for Zero+-- >>> toList Zero :: [Alg Double (HatBase CountUnit)]+-- []+toList :: (HatVal v, HatBaseClass b) => Alg v b -> [Alg v b]+toList Zero = []+toList (v:@b) = [v`Single`b]+toList (Liner m _ _ _ _ _) = Map.foldlWithKey' f [] m+ where+ f :: (HatVal v, HatBaseClass b) => [Alg v b] -> BasePart b -> Pair v -> [Alg v b]+ f xs b Pair {_hatSide = hs, _notSide = ns}+ = Foldable.foldl' (g Hat b) (Foldable.foldl' (g Not b) xs ns) hs++ g :: (HatVal v, HatBaseClass b) => Hat -> BasePart b -> [Alg v b] -> v -> [Alg v b]+ g h b ys v+ | isZeroValue v = ys+ | otherwise = (v `Single` (merge h b)):ys++{-# INLINE foldEntries #-}+-- | Strict left fold over scalar entries without building an intermediate list.+--+-- This is the implementation vehicle for the universal extension from the free+-- commutative monoid of entries when each step acts through an associative,+-- commutative accumulator operation (equivalently, entry updates commute).+-- Under that condition the result is independent of both the internal+-- @HashMap@ traversal and each side's sequence order. For a non-commutative+-- accumulator, such as list append, this is only an ordinary left fold: its+-- result records the actual traversal order and can distinguish different+-- sequence orders of the same entry multiset.+foldEntries :: (HatVal v, HatBaseClass b)+ => (acc -> v -> b -> acc)+ -> acc+ -> Alg v b+ -> acc+foldEntries _ !acc Zero = acc+foldEntries f !acc (v :@ b)+ | isZeroValue v = acc+ | otherwise = f acc v b+foldEntries f !acc (Liner m _ _ _ _ _) =+ Map.foldlWithKey' step acc m+ where+ step !acc0 !bp (Pair hs ns) =+ let !hatBase = merge Hat bp+ !notBase = merge Not bp+ !acc1 = Foldable.foldl' (\a v -> if isZeroValue v then a else f a v hatBase) acc0 hs+ in Foldable.foldl' (\a v -> if isZeroValue v then a else f a v notBase) acc1 ns++{-# INLINE toASCList #-}+-- | Complexity: O(s log s), dominated by sorting the list representation.+toASCList :: (HatVal v, HatBaseClass b) => Alg v b -> [Alg v b]+toASCList = L.sort . toList+++-- | map+-- Complexity: O(s + c), where s is traversed scalar entries and c is transformed output size.+-- Typed alternatives: 'mapPosting' and 'mapMaybePosting'.+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> map (\ (x:@hb) -> x.@(toHat hb)) $ x .+ y+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Hat:<Amount .+ 2.00:@Hat:<Amount+--+-- >>> type Test = Alg Double Hat+-- >>> x = 1.@Hat .+ 1.@Not :: Test+-- >>> y = 2.@Not .+ 2.@Hat :: Test+-- >>> map (\ (x:@hb) -> (2 * x).@hb) $ x .+ y+-- 2.00:@Hat .+ 4.00:@Hat .+ 2.00:@Not .+ 4.00:@Not++-- NB. 'map' applies its function to one singleton posting at a time and requires+-- the result to itself be a singleton @(v2 :@ b2)@ (or 'Zero', handled via the+-- 'isZeroValue' check / the 'r' helper below). The @let v2:@b2 = ...@ and the+-- @case fn (...)@ in 'r' therefore intentionally match only the @(:@)@ shape; a+-- 'Zero'\/'Liner' result is outside this contract, so the non-exhaustive patterns+-- are by design (audited). Adding catch-all arms would silently change behavior.+map :: (HasCallStack,HatVal v, HatBaseClass b)+ => (Alg v b -> Alg v b) -> Alg v b -> Alg v b+map _ Zero = Zero+map f (v:@b) = let v2:@b2 = f (v`Single`b)+ in case isZeroValue v2 of+ True -> Zero+ False -> (v2 `Single` b2)+map f (Liner m _ _ _ _ _) = mkAlgFromMap $ (Map.foldrWithKey (p f) dnilMap m) Map.empty+ where+ {-# INLINE dnilMap #-}+ dnilMap = id+ {-# INLINE dappendMap #-}+ dappendMap = (.)+ {-# INLINE dsingleMap #-}+ dsingleMap (bp, p') = Map.insertWith pairAppend bp p'++ {-# INLINE p #-}+ p :: (HatVal v, HatBaseClass b)+ => (Alg v b -> Alg v b)+ -> BasePart b+ -> Pair v+ -> DMap (BasePart b) (Pair v)+ -> DMap (BasePart b) (Pair v)+ p fn b Pair {_hatSide=hs, _notSide=ns} accDList =+ let (dl1, hs2) = q fn Hat b hs+ (dl2, ns2) = q fn Not b ns+ prefix = dappendMap dl1 dl2+ in case (Seq.null hs2, Seq.null ns2) of+ (True,True) -> dappendMap prefix accDList+ (True,False) -> dappendMap prefix+ . dappendMap (dsingleMap (b, nullPair{_notSide = ns2}))+ $ accDList+ (False,True) -> dappendMap prefix+ . dappendMap (dsingleMap (b, nullPair{_hatSide = hs2}))+ $ accDList+ (False,False) -> dappendMap prefix+ . dappendMap (dsingleMap (b, Pair hs2 ns2))+ $ accDList+ {-# INLINE q #-}+ q :: (HatVal v, HatBaseClass b)+ => (Alg v b -> Alg v b)+ -> Hat+ -> BasePart b+ -> Seq v+ -> (DMap (BasePart b) (Pair v), Seq v)+ q fn h b vs = Foldable.foldl' (r fn h b) (dnilMap, Seq.empty) vs++ {-# INLINE r #-}+ r :: (HatVal v, HatBaseClass b)+ => (Alg v b -> Alg v b)+ -> Hat+ -> BasePart b+ -> (DMap (BasePart b) (Pair v), Seq v)+ -> v+ -> (DMap (BasePart b) (Pair v), Seq v)+ r fn h b (dlAcc,vsAcc) v = case fn (v`Single`(merge h b)) of+ Zero -> (dlAcc, vsAcc)+ ------------------------------------------------------------------+ v2:@b2+ | isZeroValue v2 -> (dlAcc, vsAcc)+ -- Structural equality: a wildcard in a ledger base is+ -- a value, so rewriting it to (or from) a concrete+ -- coordinate is a change of base, not \"the same base\".+ | b2 == (merge h b) -> (dlAcc, v2 Seq.<| vsAcc)+ | isHat (hat b2) -> (dappendMap dlAcc (dsingleMap ( base b2+ ,nullPair{_hatSide = Seq.singleton v2}))+ ,vsAcc )+ | otherwise -> (dappendMap dlAcc (dsingleMap ( base b2+ ,nullPair{_notSide = Seq.singleton v2} ))+ ,vsAcc )++-- | Map every posting to exactly one posting. The typed form of 'map'.+mapPosting :: (HatVal v, HatVal v2, HatBaseClass b, HatBaseClass b2)+ => (v -> b -> (v2, b2)) -> Alg v b -> Alg v2 b2+mapPosting f = mapMaybePosting (\v b -> Just (f v b))++-- | Map every posting to zero or one posting. A 'Nothing' drops the posting;+-- a zero value is normalized as by '(.@)'.+mapMaybePosting :: (HatVal v, HatVal v2, HatBaseClass b, HatBaseClass b2)+ => (v -> b -> Maybe (v2, b2)) -> Alg v b -> Alg v2 b2+mapMaybePosting _ Zero = Zero+mapMaybePosting f (v :@ b) = case f v b of+ Nothing -> Zero+ Just (v2, b2) -> v2 .@ b2+mapMaybePosting f (Liner m _ _ _ _ _) =+ mkAlgFromMap $ Map.foldrWithKey addPair id m Map.empty+ where+ {-# INLINE addPair #-}+ addPair bp (Pair hs ns) accDMap =+ mapSide Hat bp hs . mapSide Not bp ns . accDMap++ {-# INLINE mapSide #-}+ mapSide h bp = Foldable.foldl' (mapOne (merge h bp)) id++ {-# INLINE mapOne #-}+ mapOne b dlAcc v = case f v b of+ Nothing -> dlAcc+ Just (v2, b2) -> case v2 .@ b2 of+ Zero -> dlAcc+ v3 :@ b3 -> dlAcc . Map.insertWith pairAppend (base b3) (postingPair v3 b3)+ Liner {} -> dlAcc++ {-# INLINE postingPair #-}+ postingPair v b+ | isHat (hat b) = nullPair {_hatSide = Seq.singleton v}+ | otherwise = nullPair {_notSide = Seq.singleton v}++-- Difference-map (endo) used by 'map' to accumulate Liner rebuilds in O(1).+-- NB. The plain difference-list helpers (dnil/dappend/dsingle/dToList/dFromList)+-- and the unused 'DList' type alias were removed as dead code: only the DMap+-- variants (dnilMap/dappendMap/dsingleMap, defined locally in 'map') are used.+type DMap k v = Map.HashMap k v -> Map.HashMap k v++{-# INLINE filter #-}+-- | filter+-- Complexity: O(s), where s is total number of scalar entries.+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> filter (isHat . _hatBase) $ x .+ y+-- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Amount+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> filter ((1 <). _val) $ x .+ y+-- 2.00:@Not:<Yen .+ 2.00:@Hat:<Amount+++filter :: (HatVal v, HatBaseClass b) => (Alg v b -> Bool) -> Alg v b -> Alg v b+filter _ Zero = Zero+filter f (v:@b) | f (v`Single`b) = v`Single`b+ | otherwise = Zero++filter f (Liner m _ _ _ _ _) =+ -- Build a new Map using mapMaybeWithKey+ let m' = Map.mapMaybeWithKey+ (\basePart (Pair hs ns) ->+ -- Filter each of hs and ns+ let hs' = filterSide basePart Hat hs+ ns' = filterSide basePart Not ns+ in+ -- Remove the entry (Nothing) if both become empty+ if Seq.null hs' && Seq.null ns'+ then Nothing+ else Just (Pair hs' ns'))+ m+ in+ -- If the resulting Map is empty, return Zero; otherwise Liner m'+ if Map.null m' then Zero else linerFromMap m'+ where+ ----------------------------------------------------------------+ -- Filter function that constructs "v:@(merge h basePart)" from+ -- basePart and Hat/Not, and tests whether it satisfies predicate f+ ----------------------------------------------------------------+ -- filterSide :: BasePart b -> Hat -> Seq v -> Seq v+ {-# INLINE filterSide #-}+ filterSide bp h = Seq.filter (\val -> f (val `Single` merge h bp))++------------------------------------------------------------+-- | Relabel the /base part/ of every element, preserving the Hat\/Not structure+-- and the redundancy (the ordered Hat- and Not-side sequences). Only the+-- 'BasePart' is rewritten by @f@; the Hat\/Not side and the values are untouched.+-- When @f@ maps two distinct base parts onto the same target, their sequences are+-- concatenated (pair-append), so no value is lost — hence @'norm'@ is preserved.+-- Preserving wildcards (@(.#)@) is the caller's responsibility (in @f@).+--+-- == Laws and their layer of validity+--+-- On the full subcategory of 'HatBaseClass' bases, this relabelling is a+-- functor only after observing an algebra through ℘, where ℘ forgets sequence+-- order but retains, for every full Hat\/Not base, the multiset of values.+-- The functor and additive laws at this layer are:+--+-- * @mapBasePart id x@ and @x@ are equal through ℘. Raw equality can fail when+-- @x@ is a one-key, one-value @Liner@ produced by 'bar' or filtering:+-- rebuilding it chooses the singleton @(:@)@ representation, and 'Eq'+-- distinguishes those constructors.+-- * @mapBasePart (g . f) x@ and+-- @mapBasePart g (mapBasePart f x)@ are equal through ℘, but need not be raw+-- equal.+-- * @mapBasePart f (x .+ y)@ and+-- @mapBasePart f x .+ mapBasePart f y@ are equal through ℘, but need not be+-- raw equal.+-- * @norm (mapBasePart f x) == norm x@ for exact additive value types. For+-- floating-point values, regrouping after collisions has the usual rounding+-- caveat.+-- * @mapBasePart f ((.^) x) == (.^) (mapBasePart f x)@ (raw).+-- * Algebraically, @bar (mapBasePart f (bar x))@ and+-- @bar (mapBasePart f x)@ are equal through ℘ whenever the implementation's+-- rounding of a source-base residual does not change the cancellation after+-- relabelling (always on exact values). Raw equality can additionally+-- distinguish singleton from one-key @Liner@.+--+-- In particular, @mapBasePart f (bar x) == bar (mapBasePart f x)@ is false in+-- general: distinct source bases may collide only after relabelling. When+-- collisions occur, @pairAppend@ preserves every value but the resulting+-- sequence order is a representation detail determined by @HashMap@ traversal;+-- callers must not attach semantics to it.+--+-- Complexity: O(n) over distinct base keys (rebuilds the posting index once).+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> mapBasePart id (10.@Hat:<Yen :: T) :: T+-- 10.00:@Hat:<Yen+--+-- >>> norm (mapBasePart (const Amount) (10.@Not:<Yen .+ 5.@Not:<Dollar :: T) :: T)+-- 15.0+mapBasePart :: (HatVal v, HatBaseClass b, HatBaseClass b')+ => (BasePart b -> BasePart b') -> Alg v b -> Alg v b'+mapBasePart _ Zero = Zero+mapBasePart f (v :@ b) = singleton v (merge (hat b) (f (base b)))+mapBasePart f (Liner m _ _ _ _ _) =+ mkAlgFromMap $+ Map.foldlWithKey'+ (\acc bp p -> Map.insertWith pairAppend (f bp) p acc)+ Map.empty+ m++------------------------------------------------------------+-- | Free extension: substitute every scalar entry @v :\@ b@ by the algebra+-- @h v b@ and merge the results with @('.+')@.+--+-- Through ℘ (see 'mapBasePart'), 'Alg' is the free commutative monoid on its+-- entries, and @extendBy h@ is the unique ℘-homomorphism that agrees with @h@+-- on the generators. It acts entry by entry in the /redundant/ layer, whereas+-- 'postFromNetBy' first nets through exact, unrounded base residuals and+-- extends from that quotient, as described in its rounding contract.+-- 'mapBasePart' is the special case that keeps the value and the Hat\/Not side+-- and only relabels the base part.+--+-- == Laws and their layer of validity+--+-- ℘ observes, for every full Hat\/Not base, the multiset of values and forgets+-- the per-side sequence order. Through ℘:+--+-- * @extendBy h (x .+ y)@ equals @extendBy h x .+ extendBy h y@.+-- * @extendBy (:\@)@ is the identity. Raw equality can fail because the+-- result is rebuilt by a fold: the per-side order and the+-- singleton-versus-@Liner@ representation may differ from @x@.+-- * @mapBasePart f x@ equals+-- @extendBy (\\v b -> v :\@ merge (hat b) (f (base b))) x@.+-- * @norm (extendBy h x)@ equals the sum of @norm (h v b)@ over the entries of+-- @x@ (exact additive value types; floating-point values carry the usual+-- rounding caveat). In particular, for exact additive value types, @norm@+-- is preserved whenever every @h v b@ has norm @v@. Rounded norms alone+-- do not suffice: with T = 2^53, replace a Not T posting by Not T and Not 1,+-- and preserve a separate Not 1 posting. Each replacement has the original+-- rounded norm, but the total norm changes from T to T+2.+--+-- Zero-valued entries are skipped, as in 'foldEntries'. Well-formedness of the+-- algebras returned by @h@ (non-negative, finite values) is the caller's+-- responsibility: build them with @(.\@)@ rather than the raw constructor.+-- The sequence order of the result is a representation detail determined by+-- the traversal of @x@; callers must not attach semantics to it.+--+-- Complexity: O(n · cost(h)) plus the cost of the @('.+')@ merges, in one+-- pass over the entries.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let split v b = (v / 2) .@ b .+ (v / 2) .@ b :: T+-- >>> norm (extendBy split (10.@Not:<Yen .+ 4.@Hat:<Dollar :: T))+-- 14.0+--+-- >>> extendBy (\v b -> v .@ b .+ v .@ b) (3.@Not:<Yen :: T) == (3.@Not:<Yen .+ 3.@Not:<Yen :: T)+-- True+{-# INLINE extendBy #-}+extendBy :: (HatVal v, HatBaseClass b, HatBaseClass b')+ => (v -> b -> Alg v b') -> Alg v b -> Alg v b'+extendBy h = foldEntries (\acc v b -> acc .+ h v b) Zero++------------------------------------------------------------+-- | proj+--+-- Projects an 'Alg' onto the bases matching a query list. The query list is+-- treated as a __set__: duplicate queries, or an exact base that also matches a+-- wildcard query in the same list, select each underlying posting __at most+-- once__ (no double counting). The result is the union of the selected+-- @(base, side)@ cells.+--+-- Wildcards match __one way__: only a wildcard in the /query/ is a pattern+-- (\"any value on this axis\"). A wildcard stored in a ledger base is an+-- ordinary value (\"this axis does not apply\"): a wildcard query axis selects+-- it, a concrete query axis does not. The result is the same whether the+-- ledger is a single element or a 'Liner'.+--+-- >>> type Test = Alg Double (HatBase (CountUnit, CountUnit))+-- >>> proj [Not:<(Yen,Yen)] (10.@Not:<(Yen,(.#)) :: Test)+-- 0+--+-- >>> type Test = Alg Double (HatBase (CountUnit, CountUnit))+-- >>> proj [Not:<(Yen,(.#))] (10.@Not:<(Yen,(.#)) :: Test)+-- 10.00:@Not:<(Yen,CountUnit)+--+-- Complexity:+-- exact single-key path: expected O(1)+-- wildcard single-key path: O(queryAxisPosting + c * verify)+-- multi-pattern path: O(sum pattern costs + union costs)+--+-- where c is candidate count returned by the posting index.+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> proj [Hat:<Yen] $ x .+ y+-- 1.00:@Hat:<Yen+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> proj [HatNot:<Amount] $ x .+ y+-- 2.00:@Hat:<Amount .+ 1.00:@Not:<Amount+--+-- >>> type Test = Alg Double (HatBase (CountUnit, CountUnit))+-- >>> x = 1.@Hat:<(Yen,Yen) .+ 1.@Not:<(Amount,Amount) :: Test+-- >>> y = 2.@Not:<(Yen,Yen) .+ 2.@Hat:<(Dollar,Yen) :: Test+-- >>> proj [Hat:<((.#),Yen)] $ x .+ y+-- 1.00:@Hat:<(Yen,Yen) .+ 2.00:@Hat:<(Dollar,Yen)+--+-- >>> type Test = HatBase CountUnit+-- >>> compareHatBase (Not:<(.#) :: Test) (Not:<Yen :: Test)+-- EQ+--+-- >>> type Test = Alg Double (HatBase CountUnit)+-- >>> x = 1.@Hat:<Yen .+ 1.@Not:<Amount :: Test+-- >>> y = 2.@Not:<Yen .+ 2.@Hat:<Amount :: Test+-- >>> proj [Not:<(.#)] $ x .+ y+-- 2.00:@Not:<Yen .+ 1.00:@Not:<Amount+--+------------------------------------------------------------++proj :: (HatVal v, HatBaseClass b) => [b] -> Alg v b -> Alg v b+proj [] _ = Zero+proj _ Zero = Zero+proj [b] (v:@b2)+ | matchesQuery b b2 = v`Single`b2+ | otherwise = Zero+-- Index fields bound lazily (@~@) so a concrete (non-wildcard) base never forces+-- the axis index (the module is @Strict@; see 'projExactMap').+proj [b] (Liner m ~idx _ ~idToBp _ ~allIds) =+ mkAlgFromMap $+ if haveWildcard (base b)+ then projWildMap b m idx idToBp allIds+ else projExactMap b m+proj (b:bs) (v:@b2)+ | matchesQuery b b2 = v`Single`b2+ | otherwise = proj bs (v`Single`b2)+-- Multi-pattern path: the query list is treated as a /set/. Overlapping or+-- duplicate queries (e.g. a duplicated base, or an exact base subsumed by a+-- wildcard) select the same posting only once. Per-base results are merged+-- with 'pairUnion' (set union of the selected sides) rather than @pairAppend@+-- (concatenation), so no posting is double counted.+proj (b:bs) (Liner m ~idx _ ~idToBp _ ~allIds) =+ mkAlgFromMap $+ L.foldl'+ (\acc q -> Map.unionWith pairUnion acc+ (if haveWildcard (base q)+ then projWildMap q m idx idToBp allIds+ else projExactMap q m))+ Map.empty+ (b:bs)++{-# INLINE choosePairByHat #-}+-- | Complexity: O(1)+choosePairByHat :: Hat -> Pair v -> Pair v+choosePairByHat h Pair {_hatSide = hs, _notSide = ns} =+ case h of+ Hat -> nullPair {_hatSide = hs}+ Not -> nullPair {_notSide = ns}+ HatNot -> Pair {_hatSide = hs, _notSide = ns}++{-# INLINE projExactMap #-}+-- | Exact (non-wildcard) single-base projection: a direct 'Map.lookup' on the+-- @_realg@ map. It does NOT reference the axis index, so the concrete projection+-- path can keep that lazy index unforced.+--+-- This matters because the module is compiled @{-\# LANGUAGE Strict \#-}@: handing+-- the (lazy) index to a helper that takes it as a strict argument would force its+-- whole construction even for a concrete lookup that never needs it. Callers+-- therefore dispatch on 'haveWildcard' BEFORE touching the index, binding the+-- index fields lazily (@~@) and only mentioning them on the wildcard branch.+-- Guarded by the poison-index regression test in the test suite.+--+-- Complexity: expected O(1).+projExactMap+ :: (HatBaseClass b)+ => b+ -> Map.HashMap (BasePart b) (Pair v)+ -> Map.HashMap (BasePart b) (Pair v)+projExactMap b m = case Map.lookup bp m of+ Nothing -> Map.empty+ Just p -> case choosePairByHat h p of+ selected@(Pair hs ns) -> case (Seq.null hs, Seq.null ns) of+ (True, True) -> Map.empty+ _ -> Map.singleton bp selected+ where+ !bp = base b+ !h = hat b++{-# INLINE projWildMap #-}+-- | Wildcard single-base projection: resolves candidates through the axis index+-- ('queryAxisPosting'), so it necessarily forces the index. Only invoked when+-- 'haveWildcard' holds.+--+-- Complexity: O(queryAxisPosting + c * verify).+projWildMap+ :: (HatBaseClass b)+ => b+ -> Map.HashMap (BasePart b) (Pair v)+ -> AxisPosting+ -> IntMap.IntMap (BasePart b)+ -> IntSet.IntSet+ -> Map.HashMap (BasePart b) (Pair v)+projWildMap b m idx idToBp allIds =+ let !ids = queryAxisPosting (toAxisKeys bp) idx allIds+ in IntSet.foldl'+ (\acc bpId -> case IntMap.lookup bpId idToBp of+ Nothing -> acc+ Just bp0 -> case Map.lookup bp0 m of+ Nothing -> acc+ Just p+ | matchesQuery bp bp0 -> case choosePairByHat h p of+ selected@(Pair hs ns) -> case (Seq.null hs, Seq.null ns) of+ (True, True) -> acc+ _ -> Map.insert bp0 selected acc+ | otherwise -> acc)+ Map.empty+ ids+ where+ !bp = base b+ !h = hat b++{-# INLINE mkAlgFromMap #-}+-- | Complexity: O(n) to inspect shape and possibly rebuild index.+mkAlgFromMap :: (HatVal v, HatBaseClass b) => Map.HashMap (BasePart b) (Pair v) -> Alg v b+mkAlgFromMap m+ | Map.null m = Zero+ | otherwise = case Map.toList m of+ [(b, p)] -> Maybe.fromMaybe (linerFromMap $ Map.singleton b p) (singlePairToAlg b p)+ _ -> linerFromMap m++{-# INLINE singlePairToAlg #-}+-- | Complexity: O(1)+singlePairToAlg :: (HatVal v, HatBaseClass b) => BasePart b -> Pair v -> Maybe (Alg v b)+singlePairToAlg b (Pair hs ns) = case (Seq.viewl hs, Seq.viewl ns) of+ (Seq.EmptyL, n Seq.:< nsRest) | Seq.null nsRest -> Just (n `Single` merge Not b)+ (h Seq.:< hsRest, Seq.EmptyL) | Seq.null hsRest -> Just (h `Single` merge Hat b)+ _ -> Nothing++------------------------------------------------------------------++-- | Bar-netted norm of a projection. The query list is treated as a __set__+-- (see 'proj'): overlapping or duplicate queries do not double count.+--+-- Note the semantics include the @bar@ netting: each projected base is reduced+-- to the non-negative net of its hat and not sides. On exact values+--+-- @projNetNorm bs x == norm (bar (proj bs x))@+--+-- which is /not/ the same as @norm (proj bs x)@ when a base carries both sides.+-- The implementation adds the unrounded per-base residuals and rounds once,+-- whereas @norm (bar (proj bs x))@ rounds each base first; for floating-point+-- types the two can differ (residuals @2^53 + 1@ and @1@ give @2^53 + 2@ here+-- and @2^53@ there).+--+-- Wildcards match one way, as in 'proj': a concrete query axis does not match+-- a wildcard stored in a ledger base.+-- On an axis-preserving ledger, this nets only within each projected full+-- base; it does not cancel across axis values. Use 'balanceMapBy' or+-- 'netPairMapBy' for net amounts by group.+-- Sums are exact over the given postings and the output is rounded once; sides+-- cancel only when they are exactly equal.+--+-- Complexity: O(cost(proj) + cost(bar) + cost(norm)).+{-# SPECIALIZE projNetNorm :: HatBaseClass b => [b] -> Alg Double b -> Double #-}+{-# SPECIALIZE projNetNorm :: HatBaseClass b => [b] -> Alg MoneyDouble b -> MoneyDouble #-}+{-# SPECIALIZE projNetNorm :: HatBaseClass b => [b] -> Alg MoneyDecimal b -> MoneyDecimal #-}+projNetNorm :: (HatVal n, HatBaseClass b) => [b] -> Alg n b -> n+projNetNorm queries = roundAccum . projectedNetState queries++-- | Indexed projection followed by exact base-local netting, without rounding.+projectedNetState :: (HatVal n, HatBaseClass b) => [b] -> Alg n b -> Accum n+{-# INLINE projectedNetState #-}+projectedNetState [] _ = emptyAccum+projectedNetState _ Zero = emptyAccum+projectedNetState bs (v :@ b)+ | L.any (`matchesQuery` b) bs = addAccum v emptyAccum+ | otherwise = emptyAccum+-- Index fields bound lazily (@~@); a concrete base uses 'projExactMap' (a plain+-- 'Map.lookup') and never forces the axis index. See 'projExactMap'.+projectedNetState [b] (Liner m ~idx _ ~idToBp _ ~allIds) =+ foldProjectedState $+ if haveWildcard (base b)+ then projWildMap b m idx idToBp allIds+ else projExactMap b m+-- Multi-pattern path: the query list is a /set/ (see 'proj'). Per-base results+-- are merged with 'pairUnion' so overlapping/duplicate queries do not double+-- count. Note 'projectedNetState' returns a bar-netted norm: each base contributes its+-- unrounded residual, and the residuals are rounded once at the end.+projectedNetState bs (Liner m ~idx _ ~idToBp _ ~allIds) =+ foldProjectedState $+ L.foldl'+ (\acc q -> Map.unionWith pairUnion acc+ (if haveWildcard (base q)+ then projWildMap q m idx idToBp allIds+ else projExactMap q m))+ Map.empty+ bs++{-# INLINE foldProjectedNorm #-}+-- | Complexity: O(k), where k is the number of projected base keys.+foldProjectedNorm :: (HatVal n) => Map.HashMap k (Pair n) -> n+foldProjectedNorm = roundAccum . foldProjectedState++-- | Merge projected base residuals before rounding the requested output.+foldProjectedState :: HatVal n => Map.HashMap k (Pair n) -> Accum n+{-# INLINE foldProjectedState #-}+foldProjectedState = Map.foldl' (\total pair ->+ let state = netPairState pair+ in mergeAccum total (case signAccum state of+ Just LT -> negateAccum state+ _ -> state)) emptyAccum++{-# INLINE barNormPair #-}+-- | Round the exact magnitude of one base's residual.+barNormPair :: HatVal n => Pair n -> n+barNormPair pair = case signAccum state of+ Just LT -> roundAccum (negateAccum state)+ _ -> roundAccum state+ where+ state = netPairState pair+++-- | Compute the net balance as the difference of two projections.+-- @balanceBy plusBases minusBases alg@ computes+-- @projNetNorm plusBases alg - projNetNorm minusBases alg@ on exact values.+-- The implementation takes the exact difference of the two unrounded projected+-- residuals and rounds once, so it is not the difference of two rounded+-- 'projNetNorm' results.+--+-- Useful for calculating stock quantities, profits, etc.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let alg = 100 .@ Not:<Yen .+ 30 .@ Hat:<Yen :: T+-- >>> balanceBy [Not:<Yen] [Hat:<Yen] alg+-- 70.0+--+-- >>> balanceBy [Hat:<Yen] [Not:<Yen] alg+-- -70.0+{-# SPECIALIZE balanceBy :: HatBaseClass b => [b] -> [b] -> Alg Double b -> Double #-}+{-# SPECIALIZE balanceBy :: HatBaseClass b => [b] -> [b] -> Alg MoneyDouble b -> MoneyDouble #-}+{-# SPECIALIZE balanceBy :: HatBaseClass b => [b] -> [b] -> Alg MoneyDecimal b -> MoneyDecimal #-}+balanceBy :: (HatVal n, HatBaseClass b) => [b] -> [b] -> Alg n b -> n+balanceBy plusBases minusBases alg =+ roundAccum (mergeAccum (projectedNetState plusBases alg)+ (negateAccum (projectedNetState minusBases alg)))++-- | Aggregate the net balance of an 'Alg' by a key, in a single pass.+--+-- @balanceMapBy keyOf@ is the bucketed form of 'balanceBy': for every entry it+-- projects the (side-stripped) 'BasePart' to a bucket key with @keyOf@ ('Nothing'+-- drops the entry), and nets each bucket using the Hat\/Not convention (Not adds,+-- Hat subtracts) — on exact values, @projNetNorm [Not:<k] - projNetNorm [Hat:<k]@+-- per key. The implementation takes the exact signed sum and rounds once, so with+-- floating-point values it is not the difference of two rounded 'projNetNorm'+-- results.+-- @keyOf@ sees only the 'BasePart', not the Hat\/Not side, so it cannot split one+-- key across sides.+--+-- This replaces @[ (k, balanceBy [Not:<k] [Hat:<k] alg) | k <- keys ]@ — one+-- wildcard projection per key — with a single fold; both readouts sum exactly+-- before rounding. For per-key reporting over many keys this is the+-- difference between @O(keys * entries)@ and @O(entries)@.+--+-- The values are /signed/ net balances and may be negative, so use a signed value+-- type (e.g. 'Double', @MoneyDouble@, @MoneyDecimal@); a non-negative-only type+-- such as @Number.NonNegative.Double@ is unsuitable here. Keys whose net is zero+-- are kept (like 'foldEntriesToMap'); filter afterwards if undesired.+-- Each key's signed sum (Not minus Hat) is exact over the given postings and+-- rounded once.+--+-- Complexity: O(total number of entries) — a single fold, no per-key projection.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let alg = 100 .@ Not:<Yen .+ 30 .@ Hat:<Yen .+ 50 .@ Not:<Dollar :: T+-- >>> balanceMapBy Just alg+-- fromList [(Yen,70.0),(Dollar,50.0)]+{-# INLINE balanceMapBy #-}+{-# SPECIALIZE balanceMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg Double b -> M.Map k Double #-}+{-# SPECIALIZE balanceMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg MoneyDouble b -> M.Map k MoneyDouble #-}+{-# SPECIALIZE balanceMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg MoneyDecimal b -> M.Map k MoneyDecimal #-}+balanceMapBy :: (HatVal v, HatBaseClass b, Ord k)+ => (BasePart b -> Maybe k) -> Alg v b -> M.Map k v+balanceMapBy keyOf = M.map roundAccum . foldPairs collect M.empty+ where+ collect totals basePart pair = case keyOf basePart of+ Nothing -> totals+ Just key -> M.insertWith mergeAccum key (netPairState pair) totals++-- | Aggregate the per-base /netted/ balance of an 'Alg' by a key, keeping the+-- winning side, in a single pass.+--+-- This is the implementation counterpart of the paper's @def:class-net@+-- (\(\nu_\kappa\)), the __pair__ read-out of the class-net operator:+-- Before rounding, the exact Not-minus-Hat total of each key equals the+-- difference of its two unrounded residual totals. Read a key absent from+-- netPairMapBy as (0,0): balanceMapBy retains zero keys after full cancellation.+-- The equality also holds for+-- the returned values when rounding is the identity on sums and subtraction is+-- exact (MoneyDecimal within its scale limit, or a Rational-backed value type).+-- It does not hold for Double or MoneyDouble: let T = 2^53 and group two+-- distinct bases containing Not T, Not 1 and Hat T under one key. The signed+-- readout is 1, but the pair is (T,T), whose rounded-component difference is 0.+--+-- @netPairMapBy keyOf@ projects each entry's (side-stripped) 'BasePart' to a+-- bucket key with @keyOf@ ('Nothing' drops the entry), and for every key+-- returns a pair @(notTotal, hatTotal)@ built as follows: for each base \(b\)+-- it first nets the two sides (@bar@-like cancellation of the redundant+-- sequences), keeping only the residual on the side that wins, then sums the+-- residuals across all bases mapping to the key —+--+-- \[ \Big( \textstyle\sum_{n_b > h_b} (n_b - h_b),\ \sum_{h_b > n_b} (h_b - n_b) \Big). \]+--+-- This per-base netting is named and documented here — it is /not/ an implicit+-- 'bar'; the function performs exactly the standard-element reduction the name+-- promises, mirroring how 'balanceMapBy' reports a netted read-out.+--+-- __Both components are non-negative__ (consistent with the value domain+-- \(V \subseteq \mathbb{R}_{\ge 0}\)): a base contributes to at most one side,+-- whichever residual is larger. Bases whose two sides are exactly equal+-- contribute nothing. Because of the non-negativity,+-- @netPairMapBy@ is well behaved for non-negative-only value types, whereas the+-- @n - h@ identity on returned values requires a signed value type with exact+-- sums and subtraction, as specified above; a signed floating-point type is+-- insufficient.+-- Each base is netted exactly; the unrounded residuals are merged per key and+-- winning side, and each component is rounded once. Both sides of distinct+-- bases within a key do not cancel.+--+-- Complexity: O(total number of entries) — a single fold over the entries,+-- followed by one collapse over the distinct bases.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let alg = 100 .@ Not:<Yen .+ 30 .@ Hat:<Yen .+ 50 .@ Not:<Dollar :: T+-- >>> netPairMapBy Just alg+-- fromList [(Yen,(70.0,0.0)),(Dollar,(50.0,0.0))]+{-# INLINE netPairMapBy #-}+{-# SPECIALIZE netPairMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg Double b -> M.Map k (Double, Double) #-}+{-# SPECIALIZE netPairMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg MoneyDouble b -> M.Map k (MoneyDouble, MoneyDouble) #-}+{-# SPECIALIZE netPairMapBy :: (HatBaseClass b, Ord k) => (BasePart b -> Maybe k) -> Alg MoneyDecimal b -> M.Map k (MoneyDecimal, MoneyDecimal) #-}+netPairMapBy :: (HatVal v, HatBaseClass b, Ord k)+ => (BasePart b -> Maybe k) -> Alg v b -> M.Map k (v, v)+netPairMapBy keyOf = M.map finish . foldPairs collect M.empty+ where+ collect totals basePart pair = case keyOf basePart of+ Nothing -> totals+ Just key ->+ let state = netPairState pair+ in case signAccum state of+ Just EQ -> totals+ Just GT -> M.insertWith combine key (state, emptyAccum) totals+ Just LT -> M.insertWith combine key (emptyAccum, negateAccum state) totals+ Nothing -> M.insertWith combine key (state, state) totals+ combine (!first, !second) (!left, !right) =+ let !notTotal = mergeAccum first left+ !hatTotal = mergeAccum second right+ in (notTotal, hatTotal)+ finish (!first, !second) =+ let !notTotal = roundAccum first+ !hatTotal = roundAccum second+ in (notTotal, hatTotal)++-- | Fold algebra entries into a @Map@, combining values with @(+)@.+--+-- The selector function examines each entry @(v, b)@ and optionally returns+-- a @(key, value)@ pair. Values for duplicate keys are summed.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let alg = 10 .@ Hat:<Yen .+ 20 .@ Hat:<Dollar .+ 5 .@ Hat:<Yen :: T+-- >>> let f v (Hat :< a) = Just (a, v); f _ _ = Nothing+-- >>> foldEntriesToMap f alg+-- fromList [(Yen,15.0),(Dollar,20.0)]+foldEntriesToMap :: (HatVal v, HatBaseClass b, Ord k)+ => (v -> b -> Maybe (k, v))+ -> Alg v b+ -> M.Map k v+foldEntriesToMap f = foldEntries step M.empty+ where+ step acc v b = case f v b of+ Just (k, v') -> M.insertWith (+) k v' acc+ Nothing -> acc++-- | Quotient decomposition (dec_κ): partition an algebra along the equivalence+-- classes induced by a classifier on the full 'HatBase', in a single pass.+--+-- For each entry, @keyOf@ maps its base (Hat\/Not state included) to a class+-- key; @Nothing@ drops the entry as residual. Each class is returned as an+-- 'Alg' that is exactly the restriction of the input to that class:+-- __redundancy (the per-base value sequences) is fully preserved__ — no 'bar',+-- no 'norm', no aggregation. The pieces reconstruct the input:+-- @mconcat (M.elems (decBy keyOf x)) .+ residual == x@ (up to per-base+-- sequence order).+--+-- This generalizes the decomposition operators of Deguchi & Nakano (1986)+-- ('ExchangeAlgebra.Accounting.Exchange.decR', 'ExchangeAlgebra.Accounting.Exchange.decL',+-- 'ExchangeAlgebra.Accounting.Exchange.decP', and 'ExchangeAlgebra.Accounting.Exchange.decM'+-- are two-class special cases) and replaces+-- per-class projection loops: one pass over the entries instead of one+-- projection query per class.+--+-- Choosing between the per-key family:+--+-- +--------------------+----------------------------------+---------------------++-- | function | returns | redundancy |+-- +====================+==================================+=====================++-- | 'decBy' | @Map k (Alg v b)@ (structure) | preserved |+-- +--------------------+----------------------------------+---------------------++-- | 'balanceMapBy' | @Map k v@ (signed net per key) | lost (bar-like) |+-- +--------------------+----------------------------------+---------------------++-- | 'netPairMapBy' | @Map k (v,v)@ (non-neg net pair) | lost (bar-like) |+-- +--------------------+----------------------------------+---------------------++-- | 'foldEntriesToMap' | @Map k v@ (custom collection) | lost |+-- +--------------------+----------------------------------+---------------------++-- | 'mapBasePart' | @Alg v b'@ (base coarsening π_κ) | preserved |+-- +--------------------+----------------------------------+---------------------++--+-- Note on 'bar': @bar@ commutes with 'decBy' componentwise iff @keyOf@ does not+-- distinguish Hat\/Not (i.e. factors through 'base'). Side-sensitive classifiers+-- (such as the ones underlying+-- 'ExchangeAlgebra.Accounting.Exchange.decP'\/'ExchangeAlgebra.Accounting.Exchange.decM' or+-- 'ExchangeAlgebra.Accounting.Exchange.decL'\/'ExchangeAlgebra.Accounting.Exchange.decR') do not+-- commute+-- with @bar@ — netting before or after such a split is a semantic choice.+--+-- Complexity: O(m) over distinct bases (single fold; per-class insert costs+-- O(log k) in the result 'M.Map').+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let alg = 100 .@ Not:<Yen .+ 30 .@ Hat:<Yen .+ 50 .@ Not:<Dollar :: T+-- >>> M.toList (M.map norm (decBy (\(_ :< a) -> Just a) alg))+-- [(Yen,130.0),(Dollar,50.0)]+--+-- >>> M.toList (M.map norm (decBy (\b -> if isHat b then Just () else Nothing) alg))+-- [((),30.0)]+{-# INLINE decBy #-}+decBy :: (HatVal v, HatBaseClass b, Ord k)+ => (b -> Maybe k)+ -> Alg v b+ -> M.Map k (Alg v b)+decBy _ Zero = M.empty+decBy kf a@(v :@ b)+ | isZeroValue v = M.empty+ | otherwise = case kf b of+ Nothing -> M.empty+ Just k -> M.singleton k a+decBy kf (Liner m _ _ _ _ _) =+ M.map mkAlgFromMap (Map.foldlWithKey' step M.empty m)+ where+ step !acc !bp (Pair hs ns) =+ let !acc1 = if Seq.null hs+ then acc+ else insertSide (merge Hat bp) bp (nullPair {_hatSide = hs}) acc+ in if Seq.null ns+ then acc1+ else insertSide (merge Not bp) bp (nullPair {_notSide = ns}) acc1+ {-# INLINE insertSide #-}+ insertSide hb bp p acc = case kf hb of+ Nothing -> acc+ Just k -> M.insertWith (Map.unionWith (flip pairAppend)) k (Map.singleton bp p) acc++-- | Classify-net-post, fused: net the algebra per base ('bar' — explicit in the+-- name), classify each netted entry with @keyOf@ (class totals are summed with+-- @(+)@), then generate postings per class and bulk-merge them.+--+-- On exact values,+-- @postFromNetBy keyOf post x == sigmaFromMap (foldEntriesToMap collect (bar x)) post@+-- where @collect@ pairs each netted entry with its class. With floating-point+-- values the two differ: the left side merges the unrounded residuals of a key+-- and rounds once, the right side rounds each base in 'bar' first (residuals+-- @2^53 + 1@ and @1@ in one key give @2^53 + 2@ and @2^53@). The common+-- \"shortage detection → purchase postings\" pattern becomes a single call:+--+-- @+-- purchases = postFromNetBy shortageKey purchasePosting termAlg+-- @+--+-- and runs in one pass over the netted entries — no per-pair projection loop+-- (the naive all-pairs formulation costs O(N²) queries; this costs O(m)).+--+-- __This function applies unrounded exact bar internally__ (per-base netting, the standard+-- positive-part normalization). Redundancy of the input is not preserved in the+-- intermediate; the output is whatever @post@ builds. If you need the+-- redundancy-preserving split itself, use 'decBy'.+-- Thus it factors through exact base residuals before rounding, not through+-- the returned 'bar'. With T = 2^53, two bases with residuals (T+1,1) and (T,1)+-- have identical rounded bar outputs. Grouping them under one key gives callback+-- arguments T+2 and T, respectively. It is not the free+-- extension that acts independently on entries in the redundant layer (that+-- one is 'extendBy').+-- Each base is netted exactly to choose the winning side; the unrounded+-- residuals are merged per key, and the value passed to the callback is rounded+-- once. The guarantee ends at the callback's argument. Bases containing+-- nonfinite inputs retain their original postings and are not passed to the+-- callback. Finite accumulation overflow is rounded only at the callback+-- boundary, which may receive infinity.+--+-- Complexity: O(m + Σ cost(post)).+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> let stock = 100 .@ Not:<Amount .+ 130 .@ Hat:<Amount .+ 20 .@ Not:<Yen :: T+-- >>> let shortageKey b = case b of { Hat :< Amount -> Just () ; _ -> Nothing }+-- >>> norm (postFromNetBy shortageKey (\_ v -> v .@ Not:<Amount .+ v .@ Hat:<Yen) stock)+-- 60.0+{-# INLINE postFromNetBy #-}+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Ord k) => (b -> Maybe k) -> (k -> Double -> Alg Double b) -> Alg Double b -> Alg Double b #-}+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Ord k) => (b -> Maybe k) -> (k -> MoneyDouble -> Alg MoneyDouble b) -> Alg MoneyDouble b -> Alg MoneyDouble b #-}+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Ord k) => (b -> Maybe k) -> (k -> MoneyDecimal -> Alg MoneyDecimal b) -> Alg MoneyDecimal b -> Alg MoneyDecimal b #-}+postFromNetBy :: (HatVal v, HatBaseClass b, Ord k)+ => (b -> Maybe k)+ -> (k -> v -> Alg v b)+ -> Alg v b+ -> Alg v b+postFromNetBy keyOf post algebra =+ retained .+ sigmaFromMap (M.map roundAccum amounts) post+ where+ (!retained, !amounts) = postNetStates keyOf algebra
+ src/ExchangeAlgebra/Algebra/Element.hs view
@@ -0,0 +1,33 @@+{- | Generic basis elements and one-way wildcard matching (Definition 1).++Use 'matchesQuery' to match a query pattern against a stored element. The+wildcard in the query matches any stored value; a concrete query does not+match a stored wildcard.++== Extending with user-defined types++To use your own type as a basis component, declare an 'Element' instance.+A single distinguished value must serve as the wildcard used by the+transfer engine and by projection operations. Derive t'Control.DeepSeq.NFData' when keys+containing this component must be fully evaluated:++@+data Company = CompanyA | CompanyB | CompanyWildcard+ deriving (Eq, Ord, Show, Generic, Hashable, NFData, Typeable)++instance Element Company where+ wildcard = CompanyWildcard+@++== Import guidance++Import 'Element' and its associated symbols from+"ExchangeAlgebra.Algebra.Element". The old+"ExchangeAlgebra.Algebra.Base.Element" module is deprecated.+-}+module ExchangeAlgebra.Algebra.Element+ ( Element(..), AxisKey(..), axisIsWildcard, AxisDecompose(..), (.#)+ , Name, Subject, CountUnit(..), matchesQuery, Hashable(..), Generic+ ) where++import ExchangeAlgebra.Algebra.Element.Representation
+ src/ExchangeAlgebra/Algebra/Element/Representation.hs view
@@ -0,0 +1,522 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE DeriveGeneric #-}++module ExchangeAlgebra.Algebra.Element.Representation+ ( Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , matchesQuery+ , Hashable(..)+ , Generic+ ) where++import qualified Data.Text as T+import Data.Text (Text)+import qualified Data.Time as Time+import Data.Time+import GHC.Generics (Generic)+import Data.Hashable+import Data.Typeable (Typeable, cast, typeOf)+import Control.DeepSeq (NFData(..))+import qualified Data.Binary as Binary++------------------------------------------------------------------+-- * Element (components of bases)+------------------------------------------------------------------++-- | Element Class: a type must be an instance of this class to serve as a component of a basis.+--+-- Each component of a basis must be an instance of this type class.+-- It provides wildcard-based pattern matching, enabling flexible basis+-- specification in transfer transformations and projections.+--+-- A wildcard in a ledger entry's base means that axis does not apply; it is+-- one value, not a pattern. In a projection query or transfer source pattern,+-- it matches any entry value, including a ledger wildcard. In a transfer target,+-- it preserves the source coordinate. To collapse an axis, use a function such+-- as @mapBasePart@ rather than a wildcard.+--+-- For pattern @p@ and ledger entry @e@, @p matches e@ exactly when+-- @p = wildcard@ or @p = e@, applied componentwise to tuple bases. A concrete+-- query therefore does not select a ledger wildcard. Canonical ordering treats+-- the wildcard as an ordinary value.+class (Eq a, Ord a, Show a, Hashable a, Typeable a) => Element a where++ -- | The wildcard value. Used for pattern matching in search, transfer transformation, etc.+ --+ -- Complexity: O(1)+ wildcard :: a++ -- | Determines whether the element itself or any of its internal components contains a wildcard.+ -- For tuple elements, returns True if any component is a wildcard.+ --+ -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)+ {-# INLINE haveWildcard #-}+ haveWildcard :: a -> Bool+ haveWildcard = isWildcard++ -- | Determines whether the value is exactly the wildcard.+ --+ -- Complexity: O(1)+ {-# INLINE isWildcard #-}+ isWildcard :: a -> Bool+ isWildcard a = a == wildcard++ -- | Wildcard-ignoring transformation.+ -- If @after@ is a wildcard, returns @before@.+ -- Used inside transfer to fill wildcard positions in the target basis with the original values.+ --+ -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)+ {-# INLINE ignoreWildcard #-}+ ignoreWildcard :: a -> a -> a+ ignoreWildcard before after+ | before == after = before+ | isWildcard after = before+ | otherwise = after++ -- | Wildcard-aware equality test.+ -- Returns True if either operand is a wildcard.+ --+ -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)+ {-# INLINE equal #-}+ equal :: a -> a -> Bool+ equal a b | isWildcard a = True+ | isWildcard b = True+ | otherwise = a == b++ -- | Equality operator that treats wildcards as equal.+ -- Unlike '==', @(.==)@ matches tuples that partially contain wildcards.+ -- This operator remains symmetric when either operand contains a wildcard;+ -- its meaning stays unchanged in 0.5.x. Do not use it to match a query to+ -- ledger entries: the @proj@ family matches one way since 0.5.1.0.+ --+ -- Complexity: O(k) (k is the number of tuple components; O(1) for primitive types)+ {-# INLINE (.==) #-}+ (.==) :: a -> a -> Bool+ (.==) a b = a == b || (haveWildcard a || haveWildcard b) && equal a b++ -- | Inequality operator that treats wildcards as equal. Negation of @(.==)@.+ --+ -- Complexity: O(k)+ {-# INLINE (./=) #-}+ (./=) :: a -> a -> Bool+ (./=) a b = not (a .== b)++ -- | Wildcard-aware comparison.+ -- Returns EQ if the two values are equal under @(.==)@.+ --+ -- Complexity: O(k)+ {-# INLINE compareElement #-}+ compareElement :: a -> a -> Ordering+ compareElement x y+ | x .== y = EQ+ | otherwise = compare x y++ -- | Wildcard-aware less-than comparison.+ --+ -- Complexity: O(k)+ (.<) :: a -> a -> Bool+ (.<) x y = compareElement x y == LT++ -- | Wildcard-aware greater-than comparison.+ --+ -- Complexity: O(k)+ (.>) :: a -> a -> Bool+ (.>) x y = compareElement x y == GT++ -- | Wildcard-aware less-than-or-equal comparison.+ --+ -- Complexity: O(k)+ (.<=) :: a -> a -> Bool+ (.<=) x y = compareElement x y /= GT++ -- | Wildcard-aware greater-than-or-equal comparison.+ --+ -- Complexity: O(k)+ (.>=) :: a -> a -> Bool+ (.>=) x y = compareElement x y /= LT++ -- | Wildcard-aware maximum.+ --+ -- Complexity: O(k)+ maxElement :: a -> a -> a+ maxElement x y+ | x .>= y = x+ | otherwise = y++ -- | Wildcard-aware minimum.+ --+ -- Complexity: O(k)+ minElement :: a -> a -> a+ minElement x y+ | x .<= y = x+ | otherwise = y++-- | An existential type that holds each axis of a basis with its type erased.+-- Used to decompose multi-dimensional bases (tuples) into per-axis keys for indexing.+data AxisKey = forall a. Element a => AxisKey !a++-- | Force the stored axis to weak head normal form without requiring 'NFData'+-- from every user-defined 'Element'.+instance NFData AxisKey where+ rnf (AxisKey axis) = axis `seq` ()++instance Eq AxisKey where+ AxisKey x == AxisKey y = case cast y of+ Nothing -> False+ Just y' -> x == y'++instance Hashable AxisKey where+ hashWithSalt salt (AxisKey x) = salt `hashWithSalt` (typeOf x) `hashWithSalt` x++-- | Test whether the stored axis value is its wildcard.+{-# INLINE axisIsWildcard #-}+axisIsWildcard :: AxisKey -> Bool+axisIsWildcard (AxisKey x) = isWildcard x++-- | A type class for decomposing a basis element into a list of per-axis t'AxisKey's.+-- Overlapping instances are defined for tuple types so that each component+-- is decomposed into a separate t'AxisKey'.+--+-- Complexity: O(k) (k is the number of tuple components)+class (Element a) => AxisDecompose a where+ toAxisKeys :: a -> [AxisKey]++instance {-# OVERLAPPABLE #-} Element a => AxisDecompose a where+ {-# INLINE toAxisKeys #-}+ toAxisKeys a = [AxisKey a]++-- | Shorthand notation for the wildcard. An alias for @wildcard@.+-- Write @(.#)@ when specifying patterns in projections and transfer transformations.+--+-- Complexity: O(1)+{-# INLINE (.#) #-}+(.#) :: Element a => a+(.#) = wildcard++infix 4 .==+infix 4 ./=+------------------------------------------------------------------+-- | Name :: Name of an item+type Name = Text++-- | Subject of an account title+type Subject = Text+instance Element Text where++ {-# INLINE wildcard #-}+ wildcard = T.empty++-- | Currency unit or physical quantity+data CountUnit = Yen+ | Dollar+ | Euro+ | CNY+ | Amount+ | CountUnit+ deriving (Show, Ord, Eq, Enum,Generic)++instance NFData CountUnit++instance Hashable CountUnit where+ {-# INLINE hashWithSalt #-}+ hashWithSalt salt x = hashWithSalt salt (fromEnum x)++instance Binary.Binary CountUnit where+ {-# INLINE put #-}+ put = Binary.putWord8 . fromIntegral . fromEnum+ {-# INLINE get #-}+ get = toEnum . fromIntegral <$> Binary.getWord8++instance Element CountUnit where++ {-# INLINE wildcard #-}+ wildcard = CountUnit+++-- TimeOfDay internally holds hour, minute, and second (Pico), so each is hashed individually+instance Hashable TimeOfDay where+ hashWithSalt salt (TimeOfDay hour minute sec) =+ salt `hashWithSalt` hour `hashWithSalt` minute `hashWithSalt` sec++-- Day internally holds an Integer in ModifiedJulianDay format, so that is used for hashing+instance Hashable Day where+ hashWithSalt salt day = hashWithSalt salt (toModifiedJulianDay day)++instance Element TimeOfDay where+ wildcard = Time.midnight++instance Element Day where+ wildcard = ModifiedJulianDay 0++instance (Element a ,Element b)+ => Element (a, b) where++ {-# INLINE wildcard #-}+ wildcard = (wildcard, wildcard)++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b)+ = haveWildcard a+ || haveWildcard b++ {-# INLINE equal #-}+ equal (a1, a2) (b1, b2)+ = (a1 .== b1)+ && (a2 .== b2)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2) (b1, b2)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2)++ {-# INLINE compareElement #-}+ compareElement (a1, a2) (b1, b2)+ = case compareElement a1 b1 of+ EQ -> compareElement a2 b2+ x -> x++instance {-# OVERLAPPING #-} (Element a, Element b)+ => AxisDecompose (a, b) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b) = [AxisKey a, AxisKey b]++instance (Element a, Element b, Element c)+ => Element (a, b, c) where++ {-# INLINE wildcard #-}+ wildcard = ( wildcard+ , wildcard+ , wildcard)++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b,c)+ = haveWildcard a+ || haveWildcard b+ || haveWildcard c+++ {-# INLINE equal #-}+ equal (a1, a2, a3) (b1, b2, b3)+ = (a1 .== b1)+ && (a2 .== b2)+ && (a3 .== b3)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2, a3) (b1, b2, b3)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2+ , ignoreWildcard a3 b3)++ {-# INLINE compareElement #-}+ compareElement (a1, a2, a3) (b1, b2, b3)+ = compareElement ((a1, a2), a3)+ ((b1, b2), b3)++instance {-# OVERLAPPING #-} (Element a, Element b, Element c)+ => AxisDecompose (a, b, c) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b, c) = [AxisKey a, AxisKey b, AxisKey c]+++instance (Element a, Element b, Element c, Element d)+ => Element (a, b, c, d) where++ {-# INLINE wildcard #-}+ wildcard = ( wildcard+ , wildcard+ , wildcard+ , wildcard)+++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b,c,d)+ = haveWildcard a+ || haveWildcard b+ || haveWildcard c+ || haveWildcard d++ {-# INLINE equal #-}+ equal (a1, a2, a3, a4) (b1, b2, b3, b4)+ = (a1 .== b1)+ && (a2 .== b2)+ && (a3 .== b3)+ && (a4 .== b4)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2, a3, a4) (b1, b2, b3, b4)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2+ , ignoreWildcard a3 b3+ , ignoreWildcard a4 b4)++ {-# INLINE compareElement #-}+ compareElement (a1, a2, a3, a4) (b1, b2, b3, b4)+ = compareElement ((a1, a2, a3), a4)+ ((b1, b2, b3), b4)++instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d)+ => AxisDecompose (a, b, c, d) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b, c, d) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d]+++instance (Element a, Element b, Element c, Element d, Element e)+ => Element (a, b, c, d, e) where++ {-# INLINE wildcard #-}+ wildcard = ( wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard)+++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b,c,d,e)+ = haveWildcard a+ || haveWildcard b+ || haveWildcard c+ || haveWildcard d+ || haveWildcard e++ {-# INLINE equal #-}+ equal (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)+ = (a1 .== b1)+ && (a2 .== b2)+ && (a3 .== b3)+ && (a4 .== b4)+ && (a5 .== b5)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2+ , ignoreWildcard a3 b3+ , ignoreWildcard a4 b4+ , ignoreWildcard a5 b5)++ {-# INLINE compareElement #-}+ compareElement (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)+ = compareElement ((a1, a2, a3, a4), a5)+ ((b1, b2, b3, b4), b5)++instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e)+ => AxisDecompose (a, b, c, d, e) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b, c, d, e) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e]+++instance (Element a, Element b, Element c, Element d, Element e, Element f)+ => Element (a, b, c, d, e, f) where++ {-# INLINE wildcard #-}+ wildcard = ( wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard)++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b,c,d,e,f)+ = haveWildcard a+ || haveWildcard b+ || haveWildcard c+ || haveWildcard d+ || haveWildcard e+ || haveWildcard f++ {-# INLINE equal #-}+ equal (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)+ = (a1 .== b1)+ && (a2 .== b2)+ && (a3 .== b3)+ && (a4 .== b4)+ && (a5 .== b5)+ && (a6 .== b6)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2+ , ignoreWildcard a3 b3+ , ignoreWildcard a4 b4+ , ignoreWildcard a5 b5+ , ignoreWildcard a6 b6)++ {-# INLINE compareElement #-}+ compareElement (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6)+ = compareElement ((a1, a2, a3, a4, a5), a6)+ ((b1, b2, b3, b4, b5), b6)++instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e, Element f)+ => AxisDecompose (a, b, c, d, e, f) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b, c, d, e, f) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e, AxisKey f]+++instance (Element a, Element b, Element c, Element d, Element e, Element f, Element g)+ => Element (a, b, c, d, e, f, g) where+ {-# INLINE wildcard #-}+ wildcard = ( wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard+ , wildcard)++ {-# INLINE haveWildcard #-}+ haveWildcard (a,b,c,d,e,f,g)+ = haveWildcard a+ || haveWildcard b+ || haveWildcard c+ || haveWildcard d+ || haveWildcard e+ || haveWildcard f+ || haveWildcard g++ {-# INLINE equal #-}+ equal (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)+ = (a1 .== b1)+ && (a2 .== b2)+ && (a3 .== b3)+ && (a4 .== b4)+ && (a5 .== b5)+ && (a6 .== b6)+ && (a7 .== b7)++ {-# INLINE ignoreWildcard #-}+ ignoreWildcard (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)+ = ( ignoreWildcard a1 b1+ , ignoreWildcard a2 b2+ , ignoreWildcard a3 b3+ , ignoreWildcard a4 b4+ , ignoreWildcard a5 b5+ , ignoreWildcard a6 b6+ , ignoreWildcard a7 b7)++ {-# INLINE compareElement #-}+ compareElement (a1, a2, a3, a4, a5, a6, a7) (b1, b2, b3, b4, b5, b6, b7)+ = compareElement ((a1, a2, a3, a4, a5, a6), a7)+ ((b1, b2, b3, b4, b5, b6), b7)++instance {-# OVERLAPPING #-} (Element a, Element b, Element c, Element d, Element e, Element f, Element g)+ => AxisDecompose (a, b, c, d, e, f, g) where+ {-# INLINE toAxisKeys #-}+ toAxisKeys (a, b, c, d, e, f, g) = [AxisKey a, AxisKey b, AxisKey c, AxisKey d, AxisKey e, AxisKey f, AxisKey g]++-- | One-way wildcard query matching. The query is the pattern.+-- Complexity: O(k) (k is the number of tuple components).+matchesQuery :: Element a => a -> a -> Bool+matchesQuery query entry = ignoreWildcard entry query == entry
− src/ExchangeAlgebra/Algebra/Exact.hs
@@ -1,608 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeSynonymInstances #-}---- | Checked readouts retain exact sums until each output is rounded once.--- This additive accounting layer uses the algebra, value types, and account--- balance representation; journal readouts build on it in--- "ExchangeAlgebra.Journal.Exact". Start with the accumulator contract, then--- read the algebra readouts for grouping and cancellation rules.------ Inputs must be finite and non-negative. Every intermediate aggregation unit--- and output must be at most the largest finite value of its type. Errors are--- sticky. Double outputs use nearest-even rounding and normalize negative zero.--- For Double, MoneyDouble, and NN.Double, the same multiset of complete bases,--- sides, and values gives bit-identical scalar outputs under reordering and--- accumulator merging. Changing notes, compressing, or substituting rounded--- partial totals is outside this guarantee, as are enumeration, Show, and Binary.-module ExchangeAlgebra.Algebra.Exact (- -- * Accumulators- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- -- * Algebra readouts- , normExact- , barExact- , projNetNormExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- -- * Accounting readouts- , diffRLExact- , balanceExact- , accountBalancesExact- ) where--import qualified Data.Decimal as Decimal-import qualified Data.Foldable as Foldable-import qualified Data.HashMap.Strict as HashMap-import qualified Data.Map.Strict as Map-import qualified Data.Sequence as Seq-import qualified Number.NonNegative as NN--import ExchangeAlgebra.Algebra (- Alg- , HatVal(..)- , foldEntries- , (.@)- , (.+)- )-import qualified ExchangeAlgebra.Algebra as Algebra-import qualified ExchangeAlgebra.Algebra.Internal as Internal-import ExchangeAlgebra.Algebra.Base (- AccountTitles- , ExBaseClass(..)- , Hat(..)- , HatBaseClass(..)- , Side(..)- )-import ExchangeAlgebra.TrialBalance.Balance (AccountBalance(..))-import ExchangeAlgebra.Value (MoneyDecimal(..), MoneyDouble(..))---- * Accumulators---- | Checked summation failures. A failed accumulator cannot recover by netting.-data ExactSumError- = NonFiniteInput -- ^ An input is NaN or infinite.- | NegativeInput -- ^ An input is negative.- | SumOutOfRange -- ^ An exact aggregation exceeds the largest finite value.- deriving (Eq, Show)---- | Exact non-negative aggregation with signed intermediates confined to the state.--- Floating instances require IEEE-754 nearest-even arithmetic. Decimal arithmetic--- has no upper range bound. Each readout validates its aggregation units before--- cancellation; invalid inputs and out-of-range sums produce sticky failures.------ == Laws------ For all four supplied instances, on valid states whose sums are in range,--- merge is commutative and associative, and empty is its identity, observed--- through the represented exact value. Netting returns the exact comparison--- and absolute difference. No tolerance applies. For floating instances,--- observing these laws through 'roundAccum' gives bit-identical values;--- 'roundAccum' is not a homomorphism into floating-point addition.-class HatVal n => ExactSum n where- -- | State containing an exact non-negative value or a sticky failure.- data Accum n-- -- | The valid state representing zero.- emptyAccum :: Accum n-- -- | Add one finite non-negative input without rounding the represented sum.- -- Invalid input or an exact sum beyond the type's range makes the state fail.- addAccum :: n -> Accum n -> Accum n-- -- | Merge exact values without rounding, retaining failures from either input.- -- The merged exact value must fit the type's range.- mergeAccum :: Accum n -> Accum n -> Accum n-- -- | Validate both states, then return their exact comparison and absolute- -- difference without rounding. Even equal out-of-range sides fail.- netAccumState :: Accum n -> Accum n -> Either ExactSumError (Ordering, Accum n)-- -- | Round once to the value type, normalizing negative zero to positive zero.- -- The exact value, not its rounded result, must be in range.- roundAccum :: Accum n -> Either ExactSumError n---- | Non-overlapping Shewchuk partials in increasing magnitude, with no zero terms.--- Both the elements and the spine are strict; signed values remain private.-data Partials- = NoPartials- | Partial !Double !Partials---- | Floating accumulator payload containing a checked scalar or expansion.-data FloatingState- = FloatingFailure !ExactSumError- | FloatingSingle !Double- | FloatingSum !Partials--instance ExactSum Double where- data Accum Double = DoubleAccum !FloatingState- emptyAccum = DoubleAccum (FloatingSingle 0)- addAccum value (DoubleAccum state) = DoubleAccum (addFloating value state)- mergeAccum (DoubleAccum left) (DoubleAccum right) =- DoubleAccum (mergeFloating left right)- netAccumState (DoubleAccum left) (DoubleAccum right) = do- (direction, difference) <- netFloating left right- pure (direction, DoubleAccum difference)- roundAccum (DoubleAccum state) = roundFloating state--instance ExactSum MoneyDouble where- data Accum MoneyDouble = MoneyDoubleAccum !(Accum Double)- emptyAccum = MoneyDoubleAccum emptyAccum- addAccum (MoneyDouble value) (MoneyDoubleAccum state) =- MoneyDoubleAccum (addAccum value state)- mergeAccum (MoneyDoubleAccum left) (MoneyDoubleAccum right) =- MoneyDoubleAccum (mergeAccum left right)- netAccumState (MoneyDoubleAccum left) (MoneyDoubleAccum right) = do- (direction, difference) <- netAccumState left right- pure (direction, MoneyDoubleAccum difference)- roundAccum (MoneyDoubleAccum state) = MoneyDouble <$> roundAccum state--instance ExactSum NN.Double where- data Accum NN.Double = NonNegativeAccum !(Accum Double)- emptyAccum = NonNegativeAccum emptyAccum- addAccum value (NonNegativeAccum state) =- NonNegativeAccum (addAccum (NN.toNumber value) state)- mergeAccum (NonNegativeAccum left) (NonNegativeAccum right) =- NonNegativeAccum (mergeAccum left right)- netAccumState (NonNegativeAccum left) (NonNegativeAccum right) = do- (direction, difference) <- netAccumState left right- pure (direction, NonNegativeAccum difference)- roundAccum (NonNegativeAccum state) = NN.fromNumber <$> roundAccum state--instance ExactSum MoneyDecimal where- data Accum MoneyDecimal- = DecimalFailure !ExactSumError- | DecimalAccum !(Decimal.DecimalRaw Integer)- emptyAccum = DecimalAccum 0- addAccum _ failure@(DecimalFailure _) = failure- addAccum (MoneyDecimal value) (DecimalAccum total)- | value < 0 = DecimalFailure NegativeInput- | otherwise = DecimalAccum (total + value)- mergeAccum failure@(DecimalFailure _) _ = failure- mergeAccum _ failure@(DecimalFailure _) = failure- mergeAccum (DecimalAccum left) (DecimalAccum right) = DecimalAccum (left + right)- netAccumState (DecimalFailure failure) _ = Left failure- netAccumState _ (DecimalFailure failure) = Left failure- netAccumState (DecimalAccum left) (DecimalAccum right) =- Right (compare left right, DecimalAccum (abs (left - right)))- roundAccum (DecimalFailure failure) = Left failure- roundAccum (DecimalAccum value) = Right (MoneyDecimal value)---- | Largest finite binary64 value, expressed without an overflowing intermediate.-maximumFinite :: Double-maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) (1024 - 53)---- | Error-free two-sum with the greater-magnitude operand first.-twoSum :: Double -> Double -> (Double, Double)-twoSum left right- | abs left < abs right = twoSum right left- | otherwise = let !high = left + right- !low = right - (high - left)- in (high, low)---- | Insert a signed component into an expansion without range checking.--- An infinite high component records arithmetic overflow for the caller.-insertPartial :: Double -> Partials -> Partials-insertPartial value NoPartials- | value == 0 = NoPartials- | otherwise = Partial value NoPartials-insertPartial value (Partial next rest)- | isInfinite high = Partial high NoPartials- | low == 0 = insertPartial high rest- | otherwise = Partial low (insertPartial high rest)- where- (!high, !low) = twoSum value next---- | Reverse an expansion for comparisons, subtraction, and final rounding.-reversePartials :: Partials -> Partials-reversePartials = go NoPartials- where- go !result NoPartials = result- go !result (Partial value rest) = go (Partial value result) rest---- | Compare an expansion with zero using its largest nonzero component.-signPartials :: Partials -> Ordering-signPartials NoPartials = EQ-signPartials (Partial value NoPartials) = compare value 0-signPartials (Partial _ rest) = signPartials rest---- | Keep zero and one-component states in a strict scalar payload, without a spine.-compactPartials :: Partials -> FloatingState-compactPartials NoPartials = FloatingSingle 0-compactPartials (Partial value NoPartials) = FloatingSingle value-compactPartials partials = FloatingSum partials---- | Check the exact expansion against M. If the largest component is M,--- the sign of the remaining expansion distinguishes M from M + one subnormal.-checkPartials :: Partials -> FloatingState-checkPartials partials = check 0 partials- where- check _ NoPartials = compactPartials partials- check previous (Partial largest NoPartials)- | isInfinite largest = FloatingFailure SumOutOfRange- | largest > maximumFinite = FloatingFailure SumOutOfRange- | largest == maximumFinite && previous > 0 = FloatingFailure SumOutOfRange- | otherwise = compactPartials partials- check _ (Partial value rest) = check value rest---- | Add two checked non-negative scalars with one TwoSum. Allocate partials only--- for a nonzero residual; a positive low term above M still fails before rounding.-addSingles :: Double -> Double -> FloatingState-addSingles 0 right = FloatingSingle right-addSingles left 0 = FloatingSingle left-addSingles left right- | isInfinite high = FloatingFailure SumOutOfRange- | high == maximumFinite && low > 0 = FloatingFailure SumOutOfRange- | low == 0 = FloatingSingle high- | otherwise = FloatingSum (Partial low (Partial high NoPartials))- where- (!high, !low) = twoSum left right---- | Validate an input before inserting it into a valid floating state.-addFloating :: Double -> FloatingState -> FloatingState-addFloating _ failure@(FloatingFailure _) = failure-addFloating value (FloatingSingle total)- | isNaN value || isInfinite value = FloatingFailure NonFiniteInput- | value < 0 = FloatingFailure NegativeInput- | otherwise = addSingles total value-addFloating value (FloatingSum partials)- | isNaN value || isInfinite value = FloatingFailure NonFiniteInput- | value < 0 = FloatingFailure NegativeInput- | otherwise = checkPartials (insertPartial value partials)---- | Merge ascending partials, then check the whole exact aggregation unit.-mergeFloating :: FloatingState -> FloatingState -> FloatingState-mergeFloating failure@(FloatingFailure _) _ = failure-mergeFloating _ failure@(FloatingFailure _) = failure-mergeFloating (FloatingSingle left) (FloatingSingle right) = addSingles left right-mergeFloating (FloatingSingle left) right =- mergeFloating (FloatingSum (insertPartial left NoPartials)) right-mergeFloating left (FloatingSingle right) =- mergeFloating left (FloatingSum (insertPartial right NoPartials))-mergeFloating (FloatingSum left) (FloatingSum right) = checkPartials (go left right)- where- go !total NoPartials = total- go !total (Partial value rest) = go (insertPartial value total) rest---- | Merge both expansions in descending magnitude, negating only the second--- state's components. Cancel the large opposing components before inserting--- either state's small signed tails. Inserting -M into an existing expansion--- with a negative low component could otherwise overflow before cancellation.-netFloating :: FloatingState -> FloatingState- -> Either ExactSumError (Ordering, FloatingState)-netFloating (FloatingFailure failure) _ = Left failure-netFloating _ (FloatingFailure failure) = Left failure-netFloating (FloatingSingle 0) (FloatingSingle 0) = Right (EQ, FloatingSingle 0)-netFloating (FloatingSingle 0) right = Right (LT, right)-netFloating left (FloatingSingle 0) = Right (GT, left)-netFloating (FloatingSingle left) (FloatingSingle right) =- Right (compare left right, difference)- where- (!high, !low) = twoSum (max left right) (negate (min left right))- difference- | low == 0 = FloatingSingle high- | otherwise = FloatingSum (Partial low (Partial high NoPartials))-netFloating (FloatingSingle left) right =- netFloating (FloatingSum (insertPartial left NoPartials)) right-netFloating left (FloatingSingle right) =- netFloating left (FloatingSum (insertPartial right NoPartials))-netFloating (FloatingSum NoPartials) (FloatingSum NoPartials) =- Right (EQ, FloatingSingle 0)-netFloating (FloatingSum NoPartials) right = Right (LT, right)-netFloating left (FloatingSum NoPartials) = Right (GT, left)-netFloating (FloatingSum left) (FloatingSum right) =- Right (direction, compactPartials magnitude)- where- difference = subtractPartials NoPartials (reversePartials left) (reversePartials right)- direction = signPartials difference- magnitude- | direction == LT = negatePartials difference- | otherwise = difference- subtractPartials !total NoPartials NoPartials = total- subtractPartials !total (Partial value rest) NoPartials =- subtractPartials (insertPartial value total) rest NoPartials- subtractPartials !total NoPartials (Partial value rest) =- subtractPartials (insertPartial (negate value) total) NoPartials rest- subtractPartials !total first@(Partial firstValue firstRest)- second@(Partial secondValue secondRest)- | abs firstValue >= abs secondValue =- subtractPartials (insertPartial firstValue total) firstRest second- | otherwise =- subtractPartials (insertPartial (negate secondValue) total) first secondRest- negatePartials NoPartials = NoPartials- negatePartials (Partial value rest) = Partial (negate value) (negatePartials rest)---- | Collapse from the largest component, correcting ties with the next residual.--- This is the nearest-even finalization used with Shewchuk expansions.-roundPartials :: Partials -> Double-roundPartials NoPartials = 0-roundPartials (Partial value NoPartials) = value-roundPartials partials = case reversePartials partials of- NoPartials -> 0- Partial value rest -> collapse value rest- where- collapse !high NoPartials = high- collapse !high (Partial value rest)- | low == 0 = collapse rounded rest- | otherwise = correct rounded low rest- where- (!rounded, !low) = twoSum high value- correct high low (Partial next _)- | (low < 0 && next < 0) || (low > 0 && next > 0)- , let doubled = low * 2- , let adjusted = high + doubled- , adjusted - high == doubled = adjusted- correct high _ _ = high---- | Extract one checked scalar; zero has its canonical positive sign.-roundFloating :: FloatingState -> Either ExactSumError Double-roundFloating (FloatingFailure failure) = Left failure-roundFloating (FloatingSingle value)- | value == 0 = Right 0- | otherwise = Right value-roundFloating (FloatingSum partials)- | rounded == 0 = Right 0- | otherwise = Right rounded- where- rounded = roundPartials partials---- | Validate two exact states and round their absolute difference once.--- The direction compares the first state with the second without tolerance.-netAccum :: ExactSum n => Accum n -> Accum n -> Either ExactSumError (Ordering, n)-netAccum left right = do- (direction, difference) <- netAccumState left right- magnitude <- roundAccum difference- pure (direction, magnitude)---- | Sum finite non-negative inputs exactly and round once. The exact total--- must fit the value type; unlike a sequential floating sum, order has no effect.-sumExact :: (ExactSum n, Foldable f) => f n -> Either ExactSumError n-sumExact = roundAccum . Foldable.foldl' (flip addAccum) emptyAccum---- * Algebra readouts---- | Two non-negative states, ordered as Not then Hat, or debit then credit.-data Sides n = Sides !(Accum n) !(Accum n)---- | Two empty side totals.-emptySides :: ExactSum n => Sides n-emptySides = Sides emptyAccum emptyAccum---- | Add to the first side when the predicate holds, otherwise the second.-addSide :: ExactSum n => Bool -> n -> Sides n -> Sides n-addSide True value (Sides first second) = Sides (addAccum value first) second-addSide False value (Sides first second) = Sides first (addAccum value second)---- | Add the entries observed by foldEntries, preserving its exact-zero filter.-addPosting :: ExactSum n => Accum n -> n -> Accum n-{-# INLINE addPosting #-}-addPosting total value- | isZeroValue value = total- | otherwise = addAccum value total---- | Visit the stored complete-base pairs without reconstructing posting bases.-foldPairs :: (HatVal n, HatBaseClass b)- => (a -> BasePart b -> Internal.Pair n -> a) -> a -> Alg n b -> a-{-# INLINE foldPairs #-}-foldPairs _ initial Internal.Zero = initial-foldPairs collect initial (value Internal.:@ postingBase)- | isZeroValue value = initial- | otherwise = collect initial (base postingBase) pair- where- pair- | isHat postingBase = Internal.Pair (Seq.singleton value) Seq.empty- | otherwise = Internal.Pair Seq.empty (Seq.singleton value)-foldPairs collect initial (Internal.Liner pairs _ _ _ _ _) =- HashMap.foldlWithKey' collect initial pairs---- | Accumulate each stored side before exact cancellation, without regrouping.-netPairState :: ExactSum n => Internal.Pair n -> Either ExactSumError (Ordering, Accum n)-{-# INLINE netPairState #-}-netPairState (Internal.Pair hats nots) = netAccumState- (Foldable.foldl' addPosting emptyAccum nots)- (Foldable.foldl' addPosting emptyAccum hats)---- | Exact absolute residual states, retaining the winning complete base.-baseResiduals :: (ExactSum n, HatBaseClass b)- => Alg n b -> Either ExactSumError [(b, Accum n)]-{-# INLINE baseResiduals #-}-baseResiduals = foldPairs collect (Right [])- where- collect result basePart pair = do- residuals <- result- (direction, magnitude) <- netPairState pair- case direction of- EQ -> pure residuals- GT -> pure ((merge Not basePart, magnitude) : residuals)- LT -> pure ((merge Hat basePart, magnitude) : residuals)---- | Read the gross norm of all finite non-negative postings with one rounding.--- Hat and Not both contribute; their combined exact total must fit the type.--- This avoids the order-dependent floating addition used by the existing norm.-normExact :: (ExactSum n, HatBaseClass b) => Alg n b -> Either ExactSumError n-{-# INLINABLE normExact #-}-normExact Internal.Zero = roundAccum emptyAccum-normExact (value Internal.:@ _) = roundAccum (addPosting emptyAccum value)-normExact (Internal.Liner pairs _ _ _ _ _) =- roundAccum (HashMap.foldl' collect emptyAccum pairs)- where- collect total (Internal.Pair hats nots) =- Foldable.foldl' addPosting (Foldable.foldl' addPosting total hats) nots---- | Cancel only exactly equal complete bases, returning non-negative postings.--- Each finite non-negative side total must fit the type. Each surviving base--- difference is rounded once, with no cancellation tolerance as in the old bar.--- Scalar bits follow the module's multiset guarantee; posting order does not.-barExact :: (ExactSum n, HatBaseClass b)- => Alg n b -> Either ExactSumError (Alg n b)-{-# INLINABLE barExact #-}-barExact Internal.Zero = Right Internal.Zero-barExact (value Internal.:@ _) | isZeroValue value = Right Internal.Zero-barExact (value Internal.:@ postingBase) =- (.@ merge side (base postingBase)) <$> roundAccum (addPosting emptyAccum value)- where- side- | isHat postingBase = Hat- | otherwise = Not-barExact (Internal.Liner pairs _ _ _ _ _) = do- rounded <- traverse roundPair pairs- let remaining = HashMap.mapMaybe id rounded- pure $ case HashMap.null remaining of- True -> Internal.Zero- False -> Internal.linerFromMap remaining- where- roundPair pair = do- (direction, state) <- netPairState pair- case direction of- EQ -> pure Nothing- GT -> do- value <- roundAccum state- pure (makePair Not value)- LT -> do- value <- roundAccum state- pure (makePair Hat value)- makePair _ value | isZeroValue value = Nothing- makePair Hat value = Just (Internal.Pair (Seq.singleton value) Seq.empty)- makePair _ value = Just (Internal.Pair Seq.empty (Seq.singleton value))---- | Project with set semantics, cancel per complete base, then round the sum--- of residual states once. Selected inputs must be finite and non-negative;--- each side total and the residual total must fit the type.------ Unlike the existing projection readout, no sequential rounding or tolerance--- is used. It equals @normExact =<< barExact (Algebra.proj bases algebra)@ only--- as a mathematical operation interpreting every operation at infinite precision.--- With T = 2^53, residuals T+1 and 1 give T+2 here but T after rounding each base--- first. Duplicate queries never duplicate postings.-projNetNormExact :: (ExactSum n, HatBaseClass b)- => [b] -> Alg n b -> Either ExactSumError n-projNetNormExact bases algebra = do- residuals <- baseResiduals (Algebra.proj bases algebra)- roundAccum (Foldable.foldl' (\total (_, state) -> mergeAccum total state)- emptyAccum residuals)---- | Sum selected finite non-negative postings by key and net each key once.--- Each key's side totals must fit the type. GT means Not is larger, LT means--- Hat is larger, and EQ retains a zero key. Unlike the signed, sequential old--- readout, the magnitude is non-negative and no tolerance is applied.--- Postings whose key is Nothing are not validated or aggregated.-balanceMapByExact :: (ExactSum n, HatBaseClass b, Ord k)- => (BasePart b -> Maybe k) -> Alg n b- -> Either ExactSumError (Map.Map k (Ordering, n))-{-# INLINABLE balanceMapByExact #-}-balanceMapByExact keyOf algebra = traverse finish grouped- where- grouped = case algebra of- singleton@(_ Internal.:@ _) -> foldEntries collectPosting Map.empty singleton- _ -> foldPairs collect Map.empty algebra- collectPosting previous value postingBase = case keyOf (base postingBase) of- Nothing -> previous- Just key -> Map.alter (Just . addSide (not (isHat postingBase)) value- . maybe emptySides id) key previous- collect totals basePart (Internal.Pair hats nots)- | Foldable.all isZeroValue hats && Foldable.all isZeroValue nots = totals- | otherwise = case keyOf basePart of- Nothing -> totals- Just key -> Map.alter (Just . addPair . maybe emptySides id) key totals- where- addPair (Sides first second) = Sides- (Foldable.foldl' addPosting first nots)- (Foldable.foldl' addPosting second hats)- finish (Sides first second) = netAccum first second---- | Cancel per complete base before merging residual states by key and side.--- All inputs must be finite and non-negative; base-side and key-side totals--- must fit the type. Each output side is rounded once. There is no tolerance.--- Distinct bases with Not 10 and Hat 7 give (10,7), as in the existing readout;--- the pair is ordered Not then Hat and does not net across bases.-netPairMapByExact :: (ExactSum n, HatBaseClass b, Ord k)- => (BasePart b -> Maybe k) -> Alg n b- -> Either ExactSumError (Map.Map k (n, n))-{-# INLINABLE netPairMapByExact #-}-netPairMapByExact keyOf algebra = do- residuals <- baseResiduals algebra- traverse finish (Foldable.foldl' collect Map.empty residuals)- where- collect totals (postingBase, state) = case keyOf (base postingBase) of- Nothing -> totals- Just key -> Map.alter (Just . combine . maybe emptySides id) key totals- where- combine (Sides first second)- | isHat postingBase = Sides first (mergeAccum second state)- | otherwise = Sides (mergeAccum first state) second- finish (Sides first second) = (,) <$> roundAccum first <*> roundAccum second---- | Net each complete base, restore its winning side, select its key, merge--- residual states by key, round once, then call the posting function.--- All input base-side and selected key totals must fit the type; inputs must be--- finite and non-negative. Unlike the old function, no tolerance or rounded--- intermediate algebra is used. Validation and the one-rounding guarantee end--- at the amount passed to the callback; values made by the callback are not covered.-postFromNetByExact :: (ExactSum n, HatBaseClass b, Ord k)- => (b -> Maybe k) -> (k -> n -> Alg n b) -> Alg n b- -> Either ExactSumError (Alg n b)-postFromNetByExact keyOf post algebra = do- residuals <- baseResiduals algebra- amounts <- traverse roundAccum (Foldable.foldl' collect Map.empty residuals)- pure (Map.foldlWithKey' (\result key value -> result .+ post key value) mempty amounts)- where- collect totals (postingBase, state) = case keyOf postingBase of- Nothing -> totals- Just key -> Map.insertWith mergeAccum key state totals---- * Accounting readouts---- | Collect accounting sides; a structural Side contributes to neither total.-accountSide :: ExactSum n => Side -> n -> Sides n -> Sides n-accountSide Debit = addSide True-accountSide Credit = addSide False-accountSide Side = const id---- | Recover a debit or credit direction from an exact comparison.-accountDirection :: Ordering -> Side-accountDirection GT = Debit-accountDirection LT = Credit-accountDirection EQ = Side---- | Compare debit and credit exact totals, rounding only their absolute difference.--- Finite non-negative inputs and both side totals must fit the type. Only exact--- equality returns (Side,0); the existing tolerance-based diffRL is lossy.--- Structural Side postings contribute no value and are not validated.-diffRLExact :: (ExactSum n, ExBaseClass b)- => Alg n b -> Either ExactSumError (Side, n)-diffRLExact algebra = do- let Sides debit credit = foldEntries collect emptySides algebra- (direction, amount) <- netAccum debit credit- pure (accountDirection direction, amount)- where- collect totals value postingBase = accountSide (whichSide postingBase) value totals---- | Test exact debit-credit equality without the old balance tolerance.--- The finite non-negative input and side-total range checks of 'diffRLExact'--- apply. Its amount is rounded once, while this predicate observes exact direction.-balanceExact :: (ExactSum n, ExBaseClass b) => Alg n b -> Either ExactSumError Bool-balanceExact = fmap ((== Side) . fst) . diffRLExact---- | Aggregate by account title and net debit against credit without tolerance.--- Finite non-negative inputs and each account's side totals must fit the type.--- Each account magnitude is rounded once, instead of sequentially summing as--- in the old accountBalances. Balanced accounts remain present as NoBalance.--- Structural Side postings contribute no value and are not validated.-accountBalancesExact :: (ExactSum n, ExBaseClass b)- => Alg n b -> Either ExactSumError (Map.Map AccountTitles (AccountBalance n))-accountBalancesExact = traverse finish . foldEntries collect Map.empty- where- collect totals value postingBase =- Map.alter (Just . accountSide (whichSide postingBase) value- . maybe emptySides id) (getAccountTitle postingBase) totals- finish (Sides debit credit) = do- (direction, amount) <- netAccum debit credit- pure $ case direction of- EQ -> NoBalance- GT -> DebitBalance amount- LT -> CreditBalance amount
src/ExchangeAlgebra/Algebra/Internal.hs view
@@ -43,2153 +43,58 @@ module ExchangeAlgebra.Algebra.Internal- ( module ExchangeAlgebra.Algebra.Base- , Nearly(..)- , isNearlyNum- , nearlyEqScaled- , Redundant(..)- , Exchange(..)- , HatVal(..)- , Pair(..)- , Alg(..)- , linerFromMap- , isZero- , (.@)- , (<@)- , vals- , bases- , fromList- , toList- , foldEntries- , sigma- , sigma2When- , sigmaFromMap- , toASCList- , map- , mapPosting- , mapMaybePosting- , mapBasePart- , extendBy- , filter- , proj- , projCredit- , projDebit- , projByAccountTitle- , projNetNorm- , projNorm- , balanceBy- , balanceMapBy- , netPairMapBy- , foldEntriesToMap- , decBy- , postFromNetBy- , projCurrentAssets- , projFixedAssets- , projDeferredAssets- , projCurrentLiability- , projFixedLiability- , projCapitalStock- , projContraAssets- , projContra- , rounding- , unionsMerge)where--import ExchangeAlgebra.Algebra.Base--import qualified Data.List as L (foldl', sort,sortOn,any)-import Prelude hiding (map, head, filter,tail, traverse, mapM)-import qualified Data.HashMap.Strict as Map-import qualified Data.IntMap.Strict as IntMap-import qualified Data.IntSet as IntSet-import qualified Data.Map.Strict as M-import qualified Data.Foldable as Foldable (foldl',foldr)-import qualified Data.Sequence as Seq-import Data.Sequence (Seq)-import qualified Data.Maybe as Maybe-import qualified Number.NonNegative as NN -- Non-negative real numbers-import qualified Data.Scientific as D (fromFloatDigits, formatScientific, FPFormat(..))-import Control.DeepSeq-import GHC.Stack (HasCallStack)-import qualified Data.Binary as Binary----------------------------------------------------------------------- * Approximate equality----------------------------------------------------------------------- | Type class providing approximate equality for numeric values.--- Performs equality comparison with tolerance for floating-point rounding errors.-class (Eq a, Ord a) => Nearly a where- -- | @isNearly x y t@ : Returns True if the difference between x and y is within the tolerance t.- -- Complexity: O(1)- isNearly :: a -> a -> a -> Bool--instance Nearly Int where- {-# INLINE isNearly #-}- isNearly = isNearlyNum--instance Nearly Integer where- {-# INLINE isNearly #-}- isNearly = isNearlyNum--instance Nearly Float where- {-# INLINE isNearly #-}- isNearly = isNearlyNum--instance Nearly Double where- {-# INLINE isNearly #-}- isNearly = isNearlyNum--instance Nearly NN.Double where- {-# INLINE isNearly #-}- isNearly = isNearlyNum--{-# INLINE isNearlyNum #-}--- | Complexity: O(1)--- Assumes primitive numeric operations and comparisons are constant time.------ NOTE: this is an /absolute/-tolerance test (@|x - y| <= |t|@); it does not--- scale with magnitude. For large values, rounding error easily exceeds a small--- fixed @t@, while for small values it can swallow a real residual. Internal--- accounting reconciliation uses 'nearlyEqScaled' instead. The final guard--- returns 'False' (was: 'error') when a NaN makes every ordered comparison fail,--- so a non-finite input can no longer crash the check.-isNearlyNum :: (Show a, Num a, Ord a) => a -> a -> a -> Bool-isNearlyNum x y t- | x == y = True- | x > y = abs (x - y) <= abs t- | x < y = abs (y - x) <= abs t- | otherwise = False -- NaN: not nearly-equal to anything--{-# INLINE nearlyEqScaled #-}--- | Scale-aware approximate equality for accounting reconciliation:------ @|x - y| <= atol + rtol * max |x| |y|@, with @atol = 1e-13@, @rtol = 1e-12@.------ The absolute floor @atol@ handles values near zero; the relative term @rtol@--- lets the threshold track magnitude, so the test stays meaningful for large--- balances (where a fixed @1e-13@ was far too strict and retained pure rounding--- noise as a spurious residual). Returns 'False' if either argument is a--- non-finite error value (NaN/Inf), so error values never read as nearly equal.------ Complexity: O(1)-nearlyEqScaled :: (HatVal n) => n -> n -> Bool-nearlyEqScaled x y- | isErrorValue x || isErrorValue y = False- | otherwise = abs (x - y) <= atol + rtol * max (abs x) (abs y)- where- atol = 1e-13- rtol = 1e-12----------------------------------------------------------------- * Algebra----------------------------------------------------------------------------------------------------------------------------------- ** Definition of Redundancy (subclassing this makes a redundant algebra)----------------------------------------------------------------------- | Type class for Redundant Algebra.--- Provides fundamental exchange algebra operations: hat, bar, norm, scalar product, and compress.------ Redundant ⊃ Exchange------ hat calculation--- >>> (.^) (10:@Not:<Cash .+ 10:@Hat:<Deposits)--- 10.00:@Hat:<Cash .+ 10.00:@Not:<Deposits------ bar calculation--- >>> x = 10:@Not:<Cash .+ 10:@Hat:<Deposits--- >>> y = 5:@Hat:<Cash .+ 5:@Not:<Deposits--- >>> (.-) $ x .+ y--- 5.00:@Not:<Cash .+ 5.00:@Hat:<Deposits------ norm calculation--- >>> norm $ 10:@Not:<Cash .+ 10:@Hat:<Deposits--- 20.0------ (.*) calculation--- >>> (.*) 5 $ 10:@Not:<Cash .+ 10:@Hat:<Deposits--- 50.00:@Not:<Cash .+ 50.00:@Hat:<Deposits------ compress calculation--- >>> compress $ 10:@Not:<Cash .+ 5:@Hat:<Cash .+ 3:@Not:<Cash--- 5.00:@Hat:<Cash .+ 13.00:@Not:<Cash------ == Redundant-algebra axioms (Akagi 2026, Appendix A, Definition 6)------ The operations above satisfy the following /axioms/ (the paper states five;--- they are verified as QuickCheck properties in @test\/Spec.hs@, see--- @axiomProperties@):------ 1. Hat involution: @(.^) ((.^) x) = x@--- 2. Scalar on an element: @a '.*' (v ':@' b) = (a*v) ':@' b@--- 3. Scalar distribution: @a '.*' (x '.+' y) = (a '.*' x) '.+' (a '.*' y)@--- 4. Norm homogeneity: @'norm' (a '.*' x) = a * 'norm' x@ (for @a >= 0@)--- 5. Norm additivity: @'norm' (x '.+' y) = 'norm' x + 'norm' y@------ Derived lemmas (also property-tested): @'bar'@ idempotence--- (@'bar' ('bar' x) = 'bar' x@), 'Zero' identity, and associativity of @('.+')@.--- Note @('.+')@ accumulates same-base postings as an ordered sequence (the--- /redundancy/), so 'Show' \/ 'Eq' observe that order; for the exact value type--- t'ExchangeAlgebra.Value.MoneyDecimal', 'norm' \/ 'bar' are order-independent.--- The order itself is furthermore /construction-path dependent/: the--- pairwise-union path ('fromList'\/'mconcat') and the bulk-merge path--- ('sigma'\/'unionsMerge') arrange the same multiset of postings differently.--- Do not rely on @('==')@ to compare algebras built by different routes —--- compare after 'compress'\/'bar', or use an exact value type and compare--- the netted content.--class (HatVal n, HatBaseClass b, Monoid (a n b)) => Redundant a n b where- -- | Hat operation. Flips Hat/Not on all elements.- -- Complexity: O(1) for singleton, O(n) for Liner (n is the number of base keys)- (.^) :: a n b -> a n b-- -- | Bar operation. Cancels Hat/Not on the same base and retains only the difference.- -- Floating-point side totals use sequential addition and depend on posting order;- -- near-equal sides cancel under 'nearlyEqScaled'. For exact totals, see- -- "ExchangeAlgebra.Algebra.Exact" or "ExchangeAlgebra.Journal.Exact".- -- Complexity: O(n) (n is the number of base keys)- (.-) :: a n b -> a n b-- -- | Alias for bar operation. Identical to @(.-)@.- -- On an axis-preserving ledger, @norm . bar@ cancels only within each full- -- base. Use 'balanceMapBy' or 'netPairMapBy' for net amounts by group.- -- Floating-point side totals depend on posting order, and near-equal sides- -- cancel under 'nearlyEqScaled'. See "ExchangeAlgebra.Algebra.Exact" for- -- exact netting of the original postings, or "ExchangeAlgebra.Journal.Exact"- -- for journal readouts.- bar :: a n b -> a n b- bar = (.-)-- -- | Aggregates values on the same base. Sums while preserving the Hat/Not distinction.- -- Complexity: O(n) (n is the number of base keys)- compress :: a n b -> a n b-- -- | Addition of algebra elements. Alias for the Monoid @<>@ operation.- -- Complexity: O(union cost)- (.+) :: a n b -> a n b -> a n b-- -- | Scalar product. Multiplies all element values by a scalar.- -- Complexity: O(1) for singleton, O(n) for Liner- (.*) :: n -> a n b -> a n b-- -- | Norm. Sum of all element values (both Hat and Not sides), i.e. the- -- homomorphism from the algebra into the value domain @n@ (Akagi 2026,- -- Appendix A, Definition 6). It is /additive/: @norm (x '.+' y) = norm x +- -- norm y@ (axiom 5), and /homogeneous/: @norm (a '.*' x) = a * norm x@ for- -- @a >= 0@ (axiom 4). Because it sums both sides it does not cancel Hat- -- against Not; @norm ('bar' x) <= norm x@ (bar discards the canceled part).- -- On an axis-preserving ledger, @norm ('bar' x)@ does not cancel across- -- distinct axis values. Use 'balanceMapBy' or 'netPairMapBy' to read each- -- group's net amount.- --- -- >>> norm (100:@Not:<Cash .+ 50:@Not:<Sales :: Alg Double (HatBase AccountTitles))- -- 150.0- --- -- Complexity: O(n) (n is the number of base keys)- norm :: a n b -> n-- -- | Addition in an Applicative context.- -- Complexity: O(union cost)- {-# INLINE (<+) #-}- (<+) :: (Applicative f) => f (a n b) -> f (a n b) -> f (a n b)- (<+) x y = (.+) <$> x <*> y---infixr 7 .^-infixr 2 .--infixr 3 .+-infixr 3 <+----------------------------------------------------------------- ** Definition of Exchange Algebra----------------------------------------------------------------- | Type class for Exchange Algebra. In addition to Redundant Algebra, provides--- the decomposition operators of Deguchi & Nakano (1986, Definition 2.16) and--- balance checking. Following the original convention, __L = Left = Debit--- (借方)__ and __R = Right = Credit (貸方)__: 'decL' extracts the debit side,--- 'decR' the credit side. ('decP' \/ 'decM' split along the Hat\/Not label--- instead of the debit\/credit side.)-class (Redundant a n b ) => Exchange a n b where- -- | Extracts only the credit-side elements (R = Right = Credit, 貸方),- -- i.e. those whose 'whichSide' is 'Credit'. Complexity: O(s)- decR :: a n b -> a n b- -- | Extracts only the debit-side elements (L = Left = Debit, 借方),- -- i.e. those whose 'whichSide' is 'Debit'. Complexity: O(s)- decL :: a n b -> a n b- -- | Extracts only the Hat-side elements (the P-projection of the- -- decomposition; @isHat@ holds). Complexity: O(s)- decP :: a n b -> a n b- -- | Extracts only the Not-side elements (the M-projection of the- -- decomposition; @isHat@ does not hold). Complexity: O(s)- decM :: a n b -> a n b- -- | Checks whether the norms of debit and credit sides are equal. The norms- -- sum floating-point postings sequentially, so their totals depend on order;- -- 'nearlyEqScaled' treats near-equal totals as balanced. For an exact check- -- over the original postings, see "ExchangeAlgebra.Algebra.Exact" or- -- "ExchangeAlgebra.Journal.Exact".- -- Complexity: O(s)- balance :: a n b -> Bool- -- | Returns the debit-credit difference as a (Side, difference) pair. The- -- side norms sum floating-point postings sequentially and depend on order;- -- 'nearlyEqScaled' reports a zero difference for near-equal totals. For- -- exact netting of the original postings, see "ExchangeAlgebra.Algebra.Exact"- -- or "ExchangeAlgebra.Journal.Exact".- -- Complexity: O(s)- diffRL :: a n b -> (Side, n)------------------------------------------------------------------------ * Algebra----------------------------------------------------------------------- | Type class for algebra element values.--- Provides zero-value / error-value predicates and a representation-specific--- renderer ('showValue').------ == Choosing an instance------ * 'Prelude.Double' — fast IEEE-754 (this module); the low-friction default.--- * @MoneyDouble@ ("ExchangeAlgebra.Value") — same speed, dedicated money newtype.--- * @MoneyDecimal@ ("ExchangeAlgebra.Value") — exact decimal, construction-order--- independent totals; use for audited\/deterministic ledgers.--- * @NN.Double@ (@Number.NonNegative.Double@) — __deprecated__ since 0.5.0.0,--- to be removed in 0.6: its @(-)@ /errors/ on a negative intermediate--- (e.g. inside @bar@\/@(.-)@ comparisons), and everything it offered is--- covered by @MoneyDouble@. Migrate to @MoneyDouble@ or bare 'Prelude.Double'.------ DESIGN NOTE (2026-06-06, selectable value type — Double vs exact Decimal):--- The @RealFloat@ superclass was intentionally *removed* so that exact,--- non-floating-point value types (the planned @MoneyDecimal@ = non-negative--- 'Data.Decimal.Decimal') can be 'HatVal' instances and give construction-order--- -independent, exact summation. @RealFloat@ was only ever needed in two places:--- * @showV@ (rendering via 'Data.Scientific.fromFloatDigits') — now replaced by--- the per-instance 'showValue' method, so each representation formats itself;--- * the @Double@/@NN.Double@ 'isErrorValue' (NaN/Infinity tests) — these stay--- inside the floating-point instances, which may require @RealFloat@ locally.--- @Fractional@ is *kept*: 'Data.Decimal' provides it (so numeric literals like--- @0.08@ still work without wrapping), and only an @Integer@ instance would need--- it dropped. @Integer@ is intentionally out of scope — it cannot represent the--- fractional / relative prices that the ABM simulations depend on.-class ( Show n- , Ord n- , Eq n- , Nearly n- , Fractional n- , Num n) => HatVal n where-- -- | Zero value. Complexity: O(1)- zeroValue :: n-- -- | Tests whether the value is zero. Complexity: O(1)- isZeroValue :: n -> Bool- isZeroValue x- | zeroValue == x = True- | otherwise = False-- -- | Tests whether the value is an error value (NaN, Infinity, negative, …).- -- Complexity: O(1)- isErrorValue :: n -> Bool-- -- | Render the value for the 'Show' instance of 'Alg'.- -- Per-instance because formatting is representation-specific: floating-point- -- types format to a fixed number of decimal places via 'Data.Scientific',- -- whereas exact decimal types print their own canonical form. This replaces- -- the former floating-point-only @showV@, which hard-wired @RealFloat@- -- through @fromFloatDigits@ and so blocked exact value types.- showValue :: n -> String---instance RealFloat NN.Double where- floatRadix = floatRadix . NN.toNumber- floatDigits = floatDigits . NN.toNumber- floatRange = floatRange . NN.toNumber- decodeFloat = decodeFloat . NN.toNumber- encodeFloat m e = NN.fromNumber (encodeFloat m e)- exponent = exponent . NN.toNumber- significand = NN.fromNumber . significand . NN.toNumber- scaleFloat n = NN.fromNumber . scaleFloat n . NN.toNumber- isNaN = isNaN . NN.toNumber- isInfinite = isInfinite . NN.toNumber- isDenormalized = isDenormalized . NN.toNumber- isNegativeZero = isNegativeZero . NN.toNumber- isIEEE = isIEEE . NN.toNumber---- | __Deprecated__ since 0.5.0.0 (removal planned for 0.6): @NN.Double@'s--- @(-)@ errors on a negative intermediate, and @MoneyDouble@ covers the same--- use case safely. Migrate to @MoneyDouble@ or bare 'Prelude.Double'.--- (GHC cannot attach a @DEPRECATED@ pragma to an instance, so this notice--- lives in the Haddock and the ChangeLog.)-instance HatVal NN.Double where- {-# INLINE zeroValue #-}- zeroValue = 0- {-# INLINE isErrorValue #-}- isErrorValue x = isNaN (NN.toNumber x)- || isInfinite (NN.toNumber x)- -- Identical formatting to the old top-level @showV@ (fixed 2-decimal- -- Scientific rendering); moved here so the class no longer needs @RealFloat@.- {-# INLINE showValue #-}- showValue = D.formatScientific D.Generic (Just 2) . D.fromFloatDigits--instance HatVal Prelude.Double where- {-# INLINE zeroValue #-}- zeroValue = 0-- {-# INLINE isErrorValue #-}- isErrorValue x = isNaN x- || isInfinite x- || x < 0- -- Identical formatting to the old top-level @showV@ (see NN.Double above).- {-# INLINE showValue #-}- showValue = D.formatScientific D.Generic (Just 2) . D.fromFloatDigits--data Pair v where- Pair :: {_hatSide :: !(Seq v)- ,_notSide :: !(Seq v)} -> Pair v- deriving (Eq)--instance (Binary.Binary v) => Binary.Binary (Pair v) where- {-# INLINABLE put #-}- {-# INLINABLE get #-}- put (Pair hs ns) = do- Binary.put (Seq.length hs :: Int)- Foldable.foldr (\x k -> Binary.put x >> k) (pure ()) hs- Binary.put (Seq.length ns :: Int)- Foldable.foldr (\x k -> Binary.put x >> k) (pure ()) ns- get = do- hsLen <- Binary.get :: Binary.Get Int- hs <- go hsLen Seq.empty- nsLen <- Binary.get :: Binary.Get Int- ns <- go nsLen Seq.empty- pure (Pair hs ns)- where- go :: Binary.Binary a => Int -> Seq a -> Binary.Get (Seq a)- go n !acc- | n <= 0 = pure acc- | otherwise = do- x <- Binary.get- go (n - 1) (acc Seq.|> x)---instance (HatVal v) => Ord (Pair v) where- {-# INLINE compare #-}- compare (Pair hs1 ns1) (Pair hs2 ns2) = compare ((sum hs1) - (sum ns1)) ((sum hs2) - (sum ns2))-- (<) x y | compare x y == LT = True- | otherwise = False-- (>) x y | compare x y == GT = True- | otherwise = False-- (<=) x y | compare x y == LT = True- | compare x y == EQ = True- | otherwise = False-- (>=) x y | compare x y == GT = True- | compare x y == EQ = True- | otherwise = False-- max x y | x >= y = x- | otherwise = y-- min x y | x <= y = x- | otherwise = y--{-# INLINE nullPair #-}--- | Complexity: O(1)-nullPair :: Pair v-nullPair = Pair Seq.empty Seq.empty--{-# INLINE pairAppend #-}--- | Complexity: O(log(min(h1,h2)) + log(min(n1,n2)))--- where h1/h2 and n1/n2 are the lengths of the appended 'Seq's on each side.-pairAppend :: Pair v -> Pair v -> Pair v-pairAppend (Pair x1 y1) (Pair x2 y2) =- let !hs = x1 Seq.>< x2- !ns = y1 Seq.>< y2- in Pair hs ns--{-# INLINE pairUnion #-}--- | Set-style merge of two single-base projection results.------ Used by the multi-pattern 'proj'/'projNetNorm' paths where a query list is--- treated as a /set/: when several queries select the same posting (duplicate--- bases, or an exact base overlapping a wildcard query), the selected sides--- come from the /same/ underlying t'Pair' in @_realg@, so the per-side--- sequences are identical. Taking each side from whichever operand supplies it--- (and keeping a single copy) therefore unions the selections without double--- counting. Contrast @pairAppend@, which concatenates and would duplicate.------ Each operand is a side-restricted projection ('choosePairByHat'), so at most--- one of the two contributes a non-empty hat side and at most one a non-empty--- not side for a given base; @pairUnion@ keeps the non-empty one per side.------ Complexity: O(1).-pairUnion :: Pair v -> Pair v -> Pair v-pairUnion (Pair x1 y1) (Pair x2 y2) =- let !hs = if Seq.null x1 then x2 else x1- !ns = if Seq.null y1 then y2 else y1- in Pair hs ns---- | Algebra element. An element of exchange algebra consisting of a value-base pair.--- Zero is the zero element, @(:@)@ is a singleton, and Liner is a HashMap-based multi-element representation.------ __Invariants (do not hand-construct @Liner@).__ The @Liner@ constructor carries--- internal cache\/index fields (@_axisPosting@, @_idToBp@, @_allBpIds@) that are--- /derived from/ @_realg@ and must stay consistent with it: @_axisPosting@,--- @_idToBp@ and @_allBpIds@ must be exactly the axis index, id↔base map and id set--- built from the keys of @_realg@ (as the internal @linerFromMap@ does). The--- wildcard projection path (@projWildMap@, @filterByAxis@) reads those indices and--- will return wrong answers (silently, not an exception) if they disagree with--- @_realg@. The fields @_bpToId@ and @_nextBpId@ are currently unmaintained--- /poison/ (reserved for a dormant scheme) — forcing them throws. Always build--- values via 'fromList' or the smart constructor '(.@)' rather than applying--- @Liner@ (or @(:@)@) directly.-data Alg v b where- Zero :: Alg v b- (:@) :: {_val :: !v, _hatBase :: !b} -> Alg v b- Liner :: { _realg :: !(Map.HashMap (BasePart b) (Pair v))- , _axisPosting :: ~(IntMap.IntMap (Map.HashMap AxisKey IntSet.IntSet))- , _bpToId :: ~(Map.HashMap (BasePart b) Int)- , _idToBp :: ~(IntMap.IntMap (BasePart b))- , _nextBpId :: ~Int- , _allBpIds :: ~IntSet.IntSet- } -> Alg v b--instance ( HatBaseClass b- , Binary.Binary v- , Binary.Binary b- , Binary.Binary (BasePart b)- ) => Binary.Binary (Alg v b) where- {-# INLINABLE put #-}- {-# INLINABLE get #-}- put Zero = Binary.put (0 :: Int)- put (v :@ b) = do- Binary.put (1 :: Int)- Binary.put v- Binary.put b- put (Liner m _ _ _ _ _) = do- Binary.put (2 :: Int)- Binary.put (Map.size m :: Int)- Map.foldrWithKey- (\bp p k -> Binary.put bp >> Binary.put p >> k)- (pure ())- m-- get = do- tag <- Binary.get- case (tag :: Int) of- 0 -> pure Zero- 1 -> (:@) <$> Binary.get <*> Binary.get- 2 -> do- n <- Binary.get :: Binary.Get Int- linerFromMap <$> go n Map.empty- _ -> fail ("Binary decode failure for Alg: unknown tag " ++ show tag)- where- go n !acc- | n <= 0 = pure acc- | otherwise = do- bp <- Binary.get- p <- Binary.get- go (n - 1) (Map.insert bp p acc)--type AxisPosting = IntMap.IntMap (Map.HashMap AxisKey IntSet.IntSet)--{-# INLINE emptyAxisPosting #-}--- | Complexity: O(1)-emptyAxisPosting :: AxisPosting-emptyAxisPosting = IntMap.empty--{-# INLINE insertAxisPosting #-}--- | Complexity: O(d * (hash-insert + intset-insert))--- In practice this is near O(d), where d is the number of axes in the base part.-insertAxisPosting :: [AxisKey] -> Int -> AxisPosting -> AxisPosting-insertAxisPosting !keys !bpId !idx =- snd $ L.foldl' step (0 :: Int, idx) keys- where- step (!axis, !acc) !k =- let !axisMap = IntMap.findWithDefault Map.empty axis acc- !ids0 = Map.lookupDefault IntSet.empty k axisMap- !ids1 = IntSet.insert bpId ids0- !axisMap' = Map.insert k ids1 axisMap- !acc' = IntMap.insert axis axisMap' acc- in (axis + 1, acc')--{-# INLINE queryAxisPosting #-}--- | Complexity: O(d + intersection cost)--- d is the number of axes; intersections are performed in ascending set-size order.-queryAxisPosting :: [AxisKey] -> AxisPosting -> IntSet.IntSet -> IntSet.IntSet-queryAxisPosting !keys !idx !allIds =- case matchedSets of- Left () -> IntSet.empty- Right [] -> allIds- -- 'L.sortOn' preserves length, so a non-empty 'xs' sorts to a non-empty- -- list; matching @(x:rest)@ here (rather than on a lazy let-binding) makes- -- exhaustiveness explicit and avoids the partial pattern warning.- Right xs@(_:_) ->- case L.sortOn IntSet.size xs of- (x:rest) -> L.foldl' IntSet.intersection x rest- [] -> allIds -- unreachable: xs is non-empty- where- matchedSets =- L.foldl' collect (Right []) (zip [0 :: Int ..] keys)-- collect (Left ()) _ = Left ()- collect (Right acc) (!axis, !k)- | axisHasWildcard k = Right acc- | otherwise =- case IntMap.lookup axis idx of- Nothing -> Left ()- Just axisMap -> case Map.lookup k axisMap of- Nothing -> Left ()- Just ids -> Right (ids : acc)-- -- A component with a wildcard inside it (e.g. @("a", wildcard)@) must- -- not be looked up as an exact key; the final 'matchesQuery' check selects.- axisHasWildcard (AxisKey coordinate) = haveWildcard coordinate- {-# INLINE axisHasWildcard #-}--{-# INLINE linerFromMap #-}--- | Complexity: O(n * d * (hash-insert + intset-insert))--- n is the number of distinct base keys in the map.-linerFromMap :: (HatBaseClass b)- => Map.HashMap (BasePart b) (Pair v)- -> Alg v b--- | @_bpToId@ and @_nextBpId@ are reserved for the (currently dormant) P1a--- incremental-id scheme and are not consumed by any read path. We therefore skip--- building them and leave them as lazy poison: nothing forces them in normal--- operation (the wildcard 'proj' path uses @idx@/@idToBp@/@allIds@ only). Forcing--- either field is a bug, surfaced loudly here instead of silently returning a--- stale value. Guarded by a poison-field regression test.------ NOTE: the @error@ thunks are written inline in the constructor application (the--- @Liner@ fields are lazy @~@) rather than as @where@-bindings, because this--- module is compiled @{-\# LANGUAGE Strict \#-}@, under which a @where@-bound thunk--- would be forced when 'linerFromMap' is evaluated.-linerFromMap m =- Liner m idx- (error "Liner internal: _bpToId is not maintained (reserved for P1a); do not force")- idToBp- (error "Liner internal: _nextBpId is not maintained (reserved for P1a); do not force")- allIds- where- ~(idx, idToBp, allIds) =- Map.foldlWithKey'- (\(!idxAcc, !idToBpAcc, !allIdsAcc) bp _ ->- let !bpId = IntMap.size idToBpAcc- !idx' = insertAxisPosting (toAxisKeys bp) bpId idxAcc- !idToBp' = IntMap.insert bpId bp idToBpAcc- !allIds' = IntSet.insert bpId allIdsAcc- in (idx', idToBp', allIds'))- (emptyAxisPosting, IntMap.empty, IntSet.empty)- m---- | Tests whether the algebra element is zero (empty).------ Complexity: O(1)-isZero :: Alg v b -> Bool-isZero Zero = True-isZero _ = False--{-# INLINE singleton #-}--- | Complexity: O(1)-singleton :: (HatVal v, HatBaseClass b) => v -> b -> Alg v b-singleton v b | isZeroValue v = Zero- | isErrorValue v = error $ "errorValue at (.@) val: "- ++ show v- ++ show ":@"- ++ show b- | otherwise = v :@ b--{-# INLINE (.@) #-}--- | Smart constructor that builds an algebra element from a value and a base.--- Returns Zero for zero values, and throws an exception for error values.------ Complexity: O(1)-(.@) :: (HatVal n, HatBaseClass b) => n -> b -> Alg n b-(.@) v b = singleton v b---- | Constructs an algebra element in an Applicative context. Lifted version of @(.@)@.------ Complexity: O(1) + Applicative effects-(<@) :: (HatVal n, Applicative f, HatBaseClass b)- => f n -> b -> f (Alg n b)-(<@) v b = (.@) <$> v <*> (pure b)---infixr 6 :@-infixr 6 .@-infixr 6 <@---- NOTE: the former top-level @showV@ (which hard-wired @RealFloat@ via--- @fromFloatDigits@) has been replaced by the per-instance 'showValue' method of--- 'HatVal', so that exact value types can render themselves. The 'Show' instance--- of 'Alg' below now calls 'showValue'. The @Double@/@NN.Double@ 'showValue'--- implementations reproduce the old formatting byte-for-byte.--instance (HatVal v, HatBaseClass b) => Eq (Alg v b) where- (==) Zero Zero = True- (==) Zero _ = False- (==) _ Zero = False- (==) (v1:@b1) (v2:@b2) = (v1 == v2) && (b1 == b2)- (==) (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = m1 == m2- (==) _ _ = False- (/=) x y = not (x == y)--instance (HatVal v, HatBaseClass b) => Ord (Alg v b) where- {-# INLINE compare #-}- compare Zero Zero = EQ- compare Zero _ = LT- compare _ Zero = GT-- compare (_:@_) (Liner _ _ _ _ _ _) = LT- compare (Liner _ _ _ _ _ _) (_:@_) = GT- compare (v1:@b1) (v2:@b2)- | b1 == b2 = compare v1 v2- | b1 > b2 = GT- | otherwise = LT -- b1 < b2 (Ord is total; otherwise keeps it exhaustive)-- compare (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = compare m1 m2-- (<) x y | compare x y == LT = True- | otherwise = False-- (>) x y | compare x y == GT = True- | otherwise = False-- (<=) x y | compare x y == LT = True- | compare x y == EQ = True- | otherwise = False-- (>=) x y | compare x y == GT = True- | compare x y == EQ = True- | otherwise = False-- max x y | x >= y = x- | otherwise = y-- min x y | x <= y = x- | otherwise = y--instance (HatVal v, HatBaseClass b) => Show (Alg v b) where- show Zero = "0"- show (v:@b) = (showValue v) ++ ":@" ++ show b- show xs = let ls = toASCList xs- in go ls- where- go [] = "0"- go [y] = show y- go (y:ys) = show y ++ " .+ " ++ go ys---instance NFData (Alg v b) where- rnf Zero = Zero `seq` ()- rnf (v:@b) = v `seq` b `seq` ()- rnf (Liner m _ _ _ _ _) = Map.foldrWithKey (\k v acc -> k `seq` v `seq` acc) () m---------------------------------------------------------------------- Semigroup---------------------------------------------------------------------instance (HatVal n, HatBaseClass b) => Semigroup (Alg n b) where- {-# INLINE (<>) #-}- -- | Associative law ;convert to right join- (<>) = union------ | union two trees------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Hat:<Yen .+ 2:@Not:<Amount :: Test--- >>> union x y--- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Not:<Amount .+ 2.00:@Not:<Amount-{-# INLINE union #-}--- | Complexity:--- - singleton/singleton and singleton/liner cases: O(n * d * index-build)--- - liner/liner case: O(n + m) for map union plus O((n+m) * d * index-build)--- where n and m are distinct key counts on each side.-union :: (HatVal n, HatBaseClass b) => Alg n b -> Alg n b -> Alg n b-union Zero x = x-union x Zero = x--- singletons--- NOTE: a zero-valued singleton contributes nothing, so the result must keep the--- /nonzero/ value on its OWN base. Earlier code returned @v2:@b1@ / @v1:@b2@,--- relabeling the surviving value onto the zero posting's base — this preserved--- 'norm' but silently moved the value to the wrong base, corrupting per-base--- projection and making construction order observable (raw @(:@)@ on a sparsified--- zero coefficient builds an explicit @0:@base@ singleton). Keep @v2:@b2@ / @v1:@b1@.-union (v1:@b1) (v2:@b2)- | isZeroValue v1 = case isZeroValue v2 of- True -> Zero- False -> v2:@b2- | isZeroValue v2 = v1:@b1- | otherwise = insert b2 v2 (v1:@b1)--- If one side is a singleton-union x (v:@b) = insert b v x-union (v:@b) x = insert b v x---- In the case of multiple elements-union (Liner m1 _ _ _ _ _) (Liner m2 _ _ _ _ _) = linerFromMap (Map.unionWith pairAppend m1 m2)---{-# INLINE insert #-}--- | Complexity:--- - into Zero or singleton: O(1) to O(d * index-build)--- - into Liner: O(n * d * index-build) due to rebuilding 'linerFromMap'--- where n is the number of distinct base keys after insertion.-insert :: (HatVal v,HatBaseClass b) => b -> v -> Alg v b -> Alg v b-insert _ v x | isZeroValue v = x-insert !b !v Zero = v .@ b-insert !b1 !v1 (v2:@b2) = case isHat b1 of- True -> insert b2 v2- $ linerFromMap- $ Map.singleton (base b1)- $ nullPair {_hatSide = Seq.singleton v1}- False -> insert b2 v2- $ linerFromMap- $ Map.singleton (base b1)- $ nullPair {_notSide = Seq.singleton v1}-insert !b !v (Liner m _ _ _ _ _) = case isHat b of- True -> insertLiner (nullPair {_hatSide = Seq.singleton v})- False -> insertLiner (nullPair {_notSide = Seq.singleton v})- where- !bp = base b- insertLiner !pairToInsert =- let !m' = Map.insertWith pairAppend bp pairToInsert m- in linerFromMap m'----------------------------------------------------------------------- Monoid---------------------------------------------------------------------instance (HatVal n, HatBaseClass b) => Monoid (Alg n b) where- -- Identity element- mempty = Zero- mappend = (<>)- mconcat = unions--{-# INLINE unions #-}--- | Complexity: O(sum of HashMap union costs over the fold)--- For a long list this is typically the dominant construction cost.-unions :: (HatVal n, Foldable f, HatBaseClass b) => f (Alg n b) -> Alg n b-unions ts = Foldable.foldl' union Zero ts--{-# INLINE mergeAlgMap #-}-mergeAlgMap :: (HatVal n, HatBaseClass b)- => Map.HashMap (BasePart b) (Pair n)- -> Alg n b- -> Map.HashMap (BasePart b) (Pair n)-mergeAlgMap !acc Zero = acc-mergeAlgMap !acc (v :@ b)- | isZeroValue v = acc- | otherwise =- let !p = if isHat b- then nullPair {_hatSide = Seq.singleton v}- else nullPair {_notSide = Seq.singleton v}- in Map.insertWith pairAppend (base b) p acc-mergeAlgMap !acc (Liner m _ _ _ _ _)- | Map.null m = acc- | otherwise = Map.unionWith pairAppend acc m--{-# INLINE mergeAlgMapIfNonZero #-}-mergeAlgMapIfNonZero :: (HatVal n, HatBaseClass b)- => Map.HashMap (BasePart b) (Pair n)- -> Alg n b- -> Map.HashMap (BasePart b) (Pair n)-mergeAlgMapIfNonZero !acc Zero = acc-mergeAlgMapIfNonZero !acc alg@(v :@ _)- | isZeroValue v = acc- | otherwise = mergeAlgMap acc alg-mergeAlgMapIfNonZero !acc alg = mergeAlgMap acc alg--{-# INLINE unionsMerge #-}--- | Merge multiple Algs by directly combining their internal HashMaps,--- building the AxisPosting index only once at the end.------ Produces the same /multiset/ of postings as @unions@\/'mconcat', but the--- same-base sequence order differs (the bulk-merge accumulates a new same-base--- singleton in front of the previously merged values, whereas the pairwise--- union path interleaves differently). 'Eq'\/@Binary@ observe that order, and--- 'Double' observes it through the last ULP of 'norm'\/'bar'; see the--- characterization test @testSameBaseSeqOrderPathDependence@.-unionsMerge :: (HatVal n, Foldable f, HatBaseClass b) => f (Alg n b) -> Alg n b-unionsMerge ts =- let !m = Foldable.foldl' mergeAlgMap Map.empty ts- in mkAlgFromMap m----------------------------------------------------------------------- Redundant---------------------------------------------------------------------instance (HatVal n, HatBaseClass b) => Redundant Alg n b where- (.^) Zero = Zero- (.^) (n:@ b) = n :@ (revHat b)- (.^) (Liner ms idx bpToId idToBp nextBpId allIds) = Liner- (Map.map (\ (Pair hs ns) -> Pair ns hs) ms)- idx- bpToId- idToBp- nextBpId- allIds-- (.+) = mappend-- _ .* Zero = Zero- 0 .* _ = Zero- -- The algebra is over non-negative values: reject a negative / non-finite- -- scalar instead of silently producing out-of-domain (negative) postings.- -- One check on the scalar suffices — x >= 0 and the existing values are >= 0,- -- so x*v stays non-negative and the cheap raw fmap below is safe.- x .* _ | isErrorValue x =- error ("(.*): non-negative finite scalar required, got " ++ show x)- x .* (v:@b) = (x * v) :@ b- x .* (Liner ms idx bpToId idToBp nextBpId allIds) = Liner- (Map.map (\ (Pair hs ns) -> Pair (fmap (x *) hs) (fmap (x *) ns)) ms)- idx- bpToId- idToBp- nextBpId- allIds-- norm Zero = 0- norm (v:@_) = v- norm (Liner ms _ _ _ _ _) = Map.foldl' (\ !x (Pair hs ns) -> x + Foldable.foldl' (+) 0 hs + Foldable.foldl' (+) 0 ns) 0 ms-- {-# INLINE (.-) #-}- (.-) Zero = Zero- (.-) (v:@b) = v:@b- (.-) (Liner m _ _ _ _ _) = let !res = Map.mapMaybe f m- in case null res of- True -> Zero- False -> linerFromMap res- where- {-# INLINE f #-}- f p@(Pair hs ns) =- let !h = Foldable.foldl' (+) 0 hs- !n = Foldable.foldl' (+) 0 ns- in case nearlyEqScaled h n of -- scale-aware tolerance (WI-11)- True -> Nothing- False -> case (Seq.length hs, Seq.length ns) of- -- Already in canonical form: singleton on winning side, empty on other- (1, 0) | h > n -> Just p- (0, 1) | n > h -> Just p- -- @EQ@ is unreachable here: this branch is only entered- -- when 'nearlyEqScaled' h n is False above, so h and n are- -- not (even approximately) equal; @compare h n@ is GT or- -- LT. The non-exhaustive @case@ is by design (audited) — a- -- defensive @EQ@ arm would be dead code with no canonical- -- result to return.- _ -> case compare h n of- GT -> Just (Pair (Seq.singleton (h - n)) Seq.empty)- LT -> Just (Pair Seq.empty (Seq.singleton (n - h)))-- {-# INLINE compress #-}- compress Zero = Zero- compress (v:@b) = v:@b- compress (Liner m idx bpToId idToBp nextBpId allIds) = Liner- (Map.map compressPair m)- idx- bpToId- idToBp- nextBpId- allIds- where- {-# INLINE compressPair #-}- compressPair p@(Pair hs ns) = case (Seq.length hs, Seq.length ns) of- (1, 1) -> p -- already singleton on both sides, reuse- (1, 0) -> p -- already singleton + empty, reuse- (0, 1) -> p -- already empty + singleton, reuse- _ -> Pair (Seq.singleton (Foldable.foldl' (+) 0 hs))- (Seq.singleton (Foldable.foldl' (+) 0 ns))---instance (HatVal n, ExBaseClass b) => Exchange Alg n b where- -- | filter Credit side- decR xs = filter (\x -> x /= Zero && (whichSide . _hatBase) x == Credit) xs-- -- | filter Debit side- decL xs = filter (\x -> x /= Zero && (whichSide . _hatBase) x == Debit) xs-- -- | filter Plus Stock- decP xs = filter (\x -> x /= Zero && (isHat . _hatBase ) x) xs-- -- | filter Minus Stock- decM xs = filter (\x -> x /= Zero && (not. isHat. _hatBase) x) xs-- -- | check Credit Debit balance (scale-aware tolerance, WI-12)- balance xs = nearlyEqScaled ((norm . decR) xs) ((norm . decL) xs)-- -- | (scale-aware tolerance, WI-12); near-equal sides report (Side, 0)- diffRL xs | nearlyEqScaled r l = (Side, 0)- | r > l = (Credit, r - l)- | otherwise = (Debit, l - r)- where- r = (norm . decR) xs- l = (norm . decL) xs----------------------------------------------------------------------- * Basic functions----------------------------------------------------------------------- | Returns all values contained in the algebra element as a list.------ Complexity: O(s) (s is the total number of scalar entries)-vals :: (HatVal v, HatBaseClass b) => Alg v b -> [v]-vals Zero = []-vals (v:@_) = [v]-vals (Liner m _ _ _ _ _) =- reverse $- Map.foldl'- (\acc (Pair hs ns) ->- Foldable.foldl' (flip (:))- (Foldable.foldl' (flip (:)) acc hs)- ns- )- []- m----- | Returns all bases contained in the algebra element as a list.------ Complexity: O(s) (s is the total number of scalar entries)-bases :: (HatVal v, HatBaseClass b) => Alg v b -> [b]-bases Zero = []-bases (_:@b) = [b]-bases (Liner m _ _ _ _ _) = Map.foldlWithKey' f [] m- where- f :: (HatVal v, HatBaseClass b) => [b] -> BasePart b -> Pair v -> [b]- f xs b (Pair {_hatSide = hs, _notSide = ns})- = Foldable.foldl' (g Not b) (Foldable.foldl' (g Hat b) xs hs) ns-- g :: (HatVal v, HatBaseClass b) => Hat -> BasePart b -> [b] -> v -> [b]- g h b ys _ = (merge h b):ys--{-# INLINE fromList #-}--- | convert List to Alg n b--- Complexity: O(sum of HashMap union costs), because this is implemented via 'mconcat'.------ >>> type Test = Alg Double (HatBase AccountTitles)--- >>> xs = [1:@Hat:<Cash,1:@Not:<Deposits, 2:@Hat:<Cash, 2:@Not:<Deposits] :: [Test]--- >>> fromList xs--- 1.00:@Hat:<Cash .+ 2.00:@Hat:<Cash .+ 1.00:@Not:<Deposits .+ 2.00:@Not:<Deposits------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Hat:<Yen .+ 2:@Not:<Amount :: Test--- >>> fromList [x,y]--- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Not:<Amount .+ 2.00:@Not:<Amount--fromList ::(HatVal v, HatBaseClass b ) => [Alg v b] -> Alg v b-fromList = mconcat------ | Summation function that applies a function to each element of a list and sums the results.--- Complexity: O(sum of HashMap union costs over produced elements).------ Uses the bulk-merge path ('unionsMerge'); see there for the same-base--- sequence-order caveat relative to 'fromList'\/'mconcat'.------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> sigma [1,2] (\x -> x:@Hat:<Yen)--- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen--{-# INLINE sigma #-}-sigma :: (HatVal v, HatBaseClass b) => [a] -> (a -> Alg v b) -> Alg v b-sigma xs f = mkAlgFromMap $ L.foldl' step Map.empty xs- where- step !acc !x = mergeAlgMapIfNonZero acc (f x)---- | Conditional summation over a double loop. For all combinations of two lists,--- applies the function only to pairs that satisfy the condition and sums the results.------ Complexity: O(|xs| * |ys| * union cost)-{-# INLINE sigma2When #-}-sigma2When :: (HatVal v, HatBaseClass b)- => [a]- -> [c]- -> (a -> c -> Bool)- -> (a -> c -> Alg v b)- -> Alg v b-sigma2When xs ys cond f =- mkAlgFromMap $ L.foldl' outer Map.empty xs- where- outer !acc !x = L.foldl' (inner x) acc ys- inner !x !acc !y- | cond x y = mergeAlgMapIfNonZero acc (f x y)- | otherwise = acc---- | Summation using keys and values from a Map. Skips entries with zero values.------ Complexity: O(|map| * union cost)-{-# INLINE sigmaFromMap #-}-sigmaFromMap :: (HatVal v, HatBaseClass b, Ord k)- => M.Map k v- -> (k -> v -> Alg v b)- -> Alg v b-sigmaFromMap kvs f =- mkAlgFromMap $ M.foldlWithKey' step Map.empty kvs- where- step !acc !k !v- | isZeroValue v = acc- | otherwise = mergeAlgMapIfNonZero acc (f k v)---- | Converts an algebra element to a list.--- Complexity: O(s) (s is the total number of scalar entries)------ >>> toList (10:@Hat:<(Cash) .+ 10:@Hat:<(Deposits) .+ Zero :: Alg Double (HatBase AccountTitles))--- [10.00:@Hat:<Deposits,10.00:@Hat:<Cash]------ you need define type variables to use this for Zero--- >>> toList Zero :: [Alg Double (HatBase AccountTitles)]--- []-toList :: (HatVal v, HatBaseClass b) => Alg v b -> [Alg v b]-toList Zero = []-toList (v:@b) = [v:@b]-toList (Liner m _ _ _ _ _) = Map.foldlWithKey' f [] m- where- f :: (HatVal v, HatBaseClass b) => [Alg v b] -> BasePart b -> Pair v -> [Alg v b]- f xs b Pair {_hatSide = hs, _notSide = ns}- = Foldable.foldl' (g Hat b) (Foldable.foldl' (g Not b) xs ns) hs-- g :: (HatVal v, HatBaseClass b) => Hat -> BasePart b -> [Alg v b] -> v -> [Alg v b]- g h b ys v- | isZeroValue v = ys- | otherwise = (v :@ (merge h b)):ys--{-# INLINE foldEntries #-}--- | Strict left fold over scalar entries without building an intermediate list.------ This is the implementation vehicle for the universal extension from the free--- commutative monoid of entries when each step acts through an associative,--- commutative accumulator operation (equivalently, entry updates commute).--- Under that condition the result is independent of both the internal--- @HashMap@ traversal and each side's sequence order. For a non-commutative--- accumulator, such as list append, this is only an ordinary left fold: its--- result records the actual traversal order and can distinguish different--- sequence orders of the same entry multiset.-foldEntries :: (HatVal v, HatBaseClass b)- => (acc -> v -> b -> acc)- -> acc- -> Alg v b- -> acc-foldEntries _ !acc Zero = acc-foldEntries f !acc (v :@ b)- | isZeroValue v = acc- | otherwise = f acc v b-foldEntries f !acc (Liner m _ _ _ _ _) =- Map.foldlWithKey' step acc m- where- step !acc0 !bp (Pair hs ns) =- let !hatBase = merge Hat bp- !notBase = merge Not bp- !acc1 = Foldable.foldl' (\a v -> if isZeroValue v then a else f a v hatBase) acc0 hs- in Foldable.foldl' (\a v -> if isZeroValue v then a else f a v notBase) acc1 ns--{-# INLINE toASCList #-}--- | Complexity: O(s log s), dominated by sorting the list representation.-toASCList :: (HatVal v, HatBaseClass b) => Alg v b -> [Alg v b]-toASCList = L.sort . toList----- | map--- Complexity: O(s + c), where s is traversed scalar entries and c is transformed output size.--- Typed alternatives: 'mapPosting' and 'mapMaybePosting'.------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> map (\ (x:@hb) -> x:@(toHat hb)) $ x .+ y--- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Yen .+ 1.00:@Hat:<Amount .+ 2.00:@Hat:<Amount------ >>> type Test = Alg Double Hat--- >>> x = 1:@Hat .+ 1:@Not :: Test--- >>> y = 2:@Not .+ 2:@Hat :: Test--- >>> map (\ (x:@hb) -> (2 * x):@hb) $ x .+ y--- 2.00:@Hat .+ 4.00:@Hat .+ 2.00:@Not .+ 4.00:@Not---- NB. 'map' applies its function to one singleton posting at a time and requires--- the result to itself be a singleton @(v2 :@ b2)@ (or 'Zero', handled via the--- 'isZeroValue' check / the 'r' helper below). The @let v2:@b2 = ...@ and the--- @case fn (...)@ in 'r' therefore intentionally match only the @(:@)@ shape; a--- 'Zero'\/'Liner' result is outside this contract, so the non-exhaustive patterns--- are by design (audited). Adding catch-all arms would silently change behavior.-map :: (HasCallStack,HatVal v, HatBaseClass b)- => (Alg v b -> Alg v b) -> Alg v b -> Alg v b-map _ Zero = Zero-map f (v:@b) = let v2:@b2 = f (v:@b)- in case isZeroValue v2 of- True -> Zero- False -> (v2 :@ b2)-map f (Liner m _ _ _ _ _) = mkAlgFromMap $ (Map.foldrWithKey (p f) dnilMap m) Map.empty- where- {-# INLINE dnilMap #-}- dnilMap = id- {-# INLINE dappendMap #-}- dappendMap = (.)- {-# INLINE dsingleMap #-}- dsingleMap (bp, p') = Map.insertWith pairAppend bp p'-- {-# INLINE p #-}- p :: (HatVal v, HatBaseClass b)- => (Alg v b -> Alg v b)- -> BasePart b- -> Pair v- -> DMap (BasePart b) (Pair v)- -> DMap (BasePart b) (Pair v)- p fn b Pair {_hatSide=hs, _notSide=ns} accDList =- let (dl1, hs2) = q fn Hat b hs- (dl2, ns2) = q fn Not b ns- prefix = dappendMap dl1 dl2- in case (Seq.null hs2, Seq.null ns2) of- (True,True) -> dappendMap prefix accDList- (True,False) -> dappendMap prefix- . dappendMap (dsingleMap (b, nullPair{_notSide = ns2}))- $ accDList- (False,True) -> dappendMap prefix- . dappendMap (dsingleMap (b, nullPair{_hatSide = hs2}))- $ accDList- (False,False) -> dappendMap prefix- . dappendMap (dsingleMap (b, Pair hs2 ns2))- $ accDList- {-# INLINE q #-}- q :: (HatVal v, HatBaseClass b)- => (Alg v b -> Alg v b)- -> Hat- -> BasePart b- -> Seq v- -> (DMap (BasePart b) (Pair v), Seq v)- q fn h b vs = Foldable.foldl' (r fn h b) (dnilMap, Seq.empty) vs-- {-# INLINE r #-}- r :: (HatVal v, HatBaseClass b)- => (Alg v b -> Alg v b)- -> Hat- -> BasePart b- -> (DMap (BasePart b) (Pair v), Seq v)- -> v- -> (DMap (BasePart b) (Pair v), Seq v)- r fn h b (dlAcc,vsAcc) v = case fn (v:@(merge h b)) of- Zero -> (dlAcc, vsAcc)- ------------------------------------------------------------------- v2:@b2- | isZeroValue v2 -> (dlAcc, vsAcc)- -- Structural equality: a wildcard in a ledger base is- -- a value, so rewriting it to (or from) a concrete- -- coordinate is a change of base, not \"the same base\".- | b2 == (merge h b) -> (dlAcc, v2 Seq.<| vsAcc)- | isHat (hat b2) -> (dappendMap dlAcc (dsingleMap ( base b2- ,nullPair{_hatSide = Seq.singleton v2}))- ,vsAcc )- | otherwise -> (dappendMap dlAcc (dsingleMap ( base b2- ,nullPair{_notSide = Seq.singleton v2} ))- ,vsAcc )---- | Map every posting to exactly one posting. The typed form of 'map'.-mapPosting :: (HatVal v, HatVal v2, HatBaseClass b, HatBaseClass b2)- => (v -> b -> (v2, b2)) -> Alg v b -> Alg v2 b2-mapPosting f = mapMaybePosting (\v b -> Just (f v b))---- | Map every posting to zero or one posting. A 'Nothing' drops the posting;--- a zero value is normalized as by '(.@)'.-mapMaybePosting :: (HatVal v, HatVal v2, HatBaseClass b, HatBaseClass b2)- => (v -> b -> Maybe (v2, b2)) -> Alg v b -> Alg v2 b2-mapMaybePosting _ Zero = Zero-mapMaybePosting f (v :@ b) = case f v b of- Nothing -> Zero- Just (v2, b2) -> v2 .@ b2-mapMaybePosting f (Liner m _ _ _ _ _) =- mkAlgFromMap $ Map.foldrWithKey addPair id m Map.empty- where- {-# INLINE addPair #-}- addPair bp (Pair hs ns) accDMap =- mapSide Hat bp hs . mapSide Not bp ns . accDMap-- {-# INLINE mapSide #-}- mapSide h bp = Foldable.foldl' (mapOne (merge h bp)) id-- {-# INLINE mapOne #-}- mapOne b dlAcc v = case f v b of- Nothing -> dlAcc- Just (v2, b2) -> case v2 .@ b2 of- Zero -> dlAcc- v3 :@ b3 -> dlAcc . Map.insertWith pairAppend (base b3) (postingPair v3 b3)- Liner {} -> dlAcc-- {-# INLINE postingPair #-}- postingPair v b- | isHat (hat b) = nullPair {_hatSide = Seq.singleton v}- | otherwise = nullPair {_notSide = Seq.singleton v}---- Difference-map (endo) used by 'map' to accumulate Liner rebuilds in O(1).--- NB. The plain difference-list helpers (dnil/dappend/dsingle/dToList/dFromList)--- and the unused 'DList' type alias were removed as dead code: only the DMap--- variants (dnilMap/dappendMap/dsingleMap, defined locally in 'map') are used.-type DMap k v = Map.HashMap k v -> Map.HashMap k v--{-# INLINE filter #-}--- | filter--- Complexity: O(s), where s is total number of scalar entries.------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> filter (isHat . _hatBase) $ x .+ y--- 1.00:@Hat:<Yen .+ 2.00:@Hat:<Amount------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> filter ((1 <). _val) $ x .+ y--- 2.00:@Not:<Yen .+ 2.00:@Hat:<Amount---filter :: (HatVal v, HatBaseClass b) => (Alg v b -> Bool) -> Alg v b -> Alg v b-filter _ Zero = Zero-filter f (v:@b) | f (v:@b) = v:@b- | otherwise = Zero--filter f (Liner m _ _ _ _ _) =- -- Build a new Map using mapMaybeWithKey- let m' = Map.mapMaybeWithKey- (\basePart (Pair hs ns) ->- -- Filter each of hs and ns- let hs' = filterSide basePart Hat hs- ns' = filterSide basePart Not ns- in- -- Remove the entry (Nothing) if both become empty- if Seq.null hs' && Seq.null ns'- then Nothing- else Just (Pair hs' ns'))- m- in- -- If the resulting Map is empty, return Zero; otherwise Liner m'- if Map.null m' then Zero else linerFromMap m'- where- ----------------------------------------------------------------- -- Filter function that constructs "v:@(merge h basePart)" from- -- basePart and Hat/Not, and tests whether it satisfies predicate f- ----------------------------------------------------------------- -- filterSide :: BasePart b -> Hat -> Seq v -> Seq v- {-# INLINE filterSide #-}- filterSide bp h = Seq.filter (\val -> f (val :@ merge h bp))----------------------------------------------------------------- | Relabel the /base part/ of every element, preserving the Hat\/Not structure--- and the redundancy (the ordered Hat- and Not-side sequences). Only the--- 'BasePart' is rewritten by @f@; the Hat\/Not side and the values are untouched.--- When @f@ maps two distinct base parts onto the same target, their sequences are--- concatenated (pair-append), so no value is lost — hence @'norm'@ is preserved.--- Preserving wildcards (@(.#)@) is the caller's responsibility (in @f@).------ == Laws and their layer of validity------ On the full subcategory of 'HatBaseClass' bases, this relabelling is a--- functor only after observing an algebra through ℘, where ℘ forgets sequence--- order but retains, for every full Hat\/Not base, the multiset of values.--- The functor and additive laws at this layer are:------ * @mapBasePart id x@ and @x@ are equal through ℘. Raw equality can fail when--- @x@ is a one-key, one-value @Liner@ produced by 'bar' or filtering:--- rebuilding it chooses the singleton @(:@)@ representation, and 'Eq'--- distinguishes those constructors.--- * @mapBasePart (g . f) x@ and--- @mapBasePart g (mapBasePart f x)@ are equal through ℘, but need not be raw--- equal.--- * @mapBasePart f (x .+ y)@ and--- @mapBasePart f x .+ mapBasePart f y@ are equal through ℘, but need not be--- raw equal.--- * @norm (mapBasePart f x) == norm x@ for exact additive value types. For--- floating-point values, regrouping after collisions has the usual rounding--- caveat.--- * @mapBasePart f ((.^) x) == (.^) (mapBasePart f x)@ (raw).--- * Algebraically, @bar (mapBasePart f (bar x))@ and--- @bar (mapBasePart f x)@ are equal through ℘ whenever the implementation's--- 'nearlyEqScaled' tolerance does not discard a source-base residual before--- relabelling. Raw equality can additionally distinguish singleton from--- one-key @Liner@. With tolerance-triggering magnitudes, even ℘ equality is--- not guaranteed.------ In particular, @mapBasePart f (bar x) == bar (mapBasePart f x)@ is false in--- general: distinct source bases may collide only after relabelling. When--- collisions occur, @pairAppend@ preserves every value but the resulting--- sequence order is a representation detail determined by @HashMap@ traversal;--- callers must not attach semantics to it.------ Complexity: O(n) over distinct base keys (rebuilds the posting index once).------ >>> type T = Alg Double (HatBase CountUnit)--- >>> mapBasePart id (10:@Hat:<Yen :: T) :: T--- 10.00:@Hat:<Yen------ >>> norm (mapBasePart (const Amount) (10:@Not:<Yen .+ 5:@Not:<Dollar :: T) :: T)--- 15.0-mapBasePart :: (HatVal v, HatBaseClass b, HatBaseClass b')- => (BasePart b -> BasePart b') -> Alg v b -> Alg v b'-mapBasePart _ Zero = Zero-mapBasePart f (v :@ b) = singleton v (merge (hat b) (f (base b)))-mapBasePart f (Liner m _ _ _ _ _) =- mkAlgFromMap $- Map.foldlWithKey'- (\acc bp p -> Map.insertWith pairAppend (f bp) p acc)- Map.empty- m----------------------------------------------------------------- | Free extension: substitute every scalar entry @v :\@ b@ by the algebra--- @h v b@ and merge the results with @('.+')@.------ Through ℘ (see 'mapBasePart'), 'Alg' is the free commutative monoid on its--- entries, and @extendBy h@ is the unique ℘-homomorphism that agrees with @h@--- on the generators. It acts entry by entry in the /redundant/ layer, whereas--- 'postFromNetBy' first nets through 'bar' and extends from that quotient.--- 'mapBasePart' is the special case that keeps the value and the Hat\/Not side--- and only relabels the base part.------ == Laws and their layer of validity------ ℘ observes, for every full Hat\/Not base, the multiset of values and forgets--- the per-side sequence order. Through ℘:------ * @extendBy h (x .+ y)@ equals @extendBy h x .+ extendBy h y@.--- * @extendBy (:\@)@ is the identity. Raw equality can fail because the--- result is rebuilt by a fold: the per-side order and the--- singleton-versus-@Liner@ representation may differ from @x@.--- * @mapBasePart f x@ equals--- @extendBy (\\v b -> v :\@ merge (hat b) (f (base b))) x@.--- * @norm (extendBy h x)@ equals the sum of @norm (h v b)@ over the entries of--- @x@ (exact additive value types; floating-point values carry the usual--- rounding caveat). In particular @norm@ is preserved whenever every--- @h v b@ has norm @v@.------ Zero-valued entries are skipped, as in 'foldEntries'. Well-formedness of the--- algebras returned by @h@ (non-negative, finite values) is the caller's--- responsibility: build them with @('.\@')@ rather than the raw constructor.--- The sequence order of the result is a representation detail determined by--- the traversal of @x@; callers must not attach semantics to it.------ Complexity: O(n · cost(h)) plus the cost of the @('.+')@ merges, in one--- pass over the entries.------ >>> type T = Alg Double (HatBase CountUnit)--- >>> let split v b = (v / 2) :@ b .+ (v / 2) :@ b :: T--- >>> norm (extendBy split (10:@Not:<Yen .+ 4:@Hat:<Dollar :: T))--- 14.0------ >>> extendBy (\v b -> v :@ b .+ v :@ b) (3:@Not:<Yen :: T) == (3:@Not:<Yen .+ 3:@Not:<Yen :: T)--- True-{-# INLINE extendBy #-}-extendBy :: (HatVal v, HatBaseClass b, HatBaseClass b')- => (v -> b -> Alg v b') -> Alg v b -> Alg v b'-extendBy h = foldEntries (\acc v b -> acc .+ h v b) Zero----------------------------------------------------------------- | proj------ Projects an 'Alg' onto the bases matching a query list. The query list is--- treated as a __set__: duplicate queries, or an exact base that also matches a--- wildcard query in the same list, select each underlying posting __at most--- once__ (no double counting). The result is the union of the selected--- @(base, side)@ cells.------ Wildcards match __one way__: only a wildcard in the /query/ is a pattern--- (\"any value on this axis\"). A wildcard stored in a ledger base is an--- ordinary value (\"this axis does not apply\"): a wildcard query axis selects--- it, a concrete query axis does not. The result is the same whether the--- ledger is a single element or a 'Liner'.------ >>> type Test = Alg Double (HatBase (AccountTitles, CountUnit))--- >>> proj [Not:<(Cash,Yen)] (10:@Not:<(Cash,(.#)) :: Test)--- 0------ >>> type Test = Alg Double (HatBase (AccountTitles, CountUnit))--- >>> proj [Not:<(Cash,(.#))] (10:@Not:<(Cash,(.#)) :: Test)--- 10.00:@Not:<(Cash,CountUnit)------ Complexity:--- exact single-key path: expected O(1)--- wildcard single-key path: O(queryAxisPosting + c * verify)--- multi-pattern path: O(sum pattern costs + union costs)------ where c is candidate count returned by the posting index.--- >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> proj [Hat:<Yen] $ x .+ y--- 1.00:@Hat:<Yen------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> proj [HatNot:<Amount] $ x .+ y--- 2.00:@Hat:<Amount .+ 1.00:@Not:<Amount------ >>> type Test = Alg Double (HatBase (AccountTitles, CountUnit))--- >>> x = 1:@Hat:<(Cash,Yen) .+ 1:@Not:<(Products,Amount) :: Test--- >>> y = 2:@Not:<(Cash,Yen) .+ 2:@Hat:<(Deposits,Yen) :: Test--- >>> proj [Hat:<((.#),Yen)] $ x .+ y--- 1.00:@Hat:<(Cash,Yen) .+ 2.00:@Hat:<(Deposits,Yen)------ >>> type Test = HatBase CountUnit--- >>> compareHatBase (Not:<(.#) :: Test) (Not:<Yen :: Test)--- EQ------ >>> type Test = Alg Double (HatBase CountUnit)--- >>> x = 1:@Hat:<Yen .+ 1:@Not:<Amount :: Test--- >>> y = 2:@Not:<Yen .+ 2:@Hat:<Amount :: Test--- >>> proj [Not:<(.#)] $ x .+ y--- 2.00:@Not:<Yen .+ 1.00:@Not:<Amount------------------------------------------------------------------proj :: (HatVal v, HatBaseClass b) => [b] -> Alg v b -> Alg v b-proj [] _ = Zero-proj _ Zero = Zero-proj [b] (v:@b2)- | matchesQuery b b2 = v:@b2- | otherwise = Zero--- Index fields bound lazily (@~@) so a concrete (non-wildcard) base never forces--- the axis index (the module is @Strict@; see 'projExactMap').-proj [b] (Liner m ~idx _ ~idToBp _ ~allIds) =- mkAlgFromMap $- if haveWildcard (base b)- then projWildMap b m idx idToBp allIds- else projExactMap b m-proj (b:bs) (v:@b2)- | matchesQuery b b2 = v:@b2- | otherwise = proj bs (v:@b2)--- Multi-pattern path: the query list is treated as a /set/. Overlapping or--- duplicate queries (e.g. a duplicated base, or an exact base subsumed by a--- wildcard) select the same posting only once. Per-base results are merged--- with 'pairUnion' (set union of the selected sides) rather than @pairAppend@--- (concatenation), so no posting is double counted.-proj (b:bs) (Liner m ~idx _ ~idToBp _ ~allIds) =- mkAlgFromMap $- L.foldl'- (\acc q -> Map.unionWith pairUnion acc- (if haveWildcard (base q)- then projWildMap q m idx idToBp allIds- else projExactMap q m))- Map.empty- (b:bs)--{-# INLINE choosePairByHat #-}--- | Complexity: O(1)-choosePairByHat :: Hat -> Pair v -> Pair v-choosePairByHat h Pair {_hatSide = hs, _notSide = ns} =- case h of- Hat -> nullPair {_hatSide = hs}- Not -> nullPair {_notSide = ns}- HatNot -> Pair {_hatSide = hs, _notSide = ns}--{-# INLINE projExactMap #-}--- | Exact (non-wildcard) single-base projection: a direct 'Map.lookup' on the--- @_realg@ map. It does NOT reference the axis index, so the concrete projection--- path can keep that lazy index unforced.------ This matters because the module is compiled @{-\# LANGUAGE Strict \#-}@: handing--- the (lazy) index to a helper that takes it as a strict argument would force its--- whole construction even for a concrete lookup that never needs it. Callers--- therefore dispatch on 'haveWildcard' BEFORE touching the index, binding the--- index fields lazily (@~@) and only mentioning them on the wildcard branch.--- Guarded by the poison-index regression test in the test suite.------ Complexity: expected O(1).-projExactMap- :: (HatBaseClass b)- => b- -> Map.HashMap (BasePart b) (Pair v)- -> Map.HashMap (BasePart b) (Pair v)-projExactMap b m = case Map.lookup bp m of- Nothing -> Map.empty- Just p -> Map.singleton bp (choosePairByHat h p)- where- !bp = base b- !h = hat b--{-# INLINE matchesQuery #-}--- | One-way wildcard match used by the @proj@ family: only the /query/'s--- wildcards act as a pattern. A wildcard stored in a ledger base is an--- ordinary value (\"this axis does not apply\"), so a concrete query axis does--- not match it, while a wildcard query axis does. This is the same predicate--- as @matches@ in "ExchangeAlgebra.Algebra.Transfer.Rule", and it is what the--- 'Liner' path computes through the exact lookup and the axis index, so the--- result of a projection does not depend on the shape of the ledger.------ Complexity: O(k) (k is the number of tuple components).-matchesQuery :: Element a => a -> a -> Bool-matchesQuery query entry = ignoreWildcard entry query == entry--{-# INLINE projWildMap #-}--- | Wildcard single-base projection: resolves candidates through the axis index--- ('queryAxisPosting'), so it necessarily forces the index. Only invoked when--- 'haveWildcard' holds.------ Complexity: O(queryAxisPosting + c * verify).-projWildMap- :: (HatBaseClass b)- => b- -> Map.HashMap (BasePart b) (Pair v)- -> AxisPosting- -> IntMap.IntMap (BasePart b)- -> IntSet.IntSet- -> Map.HashMap (BasePart b) (Pair v)-projWildMap b m idx idToBp allIds =- let !ids = queryAxisPosting (toAxisKeys bp) idx allIds- in IntSet.foldl'- (\acc bpId -> case IntMap.lookup bpId idToBp of- Nothing -> acc- Just bp0 -> case Map.lookup bp0 m of- Nothing -> acc- Just p -> if matchesQuery bp bp0- then Map.insert bp0 (choosePairByHat h p) acc- else acc)- Map.empty- ids- where- !bp = base b- !h = hat b--{-# INLINE mkAlgFromMap #-}--- | Complexity: O(n) to inspect shape and possibly rebuild index.-mkAlgFromMap :: (HatVal v, HatBaseClass b) => Map.HashMap (BasePart b) (Pair v) -> Alg v b-mkAlgFromMap m- | Map.null m = Zero- | otherwise = case Map.toList m of- [(b, p)] -> Maybe.fromMaybe (linerFromMap $ Map.singleton b p) (singlePairToAlg b p)- _ -> linerFromMap m--{-# INLINE singlePairToAlg #-}--- | Complexity: O(1)-singlePairToAlg :: (HatVal v, HatBaseClass b) => BasePart b -> Pair v -> Maybe (Alg v b)-singlePairToAlg b (Pair hs ns) = case (Seq.viewl hs, Seq.viewl ns) of- (Seq.EmptyL, n Seq.:< nsRest) | Seq.null nsRest -> Just (n :@ merge Not b)- (h Seq.:< hsRest, Seq.EmptyL) | Seq.null hsRest -> Just (h :@ merge Hat b)- _ -> Nothing------------------------------------------------------------------------ | Projects only the credit-side elements. For 'Alg' this coincides with the--- 'Exchange' class method 'decR' (R = Right = Credit, 貸方); the top-level name--- makes the selected side explicit at call sites. (An earlier doc sentence--- restricting this to non-'Enum' bases referred to long-removed 'Enum'-based--- class defaults and no longer applies.)------ Complexity: O(s) (s is the total number of scalar entries)-projCredit :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projCredit = filter (\x -> (whichSide . _hatBase) x == Credit)---- | Projects only the debit-side elements. For 'Alg' this coincides with the--- 'Exchange' class method 'decL' (L = Left = Debit, 借方); the top-level name--- makes the selected side explicit at call sites. (An earlier doc sentence--- restricting this to non-'Enum' bases referred to long-removed 'Enum'-based--- class defaults and no longer applies.)------ Complexity: O(s) (s is the total number of scalar entries)-projDebit :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projDebit = filter (\x -> (whichSide . _hatBase) x == Debit)---- | Projects only the elements matching the specified account title.------ The match is one-way, as in 'proj': a wildcard title in the query selects--- every element, while a concrete title does not select an element whose--- ledger title is the wildcard.------ Complexity: O(s) (s is the total number of scalar entries)-projByAccountTitle :: (HatVal n, ExBaseClass b) => AccountTitles -> Alg n b -> Alg n b-projByAccountTitle at alg = filter (f at) alg- where- f :: (HatVal n,ExBaseClass b) => AccountTitles -> Alg n b -> Bool- f _ Zero = False- f t x = matchesQuery t ((getAccountTitle ._hatBase) x)---- | Bar-netted norm of a projection. The query list is treated as a __set__--- (see 'proj'): overlapping or duplicate queries do not double count.------ Note the semantics include the @bar@ netting: each projected base is reduced--- to the non-negative net of its hat and not sides (@barNormPair@). Hence------ @projNetNorm bs x == norm (bar (proj bs x))@------ which is /not/ the same as @norm (proj bs x)@ when a base carries both sides.------ Wildcards match one way, as in 'proj': a concrete query axis does not match--- a wildcard stored in a ledger base.--- On an axis-preserving ledger, this nets only within each projected full--- base; it does not cancel across axis values. Use 'balanceMapBy' or--- 'netPairMapBy' for net amounts by group.--- Floating-point side and projected totals use sequential addition and depend--- on posting order. 'nearlyEqScaled' cancels near-equal sides. See--- "ExchangeAlgebra.Algebra.Exact" for exact sums over the original postings.------ Complexity: O(cost(proj) + cost(bar) + cost(norm)).-projNetNorm :: (HatVal n, HatBaseClass b) => [b] -> Alg n b -> n-projNetNorm [] _ = 0-projNetNorm _ Zero = 0-projNetNorm bs (v :@ b)- | L.any (`matchesQuery` b) bs = v- | otherwise = 0--- Index fields bound lazily (@~@); a concrete base uses 'projExactMap' (a plain--- 'Map.lookup') and never forces the axis index. See 'projExactMap'.-projNetNorm [b] (Liner m ~idx _ ~idToBp _ ~allIds) =- foldProjectedNorm $- if haveWildcard (base b)- then projWildMap b m idx idToBp allIds- else projExactMap b m--- Multi-pattern path: the query list is a /set/ (see 'proj'). Per-base results--- are merged with 'pairUnion' so overlapping/duplicate queries do not double--- count. Note 'projNetNorm' returns a bar-netted norm: 'foldProjectedNorm' applies--- @barNormPair@ (net of hat/not sides) per base, so the result equals--- @norm (bar (proj bs x))@, not @norm (proj bs x)@.-projNetNorm bs (Liner m ~idx _ ~idToBp _ ~allIds) =- foldProjectedNorm $- L.foldl'- (\acc q -> Map.unionWith pairUnion acc- (if haveWildcard (base q)- then projWildMap q m idx idToBp allIds- else projExactMap q m))- Map.empty- bs--{-# DEPRECATED projNorm "renamed to 'projNetNorm': the result is the bar-netted norm (norm (bar (proj bs x))), which the old name concealed — 'norm (proj bs x)' is NOT what this computes. 'projNorm' will be removed in 0.6" #-}--- | Deprecated alias for 'projNetNorm' (renamed in 0.5.0.0 so the name states--- the bar-netting).-projNorm :: (HatVal n, HatBaseClass b) => [b] -> Alg n b -> n-projNorm = projNetNorm--{-# INLINE foldProjectedNorm #-}--- | Complexity: O(k), where k is the number of projected base keys.-foldProjectedNorm :: (HatVal n) => Map.HashMap k (Pair n) -> n-foldProjectedNorm = Map.foldl' (\acc p -> acc + barNormPair p) 0--{-# INLINE barNormPair #-}--- | Complexity: O(h + n), where h/n are side lengths within the pair.-barNormPair :: (HatVal n) => Pair n -> n-barNormPair (Pair hs ns) =- let !h = Foldable.foldl' (+) 0 hs- !n = Foldable.foldl' (+) 0 ns- in if nearlyEqScaled h n- then 0- else if h > n then h - n else n - h----- | Compute the net balance as the difference of two projections.--- @balanceBy plusBases minusBases alg@ computes--- @projNetNorm plusBases alg - projNetNorm minusBases alg@.--- Floating-point totals inherit the posting-order dependence and near-equal--- cancellation of 'projNetNorm'. See "ExchangeAlgebra.Algebra.Exact" for exact--- sums over the original postings.------ Useful for calculating stock quantities, profits, etc.------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = 100 :@ Not:<Cash .+ 30 :@ Hat:<Cash :: T--- >>> balanceBy [Not:<Cash] [Hat:<Cash] alg--- 70.0------ >>> balanceBy [Hat:<Cash] [Not:<Cash] alg--- -70.0-balanceBy :: (HatVal n, HatBaseClass b) => [b] -> [b] -> Alg n b -> n-balanceBy plusBases minusBases alg =- projNetNorm plusBases alg - projNetNorm minusBases alg---- | Aggregate the net balance of an 'Alg' by a key, in a single pass.------ @balanceMapBy keyOf@ is the bucketed form of 'balanceBy': for every entry it--- projects the (side-stripped) 'BasePart' to a bucket key with @keyOf@ ('Nothing'--- drops the entry), and nets each bucket using the Hat\/Not convention (Not adds,--- Hat subtracts) — exactly @projNetNorm [Not:<k] - projNetNorm [Hat:<k]@ per key.--- @keyOf@ sees only the 'BasePart', not the Hat\/Not side, so it cannot split one--- key across sides.------ This replaces @[ (k, balanceBy [Not:<k] [Hat:<k] alg) | k <- keys ]@ — one--- wildcard projection per key — with a single fold; the result is identical up to--- floating-point reassociation. For per-key reporting over many keys this is the--- difference between @O(keys * entries)@ and @O(entries)@.------ The values are /signed/ net balances and may be negative, so use a signed value--- type (e.g. 'Double', @MoneyDouble@, @MoneyDecimal@); a non-negative-only type--- such as @Number.NonNegative.Double@ is unsuitable here. Keys whose net is zero--- are kept (like 'foldEntriesToMap'); filter afterwards if undesired.--- Floating-point bucket sums use sequential addition and depend on posting--- order. See "ExchangeAlgebra.Algebra.Exact" for exact sums over the original--- postings.------ Complexity: O(total number of entries) — a single fold, no per-key projection.------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = 100 :@ Not:<Cash .+ 30 :@ Hat:<Cash .+ 50 :@ Not:<Deposits :: T--- >>> balanceMapBy Just alg--- fromList [(Cash,70.0),(Deposits,50.0)]-{-# INLINE balanceMapBy #-}-balanceMapBy :: (HatVal v, HatBaseClass b, Ord k)- => (BasePart b -> Maybe k) -> Alg v b -> M.Map k v-balanceMapBy keyOf = foldEntriesToMap step- where- step v b = case keyOf (base b) of- Nothing -> Nothing- Just k -> Just (k, if isHat b then negate v else v)---- | Aggregate the per-base /netted/ balance of an 'Alg' by a key, keeping the--- winning side, in a single pass.------ This is the implementation counterpart of the paper's @def:class-net@--- (\(\nu_\kappa\)), the __pair__ read-out of the class-net operator:--- 'balanceMapBy' is its /signed difference/ version--- (@balanceMapBy kf == fmap (\\(n,h) -> n - h) . netPairMapBy kf@).------ @netPairMapBy keyOf@ projects each entry's (side-stripped) 'BasePart' to a--- bucket key with @keyOf@ ('Nothing' drops the entry), and for every key--- returns a pair @(notTotal, hatTotal)@ built as follows: for each base \(b\)--- it first nets the two sides (@bar@-like cancellation of the redundant--- sequences), keeping only the residual on the side that wins, then sums the--- residuals across all bases mapping to the key —------ \[ \Big( \textstyle\sum_{n_b > h_b} (n_b - h_b),\ \sum_{h_b > n_b} (h_b - n_b) \Big). \]------ This per-base netting is named and documented here — it is /not/ an implicit--- 'bar'; the function performs exactly the standard-element reduction the name--- promises, mirroring how 'balanceMapBy' reports a netted read-out.------ __Both components are non-negative__ (consistent with the value domain--- \(V \subseteq \mathbb{R}_{\ge 0}\)): a base contributes to at most one side,--- whichever residual is larger. Bases whose two sides are equal (up to--- 'nearlyEqScaled') contribute nothing. Because of the non-negativity,--- @netPairMapBy@ is well behaved for non-negative-only value types, whereas the--- @n - h@ identity with 'balanceMapBy' only holds on a /signed/ value type--- (e.g. 'Double', @MoneyDouble@, @MoneyDecimal@) where the difference can be--- negative.--- Floating-point per-base and per-key sums use sequential addition and depend--- on posting order. 'nearlyEqScaled' drops near-equal per-base sides. See--- "ExchangeAlgebra.Algebra.Exact" for exact sums over the original postings.------ Complexity: O(total number of entries) — a single fold over the entries,--- followed by one collapse over the distinct bases.------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = 100 :@ Not:<Cash .+ 30 :@ Hat:<Cash .+ 50 :@ Not:<Deposits :: T--- >>> netPairMapBy Just alg--- fromList [(Cash,(70.0,0.0)),(Deposits,(50.0,0.0))]-{-# INLINE netPairMapBy #-}-netPairMapBy :: (HatVal v, HatBaseClass b, Ord k)- => (BasePart b -> Maybe k) -> Alg v b -> M.Map k (v, v)-netPairMapBy keyOf alg =- -- collapse the per-base (notSum, hatSum) accumulator into per-key residuals- Map.foldlWithKey' collapse M.empty perBase- where- -- pass 1: accumulate (notSum, hatSum) per BasePart in one fold- perBase = foldEntries step Map.empty alg- step !acc v b =- let !bp = base b- !(notV, hatV) = if isHat b then (zeroValue, v) else (v, zeroValue)- in Map.insertWith addPair bp (notV, hatV) acc- addPair (!n1, !h1) (!n2, !h2) = (n1 + n2, h1 + h2)- collapse !acc bp (!n, !h) = case keyOf bp of- Nothing -> acc- Just k- | nearlyEqScaled h n -> acc- | n > h -> M.insertWith addPair k (n - h, zeroValue) acc- | otherwise -> M.insertWith addPair k (zeroValue, h - n) acc---- | Fold algebra entries into a @Map@, combining values with @(+)@.------ The selector function examines each entry @(v, b)@ and optionally returns--- a @(key, value)@ pair. Values for duplicate keys are summed.------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = 10 :@ Hat:<Cash .+ 20 :@ Hat:<Deposits .+ 5 :@ Hat:<Cash :: T--- >>> let f v (Hat :< a) = Just (a, v); f _ _ = Nothing--- >>> foldEntriesToMap f alg--- fromList [(Cash,15.0),(Deposits,20.0)]-foldEntriesToMap :: (HatVal v, HatBaseClass b, Ord k)- => (v -> b -> Maybe (k, v))- -> Alg v b- -> M.Map k v-foldEntriesToMap f = foldEntries step M.empty- where- step acc v b = case f v b of- Just (k, v') -> M.insertWith (+) k v' acc- Nothing -> acc---- | Quotient decomposition (dec_κ): partition an algebra along the equivalence--- classes induced by a classifier on the full 'HatBase', in a single pass.------ For each entry, @keyOf@ maps its base (Hat\/Not state included) to a class--- key; @Nothing@ drops the entry as residual. Each class is returned as an--- 'Alg' that is exactly the restriction of the input to that class:--- __redundancy (the per-base value sequences) is fully preserved__ — no 'bar',--- no 'norm', no aggregation. The pieces reconstruct the input:--- @mconcat (M.elems (decBy keyOf x)) .+ residual == x@ (up to per-base--- sequence order).------ This generalizes the decomposition operators of Deguchi & Nakano (1986)--- ('decR'\/'decL'\/'decP'\/'decM' are two-class special cases) and replaces--- per-class projection loops: one pass over the entries instead of one--- projection query per class.------ Choosing between the per-key family:------ +--------------------+----------------------------------+---------------------+--- | function | returns | redundancy |--- +====================+==================================+=====================+--- | 'decBy' | @Map k (Alg v b)@ (structure) | preserved |--- +--------------------+----------------------------------+---------------------+--- | 'balanceMapBy' | @Map k v@ (signed net per key) | lost (bar-like) |--- +--------------------+----------------------------------+---------------------+--- | 'netPairMapBy' | @Map k (v,v)@ (non-neg net pair) | lost (bar-like) |--- +--------------------+----------------------------------+---------------------+--- | 'foldEntriesToMap' | @Map k v@ (custom collection) | lost |--- +--------------------+----------------------------------+---------------------+--- | 'mapBasePart' | @Alg v b'@ (base coarsening π_κ) | preserved |--- +--------------------+----------------------------------+---------------------+------ Note on 'bar': @bar@ commutes with 'decBy' componentwise iff @keyOf@ does not--- distinguish Hat\/Not (i.e. factors through 'base'). Side-sensitive classifiers--- (such as the ones underlying 'decP'\/'decM' or 'decL'\/'decR') do not commute--- with @bar@ — netting before or after such a split is a semantic choice.------ Complexity: O(m) over distinct bases (single fold; per-class insert costs--- O(log k) in the result 'M.Map').------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = 100 :@ Not:<Cash .+ 30 :@ Hat:<Cash .+ 50 :@ Not:<Deposits :: T--- >>> M.toList (M.map norm (decBy (\(_ :< a) -> Just a) alg))--- [(Cash,130.0),(Deposits,50.0)]------ >>> M.toList (M.map norm (decBy (\b -> if isHat b then Just () else Nothing) alg))--- [((),30.0)]-{-# INLINE decBy #-}-decBy :: (HatVal v, HatBaseClass b, Ord k)- => (b -> Maybe k)- -> Alg v b- -> M.Map k (Alg v b)-decBy _ Zero = M.empty-decBy kf a@(v :@ b)- | isZeroValue v = M.empty- | otherwise = case kf b of- Nothing -> M.empty- Just k -> M.singleton k a-decBy kf (Liner m _ _ _ _ _) =- M.map mkAlgFromMap (Map.foldlWithKey' step M.empty m)- where- step !acc !bp (Pair hs ns) =- let !acc1 = if Seq.null hs- then acc- else insertSide (merge Hat bp) bp (nullPair {_hatSide = hs}) acc- in if Seq.null ns- then acc1- else insertSide (merge Not bp) bp (nullPair {_notSide = ns}) acc1- {-# INLINE insertSide #-}- insertSide hb bp p acc = case kf hb of- Nothing -> acc- Just k -> M.insertWith (Map.unionWith (flip pairAppend)) k (Map.singleton bp p) acc---- | Classify-net-post, fused: net the algebra per base ('bar' — explicit in the--- name), classify each netted entry with @keyOf@ (class totals are summed with--- @(+)@), then generate postings per class and bulk-merge them.------ @postFromNetBy keyOf post x == sigmaFromMap (foldEntriesToMap collect (bar x)) post@--- where @collect@ pairs each netted entry with its class. The common--- \"shortage detection → purchase postings\" pattern becomes a single call:------ @--- purchases = postFromNetBy shortageKey purchasePosting termAlg--- @------ and runs in one pass over the netted entries — no per-pair projection loop--- (the naive all-pairs formulation costs O(N²) queries; this costs O(m)).------ __This function applies 'bar' internally__ (per-base netting, the standard--- positive-part normalization). Redundancy of the input is not preserved in the--- intermediate; the output is whatever @post@ builds. If you need the--- redundancy-preserving split itself, use 'decBy'.--- Thus it factors through the quotient induced by 'bar': it is not the free--- extension that acts independently on entries in the redundant layer (that--- one is 'extendBy').--- Floating-point per-base and class totals use sequential addition and depend--- on posting order; 'bar' cancels near-equal sides under 'nearlyEqScaled'. See--- "ExchangeAlgebra.Algebra.Exact" for exact sums over the original postings.------ Complexity: O(m + Σ cost(post)).------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let stock = 100 :@ Not:<Products .+ 130 :@ Hat:<Products .+ 20 :@ Not:<Cash :: T--- >>> let shortageKey b = case b of { Hat :< Products -> Just () ; _ -> Nothing }--- >>> norm (postFromNetBy shortageKey (\_ v -> v :@ Not:<Products .+ v :@ Hat:<Cash) stock)--- 60.0-{-# INLINE postFromNetBy #-}-postFromNetBy :: (HatVal v, HatBaseClass b, Ord k)- => (b -> Maybe k)- -> (k -> v -> Alg v b)- -> Alg v b- -> Alg v b-postFromNetBy kf post x =- sigmaFromMap (foldEntriesToMap (\v b -> (\k -> (k, v)) <$> kf b) ((.-) x)) post---- | Projects only current assets.--- Extracts asset items classified as current from the debit side.------ Selection predicate (over every scalar entry @x@ of the input, on the debit side):--- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Current && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projCurrentAssets x) - norm ('bar' (contra x))@ where--- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)-projCurrentAssets :: ( HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projCurrentAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Current))- . (filter (\x -> (whatDiv . _hatBase) x == Assets))- . (filter (not . isContra . _hatBase))- . projDebit---- | Projects only fixed assets.--- Extracts asset items classified as fixed from the debit side.------ Selection predicate (over every scalar entry @x@ of the input, on the debit side):--- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Fixed && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projFixedAssets x) - norm ('bar' (contra x))@ where--- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)-projFixedAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projFixedAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Fixed))- . (filter (\x -> (whatDiv . _hatBase) x == Assets))- . (filter (not . isContra . _hatBase))- . projDebit---- | Projects only deferred assets.--- Tax-specific deferred assets are presented under "investments and other assets" with appropriate items such as long-term prepaid expenses.------ Selection predicate (over every scalar entry @x@ of the input, on the debit side):--- @whatDiv (_hatBase x) == Assets && fixedCurrent (_hatBase x) == Other && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projDeferredAssets x) - norm ('bar' (contra x))@ where--- @contra@ is 'projContraAssets' (Assets) or 'projContra' (any division).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)-projDeferredAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projDeferredAssets = (filter (\x -> (fixedCurrent . _hatBase) x == Other))- . (filter (\x -> (whatDiv . _hatBase) x == Assets))- . (filter (not . isContra . _hatBase))- . projDebit---- | Projects only current liabilities.--- Extracts liability items classified as current from the credit side.------ Selection predicate (over every scalar entry @x@ of the input, on the credit side):--- @whatDiv (_hatBase x) == Liability && fixedCurrent (_hatBase x) == Current && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projCurrentLiability x) - norm ('bar' (contra x))@ where--- @contra@ selects the Liability-division entries of 'projContra' (the current--- registry has no contra liability account, so gross and net coincide today).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)-projCurrentLiability :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projCurrentLiability = (filter (\x -> (fixedCurrent . _hatBase) x == Current))- . (filter (\x -> (whatDiv . _hatBase) x == Liability))- . (filter (not . isContra . _hatBase))- . projCredit---- | Projects only fixed liabilities.--- Extracts liability items classified as fixed from the credit side.------ Selection predicate (over every scalar entry @x@ of the input, on the credit side):--- @whatDiv (_hatBase x) == Liability && fixedCurrent (_hatBase x) == Fixed && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projFixedLiability x) - norm ('bar' (contra x))@ where--- @contra@ selects the Liability-division entries of 'projContra' (the current--- registry has no contra liability account, so gross and net coincide today).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)-projFixedLiability :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projFixedLiability = (filter (\x -> (fixedCurrent . _hatBase) x == Fixed))- . (filter (\x -> (whatDiv . _hatBase) x == Liability))- . (filter (not . isContra . _hatBase))- . projCredit---- | Projects only capital stock (equity).--- Extracts items classified under the 'Equity' division from the credit side.------ Selection predicate (over every scalar entry @x@ of the input, on the credit side):--- @whatDiv (_hatBase x) == Equity && not (isContra (_hatBase x))@.--- Contra accounts are excluded, so the result is the /gross/ figure of this--- class; the net figure is @norm (projCapitalStock x) - norm ('bar' (contra x))@ where--- @contra@ selects the Equity-division entries of 'projContra' (the current--- registry has no contra equity account, so gross and net coincide today).--- See 'projContraAssets' for the rationale (Definition 7 amendment, Land 2).------ Complexity: O(s) (s is the total number of scalar entries)------ >>> type Test = Alg Double (HatBase AccountTitles)--- >>> x = 100:@Not:<CapitalStock .+ 30:@Not:<Cash .+ 20:@Not:<RetainedEarnings :: Test--- >>> norm (projCapitalStock x)--- 120.0-projCapitalStock :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projCapitalStock = (filter (\x -> (whatDiv . _hatBase) x == Equity))- . (filter (not . isContra . _hatBase))- . projCredit---- | Projects contra-asset entries (@whatDiv == Assets && isContra@, e.g.--- 貸倒引当金\/減価償却累計額) — an /attribute/ selection, not a physical-side--- one: both Hat and Not postings of the contra account are kept, and normal--- assets' credit-side (Hat) postings are NOT included. The division--- projections (@proj*Assets@\/@proj*Liability@\/'projCapitalStock')--- exclude ALL contra accounts, so within the Assets division this projection--- is the sole selector — no double counting when combining them. A net--- figure is @gross - contra balance@, e.g.--- @norm (projCurrentAssets x) - norm ('ExchangeAlgebra.Algebra.bar' (projContraAssets x))@--- when the contra accounts hold normal (credit) balances; deduction\/netting--- /presentation/ policy is the Write side's job (Land 3).------ NOTE: this selects the Assets division only. In the current registry every--- contra account is an asset, but the type class does not forbid contra--- accounts in other divisions (e.g. a future treasury-stock contra equity) —--- those are excluded from the division projections too and must be selected--- with the generic 'projContra'. Consumers that need a--- net asset figure combine the gross @proj*Assets@ family with this--- projection themselves; deduction\/netting presentation policy is the--- Write side's job (Land 3 of the Definition 7 amendment).------ Complexity: O(s) (s is the total number of scalar entries)------ >>> type Test = Alg Double (HatBase AccountTitles)--- >>> x = 100:@Not:<AllowanceForDoubtfulAccounts .+ 20:@Hat:<AllowanceForDoubtfulAccounts .+ 30:@Not:<Cash .+ 10:@Hat:<Cash :: Test--- >>> projContraAssets x--- 20.00:@Hat:<AllowanceForDoubtfulAccounts .+ 100.00:@Not:<AllowanceForDoubtfulAccounts-projContraAssets :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projContraAssets = filter- (\x -> (whatDiv . _hatBase) x == Assets && (isContra . _hatBase) x)---- | Projects ALL contra entries regardless of division — the exact--- complement, w.r.t. contra-ness, of the six division projections (which all--- exclude contra accounts). Use this when the chart may contain contra--- accounts outside the Assets division; @'projContraAssets' = filter by--- Assets ∘ projContra@.------ Complexity: O(s) (s is the total number of scalar entries)-projContra :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-projContra = filter (isContra . _hatBase)----- * Rounding---- | Rounding (ceiling), fixed to @NN.Double@ and to whole units.------ Superseded by the explicit, value-type-appropriate rounding functions in--- "ExchangeAlgebra.Value": 'ExchangeAlgebra.Value.bankersRound' (unbiased--- financial default) and 'ExchangeAlgebra.Value.ceilingRound' (this function's--- behavior, with a decimal-places argument). There is no single correct--- rounding rule, so the rule should be chosen explicitly at the call site.------ Complexity: O(1)-rounding :: NN.Double -> NN.Double-rounding = fromIntegral . ceiling--{-# DEPRECATED rounding "NN.Double-only whole-unit ceiling; use ExchangeAlgebra.Value.ceilingRound / bankersRound (explicit, value-type-appropriate) instead" #-}+ ( customError+ , BaseClass(..)+ , HatBaseClass(..)+ , Hat(..)+ , BaseForSingleHat(..)+ , HatBase(..)+ , Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic+ , Nearly(..)+ , isNearlyNum+ , Redundant(..)+ , HatVal(..)+ , Pair(..)+ , Alg(.., (:@), _val, _hatBase)+ , linerFromMap+ , isZero+ , (.@)+ , (<@)+ , vals+ , bases+ , fromList+ , toList+ , foldEntries+ , sigma+ , sigma2When+ , sigmaFromMap+ , toASCList+ , map+ , mapPosting+ , mapMaybePosting+ , mapBasePart+ , extendBy+ , filter+ , proj+ , projNetNorm+ , balanceBy+ , balanceMapBy+ , netPairMapBy+ , foldEntriesToMap+ , decBy+ , postFromNetBy+ , unionsMerge) where++import ExchangeAlgebra.Algebra.Core.Representation+import ExchangeAlgebra.Algebra.Base+import ExchangeAlgebra.Algebra.Value.Class+import Prelude hiding (map, filter)
+ src/ExchangeAlgebra/Algebra/Posting.hs view
@@ -0,0 +1,227 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE RoleAnnotations #-}++-- | Build checked postings at the algebra entry boundary.+-- The module uses only the foundation algebra (@Alg@, @Hat@, @HatBaseClass@)+-- and no accounting semantics. It validates values once and constructs+-- @Posting@ values without implicit cancellation. Models return these postings,+-- and simulators attach notes when committing them.+--+-- Posting construction follows Definitions 3-5.+--+-- @+-- Question Operation+-- Check a scalar value posted+-- Build a posting entry+-- Read the underlying algebra postingAlg+-- @+--+-- For the 'Alg' returned by 'postingAlg', import+-- "ExchangeAlgebra.Algebra.Core" and choose its operations by question:+--+-- @+-- Question Core operation+-- Build an algebra .@, .++-- Cancel opposing values bar+-- Select bases proj, projNetNorm+-- Measure values norm, balanceBy+-- @+--+-- Import "ExchangeAlgebra.Accounting.Account" alongside this module when+-- the posting base uses account titles. The old "ExchangeAlgebra.Posting"+-- path re-exports the same types and functions during the transition.+module ExchangeAlgebra.Algebra.Posting+ ( -- * Posting values+ Posted+ , PostedError(..)+ , posted+ , unPosted+ -- * Posting sides+ , PostSide(..)+ , sideHat+ -- * Postings+ , Posting+ , entry+ , postingAlg+ ) where++import Control.DeepSeq (NFData(..))+import Data.Binary (Binary(..))+import Data.Hashable (Hashable(..))+import GHC.Generics (Generic)++import ExchangeAlgebra.Algebra.Core.Representation (Alg(Zero), Redundant((.+)), (.@))+import ExchangeAlgebra.Algebra.Value.Class+ (HatVal(isZeroValue, postingUpperBound, valueError, zeroValue), ValueError)+import ExchangeAlgebra.Algebra.Base.Representation+ ( Hat(..)+ , HatBaseClass(BasePart, merge, base)+ )++-- * Posting values++-- | A checked posting value of value type @v@, with no 'Num', 'Fractional',+-- or 'Real' instance (0.6.0.0: polymorphic; 0.5.x fixed @v = Double@).+--+-- Invariant: @valueError x == Nothing@ and, when @postingUpperBound == Just u@,+-- @x <= u@; zero is stored as 'zeroValue' (positive zero for 'Double').+-- Construct values through 'posted'.+--+-- The value type is nominal: @coerce@ cannot turn a value checked at one type+-- into a t'Posted' of another type (for example a user newtype with a smaller+-- 'postingUpperBound').+type role Posted nominal+newtype Posted v = Posted v+ deriving stock (Eq, Ord, Show)++instance NFData v => NFData (Posted v) where+ rnf (Posted value) = rnf value++instance Hashable v => Hashable (Posted v) where+ hashWithSalt salt (Posted value) = hashWithSalt salt value++-- | Decoding checks the same invariant as 'posted' and fails on invalid input.+-- The encoding of @Posted Double@ is the 0.5.x encoding.+instance (HatVal v, Binary v) => Binary (Posted v) where+ put = put . unPosted+ get = do+ value <- get+ case posted value of+ Left failure -> fail (show failure)+ Right validated -> pure validated++-- | The first failed check, in the order used by 'posted'.+-- (0.6.0.0: the value checks moved to 'ValueError'; 0.5.x had @NonFinite@ and+-- @Negative@ here.)+data PostedError+ = InvalidValue ValueError -- ^ 'valueError' rejected the value.+ | AboveBound -- ^ A valid value above 'postingUpperBound'.+ deriving stock (Eq, Show, Generic)++instance NFData PostedError++-- | Validate a value, normalizing zero to 'zeroValue' (negative zero to+-- positive zero for 'Double'). Checks 'valueError' and then+-- 'postingUpperBound', returning the corresponding 'PostedError' on failure.+--+-- For every @x@ with @valueError x == Nothing@ and @x <= u@ whenever+-- @postingUpperBound == Just u@, with @normalizeZero x@ equal to 'zeroValue'+-- when @isZeroValue x@ and to @x@ otherwise:+--+-- > fmap unPosted (posted x) == Right (normalizeZero x)+--+-- The law uses exact equality of the value type. The bound is checked only at+-- the posting entry point; '.@' checks 'valueError' alone.+-- Complexity: O(1).+posted :: HatVal v => v -> Either PostedError (Posted v)+posted value+ | Just failure <- valueError value = Left (InvalidValue failure)+ | Just bound <- postingUpperBound+ , value > bound = Left AboveBound+ | isZeroValue value = Right (Posted zeroValue)+ | otherwise = Right (Posted value)++-- | Read a checked value. For every @p@, @posted (unPosted p) == Right p@.+-- Complexity: O(1).+unPosted :: Posted v -> v+unPosted (Posted value) = value++-- * Posting sides++-- | The two posting sides; query wildcard 'HatNot' is excluded.+data PostSide+ = HatSide -- ^ The @Hat@ side.+ | NotSide -- ^ The 'Not' side.+ deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)++instance Binary PostSide++instance Hashable PostSide++instance NFData PostSide++-- | Embed a posting side into a query-capable hat.+-- @sideHat HatSide == Hat@ and @sideHat NotSide == Not@; 'HatNot' is never returned.+-- Complexity: O(1).+sideHat :: PostSide -> Hat+sideHat HatSide = Hat+sideHat NotSide = Not++-- * Postings++-- | Postings built through 'entry' and 'Monoid', without implicit cancellation+-- or compression. Equality and display delegate to the underlying 'Alg'.+-- Equality is structural and depends on construction order. To compare+-- multisets of postings instead, compare the results of @toASCList . postingAlg@.+-- (0.6.0.0: polymorphic in the value type @v@; 0.5.x fixed @v = Double@.)+-- The value type is nominal for the same reason as in t'Posted'.+type role Posting nominal nominal+newtype Posting v b = Posting (Alg v b)++instance (HatVal v, HatBaseClass b) => Eq (Posting v b) where+ Posting left == Posting right = left == right++instance (HatVal v, HatBaseClass b) => Show (Posting v b) where+ showsPrec precedence (Posting algebra) = showsPrec precedence algebra++instance NFData (Posting v b) where+ rnf (Posting algebra) = rnf algebra++-- | Preserve algebra addition:+--+-- > postingAlg (a <> b) == (postingAlg a .+ postingAlg b)+--+-- This law uses 'Alg' equality without a tolerance for every 'HatBaseClass'+-- instance, so it also preserves the multiset of postings. Associativity+-- holds as equality of posting multisets, using+-- @sameMultiset x y = toASCList x == toASCList y@:+--+-- > sameMultiset (postingAlg ((a <> b) <> c)) (postingAlg (a <> (b <> c)))+--+-- Complexity: the same as '(.+)' on the underlying algebras.+instance (HatVal v, HatBaseClass b) => Semigroup (Posting v b) where+ Posting left <> Posting right = Posting (left .+ right)++-- | The empty posting obeys @postingAlg mempty == Zero@ using exact 'Alg' equality.+-- The identity laws hold as equality of posting multisets for every+-- 'HatBaseClass' instance, without a numeric tolerance:+--+-- > sameMultiset (postingAlg (mempty <> a)) (postingAlg a)+-- > sameMultiset (postingAlg (a <> mempty)) (postingAlg a)+--+-- Here @sameMultiset x y = toASCList x == toASCList y@.+-- Complexity: O(1) for 'mempty'; combination uses the 'Semigroup' instance.+instance (HatVal v, HatBaseClass b) => Monoid (Posting v b) where+ mempty = Posting Zero++-- | Read the underlying algebra. For every side, checked value, and base part:+--+-- > postingAlg (entry side value part) == unPosted value .@ merge (sideHat side) part+--+-- This is a one-way conversion with exact 'Alg' equality. For a list @xs@+-- of @(side, value, part)@ triples, let @mk (s, v, p) = entry s v p@ and+-- @sameMultiset x y = toASCList x == toASCList y@. Conversion and projection+-- preserve the multiset of postings for every query list @qs@, including+-- 'HatNot' and coordinate wildcards:+--+-- > sameMultiset (postingAlg (foldMap mk xs))+-- > (foldr (.+) Zero (map (postingAlg . mk) xs))+-- > sameMultiset (proj qs (postingAlg (foldMap mk xs)))+-- > (foldr (.+) Zero [proj qs (postingAlg (mk x)) | x <- xs])+--+-- These laws use exact multiset equality without a numeric tolerance for+-- every 'HatBaseClass' instance. Complexity of 'postingAlg': O(1).+postingAlg :: Posting v b -> Alg v b+postingAlg (Posting algebra) = algebra++-- | Build one posting from a side, checked value, and base coordinates.+-- A zero value produces 'Zero' through '(.@)'. The side is embedded by+-- 'sideHat' and the coordinates are combined with 'merge'.+--+-- > postingAlg (entry side value part) == unPosted value .@ merge (sideHat side) part+--+-- The law holds with exact 'Alg' equality for every 'HatBaseClass' instance.+-- Complexity: O(1).+entry :: (HatVal v, HatBaseClass b) => PostSide -> Posted v -> BasePart b -> Posting v b+entry side value part = Posting (unPosted value .@ merge (sideHat side) part)
− src/ExchangeAlgebra/Algebra/Readout/Net.hs
@@ -1,51 +0,0 @@-{- |- Module : ExchangeAlgebra.Algebra.Readout.Net- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Read-outs that leave the redundant algebra. Every function here first- passes through the 'ExchangeAlgebra.Algebra.bar' quotient (per-base- netting) and then returns plain values, so the audit trail kept by the- redundant sequence is discarded on purpose:-- * 'projNetNorm' \/ 'projNorm' (alias): the netted norm of a projection.- * 'balanceBy': a __signed__ difference of two netted norms, the only- place in the package where a value can be negative.- * 'balanceMapBy' \/ 'netPairMapBy': keyed netted balances, signed and as- non-negative (increase, decrease) pairs respectively.- * 'projWithBaseNetNorm' \/ 'projWithNoteBaseNetNorm': the 'ExchangeAlgebra.Journal.Journal'- counterparts of 'projNetNorm'.-- The names are re-exported unchanged from "ExchangeAlgebra.Algebra.Internal"- and "ExchangeAlgebra.Journal"; this module only gives the lossy read-outs a- namespace of their own, separate from the core that never nets implicitly.- A signed read-out is a projection out of the algebra, not a group- completion of it.--}--module ExchangeAlgebra.Algebra.Readout.Net- ( -- * Netted norms of an algebra- projNetNorm- , projNorm- -- * Signed and paired balances- , balanceBy- , balanceMapBy- , netPairMapBy- -- * Netted norms of a journal- , projWithBaseNetNorm- , projWithNoteBaseNetNorm- ) where--import ExchangeAlgebra.Algebra.Internal- ( projNetNorm- , projNorm- , balanceBy- , balanceMapBy- , netPairMapBy- )-import ExchangeAlgebra.Journal- ( projWithBaseNetNorm- , projWithNoteBaseNetNorm- )
src/ExchangeAlgebra/Algebra/Transfer.hs view
@@ -1,702 +1,27 @@ {- |- Module : ExchangeAlgebra.Algebra.Transfer- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Legacy rewriting transfers. New code should use- "ExchangeAlgebra.Algebra.Transfer.Rule" for data-defined rules and entries- that are added to the ledger (Definition 9).-- Compatibility assumptions: P1, source patterns share wildcard positions;- P2, source patterns do not overlap; P3, ledger bases have no wildcards;- P4, axes are not nested tuples; P5, transformed values are nonzero.- Mixed wildcard positions can make the legacy tree lookup miss matching- entries even for disjoint rules. Overlap priority is unspecified, and- matching is symmetric (ledger wildcards also match concrete patterns).- P3 is required only for equivalence with the legacy @transfer@ and- @finalStockTransfer@ APIs, which match symmetrically. The new- "ExchangeAlgebra.Algebra.Transfer.Rule" API uses one-way matching in- @transferEntries@ and groups @closingEntries@ by actual base; both treat- a wildcard stored in the ledger as a value. These known legacy- limitations are preserved for compatibility.-- Package for Exchange Algebra defined by Hiroshi Deguchi.-- Exchange Algebra is an algebraic description of bookkeeping systems.- Details are below.-- <https://www.springer.com/gp/book/9784431209850>+Module : ExchangeAlgebra.Algebra.Transfer+Description : Generic coordinate transfers and additional entries. - <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>+This Algebra-layer module re-exports generic transfer rules and coordinate+collapse from their shared representation. Start with 'mkTransferRules', then+apply 'transferEntries' to generate source cancellations and target postings+(Definition 9). Add the returned entries to the original ledger with @(.+)@;+the original postings remain. Only 'collapseNetEntries' applies @bar@. +For account-aware transfers, use "ExchangeAlgebra.Accounting.Transfer".+For closing entries, use "ExchangeAlgebra.Accounting.Closing".+For unit conversion, use @mapPosting@ from "ExchangeAlgebra.Algebra.Core". -}---{-# LANGUAGE GADTs #-}-{-# LANGUAGE Strict #-}-{-# LANGUAGE StrictData #-}-{-# LANGUAGE PatternGuards #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PostfixOperators #-}----module ExchangeAlgebra.Algebra.Transfer- ( Size- , TransTable- , isNullTable- , transfer- , table- , TransTableParts- , (.->)- , (|%)- , createTransfer- , incomeSummaryAccount- , netIncomeTransfer- , grossProfitTransfer- , ordinaryProfitTransfer- , retainedEarningTransfer- , finalStockTransferStep- , finalStockTransfer- ) where--import ExchangeAlgebra.Algebra.Transfer.Rule (ClosingSide(..), closingSide)-import qualified ExchangeAlgebra.Algebra as EA-import ExchangeAlgebra.Algebra--import Text.Show.Unicode ( ushow)-import GHC.Exts ( reallyUnsafePtrEquality#- , isTrue#- , lazy)-import Data.Semigroup ( Semigroup(stimes)- , stimesIdempotentMonoid)-import qualified Data.Foldable as Foldable-import Data.Bits ( shiftL- , shiftR)-import qualified Data.HashMap.Strict as HM-import Utils.Containers.Internal.StrictPair----------------------------------------------------------------------- * Core computation---------------------------------------------------------------------- ** Transfer transformation-type Size = Int---- | Transfer transformation table--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.-data TransTable n b where- NullTable :: (HatVal n, HatBaseClass b) => TransTable n b- TransTable :: (HatVal n, HatBaseClass b)- => { _size :: Size- , _before :: b {- ^ Base before transformation -}- , _transFunc :: (n -> n) {- ^ Value transformation function -}- , _after :: b {- ^ Base after transformation -}- , _left :: TransTable n b- , _right :: TransTable n b }- -> TransTable n b---- | Tests whether the transfer table is empty.------ Complexity: O(1)-{-# INLINE isNullTable #-}-isNullTable :: TransTable n b -> Bool-isNullTable NullTable = True-isNullTable _ = False--instance (HatBaseClass b) => Show (TransTable n b) where- show NullTable = "[]"- show (TransTable _ b _ a l r) = "[(" ++ ushow b- ++ ","- ++ ushow a- ++ ",<function>)"- ++ (if isNullTable l then "" else "," ++ (Prelude.tail. Prelude.init .ushow) l)- ++ (if isNullTable r then "" else "," ++ (Prelude.tail. Prelude.init .ushow) r)- ++ "]"--instance (HatVal n,HatBaseClass b) => Semigroup (TransTable n b) where- (<>) = union- stimes = stimesIdempotentMonoid--instance (HatVal n, HatBaseClass b) => Monoid (TransTable n b) where- mempty = NullTable- mconcat = unions- mappend = (<>)--{-# INLINE union #-}-union ::(HatBaseClass b) => TransTable n b -> TransTable n b -> TransTable n b-union t1 NullTable = t1-union NullTable t2 = t2-union t1 (TransTable _ b f a NullTable NullTable) = insertR b f a t1-union (TransTable _ b f a NullTable NullTable) t2 = insert b f a t2-union t1@(TransTable _ b1 f1 a1 l1 r1) t2 = case split b1 t2 of- (l2, r2) | l1l2 `ptrEq` l1 && r1r2 `ptrEq` r1 -> t1- | otherwise -> link b1 f1 a1 l1l2 r1r2- where !l1l2 = union l1 l2- !r1r2 = union r1 r2--{-# INLINE link #-}-link :: (HatVal n, HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b -> TransTable n b-link kx fx x NullTable r = insertMin kx fx x r-link kx fx x l NullTable = insertMax kx fx x l-link kx fx x l@(TransTable sizeL ky fy y ly ry) r@(TransTable sizeR kz fz z lz rz)- | delta*sizeL < sizeR = balanceL kz fz z (link kx fx x l lz) rz- | delta*sizeR < sizeL = balanceR ky fy y ly (link kx fx x ry r)- | otherwise = bin kx fx x l r--{-# INLINE bin #-}-bin :: (HatVal n, HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b -> TransTable n b-bin k f x l r- = TransTable (size l + size r + 1) k f x l r--{-# INLINE split #-}-split :: (HatBaseClass b) => b -> TransTable n b -> (TransTable n b, TransTable n b)-split !k0 t0 = toPair $ go k0 t0- where- go k t =- case t of- NullTable -> NullTable :*: NullTable- TransTable _ kx fx x l r -> case compareElement k kx of- LT -> let (lt :*: gt) = go k l in lt :*: link kx fx x gt r- GT -> let (lt :*: gt) = go k r in link kx fx x l lt :*: gt- EQ -> (l :*: r)--{-# INLINE insertMax #-}-insertMax,insertMin :: (HatVal n, HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b-insertMax kx fx x t- = case t of- NullTable -> singleton kx fx x- TransTable _ ky fy y l r- -> balanceR ky fy y l (insertMax kx fx x r)--{-# INLINE insertMin #-}-insertMin kx fx x t- = case t of- NullTable -> singleton kx fx x- TransTable _ ky fy y l r- -> balanceL ky fy y (insertMin kx fx x l) r--{-# INLINE unions #-}-unions :: (HatVal n, Foldable f, HatBaseClass b) => f (TransTable n b) -> TransTable n b-unions ts = Foldable.foldl' union NullTable ts--{-# INLINE size #-}-size :: (HatBaseClass b) => TransTable n b -> Size-size NullTable = 0-size (TransTable s _ _ _ _ _) = s----- | transfer--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- Transfer transformation replaces the bases of algebra elements with other bases.--- The values of the transformed entries remain unchanged. For example, given an algebra element a:--- a = 6^ < e1 > +2 < e2 > +2 < e3 > +4 < e4 > +5^ < e5 > and the following transformation definition t:--- ( from) < e1 > -> (to) < eA >--- ( from) < e2 > -> (to) < eA >--- ( from) < e3 > -> (to) < eA >--- The transformation result r is as follows:--- r = 6^ < e1 > +2 < e2 > +2 < e3 > +4 < e4 > +5^ < e5 >--- + 6 < e 1 > + 6 ^ < e A >--- + 2 ^ < e 2 > + 2 < e A >--- + 2 ^ < e 3 > + 2 < e A >--- = 6 ^ < e 1 > + 2 < e 2 > + 2 < e 3 > + 4 < e 4 > + 5 ^ < e 5 >--- = 6 ^ < e 1 > + 2 < e 2 > + 2 < e 3 > + 4 < e 4 > + 5 ^ < e 5 >--- + 6 < e 1 > + 6 ^ < e A > + 2 ^ < e 2 > + 4 < e A > + 2 ^ < e 3 >--------- >>> type Test = Alg Double (HatBase (AccountTitles, CountUnit))--- >>> x = 1:@Hat:<(Cash,Yen) .+ 1:@Not:<(Products,Amount) :: Test--- >>> y = 2:@Not:<(Cash,Yen) .+ 2:@Hat:<(Deposits,Yen) :: Test--- >>> transfer (x .+ y) $ table $ Not:<(Products,Amount) :-> Not:<(Products,Yen) |% id ++ Hat:<(Products,Amount) :-> Hat:<(Products,Yen) |% id--- 1.00:@Hat:<(Cash,Yen) .+ 2.00:@Not:<(Cash,Yen) .+ 2.00:@Hat:<(Deposits,Yen) .+ 1.00:@Not:<(Products,Yen)------ Wildcards match but are not transformed--- >>> type Test = Alg Double (HatBase (AccountTitles, CountUnit))--- >>> x = 1:@Hat:<(Cash,Yen) .+ 1:@Not:<(Products,Amount) :: Test--- >>> y = 2:@Not:<(Cash,Yen) .+ 2:@Hat:<(Deposits,Yen) :: Test--- >>> transfer (x .+ y) $ table $ HatNot:<(Products,Amount) :-> HatNot:<(Products,Yen) |% id--- 1.00:@Hat:<(Cash,Yen) .+ 2.00:@Not:<(Cash,Yen) .+ 2.00:@Hat:<(Deposits,Yen) .+ 1.00:@Not:<(Products,Yen)------ >>> instance Element Int where wildcard = -1--- >>> type Test = Alg Double (HatBase (AccountTitles, Int,CountUnit))--- >>> x = 1:@Hat:<(Cash,(.#),Yen) .+ 1:@Not:<(Products,1,Yen) :: Test--- >>> transfer x $ table $ HatNot:<((.#),(.#),Yen) :-> HatNot:<((.#),(.#),Amount) |% id--- 1.00:@Hat:<(Cash,-1,Amount) .+ 1.00:@Not:<(Products,1,Amount)---data IndexedRule n b- = IndexedUnique !b !(n -> n) !b- | IndexedAmbiguous--data TransferIndex n b = TransferIndex- { tiTree :: !(TransTable n b)- , tiByHatTitle :: !(HM.HashMap (Hat, AccountTitles) (IndexedRule n b))- }--{-# INLINE transferWithResolver #-}-transferWithResolver :: (HatVal n, HatBaseClass b)- => (b -> Maybe ((n -> n), b))- -> Alg n b- -> Alg n b-transferWithResolver resolve = EA.map step- where- step (v :@ hb) = case resolve hb of- Nothing -> v :@ hb- Just (f, hb') ->- let !v' = f v- in if isZeroValue v'- then Zero- else v' :@ hb'- step x = x--{-# INLINE resolveByTree #-}-resolveByTree :: (HatVal n, HatBaseClass b)- => TransTable n b- -> b- -> Maybe ((n -> n), b)-resolveByTree NullTable _ = Nothing-resolveByTree (TransTable _ hb2 f a l r) hb1- | hb1 ./= hb2 = case compareElement hb1 hb2 of- LT -> resolveByTree l hb1- GT -> resolveByTree r hb1- EQ -> error $ "transfer: " ++ show hb1 ++ "," ++ show hb2- | otherwise = Just (f, ignoreWildcard hb1 a)--{-# INLINE ruleEntries #-}-ruleEntries :: TransTable n b -> [(b, n -> n, b)]-ruleEntries NullTable = []-ruleEntries (TransTable _ b f a l r) =- ruleEntries l ++ ((b, f, a) : ruleEntries r)--{-# INLINE baseKey #-}-baseKey :: (ExBaseClass b) => b -> Maybe (Hat, AccountTitles)-baseKey b- | isWildcard h = Nothing- | haveWildcard at = Nothing- | otherwise = Just (h, at)- where- h = hat b- at = getAccountTitle b--buildTransferIndex :: (HatVal n, ExBaseClass b) => TransTable n b -> TransferIndex n b-buildTransferIndex t =- TransferIndex t $- Foldable.foldl' addRule HM.empty (ruleEntries t)- where- addRule acc (before, f, after) =- case baseKey before of- Nothing -> acc- Just k ->- HM.alter- (\entry -> case entry of- Nothing -> Just (IndexedUnique before f after)- Just _ -> Just IndexedAmbiguous- )- k- acc--{-# INLINE resolveByIndex #-}-resolveByIndex :: (HatVal n, ExBaseClass b)- => TransferIndex n b- -> b- -> Maybe ((n -> n), b)-resolveByIndex idx hb =- case baseKey hb >>= (`HM.lookup` tiByHatTitle idx) of- Just (IndexedUnique before f after)- | hb .== before -> Just (f, ignoreWildcard hb after)- | otherwise -> resolveByTree (tiTree idx) hb- _ -> resolveByTree (tiTree idx) hb---- | Legacy rewriting transfer. New code should use--- "ExchangeAlgebra.Algebra.Transfer.Rule" to generate additional entries.--- See the module header for P1-P5 and the mixed-wildcard lookup limitation.-{-# INLINE transfer #-}-transfer :: (HatVal n, HatBaseClass b) => Alg n b -> TransTable n b -> Alg n b-transfer alg NullTable = alg-transfer alg tt = transferWithResolver (resolveByTree tt) alg--{-# INLINE singleton #-}-singleton :: (HatVal n,HatBaseClass b) => b ->(n -> n) -> b -> TransTable n b-singleton before f after = TransTable 1 before f after NullTable NullTable--{-# INLINE insert #-}-insert :: (HatVal n,HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b-insert b = go b b- where- {-# INLINE go #-}- go :: (HatVal n,HatBaseClass b) => b -> b -> (n -> n) -> b -> TransTable n b -> TransTable n b- go orig !_ f x NullTable = singleton (lazy orig) f x- go orig !bx fx x t@(TransTable sy by fy y l r) =- case compareElement bx by of- LT | l' `ptrEq` l -> t- | otherwise -> balanceL by fy y l' r- where !l' = go orig bx fx x l- GT | r' `ptrEq` r -> t- | otherwise -> balanceR by fy y l r'- where !r' = go orig bx fx x r- EQ | x `ptrEq` y && (lazy orig `seq` (orig `ptrEq` by)) -> t- | otherwise -> TransTable sy (lazy orig) fx x l r--{-# INLINE insertR #-}-insertR :: (HatVal n,HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b-insertR kx0 = go kx0 kx0- where- {-# INLINE go #-}- go :: (HatVal n,HatBaseClass b) => b -> b -> (n -> n) -> b -> TransTable n b -> TransTable n b- go orig !_ fx ax NullTable = singleton (lazy orig) fx ax- go orig !bx fx ax t@(TransTable _ by fy ay l r) =- case compareElement bx by of- LT | l' `ptrEq` l -> t- | otherwise -> balanceL by fy ay l' r- where !l' = go orig bx fx ax l- GT | r' `ptrEq` r -> t- | otherwise -> balanceR by fy ay l r'- where !r' = go orig bx fx ax r- EQ -> t---- NB. The unused\/unexported 'updateFunction' helper (update the transformation--- function in the table) was removed as dead code; reintroduce from history if a--- function-update operation on 'TransTable' is needed.--{-# INLINE ptrEq #-}-ptrEq :: a -> a -> Bool-ptrEq x y = isTrue# (reallyUnsafePtrEquality# x y)--delta = 3-ratio = 2--balanceL :: (HatVal n, HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b -> TransTable n b-balanceL b f a l r = case r of- NullTable -> case l of- NullTable -> TransTable 1 b f a NullTable NullTable- (TransTable _ _ _ _ NullTable NullTable)- -> TransTable 2 b f a l NullTable-- (TransTable _ lb lf la NullTable (TransTable _ lrb lrf lra _ _))- -> TransTable 3 lrb lrf lra (TransTable 1 lb lf la NullTable NullTable) (TransTable 1 b f a NullTable NullTable)-- (TransTable _ lb lf la ll@(TransTable _ _ _ _ _ _) NullTable)- -> TransTable 3 lb lf la ll (TransTable 1 b f a NullTable NullTable)-- (TransTable ls lb lf la ll@(TransTable lls _ _ _ _ _) lr@(TransTable lrs lrb lrf lra lrl lrr))- | lrs < ratio*lls -> TransTable (1+ls) lb lf la ll (TransTable (1+lrs) b f a lr NullTable)- | otherwise -> TransTable (1+ls) lrb lrf lra (TransTable (1+lls+size lrl) lb lf la ll lrl) (TransTable (1+size lrr) b f a lrr NullTable)-- (TransTable rs _ _ _ _ _) -> case l of- NullTable -> TransTable (1+rs) b f a NullTable r-- (TransTable ls lb lf la ll lr)- | ls > delta*rs -> case (ll, lr) of- (TransTable lls _ _ _ _ _, TransTable lrs lrb lrf lra lrl lrr)- | lrs < ratio*lls -> TransTable (1+ls+rs) lb lf la ll (TransTable (1+rs+lrs) b f a lr r)- | otherwise -> TransTable (1+ls+rs) lrb lrf lra (TransTable (1+lls+size lrl) lb lf la ll lrl) (TransTable (1+rs+size lrr) b f a lrr r)- (_, _) -> error "Failure in Data.Map.balanceL"- | otherwise -> TransTable (1+ls+rs) b f a l r--balanceR :: (HatVal n, HatBaseClass b) => b -> (n -> n) -> b -> TransTable n b -> TransTable n b -> TransTable n b-balanceR b f a l r = case l of- NullTable -> case r of- NullTable- -> TransTable 1 b f a NullTable NullTable -- Leaf nodes are Null-- (TransTable _ _ _ _ NullTable NullTable)- -> TransTable 2 b f a NullTable r-- (TransTable _ rb rf ra NullTable rr@(TransTable _ _ _ _ _ _))- -> TransTable 3 rb rf ra (TransTable 1 b f a NullTable NullTable) rr-- (TransTable _ rb rf ra (TransTable _ rlb rlf rla _ _) NullTable)- -> TransTable 3 rlb rlf rla (TransTable 1 b f a NullTable NullTable) (TransTable 1 rb rf ra NullTable NullTable)-- (TransTable rs rb rf ra rl@(TransTable rls rlb rlf rla rll rlr) rr@(TransTable rrs _ _ _ _ _))- | rls < ratio*rrs -> TransTable (1+rs) rb rf ra (TransTable (1+rls) b f a NullTable rl) rr- | otherwise -> TransTable (1+rs) rlb rlf rla (TransTable (1+size rll) b f a NullTable rll) (TransTable (1+rrs+size rlr) rb rf ra rlr rr)-- (TransTable ls _ _ _ _ _) -> case r of- NullTable -> TransTable (1+ls) b f a l NullTable-- (TransTable rs rb rf ra rl rr)- | rs > delta*ls -> case (rl, rr) of- (TransTable rls rlb rlf rla rll rlr, TransTable rrs _ _ _ _ _)- | rls < ratio*rrs -> TransTable (1+ls+rs) rb rf ra (TransTable (1+ls+rls) b f a l rl) rr- | otherwise -> TransTable (1+ls+rs) rlb rlf rla (TransTable (1+ls+size rll) b f a l rll) (TransTable (1+rrs+size rlr) rb rf ra rlr rr)- (_, _) -> error "Failure in Data.Map.balanceR"- | otherwise -> TransTable (1+ls+rs) b f a l r---- NB. The unused\/unexported 'lookup' helper (point lookup on 'TransTable') was--- removed as dead code; reintroduce from history if needed.---- | make TransTable from list------ >>> ExchangeAlgebra.Algebra.Transfer.fromList [(Hat:<(Cash),Hat:<(Building),(id :: Double -> Double) ),(Not:<(Building),Not:<(Cash),id)]--- [(Hat:<Cash,Hat:<Building,<function>),(Not:<Building,Not:<Cash,<function>)]--fromList :: (HatVal n, HatBaseClass b) => [(b,b,(n -> n))] -> TransTable n b-fromList [] = NullTable-fromList [(b1,a1, f1)] = a1 `seq` TransTable 1 b1 f1 a1 NullTable NullTable-fromList ((b1,a1, f1) : xs0) | not_ordered b1 xs0 = a1 `seq` fromList' (TransTable 1 b1 f1 a1 NullTable NullTable) xs0- | otherwise = a1 `seq` go (1::Int) (TransTable 1 b1 f1 a1 NullTable NullTable) xs0- where- {-# INLINE not_ordered #-}- not_ordered _ [] = False- not_ordered kx ((ky, _, _) : _) = kx >= ky-- {-# INLINE fromList' #-}- fromList' t0 xs = Foldable.foldl' ins t0 xs- where ins t (k,x,f) = insert k f x t-- {-# INLINE go #-}- go !_ t [] = t- go _ t [(kx, x, fx)] = x `seq` insertMax kx fx x t- go s l xs@((kx, x, fx) : xss) | not_ordered kx xss = fromList' l xs- | otherwise = case create s xss of- (r, ys, []) -> x `seq` go (s `shiftL` 1) (link kx fx x l r) ys- (r, _, ys) -> x `seq` fromList' (link kx fx x l r) ys-- {-# INLINE create #-}- create _ [] = (NullTable, [], [])- create s xs@(xp : xss)- | s == 1 = case xp of (kx, x, fx) | not_ordered kx xss -> x `seq` (TransTable 1 kx fx x NullTable NullTable, [], xss)- | otherwise -> x `seq` (TransTable 1 kx fx x NullTable NullTable, xss, [])- | otherwise = case create (s `shiftR` 1) xs of- res@(_, [], _) -> res- (l, [(ky, y, fy)], zs) -> y `seq` (insertMax ky fy y l, [], zs)- (l, ys@((ky, y, fy):yss), _) | not_ordered ky yss -> (l, [], ys)- | otherwise -> case create (s `shiftR` 1) yss of- (r, zs, ws) -> y `seq` (link ky fy y l r, zs, ws)----- | make TransTable from list--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- same as fromList--- >>> table $ Hat:<(Cash) :-> Hat:<(Building) |% (id :: Double -> Double) ++ Hat:<(Building) :-> Hat:<(Cash) |% id--- [(Hat:<Cash,Hat:<Building,<function>),(Hat:<Building,Hat:<Cash,<function>)]-{-# INLINE table #-}-table :: (HatVal n, HatBaseClass b) => [(b,b,(n -> n))] -> TransTable n b-table = ExchangeAlgebra.Algebra.Transfer.fromList---- | A part of a transfer rule. Represents a source-to-target base pair in @from :-> to@ form.-data TransTableParts b where- (:->) :: (HatBaseClass b) => b -> b -> TransTableParts b---- | Transfer rule construction operator. @from .-> to@ produces a 'TransTableParts'.------ Complexity: O(1)-{-# INLINE (.->) #-}-(.->) :: (HatBaseClass b) => b -> b -> TransTableParts b-(.->) b1 b2 = b1 :-> b2--instance (HatBaseClass b) => Show (TransTableParts b) where- show (b1 :-> b2) = show b1 ++ " :-> " ++ show b2---- | Syntax to make list for makeList------ >>> fmap (\(b, a, _) -> (b, a)) $ Hat:<(Yen,Cash):-> Hat:<(Yen,Building) |% (id :: Double -> Double) ++ Not:<(Yen,Building) :-> Not:<(Yen, Cash) |% id--- [(Hat:<(Yen,Cash),Hat:<(Yen,Building)),(Not:<(Yen,Building),Not:<(Yen,Cash))]-{-# INLINE (|%) #-}-(|%) :: (HatVal n, HatBaseClass b) => TransTableParts b -> (n -> n) -> [(b,b,(n -> n))]-(|%) (b1 :-> b2) f = [(b1,b2,f)]--infixr 8 .->-infixr 8 :->-infixr 7 |%---- NB. A universal @instance (HatVal n) => Show (n -> n)@ used to live here for--- the sake of showing raw rule-list tuples in one doctest. It leaked a function--- Show instance into every downstream module, so it was removed (design-review--- C5); 'TransTable''s own 'Show' prints @<function>@ without it.---- | Build an indexed fast transfer function from a list of transfer rules.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- More efficient than @transfer@ when repeatedly applying the same TransTable.------ Complexity: Build O(r log r) (r = number of rules); apply O(s) (s = number of entries)-createTransfer :: (HatVal n, ExBaseClass b) => [(b,b,(n -> n))] -> (Alg n b -> Alg n b)-createTransfer tt =- let !tb = table tt- !idx = buildTransferIndex tb- in \ts -> transferWithResolver (resolveByIndex idx) ts---- * Closing transfer entries---- | Income Summary Account: compute net income for the current period.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- When the ledger is balanced (credit == debit, net income is zero), @diffRL@--- reports the wildcard v'Side'; in that case the input is returned unchanged--- (balanced ledger = identity; appending a zero posting is not added).--- The result contains a legacy NetIncome/NetLoss balancing coordinate and is--- an intermediate closing state, not input for reporting presentation. New--- reporting code should derive the result from a validated before-closing--- trial balance with "ExchangeAlgebra.Reporting.Metric".--- This legacy API adds an entry whose non-account axes are wildcards: those--- ledger wildcards mean the axes do not apply, rather than matching patterns.-incomeSummaryAccount :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-incomeSummaryAccount alg = let (dc,diff) = diffRL alg- in case dc of- Credit -> alg .+ (diff :@ (toNot wildcard) .~ NetIncome)- Debit -> alg .+ (diff :@ (toNot wildcard) .~ NetLoss)- Side -> alg---- | Net income transfer. Transfers NetIncome/NetLoss to RetainedEarnings.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.------ Complexity: O(s) (s = total number of scalar entries)-netIncomeTransfer :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-netIncomeTransfer = createTransfer- $ (toNot wildcard) .~ NetIncome :-> (toNot wildcard) .~ RetainedEarnings |% id- ++ (toHat wildcard) .~ NetIncome :-> (toHat wildcard) .~ RetainedEarnings |% id- ++ (toNot wildcard) .~ NetLoss :-> (toHat wildcard) .~ RetainedEarnings |% id- ++ (toHat wildcard) .~ NetLoss :-> (toNot wildcard) .~ RetainedEarnings |% id---- ** Journalizing---- | Historical SNA/simulation transfer to the legacy GrossProfit coordinate.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- Consolidates Sales, Purchases, WageExpenditure, Depreciation, and ValueAdded.--- This fixed list is not a JGAAP gross-profit definition: it excludes--- SalesCost and MerchandiseInventory. New statement reporting should use--- "ExchangeAlgebra.Reporting.Metric" and--- "ExchangeAlgebra.Reporting.Presentation" instead.------ Complexity: O(s) (s = total number of scalar entries)-grossProfitTransfer :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-grossProfitTransfer- = createTransfer- $ (toNot wildcard) .~ WageExpenditure :-> (toHat wildcard) .~ GrossProfit |% id- ++ (toHat wildcard) .~ WageExpenditure :-> (toNot wildcard) .~ GrossProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ Depreciation :-> (toHat wildcard) .~ GrossProfit |% id- ++ (toHat wildcard) .~ Depreciation :-> (toNot wildcard) .~ GrossProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ ValueAdded :-> (toNot wildcard) .~ GrossProfit |% id- ++ (toHat wildcard) .~ ValueAdded :-> (toHat wildcard) .~ GrossProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ Sales :-> (toNot wildcard) .~ GrossProfit |% id- ++ (toHat wildcard) .~ Sales :-> (toHat wildcard) .~ GrossProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ Purchases :-> (toHat wildcard) .~ GrossProfit |% id- ++ (toHat wildcard) .~ Purchases :-> (toNot wildcard) .~ GrossProfit |% id---- | Historical SNA/simulation transfer to the legacy OrdinaryProfit--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- coordinate. Its fixed list predates the JCCI vocabulary and is not a--- complete JGAAP ordinary-profit definition. New statement reporting should--- use the typed reporting metric API instead.------ >>> type Test = Alg Double (HatBase (CountUnit, AccountTitles))--- >>> x = 2279.0:@Not:<(Yen,Depreciation) .+ 500475.0:@Not:<(Yen,InterestEarned) :: Test--- >>> ordinaryProfitTransfer x--- 2279.00:@Hat:<(Yen,OrdinaryProfit) .+ 500475.00:@Not:<(Yen,OrdinaryProfit)--ordinaryProfitTransfer :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-ordinaryProfitTransfer- = createTransfer- $ (toNot wildcard) .~ GrossProfit :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ GrossProfit :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ InterestEarned :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ InterestEarned :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ InterestExpense :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ InterestExpense :-> (toNot wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ SubsidyIncome :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ SubsidyIncome :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ TaxesExpense :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ TaxesExpense :-> (toNot wildcard) .~ OrdinaryProfit |% id- -- Government- ++ (toNot wildcard) .~ TaxesRevenue :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ TaxesRevenue :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ CentralBankPaymentIncome :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ CentralBankPaymentIncome :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ Depreciation :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ Depreciation :-> (toNot wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ WageExpenditure :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ WageExpenditure :-> (toNot wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ SubsidyExpense :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ SubsidyExpense :-> (toNot wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- -- Household- ++ (toNot wildcard) .~ WageEarned :-> (toNot wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ WageEarned :-> (toHat wildcard) .~ OrdinaryProfit |% id- ------------------------------------------------------------------- ++ (toNot wildcard) .~ ConsumptionExpenditure :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ ConsumptionExpenditure :-> (toNot wildcard) .~ OrdinaryProfit |% id- -- CentralBank- ++ (toNot wildcard) .~ CentralBankPaymentExpense :-> (toHat wildcard) .~ OrdinaryProfit |% id- ++ (toHat wildcard) .~ CentralBankPaymentExpense :-> (toNot wildcard) .~ OrdinaryProfit |% id----- | Transfer to Retained Earnings.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- Transfers OrdinaryProfit to RetainedEarnings.------ Complexity: O(s) (s = total number of scalar entries)-retainedEarningTransfer :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-retainedEarningTransfer- = createTransfer- $ (toNot wildcard) .~ OrdinaryProfit :-> (toNot wildcard) .~ RetainedEarnings |% id- ++ (toHat wildcard) .~ OrdinaryProfit :-> (toHat wildcard) .~ RetainedEarnings |% id--data FinalStockSide- = FinalStockKeep- | FinalStockFlip---- | Legacy private adapter for the public PIMO-aware closing classification.-{-# INLINE finalStockRule #-}-finalStockRule :: AccountTitles -> Maybe FinalStockSide-finalStockRule title = case closingSide title of- Nothing -> Nothing- Just ClosingKeep -> Just FinalStockKeep- Just ClosingFlip -> Just FinalStockFlip---- | Internal step of the final stock transfer from income statement to retained earnings.--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- Cost accounts are transferred to RetainedEarnings with Hat/Not flipped;--- revenue accounts are transferred to RetainedEarnings as-is. The registry's--- explicit 'NoClose' policy permanently exempts the balancing aggregates--- 'NetIncome'\/'NetLoss': their division encodes the P\/L presentation side,--- so this division-derived rule would invert their transfer sign — their--- closing is owned by the dedicated net-income transfer in this module.------ Complexity: O(s) (s = total number of scalar entries)-{-# INLINE finalStockTransferStep #-}-finalStockTransferStep :: (HatVal n, ExBaseClass b) => Alg n b -> Alg n b-finalStockTransferStep = EA.map go- where- go (v :@ hb) = case finalStockRule (getAccountTitle hb) of- Nothing -> v :@ hb- Just FinalStockKeep ->- v :@ setAccountTitle hb RetainedEarnings- Just FinalStockFlip ->- v :@ setAccountTitle (revHat hb) RetainedEarnings- go x = x+module ExchangeAlgebra.Algebra.Transfer ( TransferScale(Relabel)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , transferEntries+ , collapseEntries+ , collapseNetEntries+ ) where --- | Final Stock Transfer (closing entries).--- Legacy API: new code should use "ExchangeAlgebra.Algebra.Transfer.Rule".--- See the module header for compatibility assumptions P1-P5 and known lookup limitations.--- Transfers registry-eligible cost and revenue accounts to RetainedEarnings--- and cancels via the bar operation.------ Complexity: O(s) (s = total number of scalar entries)-finalStockTransfer ::(HatVal n, ExBaseClass b) => Alg n b -> Alg n b-finalStockTransfer = (.-) . finalStockTransferStep+import ExchangeAlgebra.Algebra.Transfer.Representation
− src/ExchangeAlgebra/Algebra/Transfer/Closing.hs
@@ -1,39 +0,0 @@-{-|-Module : ExchangeAlgebra.Algebra.Transfer.Closing-Description : Shared closing posting construction.--This internal Foundation module constructs a source reversal and destination-posting using the algebra. The transfer rules and Accounting settlement select-the destination side before calling 'closingPairBy'. Read 'closingPairBy' after-the source-side rules in the calling module.--The pair implements the posting construction used in Definition 9.--}-module ExchangeAlgebra.Algebra.Transfer.Closing- ( closingPairBy- ) where--import ExchangeAlgebra.Algebra- ( Alg- , HatVal- , HatBaseClass(..)- , ExBaseClass(..)- , AccountTitles- , Redundant((.+))- , (.@)- )---- | Reverse a closing balance and post it to the given destination account.--- The first argument transforms the source to the target side: @id@ retains--- Hat/Not, and 'revHat' reverses it. The caller must classify the source,--- and the value must satisfy the non-negative,--- finite posting contract of '.@'.-closingPairBy :: (HatVal v, ExBaseClass b)- => (b -> b)- -> AccountTitles- -> v- -> b- -> Alg v b-closingPairBy targetSide targetAccount value source- = (value .@ revHat source)- .+ (value .@ setAccountTitle (targetSide source) targetAccount)
+ src/ExchangeAlgebra/Algebra/Transfer/Representation.hs view
@@ -0,0 +1,292 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Module : ExchangeAlgebra.Algebra.Transfer.Representation+Description : Generic transfer rules and additional entries.++This Algebra layer owns transfer rules, their validation, and coordinate+collapse. It uses generic bases and values and supplies the public+"ExchangeAlgebra.Algebra.Transfer" entry point.+Start with 'mkTransferRules', then 'transferEntries' or the collapse functions.+Definition 9 describes a transfer by adding source cancellations and target+postings: for @Right entries = transferEntries rules ledger@, use+@ledger .+ entries@. Only 'collapseNetEntries' applies @bar@ to rewritten entries.++== Laws++The observation @obs@ is the map from complete bases (including Hat\/Not) to+summed values after @bar@; it does not observe the singleton versus composite+constructor or sequence order. Relative tolerance means a per-base difference+of at most @1e-9 * max (abs x) (abs y)@, treating absent bases as zero.++=== L1: coordinate rewriting++* Subject: 'transferEntries'.+* Preconditions: disjoint source patterns, valid non-negative postings and+ a successful application returning @Right entries@. A source wildcard+ matches any stored coordinate; a concrete source matches only itself.+* Relation: @obs (a .+ entries)@ equals the observation of independently+ rewriting each matched posting to its rule's target and scaled value;+ unmatched postings remain unchanged.+* Observation: @obs@ as defined above, including every target base.+* Tolerance: relative @1e-9@; tested values are integers in @1..1000000@,+ and coefficients are @2@, @3@, @0.5@ and @4@.+* Instances: 'Double' and @MoneyDecimal@ with flat @HatBase@ tuples.++=== L4: canonical form++* Subject: 'mkTransferRules'.+* Preconditions: construction succeeds; the second input permutes the first.+* Relation: both constructions return equal rule sets.+* Observation: 'Eq' of t'TransferRules', or 'rulesToList'.+* Tolerance: exact equality. Errors (including NaN) are outside this law.+* Instances: all lawful 'HatVal' and 'HatBaseClass' instances.++=== L6: coordinate collapse++* Subject: 'collapseEntries' and 'collapseNetEntries'.+* Preconditions: non-negative valid postings and an exact additive value type.+* Relation: @bar (x .+ collapseEntries p f x) ==+ bar (x .+ collapseNetEntries p f x)@. The raw form has twice as many+ postings as @proj p x@ and twice its norm; the net form calls 'bar' after+ rewriting the base parts.+* Observation: net ledger and raw posting count and norm.+* Tolerance: exact.+* Instances: @MoneyDecimal@ with 'HatBaseClass' bases.+-}+module ExchangeAlgebra.Algebra.Transfer.Representation ( TransferScale(..)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , transferEntries+ , collapseEntries+ , collapseNetEntries+ -- * Shared with the accounting transfer+ , matches+ , patternsOverlap+ ) where++import Data.Binary (Binary(..))+import Data.Hashable (Hashable)+import Data.List (find, sortOn, tails)+import GHC.Generics (Generic)+import ExchangeAlgebra.Algebra.Core.Representation (Alg(Single))+import ExchangeAlgebra.Algebra.Core ( Alg(..)+ , Redundant((.+), (.^), bar, norm)+ , (.@)+ , foldEntries+ , mapBasePart+ , proj+ , postFromNetBy+ , vals+ )+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass(..), Hat(..), HatBase(..))+import ExchangeAlgebra.Algebra.Element (CountUnit(..), Element(..), ignoreWildcard)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)++-- | How a rule changes the value it moves.+data TransferScale v+ = Relabel -- ^ Keep the value.+ | MulBy v -- ^ Multiply by a positive finite coefficient.+ | DivBy v -- ^ Divide by a positive finite coefficient.+ deriving (Eq, Ord, Show, Generic)++{-# DEPRECATED MulBy, DivBy "A factor on the destination only does not balance. Use mapPosting to convert units, or Portion of ExchangeAlgebra.Accounting.Transfer to move a share." #-}++instance Binary v => Binary (TransferScale v)++instance Hashable v => Hashable (TransferScale v)++-- | A source pattern, target template and value operation.+data TransferRule v b = TransferRule+ { ruleFrom :: b -- ^ One-way matching pattern.+ , ruleTo :: b -- ^ Wildcards retain source coordinates.+ , ruleScale :: TransferScale v -- ^ Value operation, validated at construction.+ } deriving (Eq, Ord, Show, Generic)++instance (Binary v, Binary b) => Binary (TransferRule v b)++instance (Hashable v, Hashable b) => Hashable (TransferRule v b)++-- | Validated, pairwise-disjoint rules, sorted by source pattern.+-- No 'Semigroup' instance: combine 'rulesToList' values and validate again.+-- Serialize the list of rules and use 'mkTransferRules' after decoding;+-- decoding revalidates the list to preserve this type's invariant.+newtype TransferRules v b = TransferRules [TransferRule v b]+ deriving (Eq, Show, Generic)++instance (HatVal v, HatBaseClass b, Binary v, Binary b) => Binary (TransferRules v b) where+ put = put . rulesToList+ get = do+ rules <- get+ case mkTransferRules rules of+ Left failure -> fail (show failure)+ Right validated -> pure validated++instance (Hashable v, Hashable b) => Hashable (TransferRules v b)++-- | A construction failure. The first invalid coefficient in input order+-- precedes overlap checks; overlaps report the first pair @(i,j)@, @i < j@.+data TransferRuleError v b+ = OverlappingRules (TransferRule v b) (TransferRule v b) -- ^ Including duplicates.+ | InvalidCoefficient (TransferRule v b) -- ^ Zero, negative or non-finite.+ deriving (Eq, Show)++-- | A value operation produced a non-finite (or otherwise invalid) value.+data TransferApplyError v b+ = NonFiniteResult (TransferRule v b) v b -- ^ Rule, source value, source base.+ | NonFiniteBalance b -- ^ Closing base, normalized to Not.+ deriving (Show)++-- | Validate coefficients, reject all overlapping source patterns, and sort.+-- Matching is one-way: a wildcard in the ledger is a value, not a pattern.+-- Overlap uses wildcard substitution recursively, including nested tuples;+-- symmetric wildcard equality is insufficient. Complexity: O(r^2).+mkTransferRules :: (HatVal v, HatBaseClass b)+ => [TransferRule v b]+ -> Either (TransferRuleError v b) (TransferRules v b)+mkTransferRules rules = case find invalidCoefficient rules of+ Just rule -> Left (InvalidCoefficient rule)+ Nothing -> case find overlaps pairs of+ Just (first, second) -> Left (OverlappingRules first second)+ Nothing -> Right (TransferRules (sortOn ruleFrom rules))+ where+ invalidCoefficient rule = case ruleScale rule of+ Relabel -> False+ MulBy coefficient -> invalid coefficient+ DivBy coefficient -> invalid coefficient+ invalid coefficient = coefficient <= zeroValue || isErrorValue coefficient+ pairs = [(first, second) | first : rest <- tails rules, second <- rest]+ overlaps (first, second) =+ patternsOverlap (ruleFrom first) (ruleFrom second)++-- | Extract the canonical list, sorted by 'ruleFrom'.+rulesToList :: TransferRules v b -> [TransferRule v b]+rulesToList (TransferRules rules) = rules++{-# DEPRECATED relabel "Use from .-> to |% Relabel of ExchangeAlgebra.Accounting.Transfer for an accounting transfer, or TransferRule from to Relabel to move coordinates." #-}+{-# DEPRECATED scaleBy, divideBy "A factor on the destination only does not balance. Use mapPosting to convert units, or Portion of ExchangeAlgebra.Accounting.Transfer to move a share." #-}++-- | Construct a value-preserving rule; validate with 'mkTransferRules'.+relabel :: b -> b -> TransferRule v b+relabel source target = TransferRule source target Relabel++-- | Construct a multiplication rule; the coefficient must be positive and finite.+scaleBy :: b -> b -> v -> TransferRule v b+scaleBy source target coefficient = TransferRule source target (MulBy coefficient)++-- | Construct a division rule. Division is not reciprocal multiplication:+-- the latter need not round the same way for floating-point values.+divideBy :: b -> b -> v -> TransferRule v b+divideBy source target coefficient = TransferRule source target (DivBy coefficient)++-- | Match only the pattern's wildcards, preserving literal ledger wildcards.+-- Also used by "ExchangeAlgebra.Accounting.Transfer" on 'BasePart'.+matches :: Element b => b -> b -> Bool+matches patternBase entry = ignoreWildcard entry patternBase == entry++-- | Whether some base matches both patterns, with wildcard substitution per coordinate.+patternsOverlap :: Element b => b -> b -> Bool+patternsOverlap first second = matches first (ignoreWildcard first second)++-- * Additional entries++-- | Generate cancellation and destination entries, without the input ledger.+-- Input values must satisfy the ordinary non-negative, finite posting contract.+-- 'Relabel'-only rules cannot fail. Scaled overflow returns 'NonFiniteResult',+-- and no partial result is returned. Source HatNot postings are unmatched.+-- A relabel to the identical base generates nothing; a zero scaled value+-- generates only the cancellation. No implicit @bar@ or legacy one-to-one+-- map is used. Complexity: O(s*r), with linear rule lookup per posting.+transferEntries :: (HatVal v, HatBaseClass b)+ => TransferRules v b+ -> Alg v b+ -> Either (TransferApplyError v b) (Alg v b)+transferEntries (TransferRules rules) = foldEntries step (Right Zero)+ where+ step result value source = do+ entries <- result+ additions <- apply value source+ pure (entries .+ additions)+ apply value source = case hat source of+ HatNot -> Right Zero+ _ -> case find (\rule -> matches (ruleFrom rule) source) rules of+ Nothing -> Right Zero+ Just rule -> generate rule value source+ generate rule value source+ | ruleScale rule == Relabel && target == source = Right Zero+ | isErrorValue moved = Left (NonFiniteResult rule value source)+ | isZeroValue moved = Right cancellation+ | otherwise = Right (cancellation .+ (moved `Single` target))+ where+ target = ignoreWildcard source (ruleTo rule)+ moved = case ruleScale rule of+ Relabel -> value+ MulBy coefficient -> value * coefficient+ DivBy coefficient -> value / coefficient+ cancellation = value `Single` revHat source++-- | Move selected entries to new base coordinates while retaining every posting.+-- Query patterns use one-way matching: only a pattern wildcard matches any+-- coordinate. The function rewrites each selected 'BasePart' with the supplied+-- function, so callers can replace an axis with its wildcard. A transfer+-- rule's target wildcard instead keeps the source coordinate; it cannot turn+-- a concrete coordinate into a wildcard.+--+-- The result contains only the added entries: a Hat-reversed copy of each+-- selected posting and its rewritten copy. Add it to the ledger with @(.+)@.+-- Values remain non-negative, and this function does not call 'bar', so it+-- retains redundant audit detail. 'collapseNetEntries' nets the rewritten+-- entries instead. 'postFromNetBy' generates new postings for each netted+-- classification; both collapse functions move the coordinates of the same+-- entries.+-- On an axis-preserving ledger, @norm . bar@ cannot cancel across axes.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> x = 10 .@ Not :< Yen .+ 4 .@ Hat :< Dollar :: T+-- >>> let moved = collapseEntries [HatNot :< wildcard] (const wildcard) x+-- >>> norm moved+-- 28.0+-- >>> length (vals moved)+-- 4+collapseEntries :: (HatVal v, HatBaseClass b)+ => [b] -> (BasePart b -> BasePart b) -> Alg v b -> Alg v b+collapseEntries pats f x = (.^) selected .+ mapBasePart f selected+ where+ selected = proj pats x++-- | Move selected entries to new base coordinates and net the rewritten side.+-- The result contains only added entries: a Hat-reversed copy of the selected+-- postings plus @bar (mapBasePart f selected)@. This function calls 'bar'+-- internally after rewriting, so opposite sides from distinct original axes+-- can cancel when the new base parts coincide. Add the result to the original+-- ledger with @(.+)@. It leaves the original audit entries in place.+--+-- Query wildcards match one way. A wildcard in a transfer rule's target+-- preserves the source coordinate; use this function to replace a concrete+-- coordinate with a wildcard. 'collapseEntries' retains all rewritten+-- postings. 'postFromNetBy' generates new postings for each netted+-- classification, while this function moves the coordinates of the same+-- entries. On an axis-preserving ledger, @norm . bar@ does not cancel across+-- axes.+--+-- >>> type T = Alg Double (HatBase CountUnit)+-- >>> x = 10 .@ Not :< Yen .+ 4 .@ Hat :< Dollar :: T+-- >>> norm (bar (x .+ collapseNetEntries [HatNot :< wildcard] (const wildcard) x))+-- 6.0+collapseNetEntries :: (HatVal v, HatBaseClass b)+ => [b] -> (BasePart b -> BasePart b) -> Alg v b -> Alg v b+collapseNetEntries pats f x = (.^) selected .+ bar (mapBasePart f selected)+ where+ selected = proj pats x
src/ExchangeAlgebra/Algebra/Transfer/Rule.hs view
@@ -1,21 +1,19 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}- {- | Module : ExchangeAlgebra.Algebra.Transfer.Rule-Description : Data-defined transfer rules and the entries they generate.+Description : Transfer rules and closing entries. +This compatibility module re-exports generic transfer rules and coordinate collapse.+Use "ExchangeAlgebra.Algebra.Transfer" for generic transfers.+Start with 'mkTransferRules', then 'transferEntries' or the collapse functions.+Closing and settlement names re-export the same entities as+"ExchangeAlgebra.Accounting.Closing". Use that module for closing operations+and their compatibility, accounting balance, and non-negativity laws.+ Definition 9 describes a transfer by adding source cancellations and target postings to the original algebra. For @Right entries = transferEntries rules a@, @a .+ entries@ is precisely that expression. This module returns only the additional entries, preserving the original audit trail. Only-'collapseNetEntries' applies @bar@ to rewritten entries. 'closingEntries'-uses sequential side totals from the source entries, while 'settleEntries'-accepts signed nets already computed by its caller.+'collapseNetEntries' applies @bar@ to rewritten entries. == Laws @@ -24,53 +22,20 @@ constructor or sequence order. Relative tolerance means a per-base difference of at most @1e-9 * max (abs x) (abs y)@, treating absent bases as zero. -=== L1: compatibility+=== L1: coordinate rewriting -* Subject: 'transferEntries' and legacy @transfer@.-* Preconditions: P1, all source patterns have the same wildcard positions;- P2, patterns are disjoint; P3, ledger bases contain no wildcards; P4, axes- are not nested tuples; P5, transformed values are nonzero. The legacy table- translates 'Relabel', 'MulBy' and 'DivBy' to @id@, @(* p)@ and @(/ p)@,- respectively, and applying the new rules returns @Right entries@. P3 is- needed only for equivalence with legacy @transfer@, whose matching is- symmetric. 'transferEntries' matches one way and treats ledger wildcards- as values.-* Relation: @obs (a .+ entries) ~= obs (transfer a table)@.+* Subject: 'transferEntries'.+* Preconditions: disjoint source patterns, valid non-negative postings and+ a successful application returning @Right entries@. A source wildcard+ matches any stored coordinate; a concrete source matches only itself.+* Relation: @obs (a .+ entries)@ equals the observation of independently+ rewriting each matched posting to its rule's target and scaled value;+ unmatched postings remain unchanged. * Observation: @obs@ as defined above, including every target base. * Tolerance: relative @1e-9@; tested values are integers in @1..1000000@, and coefficients are @2@, @3@, @0.5@ and @4@. * Instances: 'Double' and @MoneyDecimal@ with flat @HatBase@ tuples. -=== L2: closing--* Subject: 'closingEntries' and legacy @finalStockTransfer@.-* Preconditions: concrete ledger bases, valid Hat\/Not postings and values- in the L1 range; the ledger includes only entries up to the closing date.- Closing returns @Right entries@. Concrete bases are needed only for- equivalence with legacy @finalStockTransfer@ and its symmetric matching;- 'closingEntries' groups by each actual base, including ledger wildcards- as values.-* Relation: @obs (a .+ entries) ~= obs (finalStockTransfer a)@.-* Observation: @obs@, including 'RetainedEarnings' and all retained axes.-* Tolerance: relative @1e-9@ within the stated range. Large historical- cancellations are deliberately outside this compatibility law.-* Instances: 'Double' and @MoneyDecimal@ with 'ExBaseClass' bases.--=== L3: balance--* Subject: generated transfer and closing entries.-* Preconditions: valid postings, concrete accounts, 'Relabel' rules whose- actual target and source have equal 'whichSide'; finite totals. The- transfer or closing operation being observed returns @Right entries@.-* Relation: debit total equals credit total. Closing entries also satisfy- this relation, because 'closingSide' follows the account's PIMO direction.-* Observation: @norm (decL entries)@ and @norm (decR entries)@.-* Tolerance: relative @1e-9@ for 'Double'; exact for @MoneyDecimal@.-* Instances: 'Double' and @MoneyDecimal@ with 'ExBaseClass' bases.--A relabel from @Not :< Cash@ to @Not :< Sales@ violates L3's side-precondition: its cancellation and destination are both credits.- === L4: canonical form * Subject: 'mkTransferRules'.@@ -80,30 +45,22 @@ * Tolerance: exact equality. Errors (including NaN) are outside this law. * Instances: all lawful 'HatVal' and 'HatBaseClass' instances. -=== L5: non-negativity--* Subject: generated transfer and closing entries.-* Preconditions: non-negative valid input values; the transfer or closing- operation being observed returns @Right entries@.-* Relation: every generated value is greater than or equal to zero.-* Observation: posting values, before any @bar@.-* Tolerance: none.-* Instances: all lawful 'HatVal' and 'HatBaseClass' instances (closing also- requires 'ExBaseClass'). Hat reversal is never numeric negation.- === L6: coordinate collapse * Subject: 'collapseEntries' and 'collapseNetEntries'. * Preconditions: non-negative valid postings and an exact additive value type. * Relation: @bar (x .+ collapseEntries p f x) == bar (x .+ collapseNetEntries p f x)@. The raw form has twice as many- postings as @proj p x@ and twice its norm; the net form calls 'bar' after+ postings as @proj p x@ and twice its norm; the net form calls 'ExchangeAlgebra.Algebra.Core.bar'+ after rewriting the base parts. * Observation: net ledger and raw posting count and norm. * Tolerance: exact. * Instances: @MoneyDecimal@ with 'HatBaseClass' bases. -} module ExchangeAlgebra.Algebra.Transfer.Rule+ {-# DEPRECATED "Use ExchangeAlgebra.Algebra.Transfer for generic transfers and \+ \ExchangeAlgebra.Accounting.Closing for closing entries." #-} ( TransferScale(..) , TransferRule(..) , TransferRules@@ -131,363 +88,32 @@ , settlementSteps ) where -import Data.Binary (Binary(..))-import Data.Hashable (Hashable)-import Data.List (find, sortOn, tails)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Maybe (mapMaybe)-import GHC.Generics (Generic)-import ExchangeAlgebra.Algebra- ( Alg(..), HatVal(..), HatBaseClass(..), ExBaseClass(..)- , Hat(..), HatBase(..), CountUnit(..), Element(..)- , AccountTitles(..), Redundant((.+), (.^), bar, norm)- , (.@)- , ignoreWildcard, foldEntries, mapBasePart, proj- , postFromNetBy, vals- , accountSpec, asClosing, ClosingRule(..)- , classifyAccountDivision, classifyAccountContra- , pimoFromDivision, pimoFlip, PIMO(..) )-import ExchangeAlgebra.Algebra.Transfer.Closing (closingPairBy)---- | How a rule changes the value it moves.-data TransferScale v- = Relabel -- ^ Keep the value.- | MulBy v -- ^ Multiply by a positive finite coefficient.- | DivBy v -- ^ Divide by a positive finite coefficient.- deriving (Eq, Ord, Show, Generic)--instance Binary v => Binary (TransferScale v)--instance Hashable v => Hashable (TransferScale v)---- | A source pattern, target template and value operation.-data TransferRule v b = TransferRule- { ruleFrom :: b -- ^ One-way matching pattern.- , ruleTo :: b -- ^ Wildcards retain source coordinates.- , ruleScale :: TransferScale v -- ^ Value operation, validated at construction.- } deriving (Eq, Ord, Show, Generic)--instance (Binary v, Binary b) => Binary (TransferRule v b)--instance (Hashable v, Hashable b) => Hashable (TransferRule v b)---- | Validated, pairwise-disjoint rules, sorted by source pattern.--- No 'Semigroup' instance: combine 'rulesToList' values and validate again.--- Serialize the list of rules and use 'mkTransferRules' after decoding;--- decoding revalidates the list to preserve this type's invariant.-newtype TransferRules v b = TransferRules [TransferRule v b]- deriving (Eq, Show, Generic)--instance (HatVal v, HatBaseClass b, Binary v, Binary b) => Binary (TransferRules v b) where- put = put . rulesToList- get = do- rules <- get- case mkTransferRules rules of- Left failure -> fail (show failure)- Right validated -> pure validated--instance (Hashable v, Hashable b) => Hashable (TransferRules v b)---- | A construction failure. The first invalid coefficient in input order--- precedes overlap checks; overlaps report the first pair @(i,j)@, @i < j@.-data TransferRuleError v b- = OverlappingRules (TransferRule v b) (TransferRule v b) -- ^ Including duplicates.- | InvalidCoefficient (TransferRule v b) -- ^ Zero, negative or non-finite.- deriving (Eq, Show)---- | A value operation produced a non-finite (or otherwise invalid) value.-data TransferApplyError v b- = NonFiniteResult (TransferRule v b) v b -- ^ Rule, source value, source base.- | NonFiniteBalance b -- ^ Closing base, normalized to Not.- deriving (Show)---- | Validate coefficients, reject all overlapping source patterns, and sort.--- Matching is one-way: a wildcard in the ledger is a value, not a pattern.--- Overlap uses wildcard substitution recursively, including nested tuples;--- symmetric wildcard equality is insufficient. Complexity: O(r^2).-mkTransferRules :: (HatVal v, HatBaseClass b)- => [TransferRule v b]- -> Either (TransferRuleError v b) (TransferRules v b)-mkTransferRules rules = case find invalidCoefficient rules of- Just rule -> Left (InvalidCoefficient rule)- Nothing -> case find overlaps pairs of- Just (first, second) -> Left (OverlappingRules first second)- Nothing -> Right (TransferRules (sortOn ruleFrom rules))- where- invalidCoefficient rule = case ruleScale rule of- Relabel -> False- MulBy coefficient -> invalid coefficient- DivBy coefficient -> invalid coefficient- invalid coefficient = coefficient <= zeroValue || isErrorValue coefficient- pairs = [(first, second) | first : rest <- tails rules, second <- rest]- overlaps (first, second) =- matches (ruleFrom first)- (ignoreWildcard (ruleFrom first) (ruleFrom second))---- | Extract the canonical list, sorted by 'ruleFrom'.-rulesToList :: TransferRules v b -> [TransferRule v b]-rulesToList (TransferRules rules) = rules---- | Construct a value-preserving rule; validate with 'mkTransferRules'.-relabel :: b -> b -> TransferRule v b-relabel source target = TransferRule source target Relabel---- | Construct a multiplication rule; the coefficient must be positive and finite.-scaleBy :: b -> b -> v -> TransferRule v b-scaleBy source target coefficient = TransferRule source target (MulBy coefficient)---- | Construct a division rule. Division is not reciprocal multiplication:--- the latter need not round the same way for floating-point values.-divideBy :: b -> b -> v -> TransferRule v b-divideBy source target coefficient = TransferRule source target (DivBy coefficient)---- | Match only the pattern's wildcards, preserving literal ledger wildcards.-matches :: HatBaseClass b => b -> b -> Bool-matches patternBase entry = ignoreWildcard entry patternBase == entry---- * Additional entries---- | Generate cancellation and destination entries, without the input ledger.--- Input values must satisfy the ordinary non-negative, finite posting contract.--- 'Relabel'-only rules cannot fail. Scaled overflow returns 'NonFiniteResult',--- and no partial result is returned. Source HatNot postings are unmatched.--- A relabel to the identical base generates nothing; a zero scaled value--- generates only the cancellation. No implicit @bar@ or legacy one-to-one--- map is used. Complexity: O(s*r), with linear rule lookup per posting.-transferEntries :: (HatVal v, HatBaseClass b)- => TransferRules v b- -> Alg v b- -> Either (TransferApplyError v b) (Alg v b)-transferEntries (TransferRules rules) = foldEntries step (Right Zero)- where- step result value source = do- entries <- result- additions <- apply value source- pure (entries .+ additions)- apply value source = case hat source of- HatNot -> Right Zero- _ -> case find (\rule -> matches (ruleFrom rule) source) rules of- Nothing -> Right Zero- Just rule -> generate rule value source- generate rule value source- | ruleScale rule == Relabel && target == source = Right Zero- | isErrorValue moved = Left (NonFiniteResult rule value source)- | isZeroValue moved = Right cancellation- | otherwise = Right (cancellation .+ (moved :@ target))- where- target = ignoreWildcard source (ruleTo rule)- moved = case ruleScale rule of- Relabel -> value- MulBy coefficient -> value * coefficient- DivBy coefficient -> value / coefficient- cancellation = value :@ revHat source---- | Move selected entries to new base coordinates while retaining every posting.--- Query patterns use one-way matching: only a pattern wildcard matches any--- coordinate. The function rewrites each selected 'BasePart' with the supplied--- function, so callers can replace an axis with its wildcard. A transfer--- rule's target wildcard instead keeps the source coordinate; it cannot turn--- a concrete coordinate into a wildcard.------ The result contains only the added entries: a Hat-reversed copy of each--- selected posting and its rewritten copy. Add it to the ledger with @(.+)@.--- Values remain non-negative, and this function does not call 'bar', so it--- retains redundant audit detail. 'collapseNetEntries' nets the rewritten--- entries instead. 'postFromNetBy' generates new postings for each netted--- classification; both collapse functions move the coordinates of the same--- entries.--- On an axis-preserving ledger, @norm . bar@ cannot cancel across axes.------ >>> type T = Alg Double (HatBase CountUnit)--- >>> x = 10 .@ Not :< Yen .+ 4 .@ Hat :< Dollar :: T--- >>> let moved = collapseEntries [HatNot :< wildcard] (const wildcard) x--- >>> norm moved--- 28.0--- >>> length (vals moved)--- 4-collapseEntries :: (HatVal v, HatBaseClass b)- => [b] -> (BasePart b -> BasePart b) -> Alg v b -> Alg v b-collapseEntries pats f x = (.^) selected .+ mapBasePart f selected- where- selected = proj pats x---- | Move selected entries to new base coordinates and net the rewritten side.--- The result contains only added entries: a Hat-reversed copy of the selected--- postings plus @bar (mapBasePart f selected)@. This function calls 'bar'--- internally after rewriting, so opposite sides from distinct original axes--- can cancel when the new base parts coincide. Add the result to the original--- ledger with @(.+)@. It leaves the original audit entries in place.------ Query wildcards match one way. A wildcard in a transfer rule's target--- preserves the source coordinate; use this function to replace a concrete--- coordinate with a wildcard. 'collapseEntries' retains all rewritten--- postings. 'postFromNetBy' generates new postings for each netted--- classification, while this function moves the coordinates of the same--- entries. On an axis-preserving ledger, @norm . bar@ does not cancel across--- axes.------ >>> type T = Alg Double (HatBase CountUnit)--- >>> x = 10 .@ Not :< Yen .+ 4 .@ Hat :< Dollar :: T--- >>> norm (bar (x .+ collapseNetEntries [HatNot :< wildcard] (const wildcard) x))--- 6.0-collapseNetEntries :: (HatVal v, HatBaseClass b)- => [b] -> (BasePart b -> BasePart b) -> Alg v b -> Alg v b-collapseNetEntries pats f x = (.^) selected .+ bar (mapBasePart f selected)- where- selected = proj pats x---- * Closing entries---- | The retained-earnings side selected by a closing account's PIMO direction.-data ClosingSide- = ClosingKeep -- ^ IN: retain Hat/Not (revenue or contra cost).- | ClosingFlip -- ^ OUT: reverse Hat/Not (cost or contra revenue).- deriving (Eq, Ord, Show, Enum, Bounded, Generic)--instance Binary ClosingSide--instance Hashable ClosingSide---- | Public closing classification used by the legacy final stock transfer.--- Registry NoClose accounts, including NetIncome and NetLoss, are excluded.-closingSide :: AccountTitles -> Maybe ClosingSide-closingSide title = case accountSpec title of- Nothing -> Nothing- Just spec -> case asClosing spec of- NoClose -> Nothing- CloseByDivision -> case direction of- IN -> Just ClosingKeep- OUT -> Just ClosingFlip- _ -> Nothing- where- direction- | classifyAccountContra title = pimoFlip ordinaryDirection- | otherwise = ordinaryDirection- ordinaryDirection = pimoFromDivision (classifyAccountDivision title)---- | Generate closing entries from each eligible base's sequential side totals.--- Supply only postings through the closing date. Hat and Not totals are--- compared without a tolerance; their non-negative difference is closed.--- This deliberately folds the source sequences (audit detail) per base,--- but preserves separate target postings from distinct source accounts.--- It does not call @bar@: small rounding residues can become closing entries.--- All non-account axes are retained, and no note is attached.--- Input values must satisfy the ordinary non-negative, finite posting contract.--- Finite inputs normally yield 'Right'; 'Left' occurs only when a side's sum--- exceeds the value type's range. Both side totals and their difference are--- checked before generating postings, even when both totals compare equal.--- 'NonFiniteBalance' reports the first base in ascending 'Ord' order after--- normalizing Hat/Not to Not. No partial result or split balance is returned.--- Balance collection is O(s log b), for s postings and b eligible bases;--- output construction uses the ordinary algebra addition operation.-closingEntries :: (HatVal v, ExBaseClass b)- => Alg v b -> Either (TransferApplyError v b) (Alg v b)-closingEntries = Map.foldlWithKey' close (Right Zero) . foldEntries collect Map.empty- where- collect balances value source = case (hat source, closingSide (getAccountTitle source)) of- (Hat, Just _) -> Map.insertWith addTotals (toNot source) (value, zeroValue) balances- (Not, Just _) -> Map.insertWith addTotals source (zeroValue, value) balances- _ -> balances- addTotals (hatValue, notValue) (hatTotal, notTotal) =- (hatValue + hatTotal, notValue + notTotal)- close result source (hatTotal, notTotal) = do- entries <- result- additions <- netEntries source hatTotal notTotal- pure (entries .+ additions)- netEntries source hatTotal notTotal- | isErrorValue hatTotal || isErrorValue notTotal = Left (NonFiniteBalance source)- | hatTotal == notTotal = Right Zero- | hatTotal > notTotal = checkedPair source (hatTotal - notTotal) (toHat source)- | otherwise = checkedPair source (notTotal - hatTotal) source- checkedPair source value balanceBase- | isErrorValue value = Left (NonFiniteBalance source)- | otherwise = case closingSide (getAccountTitle balanceBase) of- Nothing -> Right Zero- Just side -> Right- (closingPairBy (targetSide side) RetainedEarnings value balanceBase)- targetSide side = case side of- ClosingKeep -> id- ClosingFlip -> revHat---- | A closing rule containing only its destination account title.--- The private constructor restricts destinations to accounts compatible with--- the closing directions.-newtype SettleRule = SettleRule AccountTitles---- | Close eligible accounts into 'RetainedEarnings', preserving all other axes.-retainedEarningsRule :: SettleRule-retainedEarningsRule = SettleRule RetainedEarnings---- | Settlement pairs in strictly ascending source-base order.--- A model returns only @Posting@ built with @entry@ and 'Monoid'. Settlement--- magnitudes can exceed the @Posted@ bound, so this type has no conversion to--- @Posting@ and no 'Semigroup' or 'Monoid' instance.------ Record each pair separately in source-base order. Combining all pairs first--- can change the order of additions to a shared destination. For example,--- sequential increments @T, 1, -T@ with @T = 2^53@ give 0 in 'Double', whereas--- adding @T, -T, 1@ gives 1.-newtype SettlementBatch b = SettlementBatch [(BasePart b, Alg Double b)]---- | A signed Not-minus-Hat net. This is not a non-negative posting magnitude.--- As a type synonym, it does not enforce finiteness or any numeric range.-type SignedNet = Double---- | The first non-finite input net, identified by its complete base coordinates.-data SettleError b = NonFiniteNet (BasePart b)--deriving instance Eq (BasePart b) => Eq (SettleError b)-deriving instance Show (BasePart b) => Show (SettleError b)+import ExchangeAlgebra.Algebra.Transfer.Representation ( TransferScale(..)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , transferEntries+ , collapseEntries+ , collapseNetEntries+ ) --- | Construct one reversal and destination pair per eligible source base.--- Input values are signed Not-minus-Hat nets. Zero nets, accounts without a--- closing side, and destination bases produce no pair. Every pair has two--- finite, non-negative magnitudes equal to the absolute input net; the source--- reversal cancels that net exactly. Other base axes are preserved.------ A non-finite input, including at an excluded key, returns 'Left' with the--- first key in ascending order. Finite magnitudes above--- 'ExchangeAlgebra.Posting.postedUpperBound' are accepted, without passing--- through 'ExchangeAlgebra.Posting.posted'. No implicit @bar@ or @compress@ is--- applied. Complexity: O(b) for b input bases.--- Law: subject: each generated settlement pair; preconditions: all nets finite.--- Relation: each reversal cancels its source net. For each destination base,--- its increment is the sum of @direction * net@ over the source bases, where--- @direction@ is +1 for 'ClosingKeep' and -1 for 'ClosingFlip'.--- Observation: sums of @decL@ and @decR@ lifted to Rational for each pair.--- Tolerance: exact. Instances: 'ExBaseClass' bases with Double posting values.-settleEntries :: forall b. ExBaseClass b- => SettleRule- -> Map (BasePart b) SignedNet- -> Either (SettleError b) (SettlementBatch b)-settleEntries (SettleRule destination) amounts- = case mapMaybe nonFinite (Map.toAscList amounts) of- first : _ -> Left (NonFiniteNet first)- [] -> Right (SettlementBatch (mapMaybe close (Map.toAscList amounts)))- where- finite amount = not (isNaN amount || isInfinite amount)- nonFinite (coordinates, amount)- | finite amount = Nothing- | otherwise = Just coordinates- close (coordinates, amount)- | amount == 0 = Nothing- | coordinates == base (setAccountTitle source destination) = Nothing- | otherwise = case closingSide (getAccountTitle source) of- Nothing -> Nothing- Just side -> Just- (coordinates, closingPairBy (targetSide side) destination (abs amount) source)- where- source = merge (sourceSide amount) coordinates :: b- sourceSide amount- | amount < 0 = Hat- | otherwise = Not- targetSide side = case side of- ClosingKeep -> id- ClosingFlip -> revHat+import ExchangeAlgebra.Accounting.Closing ( ClosingSide(..)+ , closingSide+ , closingEntries+ , SettleRule+ , retainedEarningsRule+ , SettlementBatch+ , SignedNet+ , SettleError(..)+ , settleEntries+ , settlementSteps+ ) --- | Read the pairs in strictly ascending source-base order, without constraints--- on the base type. Record each pair before proceeding to the next source.--- Complexity: O(1) to expose the list; O(b) to consume b pairs.-settlementSteps :: SettlementBatch b -> [(BasePart b, Alg Double b)]-settlementSteps (SettlementBatch steps) = steps+import ExchangeAlgebra.Algebra.Value.Class (HatVal)+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass)
+ src/ExchangeAlgebra/Algebra/Value.hs view
@@ -0,0 +1,494 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DerivingStrategies #-}++{- |+ Module : ExchangeAlgebra.Algebra.Value+ Copyright : (c) Kaya Akagi. 2018-2026+ Maintainer : yakagika@icloud.com++ Released under the OWL license++ Exact, non-negative decimal value type t'MoneyDecimal' for use as the @v@+ parameter of @Alg v b@ / @Journal n v b@.++ == Why this exists (DESIGN, 2026-06-06)++ The accounting value type is selectable:++ * @Double@ — fast IEEE-754; the default. Addition is /non-associative/,+ so the order in which same-base postings are summed (which depends on how a+ value was /constructed/) can change @norm@ / @bar@ results. In the+ agent-based simulations this manifested as a ~3% swing in a stock value when+ the construction order changed (see plans/in-progress/LAZY_EVAL_AUDIT.md and+ FP_SUMMATION_SURVEY.md). This is acceptable for relative-price ABM work where+ speed matters, but it is not deterministic/auditable.++ * @MoneyDecimal@ — exact base-10 fixed-point (a non-negative 'Data.Decimal').+ Addition is /exact and associative/, so results are independent of+ construction order: the fromList fold direction, parallel merges, etc. no+ longer change the answer. This is the right choice for audited ledgers and+ for making the construction-order optimizations (fromList O(N)) safe.++ @Integer@ (minimal-currency-unit) is intentionally NOT offered: ABM simulations+ use relative prices with base unit 1 and sub-unit fractional prices, which an+ integer cannot represent.++ == Ergonomics++ Numeric literals work without wrapping, because 'Num'/'Fractional' are derived:++ > import ExchangeAlgebra.Algebra+ > import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+ >+ > entry :: Alg MoneyDecimal (HatBase CountUnit)+ > entry = 10.5 .@ Hat :< Amount .+ 2 .@ Not :< Yen++ == Rounding++ The core algebra only adds/subtracts, which is exact for t'MoneyDecimal' and needs no+ rounding. Rounding is only needed by /multiplication and division/ (tax ratios,+ proration, scalar product) at the point a monetary amount is /finalized/. Use+ 'bankersRound': it rounds half-to-even (the unbiased financial default; also GHC's+ 'Prelude.round' and IEEE-754's default mode). A ceiling variant ('ceilingRound') is+ provided for jurisdictions whose+ rules differ. There is no single correct rule (e.g. Japanese consumption tax rounding+ varies by company), so the rounding function is explicit and swappable.+-}+module ExchangeAlgebra.Algebra.Value+ ( Nearly(..)+ , isNearlyNum+ , HatVal(..)+ , ValueError(..)+ , isErrorValue+ , Rounding(..)+ , Tolerance(..)+ , defaultTolerance+ -- * Sums+ , sumValues+ , roundScaled+ -- * Accumulators for user-defined value types+ , RationalAccum(..)+ , rationalAddAccum+ , rationalRoundAccum+ , MoneyDecimal(..)+ , toDecimal+ , bankersRound+ , ceilingRound+ -- * Parsing+ , MoneyParseError(..)+ , moneyDecimalFromText+ , moneyDecimalFromScientific+ -- * Fast floating-point value+ , MoneyDouble(..)+ , toDouble+ ) where++import ExchangeAlgebra.Algebra.Value.Class ( HatVal (..)+ , ValueError (..)+ , isErrorValue+ , Nearly (..)+ , Rounding (..)+ , Tolerance (..)+ , defaultTolerance+ , isNearlyNum+ , sumValues+ , roundScaled+ )+import Data.Decimal (Decimal, DecimalRaw (Decimal), normalizeDecimal, roundTo')+import Data.Ratio (numerator, denominator)+import Data.Word (Word8)+import Data.Scientific (Scientific, coefficient, base10Exponent)+import qualified Data.Scientific as Scientific+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Hashable (Hashable (..))+import Control.DeepSeq (NFData (..))+import qualified Data.Binary as Binary++-- | A non-negative exact decimal value (wraps 'Data.Decimal.Decimal').+--+-- Non-negativity is a /soft/ invariant, the same as for the @Double@ instance:+-- it is not enforced by the constructor (intermediate subtraction inside @bar@/@(.-)@+-- can produce negatives), but 'isErrorValue' reports @x < 0@ so the @(.@)@ smart+-- constructor rejects negative postings.+newtype MoneyDecimal = MoneyDecimal Decimal+ -- Num/Fractional are derived so numeric literals (@10.5@, @0.08@) work directly,+ -- with no @MoneyDecimal@ wrapper at use sites. Show/Eq/Ord delegate to t'Decimal'.+ -- 'Real' (and thus 'toRational') is derived so values can be converted to/from+ -- @Double@ via 'realToFrac' at the simulation boundary: ABM parameters, input+ -- coefficients and random draws stay 'Double', and are converted to t'MoneyDecimal'+ -- only where they enter a ledger; final stock/profit amounts convert back for+ -- reporting. The ledger arithmetic in between is exact.+ deriving newtype (Eq, Ord, Show, Num, Fractional, Real)++-- | Project out the underlying t'Decimal'.+toDecimal :: MoneyDecimal -> Decimal+toDecimal (MoneyDecimal d) = d++-- * Parsing++-- | A rejected money input. The constructors are stable failure categories.+data MoneyParseError+ = MalformedMoney -- ^ The text is not a JSON number.+ | NegativeMoney -- ^ The coefficient is negative and nonzero.+ | MoneyExponentOutOfRange -- ^ The exponent is outside [-10000, 10000].+ | MoneyScaleOutOfRange -- ^ The decimal scale exceeds 255 places.+ deriving (Eq, Show)++-- | The parts of a fully validated JSON number, before numeric conversion.+data ParsedMoney = ParsedMoney+ { parsedNegative :: Bool+ , parsedIntegerDigits :: Text+ , parsedFractionDigits :: Text+ , parsedExponentMinus :: Bool+ , parsedExponentDigits :: Text+ }++-- | Check the complete JSON number grammar using ASCII digits only.+parseMoneySyntax :: Text -> Maybe ParsedMoney+parseMoneySyntax input = do+ let (negative, unsigned) = case Text.uncons input of+ Just ('-', rest) -> (True, rest)+ _ -> (False, input)+ (integerDigits, afterInteger) <- case Text.uncons unsigned of+ Just ('0', rest) -> Just (Text.singleton '0', rest)+ Just (digit, rest) | isMoneyDigitNonzero digit ->+ let (digits, suffix) = Text.span isMoneyDigit rest+ in Just (Text.cons digit digits, suffix)+ _ -> Nothing+ (fractionDigits, afterFraction) <- case Text.uncons afterInteger of+ Just ('.', rest) -> case Text.span isMoneyDigit rest of+ (digits, _) | Text.null digits -> Nothing+ result -> Just result+ _ -> Just (Text.empty, afterInteger)+ (exponentMinus, exponentDigits) <- case Text.uncons afterFraction of+ Just ('e', rest) -> parseExponent rest+ Just ('E', rest) -> parseExponent rest+ Nothing -> Just (False, Text.singleton '0')+ _ -> Nothing+ pure ParsedMoney+ { parsedNegative = negative+ , parsedIntegerDigits = integerDigits+ , parsedFractionDigits = fractionDigits+ , parsedExponentMinus = exponentMinus+ , parsedExponentDigits = exponentDigits+ }+ where+ parseExponent :: Text -> Maybe (Bool, Text)+ parseExponent exponent+ | Text.null digits = Nothing+ | Text.null suffix = Just (minus, digits)+ | otherwise = Nothing+ where+ (minus, unsigned) = case Text.uncons exponent of+ Just ('-', rest) -> (True, rest)+ Just ('+', rest) -> (False, rest)+ _ -> (False, exponent)+ (digits, suffix) = Text.span isMoneyDigit unsigned++-- | Recognize a JSON decimal digit without accepting Unicode numerals.+isMoneyDigit :: Char -> Bool+isMoneyDigit digit = digit >= '0' && digit <= '9'++-- | Recognize a nonzero JSON digit at the start of an integer part.+isMoneyDigitNonzero :: Char -> Bool+isMoneyDigitNonzero digit = digit >= '1' && digit <= '9'++-- | Decode an exponent only after its nonzero digits pass the length bound.+boundedExponent :: Text -> Either MoneyParseError Integer+boundedExponent digits+ | Text.length significant > 5 = Left MoneyExponentOutOfRange+ | Text.length significant == 5 && significant > Text.pack "10000" = Left MoneyExponentOutOfRange+ | otherwise = Right (decimalDigits significant)+ where+ significant = Text.dropWhile (== '0') digits++-- | Convert already validated decimal digits without a binary float.+decimalDigits :: Text -> Integer+decimalDigits = Text.foldl' (\value digit -> value * 10 + toInteger (fromEnum digit - fromEnum '0')) 0++-- | Construct an exact decimal after the exponent and scale checks.+moneyFromParts :: Integer -> Integer -> Either MoneyParseError MoneyDecimal+moneyFromParts mantissa places+ | places > 255 = Left MoneyScaleOutOfRange+ | places >= 0 = Right (MoneyDecimal (Decimal (fromInteger places) mantissa))+ | otherwise = Right (MoneyDecimal (Decimal 0 (mantissa * 10 ^ negate places)))++-- | Parse a JSON number as an exact, non-negative money value.+--+-- The input has no surrounding whitespace. The result retains the written+-- decimal places and never rounds. Failures follow the priority in this table:+--+-- +---------------------+----------------------------------------------------++-- | Rule | Result |+-- +=====================+====================================================++-- | Text syntax | Optional @-@; @0@ or @[1-9][0-9]*@; optional |+-- | | @.@ and digits; optional exponent with @e@ or |+-- | | @E@, optional sign, and digits. Each present |+-- | | digit group is nonempty. |+-- | | Whitespace, @+1@, @01@, @.5@, @5.@, @NaN@, |+-- | | @Infinity@, and separators are malformed. |+-- +---------------------+----------------------------------------------------++-- | Negative value | A nonzero negative returns 'NegativeMoney'. |+-- | | @-0@ and @-0.00@ are accepted as zero. |+-- +---------------------+----------------------------------------------------++-- | Exponent | Magnitudes over 10000 return |+-- | | 'MoneyExponentOutOfRange'. |+-- +---------------------+----------------------------------------------------++-- | Scale | More than 255 decimal places return |+-- | | 'MoneyScaleOutOfRange'. |+-- +---------------------+----------------------------------------------------++-- | Exact value | Digits form the coefficient; scale is fractional |+-- | | digits minus exponent. Negative scale multiplies |+-- | | the coefficient by a power of ten. |+-- +---------------------+----------------------------------------------------++-- | Written scale | Trailing zeroes are retained: @1.50@ has scale 2 |+-- | | and mantissa 150. No normalization or rounding. |+-- +---------------------+----------------------------------------------------++-- | Failure priority | Syntax, negative value, exponent, then scale. |+-- +---------------------+----------------------------------------------------++-- | Scientific input | The raw coefficient and exponent determine scale |+-- | | and acceptance; syntax failure does not apply. |+-- +---------------------+----------------------------------------------------++--+-- Lexical scanning is linear in the input length. Converting an unbounded+-- coefficient to 'Integer' and multiplying large integers have separate costs.+--+-- >>> moneyDecimalFromText (Text.pack "19.99")+-- Right 19.99+-- >>> moneyDecimalFromText (Text.pack "1.50")+-- Right 1.50+moneyDecimalFromText :: Text -> Either MoneyParseError MoneyDecimal+moneyDecimalFromText source = do+ parsed <- maybe (Left MalformedMoney) Right (parseMoneySyntax source)+ let digits = parsedIntegerDigits parsed <> parsedFractionDigits parsed+ if parsedNegative parsed && Text.any (/= '0') digits+ then Left NegativeMoney+ else do+ magnitude <- boundedExponent (parsedExponentDigits parsed)+ let exponent+ | parsedExponentMinus parsed = negate magnitude+ | otherwise = magnitude+ places = toInteger (Text.length (parsedFractionDigits parsed)) - exponent+ if places > 255+ then Left MoneyScaleOutOfRange+ else moneyFromParts (decimalDigits digits) places++-- | Convert a 'Scientific' representation to an exact money value.+--+-- The raw 'coefficient' and 'base10Exponent' determine the result. Equal+-- numerical values can have different scales or acceptance results when their+-- representations differ. Negative nonzero coefficients fail first; an+-- exponent outside [-10000, 10000] fails next; a scale above 255 fails last.+-- Zero follows the same exponent and scale checks. This function never returns+-- 'MalformedMoney' and never rounds.+--+-- >>> moneyDecimalFromScientific (Scientific.scientific 1999 (-2))+-- Right 19.99+moneyDecimalFromScientific :: Scientific -> Either MoneyParseError MoneyDecimal+moneyDecimalFromScientific value+ | mantissa < 0 = Left NegativeMoney+ | exponent < -10000 || exponent > 10000 = Left MoneyExponentOutOfRange+ | otherwise = moneyFromParts mantissa (negate (toInteger exponent))+ where+ mantissa = coefficient value+ exponent = base10Exponent value++-- 'Nearly': for an exact type there is no rounding noise to tolerate, so the+-- tolerance argument is ignored and equality is exact. (Contrast the @Double@+-- instance, which uses a scale-aware tolerance.)+instance Nearly MoneyDecimal where+ {-# INLINE isNearly #-}+ isNearly x y _ = x == y++instance HatVal MoneyDecimal where+ {-# INLINE zeroValue #-}+ zeroValue = MoneyDecimal 0+ -- Exact decimals have no NaN/Infinity; the only invalid value is a negative+ -- amount, which violates the non-negativity invariant of the algebra. No+ -- posting bound: sums never overflow.+ {-# INLINE valueError #-}+ valueError (MoneyDecimal x)+ | x < 0 = Just NegativeValue+ | otherwise = Nothing+ -- Render exactly (e.g. "0.3", "12.34"); unlike the Double instance there is no+ -- fixed-2-decimal formatting, because the decimal value is already exact.+ {-# INLINE showValue #-}+ showValue (MoneyDecimal x) = show x++ -- Decimal addition is exact, so the state is the signed exact sum.+ newtype Accum MoneyDecimal = MoneyDecimalAccum Decimal+ emptyAccum = MoneyDecimalAccum 0+ addAccum (MoneyDecimal value) (MoneyDecimalAccum total) =+ MoneyDecimalAccum (total + value)+ mergeAccum (MoneyDecimalAccum left) (MoneyDecimalAccum right) =+ MoneyDecimalAccum (left + right)+ negateAccum (MoneyDecimalAccum value) = MoneyDecimalAccum (negate value)+ signAccum (MoneyDecimalAccum value) = Just (compare value 0)+ roundAccum (MoneyDecimalAccum value) = MoneyDecimal value+ splitAccum (MoneyDecimalAccum value)+ | value > 0 = Just [MoneyDecimal value]+ | otherwise = Nothing+ exactAccum (MoneyDecimalAccum value) = Just (toRational value)+ roundRational Nearest value = represent 0 (numerator value) (denominator value)+ where+ -- Each step supplies one decimal place without rounding.+ represent places coefficient divisor+ | divisor == 1 = Just (MoneyDecimal (Decimal places coefficient))+ | places == 255 || common == 1 = Nothing+ | otherwise = represent (places + 1)+ (coefficient * (10 `div` common)) (divisor `div` common)+ where+ common = gcd divisor 10+ roundRational (ToMultipleOf (MoneyDecimal unit)) value+ | unit > 0 = Just (MoneyDecimal (fromInteger (round (value / toRational unit)) * unit))+ | otherwise = Nothing++-- 'Binary'/'Hashable' are defined here (not orphan) because 'Data.Decimal' ships+-- neither, and 'Alg'/t'Journal' serialization and the binary spill path require+-- @Binary v@. 'Binary' keeps the (places, mantissa) structure of t'Decimal'.+-- 'Hashable' hashes the normalized form (trailing zeros removed), so values that+-- are equal under 'Eq' (@1.0 == 1.00@) have the same hash.+instance Binary.Binary MoneyDecimal where+ {-# INLINE put #-}+ put (MoneyDecimal (Decimal places mantissa)) = do+ Binary.put (places :: Word8)+ Binary.put (mantissa :: Integer)+ {-# INLINE get #-}+ get = do+ places <- Binary.get :: Binary.Get Word8+ mantissa <- Binary.get :: Binary.Get Integer+ pure (MoneyDecimal (Decimal places mantissa))++instance Hashable MoneyDecimal where+ {-# INLINE hashWithSalt #-}+ hashWithSalt s (MoneyDecimal d) = case normalizeDecimal d of+ Decimal places mantissa -> s `hashWithSalt` places `hashWithSalt` mantissa++instance NFData MoneyDecimal where+ {-# INLINE rnf #-}+ rnf (MoneyDecimal (Decimal places mantissa)) = rnf places `seq` rnf mantissa++-- * Fast floating-point value++-- | A fast IEEE-754 money value (wraps 'Prelude.Double').+--+-- This is the @newtype@ counterpart of the bare-@Double@ instance: a dedicated,+-- domain-specific money type so a ledger value cannot be silently confused with+-- an ABM coefficient, a random draw, or any other raw 'Double'. Every instance+-- it needs is owned here (via @deriving newtype@), so — exactly like+-- t'MoneyDecimal' — there are no orphan instances. Use t'MoneyDouble' for the same+-- speed as bare 'Double' while keeping the value type distinct in signatures.+--+-- Trade-off vs t'MoneyDecimal': addition is /non-associative/ (FP), so @norm@ \/+-- @bar@ can differ in the last ULP depending on construction order. It is fast+-- and runs everywhere bare 'Double' does (subtraction is signed, so the+-- intermediate negatives that arise inside @bar@\/@(.-)@ are fine — unlike+-- @Number.NonNegative.Double@, whose @(-)@ /errors/ on a negative result).+--+-- Non-negativity is the same /soft/ invariant as for t'MoneyDecimal' and bare+-- 'Double': not enforced by the constructor, but 'isErrorValue' reports+-- @isNaN x || isInfinite x || x < 0@, so the @(.\@)@ smart constructor and+-- @(.*)@ reject negative\/non-finite values.+newtype MoneyDouble = MoneyDouble Double+ -- All instances are coerced from the existing bare-'Double' instances+ -- ('Nearly'/'HatVal' live in "ExchangeAlgebra.Algebra"; 'Binary'/'Hashable'/+ -- 'NFData' come from the binary/hashable/deepseq packages), so t'MoneyDouble'+ -- is a zero-cost wrapper with identical numeric behavior and 2-decimal+ -- 'showValue' formatting.+ deriving newtype ( Eq, Ord, Show, Num, Fractional, Real, RealFrac+ , Nearly, Hashable, NFData, Binary.Binary )++-- | Delegates to the 'Prelude.Double' accumulator. Written by hand because the+-- associated data family 'Accum' cannot be derived through a newtype.+instance HatVal MoneyDouble where+ zeroValue = MoneyDouble zeroValue+ isZeroValue (MoneyDouble x) = isZeroValue x+ valueError (MoneyDouble x) = valueError x+ postingUpperBound = fmap MoneyDouble postingUpperBound+ showValue (MoneyDouble x) = showValue x+ newtype Accum MoneyDouble = MoneyDoubleAccum (Accum Double)+ emptyAccum = MoneyDoubleAccum emptyAccum+ addAccum (MoneyDouble x) (MoneyDoubleAccum a) = MoneyDoubleAccum (addAccum x a)+ mergeAccum (MoneyDoubleAccum a) (MoneyDoubleAccum b) = MoneyDoubleAccum (mergeAccum a b)+ negateAccum (MoneyDoubleAccum a) = MoneyDoubleAccum (negateAccum a)+ signAccum (MoneyDoubleAccum a) = signAccum a+ roundAccum (MoneyDoubleAccum a) = MoneyDouble (roundAccum a)+ splitAccum (MoneyDoubleAccum a) = fmap (map MoneyDouble) (splitAccum a)+ exactAccum (MoneyDoubleAccum a) = exactAccum a+ roundRational rule q = fmap MoneyDouble (roundRational (fmap' rule) q)+ where+ fmap' Nearest = Nearest+ fmap' (ToMultipleOf (MoneyDouble u)) = ToMultipleOf u++-- | Project out the underlying 'Prelude.Double'.+toDouble :: MoneyDouble -> Double+toDouble (MoneyDouble d) = d++-- | Round a value to @n@ decimal places using /banker's rounding/+-- (round-half-to-even): the unbiased financial default. Ties go to the nearest+-- even digit (@2.5 -> 2@, @3.5 -> 4@, @0.125 -> 0.12@), so repeated rounding over+-- many transactions does not drift the total upward the way half-up does. This is+-- 'Prelude.round' applied per 'Data.Decimal.roundTo''.+--+-- Apply at the point a monetary amount is finalized after multiplication/division+-- (tax, proration, scalar product). The core algebra (add/subtract) is exact and+-- needs no rounding.+bankersRound :: Word8 -> MoneyDecimal -> MoneyDecimal+bankersRound places (MoneyDecimal d) = MoneyDecimal (roundTo' round places d)++-- | Round a value to @n@ decimal places by rounding /up/ (ceiling).+-- This suits jurisdictions whose rules round up.+-- Prefer 'bankersRound' unless a ceiling rule is specifically required.+ceilingRound :: Word8 -> MoneyDecimal -> MoneyDecimal+ceilingRound places (MoneyDecimal d) = MoneyDecimal (roundTo' ceiling places d)++-- * Accumulators for user-defined value types++-- | Exact rational state for a user-defined value type without special values+-- (no NaN or infinity). A complete instance for an exact amount of yen+-- (the type name avoids the unit constructor @Yen@):+--+-- > newtype JPY = JPY Rational+-- > deriving newtype (Eq, Ord, Show, Num, Fractional, Real)+-- >+-- > instance HatVal JPY where+-- > zeroValue = 0+-- > valueError (JPY x)+-- > | x < 0 = Just NegativeValue+-- > | otherwise = Nothing+-- > showValue (JPY x) = show (fromRational x :: Double)+-- >+-- > newtype Accum JPY = JPYAccum RationalAccum+-- > emptyAccum = JPYAccum (RationalAccum 0)+-- > addAccum x (JPYAccum a) = JPYAccum (rationalAddAccum x a)+-- > mergeAccum (JPYAccum (RationalAccum a)) (JPYAccum (RationalAccum b)) = JPYAccum (RationalAccum (a + b))+-- > negateAccum (JPYAccum (RationalAccum a)) = JPYAccum (RationalAccum (negate a))+-- > signAccum (JPYAccum (RationalAccum a)) = Just (compare a 0)+-- > roundAccum (JPYAccum a) = rationalRoundAccum JPY a+-- > splitAccum (JPYAccum (RationalAccum a))+-- > | a > 0 = Just [JPY a]+-- > | otherwise = Nothing+-- > exactAccum (JPYAccum (RationalAccum a)) = Just a+-- > roundRational Nearest q = Just (JPY q)+-- > roundRational (ToMultipleOf (JPY u)) q+-- > | u > 0 = Just (JPY (u * fromInteger (round (q / u))))+-- > | otherwise = Nothing+--+-- Every value of such a type is finite, so laws A3 and A6 hold by construction;+-- the instance still has to satisfy A5' ('splitAccum' succeeds on every positive+-- state) and T1-T3. 'Prelude.round' rounds ties to even.+newtype RationalAccum = RationalAccum Rational+ deriving (Eq, Ord, Show)++-- | Add a finite value of a 'Real' type exactly.+rationalAddAccum :: Real n => n -> RationalAccum -> RationalAccum+rationalAddAccum value (RationalAccum total) = RationalAccum (total + toRational value)++-- | Round the exact value with a function the user supplies (for example+-- 'fromRational'). 'Real' alone cannot guarantee ties-to-even, so the rule is+-- the caller's.+rationalRoundAccum :: (Rational -> n) -> RationalAccum -> n+rationalRoundAccum convert (RationalAccum total) = convert total+
+ src/ExchangeAlgebra/Algebra/Value/Accum.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}++{-|+Module : ExchangeAlgebra.Algebra.Value.Accum+Description : Internal exact accumulator for IEEE binary64 values.++Internal module of the Algebra layer. It holds the exact signed-sum state+behind the 'ExchangeAlgebra.Algebra.Value.Class.Accum' instances of+'Double' and @MoneyDouble@. Users never name+'FloatingState'; they use the t'ExchangeAlgebra.Algebra.Value.HatVal'+methods. The module is not exposed: the boundary tests in+@test/Algebra/AccumSpec.hs@ observe the state only through the public+t'ExchangeAlgebra.Algebra.Value.HatVal' methods ('ExchangeAlgebra.Algebra.Value.exactAccum',+'ExchangeAlgebra.Algebra.Value.signAccum', 'ExchangeAlgebra.Algebra.Value.splitAccum',+'ExchangeAlgebra.Algebra.Value.roundAccum').++The module deliberately does /not/ enable @{-\# LANGUAGE Strict \#-}@: the+cold paths (residual capture, overflow into the integer-coefficient state)+are bound in @where@ clauses and must stay lazy so that the common in-range+addition does not allocate an 'Integer'. Its users,+"ExchangeAlgebra.Algebra.Value.Class" and+"ExchangeAlgebra.Algebra.Core.Representation", are compiled with @Strict@ and+only delegate here.++Reading order: 'FloatingState', then the operations in the order of the+t'ExchangeAlgebra.Algebra.Value.HatVal' methods.+-}+module ExchangeAlgebra.Algebra.Value.Accum+ ( -- * State+ FloatingState+ -- * Operations (one per accumulator method)+ , emptyFloating+ , addFloating+ , mergeFloating+ , negateFloating+ , signFloating+ , roundFloating+ , splitFloating+ , exactFloating+ , roundRationalDouble+ ) where++import Data.Bits (shiftL, shiftR)+import Data.Ratio ((%))+import GHC.Float (castDoubleToWord64, castWord64ToDouble)+import GHC.Exts (Int(I#), addIntC#)+import GHC.Num.Integer (integerLog2)++-- * State++-- | Exact signed sum of finite binary64 inputs, or an IEEE special value.+--+-- Invariant: a finite state represents the exact rational sum of its inputs,+-- including sums outside the binary64 range (S0 contract R5). The+-- representation (scalar, two-term expansion, integer coefficient with a+-- variable exponent, special value) is chosen by the implementation.+data FloatingState+ = Integral {-# UNPACK #-} !Int+ | Scalar {-# UNPACK #-} !Double+ | Expansion {-# UNPACK #-} !Double {-# UNPACK #-} !Double+ | Dyadic !Integer !Int+ | Special {-# UNPACK #-} !Double+++-- * Operations++-- | The state of the empty sum (value 0).+emptyFloating :: FloatingState+emptyFloating = Integral 0++-- | Normalize both signs of zero without changing any other value.+positiveZero :: Double -> Double+positiveZero value+ | value == 0 = 0+ | otherwise = value++-- | Form an exact dyadic sum, aligning integer coefficients at the smaller exponent.+addDyadic :: (Integer, Int) -> (Integer, Int) -> FloatingState+addDyadic (left, leftPower) (right, rightPower)+ | left == 0 = Dyadic right rightPower+ | right == 0 = Dyadic left leftPower+ | otherwise = Dyadic+ ((left `shiftL` (leftPower - power)) + (right `shiftL` (rightPower - power))) power+ where+ power = min leftPower rightPower++-- | Error-free two-sum with magnitude ordering. The caller handles overflow before+-- using the residual; gradual underflow preserves the identity for subnormal inputs.+twoSum :: Double -> Double -> (# Double, Double #)+{-# INLINE twoSum #-}+twoSum left right+ | abs left >= abs right = (# high, right - (high - left) #)+ | otherwise = (# high, left - (high - right) #)+ where+ high = left + right++-- | Store two exact components, collapsing an exactly zero low component.+expansion :: Double -> Double -> FloatingState+{-# INLINE expansion #-}+expansion high low+ | low == 0 = Scalar high+ | otherwise = Expansion high low++-- | Error-free scalar addition, retaining the low part until final observation.+-- Invariant: the left input is finite. A finite sum also proves the right input finite.+addScalars :: Double -> Double -> FloatingState+{-# INLINE addScalars #-}+addScalars left right = case twoSum left right of+ (# high, low #)+ | isNaN high || isInfinite high -> exceptional+ | otherwise -> expansion high low+ where+ exceptional+ | isNaN right || isInfinite right = Special right+ | otherwise = addDyadic (decodeFloat left) (decodeFloat right)++-- | Recover all three original components at one common exponent on the cold path.+-- Keeping this conversion separate leaves the floating update free of recursive calls.+addWideExpansion :: Double -> Double -> Double -> FloatingState+addWideExpansion value high low = Dyadic coefficient power+ where+ (valueCoefficient, valuePower) = decodeFloat value+ (highCoefficient, highPower) = decodeFloat high+ (lowCoefficient, lowPower) = decodeFloat low+ power = min valuePower (min highPower lowPower)+ coefficient = (valueCoefficient `shiftL` (valuePower - power))+ + (highCoefficient `shiftL` (highPower - power))+ + (lowCoefficient `shiftL` (lowPower - power))++-- | Add machine integers exactly, widening before signed overflow can lose a bit.+addIntegral :: Int -> Int -> FloatingState+{-# INLINE addIntegral #-}+addIntegral left@(I# first) right@(I# second) = case addIntC# first second of+ (# result, 0# #) -> Integral (I# result)+ _ -> Dyadic (toInteger left + toInteger right) 0++-- | Leave the integral fast path without rounding a large machine integer.+fromIntegralState :: Int -> FloatingState+fromIntegralState value+ | abs exact <= 9007199254740992 = Scalar (fromIntegral value)+ | otherwise = Dyadic exact 0+ where+ exact = toInteger value++-- | Classify an integer input by a safe machine conversion and an exact round trip.+-- The upper bound is exclusive because maxBound rounds upward on 64-bit machines.+-- A fractional or larger value leaves this fast path, preserving the existing total.+addIntegralValue :: Double -> Int -> FloatingState+{-# INLINE addIntegralValue #-}+addIntegralValue value total+ | value >= lower && value < upper && fromIntegral integral == value =+ addIntegral total integral+ | isNaN value || isInfinite value = Special value+ | abs (toInteger total) <= 9007199254740992 = addScalars (fromIntegral total) value+ | otherwise = addDyadic (toInteger total, 0) (decodeFloat value)+ where+ lower = fromIntegral (minBound :: Int)+ upper = negate lower+ integral = truncate value++-- | Add to two exact components, deferring their normalization until observation.+-- For finite inputs, a third residual or either overflow retains all values in Dyadic.+addExpansion :: Double -> Double -> Double -> FloatingState+{-# INLINE addExpansion #-}+addExpansion value high low = case twoSum high value of+ (# upper, errorValue #)+ | isNaN upper || isInfinite upper -> exceptional+ | otherwise -> case twoSum low errorValue of+ (# lower, remainder #)+ | isInfinite lower || remainder /= 0 -> fallback+ | otherwise -> expansion upper lower+ where+ exceptional+ | isNaN value || isInfinite value = Special value+ | otherwise = fallback+ fallback = addWideExpansion value high low++-- | Keep finite overflow exact and distinguish it from nonfinite inputs.+addFloating :: Double -> FloatingState -> FloatingState+{-# INLINE addFloating #-}+addFloating value (Integral total) = addIntegralValue value total+addFloating value (Scalar total) = addScalars total value+addFloating value (Expansion high low) = addExpansion value high low+addFloating value (Dyadic total power)+ | isNaN value || isInfinite value = Special value+ | otherwise = addDyadic (total, power) (decodeFloat value)+addFloating value state@(Special previous)+ | isNaN value || isInfinite value = Special (value + previous)+ | otherwise = state++-- | Merge finite dyadics exactly; special values follow IEEE addition.+mergeFloating :: FloatingState -> FloatingState -> FloatingState+mergeFloating (Special left) (Special right) = Special (left + right)+mergeFloating special@(Special _) _ = special+mergeFloating _ special@(Special _) = special+mergeFloating (Integral left) (Integral right) = addIntegral left right+mergeFloating (Integral left) right = mergeFloating (fromIntegralState left) right+mergeFloating left (Integral right) = mergeFloating left (fromIntegralState right)+mergeFloating (Expansion high low) right = addFloating low (addFloating high right)+mergeFloating left (Expansion high low) = addFloating low (addFloating high left)+mergeFloating (Scalar left) (Scalar right) = addScalars left right+mergeFloating (Scalar left) (Dyadic right power) = addDyadic (decodeFloat left) (right, power)+mergeFloating (Dyadic left power) (Scalar right) = addDyadic (left, power) (decodeFloat right)+mergeFloating (Dyadic left first) (Dyadic right second) = addDyadic (left, first) (right, second)++-- | Negation never narrows a finite state.+negateFloating :: FloatingState -> FloatingState+negateFloating (Integral value)+ | value == minBound = Dyadic (negate (toInteger value)) 0+ | otherwise = Integral (negate value)+negateFloating (Scalar value) = Scalar (negate value)+negateFloating (Expansion high low) = Expansion (negate high) (negate low)+negateFloating (Dyadic value power) = Dyadic (negate value) power+negateFloating (Special value) = Special (negate value)++-- | Inspect the exact sign, including beyond the Double range. The sum of two finite+-- binary64 components is a multiple of the smallest subnormal, so rounding their sum+-- cannot turn a nonzero value into zero; overflow also preserves its sign.+signFloating :: FloatingState -> Maybe Ordering+signFloating (Integral value) = Just (compare value 0)+signFloating (Scalar value) = Just (compare value 0)+signFloating (Expansion high low) = Just (compare (high + low) 0)+signFloating (Dyadic value _) = Just (compare value 0)+signFloating (Special value)+ | isNaN value = Nothing+ | otherwise = Just (compare value 0)++-- | Round a dyadic integer explicitly to 53 significant bits (or subnormal units).+-- encodeFloat truncates large Integer significands, so it receives only an already+-- rounded, exactly representable significand here. The final exponent may overflow.+roundDyadic :: Integer -> Int -> Double+roundDyadic 0 _ = 0+roundDyadic coefficient power+ | coefficient < 0 = negate (roundDyadic (negate coefficient) power)+ | otherwise = encodeFloat rounded (power + places)+ where+ highest = fromIntegral (integerLog2 coefficient)+ places = max 0 (max (highest - 52) (-1074 - power))+ whole = coefficient `shiftR` places+ remainder = coefficient - (whole `shiftL` places)+ halfwayComparison = compare (remainder * 2) (1 `shiftL` places)+ rounded+ | halfwayComparison == GT = whole + 1+ | halfwayComparison == EQ && odd whole = whole + 1+ | otherwise = whole++-- | Final nearest-even observation, normalizing either sign of zero.+roundFloating :: FloatingState -> Double+roundFloating (Integral value) = fromIntegral value+roundFloating (Scalar value) = positiveZero value+roundFloating (Expansion high low) = positiveZero (high + low)+roundFloating (Dyadic value power) = positiveZero (roundDyadic value power)+roundFloating (Special value) = value++-- | Greedily split a positive exact state. The predecessor correction prevents+-- a nearest-even result above the exact residual from making the next residual negative.+splitFloating :: FloatingState -> Maybe [Double]+splitFloating (Special _) = Nothing+splitFloating state+ | signFloating state /= Just GT = Nothing+ | isInfinite (roundFloating state) = Nothing+ | otherwise = Just (go state)+ where+ go remaining+ | signFloating remaining == Just EQ = []+ | otherwise = component : go (addFloating (negate component) remaining)+ where+ rounded = roundFloating remaining+ excess = addFloating (negate rounded) remaining+ component+ | signFloating excess == Just LT =+ castWord64ToDouble (castDoubleToWord64 rounded - 1)+ | otherwise = rounded++-- | The exact value of a finite state; 'Nothing' for a special value.+exactFloating :: FloatingState -> Maybe Rational+exactFloating (Integral value) = Just (fromIntegral value)+exactFloating (Scalar value) = Just (toRational value)+exactFloating (Expansion high low) = Just (toRational high + toRational low)+exactFloating (Dyadic value power)+ | power >= 0 = Just ((value `shiftL` power) % 1)+ | otherwise = Just (value % (1 `shiftL` negate power))+exactFloating (Special _) = Nothing++-- | Round a rational once to binary64 (to nearest, ties to even);+-- 'Nothing' when the result would be infinite.+roundRationalDouble :: Rational -> Maybe Double+roundRationalDouble value+ | isInfinite rounded = Nothing+ | otherwise = Just rounded+ where+ rounded = fromRational value
+ src/ExchangeAlgebra/Algebra/Value/Class.hs view
@@ -0,0 +1,361 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Strict #-}++-- | Value classes and primitive numeric instances.+module ExchangeAlgebra.Algebra.Value.Class+ ( Nearly(..), isNearlyNum, nearlyEqScaled+ , HatVal(..)+ , ValueError(..)+ , isErrorValue+ , Rounding(..)+ , Tolerance(..), defaultTolerance+ , sumValues+ , roundScaled ) where++import Data.Maybe (isJust)+import qualified Data.Foldable as Foldable+import Control.DeepSeq (NFData(..))+import qualified Data.Scientific as D (fromFloatDigits, formatScientific, FPFormat(..))+import ExchangeAlgebra.Algebra.Value.Accum+ ( FloatingState, emptyFloating, addFloating, mergeFloating, negateFloating+ , signFloating, roundFloating, splitFloating, exactFloating, roundRationalDouble )++------------------------------------------------------------------+-- * Approximate equality+------------------------------------------------------------------++-- | Type class providing approximate equality for numeric values.+-- Performs equality comparison with tolerance for floating-point rounding errors.+class (Eq a, Ord a) => Nearly a where+ -- | @isNearly x y t@ : Returns True if the difference between x and y is within the tolerance t.+ -- Complexity: O(1)+ isNearly :: a -> a -> a -> Bool++instance Nearly Int where+ {-# INLINE isNearly #-}+ isNearly = isNearlyNum++instance Nearly Integer where+ {-# INLINE isNearly #-}+ isNearly = isNearlyNum++instance Nearly Float where+ {-# INLINE isNearly #-}+ isNearly = isNearlyNum++instance Nearly Double where+ {-# INLINE isNearly #-}+ isNearly = isNearlyNum++{-# INLINE isNearlyNum #-}+-- | Complexity: O(1)+-- Assumes primitive numeric operations and comparisons are constant time.+--+-- NOTE: this is an /absolute/-tolerance test (@|x - y| <= |t|@); it does not+-- scale with magnitude. For large values, rounding error easily exceeds a small+-- fixed @t@, while for small values it can swallow a real residual.+-- 'nearlyEqScaled' is a private helper; public comparisons use 'isNearlyNum'+-- or an explicit t'Tolerance'. The final guard+-- returns 'False' (was: 'error') when a NaN makes every ordered comparison fail,+-- so a non-finite input can no longer crash the check.+isNearlyNum :: (Show a, Num a, Ord a) => a -> a -> a -> Bool+isNearlyNum x y t+ | x == y = True+ | x > y = abs (x - y) <= abs t+ | x < y = abs (y - x) <= abs t+ | otherwise = False -- NaN: not nearly-equal to anything++{-# INLINE nearlyEqScaled #-}+-- | Scale-aware approximate equality for accounting reconciliation:+--+-- @|x - y| <= atol + rtol * max |x| |y|@, with @atol = 1e-13@, @rtol = 1e-12@.+--+-- The absolute floor @atol@ handles values near zero; the relative term @rtol@+-- lets the threshold track magnitude, so the test stays meaningful for large+-- balances (where a fixed @1e-13@ was far too strict and retained pure rounding+-- noise as a spurious residual). Returns 'False' if either argument is a+-- non-finite error value (NaN/Inf), so error values never read as nearly equal.+--+-- Complexity: O(1)+--+-- >>> nearlyEqScaled (1e10 + 0.1 + 0.2) (1e10 + 0.3 :: Double)+-- True+-- >>> nearlyEqScaled (0.1 + 0.2) (0.3 :: Double)+-- True+-- >>> nearlyEqScaled (1e10 + 5.0) (1e10 :: Double)+-- False+-- >>> nearlyEqScaled (0/0) (1.0 :: Double)+-- False+nearlyEqScaled :: (HatVal n) => n -> n -> Bool+nearlyEqScaled x y+ | isErrorValue x || isErrorValue y = False+ | otherwise = abs (x - y) <= atol + rtol * max (abs x) (abs y)+ where+ atol = 1e-13+ rtol = 1e-12++------------------------------------------------------------------+-- * Algebra+------------------------------------------------------------------++-- | Type class for algebra element values.+-- Provides zero-value / error-value predicates and a representation-specific+-- renderer ('showValue').+--+-- == Choosing an instance+--+-- * 'Prelude.Double' — fast IEEE-754 (this module); the low-friction default.+-- * @MoneyDouble@ ("ExchangeAlgebra.Algebra.Value") — same speed, dedicated money newtype.+-- * @MoneyDecimal@ ("ExchangeAlgebra.Algebra.Value") — exact decimal, construction-order+-- independent totals; use for audited\/deterministic ledgers.+--+-- DESIGN NOTE (2026-06-06, selectable value type — Double vs exact Decimal):+-- The @RealFloat@ superclass was intentionally *removed* so that exact,+-- non-floating-point value types (the planned @MoneyDecimal@ = non-negative+-- 'Data.Decimal.Decimal') can be 'HatVal' instances and give construction-order+-- -independent, exact summation. @RealFloat@ was only ever needed in two places:+-- * @showV@ (rendering via 'Data.Scientific.fromFloatDigits') — now replaced by+-- the per-instance 'showValue' method, so each representation formats itself;+-- * the @Double@ 'isErrorValue' (NaN/Infinity tests) — these stay+-- inside the floating-point instances, which may require @RealFloat@ locally.+-- @Fractional@ is *kept*: 'Data.Decimal' provides it (so numeric literals like+-- @0.08@ still work without wrapping), and only an @Integer@ instance would need+-- it dropped. @Integer@ is intentionally out of scope — it cannot represent the+-- fractional / relative prices that the ABM simulations depend on.+class ( Show n+ , Ord n+ , Eq n+ , Nearly n+ , Fractional n+ , Num n) => HatVal n where++ -- | Zero value. Complexity: O(1)+ zeroValue :: n++ -- | Tests whether the value is zero. Complexity: O(1)+ isZeroValue :: n -> Bool+ isZeroValue x+ | zeroValue == x = True+ | otherwise = False++ -- | Why a value cannot be posted, or 'Nothing' for a valid value. This is+ -- the one check shared by 'ExchangeAlgebra.Algebra.Core..@', @posted@ and+ -- 'isErrorValue' (0.6.0.0;+ -- replaces the @isErrorValue@ method).+ --+ -- __Laws__ (relation; premise; observed through '=='; tolerance none;+ -- all instances):+ --+ -- * V1. @valueError zeroValue == Nothing@.+ -- * V2. @valueError x == Nothing@ implies @x >= zeroValue@ and+ -- @exactAccum (addAccum x emptyAccum) /= Nothing@ (a valid value is+ -- finite and non-negative).+ -- * V3. NaN and both infinities give @Just NonFiniteValue@ (checked+ -- first, so @-inf@ is non-finite, not negative); a finite value below+ -- 'zeroValue' gives @Just NegativeValue@. A type without special values+ -- never returns @Just NonFiniteValue@.+ --+ -- Complexity: O(1)+ valueError :: n -> Maybe ValueError++ -- | The largest value @posted@ accepts, or 'Nothing' for no bound (the+ -- default). Floating-point types set @2^900@: the exact sum of fewer than+ -- @2^123@ such values still rounds to a finite value, so readouts of+ -- posted values stay finite. Exact types have no overflow and keep+ -- 'Nothing'.+ --+ -- __Law__ B1 (premise: @postingUpperBound == Just u@): @u > zeroValue@ and+ -- @valueError u == Nothing@.+ postingUpperBound :: Maybe n+ postingUpperBound = Nothing++ -- | Render the value for the 'Show' instance of t'ExchangeAlgebra.Algebra.Core.Alg'.+ -- Per-instance because formatting is representation-specific: floating-point+ -- types format to a fixed number of decimal places via 'Data.Scientific',+ -- whereas exact decimal types print their own canonical form. This replaces+ -- the former floating-point-only @showV@, which hard-wired @RealFloat@+ -- through @fromFloatDigits@ and so blocked exact value types.+ showValue :: n -> String++ -- ** Exact accumulator (0.6.0.0)+ --+ -- The readouts of "ExchangeAlgebra.Algebra" ('ExchangeAlgebra.Algebra.Core.bar',+ -- 'ExchangeAlgebra.Algebra.Core.norm', ...) sum the postings they are given in+ -- this accumulator and round each output once.+ --+ -- __Laws__ (each item: subject; premise; relation; observed through+ -- 'exactAccum'; tolerance none; instances 'Double', @MoneyDouble@,+ -- @MoneyDecimal@ and every user instance):+ --+ -- * A1. 'mergeAccum' is commutative and associative with unit 'emptyAccum';+ -- premise: states built from finite inputs.+ -- * A2. @addAccum x a@ and @mergeAccum a (addAccum x emptyAccum)@ have the+ -- same value; premise: finite @x@.+ -- * A3. 'negateAccum' is an involution and @mergeAccum a (negateAccum a)@+ -- has value 0; premise: finite state.+ -- * A4. @roundAccum (addAccum x emptyAccum) == x@; premise: finite @x@;+ -- observed through '==' of the value type.+ -- * A5. @splitAccum a == Just xs@ implies the exact sum of @xs@ is the value+ -- of @a@, each element is finite and positive, and @xs@ depends only on+ -- the value of @a@; premise: none.+ -- * A5'. @splitAccum a@ is 'Just' for every state built from finite inputs+ -- whose value is positive and whose 'roundAccum' is finite (for example+ -- @M + s@ with @M@ the largest finite 'Double' gives @Just [M, s]@), and+ -- 'Nothing' otherwise.+ -- * A6. Special values combine as IEEE addition (NaN absorbs,+ -- @+inf + -inf = NaN@, @+inf + finite = +inf@); 'roundAccum' returns the+ -- special value and 'signAccum' returns 'Nothing' for NaN. The readouts+ -- rely on A3 and A6 to tell a non-finite base from a cancelled one, so a+ -- user instance must satisfy them.+ -- * E1. @exactAccum (foldr addAccum emptyAccum xs) == Just (sum (map toRational xs))@;+ -- premise: every element finite.+ -- * E2. For a finite state whose rounding is finite,+ -- @Just (roundAccum a) == (exactAccum a >>= roundRational Nearest)@;+ -- instances: floating-point types.+ --+ -- 'roundAccum' is not an additive homomorphism for floating-point types.++ -- | Accumulator state: the exact signed sum of the inputs, or a special value.+ data Accum n++ -- | The empty sum (value 0). Complexity: O(1)+ emptyAccum :: Accum n++ -- | Add one input. Signed finite values are accepted (negative values are+ -- outside the posting contract but are summed exactly). Complexity: O(1) amortized+ addAccum :: n -> Accum n -> Accum n++ -- | Add two states. Complexity: O(1) on the fast path+ mergeAccum :: Accum n -> Accum n -> Accum n++ -- | Negate a state; @mergeAccum a (negateAccum b)@ is the difference. Complexity: O(1)+ negateAccum :: Accum n -> Accum n++ -- | Compare the exact value with 0; 'Nothing' for NaN. Complexity: O(1)+ signAccum :: Accum n -> Maybe Ordering++ -- | Round the exact value once (to nearest, ties to even). Complexity: O(1) on the fast path+ roundAccum :: Accum n -> n++ -- | Split a positive state into finite positive values whose exact sum is the+ -- state's value: repeatedly take the largest representable value not above+ -- the remainder. 'Nothing' exactly when the state is non-finite, its value+ -- is not positive, or its 'roundAccum' is infinite (laws A5, A5'). Used by+ -- closing entries to close a base exactly.+ splitAccum :: Accum n -> Maybe [n]++ -- | The exact value of a finite state; 'Nothing' for a special value.+ -- Used to scale a balance by a ratio before rounding once.+ exactAccum :: Accum n -> Maybe Rational++ -- | Round a rational once under the given rule; 'Nothing' when the rule+ -- cannot produce a finite value of the type (see 'Rounding').+ --+ -- __Laws__ (relation; premise; observed through '=='; tolerance none):+ --+ -- * T1. @roundRational (ToMultipleOf u) q == Just y@ implies @y@ is an integer+ -- multiple of @u@, @|y - q| <= u/2@, and @y/u@ is even on a tie;+ -- premise: @u@ positive and finite; instances: exact types.+ -- * T2. For @MoneyDecimal@, @roundRational Nearest q@ is 'Just' exactly when+ -- the reduced denominator of @q@ is @2^a * 5^b@ with @max a b <= 255@+ -- (the decimal places the type can hold).+ -- * T3. If @q@ is a value of the type, @roundRational Nearest q == Just q@,+ -- and @roundRational (ToMultipleOf u) q == Just q@ for an exact type when+ -- @q@ is a multiple of @u@.+ -- * T5. Floating-point types return 'Nothing' for every 'ToMultipleOf'+ -- (they round to the nearest value only, once).+ -- * T4. For 'Double', @roundRational Nearest q == Just (fromRational q)@+ -- when the result is finite.+ roundRational :: Rounding n -> Rational -> Maybe n++-- | Why a value is invalid, in the order 'valueError' checks.+data ValueError+ = NonFiniteValue -- ^ NaN or either infinity.+ | NegativeValue -- ^ A finite value below zero.+ deriving (Eq, Show)++instance NFData ValueError where+ rnf x = seq x ()++-- | @isErrorValue = isJust . valueError@ (0.6.0.0: a function, no longer a+-- method, so that the two cannot disagree). Complexity: O(1)+isErrorValue :: HatVal n => n -> Bool+isErrorValue = isJust . valueError+{-# INLINE isErrorValue #-}++-- | How 'roundRational' rounds a rational to a value type. Ties go to even.+--+-- A ratio transfer (@from .-> to |% Portion n d@) multiplies the exact balance+-- by @n/d@ and rounds once with the rule the caller passes.+data Rounding n+ = Nearest+ -- ^ The nearest value of the type. Floating-point types round to nearest+ -- (ties to even); an exact decimal type returns 'Nothing' when the exact+ -- amount is not representable (for example @1/3@).+ | ToMultipleOf n+ -- ^ The nearest integer multiple of a positive unit (for example @0.01@ or+ -- @1@), ties to the even multiple, for exact types. Floating-point types+ -- return 'Nothing': a multiple of a unit is generally not a binary64+ -- value, and rounding it again would round twice. Use 'Nearest' with them.+ deriving (Eq, Show)++-- | Sum values exactly in the accumulator and round once. The result does not+-- depend on the order of the elements.+--+-- __Law__ S1 (relation @sumValues xs == roundAccum (foldr addAccum emptyAccum xs)@;+-- premise: every element finite; observed through '=='; tolerance none; all instances).+sumValues :: (HatVal n, Foldable f) => f n -> n+sumValues = roundAccum . Foldable.foldl' (flip addAccum) emptyAccum++-- | Scale the exact value of an accumulator by a ratio and round once with the+-- given rule. This is the amount a ratio transfer (@|% Portion n d@) moves.+-- 'Nothing' for a non-finite state, or when the rule cannot produce a finite+-- value (see 'Rounding'). The result type is the accumulator's value type, so+-- callers need no annotation.+roundScaled :: HatVal n => Rounding n -> Rational -> Accum n -> Maybe n+roundScaled rule ratio acc = exactAccum acc >>= roundRational rule . (* ratio)++------------------------------------------------------------------+-- * Tolerance+------------------------------------------------------------------++-- | Absolute and relative tolerance for the @*Within@ readouts, which keep the+-- 0.5.x meaning (sequential sums compared with a tolerance).+data Tolerance n = Tolerance+ { absTol :: n -- ^ Absolute floor, for values near zero.+ , relTol :: n -- ^ Relative term, scaled by the larger magnitude.+ } deriving (Eq, Show)++-- | @atol = 1e-13@, @rtol = 1e-12@: the formula of the 0.5.x readouts.+defaultTolerance :: Fractional n => Tolerance n+defaultTolerance = Tolerance { absTol = 1e-13, relTol = 1e-12 }+++instance HatVal Prelude.Double where+ {-# INLINE zeroValue #-}+ zeroValue = 0++ {-# INLINE valueError #-}+ valueError x+ | isNaN x || isInfinite x = Just NonFiniteValue+ | x < 0 = Just NegativeValue+ | otherwise = Nothing+ postingUpperBound = Just (2 ^ (900 :: Int))+ -- Identical formatting to the old top-level @showV@ (fixed 2-decimal rendering).+ {-# INLINE showValue #-}+ showValue = D.formatScientific D.Generic (Just 2) . D.fromFloatDigits++ newtype Accum Prelude.Double = DoubleAccum FloatingState+ emptyAccum = DoubleAccum emptyFloating+ addAccum x (DoubleAccum a) = DoubleAccum (addFloating x a)+ mergeAccum (DoubleAccum a) (DoubleAccum b) = DoubleAccum (mergeFloating a b)+ negateAccum (DoubleAccum a) = DoubleAccum (negateFloating a)+ signAccum (DoubleAccum a) = signFloating a+ roundAccum (DoubleAccum a) = roundFloating a+ splitAccum (DoubleAccum a) = splitFloating a+ exactAccum (DoubleAccum a) = exactFloating a+ roundRational Nearest value = roundRationalDouble value+ roundRational (ToMultipleOf _) _ = Nothing
src/ExchangeAlgebra/Assist.hs view
@@ -1,16 +1,7 @@ {-# LANGUAGE OverloadedStrings #-}--{- |-Module : ExchangeAlgebra.Assist-Description : Assistance helpers for LLM-facing account selection and validation feedback.--This module provides a small deterministic assistance layer for generated-journal-entry workflows. It exposes account-title metadata from the canonical-account registry, with semantic descriptions for technical and derived titles,-plus one-line explanations for validation errors from-"ExchangeAlgebra.Convert.Checked".+{- |Compatibility import for "ExchangeAlgebra.IO.Input.Assist". -}-module ExchangeAlgebra.Assist+module ExchangeAlgebra.Assist {-# DEPRECATED "Import ExchangeAlgebra.IO.Input.Assist instead." #-} ( AccountInfo(..) , describeAccount , allAccountInfos@@ -20,270 +11,4 @@ , explainSourceErrors ) where -import Data.List (sortOn)-import Data.List.NonEmpty (NonEmpty)-import qualified Data.List.NonEmpty as NE-import Data.Maybe (mapMaybe)-import Data.Ord (Down(..))-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra.Base- ( AccountRole- , AccountSemantics(..)- , AccountSpec(..)- , AccountTitles(..)- , DivisionSemantics- , HomeSideSemantics- , PostingCapability- , ReportingEligibility- , accountSemantics- , accountSpec- , concreteAccountTitles- )-import ExchangeAlgebra.Convert- ( ConvError(..) )-import ExchangeAlgebra.Convert.Checked- ( EntryError(..)- , JournalError(..)- , SourceError(..)- )---- $setup--- >>> :set -XOverloadedStrings--- >>> import Data.List.NonEmpty (NonEmpty(..))--- >>> import ExchangeAlgebra.Algebra.Base (AccountTitles(..), PostingCapability(..))--- >>> import ExchangeAlgebra.Convert (ConvError(..))--- >>> import ExchangeAlgebra.Convert.Checked (ProcessingContext(..), EntryError(..), JournalError(..), SourceError(..))---- | Account-title metadata for LLM-facing lookup.-data AccountInfo = AccountInfo- { aiTitle :: AccountTitles- , aiRoles :: [AccountRole]- , aiPostingCapability :: PostingCapability- , aiDivisionSemantics :: DivisionSemantics- , aiHomeSideSemantics :: HomeSideSemantics- , aiReportingEligibility :: ReportingEligibility- , aiNameEn :: Text- , aiNameJa :: Text- , aiDesc :: Text- } deriving (Show, Eq)---- | Describe a concrete account title.------ The wildcard 'AccountTitle' is rejected because--- 'ExchangeAlgebra.Algebra.Base.classifyAccountDivision' is intentionally--- undefined for it.------ >>> fmap ((== "現金") . aiNameJa) (describeAccount Cash)--- Just True--- >>> fmap aiDivisionSemantics (describeAccount Cash)--- Just (StatementDivision Assets)--- >>> fmap aiDivisionSemantics (describeAccount IncomeSummary)--- Just (DirectionEncoding Assets)--- >>> describeAccount AccountTitle--- Nothing-describeAccount :: AccountTitles -> Maybe AccountInfo-describeAccount title = do- spec <- accountSpec title- semantics <- accountSemantics title- pure (toInfo title spec semantics)---- | All concrete account-title descriptions in 'Enum' order.------ >>> length allAccountInfos--- 240--- >>> take 1 (map aiTitle allAccountInfos)--- [Cash]--- >>> aiTitle (last allAccountInfos)--- GoodsInTransit-allAccountInfos :: [AccountInfo]-allAccountInfos = mapMaybe describeAccount concreteAccountTitles--toInfo :: AccountTitles -> AccountSpec -> AccountSemantics -> AccountInfo-toInfo title spec semantics = AccountInfo- { aiTitle = title- , aiRoles = asemRoles semantics- , aiPostingCapability = asemPostingCapability semantics- , aiDivisionSemantics = asemDivisionSemantics semantics- , aiHomeSideSemantics = asemHomeSideSemantics semantics- , aiReportingEligibility = asemReportingEligibility semantics- , aiNameEn = asNameEn spec- , aiNameJa = safeNameJa title spec- , aiDesc = safeDescription title spec- }---- | LLM-facing names use the cleaned statement label from the registry.-safeNameJa :: AccountTitles -> AccountSpec -> Text-safeNameJa _ spec = asLabelJa spec---- | Semantic descriptions for technical, derived, and contextual titles.--- Ordinary statement accounts retain their canonical registry description.-safeDescription :: AccountTitles -> AccountSpec -> Text-safeDescription NetIncome _ =- "Period result: net income (当期純利益). Engine-generated only; the legacy Cost value is an internal direction encoding, not an expense classification."-safeDescription NetLoss _ =- "Period result: net loss (当期純損失). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a revenue classification."-safeDescription GrossProfit _ =- "Legacy engine coordinate: gross profit (売上総利益). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a statement subtotal definition."-safeDescription OrdinaryProfit _ =- "Legacy engine coordinate: ordinary profit (経常利益). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a statement subtotal definition."-safeDescription IncomeSummary _ =- "Closing device: income summary (損益). Available only during closing; the legacy Assets value is an internal direction encoding, not a balance-sheet classification."-safeDescription SuspensePayments _ =- "Temporary account: suspense payments (仮払金). Its legacy Assets value is a bookkeeping control class; unresolved balances require review before presentation."-safeDescription SuspenseReceipts _ =- "Temporary account: suspense receipts (仮受金). Its legacy Liability value is a bookkeeping control class; unresolved balances require review before presentation."-safeDescription CashOverShort _ =- "Temporary account: cash over and short (現金過不足). It must be cleared at closing and is not presented in financial statements."-safeDescription SuspenseAccount _ =- "Temporary account: suspense account (未決算). Its legacy Assets value is a bookkeeping control class; unresolved balances require review before presentation."-safeDescription BranchCurrentAccount _ =- "Reciprocal account: branch current account (支店). It may remain in head-office books but is eliminated when head-office and branch balances are combined."-safeDescription HeadOfficeCurrentAccount _ =- "Reciprocal account: head-office current account (本店). It may remain in branch books but is eliminated when head-office and branch balances are combined."-safeDescription NetIncomeAttributableToNCI _ =- "Consolidation attribution result: profit attributable to non-controlling interests (非支配株主に帰属する当期純利益). Available only in consolidation worksheets; the legacy Cost value is an internal direction encoding."-safeDescription NetLossAttributableToNCI _ =- "Consolidation attribution result: loss attributable to non-controlling interests (非支配株主に帰属する当期純損失). Available only in consolidation worksheets; the legacy Revenue value is an internal direction encoding."-safeDescription EquityInEarningsOfInvestee _ =- "Consolidation result: equity in earnings of investee (持分法による投資利益). Available only in consolidation worksheets."-safeDescription CumulativeTranslationAdjustment _ =- "Consolidation adjustment: cumulative translation adjustment (為替換算調整勘定). Available only in consolidation worksheets."-safeDescription _ spec = asDescription spec---- | Suggest account titles by deterministic substring matching.------ The query is split with 'T.words'. Each token is matched case-insensitively--- against the constructor name, English name, Japanese name, and full--- description. Results are ranked by the number of matched tokens, with 'Enum'--- order as the tie-breaker.------ >>> map aiTitle (take 3 (suggestAccounts "cash"))--- [Cash,PettyCash,CashOverShort]--- >>> map aiTitle (take 3 (suggestAccounts "現金"))--- [Cash,PettyCash,CashOverShort]--- >>> suggestAccounts ""--- []--- >>> suggestAccounts "zzzznomatch"--- []-suggestAccounts :: Text -> [AccountInfo]-suggestAccounts query- | null tokens = []- | otherwise =- map snd- . sortOn (\(rank, info) -> (Down rank, fromEnum (aiTitle info)))- . filter ((> 0) . fst)- $ [ (matchRank info, info) | info <- allAccountInfos ]- where- tokens = map T.toCaseFold (T.words query)-- matchRank info =- length- [ token- | token <- tokens- , any (T.isInfixOf token) (searchFields info)- ]-- searchFields info = map T.toCaseFold- [ T.pack (show (aiTitle info))- , aiNameEn info- , aiNameJa info- , aiDesc info- ]---- | Explain one checked-entry validation error as one English line.------ >>> explainEntryError (Imbalanced 1500 1400 :: EntryError Int)--- "entry is not balanced: debit total 1500 /= credit total 1400"--- >>> explainEntryError (NonPositiveAmount 2 Cash (0 :: Int))--- "posting 2 (Cash): amount must be > 0, got 0"--- >>> explainEntryError (EntryParse 0 (UnknownAccount "Supplies") :: EntryError Int)--- "posting 0: account \"Supplies\" does not resolve to a ledger account"--- >>> explainEntryError (PostingNotAllowed 1 NetIncome EngineGeneratedOnly OrdinaryJournal :: EntryError Int)--- "posting 1 (NetIncome): EngineGeneratedOnly is not allowed in OrdinaryJournal"-explainEntryError :: (Show v) => EntryError v -> Text-explainEntryError (EntryParse i err) =- postingOnly i <> ": " <> explainConvError err-explainEntryError (NonPositiveAmount i account amount) =- postingAccount i account <> ": amount must be > 0, got " <> showText amount-explainEntryError (WildcardAccount i) =- postingOnly i <> ": wildcard AccountTitle is not a ledger account"-explainEntryError (WildcardSide i) =- postingOnly i <> ": wildcard Side is not debit or credit"-explainEntryError (PostingNotAllowed i account capability context) =- postingAccount i account- <> ": " <> showText capability- <> " is not allowed in " <> showText context-explainEntryError EmptyEntry =- "entry has no postings"-explainEntryError (Imbalanced debitTotal creditTotal) =- "entry is not balanced: debit total "- <> showText debitTotal- <> " /= credit total "- <> showText creditTotal---- | Explain checked-journal errors, one line per error.------ >>> explainJournalErrors (EntryErrors "tx1" (Imbalanced 1500 1400 :| []) :| [] :: NonEmpty (JournalError String Int))--- "transaction \"tx1\": entry is not balanced: debit total 1500 /= credit total 1400"--- >>> explainJournalErrors (DuplicateTxId "tx1" :| [] :: NonEmpty (JournalError String Int))--- "transaction \"tx1\" appears more than once"-explainJournalErrors :: (Show n, Show v) => NonEmpty (JournalError n v) -> Text-explainJournalErrors =- T.intercalate "\n" . concatMap explainJournalError . NE.toList---- | Explain source-coverage errors, one line per error.------ >>> explainSourceErrors [MissingSource "tx7" :: SourceError String Int]--- "source transaction \"tx7\" has no journal entry"--- >>> explainSourceErrors [AmountMismatch "tx7" 1500 1400 :: SourceError String Int]--- "source transaction \"tx7\" amount mismatch: expected 1500, journal entry has 1400"-explainSourceErrors :: (Show n, Show v) => [SourceError n v] -> Text-explainSourceErrors = T.intercalate "\n" . map explainSourceError--explainJournalError :: (Show n, Show v) => JournalError n v -> [Text]-explainJournalError (EntryErrors txid errs) =- [ "transaction " <> showText txid <> ": " <> explainEntryError err- | err <- NE.toList errs- ]-explainJournalError (DuplicateTxId txid) =- [ "transaction " <> showText txid <> " appears more than once" ]--explainSourceError :: (Show n, Show v) => SourceError n v -> Text-explainSourceError (MissingSource txid) =- "source transaction " <> showText txid <> " has no journal entry"-explainSourceError (UnknownSource txid) =- "journal entry " <> showText txid <> " has no source transaction"-explainSourceError (AmountMismatch txid expected actual) =- "source transaction "- <> showText txid- <> " amount mismatch: expected "- <> showText expected- <> ", journal entry has "- <> showText actual--explainConvError :: ConvError -> Text-explainConvError (UnknownAccount account) =- "account " <> showText account <> " does not resolve to a ledger account"-explainConvError (AmbiguousAccount account candidates) =- "account "- <> showText account- <> " is ambiguous; candidates: "- <> T.intercalate ", " (map showText candidates)-explainConvError (UnknownSide sideText) =- "side " <> showText sideText <> " is not debit or credit"-explainConvError (MalformedCsv message) =- "malformed CSV input: " <> showText message-explainConvError (BadAmount message) =- "amount field is invalid: " <> showText message--postingOnly :: Int -> Text-postingOnly i = "posting " <> showText i--postingAccount :: Int -> AccountTitles -> Text-postingAccount i account =- postingOnly i <> " (" <> showText account <> ")"--showText :: (Show a) => a -> Text-showText = T.pack . show+import ExchangeAlgebra.IO.Input.Assist
src/ExchangeAlgebra/Assist/Descriptions.hs view
@@ -2,8 +2,8 @@ Module : ExchangeAlgebra.Assist.Descriptions Description : Compatibility projection of the canonical account registry. -}-module ExchangeAlgebra.Assist.Descriptions+module ExchangeAlgebra.Assist.Descriptions {-# DEPRECATED "Use ExchangeAlgebra.Accounting.Account instead." #-} ( accountDescriptions ) where -import ExchangeAlgebra.Algebra.Base.Account.Registry (accountDescriptions)+import ExchangeAlgebra.Accounting.Account (accountDescriptions)
src/ExchangeAlgebra/Bookkeeping.hs view
@@ -1,53 +1,6 @@-{- |- Module : ExchangeAlgebra.Bookkeeping- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Package for Exchange Algebra defined by Hiroshi Deguchi.-- Exchange Algebra is an algebraic description of bookkeeping systems.- Details are below.-- <https://www.springer.com/gp/book/9784431209850>-- <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>-- == Closing-adjustment entry builders (決算整理仕訳)-- This module provides /posting builders/ (journal-entry templates) for the- closing adjustments taught at the elementary-bookkeeping (日商簿記 3 級) level:- cost of goods sold under the periodic (3-account) method, depreciation, the- allowance for doubtful accounts, deferral\/accrual (経過勘定) entries, and the- settlement of consumption\/corporate income tax.-- Unlike "ExchangeAlgebra.Algebra.Transfer" (which /relabels existing ledger- balances/), most closing adjustments record /new postings whose amounts come- from outside the ledger/ (period-end inventory, estimated allowance,- depreciation expense, …). Each builder here therefore returns a fresh,- balanced @'Alg' v b@ value.-- === Design-- * Every builder is constructed exclusively with the smart constructor- @('.@')@, so a zero amount normalizes to @Zero@ and a negative\/non-finite- amount is rejected by @('.@')@'s @error@ path. No implicit @bar@\/@compress@- is performed — builders only /generate/ new postings.-- * The base polymorphism is absorbed by an injection function @'MkBase'@ that- the caller supplies once (see below).-- * The Hat\/Not label of each line follows the /accounting meaning/ of that- line: an account that /increases/ uses @Not@ (so it lands on its home side),- an account that /decreases/ uses @Hat@ (the opposite side). 'whichSide'- then places each line on the correct debit\/credit side. Consequently every- builder is debit-credit balanced: @'norm' ('decL' x) == 'norm' ('decR' x)@.--}--{-# LANGUAGE FlexibleContexts #-}-+-- | Compatibility exports for bookkeeping operations. module ExchangeAlgebra.Bookkeeping+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.Entries instead." #-} ( -- * Base injection MkBase -- * Cost of goods sold (売上原価, 3 分法)@@ -77,533 +30,4 @@ , priorPeriodErrorCorrection ) where -import ExchangeAlgebra.Algebra--- @MoneyDecimal@ is used only by the Haddock doctest examples (which run in this--- module's import scope), not the module body; keep it imported for the doctests.-import ExchangeAlgebra.Value (MoneyDecimal)-import GHC.Stack (HasCallStack)---- | Injection that tells a builder how to wrap a pair of v'Hat' and--- 'AccountTitles' into the concrete base @b@ in use. The caller supplies it once.------ @--- -- single-axis base (account title only):--- mk1 :: MkBase (HatBase AccountTitles)--- mk1 = (':<')------ -- multi-axis base, filling the extra axes with fixed values:--- mk4 :: MkBase (HatBase (AccountTitles, Name, CountUnit, Subject))--- mk4 h t = h :< (t, \"\", Yen, \"\")--- @-type MkBase b = Hat -> AccountTitles -> b---- | (借) debit-meaning convenience: the account /increases/, so it carries 'Not'--- and lands on its home side.-{-# INLINE up #-}-up :: (HatVal v, ExBaseClass b) => MkBase b -> v -> AccountTitles -> Alg v b-up mk v t = v .@ mk Not t---- | The account /decreases/, so it carries v'Hat' (the opposite of its home side).-{-# INLINE down #-}-down :: (HatVal v, ExBaseClass b) => MkBase b -> v -> AccountTitles -> Alg v b-down mk v t = v .@ mk Hat t----------------------------------------------------------------------- * Cost of goods sold (売上原価, 3 分法)----------------------------------------------------------------------- | Cost-of-goods-sold adjustment under the periodic\/3-account method--- (3 分法; lecture ch.10, 24), the four "しいくりくりしい" postings:------ > (借) 仕入 beg (貸) 繰越商品 beg -- opening inventory--- > (借) 繰越商品 end (貸) 仕入 end -- closing inventory------ After this entry the @Purchases@ balance equals cost of goods sold and the--- @MerchandiseInventory@ balance equals the closing inventory.------ The result is debit-credit balanced (each side sums to @beg + end@):------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = cogsAdjustmentEntries mk 100 50 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decL e)--- 150------ Complexity: O(1)-cogsAdjustmentEntries :: (HatVal v, ExBaseClass b)- => MkBase b -- ^ base injection- -> v -- ^ beginning inventory (期首商品棚卸高)- -> v -- ^ ending inventory (期末商品棚卸高)- -> Alg v b-cogsAdjustmentEntries mk beg end =- up mk beg Purchases -- (借) 仕入 期首- .+ down mk beg MerchandiseInventory -- (貸) 繰越商品 期首- .+ up mk end MerchandiseInventory -- (借) 繰越商品 期末- .+ down mk end Purchases -- (貸) 仕入 期末----------------------------------------------------------------------- * Depreciation (減価償却)----------------------------------------------------------------------- | Indirect-method depreciation (間接法; lecture ch.18):------ > (借) 減価償却費 amt (貸) 減価償却累計額 amt------ 'AccumulatedDepreciation' is a credit-balance valuation account (評価勘定).------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = depreciationIndirectEntry mk 150 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-depreciationIndirectEntry :: (HatVal v, ExBaseClass b)- => MkBase b -> v -> Alg v b-depreciationIndirectEntry mk amt =- up mk amt Depreciation -- (借) 減価償却費- .+ up mk amt AccumulatedDepreciation -- (貸) 減価償却累計額---- | Direct-method depreciation (直接法; lecture ch.18):------ > (借) 減価償却費 amt (貸) <asset> amt------ The asset's carrying amount is reduced directly. @assetTitle@ is the fixed--- asset being depreciated (e.g. 'Fixtures', 'Building', 'Vehicle').------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = depreciationDirectEntry mk 90 Fixtures :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-depreciationDirectEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ depreciation amount- -> AccountTitles -- ^ asset being depreciated- -> Alg v b-depreciationDirectEntry mk amt assetTitle =- up mk amt Depreciation -- (借) 減価償却費- .+ down mk amt assetTitle -- (貸) <asset>----------------------------------------------------------------------- * Allowance for doubtful accounts (貸倒引当金)----------------------------------------------------------------------- | Allowance for doubtful accounts under the /差額補充法/ (replenishment;--- lecture ch.16). Only the difference between the estimate and the current--- balance is recorded:------ * @estimate > current@: provide the shortfall —--- @(借) 貸倒引当金繰入 / (貸) 貸倒引当金@.--- * @estimate < current@: release the excess —--- @(借) 貸倒引当金 / (貸) 貸倒引当金戻入@ (uses 'ReversalOfAllowanceForDoubtfulAccounts').--- * @estimate == current@: no entry (@Zero@).------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = allowanceReplenishmentEntry mk 1400 1000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decL e)--- 400--- >>> allowanceReplenishmentEntry mk 2000 2000 == (Zero :: Alg MoneyDecimal (HatBase AccountTitles))--- True------ Complexity: O(1)-allowanceReplenishmentEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ estimated allowance (当期末貸倒見積額)- -> v -- ^ current allowance balance (貸倒引当金残高)- -> Alg v b-allowanceReplenishmentEntry mk estimate current- | estimate >= current =- let diff = estimate - current- in up mk diff ProvisionForDoubtfulAccounts -- (借) 貸倒引当金繰入- .+ up mk diff AllowanceForDoubtfulAccounts -- (貸) 貸倒引当金- | otherwise =- let diff = current - estimate- in down mk diff AllowanceForDoubtfulAccounts -- (借) 貸倒引当金- .+ up mk diff ReversalOfAllowanceForDoubtfulAccounts -- (貸) 貸倒引当金戻入---- | Allowance for doubtful accounts under the /洗替法/ (reset; lecture ch.16):--- release the whole current balance, then provide the whole estimate.------ > (借) 貸倒引当金 current (貸) 貸倒引当金戻入 current--- > (借) 貸倒引当金繰入 estimate (貸) 貸倒引当金 estimate------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = allowanceResetEntries mk 2400 1000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decL e)--- 3400------ Complexity: O(1)-allowanceResetEntries :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ estimated allowance for this period- -> v -- ^ current allowance balance to release- -> Alg v b-allowanceResetEntries mk estimate current =- down mk current AllowanceForDoubtfulAccounts -- (借) 貸倒引当金- .+ up mk current ReversalOfAllowanceForDoubtfulAccounts -- (貸) 貸倒引当金戻入- .+ up mk estimate ProvisionForDoubtfulAccounts -- (借) 貸倒引当金繰入- .+ up mk estimate AllowanceForDoubtfulAccounts -- (貸) 貸倒引当金----------------------------------------------------------------------- * Deferral / accrual (経過勘定; lecture ch.21)----------------------------------------------------------------------- | Deferral of a prepaid expense (費用の前払; 前払費用):------ > (借) 前払費用 amt (貸) <expense> amt------ Moves the unexpired portion of @expenseTitle@ out to 'PrepaidExpenses'. The--- opening reversing entry of the next period is @'reversingEntry'@ of this.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = prepaidExpenseEntry mk 4000 RentExpense :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-prepaidExpenseEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ prepaid amount- -> AccountTitles -- ^ expense being deferred- -> Alg v b-prepaidExpenseEntry mk amt expenseTitle =- up mk amt PrepaidExpenses -- (借) 前払費用- .+ down mk amt expenseTitle -- (貸) <expense>---- | Deferral of unearned revenue (収益の前受; 前受収益):------ > (借) <revenue> amt (貸) 前受収益 amt------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = unearnedRevenueEntry mk 4000 RentalIncome :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-unearnedRevenueEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ unearned amount- -> AccountTitles -- ^ revenue being deferred- -> Alg v b-unearnedRevenueEntry mk amt revenueTitle =- down mk amt revenueTitle -- (借) <revenue>- .+ up mk amt UnearnedRevenue -- (貸) 前受収益---- | Accrual of accrued revenue (収益の未収; 未収収益):------ > (借) 未収収益 amt (貸) <revenue> amt------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = accruedRevenueEntry mk 2500 InterestEarned :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-accruedRevenueEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ accrued amount- -> AccountTitles -- ^ revenue being accrued- -> Alg v b-accruedRevenueEntry mk amt revenueTitle =- up mk amt AccruedRevenue -- (借) 未収収益- .+ up mk amt revenueTitle -- (貸) <revenue>---- | Accrual of an accrued expense (費用の未払; 未払費用):------ > (借) <expense> amt (貸) 未払費用 amt------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = accruedExpenseEntry mk 2500 InterestExpense :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-accruedExpenseEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ accrued amount- -> AccountTitles -- ^ expense being accrued- -> Alg v b-accruedExpenseEntry mk amt expenseTitle =- up mk amt expenseTitle -- (借) <expense>- .+ up mk amt AccruedExpenses -- (貸) 未払費用---- | Reversing\/canceling entry, a vocabulary alias for the Hat operation--- @('.^')@ (lecture ch.20-21, 再振替仕訳\/訂正仕訳). This is the direct--- application of /Hat involution/: flipping Hat\/Not on every posting turns an--- entry into its exact reversal.------ * Opening reversal (再振替仕訳): @reversingEntry deferral@ at the start of the--- next period undoes a deferral\/accrual entry.------ * Correction (訂正仕訳): a mistaken entry @wrong@ is corrected by--- @reversingEntry wrong '.+' correct@. Because @('.+')@ keeps same-base--- postings as an ordered /sequence/ (the redundancy), the wrong entry and its--- reversal both remain in the algebra as an /audit trail/ of the correction;--- @'bar'@ then nets them out to the corrected balance.------ @reversingEntry@ is an involution (@reversingEntry . reversingEntry = id@) and--- @'bar' (x '.+' reversingEntry x) = Zero@ (an entry plus its reversal cancels).------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let x = accruedExpenseEntry mk 2500 InterestExpense :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> reversingEntry (reversingEntry x) == x--- True--- >>> bar (x .+ reversingEntry x) == Zero--- True------ Complexity: O(1) for a singleton, O(n) for an--- 'ExchangeAlgebra.Algebra.Internal.Liner' (n base keys).-{-# INLINE reversingEntry #-}-reversingEntry :: (Redundant a v b) => a v b -> a v b-reversingEntry = (.^)----------------------------------------------------------------------- * Tax settlement (消費税・法人税等; lecture ch.23)----------------------------------------------------------------------- | Consumption-tax settlement at closing (税抜処理方式; lecture ch.23): offset--- consumption tax received against consumption tax paid and book the unpaid--- balance.------ > (借) 仮受消費税 received (貸) 仮払消費税 paid--- > (貸) 未払消費税 received - paid------ __Out of 3-級 scope:__ @received < paid@ (a tax refund, 還付) is rejected with--- @error@. At the elementary level the consumption tax received always exceeds--- the consumption tax paid.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = consumptionTaxSettlementEntry mk 1000 20000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (projByAccountTitle AccruedConsumptionTax e)--- 19000------ Complexity: O(1)-consumptionTaxSettlementEntry :: (HasCallStack, HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ consumption tax paid (仮払消費税)- -> v -- ^ consumption tax received (仮受消費税)- -> Alg v b-consumptionTaxSettlementEntry mk paid received- | received < paid =- error $ "consumptionTaxSettlementEntry: received (" ++ show received- ++ ") < paid (" ++ show paid- ++ "); a consumption-tax refund is out of 日商簿記 3 級 scope."- | otherwise =- let unpaid = received - paid- in down mk received ConsumptionTaxReceived -- (借) 仮受消費税- .+ down mk paid ConsumptionTaxPaid -- (貸) 仮払消費税- .+ up mk unpaid AccruedConsumptionTax -- (貸) 未払消費税---- | Interim (mid-year) payment of corporate income tax (中間納付; lecture ch.23):------ > (借) 仮払法人税等 amt (貸) 現金 amt------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = corporateTaxInterimEntry mk 500000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-corporateTaxInterimEntry :: (HatVal v, ExBaseClass b)- => MkBase b -> v -> Alg v b-corporateTaxInterimEntry mk amt =- up mk amt PrepaidCorporateIncomeTaxes -- (借) 仮払法人税等- .+ down mk amt Cash -- (貸) 現金---- | Corporate income tax settlement at closing (lecture ch.23): book the total--- tax expense, credit the interim payment already made, and recognize the--- unpaid balance.------ > (借) 法人税等 total (貸) 仮払法人税等 interim--- > (貸) 未払法人税等 total - interim------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = corporateTaxSettlementEntries mk 800000 500000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (projByAccountTitle AccruedCorporateIncomeTaxes e)--- 300000------ Complexity: O(1)-corporateTaxSettlementEntries :: (HasCallStack, HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ total corporate income tax (法人税等)- -> v -- ^ interim payment already made (仮払法人税等)- -> Alg v b-corporateTaxSettlementEntries mk total interim- -- Same guard style as 'consumptionTaxSettlementEntry': an over-prepayment- -- (refund position) is out of 3-級 scope, so reject it with a clear error- -- instead of letting the negative @unpaid@ hit @('.@')@'s generic one.- | interim > total =- error $ "corporateTaxSettlementEntries: interim (" ++ show interim- ++ ") > total (" ++ show total- ++ "); a corporate-tax refund (over-prepayment) is out of 日商簿記 3 級 scope."- | otherwise =- let unpaid = total - interim- in up mk total CorporateIncomeTaxes -- (借) 法人税等- .+ down mk interim PrepaidCorporateIncomeTaxes -- (貸) 仮払法人税等- .+ up mk unpaid AccruedCorporateIncomeTaxes -- (貸) 未払法人税等----------------------------------------------------------------------- * Equity method (持分法; lecture T4b)----------------------------------------------------------------------- | Equity-method earnings accrual (持分法による投資利益の計上):------ > (借) 関係会社株式 share (貸) 持分法による投資利益 share------ @share@ is the investor's proportionate share of the investee's net income--- (= investee NI × ownership %). The investment carrying amount increases by--- @share@.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = equityMethodEarningsEntry mk 438000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decL e)--- 438000------ Complexity: O(1)-equityMethodEarningsEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ investor's share of investee NI (持分利益)- -> Alg v b-equityMethodEarningsEntry mk share =- up mk share InvestmentInAssociate -- (借) 関係会社株式- .+ up mk share EquityInEarningsOfInvestee -- (貸) 持分法による投資利益---- | Equity-method dividend received (受取配当による投資簿価の減額):------ > (借) 現金 div (貸) 関係会社株式 div------ Under the equity method a dividend received from the investee is /not/ income;--- it reduces the carrying amount of the investment.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = equityMethodDividendEntry mk 800000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decL e)--- 800000------ Complexity: O(1)-equityMethodDividendEntry :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ dividend received (受取配当金)- -> Alg v b-equityMethodDividendEntry mk div' =- up mk div' Cash -- (借) 現金- .+ down mk div' InvestmentInAssociate -- (貸) 関係会社株式---- | Combined equity-method closing entries: record the earnings accrual--- then the dividend reduction.------ > (借) 関係会社株式 share (貸) 持分法による投資利益 share--- > (借) 現金 div (貸) 関係会社株式 div------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = equityMethodEntries mk 438000 800000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True------ Complexity: O(1)-equityMethodEntries :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ investor's share of investee NI (持分利益)- -> v -- ^ dividend received (受取配当金)- -> Alg v b-equityMethodEntries mk share div' =- equityMethodEarningsEntry mk share- .+ equityMethodDividendEntry mk div'---- | Equity-method carrying-amount balance (関係会社株式の簿価残高).------ Projects all @'InvestmentInAssociate'@ postings from an accumulated @'Alg'@--- and returns their @'norm'@ (= net carrying amount). The caller accumulates--- the initial acquisition entry plus any 'equityMethodEarningsEntry' and--- 'equityMethodDividendEntry' calls; this function reads the result without--- recomputing it by hand — /correct-by-construction/.------ Example (#18 anchor doctest):--- acquisition cost 2,400,000 + share of NI 0.30 × 1,460,000 (= 438,000)--- − dividend received 800,000 = carrying amount 2,038,000.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> -- initial acquisition: Dr 関係会社株式 2,400,000 / Cr Cash 2,400,000--- >>> let acq = up mk 2400000 InvestmentInAssociate .+ down mk 2400000 Cash :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> let entries = equityMethodEntries mk 438000 800000 :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> let ledger = acq .+ entries--- >>> equityMethodBalance ledger--- 2038000------ Complexity: O(s) (s = number of scalar entries in the accumulated algebra)-equityMethodBalance :: (HatVal v, ExBaseClass b) => Alg v b -> v-equityMethodBalance = norm . bar . projByAccountTitle InvestmentInAssociate----------------------------------------------------------------------- * Prior-period error correction (前期修正)----------------------------------------------------------------------- | Prior-period error correction (前期修正・誤謬訂正; #15 Briggs anchor).------ Records the correction of an error discovered in a prior period:--- the current-period portion goes to an expense account, while the--- prior-period portion bypasses the income statement and is charged--- directly to 'RetainedEarnings' (繰越利益剰余金) — conforming to--- IAS 8 \/ ASC 250-10 retrospective-restatement treatment.------ > (借) \<expense\> current (貸) \<asset\> current + prior--- > (借) RetainedEarnings prior------ Structural guarantee (thesis property): the prior-period amount is--- /always/ routed to 'RetainedEarnings'; the caller cannot accidentally--- book it to an income-statement account — the parameter type forces the--- split.------ 'RetainedEarnings' is a credit-balance equity account (@home = Credit@),--- so a reduction (debit-side charge) is recorded with @down@ (@Hat@),--- which places it on the debit side — correct-by-construction.------ The credit leg (asset reduction) is collapsed into one posting--- @down mk (current + prior) assetAcc@ because @'Num' v@ is available--- from the @'HatVal'@ superclass, making value addition clean; the--- two-line @.+@ alternative is equally valid under the seq-redundancy--- principle but the single line is more readable here.------ === Example (#15 anchor: patent 55,000 \/ 10 yr, discovered in 2028)------ * Patent cost 55,000, useful life 10 years → annual amortization 5,500.--- * Error discovered in 2028: 2 prior years (2026, 2027) were not amortized.--- * Current-year amortization 5,500 → expense (@AmortizationExpense@).--- * Prior 2 years 11,000 → directly to @RetainedEarnings@ (前期修正).--- * Credit @Patent@ 16,500 total.------ >>> let mk = (:<) :: MkBase (HatBase AccountTitles)--- >>> let e = priorPeriodErrorCorrection mk 5500 11000 AmortizationExpense Patent :: Alg MoneyDecimal (HatBase AccountTitles)--- >>> norm (decL e) == norm (decR e)--- True--- >>> norm (decR e)--- 16500--- >>> norm (decL e)--- 16500------ Complexity: O(1)-priorPeriodErrorCorrection :: (HatVal v, ExBaseClass b)- => MkBase b- -> v -- ^ current-period portion (当期費用; → @expenseAcc@)- -> v -- ^ prior-period portion (前期修正; → 'RetainedEarnings')- -> AccountTitles -- ^ current-period expense account (e.g. 'AmortizationExpense')- -> AccountTitles -- ^ affected asset credited (e.g. 'Patent')- -> Alg v b-priorPeriodErrorCorrection mk current prior expenseAcc assetAcc =- up mk current expenseAcc -- (借) 当期費用 (expense 増, home=Debit → Not)- .+ down mk prior RetainedEarnings -- (借) 繰越利益剰余金 (equity 減, home=Credit → Hat=debit側)- .+ down mk (current + prior) assetAcc -- (貸) 資産 (合計 current+prior 分だけ減額)+import ExchangeAlgebra.Accounting.Entries
src/ExchangeAlgebra/Consolidation/Worksheet.hs view
@@ -1,28 +1,6 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Consolidation.Worksheet-Description : Validation boundary for consolidation worksheets.--A consolidation worksheet is not an entity's journal. Its adjustments are-therefore accepted as exchange-algebra elements, while this module checks that-each adjustment is independently balanced before any aggregation takes place.-This prevents two malformed adjustments from canceling each other only at the-worksheet total.--The validated value retains source trial balances and adjustments separately.-'combinedWorksheet' is an explicit projection and uses redundant-algebra-addition, so same-base postings remain separate sequences until a caller asks-for normalization. The projection itself does not carry per-posting provenance;-use 'validatedSources' and 'validatedAdjustments' when that provenance is-required.--Validation uses exact equality, consistently with the checked-conversion-boundary. Prefer an exact value type such as @MoneyDecimal@ for deterministic-worksheet validation rather than a floating-point representation.--}+-- | Compatibility exports for consolidation worksheets. module ExchangeAlgebra.Consolidation.Worksheet+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.Consolidation instead." #-} ( PeriodResult(..) , AccountBalance(..) , LinkField(..)@@ -39,451 +17,6 @@ , combinedWorksheet ) where -import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List as L-import qualified Data.Map.Strict as M--import ExchangeAlgebra.Algebra- ( Alg- , Exchange(decL, decR)- , HatVal(..)- , Redundant((.+), norm)- , bases- , vals- )-import ExchangeAlgebra.Accounting.PostingPolicy- ( ProcessingContext(ConsolidationWorksheet)- , postingAllowedIn- , postingCapabilityFor- )-import ExchangeAlgebra.Algebra.Base- ( AccountTitles(..)- , Hat(..)- , HatBase((:<))- , PostingCapability- )-import ExchangeAlgebra.Reporting.Metric (PeriodResult(..))-import ExchangeAlgebra.TrialBalance.Balance- ( AccountBalance(..)- , balanceAmount- , balancePair- )---- | Named linkage fields, used when reporting an invalid non-negative amount.-data LinkField- = ProfitOrLossNetIncome- | ProfitOrLossNetIncomeAttributableToOwners- | StatementOfChangesNetIncomeAttributableToOwners- | OpeningRetainedEarnings- | RetainedEarningsDividends- | StatementOfChangesClosingRetainedEarnings- | BalanceSheetRetainedEarnings- | OpeningNonControllingInterests- | NonControllingInterestsPeriodShare- | NonControllingInterestsDividends- | StatementOfChangesClosingNonControllingInterests- | BalanceSheetNonControllingInterests- deriving (Show, Eq, Ord)---- | One entity trial balance and its provenance identifier. A source trial--- balance is accepted as historical input and is therefore not restricted by--- a processing-context posting-capability gate; it is still checked for--- structural coordinates, valid values, and exact balance.-data TrialBalanceSource source v = TrialBalanceSource- { _sourceId :: source- , _sourceTrialBalance :: Alg v (HatBase AccountTitles)- }- deriving (Show)---- | One atomic worksheet adjustment. The algebra element need not originate--- from a journal entry, but it must be independently balanced and use only--- coordinates admitted by the consolidation processing context.-data WorksheetAdjustment source adjustment v = WorksheetAdjustment- { _adjustmentId :: adjustment- , _adjustmentSourceIds :: NonEmpty source- , _adjustmentElement :: Alg v (HatBase AccountTitles)- }- deriving (Show)---- | Cross-column equations for the statement of profit or loss (P/L),--- statement of changes in equity (S/S), and balance sheet (B/S).------ The P/L total is checked against its owners-of-parent and NCI attribution,--- and the owners amount is then linked to S/S. Retained earnings and NCI--- balances keep credit/debit position structural, so accumulated deficits do--- not require negative scalar values.------ These values are caller-supplied worksheet-column facts. This Land 2b type--- validates their equations but cannot derive them from the unlabelled 'Alg'--- alone; anchoring statement columns to a reporting projection is a later--- reporting-layer responsibility. The roll-forwards model period result and--- dividends only; worksheets with OCI, ownership changes, reserve transfers,--- or other equity movements require a later reporting-layer extension.-data WorksheetLinkage v = WorksheetLinkage- { _profitOrLossNetIncome :: PeriodResult v- , _profitOrLossNetIncomeAttributableToOwners :: PeriodResult v- , _statementOfChangesNetIncomeAttributableToOwners :: PeriodResult v- , _openingRetainedEarnings :: AccountBalance v- , _retainedEarningsDividends :: v- , _statementOfChangesClosingRetainedEarnings :: AccountBalance v- , _balanceSheetRetainedEarnings :: AccountBalance v- , _openingNonControllingInterests :: AccountBalance v- , _nonControllingInterestsPeriodShare :: PeriodResult v- , _nonControllingInterestsDividends :: v- , _statementOfChangesClosingNonControllingInterests- :: AccountBalance v- , _balanceSheetNonControllingInterests :: AccountBalance v- }- deriving (Show, Eq)---- | Unvalidated consolidation worksheet input.-data WorksheetInput source adjustment v = WorksheetInput- { _worksheetSources :: NonEmpty (TrialBalanceSource source v)- , _worksheetAdjustments :: [WorksheetAdjustment source adjustment v]- , _worksheetLinkage :: WorksheetLinkage v- }- deriving (Show)---- | Every rejected invariant is tied to a source, adjustment, or linkage.-data WorksheetError source adjustment v- = DuplicateSourceId source- | DuplicateAdjustmentId adjustment- | DuplicateAdjustmentSource adjustment source- | UnknownAdjustmentSource adjustment source- | InvalidSourceValue source v- | WildcardSourceAccount source- | WildcardSourceSide source- | UnbalancedSourceTrialBalance source v v- | EmptyAdjustment adjustment- | InvalidAdjustmentValue adjustment v- | WildcardAdjustmentAccount adjustment- | WildcardAdjustmentSide adjustment- | AdjustmentPostingNotAllowed adjustment AccountTitles PostingCapability- | UnbalancedAdjustment adjustment v v- | InvalidLinkAmount LinkField v- | NetIncomeAttributionMismatch v v- | OwnersPeriodResultLinkMismatch (PeriodResult v) (PeriodResult v)- | RetainedEarningsRollForwardMismatch v v- | BalanceSheetRetainedEarningsMismatch- (AccountBalance v) (AccountBalance v)- | NonControllingInterestsRollForwardMismatch v v- | BalanceSheetNonControllingInterestsMismatch- (AccountBalance v) (AccountBalance v)- deriving (Show, Eq)---- | A worksheet whose provenance, atomic balance, processing capability, and--- cross-column linkage have all passed validation. The constructor is hidden.-data ValidatedWorksheet source adjustment v = ValidatedWorksheet- (NonEmpty (TrialBalanceSource source v))- [WorksheetAdjustment source adjustment v]- (WorksheetLinkage v)---- | Validate a consolidation worksheet without normalizing any algebra term.-validateConsolidationWorksheet- :: (HatVal v, Ord source, Ord adjustment)- => WorksheetInput source adjustment v- -> Either (NonEmpty (WorksheetError source adjustment v))- (ValidatedWorksheet source adjustment v)-validateConsolidationWorksheet input =- case errors of- [] -> Right (ValidatedWorksheet sources adjustments linkage)- e : es -> Left (e :| es)- where- sources = _worksheetSources input- adjustments = _worksheetAdjustments input- linkage = _worksheetLinkage input- errors = duplicateIdErrors sources adjustments- ++ concatMap validateSource (toListNE sources)- ++ concatMap (validateAdjustment (sourceIds sources)) adjustments- ++ validateWorksheetLinkage linkage---- | Recover the source trial balances with their provenance intact.-validatedSources- :: ValidatedWorksheet source adjustment v- -> NonEmpty (TrialBalanceSource source v)-validatedSources (ValidatedWorksheet sources _ _) = sources---- | Recover the atomic adjustments with their provenance intact.-validatedAdjustments- :: ValidatedWorksheet source adjustment v- -> [WorksheetAdjustment source adjustment v]-validatedAdjustments (ValidatedWorksheet _ adjustments _) = adjustments---- | Recover the validated cross-column linkage values.-validatedLinkage- :: ValidatedWorksheet source adjustment v- -> WorksheetLinkage v-validatedLinkage (ValidatedWorksheet _ _ linkage) = linkage---- | Explicitly combine source trial balances and atomic adjustments.--- Redundant-algebra addition preserves same-base posting sequences. This is a--- calculation projection; inspect the validated source and adjustment lists--- when provenance is required.-combinedWorksheet- :: (HatVal v)- => ValidatedWorksheet source adjustment v- -> Alg v (HatBase AccountTitles)-combinedWorksheet (ValidatedWorksheet sources adjustments _) =- L.foldl' (.+) mempty- (map _sourceTrialBalance (toListNE sources)- ++ map _adjustmentElement adjustments)--duplicateIdErrors- :: (Ord source, Ord adjustment)- => NonEmpty (TrialBalanceSource source v)- -> [WorksheetAdjustment source adjustment v]- -> [WorksheetError source adjustment v]-duplicateIdErrors sources adjustments =- [ DuplicateSourceId sourceId- | (sourceId, count) <- M.toList sourceCounts- , count > 1- ]- ++ [ DuplicateAdjustmentId adjustmentId- | (adjustmentId, count) <- M.toList adjustmentCounts- , count > 1- ]- where- sourceCounts = M.fromListWith (+)- [ (_sourceId source, 1 :: Int) | source <- toListNE sources ]- adjustmentCounts = M.fromListWith (+)- [ (_adjustmentId adjustment, 1 :: Int) | adjustment <- adjustments ]--sourceIds :: Ord source- => NonEmpty (TrialBalanceSource source v)- -> M.Map source ()-sourceIds = M.fromList . map (\source -> (_sourceId source, ())) . toListNE--validateSource- :: HatVal v- => TrialBalanceSource source v- -> [WorksheetError source adjustment v]-validateSource source = structuralErrors ++ balanceErrors- where- sourceId = _sourceId source- alg = _sourceTrialBalance source- structuralErrors =- [ InvalidSourceValue sourceId value- | value <- vals alg- , isErrorValue value- ]- ++ [ WildcardSourceAccount sourceId- | _ :< AccountTitle <- bases alg- ]- ++ [ WildcardSourceSide sourceId- | HatNot :< _ <- bases alg- ]- balanceErrors- | not (null structuralErrors) = []- | debit /= credit = [UnbalancedSourceTrialBalance sourceId debit credit]- | otherwise = []- where- (debit, credit) = sideTotals alg--validateAdjustment- :: (HatVal v, Ord source)- => M.Map source ()- -> WorksheetAdjustment source adjustment v- -> [WorksheetError source adjustment v]-validateAdjustment knownSources adjustment =- provenanceErrors ++ structuralErrors ++ balanceErrors- where- adjustmentId = _adjustmentId adjustment- alg = _adjustmentElement adjustment- structuralErrors =- [ EmptyAdjustment adjustmentId | null (vals alg) ]- ++ [ InvalidAdjustmentValue adjustmentId value- | value <- vals alg- , isErrorValue value || not (value > 0)- ]- ++ [ WildcardAdjustmentAccount adjustmentId- | _ :< AccountTitle <- bases alg- ]- ++ [ WildcardAdjustmentSide adjustmentId- | HatNot :< _ <- bases alg- ]- ++ [ AdjustmentPostingNotAllowed adjustmentId account capability- | _ :< account <- bases alg- , account /= AccountTitle- , let capability = postingCapabilityFor account- , not (postingAllowedIn ConsolidationWorksheet capability)- ]- refs = toListNE (_adjustmentSourceIds adjustment)- refCounts = M.fromListWith (+) [ (sourceId, 1 :: Int) | sourceId <- refs ]- provenanceErrors =- [ DuplicateAdjustmentSource adjustmentId sourceId- | (sourceId, count) <- M.toList refCounts- , count > 1- ]- ++ [ UnknownAdjustmentSource adjustmentId sourceId- | sourceId <- M.keys refCounts- , M.notMember sourceId knownSources- ]- balanceErrors- | not (null structuralErrors) = []- | debit /= credit = [UnbalancedAdjustment adjustmentId debit credit]- | otherwise = []- where- (debit, credit) = sideTotals alg--validateWorksheetLinkage- :: HatVal v- => WorksheetLinkage v- -> [WorksheetError source adjustment v]-validateWorksheetLinkage linkage- | not (null invalidAmounts) = invalidAmounts- | otherwise = attributionErrors- ++ ownersLinkErrors- ++ retainedEarningsErrors- ++ balanceSheetErrors- ++ nciErrors- where- invalidAmounts =- [ InvalidLinkAmount field value- | (field, value) <- linkageAmounts linkage- , isErrorValue value- ]-- totalResult = _profitOrLossNetIncome linkage- ownersPlResult = _profitOrLossNetIncomeAttributableToOwners linkage- ownersSsResult = _statementOfChangesNetIncomeAttributableToOwners linkage- nciResult = _nonControllingInterestsPeriodShare linkage-- (attributionLeft, attributionRight) = attributionSides- totalResult ownersPlResult nciResult- attributionErrors =- [ NetIncomeAttributionMismatch attributionLeft attributionRight- | attributionLeft /= attributionRight- ]-- ownersLinkErrors =- [ OwnersPeriodResultLinkMismatch ownersPlResult ownersSsResult- | not (periodResultEquivalent ownersPlResult ownersSsResult)- ]-- (retainedLeft, retainedRight) = rollForwardSides- (_openingRetainedEarnings linkage)- ownersSsResult- (_retainedEarningsDividends linkage)- (_statementOfChangesClosingRetainedEarnings linkage)- retainedEarningsErrors =- [ RetainedEarningsRollForwardMismatch retainedLeft retainedRight- | retainedLeft /= retainedRight- ]-- ssClosing = _statementOfChangesClosingRetainedEarnings linkage- bsClosing = _balanceSheetRetainedEarnings linkage- balanceSheetErrors =- [ BalanceSheetRetainedEarningsMismatch ssClosing bsClosing- | not (balancePositionEquivalent ssClosing bsClosing)- ]-- (nciLeft, nciRight) = rollForwardSides- (_openingNonControllingInterests linkage)- nciResult- (_nonControllingInterestsDividends linkage)- (_statementOfChangesClosingNonControllingInterests linkage)- nciErrors =- [ NonControllingInterestsRollForwardMismatch nciLeft nciRight- | nciLeft /= nciRight- ]- ++ [ BalanceSheetNonControllingInterestsMismatch- ssClosingNci bsNci- | not (balancePositionEquivalent ssClosingNci bsNci)- ]- ssClosingNci =- _statementOfChangesClosingNonControllingInterests linkage- bsNci = _balanceSheetNonControllingInterests linkage--linkageAmounts :: Num v => WorksheetLinkage v -> [(LinkField, v)]-linkageAmounts linkage =- [ (ProfitOrLossNetIncome, periodAmount (_profitOrLossNetIncome linkage))- , (ProfitOrLossNetIncomeAttributableToOwners,- periodAmount (_profitOrLossNetIncomeAttributableToOwners linkage))- , (StatementOfChangesNetIncomeAttributableToOwners,- periodAmount (_statementOfChangesNetIncomeAttributableToOwners linkage))- , (OpeningRetainedEarnings,- balanceAmount (_openingRetainedEarnings linkage))- , (RetainedEarningsDividends, _retainedEarningsDividends linkage)- , (StatementOfChangesClosingRetainedEarnings,- balanceAmount (_statementOfChangesClosingRetainedEarnings linkage))- , (BalanceSheetRetainedEarnings,- balanceAmount (_balanceSheetRetainedEarnings linkage))- , (OpeningNonControllingInterests,- balanceAmount (_openingNonControllingInterests linkage))- , (NonControllingInterestsPeriodShare,- periodAmount (_nonControllingInterestsPeriodShare linkage))- , (NonControllingInterestsDividends,- _nonControllingInterestsDividends linkage)- , (StatementOfChangesClosingNonControllingInterests,- balanceAmount- (_statementOfChangesClosingNonControllingInterests linkage))- , (BalanceSheetNonControllingInterests,- balanceAmount (_balanceSheetNonControllingInterests linkage))- ]--periodAmount :: Num v => PeriodResult v -> v-periodAmount (PeriodProfit value) = value-periodAmount (PeriodLoss value) = value-periodAmount PeriodBreakEven = 0--periodSides :: Num v => PeriodResult v -> (v, v)-periodSides (PeriodProfit value) = (value, 0)-periodSides (PeriodLoss value) = (0, value)-periodSides PeriodBreakEven = (0, 0)---- Total result equals owners' attribution plus NCI attribution. Moving all--- loss-side values across the equation avoids signed scalars.-attributionSides :: Num v- => PeriodResult v- -> PeriodResult v- -> PeriodResult v- -> (v, v)-attributionSides total owners nci =- (totalProfit + ownersLoss + nciLoss,- totalLoss + ownersProfit + nciProfit)- where- (totalProfit, totalLoss) = periodSides total- (ownersProfit, ownersLoss) = periodSides owners- (nciProfit, nciLoss) = periodSides nci--periodResultEquivalent :: (Eq v, Num v)- => PeriodResult v -> PeriodResult v -> Bool-periodResultEquivalent left right =- leftProfit + rightLoss == leftLoss + rightProfit- where- (leftProfit, leftLoss) = periodSides left- (rightProfit, rightLoss) = periodSides right--balancePositionEquivalent :: (Eq v, Num v)- => AccountBalance v -> AccountBalance v -> Bool-balancePositionEquivalent left right =- leftCredit + rightDebit == leftDebit + rightCredit- where- (leftDebit, leftCredit) = balancePair left- (rightDebit, rightCredit) = balancePair right--rollForwardSides :: Num v- => AccountBalance v- -> PeriodResult v- -> v- -> AccountBalance v- -> (v, v)-rollForwardSides opening result dividends closing = case result of- PeriodProfit amount ->- (openingCredit + amount + closingDebit,- openingDebit + dividends + closingCredit)- PeriodLoss amount ->- (openingCredit + closingDebit,- openingDebit + amount + dividends + closingCredit)- PeriodBreakEven ->- (openingCredit + closingDebit,- openingDebit + dividends + closingCredit)- where- (openingDebit, openingCredit) = balancePair opening- (closingDebit, closingCredit) = balancePair closing--sideTotals :: HatVal v => Alg v (HatBase AccountTitles) -> (v, v)-sideTotals alg = (norm (decL alg), norm (decR alg))--toListNE :: NonEmpty a -> [a]-toListNE (x :| xs) = x : xs+import ExchangeAlgebra.Accounting.Consolidation+import ExchangeAlgebra.Accounting.Statements.Metric (PeriodResult(..))+import ExchangeAlgebra.Accounting.TrialBalance.Balance (AccountBalance(..))
src/ExchangeAlgebra/Convert.hs view
@@ -1,34 +1,11 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}--- The alias table intentionally references the deprecated 'Commutation'--- constructor so that its legacy Japanese label ("通信費") is recognized and--- flagged as ambiguous against 'CommunicationExpenses'; silence the warning. {-# OPTIONS_GHC -Wno-deprecations #-}--{- |-Module : ExchangeAlgebra.Convert-Description : Pure conversion between external (side, account-name, amount) data and- exchange-algebra terms. No serialization dependency (Text only).--This module is the dependency-free core of the input-conversion layer: it turns-externally supplied postings---a debit\/credit t'Side', an account name as 'Text',-and an amount---into exchange-algebra 'Alg' terms, and back. The format glue-(JSON\/XML, i.e. @aeson@) deliberately lives outside the published library-(see @examples\/audit-eval\/runner@), so the algebra core stays serialization-free.--The mapping from a debit\/credit side to the @Hat@\/@Not@ marker is derived from-the library's own 'whichSide', so the credit\/debit semantics are never duplicated.-Unknown or wildcard account names are rejected (a /correct-by-construction/ guard-against hallucinated accounts). Account names that are genuinely ambiguous (one-Japanese label shared by several constructors, e.g. @準備預金@ for both the asset-and liability side of reserve deposits) are also rejected, with the candidate-accounts reported, so callers must disambiguate by canonical name.--A fixed-schema CSV reader (@side,account,amount@, optional @note@ column) is-provided in "ExchangeAlgebra.Convert.Csv".+{- |Compatibility import for "ExchangeAlgebra.IO.Input" and+"ExchangeAlgebra.Accounting.Account". -}-module ExchangeAlgebra.Convert+module ExchangeAlgebra.Convert {-# DEPRECATED "Import ExchangeAlgebra.IO.Input or ExchangeAlgebra.Accounting.Account for concreteAccountTitles instead." #-} ( ConvError(..) -- $concreteAccountTitles , concreteAccountTitles@@ -40,188 +17,8 @@ , journalFromSides ) where -import Data.Char (isAlphaNum, isSpace)-import qualified Data.List as L-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra ( Alg, HatVal, Redundant((.+)), (.@) )-import ExchangeAlgebra.Algebra.Base ( AccountTitles(..)- , HatBase(..)- , Hat(..)- , Side(..)- , accountAliases- , whichSide )-import ExchangeAlgebra.Algebra.Base.Account.Registry ( concreteAccountTitles )---- $setup--- The examples use 'Text' literals, so enable @OverloadedStrings@ in doctest.--- >>> :set -XOverloadedStrings---- | Conversion errors. Kept structural so callers can report precisely.-data ConvError = UnknownAccount Text -- ^ name matched no concrete account title- | AmbiguousAccount Text [AccountTitles] -- ^ name matched several accounts (disambiguate by canonical name)- | UnknownSide Text -- ^ side string was not debit\/credit- | MalformedCsv Text -- ^ CSV header\/row was structurally invalid (see "ExchangeAlgebra.Convert.Csv")- | BadAmount Text -- ^ amount field was not a valid non-negative number- deriving (Eq, Show)+import ExchangeAlgebra.IO.Input+import ExchangeAlgebra.Accounting.Account (concreteAccountTitles) -- $concreteAccountTitles--- 'concreteAccountTitles' lists all concrete account titles, /excluding/ the--- wildcard 'AccountTitle', in stable Enum order. It is re-exported from--- "ExchangeAlgebra.Algebra.Base.Account.Registry", which is the single--- definition; external input must never resolve to the wildcard, so callers--- should use this safe range rather than @[minBound .. maxBound]@.------ >>> take 1 concreteAccountTitles--- [Cash]--- >>> Cash `elem` concreteAccountTitles && Sales `elem` concreteAccountTitles--- True--- >>> AccountsPayable `elem` concreteAccountTitles--- True--- >>> AccountTitle `elem` concreteAccountTitles--- False---- | Account-name lookup table: every key (canonical constructor names and the--- Japanese\/abbreviation aliases below) is run through 'normalizeTitle', so matching is--- case-, whitespace-, and symbol-insensitive. Keys are tagged so that a single--- alias shared by several accounts is reported as 'AmbiguousAccount' rather than--- silently resolving to whichever entry happens to come first.------ The canonical English names (derived from 'show' over 'concreteAccountTitles')--- are always unique, so they are never ambiguous and always resolve.-accountTable :: [(Text, [AccountTitles])]-accountTable = collapse [ (normalizeTitle k, a) | (k, a) <- entries ]- where- -- Group entries by normalized key, accumulating every account that key maps- -- to (so genuine collisions become ambiguous, not first-wins).- collapse kvs =- [ (k, L.nub as)- | k <- L.nub (L.map fst kvs)- , let as = [ a | (k', a) <- kvs, k' == k ] ]-- entries :: [(Text, AccountTitles)]- entries = canonical ++ aliases-- -- Canonical: the constructor name. Always unique.- canonical = [ (T.pack (show a), a) | a <- concreteAccountTitles ]-- -- Aliases: Japanese labels (from the Element.hs translation Haddock, which is- -- the canonical bilingual reference) plus a few English abbreviations.- -- Genuinely shared labels are listed for each account; lookup then returns- -- 'AmbiguousAccount' for those, forcing the caller to use a canonical name.- aliases =- [ (alias, title)- | title <- aliasTitleOrder- , alias <- accountAliases title- ]-- -- Preserve the historical candidate order for the one shared label whose- -- alias table order differs from Enum order: "通信費" reports the modern- -- title before the deprecated one.- aliasTitleOrder = CommunicationExpenses- : L.delete CommunicationExpenses concreteAccountTitles---- | Normalize an account name for matching: case-fold, drop punctuation\/symbols--- (keep only alphanumerics and spaces), and collapse internal whitespace to--- single spaces (also trimming). So @\"A\/R\"@ and @\"ar\"@ coincide, and--- @\"Accounts Receivable\"@ matches @\"accounts receivable\"@. CJK characters--- are alphanumeric (Unicode @Lo@), so Japanese labels survive unchanged.------ (Renamed from @norm@: that name collides with the core value-domain--- homomorphism 'ExchangeAlgebra.Algebra.norm', which is an entirely unrelated--- operation — the two must not be confusable in downstream imports.)------ >>> normalizeTitle " Accounts Receivable "--- "accounts receivable"--- >>> normalizeTitle "A/R"--- "ar"-normalizeTitle :: Text -> Text-normalizeTitle = T.unwords . T.words . T.filter (\c -> isAlphaNum c || isSpace c) . T.toLower---- | Parse an account name into a concrete 'AccountTitles'. Unknown names and the--- wildcard are rejected; ambiguous Japanese labels (shared by several accounts)--- are rejected with the candidates reported.------ >>> parseAccountTitle "Cash"--- Right Cash--- >>> parseAccountTitle " accounts receivable "--- Right AccountsReceivable--- >>> parseAccountTitle "Accounts Receivable"--- Right AccountsReceivable--- >>> parseAccountTitle "A/R"--- Right AccountsReceivable--- >>> parseAccountTitle "売掛金"--- Right AccountsReceivable------ A label shared by two accounts (here the asset and liability side of reserve--- deposits) is rejected as ambiguous, listing the candidates so the caller can--- pick a canonical name:------ >>> case parseAccountTitle "準備預金" of { Left (AmbiguousAccount _ as) -> as; _ -> [] }--- [ReserveDepositReceivable,ReserveDepositPayable]--- >>> parseAccountTitle "Goodwill_X"--- Left (UnknownAccount "Goodwill_X")--- >>> parseAccountTitle "AccountTitle"--- Left (UnknownAccount "AccountTitle")-parseAccountTitle :: Text -> Either ConvError AccountTitles-parseAccountTitle t =- case lookup (normalizeTitle t) accountTable of- Just [a] -> Right a- Just as -> Left (AmbiguousAccount t as)- Nothing -> Left (UnknownAccount t)---- | Parse a side string (@"debit"@\/@"credit"@, case-insensitive) into t'Side'.------ >>> parseSide "debit"--- Right Debit--- >>> parseSide "CREDIT"--- Right Credit--- >>> parseSide "left"--- Left (UnknownSide "left")-parseSide :: Text -> Either ConvError Side-parseSide t = case normalizeTitle t of- "debit" -> Right Debit- "credit" -> Right Credit- _ -> Left (UnknownSide t)---- | The @Hat@\/@Not@ marker that places @account@ on the requested debit\/credit--- @side@. Derived from the library's 'whichSide' so the credit\/debit rule is not--- duplicated: an account sits on its \"natural\" side under 'Not'; if the--- requested side differs, the v'Hat' (reversal) marker is used.------ >>> markerForSide Debit Cash--- Not--- >>> markerForSide Credit Cash--- Hat--- >>> markerForSide Credit Sales--- Not--- >>> markerForSide Debit Sales--- Hat-markerForSide :: Side -> AccountTitles -> Hat-markerForSide side account- | whichSide (Not :< account) == side = Not- | otherwise = Hat---- | Build a single posting term: @amount .\@ (marker :< account)@, with the--- marker chosen so the posting lands on @side@. Built through the smart--- constructor '(.@)' so the non-negative\/non-error value invariant is enforced.------ >>> postingFromSide Debit Cash (1000 :: Double)--- 1000.00:@Not:<Cash--- >>> postingFromSide Credit Sales (1000 :: Double)--- 1000.00:@Not:<Sales-postingFromSide :: (HatVal v)- => Side -> AccountTitles -> v -> Alg v (HatBase AccountTitles)-postingFromSide side account amount =- amount .@ (markerForSide side account :< account)---- | Fold a list of @(side, account, amount)@ postings into one algebra term.--- Balance (debit == credit) is /not/ enforced here; check it with the library's--- balance functions after conversion.------ >>> journalFromSides [(Debit, Cash, 1000), (Credit, Sales, 1000)] :: Alg Double (HatBase AccountTitles)--- 1000.00:@Not:<Cash .+ 1000.00:@Not:<Sales-journalFromSides :: (HatVal v)- => [(Side, AccountTitles, v)] -> Alg v (HatBase AccountTitles)-journalFromSides = foldr (\(s, a, v) acc -> postingFromSide s a v .+ acc) mempty+-- Concrete account titles are listed by @concreteAccountTitles@.
src/ExchangeAlgebra/Convert/Checked.hs view
@@ -1,17 +1,9 @@ {-# LANGUAGE FlexibleContexts #-} {-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Convert.Checked-Description : Checked construction for externally generated journal entries.--This module adds a validation layer for LLM- or runner-generated postings before-they are admitted as exchange-algebra terms. The unchecked constructor-'ExchangeAlgebra.Convert.journalFromSides' deliberately preserves its historical-semantics and does not enforce balance; the functions here reject malformed-entries with structural errors instead.+{- |Compatibility import for "ExchangeAlgebra.IO.Input" and+"ExchangeAlgebra.Accounting.Account". -}-module ExchangeAlgebra.Convert.Checked+module ExchangeAlgebra.Convert.Checked {-# DEPRECATED "Import ExchangeAlgebra.IO.Input or ExchangeAlgebra.Accounting.Account for posting policy instead." #-} ( -- $postingPolicy ProcessingContext(..) , EntryError(..)@@ -19,7 +11,6 @@ , JournalCert(..) , SourceError(..) , postingAllowedIn- , exactBalanced , checkedEntryIn , checkedEntry , checkedEntryTextIn@@ -31,540 +22,10 @@ , reconcileSources ) where -import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.HashMap.Strict as HM-import qualified Data.List as L-import qualified Data.Map.Strict as M-import Data.Maybe (mapMaybe)-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra- ( Alg- , ExBaseClass- , Exchange(decL, decR)- , HatVal(..)- , Redundant((.+), norm)- )-import ExchangeAlgebra.Accounting.PostingPolicy- ( ProcessingContext(..)- , postingAllowedIn- , postingCapabilityFor- )-import ExchangeAlgebra.Algebra.Base- ( AccountTitles(..)- , HatBase- , PostingCapability(..)- , Side(..)- )-import ExchangeAlgebra.Convert- ( ConvError- , journalFromSides- , normalizeTitle- , parseAccountTitle- , parseSide- )-import ExchangeAlgebra.Journal- ( Journal- , Note- , (.|)- , toMap- )---- $setup--- >>> :set -XOverloadedStrings--- >>> import Data.List.NonEmpty (NonEmpty)--- >>> import ExchangeAlgebra.Algebra (Alg, Redundant((.+), norm), Exchange(decL))--- >>> import ExchangeAlgebra.Algebra.Base (HatBase, AccountTitles(..), Side(..))--- >>> import ExchangeAlgebra.Convert (journalFromSides)--- >>> import ExchangeAlgebra.Journal (Journal)+import ExchangeAlgebra.IO.Input.Checked hiding (ProcessingContext, postingAllowedIn)+import ExchangeAlgebra.Accounting.Account (ProcessingContext(..), postingAllowedIn) -- $postingPolicy--- 'ProcessingContext' and 'postingAllowedIn' are defined in--- "ExchangeAlgebra.Accounting.PostingPolicy" and re-exported here so the--- historical import path keeps working. The gate itself is accounting-domain--- policy, not an input-adapter concern.---- | Validation errors for a single generated entry.------ Posting positions are 0-origin indices in the input entry.-data EntryError v- = EntryParse Int ConvError- | NonPositiveAmount Int AccountTitles v- | WildcardAccount Int- | WildcardSide Int- | PostingNotAllowed Int AccountTitles PostingCapability ProcessingContext- | EmptyEntry- | Imbalanced { _debitTotal :: v, _creditTotal :: v }- deriving (Show, Eq)---- | Validation errors for a txid-indexed journal batch.-data JournalError n v- = EntryErrors n (NonEmpty (EntryError v))- | DuplicateTxId n- deriving (Show, Eq)---- | Staged certification of a text-originated journal batch.------ A batch can be fully admitted, rejected for accounting or structural--- reasons, or certified as structurally valid and balanced while retaining--- account-title resolution failures for a later vocabulary pass.------ The distinction that motivates the type: an externally generated journal can--- be double-entry valid and still name accounts this chart does not carry. Folding--- both into one 'Either' makes a correct journal in an unsupported vocabulary--- indistinguishable from a wrong one.------ Note the two totals are /batch/ totals, whereas the balance test that gates--- 'BalancedUnresolved' is applied per entry (per txid), matching 'checkedEntry'.--- Per-entry balance implies batch balance, so the invariant--- @_certDebitTotal == _certCreditTotal@ holds, but the converse does not: the--- batch totals alone would not have been a sufficient gate.-data JournalCert n v- = FullyResolved (Journal n v (HatBase AccountTitles))- | BalancedUnresolved- { _certResolved :: [(n, [(Side, AccountTitles, v)])]- , _certUnresolved :: [(n, [(Int, Text, ConvError)])]- , _certDebitTotal :: v- , _certCreditTotal :: v- }- | Rejected (NonEmpty (JournalError n v))- deriving (Show)--instance (HatVal v, Note n) => Eq (JournalCert n v) where- FullyResolved left == FullyResolved right = toMap left == toMap right- BalancedUnresolved resolvedLeft unresolvedLeft debitLeft creditLeft- == BalancedUnresolved resolvedRight unresolvedRight debitRight creditRight =- resolvedLeft == resolvedRight- && unresolvedLeft == unresolvedRight- && debitLeft == debitRight- && creditLeft == creditRight- Rejected left == Rejected right = left == right- _ == _ = False---- | Source-coverage errors between input transactions and a checked journal.-data SourceError n v- = MissingSource n- | UnknownSource n- | AmountMismatch n v v- deriving (Show, Eq)---- | Exact debit-credit balance using strict equality.------ Unlike the existing 'ExchangeAlgebra.Algebra.balance', this uses @(==)@ over--- the value type and therefore does not admit tolerance-based near-matches.------ >>> let Right ok = checkedEntry [(Debit, Cash, 100), (Credit, Sales, 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- >>> exactBalanced ok--- True--- >>> let raw = journalFromSides [(Debit, Cash, 100), (Credit, Sales, 90)] :: Alg Double (HatBase AccountTitles)--- >>> exactBalanced raw--- False-exactBalanced :: (HatVal v, ExBaseClass b) => Alg v b -> Bool-exactBalanced x = norm (decL x) == norm (decR x)---- | Construct one checked entry from parsed postings in a processing context.------ The success value is built with 'journalFromSides', so accepted entries keep--- the same posting semantics as the unchecked conversion path.------ >>> fmap exactBalanced (checkedEntry [(Debit, Cash, 100), (Credit, Sales, 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))--- Right True--- >>> checkedEntry [] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (EmptyEntry :| [])--- >>> checkedEntry [(Debit, Cash, 0), (Credit, Sales, 0)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (NonPositiveAmount 0 Cash 0.0 :| [NonPositiveAmount 1 Sales 0.0])--- >>> checkedEntry [(Debit, AccountTitle, 1), (Side, Cash, 1)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (WildcardAccount 0 :| [WildcardSide 1,Imbalanced {_debitTotal = 1.0, _creditTotal = 0.0}])--- >>> checkedEntry [(Debit, Cash, 100), (Credit, Sales, 90)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (Imbalanced {_debitTotal = 100.0, _creditTotal = 90.0} :| [])--- >>> fmap exactBalanced (checkedEntryIn ClosingProcess [(Debit, Sales, 100), (Credit, IncomeSummary, 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))--- Right True-checkedEntryIn :: (HatVal v)- => ProcessingContext- -> [(Side, AccountTitles, v)]- -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))-checkedEntryIn context rows =- case validateIndexed context (null rows) indexed of- [] -> Right (journalFromSides rows)- e : es -> Left (e :| es)- where- indexed = zip [0..] rows---- | Construct one ordinary-journal entry. This compatibility API delegates--- to 'checkedEntryIn' and therefore rejects closing-only, consolidation-only,--- engine-generated, and non-postable coordinates.-checkedEntry :: (HatVal v)- => [(Side, AccountTitles, v)]- -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))-checkedEntry = checkedEntryIn OrdinaryJournal---- | Parse text-side runner input and then apply 'checkedEntry' validation.------ Parse errors are accumulated as 'EntryParse' instead of failing fast.------ >>> fmap exactBalanced (checkedEntryText [("debit", "Cash", 100), ("credit", "Sales", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))--- Right True--- >>> checkedEntryText [("left", "Cash", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (EntryParse 0 (UnknownSide "left") :| [])--- >>> checkedEntryText [("debit", "Goodwill_X", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))--- Left (EntryParse 0 (UnknownAccount "Goodwill_X") :| [Imbalanced {_debitTotal = 100.0, _creditTotal = 0.0}])-checkedEntryTextIn :: (HatVal v)- => ProcessingContext- -> [(Text, Text, v)]- -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))-checkedEntryTextIn context rows =- case parseErrors ++ structuralErrors ++ textImbalanceErrors of- [] -> Right (journalFromSides (map snd parsedRows))- e : es -> Left (e :| es)- where- parsed = map parseIndexed (zip [0..] rows)- parseErrors = concatMap first parsed- parsedRows = mapMaybe second parsed- sideAmounts = mapMaybe third parsed- structuralErrors = filter (not . isImbalance)- (validateIndexed context (null rows) parsedRows)-- -- Account vocabulary resolution and arithmetic balance are independent.- -- Retain every successfully parsed side/amount pair so an unknown account- -- does not make an otherwise balanced entry look imbalanced.- textImbalanceErrors- | null rows = []- | length sideAmounts /= length rows = []- | any invalidSideAmount sideAmounts = []- | debitTotal == creditTotal = []- | otherwise = [Imbalanced debitTotal creditTotal]- where- debitTotal = L.foldl' (+) 0- [ amount | (Debit, amount) <- sideAmounts ]- creditTotal = L.foldl' (+) 0- [ amount | (Credit, amount) <- sideAmounts ]-- invalidSideAmount (side, amount) =- side == Side || isErrorValue amount || not (amount > 0)-- isImbalance Imbalanced{} = True- isImbalance _ = False-- first (x, _, _) = x- second (_, x, _) = x- third (_, _, x) = x-- parseIndexed (i, (sideText, accountText, amount)) =- let sideResult = parseSide sideText- accountResult = parseAccountTitle accountText- errs =- (case sideResult of- Left err -> [EntryParse i err]- Right _ -> [])- ++ (case accountResult of- Left err -> [EntryParse i err]- Right _ -> [])- parsedRow = case (sideResult, accountResult) of- (Right side, Right account) -> Just (i, (side, account, amount))- _ -> Nothing- sideAmount = case sideResult of- Right side -> Just (side, amount)- Left _ -> Nothing- in (errs, parsedRow, sideAmount)---- | Parse and check an ordinary-journal entry.-checkedEntryText :: (HatVal v)- => [(Text, Text, v)]- -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))-checkedEntryText = checkedEntryTextIn OrdinaryJournal---- | Construct a checked txid-indexed journal.------ Duplicate txids are detected before entry construction, because the 'Journal'--- representation merges equal notes.------ >>> fmap (norm . decL) (checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)])] :: Either (NonEmpty (JournalError String Double)) (Journal String Double (HatBase AccountTitles)))--- Right 100.0--- >>> checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)]), ("tx1", [(Debit, Cash, 5), (Credit, Sales, 5)])] :: Either (NonEmpty (JournalError String Double)) (Journal String Double (HatBase AccountTitles))--- Left (DuplicateTxId "tx1" :| [])--- >>> checkedJournal [("bad", [(Debit, Cash, 1)])] :: Either (NonEmpty (JournalError String Double)) (Journal String Double (HatBase AccountTitles))--- Left (EntryErrors "bad" (Imbalanced {_debitTotal = 1.0, _creditTotal = 0.0} :| []) :| [])-checkedJournalIn :: (HatVal v, Note n, Ord n)- => ProcessingContext- -> [(n, [(Side, AccountTitles, v)])]- -> Either (NonEmpty (JournalError n v)) (Journal n v (HatBase AccountTitles))-checkedJournalIn context entries =- case duplicateErrors ++ entryErrors of- [] -> Right (L.foldl' (.+) mempty journals)- e : es -> Left (e :| es)- where- counts = M.fromListWith (+) [ (txid, 1 :: Int) | (txid, _) <- entries ]- isDuplicate txid = M.findWithDefault 0 txid counts > 1-- duplicateErrors =- [ DuplicateTxId txid- | (txid, count) <- M.toList counts- , count > 1- ]-- checkedUnique =- [ (txid, checkedEntryIn context rows)- | (txid, rows) <- entries- , not (isDuplicate txid)- ]-- entryErrors =- [ EntryErrors txid errs- | (txid, Left errs) <- checkedUnique- ]-- journals =- [ alg .| txid- | (txid, Right alg) <- checkedUnique- ]---- | Construct an ordinary-journal batch.-checkedJournal :: (HatVal v, Note n, Ord n)- => [(n, [(Side, AccountTitles, v)])]- -> Either (NonEmpty (JournalError n v)) (Journal n v (HatBase AccountTitles))-checkedJournal = checkedJournalIn OrdinaryJournal---- | Certify a text-originated journal in stages.------ Duplicate txids and structural errors are rejected before balance is--- considered. Balance is then checked using only parsed sides and amounts,--- independently of account-title resolution. Consequently, a structurally--- valid and balanced batch whose only remaining failures are unknown or--- ambiguous account titles is returned as 'BalancedUnresolved'.-certifyJournalTextIn :: (HatVal v, Note n, Ord n)- => ProcessingContext- -> [(n, [(Text, Text, v)])]- -> JournalCert n v-certifyJournalTextIn context entries =- case duplicateErrors of- e : es -> Rejected (e :| es)- [] -> case structuralErrors of- e : es -> Rejected (e :| es)- [] -> case imbalanceErrors of- e : es -> Rejected (e :| es)- []- | null unresolvedPostings ->- FullyResolved (L.foldl' (.+) mempty journals)- | otherwise ->- BalancedUnresolved- { _certResolved = resolvedEntries- , _certUnresolved = unresolvedEntries- , _certDebitTotal = debitTotal- , _certCreditTotal = creditTotal- }- where- counts = M.fromListWith (+) [ (txid, 1 :: Int) | (txid, _) <- entries ]- duplicateErrors =- [ DuplicateTxId txid- | (txid, count) <- M.toList counts- , count > 1- ]-- parsedEntries =- [ (txid, map parseCertPosting (zip [0..] rows))- | (txid, rows) <- entries- ]-- structuralErrors =- [ EntryErrors txid (err :| errs)- | (txid, rows) <- parsedEntries- , let entryErrors = certStructuralErrors context rows- , err : errs <- [entryErrors]- ]-- imbalanceErrors =- [ EntryErrors txid (Imbalanced debit credit :| [])- | (txid, rows) <- parsedEntries- , let (debit, credit) = certTotals rows- , debit /= credit- ]-- resolvedEntries =- [ (txid, resolved)- | (txid, rows) <- parsedEntries- , let resolved =- [ (side, account, amount)- | (_, _, amount, Right side, Right account) <- rows- ]- , not (null resolved)- ]-- unresolvedEntries =- [ (txid, unresolved)- | (txid, rows) <- parsedEntries- , let unresolved =- [ (i, accountText, err)- | (i, accountText, _, Right _, Left err) <- rows- ]- , not (null unresolved)- ]-- unresolvedPostings = concatMap snd unresolvedEntries-- (debitTotal, creditTotal) =- L.foldl' addTotals (0, 0) (map (certTotals . snd) parsedEntries)-- addTotals (debits, credits) (entryDebits, entryCredits) =- (debits + entryDebits, credits + entryCredits)-- journals =- [ journalFromSides rows .| txid- | (txid, rows) <- resolvedEntries- ]---- | Certify an ordinary-journal batch in stages.-certifyJournalText :: (HatVal v, Note n, Ord n)- => [(n, [(Text, Text, v)])]- -> JournalCert n v-certifyJournalText = certifyJournalTextIn OrdinaryJournal---- The tuple retains the original account text so resolution failures can be--- reported without reconstructing user input.-type CertPosting v =- (Int, Text, v, Either ConvError Side, Either ConvError AccountTitles)---- Wildcard names have to be recognized here rather than delegated to the--- parsers: 'parseAccountTitle' and 'parseSide' reject the wildcard--- constructors by design (the /correct-by-construction/ guard documented in--- "ExchangeAlgebra.Convert"), so on the text path a wildcard is indistinguishable--- from an unknown name. Certification must tell them apart, because a wildcard--- is a structural defect ('Rejected') whereas an unknown name is a vocabulary--- gap ('BalancedUnresolved'). Matching uses the parsers' own 'normalizeTitle',--- so the two paths cannot drift apart.-parseCertPosting :: (Int, (Text, Text, v)) -> CertPosting v-parseCertPosting (i, (sideText, accountText, amount)) =- ( i- , accountText- , amount- , if normalizeTitle sideText == T.pack "side"- then Right Side- else parseSide sideText- , if normalizeTitle accountText == T.pack "accounttitle"- then Right AccountTitle- else parseAccountTitle accountText- )--certStructuralErrors :: (HatVal v)- => ProcessingContext- -> [CertPosting v]- -> [EntryError v]-certStructuralErrors context rows =- [ EmptyEntry | null rows ] ++ concatMap rowErrors rows- where- rowErrors (i, _, amount, sideResult, accountResult) =- [ EntryParse i err | Left err <- [sideResult] ]- ++ [ NonPositiveAmount i (resolvedOrWildcard accountResult) amount- | isErrorValue amount || not (amount > 0)- ]- ++ [ WildcardAccount i | Right AccountTitle <- [accountResult] ]- ++ [ WildcardSide i | Right Side <- [sideResult] ]- ++ [ PostingNotAllowed i account capability context- | Right account <- [accountResult]- , account /= AccountTitle- , let capability = postingCapabilityFor account- , not (postingAllowedIn context capability)- ]-- resolvedOrWildcard (Right account) = account- resolvedOrWildcard (Left _) = AccountTitle--certTotals :: (HatVal v) => [CertPosting v] -> (v, v)-certTotals rows =- ( L.foldl' (+) 0- [ amount | (_, _, amount, Right Debit, _) <- rows ]- , L.foldl' (+) 0- [ amount | (_, _, amount, Right Credit, _) <- rows ]- )---- | Compare source transactions against the note-indexed journal coverage.------ The journal side is inspected with 'toMap', giving the per-note 'Alg'. The--- actual amount is each note's debit-side total, @norm . decL@.------ >>> let Right j = checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)])] :: Either (NonEmpty (JournalError String Double)) (Journal String Double (HatBase AccountTitles))--- >>> reconcileSources [("tx1", 100)] j--- []--- >>> reconcileSources [("tx1", 90)] j--- [AmountMismatch "tx1" 90.0 100.0]--- >>> reconcileSources [("tx1", 100), ("tx2", 5)] j--- [MissingSource "tx2"]--- >>> let Right j2 = checkedJournal [("tx2", [(Debit, Cash, 5), (Credit, Sales, 5)])] :: Either (NonEmpty (JournalError String Double)) (Journal String Double (HatBase AccountTitles))--- >>> reconcileSources [("tx1", 100)] (j .+ j2)--- [UnknownSource "tx2"]-reconcileSources :: (HatVal v, Note n, Ord n)- => [(n, v)]- -> Journal n v (HatBase AccountTitles)- -> [SourceError n v]-reconcileSources sources journal =- missing ++ unknown ++ mismatched- where- expected = M.fromList sources- actual = M.fromList (HM.toList (toMap journal))-- missing =- [ MissingSource txid- | (txid, _) <- sources- , M.notMember txid actual- ]-- unknown =- [ UnknownSource txid- | txid <- M.keys actual- , M.notMember txid expected- ]-- mismatched =- [ AmountMismatch txid expectedAmount actualAmount- | (txid, expectedAmount) <- sources- , Just alg <- [M.lookup txid actual]- , let actualAmount = norm (decL alg)- , expectedAmount /= actualAmount- ]--validateIndexed :: (HatVal v)- => ProcessingContext- -> Bool- -> [(Int, (Side, AccountTitles, v))]- -> [EntryError v]-validateIndexed context rawEmpty rows =- emptyErrors ++ rowErrors ++ imbalanceErrors- where- emptyErrors = [ EmptyEntry | rawEmpty ]-- rowErrors = concatMap validateRow rows-- validateRow (i, (side, account, amount)) =- [ NonPositiveAmount i account amount- | invalidAmount amount- ]- ++ [ WildcardAccount i | account == AccountTitle ]- ++ [ WildcardSide i | side == Side ]- ++ [ PostingNotAllowed i account capability context- | account /= AccountTitle- , let capability = postingCapabilityFor account- , not (postingAllowedIn context capability)- ]-- invalidAmount amount = isErrorValue amount || not (amount > 0)-- imbalanceErrors- | null rows = []- | otherwise =- let (debitTotal, creditTotal) = totals rows rowErrors- in [ Imbalanced debitTotal creditTotal | debitTotal /= creditTotal ]--totals :: (HatVal v)- => [(Int, (Side, AccountTitles, v))]- -> [EntryError v]- -> (v, v)-totals rows rowErrors- | null rowErrors =- let alg = journalFromSides (map snd rows)- in (norm (decL alg), norm (decR alg))- | otherwise =- let debitTotal = L.foldl' (+) 0- [ amount | (_, (Debit, _, amount)) <- rows ]- creditTotal = L.foldl' (+) 0- [ amount | (_, (Credit, _, amount)) <- rows ]- in (debitTotal, creditTotal)+-- 'checkedEntryIn' takes a t'ProcessingContext' as its first argument.+-- 'postingAllowedIn' checks whether each account's posting capability is allowed+-- in that context.
src/ExchangeAlgebra/Convert/Csv.hs view
@@ -1,37 +1,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}--{- |-Module : ExchangeAlgebra.Convert.Csv-Description : Fixed-schema CSV reader for general journal postings.--A deliberately tiny, dependency-free (Text + scientific only) reader for a fixed-journal CSV schema, building on the pure normalization\/parsing in-"ExchangeAlgebra.Convert". It is the read counterpart of the report\/ledger CSV-writers in "ExchangeAlgebra.Write" (writing is /not/ handled here).--The schema is a header line @side,account,amount@ with an optional trailing-@note@ column:--> side,account,amount-> debit,Cash,1000-> credit,Sales,1000--Blank lines and lines whose first non-space character is @#@ are skipped;-surrounding whitespace on each field is trimmed; there is no quoting. The field-splitter 'splitTrim' is exported and shared with the equally minimal-@parseEdgeCsv@\/@parseCoefCsv@ readers in "ExchangeAlgebra.Simulate.Network", so-the two readers cannot drift apart on how a line is split.--@account@ is resolved with 'parseAccountTitle' (canonical English names or the-Japanese\/abbreviation aliases), so unknown and ambiguous account names are-rejected. @side@ is resolved with 'parseSide'. @amount@ is parsed by a-caller-supplied function so the value type @v@ stays open; 'scientificAmount'-covers the common case (a non-negative decimal literal via 'Data.Scientific').-All values are placed through the non-negative smart constructor @(.@)@ inside-'postingFromSide'.+{- |Compatibility import for "ExchangeAlgebra.IO.Input.Csv". -}-module ExchangeAlgebra.Convert.Csv+module ExchangeAlgebra.Convert.Csv {-# DEPRECATED "Import ExchangeAlgebra.IO.Input.Csv instead." #-} ( -- * Parsing journal CSV parseJournalCsv , parseJournalCsvWith@@ -43,133 +14,4 @@ , splitTrim ) where -import qualified Data.Scientific as Sci-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra ( Alg, HatVal, Redundant((.+)) )-import ExchangeAlgebra.Algebra.Base ( AccountTitles, HatBase, Side )-import ExchangeAlgebra.Convert ( ConvError(..)- , parseAccountTitle- , parseSide- , postingFromSide )---- $setup--- >>> :set -XOverloadedStrings--- >>> import ExchangeAlgebra.Algebra (Alg)--- >>> import ExchangeAlgebra.Algebra.Base (HatBase, AccountTitles)---- | Parse a non-negative decimal amount via 'Data.Scientific' (e.g. @"1000"@,--- @"1000.50"@, @"1.5e3"@), converting to the value type @v@ exactly through--- 'toRational' (so exact-decimal types such as @MoneyDecimal@ keep their--- precision for terminating decimals). Negative amounts are rejected here so the--- error is reported as a 'BadAmount' rather than surfacing later from @(.@)@.------ >>> scientificAmount "1000.50" :: Either ConvError Double--- Right 1000.5--- >>> scientificAmount "-3" :: Either ConvError Double--- Left (BadAmount "-3")--- >>> scientificAmount "1,000" :: Either ConvError Double--- Left (BadAmount "1,000")-scientificAmount :: (Fractional v) => Text -> Either ConvError v-scientificAmount t =- case reads (T.unpack (T.strip t)) :: [(Sci.Scientific, String)] of- [(s, "")] | s >= 0 -> Right (fromRational (toRational s))- | otherwise -> Left (BadAmount t)- _ -> Left (BadAmount t)---- | Parse a fixed-schema journal CSV into a single algebra term, using--- 'scientificAmount' for the amount column. The note column, if present, is--- ignored at the 'Alg' level (use 'parseNotedJournalCsv' to keep notes).------ >>> let csv = "side,account,amount\ndebit,Cash,1000\ncredit,Sales,1000\n"--- >>> parseJournalCsv csv :: Either ConvError (Alg Double (HatBase AccountTitles))--- Right 1000.00:@Not:<Cash .+ 1000.00:@Not:<Sales-parseJournalCsv :: (HatVal v, Fractional v)- => Text -> Either ConvError (Alg v (HatBase AccountTitles))-parseJournalCsv = parseJournalCsvWith scientificAmount---- | As 'parseJournalCsv', but with a caller-supplied amount parser, so the value--- type @v@ need not be 'Fractional' (e.g. an integral or fixed-point reader).-parseJournalCsvWith :: (HatVal v)- => (Text -> Either ConvError v)- -> Text- -> Either ConvError (Alg v (HatBase AccountTitles))-parseJournalCsvWith amount txt = do- rows <- parseRows amount txt- Right (foldr (\(s, a, v) acc -> postingFromSide s a v .+ acc) mempty rows)---- | Parse the rows of a journal CSV into @(Side, AccountTitles, value, note)@--- tuples, where @note@ is the optional 4th column (empty 'Text' if absent). The--- note is returned as raw 'Text' so the caller can key an--- 'ExchangeAlgebra.Journal.Journal' by it (the--- 'ExchangeAlgebra.Journal.Note' instance for 'Text' makes the @(.|)@ operator--- immediately applicable); this reader deliberately stays at the algebra--- level and does not construct an 'ExchangeAlgebra.Journal.Journal' itself, to--- avoid pulling the Journal module into the dependency-light conversion layer.------ >>> let csv = "side,account,amount,note\ndebit,Cash,1000,opening\n"--- >>> parseNotedJournalCsv scientificAmount csv :: Either ConvError [(Side, AccountTitles, Double, Text)]--- Right [(Debit,Cash,1000.0,"opening")]-parseNotedJournalCsv :: (HatVal v)- => (Text -> Either ConvError v)- -> Text- -> Either ConvError [(Side, AccountTitles, v, Text)]-parseNotedJournalCsv amount txt = do- keptHeader <- splitHeader txt- traverse (rowNoted amount) keptHeader---- | Split one line on commas and strip surrounding whitespace from each field.--- No quoting is recognized, so a comma inside a field always separates. An--- empty line yields a single empty field, matching 'T.splitOn'.-splitTrim :: Text -> [Text]-splitTrim = map T.strip . T.splitOn ","----------------------------------------------------------------------- Internal: minimal CSV plumbing (no quoting), header check, row parse.----------------------------------------------------------------------- Parse to bare (Side, AccountTitles, v) triples (note dropped).-parseRows :: (Text -> Either ConvError v)- -> Text- -> Either ConvError [(Side, AccountTitles, v)]-parseRows amount txt = do- keptHeader <- splitHeader txt- traverse (\fs -> (\(s, a, v, _) -> (s, a, v)) <$> rowNoted amount fs) keptHeader---- | Drop blank\/comment lines, validate the header (@side,account,amount@ with an--- optional @note@ column), and return the remaining data rows as already-split,--- trimmed field lists.-splitHeader :: Text -> Either ConvError [[Text]]-splitHeader txt =- case keptLines of- [] -> Left (MalformedCsv "empty CSV (no header)")- (h : body)- | header == ["side", "account", "amount"]- || header == ["side", "account", "amount", "note"]- -> Right (map splitTrim body)- | otherwise -> Left (MalformedCsv- ("unexpected header: " <> T.intercalate "," header- <> " (expected side,account,amount[,note])"))- where header = map T.toLower (splitTrim h)- where- keptLines = filter keep (T.lines txt)- keep l = let s = T.strip l in not (T.null s) && not ("#" `T.isPrefixOf` s)---- | Parse one already-split row into @(Side, AccountTitles, value, note)@.-rowNoted :: (Text -> Either ConvError v)- -> [Text]- -> Either ConvError (Side, AccountTitles, v, Text)-rowNoted amount fields = case fields of- [s, a, v] -> build s a v ""- [s, a, v, n] -> build s a v n- other -> Left (MalformedCsv- ("row expected 3 or 4 fields, got "- <> T.pack (show (length other)) <> ": "- <> T.intercalate "," other))- where- build s a v n = do- side <- parseSide s- account <- parseAccountTitle a- value <- amount v- Right (side, account, value, n)+import ExchangeAlgebra.IO.Input.Csv
− src/ExchangeAlgebra/Foundation.hs
@@ -1,138 +0,0 @@-{- |- Module : ExchangeAlgebra.Foundation- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- The redundant algebra itself, without any accounting vocabulary: the- names that realize Definitions 1-6 of Akagi (2026), Appendix A- (elements, bases, hat bases, the redundant algebra with its Hat, Bar,- scalar and norm operations, and the base-generic projections and- substitutions).-- Everything here is re-exported item by item from- "ExchangeAlgebra.Algebra.Base.Element", "ExchangeAlgebra.Algebra.Base"- and "ExchangeAlgebra.Algebra.Internal"; nothing is defined in this- module, so a value built through this umbrella is the same type as one- built through "ExchangeAlgebra.Algebra". What this umbrella deliberately- leaves out is the accounting layer: 'ExchangeAlgebra.Accounting.ExBaseClass',- account titles, sides, and transfers live in "ExchangeAlgebra.Accounting",- and the account registry never appears here.-- Two notes on the surface:-- * t'Hat' has three constructors. v'Hat' and 'Not' are the two posting states- of the paper (decrease / increase). 'HatNot' is the query wildcard- (@'wildcard' :: t'Hat'@) used by 'proj' and by transfer patterns; it is- not a third posting state and must not be used to build postings.- * The laws of Definition 6 hold on the ℘-observation (the per-base- multiset read through 'bar'); see the Haddock of 'mapBasePart' and- 'extendBy' for the raw / ℘ distinction.-- >>> import ExchangeAlgebra.Foundation- >>> let x = 10 .@ Not :< Yen .+ 4 .@ Hat :< Yen :: Alg Double (HatBase CountUnit)- >>> norm x- 14.0- >>> norm (bar x)- 6.0--}--module ExchangeAlgebra.Foundation- ( -- * Definition 1: elements- Element(..)- , AxisDecompose(..)- , (.#)- , Name- , Subject- , CountUnit(..)- -- * Definition 2: bases- , BaseClass(..)- -- * Definition 3: hat bases- , Hat(..)- , HatBaseClass(..)- , HatBase(..)- -- * Definition 4: value domain- , HatVal(..)- , Nearly(..)- , isNearlyNum- , nearlyEqScaled- -- * Definitions 4-6: the redundant algebra- , Alg(Zero, (:@), _val, _hatBase)- , Redundant(..)- , isZero- , (.@)- , (<@)- -- ** Observation- , vals- , bases- , toList- , toASCList- , foldEntries- , foldEntriesToMap- -- ** Construction- , fromList- , sigma- , sigma2When- , sigmaFromMap- , unionsMerge- -- ** Substitution and relabelling- , map- , mapPosting- , mapMaybePosting- , mapBasePart- , extendBy- -- ** Base-generic projection and decomposition- , filter- , proj- , decBy- , postFromNetBy- ) where--import Prelude hiding (map, filter)--import ExchangeAlgebra.Algebra.Base.Element- ( Element(..)- , AxisDecompose(..)- , (.#)- , Name- , Subject- , CountUnit(..)- )-import ExchangeAlgebra.Algebra.Base- ( BaseClass(..)- , Hat(..)- , HatBaseClass(..)- , HatBase(..)- )-import ExchangeAlgebra.Algebra.Internal- ( HatVal(..)- , Nearly(..)- , isNearlyNum- , nearlyEqScaled- , Alg(Zero, (:@), _val, _hatBase)- , Redundant(..)- , isZero- , (.@)- , (<@)- , vals- , bases- , toList- , toASCList- , foldEntries- , foldEntriesToMap- , fromList- , sigma- , sigma2When- , sigmaFromMap- , unionsMerge- , map- , mapPosting- , mapMaybePosting- , mapBasePart- , extendBy- , filter- , proj- , decBy- , postFromNetBy- )
+ src/ExchangeAlgebra/IO/Input.hs view
@@ -0,0 +1,43 @@+-- | Parse and validate externally supplied bookkeeping entries.+--+-- Import this module to convert account names and sides or validate entries.+-- "ExchangeAlgebra.IO.Input.Csv" reads fixed-schema CSV, and+-- "ExchangeAlgebra.IO.Input.Assist" describes accounts and errors.+--+-- | Task | Import |+-- | :--- | :--- |+-- | Convert names and sides | "ExchangeAlgebra.IO.Input" |+-- | Validate entries | "ExchangeAlgebra.IO.Input" |+-- | Parse CSV | "ExchangeAlgebra.IO.Input.Csv" |+-- | Explain validation feedback | "ExchangeAlgebra.IO.Input.Assist" |+module ExchangeAlgebra.IO.Input+ ( ConvError(..)+ , normalizeTitle+ , parseAccountTitle+ , parseSide+ , markerForSide+ , postingFromSide+ , journalFromSides+ , EntryError(..)+ , JournalError(..)+ , JournalCert(..)+ , SourceError(..)+ , checkedEntryIn+ , checkedEntry+ , checkedEntryTextIn+ , checkedEntryText+ , checkedJournalIn+ , checkedJournal+ , certifyJournalTextIn+ , certifyJournalText+ , reconcileSources+ ) where++import ExchangeAlgebra.IO.Input.Conversion+ ( ConvError(..), normalizeTitle, parseAccountTitle, parseSide+ , markerForSide, postingFromSide, journalFromSides )+import ExchangeAlgebra.IO.Input.Checked+ ( EntryError(..), JournalError(..), JournalCert(..), SourceError(..)+ , checkedEntryIn, checkedEntry, checkedEntryTextIn, checkedEntryText+ , checkedJournalIn, checkedJournal, certifyJournalTextIn+ , certifyJournalText, reconcileSources )
+ src/ExchangeAlgebra/IO/Input/Admission.hs view
@@ -0,0 +1,214 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Check the accounting integrity of a journal written by an untrusted+-- author, such as a language model, and check it against independent+-- records.+--+-- 'admit' takes the complete journal and checks every transaction (note) on+-- its own: positive finite values, a @Hat@ or @Not@ side on every posting,+-- postable accounts, and equal debit (@decL@) and credit (@decR@) totals. It+-- returns the journal unchanged as @Admitted@, a type that only 'admit'+-- constructs. 'checkEvidence' then compares an admitted journal with expected+-- elements, after a selection chosen by the caller.+--+-- = What admission does not do+--+-- Admission does not post or derive entries. Adjusting and closing entries+-- are computed by the author with the accounting functions (for example+-- "ExchangeAlgebra.Accounting.Adjustment" and @closingEntries@) and submitted+-- in the journal; opening balances supplied by a trusted party are also+-- submitted in the journal and confirmed with 'checkEvidence'. Admission does+-- not compare the journal with the answer of an exercise, and does not+-- restrict which accounts a submission may use beyond the accounts no process+-- may post. Such rules belong to the experiment or the tool that calls the+-- library. For example, a submission that moves sales directly to retained+-- earnings is admitted; only a comparison with a reference answer finds it.+-- Each transaction is checked on its own, so admission also does not check+-- the balances carried across notes or that the closing entries are the ones+-- the ledger implies; recompute the closing entries from the admitted journal+-- when that matters.+--+-- After admission, derive reports from 'admittedJournal' with the existing+-- functions: pass the trial balance and its stage to+-- @ExchangeAlgebra.Accounting.Statements.Presentation.present@, read the+-- trial-balance findings separately with+-- @ExchangeAlgebra.Accounting.TrialBalance.trialBalanceFindings@, and write+-- rows with the CSV writers in "ExchangeAlgebra.IO.Output.Statements".+--+-- = Guarantee+--+-- @Admitted@ and @AdmissionFailure@ have package-private constructors and no+-- instance that rebuilds a value from its contents. The guarantee assumes+-- callers do not use @unsafeCoerce@ or internal modules. The EA modules are+-- not marked Safe or Trustworthy.+--+-- = Walkthrough+--+-- The examples below follow one shop through one period: write the journal,+-- add an adjusting entry and the closing entries, admit the journal, confirm+-- it against the bank record, and derive the statements. Every example is+-- checked by the doctest suite.+--+-- == 1. Imports+--+-- "ExchangeAlgebra.Journal.Core" exports names such as @map@ and @fromList@+-- that clash with the Prelude and with the umbrella "ExchangeAlgebra", so+-- import it qualified; importing this module qualified as well keeps each+-- step visible as @Admission.admit@ or @Admission.checkEvidence@. The+-- examples are GHCi input, run in order; the transaction keys need+-- @OverloadedStrings@ (in a source file, write the @LANGUAGE@ pragma instead+-- of @:set@).+--+-- > :set -XOverloadedStrings+-- > import qualified ExchangeAlgebra.Journal.Core as Journal+-- > import ExchangeAlgebra.Journal.Core ((.|))+-- > import qualified ExchangeAlgebra.IO.Input.Admission as Admission+-- > import ExchangeAlgebra.IO.Input.Admission (TxKey(..), EntityId(..), PeriodId(..), TxId(..))+-- > import qualified ExchangeAlgebra.Accounting.Adjustment as Adjustment+-- > import ExchangeAlgebra+--+-- == 2. Write the journal+--+-- A journal keeps each transaction under its own note. t'TxKey' names the+-- entity, the reporting period, and the transaction; @.\@@ builds a posting+-- (@Not@ for the usual side of the account, @Hat@ for the other side), and+-- @.|@ attaches a transaction to its note. The opening balances come from a+-- trusted party and are part of the journal.+--+-- >>> type Submission = Journal.Journal TxKey MoneyDecimal (HatBase AccountTitles)+-- >>> let key name = TxKey (EntityId "shop") (PeriodId "2026") (TxId name)+-- >>> let opening = (500 .@ Not :< Cash .+ 500 .@ Not :< CapitalStock) .| key "opening" :: Submission+-- >>> let sale = (100 .@ Not :< Cash .+ 100 .@ Not :< Sales) .| key "sale" :: Submission+-- >>> let supplies = (40 .@ Not :< SuppliesExpenses .+ 40 .@ Hat :< Cash) .| key "supplies" :: Submission+--+-- == 3. Add the adjusting and closing entries+--+-- The author computes the adjusting and closing entries with the accounting+-- functions and adds them to the journal; admission does not derive them. Each+-- adjustment reads the balances it needs from the ledger and returns only the+-- entry to add. Here 15 of the supplies are unused at the end of the period.+--+-- >>> let ledger = Journal.toAlg (opening .+ sale .+ supplies)+-- >>> let Right unused = Adjustment.suppliesOnHand 15 ledger+-- >>> let adjusted = opening .+ sale .+ supplies .+ (unused .| key "adjust supplies")+-- >>> let Right closing = closingEntries (Journal.toAlg adjusted)+-- >>> let submitted = adjusted .+ (closing .| key "close")+--+-- == 4. Admit the journal+--+-- 'admit' returns the journal unchanged when every transaction passes, or+-- every violation it found. 'failureErrors' lists them in ascending note+-- order. A violation of one posting ('NonPositiveAmount', 'WildcardSide',+-- 'NotPostableAccount') names the note and the base of the posting; an+-- 'Imbalanced' transaction names the note with its debit and credit totals.+--+-- >>> either (const "rejected") (const "admitted") (Admission.admit submitted)+-- "admitted"+-- >>> let Right accepted = Admission.admit submitted+--+-- A transaction whose debit and credit totals differ, or a posting on an+-- account that no process may post (here the wildcard @AccountTitle@), is+-- rejected:+--+-- >>> let unbalanced = (100 .@ Not :< Cash .+ 90 .@ Not :< Sales) .| key "sale" :: Submission+-- >>> either (Left . Admission.failureErrors) (const (Right ())) (Admission.admit unbalanced)+-- Left (Imbalanced (TxKey (EntityId "shop") (PeriodId "2026") (TxId "sale")) 100 90 :| [])+-- >>> let unpostable = (100 .@ Not :< Cash .+ 100 .@ Not :< AccountTitle) .| key "sale" :: Submission+-- >>> either (Left . Admission.failureErrors) (const (Right ())) (Admission.admit unpostable)+-- Left (NotPostableAccount (TxKey (EntityId "shop") (PeriodId "2026") (TxId "sale")) Not:<AccountTitle :| [])+--+-- == 5. Check the journal against independent records+--+-- 'checkEvidence' selects part of the admitted journal and compares it with+-- expected elements: each expected element must match exactly one selected+-- element, and the values must be equal. Selected elements that nothing+-- expects are ignored. The expected element is a query: a concrete note and+-- base, as in this example, match only the same note and base, while a+-- @plank@ note or a wildcard axis of the expected base (such as the account+-- @AccountTitle@) matches any value on that axis. Here the bank records the+-- opening deposit and the cash receipt of the sale; the selection keeps the+-- cash receipts (@Not :< Cash@), so the payment for supplies (@Hat :< Cash@)+-- is not compared.+--+-- >>> let bank = ((500 .@ Not :< Cash) .| key "opening") .+ ((100 .@ Not :< Cash) .| key "sale") :: Submission+-- >>> Admission.checkEvidence accepted (Journal.projWithBase [Not :< Cash]) bank+-- Right ()+--+-- A record that disagrees with the journal is reported with the expected and+-- the found value:+--+-- >>> let wrongBank = (110 .@ Not :< Cash) .| key "sale" :: Submission+-- >>> Admission.checkEvidence accepted (Journal.projWithBase [Not :< Cash]) wrongBank+-- Left (EvidenceDiffers (TxKey (EntityId "shop") (PeriodId "2026") (TxId "sale")) Not:<Cash 110 100 :| [])+--+-- == 6. Derive the trial balance and the statements+--+-- Reports come from 'admittedJournal' with the existing functions. The+-- statements of the period are presented from the trial balance before+-- closing, so drop the closing note; @Presentation.present@ takes the trial+-- balance together with its stage.+--+-- >>> import qualified ExchangeAlgebra.Accounting.Statements.Presentation as Presentation+-- >>> import qualified ExchangeAlgebra.Accounting.TrialBalance as TrialBalance+-- >>> let beforeClosing = Journal.toAlg (Journal.filterWithNote (\n _ -> n /= key "close") (Admission.admittedJournal accepted))+-- >>> let Right statements = Presentation.present (Presentation.jcciSecondGradeContext Presentation.Standalone) TrialBalance.BeforeClosing beforeClosing+--+-- The rows of the income statement show the sales, the supplies used in the+-- period (40 bought, 15 unused), and the net income. The label column holds+-- the Japanese account names and is left out here.+--+-- >>> import ExchangeAlgebra.IO.Output.Statements (financialStatementRows)+-- >>> let Right rows = financialStatementRows [Presentation.RevenueSection, Presentation.ExpenseSection] statements+-- >>> [(account, side, amount) | [_, account, _, _, side, amount] <- rows]+-- [("account","side","amount"),("Sales","Credit","100"),("SuppliesExpenses","Debit","25"),("","Credit","75")]+--+-- The audit of the trial balance is separate from presentation:+--+-- >>> import qualified Data.Map.Strict as Map+-- >>> import qualified Data.Set as Set+-- >>> TrialBalance.trialBalanceFindings (TrialBalance.TrialBalanceInput beforeClosing TrialBalance.BeforeClosing Map.empty [] Set.empty)+-- []+module ExchangeAlgebra.IO.Input.Admission+ ( -- * Transaction keys+ -- | A ready-made note type for journals keyed by entity, reporting+ -- period, and transaction. Any @ExchangeAlgebra.Journal.Core.Note@ works.+ EntityId(..)+ , PeriodId(..)+ , TxId(..)+ , TxKey(..)+ -- * Admission+ , admit+ , Admitted+ , admittedJournal+ , AdmissionFailure+ , failureErrors+ , AdmissionError(..)+ -- * Evidence+ , checkEvidence+ , EvidenceMismatch(..)+ ) where++import ExchangeAlgebra.Accounting.Transaction (EntityId(..), PeriodId(..), TxId(..), TxKey(..))+import ExchangeAlgebra.IO.Input.Admission.Diagnostic+ ( AdmissionError(..)+ , AdmissionFailure+ , EvidenceMismatch(..)+ , failureErrors+ )+import ExchangeAlgebra.IO.Input.Admission.Engine (admit)+import ExchangeAlgebra.IO.Input.Admission.Evidence (checkEvidence)+import ExchangeAlgebra.IO.Input.Admission.Representation (Admitted, admittedJournal)++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import qualified ExchangeAlgebra.Journal.Core as Journal+-- >>> import ExchangeAlgebra.Journal.Core ((.|))+-- >>> import qualified ExchangeAlgebra.IO.Input.Admission as Admission+-- >>> import ExchangeAlgebra.IO.Input.Admission (TxKey(..), EntityId(..), PeriodId(..), TxId(..))+-- >>> import qualified ExchangeAlgebra.Accounting.Adjustment as Adjustment+-- >>> import ExchangeAlgebra.Algebra+-- >>> import ExchangeAlgebra.Algebra.Base+-- >>> import ExchangeAlgebra.Accounting.Account+-- >>> import ExchangeAlgebra.Accounting.Exchange+-- >>> import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+-- >>> import ExchangeAlgebra.Accounting.Closing (closingEntries)
+ src/ExchangeAlgebra/IO/Input/Admission/Diagnostic.hs view
@@ -0,0 +1,59 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Admission and evidence failures at the IO input boundary. This+-- package-private module defines the vocabulary; the public module+-- "ExchangeAlgebra.IO.Input.Admission" exports every type with its+-- constructors except @AdmissionFailure@.+module ExchangeAlgebra.IO.Input.Admission.Diagnostic where++import Data.List.NonEmpty (NonEmpty)++import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++-- | One accounting-integrity violation of one transaction. A transaction is+-- the element of one note @n@ of the submitted journal; @b@ is the base of the+-- offending posting. Every check needs only the transaction itself, not the+-- other transactions, opening balances, or the answer of an exercise.+data AdmissionError n b+ = NonPositiveAmount n b MoneyDecimal+ -- ^ A posting whose value is zero or negative (t'MoneyDecimal' has no+ -- non-finite value).+ | WildcardSide n b+ -- ^ A posting whose side is the wildcard @HatNot@. A ledger posting is+ -- on @Hat@ or @Not@; @HatNot@ is a query pattern.+ | NotPostableAccount n b+ -- ^ A posting on an account that no process may post, such as the+ -- wildcard @AccountTitle@ (@NotPostable@).+ | Imbalanced n MoneyDecimal MoneyDecimal+ -- ^ The debit (@decL@) and credit (@decR@) totals of the transaction,+ -- in that order. Each total is the exact sum rounded once. Not reported+ -- for a transaction that has a @WildcardSide@ or @NotPostableAccount@+ -- posting: its side or its account cannot be classified.+ deriving (Eq, Show)++-- | All violations found by one @admit@ run. The constructor is+-- package-private, so a value always holds at least one violation found by+-- @admit@.+newtype AdmissionFailure n b = AdmissionFailure (NonEmpty (AdmissionError n b))+ deriving (Eq, Show)++-- | Violations in ascending note order; within a note, postings in ascending+-- base order, and @Imbalanced@ last.+failureErrors :: AdmissionFailure n b -> NonEmpty (AdmissionError n b)+failureErrors (AdmissionFailure errors) = errors++-- | Why one expected element did not match the selected journal. @m@ and @b@+-- are the note and base of the expected element.+data EvidenceMismatch m b+ = EvidenceMissing m b MoneyDecimal+ -- ^ No selected element matches the note and base. The value is the+ -- expected one.+ | EvidenceDiffers m b MoneyDecimal MoneyDecimal+ -- ^ Exactly one selected element matches the note and base, with a+ -- different value: expected, then found.+ | EvidenceAmbiguous m b [MoneyDecimal]+ -- ^ Two or more selected elements match the note and base; their values+ -- in ascending note and base order. Combine them into one element in+ -- the selection, by @bar@ for the two sides of one base, or by a+ -- transfer for different accounts.+ deriving (Eq, Show)
+ src/ExchangeAlgebra/IO/Input/Admission/Engine.hs view
@@ -0,0 +1,89 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Package-private implementation of @admit@.+module ExchangeAlgebra.IO.Input.Admission.Engine+ ( admit+ ) where++import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.HashMap.Strict as HashMap++import ExchangeAlgebra.Accounting.Account (PostingCapability(NotPostable), postingCapabilityFor)+import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(..), Exchange(decL, decR))+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass(hat), Hat(..))+import qualified ExchangeAlgebra.Algebra.Core as Algebra+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.IO.Input.Admission.Diagnostic (AdmissionFailure(..), AdmissionError(..))+import ExchangeAlgebra.IO.Input.Admission.Representation (Admitted(..))+import ExchangeAlgebra.Journal.Core (Journal, Note)+import qualified ExchangeAlgebra.Journal.Core as Journal++-- | Check the accounting integrity of every transaction (note) of a journal.+-- For each posting of each note:+--+-- * the value is positive (@NonPositiveAmount@);+-- * the side is @Hat@ or @Not@, not the wildcard @HatNot@ (@WildcardSide@);+-- * the account can be posted by some process, that is, its posting+-- capability is not @NotPostable@ (@NotPostableAccount@). Capabilities that+-- depend on the process (closing only, engine generated only) are not+-- checked: admission does not know the process.+--+-- For each note without a @WildcardSide@ or @NotPostableAccount@ posting, the+-- exact sum of the values whose @decL@ side is Debit equals the exact sum of+-- those whose side is Credit (@Imbalanced@). The sides come from @decL@ (Debit) and+-- @decR@ (Credit), not from the @Hat@ / @Not@ sign: recording a cash sale is+-- @100 .\@ Not :< Cash .+ 100 .\@ Not :< Sales@, debit Cash and credit Sales.+--+-- Every note is checked and every violation is returned; the run does not+-- stop at the first one. On success the journal is returned unchanged inside+-- @Admitted@: no note is added or removed, same-base postings are not+-- aggregated, and @bar@ is not applied.+--+-- Not checked: duplicated transaction keys (two additions with the same note+-- become one element of the journal, so the journal does not keep the+-- boundary; check keys before building the journal), balances across+-- transactions, closing, agreement with independent records (use+-- @checkEvidence@), and agreement with the answer of an exercise.+--+-- Laws:+--+-- * @admit j == Right a@ implies @admittedJournal a == j@.+-- * The checks are per note: @admit (j1 .+ j2)@ succeeds when @admit j1@ and+-- @admit j2@ succeed and @j1@ and @j2@ have disjoint notes.+-- * @admit Zero@ succeeds.+admit :: (Note n, ExBaseClass b)+ => Journal n MoneyDecimal b+ -> Either (AdmissionFailure n b) (Admitted n b)+admit journal = case errors of+ [] -> Right (Admitted journal)+ first : rest -> Left (AdmissionFailure (first :| rest))+ where+ errors = concat+ [ postingErrors ++ imbalanceErrors+ | (note, algebra) <- List.sortOn fst (HashMap.toList (Journal.toMap journal))+ , let postings = List.sortOn Algebra._hatBase (Algebra.toList algebra)+ postingErrors = concat+ [ [NonPositiveAmount note basis amount | isErrorValue amount || not (amount > 0)]+ ++ [NotPostableAccount note basis+ | postingCapabilityFor (getAccountTitle basis) == NotPostable]+ ++ [WildcardSide note basis | hat basis == HatNot]+ | posting <- postings+ , let basis = Algebra._hatBase posting+ amount = Algebra._val posting+ ]+ classifiable = all (\posting ->+ hat (Algebra._hatBase posting) /= HatNot+ && postingCapabilityFor (getAccountTitle (Algebra._hatBase posting))+ /= NotPostable) postings+ debit = List.foldl' (flip addAccum) emptyAccum+ (map Algebra._val (Algebra.toList (decL algebra)))+ credit = List.foldl' (flip addAccum) emptyAccum+ (map Algebra._val (Algebra.toList (decR algebra)))+ imbalanceErrors =+ [ Imbalanced note (roundAccum debit) (roundAccum credit)+ | classifiable+ , signAccum (mergeAccum debit (negateAccum credit)) /= Just EQ+ ]+ ]
+ src/ExchangeAlgebra/IO/Input/Admission/Evidence.hs view
@@ -0,0 +1,90 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Package-private implementation of @checkEvidence@.+module ExchangeAlgebra.IO.Input.Admission.Evidence+ ( checkEvidence+ ) where++import Data.List (sortOn)+import qualified Data.List.NonEmpty as NonEmpty+import Data.List.NonEmpty (NonEmpty)+import qualified Data.HashMap.Strict as HashMap+import qualified ExchangeAlgebra.Algebra.Core as Algebra+import ExchangeAlgebra.Algebra.Element (matchesQuery)++import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.IO.Input.Admission.Diagnostic (EvidenceMismatch(..))+import ExchangeAlgebra.IO.Input.Admission.Representation (Admitted, admittedJournal)+import ExchangeAlgebra.Journal.Core (Journal, Note(..))+import qualified ExchangeAlgebra.Journal.Core as Journal++-- | Check an admitted journal against independent records (evidence), such as+-- a bank statement, a cash count, or the opening balances supplied by a+-- trusted party. The evidence is a journal of expected elements.+--+-- The selection @f@ is applied to the admitted journal first. It chooses,+-- combines, and aggregates as the caller needs: a projection, a transfer,+-- @bar@, or a change of notes. @checkEvidence@ itself does not aggregate and+-- does not call @bar@.+--+-- Each expected posting (note @m@, base @b@, value @v@) is a query against the+-- selected journal, matched one way as in Definition 1: the expected element is+-- the pattern and the selected elements are ledger entries.+--+-- * Note: a @plank@ note matches every note; another note matches only itself.+-- * Base: matched in the query direction, so a wildcard axis of the expected+-- base (such as the account @AccountTitle@) matches any value of the+-- selected base on that axis. The side of the expected base is @Hat@ or+-- @Not@ (@(.\@)@ rejects @HatNot@); to compare both sides of one base as one+-- net value, apply @bar@ in @f@ and write the resulting side.+--+-- The expected element matches when exactly one selected element matches the+-- note and base and its value equals @v@ exactly. Otherwise the element yields+-- 'EvidenceMissing' (no element), 'EvidenceDiffers' (one element with another+-- value), or 'EvidenceAmbiguous' (two or more elements; combine them in @f@).+-- Selected elements that no expected element matches are ignored: the+-- evidence states what must be present, not the whole journal.+--+-- Every expected element is checked and every mismatch is returned, in+-- ascending note and base order of the expected elements.+--+-- Laws:+--+-- * @checkEvidence a f Zero == Right ()@.+-- * @checkEvidence a id e == Right ()@ when every posting of @e@ matches,+-- under the one-way matching above, exactly one posting of+-- @admittedJournal a@ with the same value. Postings are counted one by+-- one: each term of a same-base sequence is a separate posting.+-- * The checks are per expected posting: @checkEvidence a f (e1 .+ e2)@+-- succeeds iff @checkEvidence a f e1@ and @checkEvidence a f e2@ succeed.+checkEvidence+ :: (Note m, HatBaseClass b)+ => Admitted n b+ -> (Journal n MoneyDecimal b -> Journal m MoneyDecimal b)+ -> Journal m MoneyDecimal b+ -> Either (NonEmpty (EvidenceMismatch m b)) ()+checkEvidence accepted select expected = case NonEmpty.nonEmpty mismatches of+ Nothing -> Right ()+ Just failures -> Left failures+ where+ selected =+ [ (note, Algebra._hatBase posting, Algebra._val posting)+ | (note, algebra) <- sortOn fst+ (HashMap.toList (Journal.toMap (select (admittedJournal accepted))))+ , posting <- sortOn Algebra._hatBase (Algebra.toList algebra)+ ]+ mismatches = concat+ [ case [value | (note, basis, value) <- selected+ , isPlank expectedNote || expectedNote == note+ , matchesQuery expectedBase basis] of+ [] -> [EvidenceMissing expectedNote expectedBase expectedValue]+ [value]+ | value == expectedValue -> []+ | otherwise -> [EvidenceDiffers expectedNote expectedBase expectedValue value]+ values -> [EvidenceAmbiguous expectedNote expectedBase values]+ | (expectedNote, algebra) <- sortOn fst (HashMap.toList (Journal.toMap expected))+ , posting <- sortOn Algebra._hatBase (Algebra.toList algebra)+ , let expectedBase = Algebra._hatBase posting+ expectedValue = Algebra._val posting+ ]
+ src/ExchangeAlgebra/IO/Input/Admission/Representation.hs view
@@ -0,0 +1,18 @@+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Package-private representation of admitted journals. Only @admit@+-- constructs @Admitted@; the public module exports the type without its+-- constructor, and no instance rebuilds a value from its contents.+module ExchangeAlgebra.IO.Input.Admission.Representation where++import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Journal.Core (Journal)++-- | A journal every transaction of which passed the checks of @admit@.+data Admitted n b = Admitted (Journal n MoneyDecimal b)++-- | The admitted journal, unchanged: same notes, postings, and sequences.+-- Use it with the trial-balance, statement, and closing functions. A journal+-- built from it is not admitted again until it passes @admit@.+admittedJournal :: Admitted n b -> Journal n MoneyDecimal b+admittedJournal (Admitted journal) = journal
+ src/ExchangeAlgebra/IO/Input/Assist.hs view
@@ -0,0 +1,289 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : ExchangeAlgebra.IO.Input.Assist+Description : Assistance helpers for LLM-facing account selection and validation feedback.++This module provides a small deterministic assistance layer for generated+journal-entry workflows. It exposes account-title metadata from the canonical+account registry, with semantic descriptions for technical and derived titles,+plus one-line explanations for validation errors from+"ExchangeAlgebra.IO.Input".+-}+module ExchangeAlgebra.IO.Input.Assist+ ( AccountInfo(..)+ , describeAccount+ , allAccountInfos+ , suggestAccounts+ , explainEntryError+ , explainJournalErrors+ , explainSourceErrors+ ) where++import Data.List (sortOn)+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NE+import Data.Maybe (mapMaybe)+import Data.Ord (Down(..))+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Accounting.Account+ ( AccountRole+ , AccountSemantics(..)+ , AccountSpec(..)+ , AccountTitles(..)+ , DivisionSemantics+ , HomeSideSemantics+ , PostingCapability+ , ReportingEligibility+ , accountSemantics+ , accountSpec+ , concreteAccountTitles+ )+import ExchangeAlgebra.IO.Input.Conversion+ ( ConvError(..) )+import ExchangeAlgebra.IO.Input.Checked+ ( EntryError(..)+ , JournalError(..)+ , SourceError(..)+ )++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Data.List.NonEmpty (NonEmpty(..))+-- >>> import ExchangeAlgebra.Accounting.Account (AccountTitles(..), PostingCapability(..))+-- >>> import ExchangeAlgebra.IO.Input (ConvError(..), EntryError(..), JournalError(..), SourceError(..))+-- >>> import ExchangeAlgebra.Accounting.Account (ProcessingContext(..))++-- | Account-title metadata for LLM-facing lookup.+data AccountInfo = AccountInfo+ { aiTitle :: AccountTitles+ , aiRoles :: [AccountRole]+ , aiPostingCapability :: PostingCapability+ , aiDivisionSemantics :: DivisionSemantics+ , aiHomeSideSemantics :: HomeSideSemantics+ , aiReportingEligibility :: ReportingEligibility+ , aiNameEn :: Text+ , aiNameJa :: Text+ , aiDesc :: Text+ } deriving (Show, Eq)++-- | Describe a concrete account title.+--+-- The wildcard 'AccountTitle' is rejected because+-- 'ExchangeAlgebra.Algebra.Base.classifyAccountDivision' is intentionally+-- undefined for it.+--+-- >>> fmap ((== "現金") . aiNameJa) (describeAccount Cash)+-- Just True+-- >>> fmap aiDivisionSemantics (describeAccount Cash)+-- Just (StatementDivision Assets)+-- >>> fmap aiDivisionSemantics (describeAccount IncomeSummary)+-- Just (DirectionEncoding Assets)+-- >>> describeAccount AccountTitle+-- Nothing+describeAccount :: AccountTitles -> Maybe AccountInfo+describeAccount title = do+ spec <- accountSpec title+ semantics <- accountSemantics title+ pure (toInfo title spec semantics)++-- | All concrete account-title descriptions in 'Enum' order.+--+-- >>> length allAccountInfos+-- 243+-- >>> take 1 (map aiTitle allAccountInfos)+-- [Cash]+-- >>> aiTitle (last allAccountInfos)+-- ReversalOfAllowanceForBonuses+allAccountInfos :: [AccountInfo]+allAccountInfos = mapMaybe describeAccount concreteAccountTitles++toInfo :: AccountTitles -> AccountSpec -> AccountSemantics -> AccountInfo+toInfo title spec semantics = AccountInfo+ { aiTitle = title+ , aiRoles = asemRoles semantics+ , aiPostingCapability = asemPostingCapability semantics+ , aiDivisionSemantics = asemDivisionSemantics semantics+ , aiHomeSideSemantics = asemHomeSideSemantics semantics+ , aiReportingEligibility = asemReportingEligibility semantics+ , aiNameEn = asNameEn spec+ , aiNameJa = safeNameJa title spec+ , aiDesc = safeDescription title spec+ }++-- | LLM-facing names use the cleaned statement label from the registry.+safeNameJa :: AccountTitles -> AccountSpec -> Text+safeNameJa _ spec = asLabelJa spec++-- | Semantic descriptions for technical, derived, and contextual titles.+-- Ordinary statement accounts retain their canonical registry description.+safeDescription :: AccountTitles -> AccountSpec -> Text+safeDescription NetIncome _ =+ "Period result: net income (当期純利益). Engine-generated only; the legacy Cost value is an internal direction encoding, not an expense classification."+safeDescription NetLoss _ =+ "Period result: net loss (当期純損失). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a revenue classification."+safeDescription GrossProfit _ =+ "Legacy engine coordinate: gross profit (売上総利益). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a statement subtotal definition."+safeDescription OrdinaryProfit _ =+ "Legacy engine coordinate: ordinary profit (経常利益). Engine-generated only; the legacy Revenue value is an internal direction encoding, not a statement subtotal definition."+safeDescription IncomeSummary _ =+ "Closing device: income summary (損益). Available only during closing; the legacy Assets value is an internal direction encoding, not a balance-sheet classification."+safeDescription SuspensePayments _ =+ "Temporary account: suspense payments (仮払金). Its legacy Assets value is a bookkeeping control class; unresolved balances require review before presentation."+safeDescription SuspenseReceipts _ =+ "Temporary account: suspense receipts (仮受金). Its legacy Liability value is a bookkeeping control class; unresolved balances require review before presentation."+safeDescription CashOverShort _ =+ "Temporary account: cash over and short (現金過不足). It must be cleared at closing and is not presented in financial statements."+safeDescription SuspenseAccount _ =+ "Temporary account: suspense account (未決算). Its legacy Assets value is a bookkeeping control class; unresolved balances require review before presentation."+safeDescription BranchCurrentAccount _ =+ "Reciprocal account: branch current account (支店). It may remain in head-office books but is eliminated when head-office and branch balances are combined."+safeDescription HeadOfficeCurrentAccount _ =+ "Reciprocal account: head-office current account (本店). It may remain in branch books but is eliminated when head-office and branch balances are combined."+safeDescription NetIncomeAttributableToNCI _ =+ "Consolidation attribution result: profit attributable to non-controlling interests (非支配株主に帰属する当期純利益). Available only in consolidation worksheets; the legacy Cost value is an internal direction encoding."+safeDescription NetLossAttributableToNCI _ =+ "Consolidation attribution result: loss attributable to non-controlling interests (非支配株主に帰属する当期純損失). Available only in consolidation worksheets; the legacy Revenue value is an internal direction encoding."+safeDescription EquityInEarningsOfInvestee _ =+ "Consolidation result: equity in earnings of investee (持分法による投資利益). Available only in consolidation worksheets."+safeDescription CumulativeTranslationAdjustment _ =+ "Consolidation adjustment: cumulative translation adjustment (為替換算調整勘定). Available only in consolidation worksheets."+safeDescription _ spec = asDescription spec++-- | Suggest account titles by deterministic substring matching.+--+-- The query is split with 'T.words'. Each token is matched case-insensitively+-- against the constructor name, English name, Japanese name, and full+-- description. Results are ranked by the number of matched tokens, with 'Enum'+-- order as the tie-breaker.+--+-- >>> map aiTitle (take 3 (suggestAccounts "cash"))+-- [Cash,PettyCash,CashOverShort]+-- >>> map aiTitle (take 3 (suggestAccounts "現金"))+-- [Cash,PettyCash,CashOverShort]+-- >>> suggestAccounts ""+-- []+-- >>> suggestAccounts "zzzznomatch"+-- []+suggestAccounts :: Text -> [AccountInfo]+suggestAccounts query+ | null tokens = []+ | otherwise =+ map snd+ . sortOn (\(rank, info) -> (Down rank, fromEnum (aiTitle info)))+ . filter ((> 0) . fst)+ $ [ (matchRank info, info) | info <- allAccountInfos ]+ where+ tokens = map T.toCaseFold (T.words query)++ matchRank info =+ length+ [ token+ | token <- tokens+ , any (T.isInfixOf token) (searchFields info)+ ]++ searchFields info = map T.toCaseFold+ [ T.pack (show (aiTitle info))+ , aiNameEn info+ , aiNameJa info+ , aiDesc info+ ]++-- | Explain one checked-entry validation error as one English line.+--+-- >>> explainEntryError (Imbalanced 1500 1400 :: EntryError Int)+-- "entry is not balanced: debit total 1500 /= credit total 1400"+-- >>> explainEntryError (NonPositiveAmount 2 Cash (0 :: Int))+-- "posting 2 (Cash): amount must be > 0, got 0"+-- >>> explainEntryError (EntryParse 0 (UnknownAccount "Supplies") :: EntryError Int)+-- "posting 0: account \"Supplies\" does not resolve to a ledger account"+-- >>> explainEntryError (PostingNotAllowed 1 NetIncome EngineGeneratedOnly OrdinaryJournal :: EntryError Int)+-- "posting 1 (NetIncome): EngineGeneratedOnly is not allowed in OrdinaryJournal"+explainEntryError :: (Show v) => EntryError v -> Text+explainEntryError (EntryParse i err) =+ postingOnly i <> ": " <> explainConvError err+explainEntryError (NonPositiveAmount i account amount) =+ postingAccount i account <> ": amount must be > 0, got " <> showText amount+explainEntryError (WildcardAccount i) =+ postingOnly i <> ": wildcard AccountTitle is not a ledger account"+explainEntryError (WildcardSide i) =+ postingOnly i <> ": wildcard Side is not debit or credit"+explainEntryError (PostingNotAllowed i account capability context) =+ postingAccount i account+ <> ": " <> showText capability+ <> " is not allowed in " <> showText context+explainEntryError EmptyEntry =+ "entry has no postings"+explainEntryError (Imbalanced debitTotal creditTotal) =+ "entry is not balanced: debit total "+ <> showText debitTotal+ <> " /= credit total "+ <> showText creditTotal++-- | Explain checked-journal errors, one line per error.+--+-- >>> explainJournalErrors (EntryErrors "tx1" (Imbalanced 1500 1400 :| []) :| [] :: NonEmpty (JournalError Text Int))+-- "transaction \"tx1\": entry is not balanced: debit total 1500 /= credit total 1400"+-- >>> explainJournalErrors (DuplicateTxId "tx1" :| [] :: NonEmpty (JournalError Text Int))+-- "transaction \"tx1\" appears more than once"+explainJournalErrors :: (Show n, Show v) => NonEmpty (JournalError n v) -> Text+explainJournalErrors =+ T.intercalate "\n" . concatMap explainJournalError . NE.toList++-- | Explain source-coverage errors, one line per error.+--+-- >>> explainSourceErrors [MissingSource "tx7" :: SourceError Text Int]+-- "source transaction \"tx7\" has no journal entry"+-- >>> explainSourceErrors [AmountMismatch "tx7" 1500 1400 :: SourceError Text Int]+-- "source transaction \"tx7\" amount mismatch: expected 1500, journal entry has 1400"+explainSourceErrors :: (Show n, Show v) => [SourceError n v] -> Text+explainSourceErrors = T.intercalate "\n" . map explainSourceError++explainJournalError :: (Show n, Show v) => JournalError n v -> [Text]+explainJournalError (EntryErrors txid errs) =+ [ "transaction " <> showText txid <> ": " <> explainEntryError err+ | err <- NE.toList errs+ ]+explainJournalError (DuplicateTxId txid) =+ [ "transaction " <> showText txid <> " appears more than once" ]++explainSourceError :: (Show n, Show v) => SourceError n v -> Text+explainSourceError (MissingSource txid) =+ "source transaction " <> showText txid <> " has no journal entry"+explainSourceError (UnknownSource txid) =+ "journal entry " <> showText txid <> " has no source transaction"+explainSourceError (AmountMismatch txid expected actual) =+ "source transaction "+ <> showText txid+ <> " amount mismatch: expected "+ <> showText expected+ <> ", journal entry has "+ <> showText actual++explainConvError :: ConvError -> Text+explainConvError (UnknownAccount account) =+ "account " <> showText account <> " does not resolve to a ledger account"+explainConvError (AmbiguousAccount account candidates) =+ "account "+ <> showText account+ <> " is ambiguous; candidates: "+ <> T.intercalate ", " (map showText candidates)+explainConvError (UnknownSide sideText) =+ "side " <> showText sideText <> " is not debit or credit"+explainConvError (MalformedCsv message) =+ "malformed CSV input: " <> showText message+explainConvError (BadAmount message) =+ "amount field is invalid: " <> showText message++postingOnly :: Int -> Text+postingOnly i = "posting " <> showText i++postingAccount :: Int -> AccountTitles -> Text+postingAccount i account =+ postingOnly i <> " (" <> showText account <> ")"++showText :: (Show a) => a -> Text+showText = T.pack . show
+ src/ExchangeAlgebra/IO/Input/Checked.hs view
@@ -0,0 +1,583 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++{- |+Module : ExchangeAlgebra.IO.Input.Checked+Description : Checked construction for externally generated journal entries.++This module adds a validation layer for LLM- or runner-generated postings before+they are admitted as exchange-algebra terms. The unchecked constructor+'ExchangeAlgebra.IO.Input.Conversion.journalFromSides' deliberately preserves its historical+semantics and does not enforce balance; the functions here reject malformed+entries with structural errors instead.+-}+module ExchangeAlgebra.IO.Input.Checked+ ( -- $postingPolicy+ ProcessingContext(..)+ , EntryError(..)+ , JournalError(..)+ , JournalCert(..)+ , SourceError(..)+ , postingAllowedIn+ , checkedEntryIn+ , checkedEntry+ , checkedEntryTextIn+ , checkedEntryAccountTextIn+ , checkedEntryText+ , checkedJournalIn+ , checkedJournal+ , certifyJournalTextIn+ , certifyJournalText+ , reconcileSources+ ) where++import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.HashMap.Strict as HM+import qualified Data.List as L+import qualified Data.Map.Strict as M+import Data.Maybe (mapMaybe)+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Algebra.Core (Alg, Redundant((.+), norm))+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import ExchangeAlgebra.Accounting.Exchange (Exchange(decL, balance))+import ExchangeAlgebra.Accounting.Account.PostingPolicy+ ( ProcessingContext(..)+ , postingAllowedIn+ , postingCapabilityFor+ )+import ExchangeAlgebra.Accounting.Account+ ( AccountTitles(..), PostingCapability(..), Side(..) )+import ExchangeAlgebra.Algebra.Base.Representation (HatBase)+import ExchangeAlgebra.IO.Input.Conversion+ ( ConvError+ , journalFromSides+ , normalizeTitle+ , parseAccountTitle+ , parseSide+ )+import ExchangeAlgebra.Journal.Core+ ( Journal+ , Note+ , (.|)+ , toMap+ )++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Data.List.NonEmpty (NonEmpty)+-- >>> import ExchangeAlgebra.Algebra (Alg, Redundant((.+), norm))+-- >>> import ExchangeAlgebra.Accounting.Exchange (Exchange(decL))+-- >>> import ExchangeAlgebra.Algebra.Base (HatBase)+-- >>> import ExchangeAlgebra.Accounting.Account (AccountTitles(..), Side(..))+-- >>> import ExchangeAlgebra.IO.Input (journalFromSides)+-- >>> import ExchangeAlgebra.Journal (Journal)++-- $postingPolicy+-- 'ProcessingContext' and 'postingAllowedIn' are defined in+-- "ExchangeAlgebra.Accounting.PostingPolicy" and re-exported here so the+-- historical import path keeps working. The gate itself is accounting-domain+-- policy, not an input-adapter concern.++-- | Validation errors for a single generated entry.+--+-- Posting positions are 0-origin indices in the input entry.+data EntryError v+ = EntryParse Int ConvError+ | NonPositiveAmount Int AccountTitles v+ | WildcardAccount Int+ | WildcardSide Int+ | PostingNotAllowed Int AccountTitles PostingCapability ProcessingContext+ | EmptyEntry+ | Imbalanced { _debitTotal :: v, _creditTotal :: v }+ deriving (Show, Eq)++-- | Validation errors for a txid-indexed journal batch.+data JournalError n v+ = EntryErrors n (NonEmpty (EntryError v))+ | DuplicateTxId n+ deriving (Show, Eq)++-- | Staged certification of a text-originated journal batch.+--+-- A batch can be fully admitted, rejected for accounting or structural+-- reasons, or certified as structurally valid and balanced while retaining+-- account-title resolution failures for a later vocabulary pass.+--+-- The distinction that motivates the type: an externally generated journal can+-- be double-entry valid and still name accounts this chart does not carry. Folding+-- both into one 'Either' makes a correct journal in an unsupported vocabulary+-- indistinguishable from a wrong one.+--+-- Note the two totals are /batch/ totals, whereas the balance test that gates+-- 'BalancedUnresolved' is applied per entry (per txid), matching 'checkedEntry'.+-- Per-entry balance implies batch balance, so the invariant+-- @_certDebitTotal == _certCreditTotal@ holds, but the converse does not: the+-- batch totals alone would not have been a sufficient gate.+data JournalCert n v+ = FullyResolved (Journal n v (HatBase AccountTitles))+ | BalancedUnresolved+ { _certResolved :: [(n, [(Side, AccountTitles, v)])]+ , _certUnresolved :: [(n, [(Int, Text, ConvError)])]+ , _certDebitTotal :: v+ , _certCreditTotal :: v+ }+ | Rejected (NonEmpty (JournalError n v))+ deriving (Show)++instance (HatVal v, Note n) => Eq (JournalCert n v) where+ FullyResolved left == FullyResolved right = toMap left == toMap right+ BalancedUnresolved resolvedLeft unresolvedLeft debitLeft creditLeft+ == BalancedUnresolved resolvedRight unresolvedRight debitRight creditRight =+ resolvedLeft == resolvedRight+ && unresolvedLeft == unresolvedRight+ && debitLeft == debitRight+ && creditLeft == creditRight+ Rejected left == Rejected right = left == right+ _ == _ = False++-- | Source-coverage errors between input transactions and a checked journal.+data SourceError n v+ = MissingSource n+ | UnknownSource n+ | AmountMismatch n v v+ deriving (Show, Eq)++-- | Construct one checked entry from parsed postings in a processing context.+--+-- The success value is built with 'journalFromSides', so accepted entries keep+-- the same posting semantics as the unchecked conversion path.+--+-- >>> fmap balance (checkedEntry [(Debit, Cash, 100), (Credit, Sales, 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))+-- Right True+-- >>> checkedEntry [] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (EmptyEntry :| [])+-- >>> checkedEntry [(Debit, Cash, 0), (Credit, Sales, 0)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (NonPositiveAmount 0 Cash 0.0 :| [NonPositiveAmount 1 Sales 0.0])+-- >>> checkedEntry [(Debit, AccountTitle, 1), (Side, Cash, 1)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (WildcardAccount 0 :| [WildcardSide 1,Imbalanced {_debitTotal = 1.0, _creditTotal = 0.0}])+-- >>> checkedEntry [(Debit, Cash, 100), (Credit, Sales, 90)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (Imbalanced {_debitTotal = 100.0, _creditTotal = 90.0} :| [])+-- >>> fmap balance (checkedEntryIn ClosingProcess [(Debit, Sales, 100), (Credit, IncomeSummary, 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))+-- Right True+checkedEntryIn :: (HatVal v)+ => ProcessingContext+ -> [(Side, AccountTitles, v)]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntryIn context rows =+ case validateIndexed context (null rows) indexed of+ [] -> Right (journalFromSides rows)+ e : es -> Left (e :| es)+ where+ indexed = zip [0..] rows++-- | Construct one ordinary-journal entry. This compatibility API delegates+-- to 'checkedEntryIn' and therefore rejects closing-only, consolidation-only,+-- engine-generated, and non-postable coordinates.+checkedEntry :: (HatVal v)+ => [(Side, AccountTitles, v)]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntry = checkedEntryIn OrdinaryJournal++-- | Parse text-side runner input and then apply 'checkedEntry' validation.+--+-- Parse errors are accumulated as 'EntryParse' instead of failing fast.+--+-- >>> fmap balance (checkedEntryText [("debit", "Cash", 100), ("credit", "Sales", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles)))+-- Right True+-- >>> checkedEntryText [("left", "Cash", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (EntryParse 0 (UnknownSide "left") :| [])+-- >>> checkedEntryText [("debit", "Goodwill_X", 100)] :: Either (NonEmpty (EntryError Double)) (Alg Double (HatBase AccountTitles))+-- Left (EntryParse 0 (UnknownAccount "Goodwill_X") :| [Imbalanced {_debitTotal = 100.0, _creditTotal = 0.0}])+checkedEntryTextIn :: (HatVal v)+ => ProcessingContext+ -> [(Text, Text, v)]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntryTextIn context rows =+ checkedEntryParsedIn context+ [(i, (parseSide side, parseAccountTitle account, amount))+ | (i, (side, account, amount)) <- zip [0..] rows]++-- | Check typed sides and account text in the supplied processing context.+-- Account parse errors precede structural errors and imbalance. Amounts from+-- unresolved accounts still contribute to the balance check.+checkedEntryAccountTextIn+ :: HatVal v+ => ProcessingContext+ -> [(Side, Text, v)]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntryAccountTextIn context rows =+ checkedEntryParsedIn context+ [(i, (Right side, parseAccountTitle account, amount))+ | (i, (side, account, amount)) <- zip [0..] rows]++-- | Validate all indexed parse results without dropping unresolved rows.+-- An unresolved side suppresses imbalance; an unresolved account does not.+checkedEntryParsedIn+ :: forall v. HatVal v+ => ProcessingContext+ -> [(Int, (Either ConvError Side, Either ConvError AccountTitles, v))]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntryParsedIn context rows =+ case parseErrors ++ structuralErrors ++ textImbalanceErrors of+ [] -> Right (journalFromSides (map snd parsedRows))+ e : es -> Left (e :| es)+ where+ parsed = map parseIndexed rows+ parseErrors = concatMap first parsed+ parsedRows = mapMaybe second parsed+ sideAmounts = mapMaybe third parsed+ structuralErrors = filter (not . isImbalance)+ (validateIndexed context (null rows) parsedRows)++ -- Account vocabulary resolution and arithmetic balance are independent.+ -- Retain every successfully parsed side/amount pair so an unknown account+ -- does not make an otherwise balanced entry look imbalanced.+ textImbalanceErrors+ | null rows = []+ | length sideAmounts /= length rows = []+ | any invalidSideAmount sideAmounts = []+ | signAccum (mergeAccum debitState (negateAccum creditState)) == Just EQ = []+ | otherwise = [Imbalanced debitTotal creditTotal]+ where+ debitState = L.foldl' (flip addAccum) emptyAccum+ [ amount | (Debit, amount) <- sideAmounts ]+ creditState = L.foldl' (flip addAccum) emptyAccum+ [ amount | (Credit, amount) <- sideAmounts ]+ debitTotal = roundAccum debitState+ creditTotal = roundAccum creditState++ invalidSideAmount :: (Side, v) -> Bool+ invalidSideAmount (side, amount) =+ side == Side || isErrorValue amount || not (amount > 0)++ isImbalance :: EntryError v -> Bool+ isImbalance Imbalanced{} = True+ isImbalance _ = False++ first :: (a, b, c) -> a+ first (x, _, _) = x+ second :: (a, b, c) -> b+ second (_, x, _) = x+ third :: (a, b, c) -> c+ third (_, _, x) = x++ parseIndexed+ :: (Int, (Either ConvError Side, Either ConvError AccountTitles, v))+ -> ([EntryError v], Maybe (Int, (Side, AccountTitles, v)), Maybe (Side, v))+ parseIndexed (i, (sideResult, accountResult, amount)) =+ let errs =+ (case sideResult of+ Left err -> [EntryParse i err]+ Right _ -> [])+ ++ (case accountResult of+ Left err -> [EntryParse i err]+ Right _ -> [])+ parsedRow = case (sideResult, accountResult) of+ (Right side, Right account) -> Just (i, (side, account, amount))+ _ -> Nothing+ sideAmount = case sideResult of+ Right side -> Just (side, amount)+ Left _ -> Nothing+ in (errs, parsedRow, sideAmount)++-- | Parse and check an ordinary-journal entry.+checkedEntryText :: (HatVal v)+ => [(Text, Text, v)]+ -> Either (NonEmpty (EntryError v)) (Alg v (HatBase AccountTitles))+checkedEntryText = checkedEntryTextIn OrdinaryJournal++-- | Construct a checked txid-indexed journal.+--+-- Duplicate txids are detected before entry construction, because the 'Journal'+-- representation merges equal notes.+--+-- >>> fmap (norm . decL) (checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)])] :: Either (NonEmpty (JournalError Text Double)) (Journal Text Double (HatBase AccountTitles)))+-- Right 100.0+-- >>> checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)]), ("tx1", [(Debit, Cash, 5), (Credit, Sales, 5)])] :: Either (NonEmpty (JournalError Text Double)) (Journal Text Double (HatBase AccountTitles))+-- Left (DuplicateTxId "tx1" :| [])+-- >>> checkedJournal [("bad", [(Debit, Cash, 1)])] :: Either (NonEmpty (JournalError Text Double)) (Journal Text Double (HatBase AccountTitles))+-- Left (EntryErrors "bad" (Imbalanced {_debitTotal = 1.0, _creditTotal = 0.0} :| []) :| [])+checkedJournalIn :: (HatVal v, Note n, Ord n)+ => ProcessingContext+ -> [(n, [(Side, AccountTitles, v)])]+ -> Either (NonEmpty (JournalError n v)) (Journal n v (HatBase AccountTitles))+checkedJournalIn context entries =+ case duplicateErrors ++ entryErrors of+ [] -> Right (L.foldl' (.+) mempty journals)+ e : es -> Left (e :| es)+ where+ counts = M.fromListWith (+) [ (txid, 1 :: Int) | (txid, _) <- entries ]+ isDuplicate txid = M.findWithDefault 0 txid counts > 1++ duplicateErrors =+ [ DuplicateTxId txid+ | (txid, count) <- M.toList counts+ , count > 1+ ]++ checkedUnique =+ [ (txid, checkedEntryIn context rows)+ | (txid, rows) <- entries+ , not (isDuplicate txid)+ ]++ entryErrors =+ [ EntryErrors txid errs+ | (txid, Left errs) <- checkedUnique+ ]++ journals =+ [ alg .| txid+ | (txid, Right alg) <- checkedUnique+ ]++-- | Construct an ordinary-journal batch.+checkedJournal :: (HatVal v, Note n, Ord n)+ => [(n, [(Side, AccountTitles, v)])]+ -> Either (NonEmpty (JournalError n v)) (Journal n v (HatBase AccountTitles))+checkedJournal = checkedJournalIn OrdinaryJournal++-- | Certify a text-originated journal in stages.+--+-- Duplicate txids and structural errors are rejected before balance is+-- considered. Balance is then checked using only parsed sides and amounts,+-- independently of account-title resolution. Consequently, a structurally+-- valid and balanced batch whose only remaining failures are unknown or+-- ambiguous account titles is returned as 'BalancedUnresolved'.+certifyJournalTextIn :: (HatVal v, Note n, Ord n)+ => ProcessingContext+ -> [(n, [(Text, Text, v)])]+ -> JournalCert n v+certifyJournalTextIn context entries =+ case duplicateErrors of+ e : es -> Rejected (e :| es)+ [] -> case structuralErrors of+ e : es -> Rejected (e :| es)+ [] -> case imbalanceErrors of+ e : es -> Rejected (e :| es)+ []+ | null unresolvedPostings ->+ FullyResolved (L.foldl' (.+) mempty journals)+ | otherwise ->+ BalancedUnresolved+ { _certResolved = resolvedEntries+ , _certUnresolved = unresolvedEntries+ , _certDebitTotal = debitTotal+ , _certCreditTotal = creditTotal+ }+ where+ counts = M.fromListWith (+) [ (txid, 1 :: Int) | (txid, _) <- entries ]+ duplicateErrors =+ [ DuplicateTxId txid+ | (txid, count) <- M.toList counts+ , count > 1+ ]++ parsedEntries =+ [ (txid, map parseCertPosting (zip [0..] rows))+ | (txid, rows) <- entries+ ]++ structuralErrors =+ [ EntryErrors txid (err :| errs)+ | (txid, rows) <- parsedEntries+ , let entryErrors = certStructuralErrors context rows+ , err : errs <- [entryErrors]+ ]++ imbalanceErrors =+ [ EntryErrors txid (Imbalanced debit credit :| [])+ | (txid, rows) <- parsedEntries+ , let (debitState, creditState) = certTotals rows+ , let debit = roundAccum debitState+ , let credit = roundAccum creditState+ , signAccum (mergeAccum debitState (negateAccum creditState)) /= Just EQ+ ]++ resolvedEntries =+ [ (txid, resolved)+ | (txid, rows) <- parsedEntries+ , let resolved =+ [ (side, account, amount)+ | (_, _, amount, Right side, Right account) <- rows+ ]+ , not (null resolved)+ ]++ unresolvedEntries =+ [ (txid, unresolved)+ | (txid, rows) <- parsedEntries+ , let unresolved =+ [ (i, accountText, err)+ | (i, accountText, _, Right _, Left err) <- rows+ ]+ , not (null unresolved)+ ]++ unresolvedPostings = concatMap snd unresolvedEntries++ (debitTotal, creditTotal) =+ let (debitState, creditState) = L.foldl' addTotals (emptyAccum, emptyAccum)+ (map (certTotals . snd) parsedEntries)+ in (roundAccum debitState, roundAccum creditState)++ addTotals (!debits, !credits) (!entryDebits, !entryCredits) =+ let !debitTotal = mergeAccum debits entryDebits+ !creditTotal = mergeAccum credits entryCredits+ in (debitTotal, creditTotal)++ journals =+ [ journalFromSides rows .| txid+ | (txid, rows) <- resolvedEntries+ ]++-- | Certify an ordinary-journal batch in stages.+certifyJournalText :: (HatVal v, Note n, Ord n)+ => [(n, [(Text, Text, v)])]+ -> JournalCert n v+certifyJournalText = certifyJournalTextIn OrdinaryJournal++-- The tuple retains the original account text so resolution failures can be+-- reported without reconstructing user input.+type CertPosting v =+ (Int, Text, v, Either ConvError Side, Either ConvError AccountTitles)++-- Wildcard names have to be recognized here rather than delegated to the+-- parsers: 'parseAccountTitle' and 'parseSide' reject the wildcard+-- constructors by design (the /correct-by-construction/ guard documented in+-- "ExchangeAlgebra.IO.Input.Conversion"), so on the text path a wildcard is indistinguishable+-- from an unknown name. Certification must tell them apart, because a wildcard+-- is a structural defect ('Rejected') whereas an unknown name is a vocabulary+-- gap ('BalancedUnresolved'). Matching uses the parsers' own 'normalizeTitle',+-- so the two paths cannot drift apart.+parseCertPosting :: (Int, (Text, Text, v)) -> CertPosting v+parseCertPosting (i, (sideText, accountText, amount)) =+ ( i+ , accountText+ , amount+ , if normalizeTitle sideText == T.pack "side"+ then Right Side+ else parseSide sideText+ , if normalizeTitle accountText == T.pack "accounttitle"+ then Right AccountTitle+ else parseAccountTitle accountText+ )++certStructuralErrors :: (HatVal v)+ => ProcessingContext+ -> [CertPosting v]+ -> [EntryError v]+certStructuralErrors context rows =+ [ EmptyEntry | null rows ] ++ concatMap rowErrors rows+ where+ rowErrors (i, _, amount, sideResult, accountResult) =+ [ EntryParse i err | Left err <- [sideResult] ]+ ++ [ NonPositiveAmount i (resolvedOrWildcard accountResult) amount+ | isErrorValue amount || not (amount > 0)+ ]+ ++ [ WildcardAccount i | Right AccountTitle <- [accountResult] ]+ ++ [ WildcardSide i | Right Side <- [sideResult] ]+ ++ [ PostingNotAllowed i account capability context+ | Right account <- [accountResult]+ , account /= AccountTitle+ , let capability = postingCapabilityFor account+ , not (postingAllowedIn context capability)+ ]++ resolvedOrWildcard (Right account) = account+ resolvedOrWildcard (Left _) = AccountTitle++certTotals :: (HatVal v) => [CertPosting v] -> (Accum v, Accum v)+certTotals rows =+ ( L.foldl' (flip addAccum) emptyAccum+ [ amount | (_, _, amount, Right Debit, _) <- rows ]+ , L.foldl' (flip addAccum) emptyAccum+ [ amount | (_, _, amount, Right Credit, _) <- rows ]+ )++-- | Compare source transactions against the note-indexed journal coverage.+--+-- The journal side is inspected with 'toMap', giving the per-note 'Alg'. The+-- actual amount is each note's debit-side total, @norm . decL@.+--+-- >>> let Right j = checkedJournal [("tx1", [(Debit, Cash, 100), (Credit, Sales, 100)])] :: Either (NonEmpty (JournalError Text Double)) (Journal Text Double (HatBase AccountTitles))+-- >>> reconcileSources [("tx1", 100)] j+-- []+-- >>> reconcileSources [("tx1", 90)] j+-- [AmountMismatch "tx1" 90.0 100.0]+-- >>> reconcileSources [("tx1", 100), ("tx2", 5)] j+-- [MissingSource "tx2"]+-- >>> let Right j2 = checkedJournal [("tx2", [(Debit, Cash, 5), (Credit, Sales, 5)])] :: Either (NonEmpty (JournalError Text Double)) (Journal Text Double (HatBase AccountTitles))+-- >>> reconcileSources [("tx1", 100)] (j .+ j2)+-- [UnknownSource "tx2"]+reconcileSources :: (HatVal v, Note n, Ord n)+ => [(n, v)]+ -> Journal n v (HatBase AccountTitles)+ -> [SourceError n v]+reconcileSources sources journal =+ missing ++ unknown ++ mismatched+ where+ expected = M.fromList sources+ actual = M.fromList (HM.toList (toMap journal))++ missing =+ [ MissingSource txid+ | (txid, _) <- sources+ , M.notMember txid actual+ ]++ unknown =+ [ UnknownSource txid+ | txid <- M.keys actual+ , M.notMember txid expected+ ]++ mismatched =+ [ AmountMismatch txid expectedAmount actualAmount+ | (txid, expectedAmount) <- sources+ , Just alg <- [M.lookup txid actual]+ , let actualAmount = norm (decL alg)+ , expectedAmount /= actualAmount+ ]++validateIndexed :: (HatVal v)+ => ProcessingContext+ -> Bool+ -> [(Int, (Side, AccountTitles, v))]+ -> [EntryError v]+validateIndexed context rawEmpty rows =+ emptyErrors ++ rowErrors ++ imbalanceErrors+ where+ emptyErrors = [ EmptyEntry | rawEmpty ]++ rowErrors = concatMap validateRow rows++ validateRow (i, (side, account, amount)) =+ [ NonPositiveAmount i account amount+ | invalidAmount amount+ ]+ ++ [ WildcardAccount i | account == AccountTitle ]+ ++ [ WildcardSide i | side == Side ]+ ++ [ PostingNotAllowed i account capability context+ | account /= AccountTitle+ , let capability = postingCapabilityFor account+ , not (postingAllowedIn context capability)+ ]++ invalidAmount amount = isErrorValue amount || not (amount > 0)++ imbalanceErrors+ | null rows = []+ | otherwise =+ [ Imbalanced (roundAccum debit) (roundAccum credit)+ | signAccum (mergeAccum debit (negateAccum credit)) /= Just EQ+ ]+ where+ debit = L.foldl' (flip addAccum) emptyAccum+ [amount | (_, (Debit, _, amount)) <- rows]+ credit = L.foldl' (flip addAccum) emptyAccum+ [amount | (_, (Credit, _, amount)) <- rows]
+ src/ExchangeAlgebra/IO/Input/Conversion.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+-- The alias table intentionally references the deprecated 'Commutation'+-- constructor so that its legacy Japanese label ("通信費") is recognized and+-- flagged as ambiguous against 'CommunicationExpenses'; silence the warning.+{-# OPTIONS_GHC -Wno-deprecations #-}++{- |+Module : ExchangeAlgebra.IO.Input.Conversion+Description : Pure conversion between external (side, account-name, amount) data and+ exchange-algebra terms. No serialization dependency (Text only).++This module is the dependency-free core of the input-conversion layer: it turns+externally supplied postings---a debit\/credit t'Side', an account name as 'Text',+and an amount---into exchange-algebra 'Alg' terms, and back. The format glue+(JSON\/XML, i.e. @aeson@) deliberately lives outside the published library,+so the algebra core stays serialization-free.++The mapping from a debit\/credit side to the @Hat@\/@Not@ marker is derived from+the library's own 'whichSide', so the credit\/debit semantics are never duplicated.+Unknown or wildcard account names are rejected (a /correct-by-construction/ guard+against hallucinated accounts). Account names that are genuinely ambiguous (one+Japanese label shared by several constructors, e.g. @準備預金@ for both the asset+and liability side of reserve deposits) are also rejected, with the candidate+accounts reported, so callers must disambiguate by canonical name.++A fixed-schema CSV reader (@side,account,amount@, optional @note@ column) is+provided in "ExchangeAlgebra.IO.Input.Csv".+-}+module ExchangeAlgebra.IO.Input.Conversion+ ( ConvError(..)+ -- $concreteAccountTitles+ , concreteAccountTitles+ , normalizeTitle+ , parseAccountTitle+ , parseSide+ , markerForSide+ , postingFromSide+ , journalFromSides+ ) where++import Data.Char (isAlphaNum, isSpace)+import qualified Data.List as L+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Algebra.Core ( Alg, Redundant((.+)), (.@) )+import ExchangeAlgebra.Algebra.Value.Class (HatVal)+import ExchangeAlgebra.Algebra.Base.Representation (HatBase(..), Hat(..))+import ExchangeAlgebra.Accounting.Account (AccountTitles(..), Side(..), accountAliases)+import ExchangeAlgebra.Accounting.Exchange (whichSide)+import ExchangeAlgebra.Accounting.Account.Registry ( concreteAccountTitles )++-- $setup+-- The examples use 'Text' literals, so enable @OverloadedStrings@ in doctest.+-- >>> :set -XOverloadedStrings++-- | Conversion errors. Kept structural so callers can report precisely.+data ConvError = UnknownAccount Text -- ^ name matched no concrete account title+ | AmbiguousAccount Text [AccountTitles] -- ^ name matched several accounts (disambiguate by canonical name)+ | UnknownSide Text -- ^ side string was not debit\/credit+ | MalformedCsv Text -- ^ CSV header\/row was structurally invalid (see "ExchangeAlgebra.IO.Input.Csv")+ | BadAmount Text -- ^ amount field was not a valid non-negative number+ deriving (Eq, Show)++-- $concreteAccountTitles+-- 'concreteAccountTitles' lists all concrete account titles, /excluding/ the+-- wildcard 'AccountTitle', in stable Enum order. It is re-exported from+-- "ExchangeAlgebra.Algebra.Base.Account.Registry", which is the single+-- definition; external input must never resolve to the wildcard, so callers+-- should use this safe range rather than @[minBound .. maxBound]@.+--+-- >>> take 1 concreteAccountTitles+-- [Cash]+-- >>> Cash `elem` concreteAccountTitles && Sales `elem` concreteAccountTitles+-- True+-- >>> AccountsPayable `elem` concreteAccountTitles+-- True+-- >>> AccountTitle `elem` concreteAccountTitles+-- False++-- | Account-name lookup table: every key (canonical constructor names and the+-- Japanese\/abbreviation aliases below) is run through 'normalizeTitle', so matching is+-- case-, whitespace-, and symbol-insensitive. Keys are tagged so that a single+-- alias shared by several accounts is reported as 'AmbiguousAccount' rather than+-- silently resolving to whichever entry happens to come first.+--+-- The canonical English names (derived from 'show' over 'concreteAccountTitles')+-- are always unique, so they are never ambiguous and always resolve.+accountTable :: [(Text, [AccountTitles])]+accountTable = collapse [ (normalizeTitle k, a) | (k, a) <- entries ]+ where+ -- Group entries by normalized key, accumulating every account that key maps+ -- to (so genuine collisions become ambiguous, not first-wins).+ collapse kvs =+ [ (k, L.nub as)+ | k <- L.nub (L.map fst kvs)+ , let as = [ a | (k', a) <- kvs, k' == k ] ]++ entries :: [(Text, AccountTitles)]+ entries = canonical ++ aliases++ -- Canonical: the constructor name. Always unique.+ canonical = [ (T.pack (show a), a) | a <- concreteAccountTitles ]++ -- Aliases: Japanese labels (from the Element.hs translation Haddock, which is+ -- the canonical bilingual reference) plus a few English abbreviations.+ -- Genuinely shared labels are listed for each account; lookup then returns+ -- 'AmbiguousAccount' for those, forcing the caller to use a canonical name.+ aliases =+ [ (alias, title)+ | title <- aliasTitleOrder+ , alias <- accountAliases title+ ]++ -- Preserve the historical candidate order for the one shared label whose+ -- alias table order differs from Enum order: "通信費" reports the modern+ -- title before the deprecated one.+ aliasTitleOrder = CommunicationExpenses+ : L.delete CommunicationExpenses concreteAccountTitles++-- | Normalize an account name for matching: case-fold, drop punctuation\/symbols+-- (keep only alphanumerics and spaces), and collapse internal whitespace to+-- single spaces (also trimming). So @\"A\/R\"@ and @\"ar\"@ coincide, and+-- @\"Accounts Receivable\"@ matches @\"accounts receivable\"@. CJK characters+-- are alphanumeric (Unicode @Lo@), so Japanese labels survive unchanged.+--+-- (Renamed from @norm@: that name collides with the core value-domain+-- homomorphism 'ExchangeAlgebra.Algebra.norm', which is an entirely unrelated+-- operation — the two must not be confusable in downstream imports.)+--+-- >>> normalizeTitle " Accounts Receivable "+-- "accounts receivable"+-- >>> normalizeTitle "A/R"+-- "ar"+normalizeTitle :: Text -> Text+normalizeTitle = T.unwords . T.words . T.filter (\c -> isAlphaNum c || isSpace c) . T.toLower++-- | Parse an account name into a concrete 'AccountTitles'. Unknown names and the+-- wildcard are rejected; ambiguous Japanese labels (shared by several accounts)+-- are rejected with the candidates reported.+--+-- >>> parseAccountTitle "Cash"+-- Right Cash+-- >>> parseAccountTitle " accounts receivable "+-- Right AccountsReceivable+-- >>> parseAccountTitle "Accounts Receivable"+-- Right AccountsReceivable+-- >>> parseAccountTitle "A/R"+-- Right AccountsReceivable+-- >>> parseAccountTitle "売掛金"+-- Right AccountsReceivable+--+-- A label shared by two accounts (here the asset and liability side of reserve+-- deposits) is rejected as ambiguous, listing the candidates so the caller can+-- pick a canonical name:+--+-- >>> case parseAccountTitle "準備預金" of { Left (AmbiguousAccount _ as) -> as; _ -> [] }+-- [ReserveDepositReceivable,ReserveDepositPayable]+-- >>> parseAccountTitle "Goodwill_X"+-- Left (UnknownAccount "Goodwill_X")+-- >>> parseAccountTitle "AccountTitle"+-- Left (UnknownAccount "AccountTitle")+parseAccountTitle :: Text -> Either ConvError AccountTitles+parseAccountTitle t =+ case lookup (normalizeTitle t) accountTable of+ Just [a] -> Right a+ Just as -> Left (AmbiguousAccount t as)+ Nothing -> Left (UnknownAccount t)++-- | Parse a side string (@"debit"@\/@"credit"@, case-insensitive) into t'Side'.+--+-- >>> parseSide "debit"+-- Right Debit+-- >>> parseSide "CREDIT"+-- Right Credit+-- >>> parseSide "left"+-- Left (UnknownSide "left")+parseSide :: Text -> Either ConvError Side+parseSide t = case normalizeTitle t of+ "debit" -> Right Debit+ "credit" -> Right Credit+ _ -> Left (UnknownSide t)++-- | The @Hat@\/@Not@ marker that places @account@ on the requested debit\/credit+-- @side@. Derived from the library's 'whichSide' so the credit\/debit rule is not+-- duplicated: an account sits on its \"natural\" side under 'Not'; if the+-- requested side differs, the v'Hat' (reversal) marker is used.+--+-- >>> markerForSide Debit Cash+-- Not+-- >>> markerForSide Credit Cash+-- Hat+-- >>> markerForSide Credit Sales+-- Not+-- >>> markerForSide Debit Sales+-- Hat+markerForSide :: Side -> AccountTitles -> Hat+markerForSide side account+ | whichSide (Not :< account) == side = Not+ | otherwise = Hat++-- | Build a single posting term: @amount .\@ (marker :< account)@, with the+-- marker chosen so the posting lands on @side@. Built through the smart+-- constructor '(.@)' so the non-negative\/non-error value invariant is enforced.+--+-- >>> postingFromSide Debit Cash (1000 :: Double)+-- 1000.00:@Not:<Cash+-- >>> postingFromSide Credit Sales (1000 :: Double)+-- 1000.00:@Not:<Sales+postingFromSide :: (HatVal v)+ => Side -> AccountTitles -> v -> Alg v (HatBase AccountTitles)+postingFromSide side account amount =+ amount .@ (markerForSide side account :< account)++-- | Fold a list of @(side, account, amount)@ postings into one algebra term.+-- Balance (debit == credit) is /not/ enforced here; check it with the library's+-- balance functions after conversion.+--+-- >>> journalFromSides [(Debit, Cash, 1000), (Credit, Sales, 1000)] :: Alg Double (HatBase AccountTitles)+-- 1000.00:@Not:<Cash .+ 1000.00:@Not:<Sales+journalFromSides :: (HatVal v)+ => [(Side, AccountTitles, v)] -> Alg v (HatBase AccountTitles)+journalFromSides = foldr (\(s, a, v) acc -> postingFromSide s a v .+ acc) mempty
+ src/ExchangeAlgebra/IO/Input/Csv.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : ExchangeAlgebra.IO.Input.Csv+Description : Fixed-schema CSV reader for general journal postings.++A deliberately tiny, dependency-free (Text + scientific only) reader for a fixed+journal CSV schema, building on the pure normalization\/parsing in+"ExchangeAlgebra.IO.Input". It is the read counterpart of the report\/ledger CSV+writers in "ExchangeAlgebra.IO.Output.Csv" (writing is /not/ handled here).++The schema is a header line @side,account,amount@ with an optional trailing+@note@ column:++> side,account,amount+> debit,Cash,1000+> credit,Sales,1000++Blank lines and lines whose first non-space character is @#@ are skipped;+surrounding whitespace on each field is trimmed; there is no quoting. The field+splitter 'splitTrim' is exported and shared with the equally minimal+@parseEdgeCsv@\/@parseCoefCsv@ readers in "ExchangeAlgebra.Simulation.Network.Csv", so+the two readers cannot drift apart on how a line is split.++@account@ is resolved with 'parseAccountTitle' (canonical English names or the+Japanese\/abbreviation aliases), so unknown and ambiguous account names are+rejected. @side@ is resolved with 'parseSide'. @amount@ is parsed by a+caller-supplied function so the value type @v@ stays open; 'scientificAmount'+covers the common case (a non-negative decimal literal via 'Data.Scientific').+All values are placed through the non-negative smart constructor @(.@)@ inside+'postingFromSide'.+-}+module ExchangeAlgebra.IO.Input.Csv+ ( -- * Parsing journal CSV+ parseJournalCsv+ , parseJournalCsvWith+ -- * Note-keyed journal (when the optional @note@ column is present)+ , parseNotedJournalCsv+ -- * Amount parsers+ , scientificAmount+ -- * Field splitting+ , splitTrim+ ) where++import qualified Data.Scientific as Sci+import Data.Text (Text)+import qualified Data.Text as T++import ExchangeAlgebra.Algebra.Core ( Alg, Redundant((.+)) )+import ExchangeAlgebra.Algebra.Value.Class (HatVal)+import ExchangeAlgebra.Algebra.Base.Representation (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles, Side)+import ExchangeAlgebra.IO.Input.Conversion ( ConvError(..)+ , parseAccountTitle+ , parseSide+ , postingFromSide )++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import ExchangeAlgebra.Algebra (Alg)+-- >>> import ExchangeAlgebra.Algebra.Base (HatBase)+-- >>> import ExchangeAlgebra.Accounting.Account (AccountTitles)++-- | Parse a non-negative decimal amount via 'Data.Scientific' (e.g. @"1000"@,+-- @"1000.50"@, @"1.5e3"@), converting to the value type @v@ exactly through+-- 'toRational' (so exact-decimal types such as @MoneyDecimal@ keep their+-- precision for terminating decimals). Negative amounts are rejected here so the+-- error is reported as a 'BadAmount' rather than surfacing later from @(.@)@.+--+-- >>> scientificAmount "1000.50" :: Either ConvError Double+-- Right 1000.5+-- >>> scientificAmount "-3" :: Either ConvError Double+-- Left (BadAmount "-3")+-- >>> scientificAmount "1,000" :: Either ConvError Double+-- Left (BadAmount "1,000")+scientificAmount :: (Fractional v) => Text -> Either ConvError v+scientificAmount t =+ case reads (T.unpack (T.strip t)) :: [(Sci.Scientific, String)] of+ [(s, "")] | s >= 0 -> Right (fromRational (toRational s))+ | otherwise -> Left (BadAmount t)+ _ -> Left (BadAmount t)++-- | Parse a fixed-schema journal CSV into a single algebra term, using+-- 'scientificAmount' for the amount column. The note column, if present, is+-- ignored at the 'Alg' level (use 'parseNotedJournalCsv' to keep notes).+--+-- >>> let csv = "side,account,amount\ndebit,Cash,1000\ncredit,Sales,1000\n"+-- >>> parseJournalCsv csv :: Either ConvError (Alg Double (HatBase AccountTitles))+-- Right 1000.00:@Not:<Cash .+ 1000.00:@Not:<Sales+parseJournalCsv :: (HatVal v, Fractional v)+ => Text -> Either ConvError (Alg v (HatBase AccountTitles))+parseJournalCsv = parseJournalCsvWith scientificAmount++-- | As 'parseJournalCsv', but with a caller-supplied amount parser, so the value+-- type @v@ need not be 'Fractional' (e.g. an integral or fixed-point reader).+parseJournalCsvWith :: (HatVal v)+ => (Text -> Either ConvError v)+ -> Text+ -> Either ConvError (Alg v (HatBase AccountTitles))+parseJournalCsvWith amount txt = do+ rows <- parseRows amount txt+ Right (foldr (\(s, a, v) acc -> postingFromSide s a v .+ acc) mempty rows)++-- | Parse the rows of a journal CSV into @(Side, AccountTitles, value, note)@+-- tuples, where @note@ is the optional 4th column (empty 'Text' if absent). The+-- note is returned as raw 'Text' so the caller can key an+-- 'ExchangeAlgebra.Journal.Journal' by it (the+-- 'ExchangeAlgebra.Journal.Note' instance for 'Text' makes the @(.|)@ operator+-- immediately applicable); this reader deliberately stays at the algebra+-- level and does not construct an 'ExchangeAlgebra.Journal.Journal' itself, to+-- avoid pulling the Journal module into the dependency-light conversion layer.+--+-- >>> let csv = "side,account,amount,note\ndebit,Cash,1000,opening\n"+-- >>> parseNotedJournalCsv scientificAmount csv :: Either ConvError [(Side, AccountTitles, Double, Text)]+-- Right [(Debit,Cash,1000.0,"opening")]+parseNotedJournalCsv :: (HatVal v)+ => (Text -> Either ConvError v)+ -> Text+ -> Either ConvError [(Side, AccountTitles, v, Text)]+parseNotedJournalCsv amount txt = do+ keptHeader <- splitHeader txt+ traverse (rowNoted amount) keptHeader++-- | Split one line on commas and strip surrounding whitespace from each field.+-- No quoting is recognized, so a comma inside a field always separates. An+-- empty line yields a single empty field, matching 'T.splitOn'.+splitTrim :: Text -> [Text]+splitTrim = map T.strip . T.splitOn ","++------------------------------------------------------------------+-- Internal: minimal CSV plumbing (no quoting), header check, row parse.+------------------------------------------------------------------++-- Parse to bare (Side, AccountTitles, v) triples (note dropped).+parseRows :: (Text -> Either ConvError v)+ -> Text+ -> Either ConvError [(Side, AccountTitles, v)]+parseRows amount txt = do+ keptHeader <- splitHeader txt+ traverse (\fs -> (\(s, a, v, _) -> (s, a, v)) <$> rowNoted amount fs) keptHeader++-- | Drop blank\/comment lines, validate the header (@side,account,amount@ with an+-- optional @note@ column), and return the remaining data rows as already-split,+-- trimmed field lists.+splitHeader :: Text -> Either ConvError [[Text]]+splitHeader txt =+ case keptLines of+ [] -> Left (MalformedCsv "empty CSV (no header)")+ (h : body)+ | header == ["side", "account", "amount"]+ || header == ["side", "account", "amount", "note"]+ -> Right (map splitTrim body)+ | otherwise -> Left (MalformedCsv+ ("unexpected header: " <> T.intercalate "," header+ <> " (expected side,account,amount[,note])"))+ where header = map T.toLower (splitTrim h)+ where+ keptLines = filter keep (T.lines txt)+ keep l = let s = T.strip l in not (T.null s) && not ("#" `T.isPrefixOf` s)++-- | Parse one already-split row into @(Side, AccountTitles, value, note)@.+rowNoted :: (Text -> Either ConvError v)+ -> [Text]+ -> Either ConvError (Side, AccountTitles, v, Text)+rowNoted amount fields = case fields of+ [s, a, v] -> build s a v ""+ [s, a, v, n] -> build s a v n+ other -> Left (MalformedCsv+ ("row expected 3 or 4 fields, got "+ <> T.pack (show (length other)) <> ": "+ <> T.intercalate "," other))+ where+ build s a v n = do+ side <- parseSide s+ account <- parseAccountTitle a+ value <- amount v+ Right (side, account, value, n)
+ src/ExchangeAlgebra/IO/Output.hs view
@@ -0,0 +1,21 @@+-- | CSV and bookkeeping row output.+--+-- Import "ExchangeAlgebra.IO.Output.Csv" for CSV serialization or+-- "ExchangeAlgebra.IO.Output.Statements" for bookkeeping layouts.+--+-- | Task | Import |+-- | :--- | :--- |+-- | Serialize CSV | "ExchangeAlgebra.IO.Output.Csv" |+-- | Render bookkeeping rows | "ExchangeAlgebra.IO.Output.Statements" |+--+-- Prepare financial statements with+-- 'ExchangeAlgebra.Accounting.Statements.Presentation.present', then render+-- them with 'financialStatementRows'. Use 'accountLedgerRowsJournal' for+-- account ledger rows without dates.+module ExchangeAlgebra.IO.Output+ ( module ExchangeAlgebra.IO.Output.Csv+ , module ExchangeAlgebra.IO.Output.Statements+ ) where++import ExchangeAlgebra.IO.Output.Csv+import ExchangeAlgebra.IO.Output.Statements
+ src/ExchangeAlgebra/IO/Output/Csv.hs view
@@ -0,0 +1,32 @@+-- | Serialize text tables as CSV.+--+-- Import this module for 'writeCSV' and 'csvTranspose'.+module ExchangeAlgebra.IO.Output.Csv+ ( writeCSV+ , csvTranspose+ ) where++import qualified Data.List as L+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import System.IO (openFile, IOMode(WriteMode), hClose)++-- | Transpose a matrix of Text, padding shorter rows with empty Text.+csvTranspose :: [[T.Text]] -> [[T.Text]]+csvTranspose [] = []+csvTranspose mx = [ [ getCell r i | r <- mx ] | i <- [0 .. maxLen - 1] ]+ where+ maxLen = L.maximum (L.map L.length mx)+ getCell row i+ | i < L.length row = row !! i+ | otherwise = T.empty++-- | Write a matrix of Text as a CSV file. Each cell is quoted.+writeCSV :: FilePath -> [[T.Text]] -> IO ()+writeCSV path rows = do+ h <- openFile path WriteMode+ mapM_ (TIO.hPutStrLn h . toCsvLine) rows+ hClose h+ where+ toCsvLine = T.intercalate (T.pack ",") . L.map quoteCell+ quoteCell t = T.concat [T.pack "\"", T.replace (T.pack "\"") (T.pack "\"\"") t, T.pack "\""]
+ src/ExchangeAlgebra/IO/Output/Statements.hs view
@@ -0,0 +1,775 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Render journals, account ledgers, trial balances, and statements.+--+-- Import this module for row layouts and their file-writing wrappers.+-- 'accountLedgerRowsJournal' produces ledger rows when no date is needed.+-- For financial statements, close the ledger with+-- 'ExchangeAlgebra.Accounting.Closing.closingEntries' before preparing a+-- balance sheet, then use+-- 'ExchangeAlgebra.Accounting.Statements.Presentation.present' and+-- 'financialStatementRows'.+--+-- Statements receive one reporting scope. A ledger with several axes (entity,+-- period, department) is narrowed by the caller and mapped to account titles+-- with 'ExchangeAlgebra.Algebra.Core.mapMaybePosting' or+-- 'ExchangeAlgebra.Algebra.Core.mapPosting' before presentation. The remaining+-- axes are summed, and no axis name appears in the output.+--+-- | Task | Row layout | Writer |+-- | :--- | :--- | :--- |+-- | Journal | 'journalRows' | 'writeJournal' |+-- | Account ledger | 'accountLedgerRows' | 'writeAccountOf' |+-- | Account ledger without dates | 'accountLedgerRowsJournal' | 'writeAccountOfJournal' |+-- | Compound trial balance | 'compoundTrialBalanceRows' | 'writeCompoundTrialBalance' |+-- | Worksheet | 'worksheetRows' | 'writeWorksheet' |+-- | Post-closing trial balance | 'postClosingTrialBalanceRows' | 'writePostClosingTrialBalance' |+-- | Presented financial statements | 'financialStatementRows' | 'writeFinancialStatements' |+module ExchangeAlgebra.IO.Output.Statements+ ( journalRows+ , writeJournal+ , accountLedgerRows+ , writeAccountOf+ , accountLedgerRowsJournal+ , writeAccountOfJournal+ , compoundTrialBalanceRows+ , writeCompoundTrialBalance+ , worksheetRows+ , writeWorksheet+ , postClosingTrialBalanceRows+ , writePostClosingTrialBalance+ , StatementRowsError(..)+ , financialStatementRows+ , writeFinancialStatements+ ) where++import Data.Text (Text)+import qualified Data.Text as T+import ExchangeAlgebra.Accounting.Statements.Metric (DerivedMetric(..))+import ExchangeAlgebra.Accounting.TrialBalance.Balance (balanceSide, balanceAmount)++import qualified ExchangeAlgebra.Accounting.Statements.Presentation as Presentation+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as TB+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.IO.Output.Csv (writeCSV, csvTranspose)+import qualified ExchangeAlgebra.Algebra.Core as EA+import ExchangeAlgebra.Algebra.Core+import ExchangeAlgebra.Algebra.Base.Representation+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import qualified ExchangeAlgebra.Journal.Core as EJ+import ExchangeAlgebra.Journal.Core ((.|))+import qualified ExchangeAlgebra.Accounting.TrialBalance.Accum as Accum+import qualified Data.List as L+import qualified Data.Set as Set+import qualified Data.HashMap.Strict as Map+import qualified Data.Map.Strict as OMap+import Data.Time (Day)++-- $setup+-- >>> :set -XOverloadedStrings++-- | Why statement rows cannot be rendered for the requested sections.+data StatementRowsError+ = PeriodResultMissing TB.TrialBalanceStage+ -- ^ A revenue or expense section was requested, but the statements carry+ -- no period-result subtotal (for example statements prepared from an+ -- after-closing trial balance).+ deriving (Show, Eq)++-- | Rows of presented financial statements for the requested sections.+--+-- The header is @section, account, label, row, side, amount@. Sections are+-- rendered in the order given; within a section, lines keep presentation+-- order. The @row@ column is @line@ for one statement line (the net balance of+-- one account title) and @subtotal@ for a subtotal row. When a revenue or+-- expense section is requested, the subtotals of the statements follow the+-- requested sections as @subtotal@ rows with empty section and account cells.+-- Amounts use 'ExchangeAlgebra.Algebra.Value.Class.showValue'.+--+-- Subtotal rows must not be summed together with line rows; the @row@ column+-- distinguishes them, but the rendered 'Text' cannot enforce it.+financialStatementRows+ :: HatVal v+ => [Presentation.StatementSection]+ -> Presentation.FinancialStatements v+ -> Either StatementRowsError [[Text]]+financialStatementRows sections statements+ | requestsResult && not hasResult = Left (PeriodResultMissing+ (Presentation._statementTrialBalanceStage statements))+ | otherwise = Right+ (["section", "account", "label", "row", "side", "amount"]+ : lineRows ++ subtotalRows)+ where+ requestsResult = any (`elem` [Presentation.RevenueSection, Presentation.ExpenseSection])+ sections+ subtotals = Presentation._statementSubtotals statements+ hasResult = any ((== PeriodResultMetric) . Presentation._statementSubtotalMetric) subtotals+ lineRows =+ [ [ T.pack (show section)+ , T.pack (show (Presentation._lineAccount line))+ , Presentation._lineLabel line+ , "line"+ , T.pack (show (Presentation._lineSide line))+ , T.pack (showValue (Presentation._lineAmount line))+ ]+ | section <- sections+ , line <- Presentation._statementLines statements+ , Presentation._lineSection line == section+ ]+ subtotalRows =+ [ [ "", "", label, "subtotal", T.pack (show (balanceSide balance))+ , T.pack (showValue (balanceAmount balance)) ]+ | requestsResult+ , Presentation.StatementSubtotal _ label balance <- subtotals+ ]++-- | Write 'financialStatementRows' as CSV with 'writeCSV' (every cell quoted).+-- Nothing is written when the rows cannot be rendered.+writeFinancialStatements+ :: HatVal v+ => FilePath+ -> [Presentation.StatementSection]+ -> Presentation.FinancialStatements v+ -> IO (Either StatementRowsError ())+writeFinancialStatements path sections statements =+ case financialStatementRows sections statements of+ Left issue -> pure (Left issue)+ Right rows -> do+ writeCSV path rows+ pure (Right ())++-- | Convert a value to a text cell.+--+-- Complexity: O(show cost)+tshow :: (Show a) => a -> T.Text+tshow = T.pack . show++-- | Pad two lists to the same length. Appends empty text to the shorter list.+--+-- Complexity: O(max(|xs|, |ys|))+toSameLength :: [T.Text] -> [T.Text] -> ([T.Text],[T.Text])+toSameLength xs ys =+ case compare lx ly of+ EQ -> (xs, ys)+ LT -> (xs ++ replicate (ly - lx) T.empty, ys)+ GT -> (xs, ys ++ replicate (lx - ly) T.empty)+ where+ lx = Prelude.length xs+ ly = Prelude.length ys++-- | Build the rows of a journal (仕訳帳), as a pure value (the part+-- 'writeJournal' renders to CSV).+--+-- Entries are grouped by date (via @f@) into a deterministic, deduplicated,+-- ascending day sequence; within a day the debit and credit postings are+-- listed by 'decL'\/'decR' and padded to equal length with @toSameLength@ --+-- no aggregation\/@bar@ is applied, so a day with more debit than credit+-- postings (or vice versa) simply gets blank cells on the shorter side.+--+-- ==== __Examples__+--+-- Three days: a capital contribution, a cash sale, and a day with two debit+-- postings against one credit posting (the debit column is one line longer,+-- and the credit/date cells of that extra line are padded blank):+--+-- >>> import Data.Time (fromGregorian)+-- >>> type T = Alg Double (HatBase (AccountTitles, Day))+-- >>> let d1 = fromGregorian 2024 4 1+-- >>> let d2 = fromGregorian 2024 4 2+-- >>> let d3 = fromGregorian 2024 4 3+-- >>> let getDay (_ :< (_, d)) = d+-- >>> let alg = (100 .@ Not:<(Cash,d1)) .+ (100 .@ Not:<(CapitalStock,d1)) .+ (50 .@ Not:<(Cash,d2)) .+ (50 .@ Not:<(Sales,d2)) .+ (30 .@ Not:<(Cash,d3)) .+ (10 .@ Not:<(AccountsReceivable,d3)) .+ (40 .@ Not:<(Sales,d3)) :: T+-- >>> mapM_ print (journalRows alg getDay)+-- ["Day","Debit","Amount","Credit","Amount"]+-- ["2024-04-01","Cash","100.0","CapitalStock","100.0"]+-- ["2024-04-02","Cash","50.0","Sales","50.0"]+-- ["2024-04-03","AccountsReceivable","10.0","Sales","40.0"]+-- ["","Cash","30.0","",""]+--+-- Complexity: O(s * log d) (s = number of entries, d = number of distinct dates)+journalRows :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => Alg n b+ -> (b -> Day)+ -> [[T.Text]]+journalRows alg f = csvTranspose [ds, dt, dv, ct, cv]+ where+ days = L.sort $ Set.toList . Set.fromList $ L.map (f . _hatBase) $ EA.toList alg+ rows = L.map perDay days+ perDay d =+ let da = EA.filter (\y -> (f . _hatBase) y == d) alg+ dl = decL da+ dr = decR da+ dlTexts = L.map (tshow . getAccountTitle . _hatBase) (EA.toList dl)+ drTexts = L.map (tshow . getAccountTitle . _hatBase) (EA.toList dr)+ dlValues = L.map (tshow . _val) (EA.toList dl)+ drValues = L.map (tshow . _val) (EA.toList dr)+ (dt', ct') = toSameLength dlTexts drTexts+ (dv', cv') = toSameLength dlValues drValues+ (ds', _) = toSameLength [tshow d] cv'+ in (ds', dt', dv', ct', cv')+ ds = [T.pack "Day"] ++ concatMap (\(a,_,_,_,_) -> a) rows+ dt = [T.pack "Debit"] ++ concatMap (\(_,a,_,_,_) -> a) rows+ dv = [T.pack "Amount"] ++ concatMap (\(_,_,a,_,_) -> a) rows+ ct = [T.pack "Credit"] ++ concatMap (\(_,_,_,a,_) -> a) rows+ cv = [T.pack "Amount"] ++ concatMap (\(_,_,_,_,a) -> a) rows++-- | Output journal entries in CSV format.+-- Groups by date and records the debit/credit account titles and amounts for each day.+-- Pure layout is delegated to 'journalRows'; this function only writes the file.+--+-- Complexity: O(s * log d) (s = number of entries, d = number of distinct dates; see 'journalRows')+writeJournal :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => FilePath+ -> Alg n b+ -> (b -> Day)+ -> IO ()+writeJournal path alg f = writeCSV path (journalRows alg f)+++-- | Build the rows of a general ledger (総勘定元帳 / T-account) for the given+-- account titles, as a pure value (the part 'writeAccountOf' renders to CSV).+--+-- The ledger is the audit-trail view of an algebra: for each requested account+-- title, every posting touching that title is listed __individually in date+-- order, with no aggregation__ — the redundant sequence (seq) of the algebra is+-- preserved verbatim. (Contrast with 'writeCompoundTrialBalance', which is the+-- aggregated, @bar@\/@norm@-netted view.) This is the showcase of the algebra's+-- redundancy: each historical posting is kept as a separate ledger line.+--+-- For each title the layout is a two-sided T-account:+--+-- > <Title>+-- > Date | Debit | Date | Credit+--+-- Debit-side postings (@'whichSide' == 'Debit'@) go on the left, credit-side+-- postings on the right; the two columns are padded to equal length. Within a+-- side, postings are sorted by date and otherwise keep their original order+-- (so the line count equals the number of postings — no netting).+--+-- ==== __Examples__+--+-- Two separate debit postings to Cash are kept as two ledger lines (the seq is+-- /not/ aggregated into a single 30; the count of postings is preserved):+--+-- >>> import Data.Time (fromGregorian)+-- >>> type T = Alg Double (HatBase AccountTitles)+-- >>> let led = (10 .@ Not:<Cash) .+ (20 .@ Not:<Cash) .+ (5 .@ Hat:<Cash) :: T+-- >>> let d = fromGregorian 2024 4 1+-- >>> mapM_ print (accountLedgerRows [Cash] led (const d))+-- ["Cash","","",""]+-- ["Date","Debit","Date","Credit"]+-- ["2024-04-01","20.0","2024-04-01","5.0"]+-- ["2024-04-01","10.0","",""]+--+-- Complexity: O(t * s) (t = number of titles, s = number of entries).+accountLedgerRows :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => [AccountTitles]+ -> Alg n b+ -> (b -> Day)+ -> [[T.Text]]+accountLedgerRows titles alg f =+ concatMap titleBlock titles+ where+ titleBlock t =+ let xs = projByAccountTitle t alg+ debits = sortByDay (EA.toList (decL xs))+ credits = sortByDay (EA.toList (decR xs))+ dDates = L.map (tshow . f . _hatBase) debits+ dVals = L.map (tshow . _val) debits+ cDates = L.map (tshow . f . _hatBase) credits+ cVals = L.map (tshow . _val) credits+ (dDates', cDates') = toSameLength dDates cDates+ (dVals', cVals') = toSameLength dVals cVals+ header = [tshow t, T.empty, T.empty, T.empty]+ sub = [T.pack "Date", T.pack "Debit", T.pack "Date", T.pack "Credit"]+ body = L.zipWith4 (\a b c d -> [a,b,c,d]) dDates' dVals' cDates' cVals'+ in header : sub : body+ sortByDay = L.sortBy (\x y -> compare ((f . _hatBase) x) ((f . _hatBase) y))++-- | Output general ledgers (総勘定元帳) for the given account titles in CSV+-- format. Pure layout is delegated to 'accountLedgerRows'; this function only+-- performs the file write.+--+-- Each requested title is rendered as a two-sided T-account whose postings are+-- listed __individually, without aggregation__ (the seq redundancy is the audit+-- trail — see 'accountLedgerRows').+--+-- Complexity: O(t * s) (t = number of titles, s = number of entries).+writeAccountOf :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => [AccountTitles]+ -> FilePath+ -> Alg n b+ -> (b -> Day)+ -> IO ()+writeAccountOf titles path alg f = writeCSV path (accountLedgerRows titles alg f)++-- | Build the rows of general ledgers (総勘定元帳) from a 'EJ.Journal', as a+-- pure value (the part 'writeAccountOfJournal' renders to CSV), carrying the+-- per-posting note (摘要) as an extra column.+--+-- As with 'accountLedgerRows', postings are listed __individually, without+-- aggregation__ — the redundant sequence is the audit trail. Because each+-- posting carries its own note, this version uses a flat detail layout rather+-- than the two-sided T-account:+--+-- > <Title>+-- > Note | Debit | Credit+-- > <note> | <amount> | -- debit-side posting+-- > <note> | | <amount> -- credit-side posting+--+-- The note is rendered with 'show'. Postings are emitted in note order+-- (the 'EJ.Journal' is keyed by note); within a note the algebra's own seq+-- order is preserved. No 'EA.bar' \/ aggregation is applied.+--+-- ==== __Examples__+--+-- Two Cash postings under different notes; note order ("pay" < "sale")+-- determines the row order, not posting order:+--+-- >>> let jrn = ((100 .@ Not:<Cash) .| "sale") .+ ((40 .@ Hat:<Cash) .| "pay") :: EJ.Journal Text Double (HatBase AccountTitles)+-- >>> mapM_ print (accountLedgerRowsJournal [Cash] jrn)+-- ["Cash","",""]+-- ["Note","Debit","Credit"]+-- ["\"pay\"","","40.0"]+-- ["\"sale\"","100.0",""]+--+-- Complexity: O(t * s) (t = number of titles, s = number of postings).+accountLedgerRowsJournal :: (HatVal n, HatBaseClass b, ExBaseClass b, EJ.Note note)+ => [AccountTitles]+ -> EJ.Journal note n b+ -> [[T.Text]]+accountLedgerRowsJournal titles j =+ concatMap titleBlock titles+ where+ pairs = L.sortBy (\(a,_) (b,_) -> compare a b) (Map.toList (EJ.toMap j))+ titleBlock t =+ let header = [tshow t, T.empty, T.empty]+ sub = [T.pack "Note", T.pack "Debit", T.pack "Credit"]+ body = concatMap (noteRows t) pairs+ in header : sub : body+ noteRows t (note, alg) =+ let xs = projByAccountTitle t alg+ mkRow x =+ let amt = tshow (_val x)+ in if (whichSide . _hatBase) x == Debit+ then [tshow note, amt, T.empty]+ else [tshow note, T.empty, amt]+ in L.map mkRow (EA.toList (EA.filter (\x -> x /= EA.Zero) xs))++-- | Output general ledgers (総勘定元帳) from a 'EJ.Journal' in CSV format,+-- carrying the per-posting note (摘要) as an extra column. Pure layout is+-- delegated to 'accountLedgerRowsJournal'; this function only writes the+-- file. See 'accountLedgerRowsJournal' for the flat note-detail layout.+--+-- Complexity: O(t * s) (t = number of titles, s = number of postings; see+-- 'accountLedgerRowsJournal').+writeAccountOfJournal :: (HatVal n, HatBaseClass b, ExBaseClass b, EJ.Note note)+ => [AccountTitles]+ -> FilePath+ -> EJ.Journal note n b+ -> IO ()+writeAccountOfJournal titles path j =+ writeCSV path (accountLedgerRowsJournal titles j)+++------------------------------------------------------------------+-- Trial-balance aggregation (non-negative single-pass)+------------------------------------------------------------------++-- | Per-account gross debit/credit totals, accumulated in a __single pass__ over+-- the algebra's scalar entries (O(s)), keyed by 'AccountTitles' in 'Ord' order.+--+-- Each entry contributes its (non-negative) value to either the debit or the+-- credit total of its account title, classified by 'whichSide' (which already+-- folds in the Hat\/Not reversal) — exactly the @Debit@\/@Credit@ partition that+-- 'decL'\/'decR' produce. Summing per side reproduces @'norm' . 'decL'@ (debit+-- gross) and @'norm' . 'decR'@ (credit gross) without any signed netting, so the+-- value-domain invariant ($\\mathbb{R}_0^+$) is preserved: only non-negative+-- magnitudes are stored.+--+-- 'foldEntries' already skips zero values, matching the @x /= 'Zero'@ guard in+-- 'decL'\/'decR'. The resulting 'OMap.Map' iterates titles in ascending order,+-- matching the previous @L.sort . Set.toList . Set.fromList@ title enumeration.+--+-- Exact gross sums are retained until the caller has netted or combined accounts.+-- Each displayed amount is then rounded once.+--+-- Complexity: O(s) (s = number of scalar entries).+accountGrossTotals :: (HatVal n, ExBaseClass b)+ => Alg n b -> OMap.Map AccountTitles (Accum n, Accum n)+accountGrossTotals = EA.foldEntries step OMap.empty+ where+ step totals value base =+ let (!debit, !credit) = case whichSide base of+ Debit -> (addAccum value emptyAccum, emptyAccum)+ Credit -> (emptyAccum, addAccum value emptyAccum)+ Side -> (emptyAccum, emptyAccum)+ in OMap.insertWith merge (getAccountTitle base) (debit, credit) totals+ merge (!debit, !credit) (!otherDebit, !otherCredit) =+ let !debitTotal = mergeAccum debit otherDebit+ !creditTotal = mergeAccum credit otherCredit+ in (debitTotal, creditTotal)++-- | Read the exact difference before rounding its non-negative magnitude once.+-- NaN retains the legacy debit-column placement.+readAccumBalance :: HatVal n => (Accum n, Accum n) -> (Side, n)+readAccumBalance (debit, credit) =+ Accum.readAccountAccum Debit (mergeAccum debit (negateAccum credit))+++-- | Build the rows of a Compound Trial Balance (合計残高試算表), as a pure+-- value (the part 'writeCompoundTrialBalance' renders to CSV). Calculates the+-- debit total, credit total, and balance for each account title, aggregated+-- from exact gross accumulators. Each gross total and net balance is rounded+-- once; netting compares the accumulators before rounding, without tolerance.+-- Displayed rows need not sum to the displayed total. No implicit @bar@. Layout:+--+-- > Debit Balance | Debit Total | Account Title | Credit Total | Credit Balance+--+-- A debit-heavy account's balance is in the __Debit Balance__ (leftmost)+-- column and a credit-heavy account's balance is in the __Credit Balance__+-- (rightmost) column, as in 'worksheetRows' and+-- 'postClosingTrialBalanceRows'. The Total row sums each column. Releases up+-- to 0.5.3.1 placed each balance in the opposite column.+--+-- ==== __Examples__+--+-- Cash is debit-heavy (gross debit 100, credit 0); CapitalStock and+-- LoansPayable are credit-heavy:+--+-- >>> type T = Alg Double (HatBase AccountTitles)+-- >>> let alg = (100 .@ Not:<Cash) .+ (60 .@ Not:<LoansPayable) .+ (40 .@ Not:<CapitalStock) :: T+-- >>> mapM_ print (compoundTrialBalanceRows alg)+-- ["Debit Balance","Debit Total","Account Title","Credit Total","Credit Balance"]+-- ["100.0","100.0","Cash","0.0",""]+-- ["","0.0","CapitalStock","40.0","40.0"]+-- ["","0.0","LoansPayable","60.0","60.0"]+-- ["100.0","100.0","Total","100.0","100.0"]+--+-- Complexity: O(s) (single pass over s scalar entries; see @accountGrossTotals@)+compoundTrialBalanceRows :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => Alg n b -> [[T.Text]]+compoundTrialBalanceRows alg =+ header : lines' ++ [totalLine]+ where+ header = [T.pack "Debit Balance"+ ,T.pack "Debit Total"+ ,T.pack "Account Title"+ ,T.pack "Credit Total"+ ,T.pack "Credit Balance"]+ -- Single pass (O(s)): gross debit/credit totals per title, in Ord order.+ accounts = OMap.toList (accountGrossTotals alg)+ (lines', debitBalanceTotal, debitTotal, creditBalanceTotal, creditTotal) =+ L.foldl' step ([], emptyAccum, emptyAccum, emptyAccum, emptyAccum) accounts+ totalLine = [ tshow (roundAccum debitBalanceTotal)+ , tshow (roundAccum creditTotal)+ , T.pack "Total"+ , tshow (roundAccum debitTotal)+ , tshow (roundAccum creditBalanceTotal)+ ]+ step (accLines, dbt, dt, cbt, ct) (a, gross) =+ let xl = roundAccum (fst gross) -- norm (decL xs) : debit gross+ xr = roundAccum (snd gross) -- norm (decR xs) : credit gross+ difference = mergeAccum (fst gross) (negateAccum (snd gross))+ (dc, diff) = readAccumBalance gross+ -- The exact difference returns the wildcard v'Side' with a zero+ -- difference when an account nets to zero (e.g. a fully-cleared+ -- suspense account). Treat that as no balance on either side+ -- (cf. @sideCells@).+ (dbt', cbt') = case dc of+ Credit -> (dbt, mergeAccum cbt (negateAccum difference))+ Debit -> (mergeAccum dbt difference, cbt)+ Side -> (dbt, cbt)+ line = case dc of+ Debit -> [ tshow diff+ , tshow xl+ , tshow a+ , tshow xr+ , T.empty+ ]+ Credit -> [ T.empty+ , tshow xl+ , tshow a+ , tshow xr+ , tshow diff+ ]+ Side -> [ T.empty+ , tshow xl+ , tshow a+ , tshow xr+ , T.empty+ ]+ in (accLines ++ [line], dbt', mergeAccum dt (snd gross), cbt', mergeAccum ct (fst gross))++-- | Output a Compound Trial Balance in CSV format.+-- Calculates the debit total, credit total, and balance for each account+-- title and outputs as a table. Pure layout is delegated to+-- 'compoundTrialBalanceRows'; this function only writes the file. See+-- 'compoundTrialBalanceRows' for the column layout.+--+-- Complexity: O(s) (single pass over s scalar entries; see+-- 'compoundTrialBalanceRows')+writeCompoundTrialBalance :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => FilePath+ -> Alg n b+ -> IO ()+writeCompoundTrialBalance path alg =+ writeCSV path (compoundTrialBalanceRows alg)+++-- | Place a @(side, magnitude)@ balance into a (debit, credit) text column pair.+-- A zero magnitude produces two empty cells.+sideCells :: (HatVal n) => (Side, n) -> (T.Text, T.Text)+sideCells (side, mag)+ | mag == zeroValue = (T.empty, T.empty)+ -- 'diffRL' returns the wildcard v'Side' only when the net magnitude is zero,+ -- which the guard above has already handled; a non-zero balance is always+ -- 'Debit' or 'Credit'. The non-exhaustive @case@ is by design (audited).+ | otherwise = case side of+ Debit -> (tshow mag, T.empty)+ Credit -> (T.empty, tshow mag)++-- | Build the rows of an 8-column worksheet (8 桁精算表), as a pure value+-- (the part 'writeWorksheet' renders to CSV).+--+-- Columns (matching the lecture's 精算表): per account title there are four+-- debit\/credit column pairs —+--+-- > Account Title+-- > | Trial Balance (Debit, Credit) -- 残高試算表+-- > | Adjustments (Debit, Credit) -- 整理記入+-- > | Profit & Loss (Debit, Credit) -- 損益計算書+-- > | Balance Sheet (Debit, Credit) -- 貸借対照表+--+-- For each account title:+--+-- * the __Trial Balance__ pair is the net balance of the+-- /pre-adjustment ledger/ (@diffRL (projByAccountTitle title pre)@ — an explicit+-- @diffRL@ netting, no implicit @bar@);+-- * the __Adjustments__ pair is the net balance of the /adjustment entries/+-- (@diffRL (projByAccountTitle title adj)@);+-- * the __final balance__ is the net balance of @pre '.+' adj@, routed by+-- 'whatDiv': 'Cost'\/'Revenue' titles go to the __Profit & Loss__ columns,+-- 'Assets'\/'Liability'\/'Equity' titles go to the __Balance Sheet__+-- columns.+--+-- The penultimate row is the column totals of the trial-balance, P\/L and B\/S+-- column pairs. The final row is the profit\/loss balancing figure+-- (当期純利益\/純損失): it is the amount that makes each of the P\/L and B\/S+-- column pairs balance. By construction (the homomorphism @norm@) the P\/L+-- imbalance equals the B\/S imbalance — that equality is the worksheet's own+-- self-check (精算表の貸借差額の一致). __This function does not enforce the+-- equality__; if the inputs are inconsistent it still emits both figures so the+-- discrepancy is visible (rather than raising an error).+--+-- Account titles are listed in 'Ord' order so the output is deterministic.+--+-- ==== __Examples__+--+-- Opening capital 100 + a cash sale of 50, with no adjustments. Cash (150) and+-- CapitalStock (100) go to the Balance Sheet; Sales (50) goes to Profit & Loss.+-- The net income of 50 appears on the P\/L debit and B\/S credit, making both+-- statement column pairs balance at 50 and 150 respectively:+--+-- >>> type T = Alg Double (HatBase AccountTitles)+-- >>> let pre = (150 .@ Not:<Cash) .+ (100 .@ Not:<CapitalStock) .+ (50 .@ Not:<Sales) :: T+-- >>> mapM_ print (worksheetRows pre (zeroValue .@ Not:<Cash))+-- ["Account Title","Trial Balance","","Adjustments","","Profit & Loss","","Balance Sheet",""]+-- ["","Debit","Credit","Debit","Credit","Debit","Credit","Debit","Credit"]+-- ["Cash","150.0","","","","","","150.0",""]+-- ["CapitalStock","","100.0","","","","","","100.0"]+-- ["Sales","","50.0","","","","50.0","",""]+-- ["Subtotal","150.0","150.0","","","0.0","50.0","150.0","100.0"]+-- ["Net Income","","","","","50.0","","","50.0"]+-- ["Total","","","","","50.0","50.0","150.0","150.0"]+--+-- Complexity: O(s) (single pass per algebra over s scalar entries;+-- see @accountGrossTotals@).+worksheetRows :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => Alg n b -- ^ pre-adjustment ledger (決算整理前残高)+ -> Alg n b -- ^ adjustment entries (決算整理仕訳)+ -> [[T.Text]]+worksheetRows pre adj =+ header : sub : bodyRows ++ [tbTotalRow, netRow, grandTotalRow]+ where+ header =+ [ T.pack "Account Title"+ , T.pack "Trial Balance", T.empty+ , T.pack "Adjustments", T.empty+ , T.pack "Profit & Loss", T.empty+ , T.pack "Balance Sheet", T.empty ]+ sub =+ [ T.empty+ , T.pack "Debit", T.pack "Credit"+ , T.pack "Debit", T.pack "Credit"+ , T.pack "Debit", T.pack "Credit"+ , T.pack "Debit", T.pack "Credit" ]+ combined = pre .+ adj+ -- Single pass per algebra (O(s)): gross debit/credit totals per title.+ -- @diffRL (projByAccountTitle t alg)@ is reproduced as+ -- the exact difference of the per-title gross accumulators; a title absent from a map+ -- (e.g. only in 'adj') contributes the empty sums.+ preTotals = accountGrossTotals pre+ adjTotals = accountGrossTotals adj+ combTotals = accountGrossTotals combined+ grossOf m t = OMap.findWithDefault (emptyAccum, emptyAccum) t m+ -- Title enumeration: the keys of 'combined' in Ord order (= the previous+ -- 'L.sort . Set.toList . Set.fromList' over 'EA.toList combined').+ titles = OMap.keys combTotals+ -- per-title row + accumulate the four debit/credit column totals+ (bodyRows, tbD, tbC, plD, plC, bsD, bsC) =+ L.foldl' step+ ([], emptyAccum, emptyAccum, emptyAccum, emptyAccum, emptyAccum, emptyAccum) titles+ step (rows, tbd, tbc, pld, plc, bsd, bsc) t =+ let tb = readAccumBalance (grossOf preTotals t)+ (tbDc, tbCc) = sideCells tb+ adjPair = readAccumBalance (grossOf adjTotals t)+ (adjDc, adjCc) = sideCells adjPair+ finalPair = readAccumBalance (grossOf combTotals t)+ div' = classifyAccountDivision t+ isPL = div' == Cost || div' == Revenue+ (plDc, plCc, bsDc, bsCc)+ | isPL = let (d,c) = sideCells finalPair in (d, c, T.empty, T.empty)+ | otherwise = let (d,c) = sideCells finalPair in (T.empty, T.empty, d, c)+ row = [ tshow t, tbDc, tbCc, adjDc, adjCc, plDc, plCc, bsDc, bsCc ]+ -- column totals+ addCol (debit, credit) d c = case signAccum difference of+ Just GT -> (mergeAccum d difference, c)+ Just LT -> (d, mergeAccum c (negateAccum difference))+ Just EQ -> (d, c) -- balanced to zero: adds nothing+ Nothing -> (mergeAccum d difference, c) -- NaN follows the debit cell+ where+ difference = mergeAccum debit (negateAccum credit)+ (ntbd, ntbc) = addCol (grossOf preTotals t) tbd tbc+ (npld, nplc) | isPL = addCol (grossOf combTotals t) pld plc+ | otherwise = (pld, plc)+ (nbsd, nbsc) | not isPL = addCol (grossOf combTotals t) bsd bsc+ | otherwise = (bsd, bsc)+ in (rows ++ [row], ntbd, ntbc, npld, nplc, nbsd, nbsc)+ -- trial-balance totals row (整理記入 totals are intentionally omitted: the+ -- adjustment column does not have a meaningful single grand total in the+ -- lecture's layout; the self-check is on P/L and B/S).+ tbTotalRow =+ [ T.pack "Subtotal"+ , tshow (roundAccum tbD), tshow (roundAccum tbC)+ , T.empty, T.empty+ , tshow (roundAccum plD), tshow (roundAccum plC)+ , tshow (roundAccum bsD), tshow (roundAccum bsC) ]+ -- profit/loss balancing figure (当期純利益 / 純損失).+ -- P/L imbalance: if credit (revenue) side exceeds debit (cost) side, the+ -- difference is net income, recorded on the P/L debit side and the B/S+ -- credit side (it increases equity). The reverse is a net loss.+ plDiff = snd (readAccumBalance (plD, plC))+ bsDiff = snd (readAccumBalance (bsD, bsC))+ netProfit = signAccum (mergeAccum plC (negateAccum plD)) == Just GT+ (netLabel, netPlD, netPlC, netBsD, netBsC)+ | signAccum (mergeAccum plC (negateAccum plD)) == Just EQ =+ (T.pack "Net Income/Loss", T.empty, T.empty, T.empty, T.empty)+ | netProfit = (T.pack "Net Income", tshow plDiff, T.empty, T.empty, tshow bsDiff)+ | otherwise = (T.pack "Net Loss", T.empty, tshow plDiff, tshow bsDiff, T.empty)+ netRow =+ [ netLabel+ , T.empty, T.empty+ , T.empty, T.empty+ , netPlD, netPlC+ , netBsD, netBsC ]+ -- grand totals after adding the balancing figure (both sides now equal).+ plTotal = if netProfit then plC else plD+ bsTotal = case signAccum (mergeAccum bsD (negateAccum bsC)) of+ Just GT -> bsD+ _ -> bsC+ grandTotalRow =+ [ T.pack "Total"+ , T.empty, T.empty+ , T.empty, T.empty+ , tshow (roundAccum plTotal), tshow (roundAccum plTotal)+ , tshow (roundAccum bsTotal), tshow (roundAccum bsTotal) ]++-- | Output an 8-column worksheet (8 桁精算表) in CSV format.+-- Pure layout is delegated to 'worksheetRows'; this function only writes the+-- file. See 'worksheetRows' for the column structure and the self-check+-- (P\/L vs B\/S balancing figure) semantics.+--+-- Complexity: O(s) (single pass per algebra; see 'worksheetRows').+writeWorksheet :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => FilePath+ -> Alg n b -- ^ pre-adjustment ledger+ -> Alg n b -- ^ adjustment entries+ -> IO ()+writeWorksheet path pre adj = writeCSV path (worksheetRows pre adj)++-- | Build the rows of a post-closing trial balance (繰越試算表), as a pure+-- value (the part 'writePostClosingTrialBalance' renders to CSV).+--+-- After the closing transfers, only the /real/ (permanent) accounts remain on+-- the ledger. This lists the net balance of every 'Assets'\/'Liability'\/+-- 'Equity' account (the carried-forward balances, 次期繰越); 'Cost'\/'Revenue'+-- (nominal) accounts are __excluded by construction__, since they have been+-- closed out to the income summary. Layout:+--+-- > Debit | Account Title | Credit+--+-- The net balance per title is computed with an explicit @diffRL@ netting+-- (the same aggregation as 'writeCompoundTrialBalance'); no implicit @bar@.+-- The final row gives the debit and credit totals (which must agree).+--+-- Titles are listed in 'Ord' order for deterministic output.+--+-- ==== __Examples__+--+-- A ledger with cash, a loan (liability) and a sale (revenue). Only the real+-- accounts (Cash, LoansPayable) survive; the nominal Sales account is dropped.+--+-- >>> type T = Alg Double (HatBase AccountTitles)+-- >>> let led = (100 .@ Not:<Cash) .+ (40 .@ Hat:<Cash) .+ (60 .@ Not:<LoansPayable) .+ (100 .@ Not:<Sales) :: T+-- >>> mapM_ print (postClosingTrialBalanceRows led)+-- ["Debit","Account Title","Credit"]+-- ["60.0","Cash",""]+-- ["","LoansPayable","60.0"]+-- ["60.0","Total","60.0"]+--+-- Complexity: O(s) (single pass over s scalar entries; see @accountGrossTotals@).+postClosingTrialBalanceRows :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => Alg n b -> [[T.Text]]+postClosingTrialBalanceRows alg =+ header : bodyRows ++ [totalRow]+ where+ header = [T.pack "Debit", T.pack "Account Title", T.pack "Credit"]+ -- Single pass (O(s)): gross debit/credit totals per title, in Ord order;+ -- keep only real (Assets/Liability/Equity) titles.+ totals = accountGrossTotals alg+ titles = L.filter isReal (OMap.keys totals)+ isReal t = let d = classifyAccountDivision t+ in d == Assets || d == Liability || d == Equity+ (bodyRows, debitTotal, creditTotal) =+ L.foldl' step ([], emptyAccum, emptyAccum) titles+ step (rows, dt, ct) t =+ let (side, mag) = readAccumBalance+ (OMap.findWithDefault (emptyAccum, emptyAccum) t totals)+ (debit, credit) = OMap.findWithDefault (emptyAccum, emptyAccum) t totals+ difference = mergeAccum debit (negateAccum credit)+ (dCell, cCell) = sideCells (side, mag)+ (dt', ct') = case side of+ Debit -> (mergeAccum dt difference, ct)+ Credit -> (dt, mergeAccum ct (negateAccum difference))+ _ -> (dt, ct)+ row = [dCell, tshow t, cCell]+ in (rows ++ [row], dt', ct')+ totalRow = [tshow (roundAccum debitTotal), T.pack "Total", tshow (roundAccum creditTotal)]++-- | Output a post-closing trial balance (繰越試算表) in CSV format.+-- Pure layout is delegated to 'postClosingTrialBalanceRows'; this function only+-- writes the file. Only 'Assets'\/'Liability'\/'Equity' (real) accounts appear;+-- 'Cost'\/'Revenue' accounts are excluded — see 'postClosingTrialBalanceRows'.+--+-- Complexity: O(s) (single pass; see 'postClosingTrialBalanceRows').+writePostClosingTrialBalance :: (HatVal n, HatBaseClass b, ExBaseClass b)+ => FilePath+ -> Alg n b+ -> IO ()+writePostClosingTrialBalance path alg =+ writeCSV path (postClosingTrialBalanceRows alg)+
src/ExchangeAlgebra/Journal.hs view
@@ -3,6 +3,9 @@ Copyright : (c) Kaya Akagi. 2018-2026 Maintainer : yakagika@icloud.com + Import "ExchangeAlgebra.Journal.Core" for generic journal operations and+ "ExchangeAlgebra.Accounting.Exchange" for accounting operations.+ Released under the OWL license Package for Exchange Algebra defined by Hiroshi Deguchi.@@ -29,11 +32,24 @@ {-# LANGUAGE ExistentialQuantification #-} module ExchangeAlgebra.Journal- ( module ExchangeAlgebra.Algebra.Base+ ( customError+ , BaseClass(..)+ , Hat(..)+ , BaseForSingleHat(..)+ , HatBase(..)+ , Element(..)+ , AxisKey(..)+ , axisIsWildcard+ , AxisDecompose(..)+ , (.#)+ , Name+ , Subject+ , CountUnit(..)+ , Hashable(..)+ , Generic , HatVal(..) , HatBaseClass(..) , Redundant(..)- , Exchange(..) , pattern (:@) , (.@) , Note(..)@@ -55,935 +71,48 @@ , sigmaM , map , replaceNotes- , insert , projWithNote , projWithBase , projWithNoteBase , projWithBaseNetNorm , projWithNoteBaseNetNorm- , projWithBaseNorm- , projWithNoteNorm , filterWithNote , filterByAxis+ , postFromNetBy , gather ) where -import qualified ExchangeAlgebra.Algebra as EA-import ExchangeAlgebra.Algebra.Base-import ExchangeAlgebra.Algebra ( HatVal(..)- , Alg(..)- , Redundant(..)- , Exchange(..)- , pattern (:@)- , (.@))-import Prelude hiding (map, filter)-import qualified Data.HashMap.Strict as Map-import qualified Data.IntMap.Strict as IntMap-import Data.IntMap.Strict (IntMap)-import qualified Data.HashSet as HSet-import Data.HashSet (HashSet)-import Control.DeepSeq (NFData, rnf)-import qualified Data.Set as S-import qualified Data.List as L-import qualified Data.Map.Strict as M-import qualified Data.Text as T-import qualified Control.Monad as CM-import qualified Data.Binary as Binary-import Data.Typeable (Typeable, cast, typeOf)----------------------------------------------------------------------- * NoteAxisKey----------------------------------------------------------------------- | An existential type that holds each axis of a Note with its type erased.--- Used to decompose multi-dimensional Note types (tuples) into per-axis keys--- for indexing, mirroring how @AxisKey@ works for basis elements in 'Alg'.-data NoteAxisKey = forall a. (Eq a, Hashable a, Typeable a) => NoteAxisKey !a--instance Eq NoteAxisKey where- NoteAxisKey x == NoteAxisKey y = case cast y of- Nothing -> False- Just y' -> x == y'--instance Hashable NoteAxisKey where- hashWithSalt salt (NoteAxisKey x) = salt `hashWithSalt` typeOf x `hashWithSalt` x----------------------------------------------------------------------- * NoteAxisPosting----------------------------------------------------------------------- | Per-axis index for Note keys.--- Maps axis_number -> axis_value -> set of Notes.--- Mirrors @AxisPosting@ in 'Alg'.-type NoteAxisPosting n = IntMap (Map.HashMap NoteAxisKey (HashSet n))--{-# INLINE emptyNoteAxisPosting #-}-emptyNoteAxisPosting :: NoteAxisPosting n-emptyNoteAxisPosting = IntMap.empty--{-# INLINE insertNoteAxisPosting #-}--- | Complexity: O(d) where d is the number of axes in the Note-insertNoteAxisPosting :: (Eq n, Hashable n) => [NoteAxisKey] -> n -> NoteAxisPosting n -> NoteAxisPosting n-insertNoteAxisPosting !keys !note !idx =- snd $ L.foldl' step (0 :: Int, idx) keys- where- step (!axis, !acc) !k =- let !axisMap = IntMap.findWithDefault Map.empty axis acc- !notes0 = Map.lookupDefault HSet.empty k axisMap- !notes1 = HSet.insert note notes0- !axisMap' = Map.insert k notes1 axisMap- !acc' = IntMap.insert axis axisMap' acc- in (axis + 1, acc')--{-# INLINE deleteNoteAxisPosting #-}--- | Complexity: O(d) where d is the number of axes in the Note-deleteNoteAxisPosting :: (Eq n, Hashable n) => [NoteAxisKey] -> n -> NoteAxisPosting n -> NoteAxisPosting n-deleteNoteAxisPosting !keys !note !idx =- snd $ L.foldl' step (0 :: Int, idx) keys- where- step (!axis, !acc) !k =- case IntMap.lookup axis acc of- Nothing -> (axis + 1, acc)- Just axisMap ->- case Map.lookup k axisMap of- Nothing -> (axis + 1, acc)- Just notes0 ->- let !notes1 = HSet.delete note notes0- !axisMap' = if HSet.null notes1- then Map.delete k axisMap- else Map.insert k notes1 axisMap- !acc' = if Map.null axisMap'- then IntMap.delete axis acc- else IntMap.insert axis axisMap' acc- in (axis + 1, acc')--{-# INLINE queryNoteAxisPosting #-}--- | Query the NoteAxisPosting index for a single axis.--- Returns the set of Notes whose value matches on the specified axis.------ Complexity: O(1) (two map lookups)-queryNoteAxisPosting :: Int -> NoteAxisKey -> NoteAxisPosting n -> HashSet n-queryNoteAxisPosting !axis !key !idx =- case IntMap.lookup axis idx of- Nothing -> HSet.empty- Just axisMap -> Map.lookupDefault HSet.empty key axisMap----------------------------------------------------------------------- * Note----------------------------------------------------------------------- | Type class for journal annotations (notes attached to postings).--- @plank@ represents a blank note (analogous to @mempty@ in @Monoid@).------ @toNoteAxisKeys@ decomposes a Note into per-axis keys for AxisPosting--- indexing, mirroring how @toAxisKeys@ works for basis elements.--- For tuple Note types, each component becomes a separate axis.--- The default returns a single axis containing the Note itself.------ == Prefer an ADT note over @String@------ A note's /event/ axis is best modeled as a small enumeration (an ADT)--- rather than a @String@. A @String@ tag is a __stringly-typed semantic key__:--- the same literal must appear at the write site (@.| (\"trade\", t)@) and at--- every read site (@projWithNote [(\"trade\", t)]@), and a typo on either side--- still type-checks — the projection just /silently matches nothing/. With an--- ADT a mistyped constructor is a __compile error__, so reads and writes can--- never drift apart. Add 'plank' as its own explicit constructor (the note is a--- pointed set, so the blank tag is a distinguished element, not the empty--- string):------ @--- data MTag = PlankTag | Trade | Production | Report | Closing | Carryover--- deriving (Show, Eq, Ord, Enum, Bounded, Generic)--- instance Hashable MTag--- instance Note MTag where plank = PlankTag--- type MNote = (MTag, Int) -- the tuple instance keeps the (event, term) index--- @------ (The @marketEx1@ example and the @SimEvent@ note in the test suite follow--- this pattern.) If the ledger is ever spilled\/restored, also give the tag a--- @Binary@ instance (structurally derivable from @Generic@).-class (Show a, Eq a, Ord a, Hashable a, Typeable a) => Note a where- plank :: a- isPlank :: a -> Bool- isPlank x = x == plank- -- | Decompose a Note into per-axis keys for AxisPosting indexing.- -- Default: single axis with the Note itself.- toNoteAxisKeys :: a -> [NoteAxisKey]- toNoteAxisKeys a = [NoteAxisKey a]---- | Default instance for using @Int@ as a time axis (Term).--- @plank = -1@ is distinguished from non-negative term numbers.------ In simulations, typically used as @type Term = Int@.------ >>> (plank :: Int)--- -1------ >>> isPlank (0 :: Int)--- False------ >>> isPlank (-1 :: Int)--- True-instance Note Int where- plank = -1--instance Note String where- plank = ""--instance Note T.Text where- plank = ""--instance (Note a, Note b) => Note (a, b) where- plank = (plank, plank)- toNoteAxisKeys (a, b) = [NoteAxisKey a, NoteAxisKey b]--instance (Note a, Note b, Note c) => Note (a, b, c) where- plank = (plank, plank, plank)- toNoteAxisKeys (a, b, c) = [NoteAxisKey a, NoteAxisKey b, NoteAxisKey c]--instance (Note a, Note b, Note c, Note d) => Note (a, b, c, d) where- plank = (plank, plank, plank, plank)- toNoteAxisKeys (a, b, c, d) = [NoteAxisKey a, NoteAxisKey b, NoteAxisKey c, NoteAxisKey d]----------------------------------------------------------------------- * Journal----------------------------------------------------------------------- | Transaction data with annotations.--- Stored in a base + delta two-layer structure with per-axis indices.--- Base index is lazy (built on first axis query), while delta index is updated incrementally.--- Updates are appended only to delta and periodically compacted into base.------ __Invariants.__ The constructor is not exported; build with 'mkJournal',--- '(.|)' or 'fromList'. Its internal cache\/index fields @_jBaseAxis@ and--- @_jDeltaAxis@ must be--- exactly the Note axis indices (@buildNoteAxisPosting@) of @_jBase@ and--- @_jDelta@ respectively. The axis-filtered query path ('filterByAxis') reads--- those indices, so a value whose indices disagree with its maps yields wrong--- answers silently (not an exception).-data Journal n v b where- Journal :: (Note n, HatVal v, HatBaseClass b)- => { _jBase :: !(Map.HashMap n (Alg v b))- , _jDelta :: !(Map.HashMap n (Alg v b))- , _jBaseAxis :: NoteAxisPosting n- , _jDeltaAxis :: !(NoteAxisPosting n)- } -> Journal n v b--deltaCompactThreshold :: Int-deltaCompactThreshold = 128---- | Build Note axis index from map keys.-{-# INLINE buildNoteAxisPosting #-}-buildNoteAxisPosting :: Note n => Map.HashMap n a -> NoteAxisPosting n-buildNoteAxisPosting =- Map.foldlWithKey'- (\acc n _ -> insertNoteAxisPosting (toNoteAxisKeys n) n acc)- emptyNoteAxisPosting---- | The only safe way to build a t'Journal' from a base layer and a delta--- layer. It keeps the Note-axis indices consistent with both layers; see the--- invariants note on t'Journal'. The base axis index is lazy, while the delta--- axis index is built eagerly.-{-# INLINE mkJournal #-}-mkJournal :: (Note n, HatVal v, HatBaseClass b)- => Map.HashMap n (Alg v b) -> Map.HashMap n (Alg v b) -> Journal n v b-mkJournal bs delta = Journal bs delta baseIdx deltaIdx- where- ~baseIdx = buildNoteAxisPosting bs- !deltaIdx = buildNoteAxisPosting delta---- | Construct a Journal from a HashMap.------ Complexity: O(1). Base axis index is built lazily on first axis query.-{-# INLINE fromMap #-}-fromMap :: (HatVal v, HatBaseClass b, Note n)- => Map.HashMap n (Alg v b) -> Journal n v b-fromMap m = mkJournal m Map.empty---- | Retrieve all entries of a Journal as a HashMap.--- Merges the base and delta layers.------ Complexity: O(n) where n is the number of Notes-{-# INLINE toMap #-}-toMap :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Map.HashMap n (Alg v b)-toMap = materializeMap--{-# INLINE materializeMap #-}--- Short-circuits when one layer is empty: returns the other layer verbatim--- instead of building a fresh map via 'Map.unionWith'. This is the common--- case for @fromMap@ products (delta empty — the Lite @sigma msgs id@ shape)--- and for freshly @(.|)@-built journals (base empty). Values are untouched,--- so the result is identical to the unconditional union--- (audit R5 / ROAD_MAP P1b).-materializeMap :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Map.HashMap n (Alg v b)-materializeMap (Journal bs delta _ _)- | Map.null delta = bs- | Map.null bs = delta- | otherwise = Map.unionWith (.+) bs delta--{-# INLINE lookupNote #-}-lookupNote :: (HatVal v, HatBaseClass b, Note n)- => n -> Journal n v b -> Maybe (Alg v b)-lookupNote n (Journal bs delta _ _) =- case (Map.lookup n delta, Map.lookup n bs) of- (Nothing, Nothing) -> Nothing- (Just d, Nothing) -> Just d- (Nothing, Just b) -> Just b- (Just d, Just b) -> Just (b .+ d)--{-# INLINE compactIfNeeded #-}-compactIfNeeded :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Journal n v b-compactIfNeeded j@(Journal bs delta _ _)- | Map.size delta < deltaCompactThreshold = j- | otherwise = mkJournal (Map.unionWith (.+) bs delta) Map.empty--{-# INLINE appendMap #-}-appendMap :: (HatVal v, HatBaseClass b, Note n)- => Map.HashMap n (Alg v b) -> Journal n v b -> Journal n v b-appendMap rhs j@(Journal bs delta baseAxis deltaAxis)- | Map.null rhs = j- | otherwise = compactIfNeeded $ Journal bs delta' baseAxis deltaAxis'- where- (delta', deltaAxis') = Map.foldlWithKey' step (delta, deltaAxis) rhs-- step (!dAcc, !idxAcc) !k !v =- case Map.lookup k dAcc of- -- New note key: index it (insert) unless the value is zero.- Nothing- | EA.isZero v -> (dAcc, idxAcc)- | otherwise ->- ( Map.insert k v dAcc- , insertNoteAxisPosting (toNoteAxisKeys k) k idxAcc )- -- Existing note key: 'k' is already in the delta axis index- -- (the index tracks exactly the keys present in delta, and the- -- note IS the key, so its axis keys are unchanged). The- -- re-insert was therefore idempotent — skip it. Only a result- -- that collapses to zero changes the index, via delete- -- (audit R5 / ROAD_MAP P1b).- Just dv ->- let !dMerged = dv .+ v- in if EA.isZero dMerged- then ( Map.delete k dAcc- , deleteNoteAxisPosting (toNoteAxisKeys k) k idxAcc )- else ( Map.insert k dMerged dAcc, idxAcc )--instance ( Note n- , HatVal v- , HatBaseClass b- , Binary.Binary n- , Binary.Binary (Alg v b)- ) => Binary.Binary (Journal n v b) where- {-# INLINABLE put #-}- {-# INLINABLE get #-}- put j = do- let !m = toMap j- Binary.put (Map.size m :: Int)- Map.foldrWithKey- (\n alg k -> Binary.put n >> Binary.put alg >> k)- (pure ())- m- get = do- n <- Binary.get :: Binary.Get Int- fromMap <$> go n Map.empty- where- go !remaining !acc- | remaining <= 0 = pure acc- | otherwise = do- k <- Binary.get- v <- Binary.get- go (remaining - 1) (Map.insert k v acc)---- | Test whether the Journal is empty (zero).------ Complexity: O(1)-isZero :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Bool-isZero (Journal bs delta _ _) = Map.null bs && Map.null delta--pattern Zero :: (HatVal v, HatBaseClass b, Note n) => Journal n v b-pattern Zero <- (isZero -> True)- where- Zero = mkJournal Map.empty Map.empty---- | Smart constructor that attaches a Note (annotation) to an algebra element to build a Journal.------ Complexity: O(1)-(.|) :: (HatVal v, HatBaseClass b, Note n)- => Alg v b -> n -> Journal n v b-(.|) alg n = mkJournal Map.empty (Map.singleton n alg)--infixr 2 .|----------------------------------------------------------------------- Show---------------------------------------------------------------------- | Render each non-blank note as the sum of its algebra entries followed by--- one '(.|)'. A single group needs no parentheses because '(.+)' binds more--- tightly than '(.|)'; multiple groups are parenthesized individually so the--- result can be pasted back as an expression with the current fixities.------ The blank note remains invisible when it is the only group. In a mixed--- journal it is rendered explicitly, so every summand is one complete group.------ >>> type ShowTest = Journal String Double (HatBase AccountTitles)--- >>> (mempty :: ShowTest)--- 0--- >>> (5.00:@Not:<Cash .| plank :: ShowTest)--- 5.00:@Not:<Cash--- >>> one = 20.00:@Not:<Cash .+ 20.00:@Hat:<Deposits .| "N" :: ShowTest--- >>> one--- 20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "N"--- >>> toMap (20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "N" :: ShowTest) == toMap one--- True--- >>> two = 10.00:@Hat:<Cash .| "M" :: ShowTest--- >>> one .+ two--- (10.00:@Hat:<Cash .| "M") .+ (20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "N")--- >>> toMap ((10.00:@Hat:<Cash .| "M") .+ (20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "N") :: ShowTest) == toMap (one .+ two)--- True--- >>> blank = 5.00:@Not:<Cash .| plank :: ShowTest--- >>> one .+ blank--- (20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "N") .+ (5.00:@Not:<Cash .| "")-instance (HatVal v, HatBaseClass b, Note n) => Show (Journal n v b) where- showsPrec d js =- case groups of- [] -> showString "0"- [(k, a)]- | isPlank k -> showsPrec d a- | otherwise -> showParen (d > 2) (showsGroup k a)- _ -> showParen (d > 3) (showsGroups groups)- where- m = toMap js- groups = reverse $ Map.foldrWithKey collectGroup [] m-- collectGroup k a acc- | not (isPlank k) && Prelude.null (EA.toASCList a) = acc- | otherwise = (k, a) : acc-- showsGroups [] = id- showsGroups [(k, a)] = showParen True (showsGroup k a)- showsGroups ((k, a):rest) =- showParen True (showsGroup k a)- . showString " .+ "- . showsGroups rest-- showsGroup k a =- showsGroupAlg k a- . showString " .| "- . showsPrec 3 k-- showsGroupAlg k a- | isPlank k = showsPrec 3 a- | otherwise = showsAlgEntries (reverse (EA.toASCList a))-- showsAlgEntries [] = showString "0"- showsAlgEntries [x] = showsPrec 3 x- showsAlgEntries (x:xs) =- showsPrec 3 x- . showString " .+ "- . showsAlgEntries xs---------------------------------------------------------------------instance (HatVal v, HatBaseClass b, Note n) => Semigroup (Journal n v b) where- {-# INLINE (<>) #-}- (<>) = addJournal---- | Journal addition. Appends right-hand entries to the left-hand side.------ Complexity: Amortized O(size(rhs)); O(n) compaction when the delta exceeds the threshold------ >>> type Test = Journal String Double (HatBase AccountTitles)--- >>> x = 20.00:@Not:<Cash .+ 20.00:@Hat:<Deposits .| "Withdrawal" :: Test--- >>> y = 10.00:@Hat:<Cash .+ 10.00:@Not:<Deposits .| "Deposits" :: Test--- >>> x .+ y--- (10.00:@Not:<Deposits .+ 10.00:@Hat:<Cash .| "Deposits") .+ (20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .| "Withdrawal")-addJournal :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Journal n v b -> Journal n v b-addJournal lhs rhs = appendMap (toMap rhs) lhs--instance (HatVal v, HatBaseClass b, Note n) => Monoid (Journal n v b) where- mempty = mkJournal Map.empty Map.empty- mappend = (<>)---- | Shallow-structural 'NFData', mirroring the @'Alg' v b@ instance in--- "ExchangeAlgebra.Algebra": it forces the two-layer @base@ / @delta@ maps to--- WHNF on their spines and forces every contained 'Alg' (via the 'Alg' 'NFData'--- instance), but does not touch the lazily built axis indices (@_jBaseAxis@ /--- @_jDeltaAxis@), which are derived caches. This is enough for--- 'Control.Parallel.Strategies.rdeepseq' to fully evaluate journal "messages"--- before merging them in parallel.-instance NFData (Journal n v b) where- rnf (Journal bs delta _ _) =- Map.foldr (\alg acc -> rnf alg `seq` acc)- (Map.foldr (\alg acc -> rnf alg `seq` acc)- ()- bs)- delta--instance (HatVal v, HatBaseClass b, Note n) => Redundant (Journal n) v b where- (.^) = map (.^)- (.+) = mappend- (.*) x = map ((.*) x)- norm = norm . toAlg- (.-) x = map (.-) (gather plank x)- compress = map compress--instance (Note n, HatVal v, ExBaseClass b) => Exchange (Journal n) v b where- decR js = map (EA.filter (\x -> x /= EA.Zero && (whichSide . EA._hatBase) x == Credit)) js- decL xs = map (EA.filter (\x -> x /= EA.Zero && (whichSide . EA._hatBase) x == Debit)) xs- decP xs = map (EA.filter (\x -> x /= EA.Zero && (isHat . EA._hatBase) x)) xs- decM xs = map (EA.filter (\x -> x /= EA.Zero && (not . isHat . EA._hatBase) x)) xs-- -- scale-aware tolerance (WI-12), consistent with Alg's Exchange instance- balance xs = EA.nearlyEqScaled ((norm . decR) xs) ((norm . decL) xs)-- diffRL xs- | EA.nearlyEqScaled r l = (Side, 0)- | r > l = (Credit, r - l)- | otherwise = (Debit, l - r)- where- r = (norm . decR) xs- l = (norm . decL) xs----------------------------------------------------------------------- | Build a t'Journal' from a list of postings. @O(N)@ via a strict left fold--- (@L.foldl' (.+) mempty@).------ == Why a strict left fold------ This was previously a lazy right fold (@foldr (.+) mempty@), kept that way to--- freeze the same-base accumulation order for 'Double' (whose addition is--- non-associative, so reordering shifts the last-ULP of 'norm'). The strict left--- fold is far cheaper at scale — the lazy right fold builds a deep right-nested--- thunk that is expensive to force (≈40x at N=20000, ~15x at N=10000 in the core--- benchmark). The fold direction was switched to the fast version once the--- audited/exact path moved to t'ExchangeAlgebra.Value.MoneyDecimal'; see--- plans/in-progress/LAZY_EVAL_AUDIT.md and SELECTABLE_VALUE_TYPE_PLAN.md.------ == Behavior contract------ 'fromList' preserves the /multiset/ of postings exactly (no posting is added,--- dropped, or merged across bases that would otherwise be kept apart). When two--- postings collide on the /same note key and same base/ (and therefore land in--- one 'ExchangeAlgebra.Algebra.Alg' sequence) the strict left fold orders that--- @Seq@ opposite to the old lazy right fold. That order is observable through--- 'Eq' \/ 'Show' \/ 'toAlg' \/ @Binary@, and for 'Double' through the last-ULP of--- 'norm' \/ 'bar' (IEEE-754 addition is non-associative). Postings that differ in--- note or base land in separate map entries and are unaffected.------ For an /exact/ value type (t'ExchangeAlgebra.Value.MoneyDecimal') addition is--- associative, so 'norm' \/ 'bar' \/ balance are independent of construction order--- (the fold direction here, parallel merges, etc.). Use--- t'ExchangeAlgebra.Value.MoneyDecimal' when you need--- deterministic, auditable totals.------ >>> type Test = Journal String Double (HatBase AccountTitles)--- >>> x = [(1.00:@Hat:<Cash .| z) | z <- ["Loan Payment","Purchace Apple"]] :: [Test]--- >>> fromList x--- (1.00:@Hat:<Cash .| "Purchace Apple") .+ (1.00:@Hat:<Cash .| "Loan Payment")-fromList :: (HatVal v, HatBaseClass b, Note n)- => [Journal n v b] -> Journal n v b-fromList = L.foldl' (.+) mempty---------------------------------------------------------------------{-# INLINE mergeJournalMap #-}-mergeJournalMap :: (HatVal v, HatBaseClass b, Note n)- => Map.HashMap n (Alg v b)- -> Journal n v b- -> Map.HashMap n (Alg v b)-mergeJournalMap !acc (Journal bs delta _ _)- | Map.null bs && Map.null delta = acc- | otherwise =- let !acc1 = Map.foldlWithKey' mergeOne acc bs- in Map.foldlWithKey' mergeOne acc1 delta- where- mergeOne !m !n !alg- | EA.isZero alg = m- | otherwise = Map.insertWith (.+) n alg m--{-# INLINE mergeJournalMapIfNonZero #-}-mergeJournalMapIfNonZero :: (HatVal v, HatBaseClass b, Note n)- => Map.HashMap n (Alg v b)- -> Journal n v b- -> Map.HashMap n (Alg v b)-mergeJournalMapIfNonZero !acc js- | isZero js = acc- | otherwise = mergeJournalMap acc js---- | Summation function that applies a function to each list element and sums the resulting Journals.------ Complexity: O(|xs| * union cost)-{-# INLINE sigma #-}-sigma :: (HatVal v, HatBaseClass b, Note n)- => [a] -> (a -> Journal n v b) -> Journal n v b-sigma xs f = fromMap $ L.foldl' step Map.empty xs- where- step !acc !x = mergeJournalMapIfNonZero acc (f x)---- | Conditional summation over a double loop (Journal version).--- Applies the function only to pairs that satisfy the condition across all combinations of two lists, and sums the results.------ Complexity: O(|xs| * |ys| * union cost)-{-# INLINE sigma2When #-}-sigma2When :: (HatVal v, HatBaseClass b, Note n)- => [a]- -> [c]- -> (a -> c -> Bool)- -> (a -> c -> Journal n v b)- -> Journal n v b-sigma2When xs ys cond f =- fromMap $ L.foldl' outer Map.empty xs- where- outer !acc !x = L.foldl' (inner x) acc ys- inner !x !acc !y- | cond x y = mergeJournalMapIfNonZero acc (f x y)- | otherwise = acc---- | Sum each list element as an Alg on the specified Note and store the result in a Journal.--- Returns an empty Journal if the result is zero.------ Complexity: O(|xs| * union cost)-{-# INLINE sigmaOn #-}-sigmaOn :: (HatVal v, HatBaseClass b, Note n)- => n- -> [a]- -> (a -> Alg v b)- -> Journal n v b-sigmaOn n xs f =- let !alg = EA.sigma xs f- in if EA.isZero alg- then mempty- else alg .| n---- | Sum Alg values from Map keys and values on the specified Note and store the result in a Journal.--- Map version of 'sigmaOn'.------ Complexity: O(|map| * union cost)-{-# INLINE sigmaOnFromMap #-}-sigmaOnFromMap :: (HatVal v, HatBaseClass b, Note n, Ord k)- => n- -> M.Map k v- -> (k -> v -> Alg v b)- -> Journal n v b-sigmaOnFromMap n kvs f =- let !alg = EA.sigmaFromMap kvs f- in if EA.isZero alg- then mempty- else alg .| n---- | Quotient decomposition into the Journal (dec_κ landing on the graded--- carrier): partition an 'Alg' along the classes induced by a classifier and--- return the family as a t'Journal' keyed by the class 'Note'.------ A t'Journal' is exactly a finite map @Note → Alg@ (paper Definition 12), i.e.--- the library's native \"keyed family of algebras\" — so the decomposition--- stays inside the algebra vocabulary (no external container in the result).--- Each note's entry is the redundancy-preserving restriction of the input to--- that class (same guarantees as 'EA.decBy'): no 'bar', no aggregation, and--- @norm (decTo keyOf x) + norm residual == norm x@ (norm additivity of the--- decomposition).------ Entries classified to @Nothing@ or to 'plank' are dropped as residual--- ('plank' is the blank note and cannot carry a class).------ Complexity: O(m) single pass (via 'EA.decBy') + O(k) journal construction.------ >>> type TJ = Journal String Double (HatBase AccountTitles)--- >>> let alg = 100 :@ Not:<Cash .+ 30 :@ Hat:<Cash .+ 50 :@ Not:<Deposits :: Alg Double (HatBase AccountTitles)--- >>> let j = decTo (\(_ :< a) -> Just (if a == Cash then "cash" else "other")) alg :: TJ--- >>> norm j--- 180.0------ >>> norm (projWithNote ["cash"] j)--- 130.0-{-# INLINE decTo #-}-decTo :: (HatVal v, HatBaseClass b, Note n)- => (b -> Maybe n)- -> Alg v b- -> Journal n v b-decTo kf x =- fromMap $ Map.fromList- [ (n, alg)- | (n, alg) <- M.toList (EA.decBy kf x)- , not (isPlank n) ]---- | Summation in a monadic context. Applies a monadic function to each element and mconcats the results.------ Complexity: O(|xs| * cost(f))------ NOTE: kept as @mconcat <$> forM xs f@ deliberately. A strict @foldM@ left fold--- was tried (plan WI-3) but it changes the '<>' association order, which for--- 'Alg'/t'Journal' reorders the audit-trail sequence and (via non-associative--- 'Double' addition) shifts 'norm' results. Although the 'Monoid' laws make the--- value equal in exact arithmetic, it is observably different under floating point.--- See plans/in-progress/LAZY_EVAL_AUDIT.md (WI-3).-sigmaM :: (Monoid m, Monad m0) => [a] -> (a -> m0 m) -> m0 m-sigmaM xs f = mconcat <$> CM.forM xs f----------------------------------------------------------------------- | Combine entries from all Notes in a Journal into a single Alg.------ Complexity: O(total number of base keys across all Notes)-toAlg :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Alg v b-toAlg (Journal bs delta _ _) =- -- Fold base's elements directly onto delta's element list instead of- -- @Map.elems base ++ Map.elems delta@, which avoids materializing the- -- separate @Map.elems base@ list and the @(++)@ traversal.- EA.unionsMerge (Map.foldr (:) (Map.elems delta) bs)----------------------------------------------------------------------- | Apply function f to the entry of each Note in the Journal.--- Applies to merged Note entries (base + delta), preserving semantics.------ Complexity: O(j * cost(f)) where j is the number of Notes-map :: (HatVal v, HatBaseClass b, Note n)- => (Alg v b -> Alg v b) -> Journal n v b -> Journal n v b-map f = fromMap . Map.map f . toMap---- NB. The unused 'parallelMap'\/'parMap' helpers (a 'Control.Parallel.Strategies'--- based variant of 'map') were removed as dead code: neither was exported nor--- called. Reintroduce from history if a parallel journal map is needed.---- | Left-biased replacement of whole Notes. If a Note in the left Journal also--- exists in the right Journal, the complete left value replaces the right--- value. This is not redundant addition; contrast with '(.+)'.------ Complexity: O(n + m) where n, m are the number of Notes in each Journal-replaceNotes :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Journal n v b -> Journal n v b-replaceNotes x y = fromMap (Map.union (toMap x) (toMap y))--{-# DEPRECATED insert "Use replaceNotes; insert replaces whole notes and is not the redundant (.+)" #-}--- | Deprecated alias for 'replaceNotes'.------ >>> type Test = Journal String Double (HatBase AccountTitles)--- >>> x = 10.00:@Not:<Cash .| "A" :: Test--- >>> y = 20.00:@Not:<Cash .| "B" :: Test--- >>> z = 30.00:@Hat:<Cash .| "A" :: Test--- >>> insert z (x .+ y)--- (20.00:@Not:<Cash .| "B") .+ (30.00:@Hat:<Cash .| "A")-insert :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> Journal n v b -> Journal n v b-insert = replaceNotes----------------------------------------------------------------------- | projWithNote--- Projecting with Note.------ A query list containing 'plank' acts as a __wildcard__: the journal is--- returned unchanged (the blank note matches every note).------ >>> type Test = Journal String Double (HatBase CountUnit)--- >>> x = 1.00:@Hat:<Yen .+ 1.00:@Not:<Amount .| "cat" :: Test--- >>> y = 2.00:@Hat:<Yen .+ 2.00:@Not:<Amount .| "dog" :: Test--- >>> z = 3.00:@Hat:<Yen .+ 3.00:@Not:<Amount .| "fish" :: Test--- >>> projWithNote ["dog","cat"] (x .+ y .+ z)--- (1.00:@Not:<Amount .+ 1.00:@Hat:<Yen .| "cat") .+ (2.00:@Not:<Amount .+ 2.00:@Hat:<Yen .| "dog")-projWithNote :: (HatVal v, HatBaseClass b, Note n)- => [n] -> Journal n v b -> Journal n v b-projWithNote ns js- | any isPlank ns = js-projWithNote [n] js = fromMap $ case lookupNote n js of- Nothing -> Map.empty- Just a -> Map.singleton n a-projWithNote ns js =- fromMap $- S.foldl'- (\acc n -> case lookupNote n js of- Nothing -> acc- Just a -> Map.insert n a acc)- Map.empty- (S.fromList ns)----------------------------------------------------------------------- | projWithBase--- Projecting with Base.------ >>> type Test = Journal String Double (HatBase CountUnit)--- >>> x = 1.00:@Hat:<Yen .+ 1.00:@Not:<Amount .| "cat" :: Test--- >>> y = 2.00:@Not:<Yen .+ 2.00:@Hat:<Amount .| "dog" :: Test--- >>> z = 3.00:@Hat:<Yen .+ 3.00:@Not:<Amount .| "fish" :: Test--- >>> projWithBase [Not:<Amount] (x .+ y .+ z)--- (3.00:@Not:<Amount .| "fish") .+ (1.00:@Not:<Amount .| "cat")-projWithBase :: (HatVal v, HatBaseClass b, Note n)- => [b] -> Journal n v b -> Journal n v b-{-# INLINE projWithBase #-}-projWithBase [] _ = mempty-projWithBase bs js = fromMap $ Map.map (EA.proj bs) (toMap js)---- | Directly compute the /bar-netted/ norm of a base projection, without--- constructing an intermediate Journal. Per note this applies 'EA.projNetNorm',--- which nets each projected base's hat and not sides (the positive-part--- normalization); hence------ @projWithBaseNetNorm bs js == norm (map bar (projWithBase bs js))@------ which is __not__ the same as @norm (projWithBase bs js)@ when a query--- (e.g. a @HatNot@ wildcard, or a list selecting both sides of one base)--- selects both the hat and the not side of a base: the un-netted norm sums--- both sides, the netted one cancels them. See 'EA.projNetNorm'.--- Floating-point sums within each note and across notes use sequential addition--- and depend on posting and note order. Near-equal sides cancel under--- 'EA.nearlyEqScaled'. See "ExchangeAlgebra.Journal.Exact" for exact sums over--- the original postings.------ Complexity: O(j * proj cost) where j is the number of Notes-projWithBaseNetNorm :: (HatVal v, HatBaseClass b, Note n)- => [b] -> Journal n v b -> v-projWithBaseNetNorm [] _ = 0-projWithBaseNetNorm bs js =- Map.foldl' (\acc alg -> acc + EA.projNetNorm bs alg) 0 (toMap js)----------------------------------------------------------------------- | projWithNoteBase--- Projecting with Note and Base.------ 'plank' in the note list acts as a note-wildcard: the projection widens to--- all notes (falls back to 'projWithBase').------ >>> type Test = Journal String Double (HatBase CountUnit)--- >>> x = 1.00:@Hat:<Yen .+ 1.00:@Not:<Amount .| "cat" :: Test--- >>> y = 2.00:@Not:<Yen .+ 2.00:@Hat:<Amount .| "dog" :: Test--- >>> z = 3.00:@Hat:<Yen .+ 3.00:@Not:<Amount .| "fish" :: Test--- >>> projWithNoteBase ["dog","fish"] [Not:<Amount] (x .+ y .+ z)--- 3.00:@Not:<Amount .| "fish"-projWithNoteBase :: (HatVal v, HatBaseClass b, Note n)- => [n] -> [b] -> Journal n v b -> Journal n v b-{-# INLINE projWithNoteBase #-}-projWithNoteBase _ [] _ = mempty-projWithNoteBase ns bs js- | any isPlank ns = projWithBase bs js-projWithNoteBase [n] bs js = fromMap $ case lookupNote n js of- Nothing -> Map.empty- Just a -> Map.singleton n (EA.proj bs a)-projWithNoteBase [] bs js = projWithBase bs js-projWithNoteBase ns bs js =- fromMap $- S.foldl'- (\acc n -> case lookupNote n js of- Nothing -> acc- Just a -> Map.insert n (EA.proj bs a) acc)- Map.empty- (S.fromList ns)---- | Directly compute the /bar-netted/ norm of a note-and-base projection,--- without constructing an intermediate Journal. Like 'projWithBaseNetNorm' this--- goes through 'EA.projNetNorm', so per note each projected base is netted--- (positive-part normalization):------ @projWithNoteBaseNetNorm ns bs js == norm (map bar (projWithNoteBase ns bs js))@------ 'plank' in the note list acts as a note-wildcard (widens to all notes, like--- 'projWithNote').------ which is __not__ the same as @norm (projWithNoteBase ns bs js)@ when a query--- selects both sides of one base (see 'projWithBaseNetNorm').--- Floating-point sums within each note and across selected notes use sequential--- addition and depend on posting and note order. Near-equal sides cancel under--- 'EA.nearlyEqScaled'. See "ExchangeAlgebra.Journal.Exact" for exact sums over--- the original postings.------ Complexity: O(|ns| * proj cost)-projWithNoteBaseNetNorm :: (HatVal v, HatBaseClass b, Note n)- => [n] -> [b] -> Journal n v b -> v-projWithNoteBaseNetNorm _ [] _ = 0-projWithNoteBaseNetNorm ns bs js- | any isPlank ns = projWithBaseNetNorm bs js-projWithNoteBaseNetNorm [n] bs js = case lookupNote n js of- Nothing -> 0- Just a -> EA.projNetNorm bs a-projWithNoteBaseNetNorm [] bs js = projWithBaseNetNorm bs js-projWithNoteBaseNetNorm ns bs js =- S.foldl'- (\acc n -> case lookupNote n js of- Nothing -> acc- Just a -> acc + EA.projNetNorm bs a)- 0- (S.fromList ns)---- NB. Two RULES that rewrote @norm (projWithBase bs js)@ to 'projWithBaseNetNorm'--- (and the note-base analogue) were REMOVED here: the equation is false whenever--- a query selects both sides of one base (e.g. a @HatNot@ wildcard) — the--- left-hand side sums both sides, the right-hand side bar-nets them (verified:--- 14.0 vs 6.0 on a both-sided base). A rewrite rule must be semantics-preserving;--- callers who want the fused netted read-out call 'projWithBaseNetNorm' \/--- 'projWithNoteBaseNetNorm' explicitly.--{-# DEPRECATED projWithBaseNorm "renamed to 'projWithBaseNetNorm': the result is bar-netted per base, which the old name concealed. Will be removed in 0.6" #-}--- | Deprecated alias for 'projWithBaseNetNorm' (renamed in 0.5.0.0 so the name--- states the bar-netting).-projWithBaseNorm :: (HatVal v, HatBaseClass b, Note n)- => [b] -> Journal n v b -> v-projWithBaseNorm = projWithBaseNetNorm--{-# DEPRECATED projWithNoteNorm "renamed to 'projWithNoteBaseNetNorm': it takes note AND base queries and the result is bar-netted per base — both were missing from the old name. Will be removed in 0.6" #-}--- | Deprecated alias for 'projWithNoteBaseNetNorm' (renamed in 0.5.0.0 so the--- name states both the base argument and the bar-netting).-projWithNoteNorm :: (HatVal v, HatBaseClass b, Note n)- => [n] -> [b] -> Journal n v b -> v-projWithNoteNorm = projWithNoteBaseNetNorm----------------------------------------------------------------------- | Filter by a predicate on Note-entry pairs.--- Applies the filter to both the base and delta layers.------ Complexity: O(n) where n is the number of Notes-filterWithNote :: (HatVal v, HatBaseClass b, Note n)- => (n -> Alg v b -> Bool) -> Journal n v b -> Journal n v b-filterWithNote f (Journal bs delta _ _) =- let !base' = Map.filterWithKey f bs- !delta' = Map.filterWithKey f delta- in mkJournal base' delta'---- | Efficiently filter a Journal to entries whose Note matches on the specified axis.--- Uses base/delta NoteAxisPosting indices for O(|result|) retrieval after index construction.------ Axis numbers are 0-indexed. For a Note type @(EventName, Term)@:------ * axis 0 corresponds to EventName--- * axis 1 corresponds to Term------ For non-tuple Note types, axis 0 is the only valid axis.------ Complexity: O(|result|) after index construction; O(n) for first base-axis query on a Journal value------ >>> type Test = Journal (String, Int) Double (HatBase AccountTitles)--- >>> x = 10.00:@Not:<Cash .| ("A", 1) :: Test--- >>> y = 20.00:@Hat:<Cash .| ("B", 1) :: Test--- >>> z = 30.00:@Not:<Cash .| ("A", 2) :: Test--- >>> filterByAxis 0 (NoteAxisKey "A") (x .+ y .+ z)--- (10.00:@Not:<Cash .| ("A",1)) .+ (30.00:@Not:<Cash .| ("A",2))------ >>> filterByAxis 1 (NoteAxisKey (1 :: Int)) (x .+ y .+ z)--- (10.00:@Not:<Cash .| ("A",1)) .+ (20.00:@Hat:<Cash .| ("B",1))-{-# INLINE filterByAxis #-}-filterByAxis :: (HatVal v, HatBaseClass b, Note n)- => Int -> NoteAxisKey -> Journal n v b -> Journal n v b-filterByAxis axis key j@(Journal _ _ baseIdx deltaIdx) =- let !matched = HSet.union- (queryNoteAxisPosting axis key baseIdx)- (queryNoteAxisPosting axis key deltaIdx)- !result = HSet.foldl'- (\acc n -> case lookupNote n j of- Nothing -> acc- Just alg -> Map.insert n alg acc)- Map.empty- matched- in fromMap result----------------------------------------------------------------------- | gather--- Gathers all Alg into one on the given Note.------ >>> type Test = Journal String Double (EA.HatBase EA.AccountTitles)--- >>> x = 20.00:@Not:<Cash .+ 20.00:@Hat:<Deposits .| "Withdrawal" :: Test--- >>> y = 10.00:@Hat:<Cash .+ 10.00:@Not:<Deposits .| "Deposits" :: Test--- >>> gather "A" (x .+ y)--- 10.00:@Not:<Deposits .+ 20.00:@Hat:<Deposits .+ 20.00:@Not:<Cash .+ 10.00:@Hat:<Cash .| "A"-gather :: (HatVal v, HatBaseClass b, Note n)- => n -> Journal n v b -> Journal n v b-gather n js = (toAlg js) .| n+import ExchangeAlgebra.Algebra.Base+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Algebra.Core.Representation (Redundant(..), pattern (:@), (.@))+import ExchangeAlgebra.Journal.Core.Representation as ExchangeAlgebra.Journal+ ( Note(..)+ , NoteAxisKey(..)+ , NoteAxisPosting+ , Journal+ , pattern Zero+ , mkJournal+ , (.|)+ , toAlg+ , toMap+ , fromMap+ , fromList+ , sigma+ , sigma2When+ , sigmaOn+ , sigmaOnFromMap+ , decTo+ , sigmaM+ , map+ , replaceNotes+ , projWithNote+ , projWithBase+ , projWithNoteBase+ , projWithBaseNetNorm+ , projWithNoteBaseNetNorm+ , filterWithNote+ , filterByAxis+ , postFromNetBy+ , gather+ )+import Prelude hiding (map)
+ src/ExchangeAlgebra/Journal/Core.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE PatternSynonyms #-}++-- | Annotate algebra entries with notes, combine journals, and select entries.+-- This Journal layer exposes generic operations from its private representation+-- (Definitions 10-12). It uses Algebra values without account classification.+-- Start with Note and Journal, then choose an operation from the table.+--+-- @+-- Question Operation+-- Attach a note to an Alg .|+-- Combine journals fromList, sigma+-- Read entries as an Alg toAlg+-- Select notes or bases projWithNote, projWithBase+-- Select an indexed note axis filterByAxis+-- Replace whole notes replaceNotes+-- @+--+-- Import this module qualified alongside "ExchangeAlgebra.Algebra.Core" for+-- generic operations. For accounts, also import "ExchangeAlgebra.Accounting.Account"+-- and "ExchangeAlgebra.Accounting.Exchange". "ExchangeAlgebra.Journal"+-- re-exports the generic Journal API.+module ExchangeAlgebra.Journal.Core ( Note(..)+ , HasTermAxis(..)+ , NoteAxisKey(..)+ , NoteAxisPosting+ , Journal+ , pattern Zero+ , mkJournal+ , (.|)+ , toAlg+ , toMap+ , fromMap+ , fromList+ , sigma+ , sigma2When+ , sigmaOn+ , sigmaOnFromMap+ , decTo+ , sigmaM+ , map+ , replaceNotes+ , projWithNote+ , projWithBase+ , projWithNoteBase+ , projWithBaseNetNorm+ , projWithNoteBaseNetNorm+ , filterWithNote+ , filterByAxis+ , postFromNetBy+ , gather+ ) where++import Prelude hiding (map)+import ExchangeAlgebra.Journal.Core.Representation ( Note(..)+ , HasTermAxis(..)+ , NoteAxisKey(..)+ , NoteAxisPosting+ , Journal+ , pattern Zero+ , mkJournal+ , (.|)+ , toAlg+ , toMap+ , fromMap+ , fromList+ , sigma+ , sigma2When+ , sigmaOn+ , sigmaOnFromMap+ , decTo+ , sigmaM+ , map+ , replaceNotes+ , projWithNote+ , projWithBase+ , projWithNoteBase+ , projWithBaseNetNorm+ , projWithNoteBaseNetNorm+ , filterWithNote+ , filterByAxis+ , postFromNetBy+ , gather+ )
+ src/ExchangeAlgebra/Journal/Core/Representation.hs view
@@ -0,0 +1,1070 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE TypeFamilies #-}++-- | Generic journals, note indices, and redundancy-preserving operations.+-- This Journal layer owns the representation and instances for Definitions 10-12.+-- It uses generic Algebra owners; "ExchangeAlgebra.Journal.Core" exposes the+-- public operations. Read the note definitions before the journal operations.+module ExchangeAlgebra.Journal.Core.Representation ( Note(..)+ , HasTermAxis(..)+ , NoteAxisKey(..)+ , NoteAxisPosting+ , Journal+ , pattern Zero+ , mkJournal+ , (.|)+ , toAlg+ , toMap+ , fromMap+ , fromList+ , sigma+ , sigma2When+ , sigmaOn+ , sigmaOnFromMap+ , decTo+ , sigmaM+ , map+ , replaceNotes+ , projWithNote+ , projWithBase+ , projWithNoteBase+ , projWithBaseNetNorm+ , projWithNoteBaseNetNorm+ , filterWithNote+ , filterByAxis+ , postFromNetBy+ , gather+ ) where++import qualified ExchangeAlgebra.Algebra.Core.Representation as EA+import qualified ExchangeAlgebra.Algebra.Base.Representation as EA+import qualified ExchangeAlgebra.Algebra.Element.Representation as EA+import qualified ExchangeAlgebra.Algebra.Value.Class as EA+import ExchangeAlgebra.Algebra.Base.Representation+import ExchangeAlgebra.Algebra.Element.Representation+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..))+import ExchangeAlgebra.Algebra.Value (MoneyDouble, MoneyDecimal)+import ExchangeAlgebra.Algebra.Core.Representation (Alg, pattern (:@), Redundant(..), (.@))+import Prelude hiding (map, filter)+import qualified Prelude+import qualified Data.HashMap.Strict as Map+import qualified Data.IntMap.Strict as IntMap+import Data.IntMap.Strict (IntMap)+import qualified Data.HashSet as HSet+import Data.HashSet (HashSet)+import Control.DeepSeq (NFData, rnf)+import qualified Data.Set as S+import qualified Data.List as L+import qualified Data.Map.Strict as M+import qualified Data.Text as T+import qualified Control.Monad as CM+import qualified Data.Binary as Binary+import Data.Typeable (Typeable, cast, typeOf)++-- $setup+-- >>> :set -XOverloadedStrings++------------------------------------------------------------------+-- * NoteAxisKey+------------------------------------------------------------------++-- | An existential type that holds each axis of a Note with its type erased.+-- Used to decompose multi-dimensional Note types (tuples) into per-axis keys+-- for indexing, mirroring how @AxisKey@ works for basis elements in 'Alg'.+data NoteAxisKey = forall a. (Eq a, Hashable a, Typeable a) => NoteAxisKey !a++instance Eq NoteAxisKey where+ NoteAxisKey x == NoteAxisKey y = case cast y of+ Nothing -> False+ Just y' -> x == y'++instance Hashable NoteAxisKey where+ hashWithSalt salt (NoteAxisKey x) = salt `hashWithSalt` typeOf x `hashWithSalt` x++------------------------------------------------------------------+-- * NoteAxisPosting+------------------------------------------------------------------++-- | Per-axis index for Note keys.+-- Maps axis_number -> axis_value -> set of Notes.+-- Mirrors @AxisPosting@ in 'Alg'.+type NoteAxisPosting n = IntMap (Map.HashMap NoteAxisKey (HashSet n))++{-# INLINE emptyNoteAxisPosting #-}+emptyNoteAxisPosting :: NoteAxisPosting n+emptyNoteAxisPosting = IntMap.empty++{-# INLINE insertNoteAxisPosting #-}+-- | Complexity: O(d) where d is the number of axes in the Note+insertNoteAxisPosting :: (Eq n, Hashable n) => [NoteAxisKey] -> n -> NoteAxisPosting n -> NoteAxisPosting n+insertNoteAxisPosting !keys !note !idx =+ snd $ L.foldl' step (0 :: Int, idx) keys+ where+ step (!axis, !acc) !k =+ let !axisMap = IntMap.findWithDefault Map.empty axis acc+ !notes0 = Map.lookupDefault HSet.empty k axisMap+ !notes1 = HSet.insert note notes0+ !axisMap' = Map.insert k notes1 axisMap+ !acc' = IntMap.insert axis axisMap' acc+ in (axis + 1, acc')++{-# INLINE deleteNoteAxisPosting #-}+-- | Complexity: O(d) where d is the number of axes in the Note+deleteNoteAxisPosting :: (Eq n, Hashable n) => [NoteAxisKey] -> n -> NoteAxisPosting n -> NoteAxisPosting n+deleteNoteAxisPosting !keys !note !idx =+ snd $ L.foldl' step (0 :: Int, idx) keys+ where+ step (!axis, !acc) !k =+ case IntMap.lookup axis acc of+ Nothing -> (axis + 1, acc)+ Just axisMap ->+ case Map.lookup k axisMap of+ Nothing -> (axis + 1, acc)+ Just notes0 ->+ let !notes1 = HSet.delete note notes0+ !axisMap' = if HSet.null notes1+ then Map.delete k axisMap+ else Map.insert k notes1 axisMap+ !acc' = if Map.null axisMap'+ then IntMap.delete axis acc+ else IntMap.insert axis axisMap' acc+ in (axis + 1, acc')++{-# INLINE queryNoteAxisPosting #-}+-- | Query the NoteAxisPosting index for a single axis.+-- Returns the set of Notes whose value matches on the specified axis.+--+-- Complexity: O(1) (two map lookups)+queryNoteAxisPosting :: Int -> NoteAxisKey -> NoteAxisPosting n -> HashSet n+queryNoteAxisPosting !axis !key !idx =+ case IntMap.lookup axis idx of+ Nothing -> HSet.empty+ Just axisMap -> Map.lookupDefault HSet.empty key axisMap++------------------------------------------------------------------+-- * Note+------------------------------------------------------------------++-- | Type class for journal annotations (notes attached to postings).+-- @plank@ represents a blank note (analogous to @mempty@ in @Monoid@).+--+-- @toNoteAxisKeys@ decomposes a Note into per-axis keys for AxisPosting+-- indexing, mirroring how @toAxisKeys@ works for basis elements.+-- For tuple Note types, each component becomes a separate axis.+-- The default returns a single axis containing the Note itself.+--+-- == Prefer an ADT note over @String@+--+-- A note's /event/ axis is best modeled as a small enumeration (an ADT)+-- rather than a @String@. A @String@ tag is a __stringly-typed semantic key__:+-- the same literal must appear at the write site (@.| (\"trade\", t)@) and at+-- every read site (@projWithNote [(\"trade\", t)]@), and a typo on either side+-- still type-checks — the projection just /silently matches nothing/. With an+-- ADT a mistyped constructor is a __compile error__, so reads and writes can+-- never drift apart. Add 'plank' as its own explicit constructor (the note is a+-- pointed set, so the blank tag is a distinguished element, not the empty+-- string):+--+-- @+-- data MTag = PlankTag | Trade | Production | Report | Closing | Carryover+-- deriving (Show, Eq, Ord, Enum, Bounded, Generic)+-- instance Hashable MTag+-- instance Note MTag where plank = PlankTag+-- type MNote = (MTag, Int) -- the tuple instance keeps the (event, term) index+-- @+--+-- (The @SimEvent@ note in the test suite follows this pattern.)+-- If the ledger is ever spilled\/restored, also give the tag a+-- @Binary@ instance (structurally derivable from @Generic@).+class (Show a, Eq a, Ord a, Hashable a, Typeable a) => Note a where+ plank :: a+ isPlank :: a -> Bool+ isPlank x = x == plank+ -- | Decompose a Note into per-axis keys for AxisPosting indexing.+ -- Default: single axis with the Note itself.+ toNoteAxisKeys :: a -> [NoteAxisKey]+ toNoteAxisKeys a = [NoteAxisKey a]++-- | The convention that fixes /which/ part of a 'Note' is the simulation term.+--+-- A simulation policy or a multi-period ledger maps a Note to its term to+-- decide which entries belong to a period. Since a tuple Note has no intrinsic "term" component, the rule+-- is fixed here: __the term is the last component of the Note__. The shipped+-- instances follow this rule; a custom Note type provides a one-line instance.+--+-- >>> termOf ("buy" :: T.Text, 7 :: Int)+-- 7+--+-- >>> termOf ("buy" :: T.Text, "shopA" :: T.Text, 3 :: Int)+-- 3+class (Note n, Ord (TermOf n)) => HasTermAxis n where+ -- | The term type extracted from the Note.+ type TermOf n+ -- | Extract the term (last Note component, by the library convention).+ termOf :: n -> TermOf n++-- | Pair Note: the term is the second (last) component.+instance (Note e, Note t) => HasTermAxis (e, t) where+ type TermOf (e, t) = t+ termOf = snd++-- | Triple Note: the term is the third (last) component.+instance (Note e1, Note e2, Note t) => HasTermAxis (e1, e2, t) where+ type TermOf (e1, e2, t) = t+ termOf (_, _, t) = t++-- | Default instance for using @Int@ as a time axis (Term).+-- @plank = -1@ is distinguished from non-negative term numbers.+--+-- In simulations, typically used as @type Term = Int@.+--+-- >>> (plank :: Int)+-- -1+--+-- >>> isPlank (0 :: Int)+-- False+--+-- >>> isPlank (-1 :: Int)+-- True+instance Note Int where+ plank = -1++instance Note String where+ plank = ""++instance Note T.Text where+ plank = ""++instance (Note a, Note b) => Note (a, b) where+ plank = (plank, plank)+ toNoteAxisKeys (a, b) = [NoteAxisKey a, NoteAxisKey b]++instance (Note a, Note b, Note c) => Note (a, b, c) where+ plank = (plank, plank, plank)+ toNoteAxisKeys (a, b, c) = [NoteAxisKey a, NoteAxisKey b, NoteAxisKey c]++instance (Note a, Note b, Note c, Note d) => Note (a, b, c, d) where+ plank = (plank, plank, plank, plank)+ toNoteAxisKeys (a, b, c, d) = [NoteAxisKey a, NoteAxisKey b, NoteAxisKey c, NoteAxisKey d]++------------------------------------------------------------------+-- * Journal+------------------------------------------------------------------++-- | Transaction data with annotations.+-- Stored in a base + delta two-layer structure with per-axis indices.+-- Base index is lazy (built on first axis query), while delta index is updated incrementally.+-- Updates are appended only to delta and periodically compacted into base.+--+-- __Invariants.__ The constructor is not exported; build with 'mkJournal',+-- '(.|)' or 'fromList'. Its internal cache\/index fields @_jBaseAxis@ and+-- @_jDeltaAxis@ must be+-- exactly the Note axis indices (@buildNoteAxisPosting@) of @_jBase@ and+-- @_jDelta@ respectively. The axis-filtered query path ('filterByAxis') reads+-- those indices, so a value whose indices disagree with its maps yields wrong+-- answers silently (not an exception).+--+-- __Normal form.__ Neither layer holds a note whose algebra is zero+-- ('EA.isZero'). The redundant '(.+)' never cancels terms, so the sum of two+-- nonzero algebras is nonzero, and the merged entry of a note ('toMap') is+-- never zero either. Every public operation returns a journal in this form:+-- the operations that can produce a zero note ('map', 'projWithBase',+-- 'projWithNoteBase') drop it while they traverse the notes, and+-- 'mkJournal', 'fromMap' and the @Binary@ decoder drop the zero notes they+-- are given. Hence a journal without postings always matches 'Zero', and+-- the keys of 'toMap', 'Show' and @Binary@ do not depend on how a journal+-- was built. Postings, balances and readouts are unaffected, since a zero+-- note holds no posting.+data Journal n v b where+ Journal :: (Note n, HatVal v, HatBaseClass b)+ => { _jBase :: !(Map.HashMap n (Alg v b))+ , _jDelta :: !(Map.HashMap n (Alg v b))+ , _jBaseAxis :: NoteAxisPosting n+ , _jDeltaAxis :: !(NoteAxisPosting n)+ } -> Journal n v b++deltaCompactThreshold :: Int+deltaCompactThreshold = 128++-- | Build Note axis index from map keys.+{-# INLINE buildNoteAxisPosting #-}+buildNoteAxisPosting :: Note n => Map.HashMap n a -> NoteAxisPosting n+buildNoteAxisPosting =+ Map.foldlWithKey'+ (\acc n _ -> insertNoteAxisPosting (toNoteAxisKeys n) n acc)+ emptyNoteAxisPosting++-- | The only safe way to build a t'Journal' from a base layer and a delta+-- layer. It keeps the Note-axis indices consistent with both layers and+-- returns the normal form; see the invariants note on t'Journal'.+--+-- A note whose algebra is zero ('EA.isZero') is dropped from the layer that+-- holds it. Both maps are read once; a map without a zero note is used as+-- given, and only a map that holds one is rebuilt without it.+--+-- Complexity: O(n) where n is the number of notes in both layers; each note's+-- algebra is evaluated to WHNF. The base axis index is lazy, while the delta+-- axis index is built eagerly.+{-# INLINE mkJournal #-}+mkJournal :: (Note n, HatVal v, HatBaseClass b)+ => Map.HashMap n (Alg v b) -> Map.HashMap n (Alg v b) -> Journal n v b+mkJournal bs delta = mkJournalNonZero base' delta'+ where+ base'+ | any EA.isZero bs = Map.filter (not . EA.isZero) bs+ | otherwise = bs+ delta'+ | any EA.isZero delta = Map.filter (not . EA.isZero) delta+ | otherwise = delta++-- Internal constructor for callers that already hold the normal form (see the+-- invariants note on t'Journal'): neither map may hold a note whose algebra is+-- zero. It does not check this. It builds the Note-axis indices as+-- 'mkJournal' does: the base axis index lazily, the delta axis index eagerly.+{-# INLINE mkJournalNonZero #-}+mkJournalNonZero :: (Note n, HatVal v, HatBaseClass b)+ => Map.HashMap n (Alg v b) -> Map.HashMap n (Alg v b) -> Journal n v b+mkJournalNonZero bs delta = Journal bs delta baseIdx deltaIdx+ where+ ~baseIdx = buildNoteAxisPosting bs+ !deltaIdx = buildNoteAxisPosting delta++-- | Construct a Journal from a HashMap. A note whose algebra is zero is+-- dropped (see 'mkJournal').+--+-- Complexity: O(n) where n is the number of notes; the map is rebuilt only+-- when it holds a zero note. Base axis index is built lazily on first axis+-- query.+--+-- >>> case fromMap (Map.singleton "empty" EA.Zero) :: Journal T.Text Double (HatBase CountUnit) of { Zero -> True; _ -> False }+-- True+{-# INLINE fromMap #-}+fromMap :: (HatVal v, HatBaseClass b, Note n)+ => Map.HashMap n (Alg v b) -> Journal n v b+fromMap m = mkJournal m Map.empty++-- | Retrieve all entries of a Journal as a HashMap.+-- Merges the base and delta layers.+--+-- Complexity: O(n) where n is the number of Notes+{-# INLINE toMap #-}+toMap :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Map.HashMap n (Alg v b)+toMap = materializeMap++{-# INLINE materializeMap #-}+-- Short-circuits when one layer is empty: returns the other layer verbatim+-- instead of building a fresh map via 'Map.unionWith'. This is the common+-- case for @fromMap@ products (delta empty — the Lite @sigma msgs id@ shape)+-- and for freshly @(.|)@-built journals (base empty). Values are untouched,+-- so the result is identical to the unconditional union+-- (audit R5 / ROAD_MAP P1b).+materializeMap :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Map.HashMap n (Alg v b)+materializeMap (Journal bs delta _ _)+ | Map.null delta = bs+ | Map.null bs = delta+ | otherwise = Map.unionWith (.+) bs delta++{-# INLINE lookupNote #-}+lookupNote :: (HatVal v, HatBaseClass b, Note n)+ => n -> Journal n v b -> Maybe (Alg v b)+lookupNote n (Journal bs delta _ _) =+ case (Map.lookup n delta, Map.lookup n bs) of+ (Nothing, Nothing) -> Nothing+ (Just d, Nothing) -> Just d+ (Nothing, Just b) -> Just b+ (Just d, Just b) -> Just (b .+ d)++{-# INLINE compactIfNeeded #-}+compactIfNeeded :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Journal n v b+compactIfNeeded j@(Journal bs delta _ _)+ | Map.size delta < deltaCompactThreshold = j+ | otherwise = mkJournalNonZero (Map.unionWith (.+) bs delta) Map.empty++{-# INLINE appendMap #-}+appendMap :: (HatVal v, HatBaseClass b, Note n)+ => Map.HashMap n (Alg v b) -> Journal n v b -> Journal n v b+appendMap rhs j@(Journal bs delta baseAxis deltaAxis)+ | Map.null rhs = j+ | otherwise = compactIfNeeded $ Journal bs delta' baseAxis deltaAxis'+ where+ (delta', deltaAxis') = Map.foldlWithKey' step (delta, deltaAxis) rhs++ step (!dAcc, !idxAcc) !k !v =+ case Map.lookup k dAcc of+ -- New note key: index it (insert) unless the value is zero.+ Nothing+ | EA.isZero v -> (dAcc, idxAcc)+ | otherwise ->+ ( Map.insert k v dAcc+ , insertNoteAxisPosting (toNoteAxisKeys k) k idxAcc )+ -- Existing note key: 'k' is already in the delta axis index+ -- (the index tracks exactly the keys present in delta, and the+ -- note IS the key, so its axis keys are unchanged). The+ -- re-insert was therefore idempotent — skip it. Only a result+ -- that collapses to zero changes the index, via delete+ -- (audit R5 / ROAD_MAP P1b).+ Just dv ->+ let !dMerged = dv .+ v+ in if EA.isZero dMerged+ then ( Map.delete k dAcc+ , deleteNoteAxisPosting (toNoteAxisKeys k) k idxAcc )+ else ( Map.insert k dMerged dAcc, idxAcc )++-- | Serialize a journal as its note count followed by each note and its+-- algebra. Decoding checks each algebra as the 'Alg' instance does and fails+-- on a repeated note. The encoder never writes a note whose algebra is zero+-- (see the normal form on t'Journal'); the decoder drops one written by an+-- older encoder instead of rejecting the bytes, so they still decode.+instance ( Note n+ , HatVal v+ , HatBaseClass b+ , Binary.Binary n+ , Binary.Binary (Alg v b)+ ) => Binary.Binary (Journal n v b) where+ {-# INLINABLE put #-}+ {-# INLINABLE get #-}+ put j = do+ let !m = toMap j+ Binary.put (Map.size m :: Int)+ Map.foldrWithKey+ (\n alg k -> Binary.put n >> Binary.put alg >> k)+ (pure ())+ m+ get = do+ count <- Binary.get :: Binary.Get Int+ fromMap <$> go count Map.empty+ where+ go :: (Note note, Binary.Binary note, Binary.Binary algebra)+ => Int -> Map.HashMap note algebra -> Binary.Get (Map.HashMap note algebra)+ go remaining !acc+ | remaining <= 0 = pure acc+ | otherwise = do+ note <- Binary.get+ algebra <- Binary.get+ CM.when (Map.member note acc) (fail "Journal: repeated note")+ go (remaining - 1) (Map.insert note algebra acc)++-- | Test whether the Journal is empty (zero).+--+-- Complexity: O(1)+isZero :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Bool+isZero (Journal bs delta _ _) = Map.null bs && Map.null delta++-- | Construct an empty journal or match a journal with no postings.+pattern Zero :: (HatVal v, HatBaseClass b, Note n) => Journal n v b+pattern Zero <- (isZero -> True)+ where+ Zero = mkJournalNonZero Map.empty Map.empty++-- | Smart constructor that attaches a Note (annotation) to an algebra element to build a Journal.+-- Returns an empty Journal when the algebra element is zero, consistent with+-- the zero-value handling in @appendMap@ and 'sigmaOn'.+--+-- Complexity: O(1)+(.|) :: (HatVal v, HatBaseClass b, Note n)+ => Alg v b -> n -> Journal n v b+(.|) alg n+ | EA.isZero alg = mempty+ | otherwise = mkJournalNonZero Map.empty (Map.singleton n alg)++infixr 2 .|++------------------------------------------------------------------+-- Show+------------------------------------------------------------------+-- | Render each non-blank note as the sum of its algebra entries followed by+-- one '(.|)'. A single group needs no parentheses because '(.+)' binds more+-- tightly than '(.|)'; multiple groups are parenthesized individually so the+-- result can be pasted back as an expression with the current fixities.+--+-- The blank note remains invisible when it is the only group. In a mixed+-- journal it is rendered explicitly, so every summand is one complete group.+--+-- >>> type ShowTest = Journal T.Text Double (HatBase CountUnit)+-- >>> (mempty :: ShowTest)+-- 0+-- >>> (5.00.@Not:<Yen .| plank :: ShowTest)+-- 5.00:@Not:<Yen+-- >>> one = 20.00.@Not:<Yen .+ 20.00.@Hat:<Dollar .| "N" :: ShowTest+-- >>> one+-- 20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .| "N"+-- >>> toMap (20.00.@Hat:<Dollar .+ 20.00.@Not:<Yen .| "N" :: ShowTest) == toMap one+-- True+-- >>> two = 10.00.@Hat:<Yen .| "M" :: ShowTest+-- >>> show (one .+ two) `elem` ["(10.00:@Hat:<Yen .| \"M\") .+ (20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .| \"N\")", "(20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .| \"N\") .+ (10.00:@Hat:<Yen .| \"M\")"]+-- True+-- >>> toMap ((10.00.@Hat:<Yen .| "M") .+ (20.00.@Hat:<Dollar .+ 20.00.@Not:<Yen .| "N") :: ShowTest) == toMap (one .+ two)+-- True+-- >>> blank = 5.00.@Not:<Yen .| plank :: ShowTest+-- >>> show (one .+ blank) `elem` ["(20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .| \"N\") .+ (5.00:@Not:<Yen .| \"\")", "(5.00:@Not:<Yen .| \"\") .+ (20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .| \"N\")"]+-- True+instance (HatVal v, HatBaseClass b, Note n) => Show (Journal n v b) where+ showsPrec d js =+ case groups of+ [] -> showString "0"+ [(k, a)]+ | isPlank k -> showsPrec d a+ | otherwise -> showParen (d > 2) (showsGroup k a)+ _ -> showParen (d > 3) (showsGroups groups)+ where+ m = toMap js+ groups = reverse $ Map.foldrWithKey collectGroup [] m++ collectGroup k a acc+ | not (isPlank k) && Prelude.null (EA.toASCList a) = acc+ | otherwise = (k, a) : acc++ showsGroups [] = id+ showsGroups [(k, a)] = showParen True (showsGroup k a)+ showsGroups ((k, a):rest) =+ showParen True (showsGroup k a)+ . showString " .+ "+ . showsGroups rest++ showsGroup k a =+ showsGroupAlg k a+ . showString " .| "+ . showsPrec 3 k++ showsGroupAlg k a+ | isPlank k = showsPrec 3 a+ | otherwise = showsAlgEntries (reverse (EA.toASCList a))++ showsAlgEntries [] = showString "0"+ showsAlgEntries [x] = showsPrec 3 x+ showsAlgEntries (x:xs) =+ showsPrec 3 x+ . showString " .+ "+ . showsAlgEntries xs+------------------------------------------------------------------++instance (HatVal v, HatBaseClass b, Note n) => Semigroup (Journal n v b) where+ {-# INLINE (<>) #-}+ (<>) = addJournal++-- | Journal addition. Appends right-hand entries to the left-hand side.+--+-- Complexity: Amortized O(size(rhs)); O(n) compaction when the delta exceeds the threshold+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = 20.00.@Not:<Yen .+ 20.00.@Hat:<Dollar .| "Withdrawal" :: Test+-- >>> y = 10.00.@Hat:<Yen .+ 10.00.@Not:<Dollar .| "Deposits" :: Test+-- >>> toMap (x .+ y) == toMap ((10.00.@Not:<Dollar .+ 10.00.@Hat:<Yen .| "Deposits") .+ (20.00.@Hat:<Dollar .+ 20.00.@Not:<Yen .| "Withdrawal") :: Test)+-- True+addJournal :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Journal n v b -> Journal n v b+addJournal lhs rhs = appendMap (toMap rhs) lhs++instance (HatVal v, HatBaseClass b, Note n) => Monoid (Journal n v b) where+ mempty = mkJournalNonZero Map.empty Map.empty+ mappend = (<>)++-- | Shallow-structural 'NFData', mirroring the @'Alg' v b@ instance in+-- "ExchangeAlgebra.Algebra.Core": it forces the two-layer @base@ / @delta@ maps to+-- WHNF on their spines and forces every contained 'Alg' (via the 'Alg' 'NFData'+-- instance), but does not touch the lazily built axis indices (@_jBaseAxis@ /+-- @_jDeltaAxis@), which are derived caches. This is enough for+-- 'Control.Parallel.Strategies.rdeepseq' to fully evaluate journal "messages"+-- before merging them in parallel.+instance NFData (Journal n v b) where+ rnf (Journal bs delta _ _) =+ Map.foldr (\alg acc -> rnf alg `seq` acc)+ (Map.foldr (\alg acc -> rnf alg `seq` acc)+ ()+ bs)+ delta++instance (HatVal v, HatBaseClass b, Note n) => Redundant (Journal n) v b where+ {-# SPECIALIZE instance (HatBaseClass b, Note n) => Redundant (Journal n) Double b #-}+ {-# SPECIALIZE instance (HatBaseClass b, Note n) => Redundant (Journal n) MoneyDouble b #-}+ {-# SPECIALIZE instance (HatBaseClass b, Note n) => Redundant (Journal n) MoneyDecimal b #-}+ barWithin tolerance x = map (barWithin tolerance) (gather plank x)+ (.^) = map (.^)+ (.+) = mappend+ (.*) x = map ((.*) x)+ norm = norm . toAlg+ (.-) x = map (.-) (gather plank x)+ compress = map compress++------------------------------------------------------------------+-- | Build a t'Journal' from a list of postings. @O(N)@ via a strict left fold+-- (@L.foldl' (.+) mempty@).+--+-- == Why a strict left fold+--+-- This was previously a lazy right fold (@foldr (.+) mempty@), kept that way to+-- freeze the same-base accumulation order for 'Double' (whose addition is+-- non-associative, so reordering affected the former sequential 'norm'). The strict left+-- fold is far cheaper at scale — the lazy right fold builds a deep right-nested+-- thunk that is expensive to force (≈40x at N=20000, ~15x at N=10000 in the core+-- benchmark). The fold direction was switched to the fast version once the+-- audited/exact path moved to t'ExchangeAlgebra.Algebra.Value.MoneyDecimal'; see+-- plans/in-progress/LAZY_EVAL_AUDIT.md and SELECTABLE_VALUE_TYPE_PLAN.md.+--+-- == Behavior contract+--+-- 'fromList' preserves the /multiset/ of postings exactly (no posting is added,+-- dropped, or merged across bases that would otherwise be kept apart). When two+-- postings collide on the /same note key and same base/ (and therefore land in+-- one t'ExchangeAlgebra.Algebra.Core.Alg' sequence) the strict left fold orders that+-- @Seq@ opposite to the old lazy right fold. That order is observable through+-- 'Eq' \/ 'Show' \/ @Binary@, and for 'Double' through the sequential+-- sums in 'barWithin' (IEEE-754 addition is non-associative). Postings that differ in+-- note or base land in separate map entries and are unaffected.+--+-- For finite postings, 'norm', 'bar', and balance are independent of+-- construction order for every value type, including Double and MoneyDouble.+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = [(1.00.@Hat:<Yen .| z) | z <- ["Loan Payment","Purchace Apple"]] :: [Test]+-- >>> toMap (fromList x) == toMap ((1.00.@Hat:<Yen .| "Purchace Apple") .+ (1.00.@Hat:<Yen .| "Loan Payment") :: Test)+-- True+fromList :: (HatVal v, HatBaseClass b, Note n)+ => [Journal n v b] -> Journal n v b+fromList = L.foldl' (.+) mempty++------------------------------------------------------------------+{-# INLINE mergeJournalMap #-}+mergeJournalMap :: (HatVal v, HatBaseClass b, Note n)+ => Map.HashMap n (Alg v b)+ -> Journal n v b+ -> Map.HashMap n (Alg v b)+mergeJournalMap !acc (Journal bs delta _ _)+ | Map.null bs && Map.null delta = acc+ | otherwise =+ let !acc1 = Map.foldlWithKey' mergeOne acc bs+ in Map.foldlWithKey' mergeOne acc1 delta+ where+ mergeOne !m !n !alg+ | EA.isZero alg = m+ | otherwise = Map.insertWith (.+) n alg m++{-# INLINE mergeJournalMapIfNonZero #-}+mergeJournalMapIfNonZero :: (HatVal v, HatBaseClass b, Note n)+ => Map.HashMap n (Alg v b)+ -> Journal n v b+ -> Map.HashMap n (Alg v b)+mergeJournalMapIfNonZero !acc js+ | isZero js = acc+ | otherwise = mergeJournalMap acc js++-- | Summation function that applies a function to each list element and sums the resulting Journals.+--+-- Complexity: O(|xs| * union cost)+{-# INLINE sigma #-}+sigma :: (HatVal v, HatBaseClass b, Note n)+ => [a] -> (a -> Journal n v b) -> Journal n v b+sigma xs f = (`mkJournalNonZero` Map.empty) $ L.foldl' step Map.empty xs+ where+ step !acc !x = mergeJournalMapIfNonZero acc (f x)++-- | Conditional summation over a double loop (Journal version).+-- Applies the function only to pairs that satisfy the condition across all combinations of two lists, and sums the results.+--+-- Complexity: O(|xs| * |ys| * union cost)+{-# INLINE sigma2When #-}+sigma2When :: (HatVal v, HatBaseClass b, Note n)+ => [a]+ -> [c]+ -> (a -> c -> Bool)+ -> (a -> c -> Journal n v b)+ -> Journal n v b+sigma2When xs ys cond f =+ (`mkJournalNonZero` Map.empty) $ L.foldl' outer Map.empty xs+ where+ outer !acc !x = L.foldl' (inner x) acc ys+ inner !x !acc !y+ | cond x y = mergeJournalMapIfNonZero acc (f x y)+ | otherwise = acc++-- | Sum each list element as an Alg on the specified Note and store the result in a Journal.+-- Returns an empty Journal if the result is zero.+--+-- Complexity: O(|xs| * union cost)+{-# INLINE sigmaOn #-}+sigmaOn :: (HatVal v, HatBaseClass b, Note n)+ => n+ -> [a]+ -> (a -> Alg v b)+ -> Journal n v b+sigmaOn n xs f =+ let !alg = EA.sigma xs f+ in if EA.isZero alg+ then mempty+ else alg .| n++-- | Sum Alg values from Map keys and values on the specified Note and store the result in a Journal.+-- Map version of 'sigmaOn'.+--+-- Complexity: O(|map| * union cost)+{-# INLINE sigmaOnFromMap #-}+sigmaOnFromMap :: (HatVal v, HatBaseClass b, Note n, Ord k)+ => n+ -> M.Map k v+ -> (k -> v -> Alg v b)+ -> Journal n v b+sigmaOnFromMap n kvs f =+ let !alg = EA.sigmaFromMap kvs f+ in if EA.isZero alg+ then mempty+ else alg .| n++-- | Quotient decomposition into the Journal (dec_κ landing on the graded+-- carrier): partition an 'Alg' along the classes induced by a classifier and+-- return the family as a t'Journal' keyed by the class 'Note'.+--+-- A t'Journal' is exactly a finite map @Note → Alg@ (paper Definition 12), i.e.+-- the library's native \"keyed family of algebras\" — so the decomposition+-- stays inside the algebra vocabulary (no external container in the result).+-- Each note's entry is the redundancy-preserving restriction of the input to+-- that class (same guarantees as 'EA.decBy'): no 'bar', no aggregation, and+-- @norm (decTo keyOf x) + norm residual == norm x@ before rounding, or for+-- value types with exact sums. Rounded Double values need not satisfy this+-- decomposition law: with T = 2^53, put Not T and Not 1 on Cash and Not 1+-- on Sales, and classify Cash only. The left side is T, but norm x is T+2.+--+-- Entries classified to @Nothing@ or to 'plank' are dropped as residual+-- ('plank' is the blank note and cannot carry a class).+--+-- Complexity: O(m) single pass (via 'EA.decBy') + O(k) journal construction.+--+-- >>> type TJ = Journal T.Text Double (HatBase CountUnit)+-- >>> let alg = 100 .@ Not:<Yen .+ 30 .@ Hat:<Yen .+ 50 .@ Not:<Dollar :: Alg Double (HatBase CountUnit)+-- >>> let j = decTo (\(_ :< a) -> Just (if a == Yen then "cash" else "other")) alg :: TJ+-- >>> norm j+-- 180.0+--+-- >>> norm (projWithNote ["cash"] j)+-- 130.0+{-# INLINE decTo #-}+decTo :: (HatVal v, HatBaseClass b, Note n)+ => (b -> Maybe n)+ -> Alg v b+ -> Journal n v b+decTo kf x =+ (`mkJournalNonZero` Map.empty) $ Map.fromList+ [ (n, alg)+ | (n, alg) <- M.toList (EA.decBy kf x)+ , not (isPlank n) ]++-- | Summation in a monadic context. Applies a monadic function to each element and mconcats the results.+--+-- Complexity: O(|xs| * cost(f))+--+-- NOTE: kept as @mconcat <$> forM xs f@ deliberately. A strict @foldM@ left fold+-- was tried (plan WI-3) but it changes the '<>' association order, which for+-- 'Alg'/t'Journal' reorders the audit-trail sequence. The order is observable+-- through Eq, Show, Binary, and the sequential sums in barWithin. For finite+-- postings, norm, bar, and balance are order-independent for every value type.+-- See plans/in-progress/LAZY_EVAL_AUDIT.md (WI-3).+sigmaM :: (Monoid m, Monad m0) => [a] -> (a -> m0 m) -> m0 m+sigmaM xs f = mconcat <$> CM.forM xs f++------------------------------------------------------------------+-- | Combine entries from all Notes in a Journal into a single Alg.+--+-- Complexity: O(total number of base keys across all Notes)+toAlg :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Alg v b+toAlg (Journal bs delta _ _) =+ -- Fold base's elements directly onto delta's element list instead of+ -- @Map.elems base ++ Map.elems delta@, which avoids materializing the+ -- separate @Map.elems base@ list and the @(++)@ traversal.+ EA.unionsMerge (Map.foldr (:) (Map.elems delta) bs)++------------------------------------------------------------------+-- | Apply function f to the entry of each Note in the Journal.+-- Applies to merged Note entries (base + delta), preserving semantics.+-- A note for which @f@ returns zero is dropped (the normal form on+-- t'Journal'), in the same traversal.+--+-- Complexity: O(j * cost(f)) where j is the number of Notes+--+-- >>> case map (const EA.Zero) (1 .@ Not:<Yen .| "entry" :: Journal T.Text Double (HatBase CountUnit)) of { Zero -> True; _ -> False }+-- True+map :: (HatVal v, HatBaseClass b, Note n)+ => (Alg v b -> Alg v b) -> Journal n v b -> Journal n v b+map f js = mkJournalNonZero result Map.empty+ where+ result = Map.mapMaybe (\a -> case f a of+ projected | EA.isZero projected -> Nothing+ | otherwise -> Just projected) (toMap js)++-- NB. The unused 'parallelMap'\/'parMap' helpers (a 'Control.Parallel.Strategies'+-- based variant of 'map') were removed as dead code: neither was exported nor+-- called. Reintroduce from history if a parallel journal map is needed.++-- | Left-biased replacement of whole Notes. If a Note in the left Journal also+-- exists in the right Journal, the complete left value replaces the right+-- value. This is not redundant addition; contrast with '(.+)'.+--+-- Complexity: O(n + m) where n, m are the number of Notes in each Journal+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = 10.00.@Not:<Yen .| "A" :: Test+-- >>> y = 20.00.@Not:<Yen .| "B" :: Test+-- >>> z = 30.00.@Hat:<Yen .| "A" :: Test+-- >>> toMap (replaceNotes z (x .+ y)) == toMap (y .+ z)+-- True+replaceNotes :: (HatVal v, HatBaseClass b, Note n)+ => Journal n v b -> Journal n v b -> Journal n v b+replaceNotes x y = mkJournalNonZero (Map.union (toMap x) (toMap y)) Map.empty++------------------------------------------------------------------+-- | projWithNote+-- Projecting with Note.+--+-- A query list containing 'plank' acts as a __wildcard__: the journal is+-- returned unchanged (the blank note matches every note).+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = 1.00.@Hat:<Yen .+ 1.00.@Not:<Amount .| "cat" :: Test+-- >>> y = 2.00.@Hat:<Yen .+ 2.00.@Not:<Amount .| "dog" :: Test+-- >>> z = 3.00.@Hat:<Yen .+ 3.00.@Not:<Amount .| "fish" :: Test+-- >>> toMap (projWithNote ["dog","cat"] (x .+ y .+ z)) == toMap ((1.00.@Not:<Amount .+ 1.00.@Hat:<Yen .| "cat") .+ (2.00.@Not:<Amount .+ 2.00.@Hat:<Yen .| "dog") :: Test)+-- True+projWithNote :: (HatVal v, HatBaseClass b, Note n)+ => [n] -> Journal n v b -> Journal n v b+projWithNote ns js+ | any isPlank ns = js+projWithNote [n] js = (`mkJournalNonZero` Map.empty) $ case lookupNote n js of+ Nothing -> Map.empty+ Just a -> Map.singleton n a+projWithNote ns js =+ (`mkJournalNonZero` Map.empty) $+ S.foldl'+ (\acc n -> case lookupNote n js of+ Nothing -> acc+ Just a -> Map.insert n a acc)+ Map.empty+ (S.fromList ns)++------------------------------------------------------------------+-- | projWithBase+-- Projecting with Base. A note whose projection is zero is dropped (the+-- normal form on t'Journal').+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = 1.00.@Hat:<Yen .+ 1.00.@Not:<Amount .| "cat" :: Test+-- >>> y = 2.00.@Not:<Yen .+ 2.00.@Hat:<Amount .| "dog" :: Test+-- >>> z = 3.00.@Hat:<Yen .+ 3.00.@Not:<Amount .| "fish" :: Test+-- >>> L.sort [(n, EA.toASCList a) | (n, a) <- Map.toList (toMap (projWithBase [Not:<Amount] (x .+ y .+ z)))]+-- [("cat",[1.00:@Not:<Amount]),("fish",[3.00:@Not:<Amount])]+projWithBase :: (HatVal v, HatBaseClass b, Note n)+ => [b] -> Journal n v b -> Journal n v b+{-# INLINE projWithBase #-}+projWithBase [] _ = mempty+projWithBase bs js = map (EA.proj bs) js++-- | Directly compute the /bar-netted/ norm of a base projection, without+-- constructing an intermediate Journal. Per note this applies 'EA.projNetNorm',+-- which nets each projected base's hat and not sides (the positive-part+-- normalization); hence+--+-- @projWithBaseNetNorm bs js == norm (map bar (projWithBase bs js))@+--+-- which is __not__ the same as @norm (projWithBase bs js)@ when a query+-- (e.g. a @HatNot@ wildcard, or a list selecting both sides of one base)+-- selects both the hat and the not side of a base: the un-netted norm sums+-- both sides, the netted one cancels them. See 'EA.projNetNorm'.+-- Each note is netted exactly, and the unrounded residuals of the notes are+-- added and rounded once; the equation holds before rounding, or when sums+-- are represented exactly. Residuals T+1 and 1 in separate notes (T = 2^53)+-- yield T+2 here, but T after rounded note-local bar outputs. Sides cancel+-- only when exactly equal.+--+-- Complexity: O(j * proj cost) where j is the number of Notes+{-# SPECIALIZE projWithBaseNetNorm :: (HatBaseClass b, Note n) => [b] -> Journal n Double b -> Double #-}+{-# SPECIALIZE projWithBaseNetNorm :: (HatBaseClass b, Note n) => [b] -> Journal n MoneyDouble b -> MoneyDouble #-}+{-# SPECIALIZE projWithBaseNetNorm :: (HatBaseClass b, Note n) => [b] -> Journal n MoneyDecimal b -> MoneyDecimal #-}+projWithBaseNetNorm :: (HatVal v, HatBaseClass b, Note n)+ => [b] -> Journal n v b -> v+projWithBaseNetNorm [] _ = 0+projWithBaseNetNorm bs js =+ roundAccum $ Map.foldl' (\total alg ->+ mergeAccum total (EA.projectedNetState bs alg)) emptyAccum (toMap js)++------------------------------------------------------------------+-- | projWithNoteBase+-- Projecting with Note and Base. A note whose projection is zero is dropped+-- (the normal form on t'Journal').+--+-- 'plank' in the note list acts as a note-wildcard: the projection widens to+-- all notes (falls back to 'projWithBase').+--+-- >>> type Test = Journal T.Text Double (HatBase CountUnit)+-- >>> x = 1.00.@Hat:<Yen .+ 1.00.@Not:<Amount .| "cat" :: Test+-- >>> y = 2.00.@Not:<Yen .+ 2.00.@Hat:<Amount .| "dog" :: Test+-- >>> z = 3.00.@Hat:<Yen .+ 3.00.@Not:<Amount .| "fish" :: Test+-- >>> projWithNoteBase ["dog","fish"] [Not:<Amount] (x .+ y .+ z)+-- 3.00:@Not:<Amount .| "fish"+projWithNoteBase :: (HatVal v, HatBaseClass b, Note n)+ => [n] -> [b] -> Journal n v b -> Journal n v b+{-# INLINE projWithNoteBase #-}+projWithNoteBase _ [] _ = mempty+projWithNoteBase ns bs js+ | any isPlank ns = projWithBase bs js+projWithNoteBase [n] bs js = (`mkJournalNonZero` Map.empty) $ case lookupNote n js of+ Nothing -> Map.empty+ Just a -> case EA.proj bs a of+ projected | EA.isZero projected -> Map.empty+ | otherwise -> Map.singleton n projected+projWithNoteBase [] bs js = projWithBase bs js+projWithNoteBase ns bs js =+ (`mkJournalNonZero` Map.empty) $+ S.foldl'+ (\acc n -> case lookupNote n js of+ Nothing -> acc+ Just a -> case EA.proj bs a of+ projected | EA.isZero projected -> acc+ | otherwise -> Map.insert n projected acc)+ Map.empty+ (S.fromList ns)++-- | Directly compute the /bar-netted/ norm of a note-and-base projection,+-- without constructing an intermediate Journal. Like 'projWithBaseNetNorm' this+-- goes through 'EA.projNetNorm', so per note each projected base is netted+-- (positive-part normalization):+--+-- @projWithNoteBaseNetNorm ns bs js == norm (map bar (projWithNoteBase ns bs js))@+--+-- 'plank' in the note list acts as a note-wildcard (widens to all notes, like+-- 'projWithNote').+--+-- which is __not__ the same as @norm (projWithNoteBase ns bs js)@ when a query+-- selects both sides of one base (see 'projWithBaseNetNorm').+-- The equation holds before rounding, or when sums are represented exactly.+-- Residuals T+1 and 1 in separate notes (T = 2^53) yield T+2 here, but T+-- after rounded note-local bar outputs.+-- Each selected note is netted exactly, and the unrounded residuals are added+-- and rounded once. Sides cancel only when exactly equal.+--+-- Complexity: O(|ns| * proj cost)+{-# SPECIALIZE projWithNoteBaseNetNorm :: (HatBaseClass b, Note n) => [n] -> [b] -> Journal n Double b -> Double #-}+{-# SPECIALIZE projWithNoteBaseNetNorm :: (HatBaseClass b, Note n) => [n] -> [b] -> Journal n MoneyDouble b -> MoneyDouble #-}+{-# SPECIALIZE projWithNoteBaseNetNorm :: (HatBaseClass b, Note n) => [n] -> [b] -> Journal n MoneyDecimal b -> MoneyDecimal #-}+projWithNoteBaseNetNorm :: (HatVal v, HatBaseClass b, Note n)+ => [n] -> [b] -> Journal n v b -> v+projWithNoteBaseNetNorm _ [] _ = 0+projWithNoteBaseNetNorm ns bs js+ | any isPlank ns = projWithBaseNetNorm bs js+projWithNoteBaseNetNorm [n] bs js = case lookupNote n js of+ Nothing -> 0+ Just a -> EA.projNetNorm bs a+projWithNoteBaseNetNorm [] bs js = projWithBaseNetNorm bs js+projWithNoteBaseNetNorm ns bs js =+ roundAccum $ S.foldl'+ (\total n -> case lookupNote n js of+ Nothing -> total+ Just a -> mergeAccum total (EA.projectedNetState bs a))+ emptyAccum+ (S.fromList ns)++-- NB. Two RULES that rewrote @norm (projWithBase bs js)@ to 'projWithBaseNetNorm'+-- (and the note-base analogue) were REMOVED here: the equation is false whenever+-- a query selects both sides of one base (e.g. a @HatNot@ wildcard) — the+-- left-hand side sums both sides, the right-hand side bar-nets them (verified:+-- 14.0 vs 6.0 on a both-sided base). A rewrite rule must be semantics-preserving;+-- callers who want the fused netted read-out call 'projWithBaseNetNorm' \/+-- 'projWithNoteBaseNetNorm' explicitly.++------------------------------------------------------------------+-- | Filter by a predicate on Note-entry pairs.+-- Applies the filter to both the base and delta layers.+--+-- Complexity: O(n) where n is the number of Notes+filterWithNote :: (HatVal v, HatBaseClass b, Note n)+ => (n -> Alg v b -> Bool) -> Journal n v b -> Journal n v b+filterWithNote f (Journal bs delta _ _) =+ let !base' = Map.filterWithKey f bs+ !delta' = Map.filterWithKey f delta+ in mkJournalNonZero base' delta'++-- | Efficiently filter a Journal to entries whose Note matches on the specified axis.+-- Uses base/delta NoteAxisPosting indices for O(|result|) retrieval after index construction.+--+-- Axis numbers are 0-indexed. For a Note type @(EventName, Term)@:+--+-- * axis 0 corresponds to EventName+-- * axis 1 corresponds to Term+--+-- For non-tuple Note types, axis 0 is the only valid axis.+--+-- Complexity: O(|result|) after index construction; O(n) for first base-axis query on a Journal value+--+-- >>> type Test = Journal (T.Text, Int) Double (HatBase CountUnit)+-- >>> x = 10.00.@Not:<Yen .| ("A", 1) :: Test+-- >>> y = 20.00.@Hat:<Yen .| ("B", 1) :: Test+-- >>> z = 30.00.@Not:<Yen .| ("A", 2) :: Test+-- >>> toMap (filterByAxis 0 (NoteAxisKey ("A" :: T.Text)) (x .+ y .+ z)) == toMap ((10.00.@Not:<Yen .| ("A",1)) .+ (30.00.@Not:<Yen .| ("A",2)) :: Test)+-- True+--+-- >>> toMap (filterByAxis 1 (NoteAxisKey (1 :: Int)) (x .+ y .+ z)) == toMap ((10.00.@Not:<Yen .| ("A",1)) .+ (20.00.@Hat:<Yen .| ("B",1)) :: Test)+-- True+{-# INLINE filterByAxis #-}+filterByAxis :: (HatVal v, HatBaseClass b, Note n)+ => Int -> NoteAxisKey -> Journal n v b -> Journal n v b+filterByAxis axis key j@(Journal _ _ baseIdx deltaIdx) =+ let !matched = HSet.union+ (queryNoteAxisPosting axis key baseIdx)+ (queryNoteAxisPosting axis key deltaIdx)+ !result = HSet.foldl'+ (\acc n -> case lookupNote n j of+ Nothing -> acc+ Just alg -> Map.insert n alg acc)+ Map.empty+ matched+ in mkJournalNonZero result Map.empty++------------------------------------------------------------------+-- | gather+-- Gathers all Alg into one on the given Note.+--+-- >>> type Test = Journal T.Text Double (EA.HatBase EA.CountUnit)+-- >>> x = 20.00.@Not:<Yen .+ 20.00.@Hat:<Dollar .| "Withdrawal" :: Test+-- >>> y = 10.00.@Hat:<Yen .+ 10.00.@Not:<Dollar .| "Deposits" :: Test+-- >>> gather "A" (x .+ y)+-- 10.00:@Not:<Dollar .+ 20.00:@Hat:<Dollar .+ 20.00:@Not:<Yen .+ 10.00:@Hat:<Yen .| "A"+gather :: (HatVal v, HatBaseClass b, Note n)+ => n -> Journal n v b -> Journal n v b+gather n js = (toAlg js) .| n++-- | Gather notes, net each full base exactly, and group unrounded residuals by+-- the winning base's key. Round each callback argument once. The callback can+-- return multiple notes. Nonfinite-input bases retain their original postings+-- and notes and are not passed to the callback. Finite accumulation overflow+-- is rounded only at the callback boundary, which may receive infinity.+--+-- For finite postings, each callback amount is the once-rounded sum of the+-- selected exact base residuals, independent of posting order and with no+-- tolerance. This law applies to Double, MoneyDouble, and MoneyDecimal.+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Note n, Ord k) => (b -> Maybe k) -> (k -> Double -> Journal n Double b) -> Journal n Double b -> Journal n Double b #-}+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Note n, Ord k) => (b -> Maybe k) -> (k -> MoneyDouble -> Journal n MoneyDouble b) -> Journal n MoneyDouble b -> Journal n MoneyDouble b #-}+{-# SPECIALIZE postFromNetBy :: (HatBaseClass b, Note n, Ord k) => (b -> Maybe k) -> (k -> MoneyDecimal -> Journal n MoneyDecimal b) -> Journal n MoneyDecimal b -> Journal n MoneyDecimal b #-}+postFromNetBy :: (HatVal v, HatBaseClass b, Note n, Ord k)+ => (b -> Maybe k)+ -> (k -> v -> Journal n v b)+ -> Journal n v b+ -> Journal n v b+postFromNetBy keyOf post journal = retained .+ M.foldlWithKey'+ (\result key state -> result .+ post key (roundAccum state)) mempty amounts+ where+ (nonfinite, amounts) = EA.postNetStates keyOf (toAlg journal)+ retained+ | HSet.null nonfiniteBases = mempty+ | otherwise = map (EA.filter (\entry ->+ HSet.member (base (EA._hatBase entry)) nonfiniteBases)) journal+ nonfiniteBases = HSet.fromList (Prelude.map base (EA.bases nonfinite))
− src/ExchangeAlgebra/Journal/Exact.hs
@@ -1,224 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}---- | Checked journal readouts preserve exact residuals across note boundaries.--- This journal layer uses "ExchangeAlgebra.Algebra.Exact" and the existing--- journal projections. Start with the algebra accumulator contract, then read--- the projection section for cancellation within each note. Other readouts--- aggregate notes, as the existing journal bar does.------ Inputs must be finite and non-negative; all intermediate aggregation units--- and final outputs must fit the value type. Each scalar is rounded once,--- without cancellation tolerance, and negative zero becomes positive zero.--- For Double, MoneyDouble, and NN.Double, scalar bits depend only on the multiset--- of (note, complete base, side, value), including under accumulator merging.--- Reassigning notes, compressing, substituting rounded partial sums, enumeration,--- Show, and Binary are outside that guarantee.-module ExchangeAlgebra.Journal.Exact (- -- * Accumulators- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- -- * Journal readouts- , normExact- , barExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- -- * Projections- , projNetNormExact- , projWithBaseNetNormExact- , projWithNoteBaseNetNormExact- -- * Accounting readouts- , diffRLExact- , balanceExact- , accountBalancesExact- ) where--import qualified Data.Foldable as Foldable-import qualified Data.HashMap.Strict as HashMap-import qualified Data.Map.Strict as Map--import ExchangeAlgebra.Algebra (Alg, HatVal(..))-import qualified ExchangeAlgebra.Algebra.Internal as Internal-import ExchangeAlgebra.Algebra.Base (- AccountTitles- , ExBaseClass- , Hat(..)- , HatBaseClass(..)- , Side- )-import ExchangeAlgebra.Algebra.Exact (- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- )-import qualified ExchangeAlgebra.Algebra.Exact as Exact-import ExchangeAlgebra.Journal (Journal, Note(..), (.|))-import qualified ExchangeAlgebra.Journal as Journal-import ExchangeAlgebra.TrialBalance.Balance (AccountBalance)---- * Accumulators---- The scalar accumulator operations are re-exported from the algebra layer.---- * Journal readouts---- | Scan stored complete-base pairs, retaining Not and Hat states independently.--- No note boundary is crossed until the caller explicitly combines its input.-residuals :: (ExactSum n, HatBaseClass b)- => Alg n b -> Either ExactSumError [(b, Accum n)]-residuals Internal.Zero = Right []-residuals (value Internal.:@ postingBase)- | isZeroValue value = Right []- | otherwise = do- (direction, state) <- netAccumState (addAccum value emptyAccum) emptyAccum- pure $ case direction of- EQ -> []- _ -> [(merge side (base postingBase), state)]- where- side- | isHat postingBase = Hat- | otherwise = Not-residuals (Internal.Liner pairs _ _ _ _ _) = HashMap.foldlWithKey' finish (Right []) pairs- where- add total value- | isZeroValue value = total- | otherwise = addAccum value total- finish result basePart (Internal.Pair hats nots) = do- previous <- result- (direction, difference) <- netAccumState- (Foldable.foldl' add emptyAccum nots) (Foldable.foldl' add emptyAccum hats)- pure $ case direction of- EQ -> previous- GT -> (merge Not basePart, difference) : previous- LT -> (merge Hat basePart, difference) : previous---- | Sum every posting across notes and round the gross norm once.--- Finite non-negative inputs and their combined Hat-plus-Not total must fit the--- type. Unlike the existing norm, floating summation order does not affect bits.-normExact :: (ExactSum n, HatBaseClass b, Note t)- => Journal t n b -> Either ExactSumError n-normExact = Exact.normExact . Journal.toAlg---- | Gather notes into plank, then cancel complete bases exactly.--- Each finite non-negative base-side total must fit the type. Each residual is--- rounded once, with no tolerance. Notes are aggregated as in the existing bar;--- use the projection readouts for cancellation confined to each note.-barExact :: (ExactSum n, HatBaseClass b, Note t)- => Journal t n b -> Either ExactSumError (Journal t n b)-barExact journal = (.| plank) <$> Exact.barExact (Journal.toAlg journal)---- | Aggregate selected postings across notes by key and net each key once.--- Inputs must be finite and non-negative and each key-side total must fit the--- type. GT means Not wins, LT means Hat wins, and zero keys remain (EQ,0).--- The magnitude is non-negative, without the old signed sequential summation.--- Postings whose key is Nothing are not validated or aggregated.-balanceMapByExact :: (ExactSum n, HatBaseClass b, Note t, Ord k)- => (BasePart b -> Maybe k) -> Journal t n b- -> Either ExactSumError (Map.Map k (Ordering, n))-balanceMapByExact keyOf = Exact.balanceMapByExact keyOf . Journal.toAlg---- | Gather notes, cancel per complete base, and merge by key and winning side.--- Finite non-negative inputs, base-side totals, and key-side residual totals--- must fit the type. Each (Not,Hat) output component is rounded once, without--- tolerance. Distinct bases with Not 10 and Hat 7 retain (10,7).-netPairMapByExact :: (ExactSum n, HatBaseClass b, Note t, Ord k)- => (BasePart b -> Maybe k) -> Journal t n b- -> Either ExactSumError (Map.Map k (n, n))-netPairMapByExact keyOf = Exact.netPairMapByExact keyOf . Journal.toAlg---- | Gather notes, net each base, restore its winning side, select the key,--- merge residual states, round once per key, then invoke the journal callback.--- Finite non-negative inputs and all base-side and selected key totals must--- fit the type. No tolerance or rounded intermediate bar is used. The checks--- and rounding guarantee end at the callback's argument, not its output.-postFromNetByExact :: (ExactSum n, HatBaseClass b, Note t, Ord k)- => (b -> Maybe k) -> (k -> n -> Journal t n b) -> Journal t n b- -> Either ExactSumError (Journal t n b)-postFromNetByExact keyOf post journal = do- remaining <- residuals (Journal.toAlg journal)- amounts <- traverse roundAccum (Foldable.foldl' collect Map.empty remaining)- pure (Map.foldlWithKey' (\result key value -> result <> post key value) mempty amounts)- where- collect totals (postingBase, state) = case keyOf postingBase of- Nothing -> totals- Just key -> Map.insertWith mergeAccum key state totals---- * Projections---- | Merge every note's exact base residuals before the only scalar rounding.--- The caller has already selected notes and bases with the existing projections.-projectedNorm :: (ExactSum n, HatBaseClass b, Note t)- => Journal t n b -> Either ExactSumError n-projectedNorm journal = do- total <- HashMap.foldl' collect (Right emptyAccum) (Journal.toMap journal)- roundAccum total- where- collect result algebra = do- previous <- result- remaining <- residuals algebra- pure (Foldable.foldl' (\total (_, state) -> mergeAccum total state) previous remaining)---- | Project bases with set semantics and cancel within each note.--- This is 'projWithBaseNetNormExact': finite non-negative selected inputs,--- base-side totals, and the combined residual total must fit the type. There--- is one final rounding and no tolerance; repeated queries do not repeat values.-projNetNormExact :: (ExactSum n, HatBaseClass b, Note t)- => [b] -> Journal t n b -> Either ExactSumError n-projNetNormExact = projWithBaseNetNormExact---- | Project bases, net each complete base within each note, merge residual--- states across notes, and round once. Finite non-negative selected inputs,--- each base-side total, and the final residual sum must fit the type.--- Different notes containing Not 10 and Hat 10 yield 20, not zero.------ Unlike the old readout, no note-local scalar is rounded before summation.--- Equality with a norm of per-note bars holds only as a mathematical operation--- interpreting every operation at infinite precision. With T = 2^53, residuals--- T+1 and 1 yield T+2 here, but T if each base is rounded first. Duplicate base--- queries have set semantics and do not change output bits.-projWithBaseNetNormExact :: (ExactSum n, HatBaseClass b, Note t)- => [b] -> Journal t n b -> Either ExactSumError n-projWithBaseNetNormExact bases = projectedNorm . Journal.projWithBase bases---- | Select notes and bases with the existing wildcard and set semantics, then--- cancel within each note and round the merged residual states once.--- Empty notes or plank select all notes; duplicate queries do not duplicate--- postings. Finite non-negative selected inputs, base-side totals, and the--- final residual sum must fit the type. No tolerance or intermediate rounding--- is used, with the same mathematical equality qualification as--- 'projWithBaseNetNormExact'.-projWithNoteBaseNetNormExact :: (ExactSum n, HatBaseClass b, Note t)- => [t] -> [b] -> Journal t n b -> Either ExactSumError n-projWithNoteBaseNetNormExact notes bases =- projectedNorm . Journal.projWithNoteBase notes bases---- * Accounting readouts---- | Net debit and credit across notes and round the absolute difference once.--- Finite non-negative inputs and both side totals must fit the type. Only--- exact equality yields Side, without the tolerance of the old diffRL.--- Structural Side postings contribute no value and are not validated.-diffRLExact :: (ExactSum n, ExBaseClass b, Note t)- => Journal t n b -> Either ExactSumError (Side, n)-diffRLExact = Exact.diffRLExact . Journal.toAlg---- | Test exact debit-credit equality across notes with the checks of 'diffRLExact'.--- This observes exact direction, with no tolerance; its scalar is rounded once.-balanceExact :: (ExactSum n, ExBaseClass b, Note t)- => Journal t n b -> Either ExactSumError Bool-balanceExact = Exact.balanceExact . Journal.toAlg---- | Aggregate by account title across notes and net each account exactly.--- Finite non-negative inputs and account-side totals must fit the type. Each--- magnitude is rounded once, replacing sequential sums without a tolerance.--- Exact zero accounts remain NoBalance, as in the algebra account readout.--- Structural Side postings contribute no value and are not validated.-accountBalancesExact :: (ExactSum n, ExBaseClass b, Note t)- => Journal t n b- -> Either ExactSumError (Map.Map AccountTitles (AccountBalance n))-accountBalancesExact = Exact.accountBalancesExact . Journal.toAlg
src/ExchangeAlgebra/Journal/Transfer.hs view
@@ -1,181 +1,26 @@ {- |- Module : ExchangeAlgebra.Journal.Transfer- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Package for Exchange Algebra defined by Hiroshi Deguchi.-- Exchange Algebra is an algebraic description of bookkeeping system.- Details are below.-- <https://www.springer.com/gp/book/9784431209850>-- <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>-- This module provides the legacy transfer API. New code should import- "ExchangeAlgebra.Journal.Transfer.Rule" qualified.-- The legacy tree-based transfer walker is correct only under all of these- preconditions:-- (P1) Every @from@ pattern places wildcards in the same tuple positions.-- (P2) The @from@ patterns are pairwise non-overlapping.-- (P3) Ledger bases contain no wildcards.-- (P4) Base tuples are not nested.-- (P5) Value transformations do not return zero.+Module : ExchangeAlgebra.Journal.Transfer+Description : Carry entries across notes and read side totals. - Rules with mixed wildcard positions violate P1 and can make the tree- search miss matching entries.+This Journal-layer module re-exports carry operations from+"ExchangeAlgebra.Journal.Transfer.Rule". Start with 'carryEntries' to generate+additional entries that retain the original postings, or 'carryBefore' to+replace selected notes with a carry note. 'sideTotalsBy' accumulates each+side exactly and rounds its total once to 'Double'. +For note-aware accounting transfers, use @transferEntriesBy@ from+"ExchangeAlgebra.Accounting.Transfer". For closing, call+@Closing.closingEntries (toAlg journal)@ with a qualified import of+"ExchangeAlgebra.Accounting.Closing", then attach a note to the returned+entries with @(.|)@ from "ExchangeAlgebra.Journal.Core". Add those entries+to the original journal with @(.+)@. -}---{-# LANGUAGE GADTs #-}-{-# LANGUAGE PatternGuards #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PostfixOperators #-}----module ExchangeAlgebra.Journal.Transfer- ( TransTable- , isNullTable- , table- , TransTableParts- , (.->)- , (|%)- , ExchangeAlgebra.Journal.Transfer.transfer- , ExchangeAlgebra.Journal.Transfer.createTransfer- , ExchangeAlgebra.Journal.Transfer.incomeSummaryAccount- , ExchangeAlgebra.Journal.Transfer.netIncomeTransfer- , ExchangeAlgebra.Journal.Transfer.grossProfitTransfer- , ExchangeAlgebra.Journal.Transfer.ordinaryProfitTransfer- , ExchangeAlgebra.Journal.Transfer.retainedEarningTransfer- , ExchangeAlgebra.Journal.Transfer.finalStockTransferAggregated- , ExchangeAlgebra.Journal.Transfer.finalStockTransfer- ) where--import ExchangeAlgebra.Algebra hiding (map)-import qualified ExchangeAlgebra.Algebra.Transfer as EAT-import ExchangeAlgebra.Algebra.Transfer (TransTable- , isNullTable- , table- , TransTableParts- , (.->)- , (|%)- , finalStockTransferStep)-import qualified ExchangeAlgebra.Journal as EJ-import ExchangeAlgebra.Journal hiding ()------ | Apply legacy transfer transformations to each Note entry in a Journal.--- Wildcard portions within tuples are not transformed and retain their original values.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s) (j = number of Notes, s = number of scalar entries per Note)-{-# INLINE transfer #-}-transfer :: (HatVal v, HatBaseClass b, Note n)- => Journal n v b -> TransTable v b -> Journal n v b-transfer js tb = EJ.map (\x -> EAT.transfer x tb) js---- | Build a legacy Journal transfer from a list of rule triples.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".-createTransfer :: (Note n, HatVal v, ExBaseClass b)- => [(b,b,(v -> v))] -> (Journal n v b -> Journal n v b)-createTransfer tt = \ts -> transfer ts $ EAT.table tt---- * Closing transfer entries---- | Compute net income for the current period (Income Summary Account).--- Calculate the debit-credit difference and add it as NetIncome or NetLoss to the plank Note.--- When the ledger is balanced (credit == debit, net income is zero), @diffRL@ reports the--- wildcard v'Side'; in that case the journal is returned unchanged (balanced ledger =--- identity). Appending @Zero .| plank@ is not an identity for t'Journal' because @(.|)@ builds--- a @Map.singleton plank Zero@ and drives version/compaction, so the input is returned directly.--- The result contains a legacy NetIncome/NetLoss balancing coordinate and is--- an intermediate closing state, not input for reporting presentation. New--- reporting code should derive the result from a validated before-closing--- trial balance with "ExchangeAlgebra.Reporting.Metric".--- This is a legacy named transfer; see the module header for its preconditions.--- New transfer code should use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(s) (s = total number of scalar entries)-incomeSummaryAccount :: (Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-incomeSummaryAccount js = let (dc,diff) = diffRL js- in case dc of- Credit -> js .+ ((diff :@ (toNot wildcard) .~ NetIncome) .| plank)- Debit -> js .+ ((diff :@ (toNot wildcard) .~ NetLoss) .| plank)- Side -> js---- | Legacy net income transfer (Journal version). Transfer NetIncome/NetLoss to RetainedEarnings for each Note.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s) (j = number of Notes, s = number of scalar entries per Note)-netIncomeTransfer :: (Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-netIncomeTransfer = EJ.map EAT.netIncomeTransfer---- ** Journalizing---- | Legacy SNA/simulation gross-profit transfer (Journal version).--- This delegates to the legacy fixed-list rule and is not a JGAAP subtotal.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s)-grossProfitTransfer :: (Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-grossProfitTransfer = EJ.map EAT.grossProfitTransfer---- | Legacy SNA/simulation ordinary-profit transfer (Journal version).--- The fixed list predates the JCCI chart and is not a JGAAP subtotal.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ >>> type Test = Journal String Double (HatBase (CountUnit, AccountTitles))--- >>> x = 2279.0:@Not:<(Yen,Depreciation) .| "A" :: Test--- >>> y = 500475.0:@Not:<(Yen,InterestEarned) .| "B" :: Test--- >>> ExchangeAlgebra.Journal.Transfer.ordinaryProfitTransfer ( x .+ y)--- (2279.00:@Hat:<(Yen,OrdinaryProfit) .| "A") .+ (500475.00:@Not:<(Yen,OrdinaryProfit) .| "B")--ordinaryProfitTransfer :: (Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-ordinaryProfitTransfer = EJ.map EAT.ordinaryProfitTransfer---- | Legacy retained earnings transfer (Journal version). Transfer OrdinaryProfit to RetainedEarnings for each Note.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s)-retainedEarningTransfer :: (Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-retainedEarningTransfer = EJ.map EAT.retainedEarningTransfer---- | Apply the legacy Algebra-level closing to every Note and then fold the Note axis--- onto the plank via the Journal's '(.-)'. The per-Note lift without folding is--- @EJ.map EAT.finalStockTransfer@.--- See the module header for the legacy API's preconditions. New code should--- use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s)-finalStockTransferAggregated ::(Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-finalStockTransferAggregated = (.-) . EJ.map finalStockTransferStep+module ExchangeAlgebra.Journal.Transfer ( carryEntries+ , carryBefore+ , SideTotals(..)+ , sideTotal+ , CarryError(..)+ , sideTotalsBy+ ) where --- | Compatibility name for 'finalStockTransferAggregated'.--- Its behavior is unchanged: it closes each Note and folds the Note axis onto--- the plank.--- This is a legacy named transfer; see the module header for its preconditions.--- New code should use qualified "ExchangeAlgebra.Journal.Transfer.Rule".------ Complexity: O(j * s)-finalStockTransfer ::(Note n, HatVal v, ExBaseClass b) => Journal n v b -> Journal n v b-finalStockTransfer = finalStockTransferAggregated+import ExchangeAlgebra.Journal.Transfer.Rule
src/ExchangeAlgebra/Journal/Transfer/Rule.hs view
@@ -8,32 +8,52 @@ The additional-entry operations return entries to add to the input journal. 'carryBefore' instead returns the complete journal after replacing selected-notes. Transfer lifts Definition 9 over notes; closing reads the combined-ledger and returns an unannotated algebra through 'Either'. Read the-additional-entry section before the complete-result section.+notes. Transfer lifts Definition 9 over notes. Read the additional-entry+section before the complete-result section. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+ module ExchangeAlgebra.Journal.Transfer.Rule ( -- * Additional entries transferEntries- , closingEntries , carryEntries -- * Complete results , carryBefore+ -- * Side totals+ , SideTotals(..)+ , sideTotal+ , CarryError(..)+ , sideTotalsBy ) where import qualified Data.HashMap.Strict as Map import qualified Data.Map.Strict as OrderedMap-import ExchangeAlgebra.Algebra (Alg, HatVal, ExBaseClass, (.@), Redundant((.^)))-import ExchangeAlgebra.Algebra.Transfer.Rule (TransferRules, TransferApplyError)-import qualified ExchangeAlgebra.Algebra.Transfer.Rule as Rule-import ExchangeAlgebra.Algebra.Base (Hat(..), HatBaseClass(..))-import ExchangeAlgebra.Journal (Journal, Note, (.|), toMap, fromMap, toAlg)-import qualified ExchangeAlgebra.Journal as Journal-import ExchangeAlgebra.Journal.Exact (ExactSumError)-import qualified ExchangeAlgebra.Journal.Exact as Exact+import ExchangeAlgebra.Algebra.Core ( Alg( (:@) )+ , foldEntries+ , (.@)+ , Redundant((.^))+ )+import qualified ExchangeAlgebra.Algebra.Core as Algebra+import ExchangeAlgebra.Algebra.Transfer.Representation (TransferRules, TransferApplyError)+import qualified ExchangeAlgebra.Algebra.Transfer.Representation as Rule+import ExchangeAlgebra.Algebra.Base.Representation (Hat(..), HatBaseClass(..))+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import qualified ExchangeAlgebra.Algebra.Value.Class as Value+import ExchangeAlgebra.Journal.Core ( Journal+ , Note+ , (.|)+ , toMap+ , fromMap+ )+import qualified ExchangeAlgebra.Journal.Core as Journal+import ExchangeAlgebra.Algebra.Posting (PostSide(..)) -- * Additional entries +{-# DEPRECATED transferEntries "Use transferEntriesBy of ExchangeAlgebra.Accounting.Transfer, which reads the exact balance per target note." #-}+ -- | Generate additional entries under the same notes as their sources. -- Each note is processed in the traversal order of 'toMap'. The first failure -- aborts the whole operation; no partial Journal is returned. This order is@@ -50,67 +70,118 @@ entries <- Rule.transferEntries rules algebra pure (note, entries) --- | Read net balances across all notes and return unannotated closing entries.--- The input must include only postings through the closing date; 'toAlg'--- does not select a period. Earlier periods must already have their closing--- entries included. Attach the settlement note with @(.|)@ at the call site.--- Source sequences are folded per base by the Algebra operation, without--- a tolerance or implicit @bar@; small floating-point residues can remain.--- Finite, non-negative inputs normally yield 'Right'; 'Left' occurs only--- when a side's sum exceeds the value type's range. The Algebra operation--- checks totals and differences and reports the first failing base in--- ascending order, with Hat/Not normalized to Not. No partial result is returned.-closingEntries :: (Note n, HatVal v, ExBaseClass b)- => Journal n v b -> Either (TransferApplyError v b) (Alg v b)-closingEntries = Rule.closingEntries . toAlg- -- | Round selected complete-base nets once and attach the carry note.-carryNetEntries :: (Note n, HatBaseClass b)+-- Each complete base's Not and Hat values are accumulated exactly with the+-- 'HatVal' accumulator; the net is rounded once. Failures are reported as+-- 'CarryError' (see 'carryEntries').+carryNetEntries :: forall n v b. (Note n, HatVal v, HatBaseClass b) => n- -> Journal n Double b- -> Either ExactSumError (Journal n Double b)-carryNetEntries carryNote selected = do- balances <- Exact.balanceMapByExact Just selected- pure (OrderedMap.foldlWithKey' append mempty balances)+ -> Journal n v b+ -> Either CarryError (Journal n v b)+carryNetEntries carryNote journal = case validationError of+ Just failure -> Left failure+ Nothing -> (.| carryNote) . mconcat <$> traverse finish (OrderedMap.toAscList totals) where- append result coordinates (direction, value) = case direction of- EQ -> result- GT -> result <> ((value .@ merge Not coordinates) .| carryNote)- LT -> result <> ((value .@ merge Hat coordinates) .| carryNote)+ (validationError, totals) = Map.foldl' (foldEntries scanEntry)+ (Nothing, OrderedMap.empty) (toMap journal) + scanEntry :: (Maybe CarryError, OrderedMap.Map (BasePart b) (Accum v))+ -> v+ -> b+ -> (Maybe CarryError, OrderedMap.Map (BasePart b) (Accum v))+ scanEntry (!failure, !acc) value coordinates+ | Just current <- classify value coordinates = (Just (prefer failure current), acc)+ | otherwise = (failure, OrderedMap.insertWith mergeAccum (base coordinates) amount acc)+ where+ amount = case hat coordinates of+ Hat -> negateAccum (addAccum value emptyAccum)+ _ -> addAccum value emptyAccum++ classify :: v -> b -> Maybe CarryError+ classify value coordinates+ | hat coordinates == HatNot = Just WildcardSide+ | Just Value.NonFiniteValue <- valueError value = Just NonFiniteValue+ | Just Value.NegativeValue <- valueError value = Just NegativeValue+ | otherwise = Nothing++ prefer :: Maybe CarryError -> CarryError -> CarryError+ prefer (Just WildcardSide) _ = WildcardSide+ prefer _ WildcardSide = WildcardSide+ prefer (Just NonFiniteValue) _ = NonFiniteValue+ prefer _ NonFiniteValue = NonFiniteValue+ prefer (Just NegativeValue) _ = NegativeValue+ prefer _ NegativeValue = NegativeValue+ prefer _ ResultOutOfRange = ResultOutOfRange++ finish :: (BasePart b, Accum v) -> Either CarryError (Alg v b)+ finish (coordinates, amount) = case signAccum amount of+ Just EQ -> Right mempty+ Just LT -> roundedPosting Hat coordinates (negateAccum amount)+ _ -> roundedPosting Not coordinates amount++ roundedPosting :: Hat -> BasePart b -> Accum v -> Either CarryError (Alg v b)+ roundedPosting side coordinates amount+ | isErrorValue value = Left ResultOutOfRange+ | otherwise = Right (value .@ merge side coordinates)+ where+ value = roundAccum amount+ -- | Generate carry entries while retaining every original posting. -- Select source notes with the predicate, reverse the Hat or Not side of each -- selected scalar under its original note, and append one rounded exact net -- per complete base under the carry note. An exact zero net adds no entry.--- The net uses 'Exact.balanceMapByExact' on the selected original scalars;--- no @bar@ or intermediate rounding is applied. An aggregation failure--- returns 'Left' with 'ExactSumError'.+-- The net accumulates the selected original scalars exactly with the 'HatVal'+-- accumulator and rounds once per complete base with the value type's+-- 'ExchangeAlgebra.Algebra.Value.Class.roundAccum'; no @bar@ or intermediate+-- rounding is applied. --+-- Failures return 'Left' with 'CarryError' and no partial journal. Every+-- selected posting is checked before any net is rounded, and the+-- highest-priority failure wins regardless of traversal order:+-- 'WildcardSide' (a selected nonzero HatNot posting), 'NonFiniteValue',+-- 'NegativeValue', then 'ResultOutOfRange' (a rounded net is not finite).+-- A selected raw zero HatNot posting, which ordinary construction never+-- produces, is not reported: it contributes nothing to the net and is+-- omitted from the reversal, since 'revHat' is undefined for HatNot.+-- -- Inputs require concrete Hat or Not sides and finite, non-negative values.--- Selected nonzero HatNot entries lie outside this contract. Applying the--- returned entries cancels selected notes mathematically, but reading the--- resulting journal with Exact can fail: reversing Not and Hat entries of--- @2^1023@ at one base makes each side total @2^1024@.+-- Applying the returned entries cancels selected notes mathematically. The+-- net read out by the 'HatVal' accumulator succeeds on the resulting journal,+-- but rounding each gross side can overflow: reversing Not and Hat+-- entries of @2^1023@ at one base makes each side total @2^1024@. For the+-- same reason 'sideTotalsBy' reports 'ResultOutOfRange' on such a journal,+-- because it rounds each side total rather than the net. ----- Law: subject: 'carryEntries'. Preconditions: selected exact aggregation--- succeeds and the input meets the side and value contract above.+-- Law: subject: 'carryEntries'. Preconditions: the input meets the side and+-- value contract above and every rounded net is finite. -- Relation: for each base @b@, the Rational Not-minus-Hat balance obeys -- @exact_b(j <> e) - exact_b(j) = RN(s_b) - s_b@, where @e@ is the generated--- journal, @s_b@ is the selected exact net, and @RN@ rounds once to Double.+-- journal, @s_b@ is the selected exact net, and @RN@ is one rounding by+-- 'ExchangeAlgebra.Algebra.Value.Class.roundAccum' of the value type (the+-- identity for t'ExchangeAlgebra.Algebra.Value.MoneyDecimal' within its+-- range, nearest-even for Double). -- Observation: Rational balances of original scalar entries, including zero -- bases. Tolerance: exact Rational equality. Instances: 'Note' and--- 'HatBaseClass' with Double values.-carryEntries :: (Note n, HatBaseClass b)+-- 'HatBaseClass' with any 'HatVal' value type. For Double, on every input+-- for which the 0.5.x implementation returned 'Right', the results are+-- bit-identical; some inputs it rejected because a side sum left the Double+-- range now succeed, because only the net is rounded.+carryEntries :: forall n v b. (Note n, HatVal v, HatBaseClass b) => (n -> Bool) -> n- -> Journal n Double b- -> Either ExactSumError (Journal n Double b)+ -> Journal n v b+ -> Either CarryError (Journal n v b) carryEntries selectedNote carryNote journal = do carried <- carryNetEntries carryNote selected- pure (Journal.map (.^) selected <> carried)+ pure (Journal.map ((.^) . Algebra.filter reversible) selected <> carried) where selected = Journal.filterWithNote (\note _ -> selectedNote note) journal + reversible :: Alg v b -> Bool+ reversible (value :@ coordinates) =+ not (isZeroValue value && hat coordinates == HatNot)+ reversible _ = True+ -- * Complete results -- | Replace selected notes with one rounded exact net per complete base.@@ -133,9 +204,8 @@ -- selected notes would also remove @Q@. Selected nonzero HatNot entries lie -- outside the contract. ----- The selected net is computed by 'Exact.balanceMapByExact' and rounded once--- per complete base. An aggregation failure returns 'Left' with--- 'ExactSumError'. Structurally, 'carryEntries' resembles+-- The selected net is accumulated exactly and rounded once per complete base,+-- with the same 'CarryError' failures as 'carryEntries'. Structurally, 'carryEntries' resembles -- 'ExchangeAlgebra.Algebra.Transfer.Rule.collapseNetEntries': both generate -- reversals and moved nets. The latter uses @bar@ after rewriting and has a -- different numeric contract. Complexity: O(e log(k + 1)) for e selected@@ -144,15 +214,138 @@ -- above. Relation: the nonzero scalar-entry multiset equals that obtained -- from @forgetNotes p (j <> e)@ for @Right e = carryEntries p n j@. -- Observation: nonzero scalar-entry multiset. Tolerance: exact.--- Instances: 'Note' and 'HatBaseClass' with Double values.-carryBefore :: (Note n, HatBaseClass b)+-- Instances: 'Note' and 'HatBaseClass' with any 'HatVal' value type.+carryBefore :: (Note n, HatVal v, HatBaseClass b) => (n -> Bool) -> n- -> Journal n Double b- -> Either ExactSumError (Journal n Double b)+ -> Journal n v b+ -> Either CarryError (Journal n v b) carryBefore selectedNote carryNote journal = do carried <- carryNetEntries carryNote selected pure (retained <> carried) where selected = Journal.filterWithNote (\note _ -> selectedNote note) journal retained = Journal.filterWithNote (\note _ -> not (selectedNote note)) journal++-- * Side totals++-- | Pre-cancellation totals for one key. An absent side has positive zero.+data SideTotals = SideTotals+ { notTotal :: !Double -- ^ The Not sum, rounded once to nearest even.+ , hatTotal :: !Double -- ^ The Hat sum, rounded once to nearest even.+ } deriving (Eq, Show)++-- | Select a pre-cancellation total by posting side.+sideTotal :: PostSide -> SideTotals -> Double+sideTotal HatSide = hatTotal+sideTotal NotSide = notTotal++-- | Failures of 'carryEntries', 'carryBefore', and 'sideTotalsBy', in+-- descending priority. For these functions, this replaces the 0.5.x+-- ExactSumError (removed in 0.6.0.0).+data CarryError+ = WildcardSide -- ^ A posting uses HatNot, including a retained zero posting.+ | NonFiniteValue -- ^ A posting is NaN or infinite.+ | NegativeValue -- ^ A posting has a negative value.+ | ResultOutOfRange -- ^ A rounded side total or net is not finite.+ deriving (Eq, Show)++-- | Accumulate one side without retaining its source postings.+data SideAccum = SideAccum !(Accum Double)++-- | Keep Not and Hat independent until both sides have been rounded.+data SideAccums = SideAccums !SideAccum !SideAccum++emptySide :: SideAccum+emptySide = SideAccum emptyAccum++addSideValue :: Double -> SideAccum -> SideAccum+addSideValue value (SideAccum total) = SideAccum (addAccum value total)++roundSide :: SideAccum -> Either CarryError Double+roundSide (SideAccum total)+ | isInfinite value = Left ResultOutOfRange+ | value == 0 = Right 0+ | otherwise = Right value+ where+ value = roundAccum total++-- | Sum every posting by a key derived from its 'BasePart', without netting+-- Hat against Not or combining source postings. Each side is the exact sum of+-- its finite, non-negative input values rounded once to nearest-even Double.+-- Results, including zero, have a positive sign. The empty journal gives an+-- empty map; a key with one side absent has positive zero for that side.+-- Reordering postings, regrouping @(.+)@, or reassigning notes leaves each+-- output's bits unchanged. Every posting is validated independently of the+-- accumulator. The highest-priority failure wins regardless of traversal:+-- 'WildcardSide', 'NonFiniteValue', 'NegativeValue', then 'ResultOutOfRange'.+-- A finite rounded total succeeds even if its exact sum exceeds the largest+-- finite Double; for example, maximum finite plus the least subnormal rounds+-- back to maximum finite. No partial map is returned on failure.+-- A raw zero @(:@)@ posting is inspected, including HatNot; ordinary algebra+-- construction discards zero postings before they reach this function.+-- Complexity: one traversal of all entries with O(log(k + 1)) map updates+-- per entry for k distinct keys, followed by one rounding per side and key.+--+-- Law: subject: 'sideTotalsBy'. Preconditions: all postings have concrete+-- sides and finite, non-negative values, and all rounded side sums are finite.+-- Relation: each output side equals nearest-even rounding of the Rational sum+-- of that key's postings on that side, before Hat/Not cancellation.+-- Observation: Double bits for each key and side. Tolerance: bit-identical.+-- Instances: 'Note' and 'HatBaseClass' with Double values and ordered keys.+sideTotalsBy :: forall n b k. (Note n, HatBaseClass b, Ord k)+ => (BasePart b -> k)+ -> Journal n Double b+ -> Either CarryError (OrderedMap.Map k SideTotals)+sideTotalsBy keyOf journal = case validationError of+ Just failure -> Left failure+ Nothing -> traverse finish totals+ where+ (validationError, totals) = Map.foldl' (scanAlgebra scanEntry)+ (Nothing, OrderedMap.empty)+ (toMap journal)++ scanAlgebra :: (state -> Double -> b -> state) -> state -> Alg Double b -> state+ scanAlgebra step !state algebra = case algebra of+ value :@ coordinates -> step state value coordinates+ _ -> foldEntries step state algebra++ scanEntry :: (Maybe CarryError, OrderedMap.Map k SideAccums)+ -> Double+ -> b+ -> (Maybe CarryError, OrderedMap.Map k SideAccums)+ scanEntry (!failure, !acc) value coordinates =+ case classify value coordinates of+ Just current -> (Just (prefer failure current), acc)+ Nothing ->+ let !key = keyOf (base coordinates)+ !updated = OrderedMap.alter (Just . addToSide (hat coordinates) value+ . maybe (SideAccums emptySide emptySide) id) key acc+ in (failure, updated)++ classify :: Double -> b -> Maybe CarryError+ classify value coordinates+ | hat coordinates == HatNot = Just WildcardSide+ | isNaN value || isInfinite value = Just NonFiniteValue+ | value < 0 = Just NegativeValue+ | otherwise = Nothing++ prefer :: Maybe CarryError -> CarryError -> CarryError+ prefer (Just WildcardSide) _ = WildcardSide+ prefer _ WildcardSide = WildcardSide+ prefer (Just NonFiniteValue) _ = NonFiniteValue+ prefer _ NonFiniteValue = NonFiniteValue+ prefer (Just NegativeValue) _ = NegativeValue+ prefer _ NegativeValue = NegativeValue+ prefer _ ResultOutOfRange = ResultOutOfRange++ addToSide :: Hat -> Double -> SideAccums -> SideAccums+ addToSide Hat value (SideAccums notSide hatSide) =+ SideAccums notSide (addSideValue value hatSide)+ addToSide Not value (SideAccums notSide hatSide) =+ SideAccums (addSideValue value notSide) hatSide+ addToSide HatNot _ sides = sides++ finish :: SideAccums -> Either CarryError SideTotals+ finish (SideAccums notSide hatSide) =+ SideTotals <$> roundSide notSide <*> roundSide hatSide
src/ExchangeAlgebra/Optimize.hs view
@@ -2,100 +2,15 @@ {- | Module : ExchangeAlgebra.Optimize- Copyright : (c) Kaya Akagi. 2026- License : OtherLicense - A pluggable optimization solver interface.-- This module defines the strategy-agnostic 'Solver' class. Concrete- strategies live in sibling modules:-- * "ExchangeAlgebra.Optimize.Annealing" — simulated annealing over an- arbitrary candidate type.- * "ExchangeAlgebra.Optimize.GA" — a real-coded genetic algorithm over- numeric vector chromosomes.-- New strategies (differential evolution, particle swarm, CMA-ES, ...)- are added by defining a new strategy type with a 'Solver' instance;- the interface itself does not change.-- == Design notes-- * The objective is monadic (@'Candidate' strategy -> m Double@) so that- it can run stateful simulations, e.g. the @ST s@ state spaces of- "ExchangeAlgebra.Simulate". Solvers only require 'Monad', nothing- stronger.- * Randomness is threaded internally by each solver from a seed in its- 'Config' (a pure generator, sequentially updated), so no random-monad- constraint leaks into the objective. Runs are reproducible given the- same seed /provided the objective itself is deterministic/ (same- state transitions, no external randomness, clocks or concurrency).- * The returned 'Double' is the objective value observed when the- returned candidate was evaluated, in the user's orientation.- Solvers never re-evaluate a candidate they have already scored- (re-evaluation would be a visible side effect for stateful- objectives such as @ST s@ simulations).- * Objective values must be finite: solvers reject @NaN@ and- infinities with a call to 'error' as soon as they are observed,- because non-finite energies silently break comparisons, sorting- and best-candidate tracking. Configurations are also validated- up front (fail-fast; invalid settings are never clamped).- * This subsystem is a generic numeric layer: it does not touch the- redundant-algebra core ("ExchangeAlgebra.Algebra",- "ExchangeAlgebra.Journal") and imposes no Hat\/Not or non-negativity- semantics on candidates. Objective values are plain 'Double's in the- user's orientation (see 'Direction').- * Parallel evaluation of candidates is deliberately left to the caller- (e.g. run several 'optimize' calls with different seeds and pick the- best); solvers themselves evaluate sequentially.+ Compatibility entry point for optimization solvers. Import+ "ExchangeAlgebra.Simulation.Optimize" for new code. -} module ExchangeAlgebra.Optimize- ( -- * Solver interface- Solver (..)- -- * Optimization direction+ {-# DEPRECATED "Use ExchangeAlgebra.Simulation.Optimize instead." #-}+ ( Solver (..) , Direction (..) , orient ) where --- | Whether the objective is to be maximized or minimized.------ Solvers convert objective values to an internal /minimization energy/--- ('orient') and report the final score back in the user's orientation.-data Direction = Maximize | Minimize- deriving (Show, Eq)---- | Convert a score in the user's orientation to a minimization energy.------ >>> orient Minimize 3.0--- 3.0------ >>> orient Maximize 3.0--- -3.0-orient :: Direction -> Double -> Double-orient Minimize = id-orient Maximize = negate-{-# INLINE orient #-}---- | A pluggable optimization strategy.------ @strategy@ is a dispatch value carrying no data of its own — a--- /proxy/ such as @GA@ ("ExchangeAlgebra.Optimize.GA") or @Annealing@--- ("ExchangeAlgebra.Optimize.Annealing"). The associated types fix what--- a candidate solution looks like and what configuration the strategy--- needs.-class Solver strategy where- -- | Candidate solution type searched by this strategy.- type Candidate strategy- -- | Strategy-specific configuration (schedules, rates, seed,- -- 'Direction', ...).- type Config strategy-- -- | Run the optimizer: given a configuration, a monadic objective and- -- an initial candidate, return the best candidate found together- -- with its objective value (in the user's orientation).- optimize :: Monad m- => strategy- -> Config strategy- -> (Candidate strategy -> m Double) -- ^ objective- -> Candidate strategy -- ^ initial candidate- -> m (Candidate strategy, Double)+import ExchangeAlgebra.Simulation.Optimize
src/ExchangeAlgebra/Optimize/Annealing.hs view
@@ -1,154 +1,15 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE BangPatterns #-}- {- | Module : ExchangeAlgebra.Optimize.Annealing- Copyright : (c) Kaya Akagi. 2026- License : OtherLicense - Simulated annealing over an arbitrary candidate type.-- The candidate type is free (@a@ in @t'Annealing' a@): investment- schedules, parameter records, vectors, ... — anything the- 'acNeighbor' move can perturb. The objective may be stateful- (e.g. run an @ST s@ simulation); see "ExchangeAlgebra.Optimize"- for the interface contract.-- == Example-- Minimize @(x - 3)^2@ over 'Double':-- > import ExchangeAlgebra.Optimize- > import ExchangeAlgebra.Optimize.Annealing- > import System.Random (uniformR)- > import Data.Functor.Identity (runIdentity)- >- > cfg :: AnnealingConfig Double- > cfg = AnnealingConfig- > { acDirection = Minimize- > , acSteps = 2000- > , acSchedule = geometricCooling 1.0 0.995- > , acNeighbor = \g x -> let (d, g') = uniformR (-0.5, 0.5) g- > in (x + d, g')- > , acAccept = metropolis- > , acSeed = 42- > }- >- > best :: (Double, Double)- > best = runIdentity (optimize Annealing cfg (\x -> pure ((x - 3)^2)) 0)+ Compatibility entry point. Import+ "ExchangeAlgebra.Simulation.Optimize.Annealing" for new code. -} module ExchangeAlgebra.Optimize.Annealing- ( -- * Strategy- Annealing (..)+ {-# DEPRECATED "Use ExchangeAlgebra.Simulation.Optimize.Annealing instead." #-}+ ( Annealing (..) , AnnealingConfig (..)- -- * Standard building blocks , geometricCooling , metropolis ) where -import ExchangeAlgebra.Optimize-import System.Random (StdGen, mkStdGen, uniformR)---- | Simulated-annealing strategy proxy. The phantom @a@ is the candidate--- type; it is inferred from the t'AnnealingConfig' at the call site, so--- plain @'optimize' Annealing cfg obj x0@ needs no annotation.-data Annealing a = Annealing---- | Configuration of one annealing run.------ Energies passed to 'acAccept' are /minimization-oriented/ ('orient'--- applied), regardless of 'acDirection'.-data AnnealingConfig a = AnnealingConfig- { acDirection :: Direction- -- ^ Whether the objective is maximized or minimized.- , acSteps :: Int- -- ^ Total number of proposal steps (@>= 0@; @0@ evaluates the- -- initial candidate once and returns it).- , acSchedule :: Int -> Double- -- ^ Cooling schedule: 1-based step number to temperature.- -- Each temperature must be finite and @>= 0@ (@0@ acts as greedy- -- acceptance); anything else is rejected with 'error'.- , acNeighbor :: StdGen -> a -> (a, StdGen)- -- ^ Proposal move: perturb the current candidate, threading the- -- solver's random generator.- , acAccept :: Double -> Double -> Double -> Double- -- ^ Acceptance rule: @temperature -> current energy -> proposed- -- energy -> probability@. The probability must be finite and in- -- @[0, 1]@ (validated at every step). See 'metropolis'.- , acSeed :: Int- -- ^ Seed of the solver's internal random generator.- }---- | Geometric cooling: @geometricCooling t0 alpha step = t0 * alpha^(step-1)@.------ Typical use: @'geometricCooling' 1.0 0.995@.-geometricCooling :: Double -- ^ initial temperature @t0@- -> Double -- ^ decay factor @alpha@ (usually just below 1)- -> Int -- ^ 1-based step- -> Double-geometricCooling t0 alpha step = t0 * alpha ^^ (step - 1)---- | The Metropolis acceptance rule: always accept an improvement,--- otherwise accept with probability @exp ((e - e') / t)@.--- At @t = 0@ this degenerates to greedy acceptance.-metropolis :: Double -- ^ temperature- -> Double -- ^ current energy- -> Double -- ^ proposed energy- -> Double-metropolis t e e'- | e' <= e = 1- | t <= 0 = 0- | otherwise = exp ((e - e') / t)--instance Solver (Annealing a) where- type Candidate (Annealing a) = a- type Config (Annealing a) = AnnealingConfig a-- optimize _ cfg obj x0- | acSteps cfg < 0- = error ("ExchangeAlgebra.Optimize.Annealing: acSteps must be >= 0, got "- ++ show (acSteps cfg))- | otherwise- = do e0 <- energyOf dir "initial candidate" (obj x0)- go 1 (mkStdGen (acSeed cfg)) x0 e0 x0 e0- where- dir = acDirection cfg- go !step !g !cur !eCur !best !eBest- | step > acSteps cfg = return (best, orient dir eBest)- -- orient is self-inverse: energy back to the user's score.- | otherwise = do- let t = checkTemperature step (acSchedule cfg step)- (cand, g1) = acNeighbor cfg g cur- eCand <- energyOf dir ("step " ++ show step) (obj cand)- let p = checkProbability step (acAccept cfg t eCur eCand)- (u, g2) = uniformR (0 :: Double, 1) g1- accepted = p >= 1 || u < p- (cur', eCur') | accepted = (cand, eCand)- | otherwise = (cur, eCur)- (best', eBest') | eCand < eBest = (cand, eCand)- | otherwise = (best, eBest)- go (step + 1) g2 cur' eCur' best' eBest'---- | Evaluate the objective and convert to minimization energy, rejecting--- non-finite scores (see the contract in "ExchangeAlgebra.Optimize").-energyOf :: Monad m => Direction -> String -> m Double -> m Double-energyOf dir what mScore = do- s <- mScore- if isNaN s || isInfinite s- then error ("ExchangeAlgebra.Optimize.Annealing: objective returned "- ++ "a non-finite score (" ++ show s ++ ") at " ++ what)- else return (orient dir s)--checkTemperature :: Int -> Double -> Double-checkTemperature step t- | isNaN t || isInfinite t || t < 0- = error ("ExchangeAlgebra.Optimize.Annealing: acSchedule returned an "- ++ "invalid temperature (" ++ show t ++ ") at step " ++ show step)- | otherwise = t--checkProbability :: Int -> Double -> Double-checkProbability step p- | isNaN p || isInfinite p || p < 0 || p > 1- = error ("ExchangeAlgebra.Optimize.Annealing: acAccept returned an "- ++ "invalid probability (" ++ show p ++ ") at step " ++ show step)- | otherwise = p+import ExchangeAlgebra.Simulation.Optimize.Annealing
src/ExchangeAlgebra/Optimize/GA.hs view
@@ -1,293 +1,14 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE BangPatterns #-}- {- | Module : ExchangeAlgebra.Optimize.GA- Copyright : (c) Kaya Akagi. 2026- License : OtherLicense - A real-coded genetic algorithm over numeric vector chromosomes- (@'U.Vector' 'Double'@).-- Generational scheme with elitism: tournament selection, uniform- crossover and per-gene Gaussian mutation. The initial population is- the (clamped) initial candidate plus Gaussian perturbations of it- ('gaInitSpread'), so 'optimize' needs no separate population input.- Elites carry their already-observed scores into the next generation- and are never re-evaluated.-- Blend/simulated-binary crossover (BLX-alpha, SBX) are deliberately- /not/ options of t'GAConfig'; if needed they should become separate- strategies so each strategy's rates keep one clear meaning.-- == Example-- Minimize the 3-dimensional sphere function @sum (v - 1)^2@:-- > import ExchangeAlgebra.Optimize- > import ExchangeAlgebra.Optimize.GA- > import qualified Data.Vector.Unboxed as U- > import Data.Functor.Identity (runIdentity)- >- > sphere :: U.Vector Double -> Double- > sphere v = U.sum (U.map (\x -> (x - 1) ^ 2) v)- >- > best :: (U.Vector Double, Double)- > best = runIdentity- > (optimize GA defaultGAConfig (pure . sphere) (U.replicate 3 0))+ Compatibility entry point. Import+ "ExchangeAlgebra.Simulation.Optimize.GA" for new code. -} module ExchangeAlgebra.Optimize.GA- ( -- * Strategy- GA (..)+ {-# DEPRECATED "Use ExchangeAlgebra.Simulation.Optimize.GA instead." #-}+ ( GA (..) , GAConfig (..) , defaultGAConfig ) where -import ExchangeAlgebra.Optimize-import qualified Data.List as L-import Data.Ord (comparing)-import qualified Data.Vector as V-import qualified Data.Vector.Unboxed as U-import System.Random (StdGen, mkStdGen, uniformR)---- | Genetic-algorithm strategy proxy.-data GA = GA---- | Configuration of one GA run. See 'defaultGAConfig' for a starting--- point; all fields are validated up front (fail-fast 'error', never--- clamped).-data GAConfig = GAConfig- { gaDirection :: Direction- -- ^ Whether the objective is maximized or minimized.- , gaPopulationSize :: Int- -- ^ Number of individuals per generation (@>= 1@).- , gaGenerations :: Int- -- ^ Number of generations (@>= 0@; @0@ evaluates only the initial- -- population).- , gaCrossoverRate :: Double- -- ^ Probability that a selected pair undergoes uniform crossover- -- (in @[0, 1]@); otherwise the first parent is copied.- , gaMutationRate :: Double- -- ^ Per-gene mutation probability (in @[0, 1]@).- , gaMutationScale :: Double- -- ^ Standard deviation of the Gaussian gene perturbation (@>= 0@).- , gaTournamentSize :: Int- -- ^ Tournament size for parent selection (@>= 1@; @1@ degenerates- -- to uniform random selection, which barely selects at all).- , gaEliteCount :: Int- -- ^ Number of best individuals carried over unchanged- -- (@0 <= gaEliteCount <= gaPopulationSize@).- , gaInitSpread :: Double- -- ^ Standard deviation of the Gaussian spread used to build the- -- initial population around the initial candidate (@>= 0@).- , gaBounds :: Maybe (U.Vector (Double, Double))- -- ^ Optional per-gene @(low, high)@ box constraints. Must have the- -- same length as the chromosome; every generated gene (initial- -- spread and mutation) is clamped into its interval.- , gaSeed :: Int- -- ^ Seed of the solver's internal random generator.- }---- | A reasonable starting configuration: minimization, population 50,--- 100 generations, crossover rate 0.9, per-gene mutation rate 0.1 with--- scale 0.1, tournament size 3, 2 elites, initial spread 1.0, no--- bounds, seed 42. Override fields as needed.-defaultGAConfig :: GAConfig-defaultGAConfig = GAConfig- { gaDirection = Minimize- , gaPopulationSize = 50- , gaGenerations = 100- , gaCrossoverRate = 0.9- , gaMutationRate = 0.1- , gaMutationScale = 0.1- , gaTournamentSize = 3- , gaEliteCount = 2- , gaInitSpread = 1.0- , gaBounds = Nothing- , gaSeed = 42- }--instance Solver GA where- type Candidate GA = U.Vector Double- type Config GA = GAConfig-- optimize _ cfg obj x0 = do- validateConfig cfg x0- let x0' = clampTo (gaBounds cfg) x0- (rest, g1) = initialPopulation cfg x0' (mkStdGen (gaSeed cfg))- pop0 <- mapM evalOne (x0' : rest)- let best0 = L.minimumBy (comparing snd) pop0- (bc, be) <- go 1 g1 pop0 best0- return (bc, orient dir be)- where- dir = gaDirection cfg-- evalOne c = do- s <- obj c- if isNaN s || isInfinite s- then error ("ExchangeAlgebra.Optimize.GA: objective returned "- ++ "a non-finite score (" ++ show s ++ ")")- else return (c, orient dir s)-- go !gen !g !pop !best- | gen > gaGenerations cfg = return best- | otherwise = do- let sorted = L.sortBy (comparing snd) pop- elites = take (gaEliteCount cfg) sorted- nChildren = gaPopulationSize cfg - gaEliteCount cfg- pool = V.fromList sorted- (childs, g') = makeChildren cfg pool nChildren g- children <- mapM evalOne childs- let pop' = elites ++ children- best' = L.minimumBy (comparing snd) (best : pop')- go (gen + 1) g' pop' best'---- --------------------------------------------------------------------- Pure genetics (random generator threaded explicitly)--- ---------------------------------------------------------------------- | Initial population (without the seed candidate itself): Gaussian--- perturbations of the seed candidate, clamped to the bounds.-initialPopulation :: GAConfig -> U.Vector Double -> StdGen- -> ([U.Vector Double], StdGen)-initialPopulation cfg x0 = goN (gaPopulationSize cfg - 1)- where- goN 0 g = ([], g)- goN k g = let (v, g1) = perturbAll x0 g- (vs, g2) = goN (k - 1) g1- in (v : vs, g2)- perturbAll v g =- let (genes, g') = threadGenes step (U.toList v) g- in (clampTo (gaBounds cfg) (U.fromList genes), g')- where- step x gg = let (z, gg') = gauss gg- in (x + gaInitSpread cfg * z, gg')---- | Produce @k@ children by tournament selection, uniform crossover and--- Gaussian mutation.-makeChildren :: GAConfig -> V.Vector (U.Vector Double, Double) -> Int -> StdGen- -> ([U.Vector Double], StdGen)-makeChildren cfg pool = goN- where- goN 0 g = ([], g)- goN k g = let (c, g1) = makeOne g- (cs, g2) = goN (k - 1) g1- in (c : cs, g2)-- makeOne g0 =- let (p1, g1) = tournament g0- (p2, g2) = tournament g1- (u, g3) = uniformR (0 :: Double, 1) g2- (raw, g4) = if u < gaCrossoverRate cfg- then crossover p1 p2 g3- else (p1, g3)- (mut, g5) = mutate raw g4- in (clampTo (gaBounds cfg) mut, g5)-- -- Tournament selection: best (lowest energy) of k uniform picks.- tournament g0 = go (gaTournamentSize cfg) g0 Nothing- where- go 0 g acc = case acc of- Just (c, _) -> (c, g)- Nothing -> error "ExchangeAlgebra.Optimize.GA: empty tournament"- go k g acc =- let (i, g') = uniformR (0, V.length pool - 1) g- cand = pool V.! i- acc' = case acc of- Just (_, e) | e <= snd cand -> acc- _ -> Just cand- in go (k - 1 :: Int) g' acc'-- -- Uniform crossover: each gene comes from either parent with p = 1/2.- crossover p1 p2 g0 =- let (genes, g') = crossGenes (U.toList p1) (U.toList p2) g0- in (U.fromList genes, g')- crossGenes [] _ g = ([], g)- crossGenes _ [] g = ([], g)- crossGenes (a:as) (b:bs) g =- let (u, g1) = uniformR (0 :: Double, 1) g- (rest, g2) = crossGenes as bs g1- in ((if u < 0.5 then a else b) : rest, g2)-- -- Per-gene Gaussian mutation.- mutate v g0 =- let (genes, g') = threadGenes step (U.toList v) g0- in (U.fromList genes, g')- where- step x g =- let (u, g1) = uniformR (0 :: Double, 1) g- in if u < gaMutationRate cfg- then let (z, g2) = gauss g1- in (x + gaMutationScale cfg * z, g2)- else (x, g1)---- | Thread the generator through a per-gene transformation.-threadGenes :: (Double -> StdGen -> (Double, StdGen))- -> [Double] -> StdGen -> ([Double], StdGen)-threadGenes _ [] g = ([], g)-threadGenes f (x:xs) g =- let (y, g1) = f x g- (ys, g2) = threadGenes f xs g1- in (y : ys, g2)---- | One standard-normal draw via Box–Muller. The first uniform is lifted--- away from zero so @log u1@ stays finite. (The unguarded @normal@ in--- "ExchangeAlgebra.Simulate" has a @log 0@ failure mode and must not be--- copied here.)-gauss :: StdGen -> (Double, StdGen)-gauss g0 =- let (u1raw, g1) = uniformR (0 :: Double, 1) g0- u1 = max 2.2250738585072014e-308 u1raw -- smallest normal- (u2, g2) = uniformR (0 :: Double, 1) g1- in (sqrt (-2 * log u1) * cos (2 * pi * u2), g2)---- | Clamp each gene into its bound interval (identity without bounds).-clampTo :: Maybe (U.Vector (Double, Double)) -> U.Vector Double -> U.Vector Double-clampTo Nothing v = v-clampTo (Just bs) v = U.zipWith (\(lo, hi) x -> max lo (min hi x)) bs v---- --------------------------------------------------------------------- Fail-fast configuration validation (see ExchangeAlgebra.Optimize)--- --------------------------------------------------------------------validateConfig :: Monad m => GAConfig -> U.Vector Double -> m ()-validateConfig cfg x0- | gaPopulationSize cfg < 1- = bad ("gaPopulationSize must be >= 1, got " ++ show (gaPopulationSize cfg))- | gaGenerations cfg < 0- = bad ("gaGenerations must be >= 0, got " ++ show (gaGenerations cfg))- | gaEliteCount cfg < 0 || gaEliteCount cfg > gaPopulationSize cfg- = bad ("gaEliteCount must be in [0, gaPopulationSize], got "- ++ show (gaEliteCount cfg))- | gaTournamentSize cfg < 1- = bad ("gaTournamentSize must be >= 1, got " ++ show (gaTournamentSize cfg))- | badRate (gaCrossoverRate cfg)- = bad ("gaCrossoverRate must be a finite value in [0,1], got "- ++ show (gaCrossoverRate cfg))- | badRate (gaMutationRate cfg)- = bad ("gaMutationRate must be a finite value in [0,1], got "- ++ show (gaMutationRate cfg))- | badScale (gaMutationScale cfg)- = bad ("gaMutationScale must be finite and >= 0, got "- ++ show (gaMutationScale cfg))- | badScale (gaInitSpread cfg)- = bad ("gaInitSpread must be finite and >= 0, got "- ++ show (gaInitSpread cfg))- | U.null x0- = bad "the initial candidate (chromosome) must be non-empty"- | Just bs <- gaBounds cfg, U.length bs /= U.length x0- = bad ("gaBounds length (" ++ show (U.length bs)- ++ ") does not match the chromosome length (" ++ show (U.length x0) ++ ")")- | Just bs <- gaBounds cfg- , Just (i, (lo, hi)) <- badBound bs- = bad ("gaBounds at index " ++ show i ++ " is invalid: "- ++ show (lo, hi) ++ " (need finite lo <= hi)")- | otherwise = return ()- where- bad msg = error ("ExchangeAlgebra.Optimize.GA: " ++ msg)- badRate r = isNaN r || isInfinite r || r < 0 || r > 1- badScale s = isNaN s || isInfinite s || s < 0- badBound bs = U.ifoldr- (\i b acc -> if invalid b then Just (i, b) else acc) Nothing bs- where invalid (lo, hi) =- isNaN lo || isInfinite lo || isNaN hi || isInfinite hi || lo > hi+import ExchangeAlgebra.Simulation.Optimize.GA
src/ExchangeAlgebra/Posting.hs view
@@ -1,199 +1,8 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- | Build checked postings in the Accounting layer.--- The module uses the foundation algebra to construct @Posting@ values without--- implicit cancellation. Models and simulators consume these postings. Read--- checked values, sides, and then entries.------ Posting construction follows Definitions 3-5.-module ExchangeAlgebra.Posting- ( -- * Posting values- Posted- , PostedError(..)- , postedUpperBound- , posted- , unPosted- -- * Posting sides- , PostSide(..)- , sideHat- -- * Postings- , Posting- , entry- , postingAlg+-- | Compatibility entry for checked postings. Import+-- "ExchangeAlgebra.Algebra.Posting" for new code.+module ExchangeAlgebra.Posting {-# DEPRECATED "Use ExchangeAlgebra.Algebra.Posting instead." #-}+ ( Posted, PostedError(..), posted, unPosted+ , PostSide(..), sideHat, Posting, entry, postingAlg ) where -import Control.DeepSeq (NFData(..))-import Data.Binary (Binary(..))-import Data.Hashable (Hashable(..))-import GHC.Generics (Generic)--import ExchangeAlgebra.Algebra (Alg(Zero), HatVal(isErrorValue), (.+), (.@))-import ExchangeAlgebra.Algebra.Base- ( Hat(..)- , HatBaseClass(BasePart, merge, base)- )---- * Posting values---- | A checked posting value with no 'Num', 'Fractional', or 'Real' instance.------ Invariant: the stored 'Double' is finite and lies in @[0, 2^900]@;--- zero is stored as positive zero. Construct values through 'posted'.-newtype Posted = Posted Double- deriving stock (Eq, Ord, Show)--instance NFData Posted where- rnf (Posted value) = rnf value--instance Hashable Posted where- hashWithSalt salt (Posted value) = hashWithSalt salt value---- | Decoding checks the same invariant as 'posted' and fails on invalid input.-instance Binary Posted where- put = put . unPosted- get = do- value <- get- case posted value of- Left failure -> fail (show failure)- Right validated -> pure validated---- | The first failed check, in the order used by 'posted'.-data PostedError- = NonFinite -- ^ NaN or either infinity.- | Negative -- ^ A finite value below zero.- | AboveBound -- ^ A finite value above 'postedUpperBound'.- deriving stock (Eq, Show, Generic)--instance NFData PostedError---- | Inclusive bound @2^900@. A sum of nearly @2^123@ such values remains below--- the largest finite Double (less than @2^1024@), leaving aggregation headroom.--- Complexity: O(1).-postedUpperBound :: Double-postedUpperBound = 2 ^ (900 :: Int)---- | Validate a value, normalizing negative zero to positive zero.--- Checks non-finiteness, negativity, and the upper bound in that order,--- returning the corresponding 'PostedError' on failure.------ For every finite @x@ in @[0, postedUpperBound]@, with @normalizeZero x@--- equal to positive zero when @x == 0@ and to @x@ otherwise:------ > fmap unPosted (posted x) == Right (normalizeZero x)------ The law uses exact 'Double' equality, with the sign of zero also normalized.--- Finite, nonnegative values use the same 'isErrorValue' check as '.@'.--- 'postedUpperBound' is checked only at the posting entry point.--- Complexity: O(1).-posted :: Double -> Either PostedError Posted-posted value- | isErrorValue value = Left (invalidValueError value)- | value > postedUpperBound = Left AboveBound- | value == 0 = Right (Posted 0)- | otherwise = Right (Posted value)- where- invalidValueError invalid- | isNaN invalid || isInfinite invalid = NonFinite- | otherwise = Negative---- | Read a checked value. For every @p@, @posted (unPosted p) == Right p@.--- Complexity: O(1).-unPosted :: Posted -> Double-unPosted (Posted value) = value---- * Posting sides---- | The two posting sides; query wildcard 'HatNot' is excluded.-data PostSide- = HatSide -- ^ The @Hat@ side.- | NotSide -- ^ The 'Not' side.- deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)--instance Binary PostSide--instance Hashable PostSide--instance NFData PostSide---- | Embed a posting side into a query-capable hat.--- @sideHat HatSide == Hat@ and @sideHat NotSide == Not@; 'HatNot' is never returned.--- Complexity: O(1).-sideHat :: PostSide -> Hat-sideHat HatSide = Hat-sideHat NotSide = Not---- * Postings---- | Postings built through 'entry' and 'Monoid', without implicit cancellation--- or compression. Equality and display delegate to the underlying 'Alg'.--- Equality is structural and depends on construction order. To compare--- multisets of postings instead, compare the results of @toASCList . postingAlg@.-newtype Posting b = Posting (Alg Double b)--instance HatBaseClass b => Eq (Posting b) where- Posting left == Posting right = left == right--instance HatBaseClass b => Show (Posting b) where- showsPrec precedence (Posting algebra) = showsPrec precedence algebra--instance NFData (Posting b) where- rnf (Posting algebra) = rnf algebra---- | Preserve algebra addition:------ > postingAlg (a <> b) == (postingAlg a .+ postingAlg b)------ This law uses 'Alg' equality without a tolerance for every 'HatBaseClass'--- instance, so it also preserves the multiset of postings. Associativity--- holds as equality of posting multisets, using--- @sameMultiset x y = toASCList x == toASCList y@:------ > sameMultiset (postingAlg ((a <> b) <> c)) (postingAlg (a <> (b <> c)))------ Complexity: the same as '(.+)' on the underlying algebras.-instance HatBaseClass b => Semigroup (Posting b) where- Posting left <> Posting right = Posting (left .+ right)---- | The empty posting obeys @postingAlg mempty == Zero@ using exact 'Alg' equality.--- The identity laws hold as equality of posting multisets for every--- 'HatBaseClass' instance, without a numeric tolerance:------ > sameMultiset (postingAlg (mempty <> a)) (postingAlg a)--- > sameMultiset (postingAlg (a <> mempty)) (postingAlg a)------ Here @sameMultiset x y = toASCList x == toASCList y@.--- Complexity: O(1) for 'mempty'; combination uses the 'Semigroup' instance.-instance HatBaseClass b => Monoid (Posting b) where- mempty = Posting Zero---- | Read the underlying algebra. For every side, checked value, and base part:------ > postingAlg (entry side value part) == unPosted value .@ merge (sideHat side) part------ This is a one-way conversion with exact 'Alg' equality. For a list @xs@--- of @(side, value, part)@ triples, let @mk (s, v, p) = entry s v p@ and--- @sameMultiset x y = toASCList x == toASCList y@. Conversion and projection--- preserve the multiset of postings for every query list @qs@, including--- 'HatNot' and coordinate wildcards:------ > sameMultiset (postingAlg (foldMap mk xs))--- > (foldr (.+) Zero (map (postingAlg . mk) xs))--- > sameMultiset (proj qs (postingAlg (foldMap mk xs)))--- > (foldr (.+) Zero [proj qs (postingAlg (mk x)) | x <- xs])------ These laws use exact multiset equality without a numeric tolerance for--- every 'HatBaseClass' instance. Complexity of 'postingAlg': O(1).-postingAlg :: Posting b -> Alg Double b-postingAlg (Posting algebra) = algebra---- | Build one posting from a side, checked value, and base coordinates.--- A zero value produces 'Zero' through '(.@)'. The side is embedded by--- 'sideHat' and the coordinates are combined with 'merge'.------ > postingAlg (entry side value part) == unPosted value .@ merge (sideHat side) part------ The law holds with exact 'Alg' equality for every 'HatBaseClass' instance.--- Complexity: O(1).-entry :: HatBaseClass b => PostSide -> Posted -> BasePart b -> Posting b-entry side value part = Posting (unPosted value .@ merge (sideHat side) part)+import ExchangeAlgebra.Algebra.Posting
src/ExchangeAlgebra/Render/Bookkeeping.hs view
@@ -5,34 +5,16 @@ Released under the OWL license - Bookkeeping documents rendered from an algebra or a journal: balance- sheet, profit and loss, journal, account ledgers, compound trial- balance, the eight-column worksheet and the post-closing trial balance.- Each document comes as a pure @…Rows@ layout and a @write…@ wrapper that- serializes the rows through 'ExchangeAlgebra.Render.Csv.writeCSV'.-- Every name is re-exported unchanged from "ExchangeAlgebra.Write"; nothing- is defined here. The row layouts are frozen by the- @test/fixtures/write-rows-0510@ goldens, so the shim is behavior-- identical to the original module by construction. Note that 'bsRows' and- 'plRows' are the legacy presentation (no per-title aggregation, credit-- balance assets dropped from the sheet); the grouped presentation lives in- "ExchangeAlgebra.Reporting.Presentation".-- >>> import ExchangeAlgebra.Algebra- >>> let x = 100 .@ Not :< Cash .+ 100 .@ Not :< Sales :: Alg Double (HatBase AccountTitles)- >>> balanceOf Cash x- (Debit,100.0)+ Journals, account ledgers, trial balances, and closing worksheets.+ These layouts and their file writers are re-exported from+ "ExchangeAlgebra.IO.Output.Statements". For financial statements, use+ "ExchangeAlgebra.Accounting.Statements.Presentation" followed by+ 'ExchangeAlgebra.IO.Output.Statements.financialStatementRows'. -} -module ExchangeAlgebra.Render.Bookkeeping- ( -- * Balance sheet and profit and loss- writeBS- , bsRows- , writePL- , plRows- -- * Journal and ledgers- , writeJournal+module ExchangeAlgebra.Render.Bookkeeping {-# DEPRECATED "Import ExchangeAlgebra.IO.Output.Statements instead." #-}+ ( -- * Journal and ledgers+ writeJournal , journalRows , writeAccountOf , accountLedgerRows@@ -45,30 +27,6 @@ , worksheetRows , writePostClosingTrialBalance , postClosingTrialBalanceRows- -- * Helpers- , balanceOf- , tshow- , toSameLength ) where -import ExchangeAlgebra.Write- ( writeBS- , bsRows- , writePL- , plRows- , writeJournal- , journalRows- , writeAccountOf- , accountLedgerRows- , writeAccountOfJournal- , accountLedgerRowsJournal- , writeCompoundTrialBalance- , compoundTrialBalanceRows- , writeWorksheet- , worksheetRows- , writePostClosingTrialBalance- , postClosingTrialBalanceRows- , balanceOf- , tshow- , toSameLength- )+import ExchangeAlgebra.IO.Output.Statements
src/ExchangeAlgebra/Render/Csv.hs view
@@ -7,19 +7,20 @@ CSV serialization of text tables: 'writeCSV' (every cell quoted) and 'csvTranspose' (transpose with blank padding). Both names are- re-exported unchanged from "ExchangeAlgebra.Write"; this module only+ re-exported unchanged from "ExchangeAlgebra.IO.Output.Csv"; this module only separates the file format from the bookkeeping layouts ("ExchangeAlgebra.Render.Bookkeeping") and the simulation dumps- ("ExchangeAlgebra.Render.Simulation").+ ("ExchangeAlgebra.Simulation.Output"). >>> import qualified Data.Text as T >>> csvTranspose [[T.pack "a", T.pack "b"], [T.pack "c"]] [["a","c"],["b",""]] -} -module ExchangeAlgebra.Render.Csv+module ExchangeAlgebra.Render.Csv {-# DEPRECATED "Import ExchangeAlgebra.IO.Output.Csv instead." #-} ( writeCSV , csvTranspose ) where -import ExchangeAlgebra.Write (writeCSV, csvTranspose)++import ExchangeAlgebra.IO.Output.Csv (writeCSV, csvTranspose)
src/ExchangeAlgebra/Render/Simulation.hs view
@@ -5,13 +5,9 @@ Released under the OWL license - CSV and file dumps of simulation results. 'writeFuncResults' and- 'writeFuncResultsWithContext' stream function results as time-series CSV- without requiring Chart. 'writeTermIO' and 'writeIOMatrix' are re-exported- unchanged from "ExchangeAlgebra.Write". Spill-file restoration is not a- rendering concern and stays in "ExchangeAlgebra.Simulate.Spill".+ Compatibility entry point for simulation CSV output. The CSV functions+ live in "ExchangeAlgebra.Simulation.Output". -}- module ExchangeAlgebra.Render.Simulation ( Header , writeFuncResults@@ -20,73 +16,5 @@ , writeIOMatrix ) where -import qualified Control.Monad as CM-import Control.Monad.ST-import qualified Data.Text as T-import qualified Data.Text.IO as TIO-import System.IO (IOMode(WriteMode), withFile)--import ExchangeAlgebra.Simulate (StateTime)-import ExchangeAlgebra.Write (writeTermIO, writeIOMatrix)--type Header = T.Text---- | Build a context for each term, then evaluate multiple functions together and output to CSV.--- By sharing each term's context, expensive preprocessing (e.g., termJournal, transfer) is reduced to once per term.--- Uses streaming output, so memory usage does not depend on the number of terms.------ Complexity: O(T * (cost(buildCtx) + |funcs| * cost(f)))-writeFuncResultsWithContext- :: ( StateTime t- , Show x- , Num x- )- => (a RealWorld -> t -> ST RealWorld c)- -> [(Header, c -> ST RealWorld x)]- -> (t,t)- -> a RealWorld- -> FilePath- -> IO ()-writeFuncResultsWithContext buildCtx funcs (tStart,tEnd) wld path = do- withFile path WriteMode $ \h -> do- TIO.hPutStrLn h (toCsvRow (T.pack "Time" : map fst funcs))- CM.forM_ [tStart .. tEnd] $ \t -> do- vals <- stToIO $ do- ctx <- buildCtx wld t- CM.forM funcs $ \(_, f) -> f ctx- let row = T.pack (show t) : map (T.pack . show) vals- TIO.hPutStrLn h (toCsvRow row)---- | Output the results of given functions as CSV time series data.--- Internally uses streaming output via 'writeFuncResultsWithContext'.------ Complexity: O(T * |funcs| * cost(f))-writeFuncResults- :: ( StateTime t- , Show x- , Num x- )- => [(Header,(a RealWorld -> t -> ST RealWorld x))]- -> (t,t)- -> a RealWorld- -> FilePath- -> IO ()-writeFuncResults funcs termRange wld path =- writeFuncResultsWithContext- (\_ t -> return t)- (map (\(header, f) -> (header, \t -> f wld t)) funcs)- termRange- wld- path--{-# INLINE toCsvRow #-}-toCsvRow :: [T.Text] -> T.Text-toCsvRow = T.intercalate (T.pack ",") . map escapeCsv--{-# INLINE escapeCsv #-}-escapeCsv :: T.Text -> T.Text-escapeCsv t- | T.any isSpecial t = T.concat [T.pack "\"", T.replace (T.pack "\"") (T.pack "\"\"") t, T.pack "\""]- | otherwise = t- where- isSpecial c = c == ',' || c == '"' || c == '\n' || c == '\r'+import ExchangeAlgebra.Simulation.Output+ ( Header, writeFuncResults, writeFuncResultsWithContext, writeTermIO, writeIOMatrix )
src/ExchangeAlgebra/Reporting/Group.hs view
@@ -1,55 +1,6 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Reporting.Group-Description : Presentation groups and the contra netting policy (Definition 7, Land 3).--A /presentation group/ is a statement block made of one or more __gross__-account titles and the __contra__ (評価勘定) titles that are deducted from-them, rendered as @gross lines → deduction lines → net line@. It is the-mechanism the Definition 7 contra amendment left open: Land 2 fixed the-classification (contra assets are @Assets@ with @isContra = True@) but kept a-display-compatibility shim so that the contra accounts still /appeared/ in the-liability column; this module supplies the real deduction presentation and-lets that shim be removed.--This module is deliberately free of both the CSV writers and the validated-reporting pipeline: it is the __single implementation__ of grouping and-netting, and "ExchangeAlgebra.Write" ('ExchangeAlgebra.Write.bsRows',-'ExchangeAlgebra.Write.plRows') is its consumer. Placing it here rather than-inside "ExchangeAlgebra.Reporting.Presentation" is forced by the type of the-legacy writers: they are total, base-polymorphic functions-(@ExBaseClass b => Alg n b -> [[Text]]@), whereas @present@ is specialized to-@HatBase AccountTitles@, is gated by an opaque-@ValidatedTrialBalance@, and answers in @Either@. Routing @bsRows@ through-@present@ would therefore change it from a pure formatter into a partial,-validation-gated one; duplicating the netting rule in both places is exactly-what this module exists to prevent.--=== Value domain--Statement amounts are held as a @(debit gross, credit gross)@ pair and only-netted at the last moment, into a t'RelativeAmount' carrying a __non-negative__-magnitude plus a direction flag. Stored and rendered magnitudes therefore stay-in \(\mathbb{R}_0^+\), including for soft-invariant value types such as-@MoneyDecimal@. A minus sign exists only in rendered text, never in a stored-value of t'RelativeAmount'.--=== Block invariant--Within one block the displayed amounts sum to the net line:--> Σ (gross rows) + Σ (subgroup rows) + Σ (deduction rows) = net row--A deduction row is stated /relative to the group's normal side/, so an-ordinary contra balance is already negative there and the sum above is a plain-addition, not a subtraction. A contra that exceeds its gross members simply-makes the net row negative (that is the @contra > parent@ edge case); a group-whose gross members are absent entirely nets to the negated deduction (the-@parent absent@ edge case). Both are handled by the same arithmetic.--}+-- | Compatibility exports for statement presentation groups. module ExchangeAlgebra.Reporting.Group+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.Statements.Group instead." #-} ( -- * Groups PresentationGroup(..) , PresentationGroupDef(..)@@ -69,345 +20,4 @@ , presentGroups ) where -import qualified Data.List as L-import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)-import qualified Data.Set as S-import Data.Set (Set)-import Data.Text (Text)--import ExchangeAlgebra.Algebra (HatVal(zeroValue), nearlyEqScaled)-import ExchangeAlgebra.Algebra.Base- ( AccountDivision(..)- , AccountTitles(..)- , Side(..)- , defaultSide- )----------------------------------------------------------------------- Groups----------------------------------------------------------------------- | Identity of a statement presentation block. These are reporting--- identities, never posting coordinates: they name a group of account titles,--- not an account.-data PresentationGroup- = TradeReceivablesGroup -- ^ 売上債権 − 貸倒引当金- | DepreciableAssetsGroup -- ^ 償却性有形固定資産 − 減価償却累計額- | NetSalesGroup -- ^ 総売上高 − 売上割戻- | NetPurchasesGroup -- ^ 総仕入高 − 仕入割戻- | IncomeTaxesGroup -- ^ 法人税等 − 還付法人税等- deriving (Show, Eq, Ord, Enum, Bounded)---- | One block of a statement: the gross titles, the contra titles deducted--- from them, and (optionally) the group this block rolls up into.------ @pgGross@ and @pgDeductions@ are ordered; rows are emitted in that order so--- that the output is deterministic. Every @pgDeductions@ member is expected to--- be a registry contra account of the same division as @pgDivision@, and no--- @pgGross@ member is; the test-suite pins both, together with the disjointness--- of the membership sets and the fact that every registry contra account--- belongs to exactly one group.-data PresentationGroupDef = PresentationGroupDef- { pgKey :: PresentationGroup- , pgDivision :: AccountDivision- -- ^ Statement column the block belongs to. Fixes the group's normal- -- side via 'groupNormalSide'.- , pgLabel :: Text- -- ^ Label of the net line (and of the carried subtotal when this block- -- rolls up into another one).- , pgGross :: [AccountTitles]- , pgDeductions :: [AccountTitles]- , pgParent :: Maybe PresentationGroup- -- ^ When set, this block's net is carried into the named block as a- -- 'SubgroupRow' and only the outermost block contributes to the column- -- total. The default grouping is flat; nesting exists so that a caller- -- can state a multi-level statement without a second netting rule.- }- deriving (Show, Eq)---- | Normal (deduction-free) side of a group, i.e. the side its net line is--- stated against.-groupNormalSide :: PresentationGroupDef -> Side-groupNormalSide = defaultSide . pgDivision---- | The grouping applied by 'ExchangeAlgebra.Write.bsRows' and--- 'ExchangeAlgebra.Write.plRows' unless a caller supplies its own.------ The membership lists are presentation policy, not algebra: they say which--- gross titles a single undifferentiated valuation account is deducted from--- under JGAAP's indirect method (間接控除法). The registry keeps exactly five--- contra accounts, so there are exactly five blocks. All blocks are flat —--- a nested default would assert a subtotal structure that the chart does not--- yet carry.-defaultPresentationGrouping :: [PresentationGroupDef]-defaultPresentationGrouping =- [ PresentationGroupDef- { pgKey = TradeReceivablesGroup- , pgDivision = Assets- , pgLabel = "TradeReceivablesNet"- , pgGross =- [ NotesReceivable- , AccountsReceivable- , ElectronicallyRecordedReceivable- , CreditCardReceivable- ]- , pgDeductions = [AllowanceForDoubtfulAccounts]- , pgParent = Nothing- }- , PresentationGroupDef- { pgKey = DepreciableAssetsGroup- , pgDivision = Assets- , pgLabel = "DepreciableAssetsNet"- , pgGross =- [ Building- , Structures- , Machinery- , Vehicle- , ToolsAndInstruments- , Fixtures- ]- , pgDeductions = [AccumulatedDepreciation]- , pgParent = Nothing- }- , PresentationGroupDef- { pgKey = NetSalesGroup- , pgDivision = Revenue- , pgLabel = "NetSales"- , pgGross = [Sales]- , pgDeductions = [SalesRebates]- , pgParent = Nothing- }- , PresentationGroupDef- { pgKey = NetPurchasesGroup- , pgDivision = Cost- , pgLabel = "NetPurchases"- , pgGross = [Purchases]- , pgDeductions = [PurchaseRebates]- , pgParent = Nothing- }- , PresentationGroupDef- { pgKey = IncomeTaxesGroup- , pgDivision = Cost- , pgLabel = "IncomeTaxesNet"- , pgGross = [CorporateIncomeTaxes, AdditionalIncomeTaxesForPriorPeriods]- , pgDeductions = [RefundOfIncomeTaxes]- , pgParent = Nothing- }- ]---- | Block a title belongs to under 'defaultPresentationGrouping', if any.-presentationGroupOf :: AccountTitles -> Maybe PresentationGroup-presentationGroupOf title = fmap pgKey (L.find member defaultPresentationGrouping)- where- member def = title `elem` pgGross def || title `elem` pgDeductions def---- | Look one block up in the grouping in force.-lookupGroupDef :: [PresentationGroupDef] -> PresentationGroup -> Maybe PresentationGroupDef-lookupGroupDef defs key = L.find ((== key) . pgKey) defs---- | Restrict a grouping to the statement columns a writer renders. A balance--- sheet must not net revenue accounts, and a profit and loss statement must--- not net asset accounts.-groupingForDivisions :: [AccountDivision] -> [PresentationGroupDef] -> [PresentationGroupDef]-groupingForDivisions divisions = filter ((`elem` divisions) . pgDivision)----------------------------------------------------------------------- Amounts----------------------------------------------------------------------- | An amount stated relative to a group's normal side. @raMagnitude@ is--- always non-negative; @raBelowZero@ says whether the amount sits on the--- opposite side (a deduction, or a net that its deductions pushed past zero).--- Callers render the direction — as a leading @-@, a triangle (△), or a move--- to the other column — and never negate the value itself.-data RelativeAmount v = RelativeAmount- { raBelowZero :: !Bool- , raMagnitude :: !v- }- deriving (Show, Eq)---- | Net a @(debit gross, credit gross)@ pair against a side. Uses the same--- scale-aware near-equality as @ExchangeAlgebra.Write.netGross@\/@diffRL@, so--- a balance that nets to zero within tolerance reports a zero magnitude--- rather than floating-point dust.--- This function compares two supplied totals; it does not sum postings. If--- floating-point addition has already rounded those totals, use--- @ExchangeAlgebra.Journal.Exact.accountBalancesExact@ or--- @ExchangeAlgebra.Algebra.Exact.diffRLExact@ upstream on the original postings.-relativeTo :: HatVal v => Side -> (v, v) -> RelativeAmount v-relativeTo side (debit, credit)- | nearlyEqScaled debit credit = RelativeAmount False zeroValue- | otherwise = case side of- Credit -> orient credit debit- -- 'defaultSide' only ever answers Debit or Credit, so the wildcard- -- cannot arise from a group; orient it like Debit rather than- -- inventing a third direction.- Debit -> orient debit credit- Side -> orient debit credit- where- orient toward away- | toward >= away = RelativeAmount False (toward - away)- | otherwise = RelativeAmount True (away - toward)---- | Accumulate @(debit gross, credit gross)@ pairs.-addGross :: Num v => (v, v) -> (v, v) -> (v, v)-addGross (leftDebit, leftCredit) (rightDebit, rightCredit) =- (leftDebit + rightDebit, leftCredit + rightCredit)----------------------------------------------------------------------- Grouped rows----------------------------------------------------------------------- | What one row of a block states.-data GroupRowKind- = GrossRow AccountTitles- -- ^ A constituent account, before deduction.- | SubgroupRow PresentationGroup- -- ^ The net of a nested block, carried into this one.- | DeductionRow AccountTitles- -- ^ A contra account. Its amount is already stated relative to the- -- group's normal side, so it is normally 'raBelowZero'.- | NetRow PresentationGroup- -- ^ The block's net. Equals the sum of the rows above it.- deriving (Show, Eq)--data GroupRow v = GroupRow- { grKind :: GroupRowKind- , grAmount :: RelativeAmount v- }- deriving (Show, Eq)---- | Result of applying a grouping to a set of per-title balances.-data GroupedPresentation v = GroupedPresentation- { gpBlocks :: [(PresentationGroupDef, [GroupRow v])]- -- ^ Blocks in render order: nested blocks precede the block they roll- -- up into, roots follow the order of the grouping list.- , gpRootTotals :: Map AccountDivision (v, v)- -- ^ Per-division @(debit, credit)@ contribution of the __outermost__- -- blocks only. A caller adds this to the ungrouped part of the column;- -- adding nested blocks as well would double count.- , gpConsumed :: Set AccountTitles- -- ^ Titles the blocks have taken over. The caller must not render these- -- again through its ordinary per-entry path.- }- deriving (Show, Eq)---- | Apply a grouping to per-title @(debit gross, credit gross)@ totals.------ A block is /active/ when one of its own contra titles carries non-zero gross--- activity, or when a nested block is active: a group with nothing to deduct is--- not a group, and leaving it inactive is what keeps output identical to the--- pre-amendment behavior for charts that contain no contra posting. Testing--- gross activity rather than the net balance also keeps a fully offset contra--- title inside its group instead of leaking one side into the ordinary rows.------ Complexity: O(g · m + g²) for @g@ blocks of @m@ members (the grouping is a--- fixed, small table).-presentGroups- :: HatVal v- => [PresentationGroupDef]- -> Map AccountTitles (v, v)- -> GroupedPresentation v-presentGroups defs balances = GroupedPresentation- { gpBlocks = blocks- , gpRootTotals = rootTotals- , gpConsumed = consumed- }- where- grossOf title = M.findWithDefault (zeroValue, zeroValue) title balances- hasBalance title = not (uncurry nearlyEqScaled (grossOf title))- hasActivity title = case M.lookup title balances of- Nothing -> False- Just (debit, credit) ->- not (nearlyEqScaled debit zeroValue)- || not (nearlyEqScaled credit zeroValue)-- memberTitles def = pgGross def ++ pgDeductions def-- -- Children of a block, in grouping order. 'pgParent' is followed with a- -- visited set so that a malformed (cyclic) grouping is truncated instead- -- of diverging.- childrenOf visited key =- [ def- | def <- defs- , pgParent def == Just key- , not (S.member (pgKey def) visited)- ]-- ownPair def = L.foldl' addGross (zeroValue, zeroValue)- (map grossOf (memberTitles def))-- subtreePair visited def = L.foldl' addGross (ownPair def)- (map (subtreePair visited') (activeChildrenOf visited' def))- where- visited' = S.insert (pgKey def) visited-- ownActive def = any hasActivity (pgDeductions def)-- isActive visited def = ownActive def- || any (isActive visited') (childrenOf visited' (pgKey def))- where- visited' = S.insert (pgKey def) visited-- activeChildrenOf visited def =- [ child- | child <- childrenOf visited (pgKey def)- , pgDivision child == pgDivision def- , isActive visited child- ]-- -- A block is a root when it has no usable parent, or when it is the stable- -- representative of a malformed parent cycle. Cross-division parents are- -- unusable because a carried subtotal and its root total must share a- -- statement column.- isRoot def = case pgParent def of- Nothing -> True- Just parent -> case lookupGroupDef defs parent of- Nothing -> True- Just parentDef- | pgDivision parentDef /= pgDivision def -> True- | otherwise -> case parentCycle def of- Nothing -> False- Just keys -> pgKey def == minimum keys-- parentCycle def = follow [] (pgKey def)- where- follow path key- | key `elem` path = Just (dropWhile (/= key) path)- | otherwise = case lookupGroupDef defs key >>= pgParent of- Nothing -> Nothing- Just parent -> follow (path ++ [key]) parent-- activeRoots = [def | def <- defs, isRoot def, isActive S.empty def]-- -- Depth-first: nested blocks are rendered before the block that carries- -- their net, so a reader meets a subtotal only after its constituents.- blocksOf visited def =- concatMap (blocksOf visited') activeChildren- ++ [(def, rowsOf visited' def activeChildren)]- where- visited' = S.insert (pgKey def) visited- activeChildren = activeChildrenOf visited' def-- rowsOf visited def activeChildren =- [ GroupRow (GrossRow title) (relative (grossOf title))- | title <- pgGross def, hasBalance title ]- ++- [ GroupRow (SubgroupRow (pgKey child)) (relative (subtreePair visited child))- | child <- activeChildren ]- ++- [ GroupRow (DeductionRow title) (relative (grossOf title))- | title <- pgDeductions def, hasBalance title ]- ++- [ GroupRow (NetRow (pgKey def)) (relative (subtreePair visited def)) ]- where- relative = relativeTo (groupNormalSide def)-- blocks = concatMap (blocksOf S.empty) activeRoots-- rootTotals = M.fromListWith addGross- [ (pgDivision def, subtreePair S.empty def) | def <- activeRoots ]-- consumed = S.fromList- [ title | (def, _) <- blocks, title <- memberTitles def ]+import ExchangeAlgebra.Accounting.Statements.Group
src/ExchangeAlgebra/Reporting/Metric.hs view
@@ -1,18 +1,6 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Reporting.Metric-Description : Typed, read-only metrics derived from exchange-algebra coordinates.--Derived metrics are not posting coordinates. In particular, profit and loss-are two directions of one period-result identity, represented by-'PeriodProfit' and 'PeriodLoss' with non-negative values. The legacy-@AccountTitles@ constructors remain available for the established transfer-pipeline, but new reporting code should derive values without inserting those-coordinates.--}+-- | Compatibility exports for reporting metrics. module ExchangeAlgebra.Reporting.Metric+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.Statements.Metric instead." #-} ( MetricId , mkMetricId , metricIdText@@ -25,158 +13,4 @@ , periodResultOf ) where -import qualified Data.Map.Strict as M-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra- ( Alg, HatVal(isErrorValue), foldEntries )-import ExchangeAlgebra.Algebra.Base- ( AccountDivision(..)- , AccountSemantics(asemDivisionSemantics)- , AccountTitles(..)- , DivisionSemantics(..)- , ExBaseClass(getAccountTitle, whichSide)- , Hat(..)- , HatBaseClass(hat)- , Side(..)- , accountSemantics- )-import qualified ExchangeAlgebra.TrialBalance.Validation as TB---- | Stable identity for a caller-defined metric. Display labels are supplied--- separately by a reporting context.-newtype MetricId = MetricId Text- deriving (Show, Eq, Ord)---- | Construct a non-blank custom metric identity.-mkMetricId :: Text -> Maybe MetricId-mkMetricId raw- | T.null normalized = Nothing- | otherwise = Just (MetricId normalized)- where- normalized = T.strip raw--metricIdText :: MetricId -> Text-metricIdText (MetricId value) = value---- | Identity of a value derived for reporting. Profit versus loss is carried--- by the result value, not duplicated as two metric identities.-data DerivedMetric- = PeriodResultMetric- | GrossProfitMetric- | OrdinaryProfitMetric- | CustomMetric MetricId- deriving (Show, Eq, Ord)---- | A period result with structural direction and a non-negative amount.-data PeriodResult v- = PeriodProfit v- | PeriodLoss v- | PeriodBreakEven- deriving (Show, Eq)--data MetricError- = MetricNotAvailableAtStage TB.TrialBalanceStage- | ResidualDerivedCoordinate AccountTitles- | WildcardMetricSide AccountTitles- | InvalidMetricValue AccountTitles- deriving (Show, Eq)---- | Map an engine-only legacy coordinate to its reporting identity.-metricForLegacyTitle :: AccountTitles -> Maybe DerivedMetric-metricForLegacyTitle title = case title of- NetIncome -> Just PeriodResultMetric- NetLoss -> Just PeriodResultMetric- GrossProfit -> Just GrossProfitMetric- OrdinaryProfit -> Just OrdinaryProfitMetric- _ -> Nothing---- | Legacy coordinates retained for one metric. This is a migration aid,--- not a list of statement lines.-legacyTitlesForMetric :: DerivedMetric -> [AccountTitles]-legacyTitlesForMetric metric = case metric of- PeriodResultMetric -> [NetIncome, NetLoss]- GrossProfitMetric -> [GrossProfit]- OrdinaryProfitMetric -> [OrdinaryProfit]- CustomMetric _ -> []---- | Derive period profit or loss without adding a balancing coordinate.--- Only genuine statement-classified Cost and Revenue titles participate;--- direction-encoding legacy coordinates are therefore excluded structurally.--- Invalid values and wildcard sides are returned as explicit errors. For--- externally sourced trial balances, prefer 'periodResultOf'.-periodResultOfAlg- :: (HatVal v, ExBaseClass b)- => Alg v b- -> Either MetricError (PeriodResult v)-periodResultOfAlg alg- | (_, base) : _ <- invalidEntries =- Left (InvalidMetricValue (getAccountTitle base))- | (_, base) : _ <- wildcardEntries =- Left (WildcardMetricSide (getAccountTitle base))- | credit == debit = Right PeriodBreakEven- | credit > debit = Right (PeriodProfit (credit - debit))- | otherwise = Right (PeriodLoss (debit - credit))- where- entries = foldEntries (\current value base -> (value, base) : current) [] alg- invalidEntries = filter (isErrorValue . fst) entries- wildcardEntries = filter ((== HatNot) . hat . snd) entries- (debit, credit) = foldEntries collect (0, 0) alg- collect totals value base- | isStatementNominal (getAccountTitle base) =- addOnSide (whichSide base) value totals- | otherwise = totals---- | Derive a period result from an accepted before-closing trial balance.--- A non-zero legacy derived coordinate is rejected to prevent a caller from--- treating an already-derived balancing item as another nominal account.--- Consolidation attribution coordinates do not participate, so the result is--- the period result before attribution to owners and non-controlling interests.-periodResultOf- :: HatVal v- => TB.ValidatedTrialBalance v- -> Either MetricError (PeriodResult v)-periodResultOf validated- | TB.validatedStage validated /= TB.BeforeClosing =- Left (MetricNotAvailableAtStage (TB.validatedStage validated))- | legacy : _ <- nonZeroLegacyTitles alg =- Left (ResidualDerivedCoordinate legacy)- | otherwise = periodResultOfAlg alg- where- alg = TB.validatedTrialBalance validated--isStatementNominal :: AccountTitles -> Bool-isStatementNominal title = case accountSemantics title of- Just semantics -> case asemDivisionSemantics semantics of- StatementDivision Cost -> True- StatementDivision Revenue -> True- _ -> False- Nothing -> False--addOnSide :: Num v => Side -> v -> (v, v) -> (v, v)-addOnSide Debit value (debit, credit) = (debit + value, credit)-addOnSide Credit value (debit, credit) = (debit, credit + value)-addOnSide Side _ totals = totals--nonZeroLegacyTitles- :: (HatVal v, ExBaseClass b)- => Alg v b- -> [AccountTitles]-nonZeroLegacyTitles alg =- [ title- | (title, (debit, credit)) <- M.toList totals- , debit /= credit- ]- where- totals = foldEntries collect M.empty alg- collect current value base = case metricForLegacyTitle title of- Nothing -> current- Just _ -> M.insertWith add title (sidePair (whichSide base) value) current- where- title = getAccountTitle base- add (newDebit, newCredit) (oldDebit, oldCredit) =- (newDebit + oldDebit, newCredit + oldCredit)- sidePair Debit value = (value, 0)- sidePair Credit value = (0, value)- sidePair Side _ = (0, 0)+import ExchangeAlgebra.Accounting.Statements.Metric
src/ExchangeAlgebra/Reporting/Presentation.hs view
@@ -1,17 +1,6 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.Reporting.Presentation-Description : Context-sensitive JGAAP presentation from validated trial balances.--This module is the reporting boundary: presentation accepts only an opaque-'TB.ValidatedTrialBalance'. Bookkeeping coordinates remain unchanged; all-eliminations, relabelings, maturity allocations, netting decisions, and-subtotals are recorded as presentation audit events.--}+-- | Compatibility exports for financial statement presentation. module ExchangeAlgebra.Reporting.Presentation+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.Statements.Presentation instead." #-} ( AccountingFramework(..) , ReportingScope(..) , PresentationProfile(..)@@ -36,753 +25,4 @@ , present ) where -import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)-import qualified Data.Set as S-import Data.Set (Set)-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra (HatVal)-import ExchangeAlgebra.Algebra.Base- ( AccountDivision(..)- , AccountRole(..)- , AccountSemantics(asemDivisionSemantics,- asemReportingEligibility, asemRoles)- , AccountTitles(..)- , DivisionSemantics(..)- , FixedCurrent(..)- , ReportingEligibility(..)- , Side(..)- )-import qualified ExchangeAlgebra.Algebra.Base.Account.Registry as Registry-import ExchangeAlgebra.Reporting.Metric- ( DerivedMetric(..), MetricId )-import qualified ExchangeAlgebra.TrialBalance.Validation as TB-import ExchangeAlgebra.TrialBalance.Balance- ( accountBalances- , balanceAmount- , balanceFor- , balancePair- , balanceSide- , combineBalances- , netPair- )---- | Land 4 deliberately supports JGAAP only.-data AccountingFramework = JGAAP- deriving (Show, Eq)--data ReportingScope = Standalone | Combined- deriving (Show, Eq)--data PresentationProfile- = JcciSecondGradeReport- | CanonicalJapanese- | CanonicalEnglish- deriving (Show, Eq)--data StatementSection- = CurrentAssetsSection- | NoncurrentAssetsSection- | CurrentLiabilitiesSection- | NoncurrentLiabilitiesSection- | EquitySection- | RevenueSection- | ExpenseSection- | ContextualDebitSection- | ContextualCreditSection- deriving (Show, Eq)--data StatementLine v = StatementLine- { _lineAccount :: AccountTitles- , _lineLabel :: Text- , _lineSection :: StatementSection- , _lineSide :: Side- , _lineAmount :: v- , _lineIsDeduction :: Bool- }- deriving (Show, Eq)---- | Evidence-backed split of one title without creating new bookkeeping--- coordinates. Current and non-current parts must sum exactly to its balance.-data PresentationAllocation v = PresentationAllocation- { _allocationAccount :: AccountTitles- , _allocationCurrent :: v- , _allocationNoncurrent :: v- , _allocationEvidence :: Text- }- deriving (Show, Eq)--data PresentationRelabel = PresentationRelabel- { _relabelSource :: AccountTitles- , _relabelTarget :: AccountTitles- , _relabelRationale :: Text- }- deriving (Show, Eq)--data MaterialityTreatment- = PresentGross- | PresentSeparately- | NetAgainst AccountTitles- deriving (Show, Eq)--data MaterialityDecision = MaterialityDecision- { _materialityAccount :: AccountTitles- , _materialityTreatment :: MaterialityTreatment- , _materialityRationale :: Text- }- deriving (Show, Eq)--data ContraPresentationRule- = PresentContraSeparately AccountTitles Text- | NetContraAgainst AccountTitles AccountTitles Text- deriving (Show, Eq)---- | Profile-specific display labels for a caller-defined metric. The metric--- identity remains separate from these labels.-data CustomMetricLabel = CustomMetricLabel- { _customMetricIdentity :: MetricId- , _customMetricLabelJapanese :: Text- , _customMetricLabelEnglish :: Text- }- deriving (Show, Eq)--data SubtotalCoverage- = RequireAllTitlesPresent- | TreatAbsentAsZero- deriving (Show, Eq)---- | A subtotal is a typed reporting definition, never an account-basis title.-data SubtotalDefinition = SubtotalDefinition- { _subtotalMetric :: DerivedMetric- , _subtotalCreditTitles :: [AccountTitles]- , _subtotalDebitTitles :: [AccountTitles]- , _subtotalCoverage :: SubtotalCoverage- }- deriving (Show, Eq)--data StatementSubtotal v = StatementSubtotal- { _statementSubtotalMetric :: DerivedMetric- , _statementSubtotalLabel :: Text- , _statementSubtotalBalance :: TB.AccountBalance v- }- deriving (Show, Eq)--data ReportingContext v = ReportingContext- { _reportingFramework :: AccountingFramework- , _reportingScope :: ReportingScope- , _presentationProfile :: PresentationProfile- , _maturitySensitiveTitles :: Set AccountTitles- , _presentationAllocations :: [PresentationAllocation v]- , _presentationRelabels :: [PresentationRelabel]- , _materialityDecisions :: [MaterialityDecision]- , _contraPresentationRules :: [ContraPresentationRule]- , _subtotalDefinitions :: [SubtotalDefinition]- , _customMetricLabels :: [CustomMetricLabel]- }- deriving (Show, Eq)--jcciSecondGradeContext :: ReportingScope -> ReportingContext v-jcciSecondGradeContext scope = ReportingContext- { _reportingFramework = JGAAP- , _reportingScope = scope- , _presentationProfile = JcciSecondGradeReport- , _maturitySensitiveTitles = S.empty- , _presentationAllocations = []- , _presentationRelabels = []- , _materialityDecisions = []- , _contraPresentationRules = []- , _subtotalDefinitions = []- , _customMetricLabels = []- }--data PresentationAuditEvent v- = ReciprocalAccountsEliminated- (TB.AccountBalance v) (TB.AccountBalance v)- | AccountRelabeled- AccountTitles AccountTitles (TB.AccountBalance v) Text- | BalanceAllocated AccountTitles v v Text- | MaterialityApplied- AccountTitles MaterialityTreatment (TB.AccountBalance v) Text- | ContraPresentationApplied- AccountTitles (Maybe AccountTitles) (TB.AccountBalance v) Text- | SubtotalCalculated DerivedMetric (TB.AccountBalance v)- | LabelOverridden AccountTitles Text Text- deriving (Show, Eq)--data PresentationIssue v- = ValidationFindingBlocks (TB.TBFinding v)- | MissingPresentationAllocation AccountTitles- | DuplicatePresentationAllocation AccountTitles- | InvalidPresentationAllocation- AccountTitles (TB.AccountBalance v) v v- | BlankPresentationEvidence AccountTitles- | UnexpectedPresentationAllocation AccountTitles- | ConflictingPresentationInstruction AccountTitles- | MissingPresentationAccount AccountTitles- | BlankPresentationRationale AccountTitles- | UnpresentableBalance AccountTitles (TB.AccountBalance v)- | InvalidSubtotalDefinition DerivedMetric- | DuplicateMetricIdentity DerivedMetric- | UnlabelledCustomMetric MetricId- | UnreconciledPresentation v v- deriving (Show, Eq)--data FinancialStatements v = FinancialStatements- { _statementFramework :: AccountingFramework- , _statementScope :: ReportingScope- , _statementProfile :: PresentationProfile- , _statementTrialBalanceStage :: TB.TrialBalanceStage- , _statementLines :: [StatementLine v]- , _statementSubtotals :: [StatementSubtotal v]- , _presentationAudit :: [PresentationAuditEvent v]- }- deriving (Show, Eq)---- | Profile-specific Japanese display-label overrides.-jcciSecondGradeLabelOverrides :: Map AccountTitles Text-jcciSecondGradeLabelOverrides = M.fromList- [ (AdvancesReceived, T.pack "契約負債")- ]---- | Profile-specific display label. Japanese profiles use the registry's--- cleaned statement label, with the JCCI grade-2 overrides applied first.-presentationLabel :: PresentationProfile -> AccountTitles -> Text-presentationLabel profile title = case Registry.accountSpec title of- Nothing -> T.pack (show title)- Just spec -> case profile of- JcciSecondGradeReport -> M.findWithDefault- (Registry.asLabelJa spec) title jcciSecondGradeLabelOverrides- CanonicalJapanese -> Registry.asLabelJa spec- CanonicalEnglish -> Registry.asNameEn spec---- | Resolve a metric's display label independently of its stable identity.--- Profit/loss wording follows the structural balance direction.-metricLabel- :: ReportingContext v- -> DerivedMetric- -> TB.AccountBalance v- -> Maybe Text-metricLabel context metric balance = case metric of- PeriodResultMetric -> Just (builtinLabel- "当期純損益" "当期純利益" "当期純損失"- "Net result" "Net income" "Net loss")- GrossProfitMetric -> Just (builtinLabel- "売上総損益" "売上総利益" "売上総損失"- "Gross result" "Gross profit" "Gross loss")- OrdinaryProfitMetric -> Just (builtinLabel- "経常損益" "経常利益" "経常損失"- "Ordinary result" "Ordinary profit" "Ordinary loss")- CustomMetric metricId -> customLabel metricId- where- english = _presentationProfile context == CanonicalEnglish- builtinLabel neutralJa creditJa debitJa neutralEn creditEn debitEn =- case (english, balance) of- (False, TB.NoBalance) -> neutralJa- (False, TB.CreditBalance _) -> creditJa- (False, TB.DebitBalance _) -> debitJa- (True, TB.NoBalance) -> neutralEn- (True, TB.CreditBalance _) -> creditEn- (True, TB.DebitBalance _) -> debitEn- customLabel metricId = case filter- ((== metricId) . _customMetricIdentity)- (_customMetricLabels context) of- [label] -> Just (if english- then _customMetricLabelEnglish label- else _customMetricLabelJapanese label)- _ -> Nothing--present- :: HatVal v- => ReportingContext v- -> TB.ValidatedTrialBalance v- -> Either (NonEmpty (PresentationIssue v)) (FinancialStatements v)-present context validated = case issues of- issue : rest -> Left (issue :| rest)- [] -> Right FinancialStatements- { _statementFramework = framework- , _statementScope = _reportingScope context- , _statementProfile = _presentationProfile context- , _statementTrialBalanceStage = TB.validatedStage validated- , _statementLines = statementLines- , _statementSubtotals = subtotals- , _presentationAudit = eliminationAudit- ++ relabelAudit ++ materialityAudit ++ contraAudit- ++ allocationAudit ++ subtotalAudit ++ labelAudit- }- where- framework = case _reportingFramework context of- JGAAP -> JGAAP- initial = accountBalances (TB.validatedTrialBalance validated)- gatePolicy = case _reportingScope context of- Standalone -> TB.standaloneTrialBalancePolicy- Combined -> TB.strictTrialBalancePolicy- gateIssues = map ValidationFindingBlocks- (filter (TB.findingBlocksPresentation gatePolicy)- (TB.validatedFindings validated))- (eliminated, eliminationAudit) = eliminateReciprocals context initial- (relabeled, relabelAudit) = applyRelabels context eliminated- (materialized, materialityAudit) = applyMateriality context relabeled- (transformed, contraAudit) = applyContraRules context materialized- explicitRequired = S.union- (TB.validatedMaturityRequiredTitles validated)- (_maturitySensitiveTitles context)- requiredMaturity = S.union explicitRequired- (implicitMaturityTitles transformed)- statementLines = renderLines context transformed- contextIssues = instructionIssues explicitRequired context initial- ++ allocationIssues requiredMaturity context transformed- ++ subtotalIssues context initial transformed- ++ coverageIssues transformed- ++ reconciliationIssues statementLines- issues = gateIssues ++ contextIssues- allocationAudit =- [ BalanceAllocated title current noncurrent evidence- | PresentationAllocation title current noncurrent evidence- <- _presentationAllocations context- , balanceFor title transformed /= TB.NoBalance- ]- subtotals =- [ StatementSubtotal metric label balance- | SubtotalDefinition metric credits debits _ <-- _subtotalDefinitions context- , let balance = subtotalBalance transformed credits debits- , Just label <- [metricLabel context metric balance]- ]- subtotalAudit =- [ SubtotalCalculated metric balance- | StatementSubtotal metric _ balance <- subtotals- ]- labelAudit =- [ LabelOverridden title canonical displayed- | _presentationProfile context == JcciSecondGradeReport- , (title, _) <- M.toList jcciSecondGradeLabelOverrides- , title `elem` map _lineAccount statementLines- , let canonical = presentationLabel CanonicalJapanese title- , let displayed = presentationLabel JcciSecondGradeReport title- , canonical /= displayed- ]--eliminateReciprocals- :: Eq v => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> (Map AccountTitles (TB.AccountBalance v), [PresentationAuditEvent v])-eliminateReciprocals context balances = case _reportingScope context of- Standalone -> (balances, [])- Combined ->- ( M.delete BranchCurrentAccount- (M.delete HeadOfficeCurrentAccount balances)- , [ ReciprocalAccountsEliminated branch headOffice- | branch /= TB.NoBalance || headOffice /= TB.NoBalance- ]- )- where- branch = balanceFor BranchCurrentAccount balances- headOffice = balanceFor HeadOfficeCurrentAccount balances--applyRelabels- :: (Ord v, Num v)- => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> (Map AccountTitles (TB.AccountBalance v), [PresentationAuditEvent v])-applyRelabels context balances0 =- Prelude.foldl applyOne (balances0, []) (_presentationRelabels context)- where- applyOne (balances, events) (PresentationRelabel source target rationale) =- let sourceBalance = balanceFor source balances- targetBalance = balanceFor target balances- next = M.insert target (combineBalances targetBalance sourceBalance)- (M.delete source balances)- in (next, events ++- [AccountRelabeled source target sourceBalance rationale])--applyMateriality- :: (Ord v, Num v)- => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> (Map AccountTitles (TB.AccountBalance v), [PresentationAuditEvent v])-applyMateriality context balances0 =- Prelude.foldl applyOne (balances0, []) (_materialityDecisions context)- where- applyOne (balances, events) decision =- let source = _materialityAccount decision- treatment = _materialityTreatment decision- next = case treatment of- PresentGross -> balances- PresentSeparately -> balances- NetAgainst target -> moveBalance source target balances- event = MaterialityApplied source treatment- (balanceFor source balances) (_materialityRationale decision)- in (next, events ++ [event])--applyContraRules- :: (Ord v, Num v)- => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> (Map AccountTitles (TB.AccountBalance v), [PresentationAuditEvent v])-applyContraRules context balances0 =- Prelude.foldl applyOne (balances0, [])- (_contraPresentationRules context)- where- applyOne (balances, events) rule = case rule of- PresentContraSeparately source rationale ->- (balances, events ++ [ContraPresentationApplied source Nothing- (balanceFor source balances) rationale])- NetContraAgainst source target rationale ->- ( moveBalance source target balances- , events ++ [ContraPresentationApplied source (Just target)- (balanceFor source balances) rationale]- )--moveBalance- :: (Ord v, Num v)- => AccountTitles -> AccountTitles- -> Map AccountTitles (TB.AccountBalance v)- -> Map AccountTitles (TB.AccountBalance v)-moveBalance source target balances =- if source == target- then balances- else M.insert target- (combineBalances (balanceFor target balances)- (balanceFor source balances))- (M.delete source balances)--allocationIssues- :: HatVal v- => Set AccountTitles- -> ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> [PresentationIssue v]-allocationIssues required context balances = concatMap checkTitle checkedTitles- ++ [ DuplicatePresentationAllocation title- | title <- duplicateTitles (map _allocationAccount allocations)- ]- ++ map UnexpectedPresentationAllocation- (S.toList (allocationTitles `S.difference` required))- where- allocations = _presentationAllocations context- allocationTitles = S.fromList (map _allocationAccount allocations)- checkedTitles = S.toList (required `S.union` allocationTitles)- checkTitle title = case balanceFor title balances of- TB.NoBalance- | title `S.member` allocationTitles ->- [MissingPresentationAccount title]- | otherwise -> []- balance -> case filter ((== title) . _allocationAccount) allocations of- [] -> [MissingPresentationAllocation title]- [allocation]- | T.null (T.strip (_allocationEvidence allocation)) ->- [BlankPresentationEvidence title]- | allocationMatches balance allocation -> []- | otherwise -> [InvalidPresentationAllocation title balance- (_allocationCurrent allocation)- (_allocationNoncurrent allocation)]- _ -> []--allocationMatches- :: (Ord v, Num v)- => TB.AccountBalance v -> PresentationAllocation v -> Bool-allocationMatches TB.NoBalance _ = False-allocationMatches (TB.DebitBalance value) allocation =- _allocationCurrent allocation >= 0- && _allocationNoncurrent allocation >= 0- && _allocationCurrent allocation + _allocationNoncurrent allocation == value-allocationMatches (TB.CreditBalance value) allocation =- _allocationCurrent allocation >= 0- && _allocationNoncurrent allocation >= 0- && _allocationCurrent allocation + _allocationNoncurrent allocation == value--instructionIssues- :: HatVal v- => Set AccountTitles- -> ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> [PresentationIssue v]-instructionIssues explicitRequired context balances =- duplicateInstructionIssues- ++ concatMap relabelIssue (_presentationRelabels context)- ++ concatMap materialityIssue (_materialityDecisions context)- ++ concatMap contraIssue (_contraPresentationRules context)- where- duplicateInstructionIssues =- map ConflictingPresentationInstruction- (duplicateTitles instructionSources)- ++ [ ConflictingPresentationInstruction title- | _reportingScope context == Combined- , title <- [BranchCurrentAccount, HeadOfficeCurrentAccount]- , title `elem` instructionSources- ]- instructionSources = concatMap relabelTitles (_presentationRelabels context)- ++ concatMap materialityTitles (_materialityDecisions context)- ++ concatMap contraTitles (_contraPresentationRules context)- relabelIssue rule- | T.null (T.strip (_relabelRationale rule)) =- [BlankPresentationRationale (_relabelSource rule)]- | balanceFor (_relabelSource rule) balances == TB.NoBalance =- [MissingPresentationAccount (_relabelSource rule)]- | _relabelSource rule `S.member` explicitRequired =- [ConflictingPresentationInstruction (_relabelSource rule)]- | not (presentableTarget (_relabelTarget rule)) =- [ConflictingPresentationInstruction (_relabelTarget rule)]- | otherwise = []- materialityIssue decision- | T.null (T.strip (_materialityRationale decision)) =- [BlankPresentationRationale (_materialityAccount decision)]- | balanceFor (_materialityAccount decision) balances == TB.NoBalance =- [MissingPresentationAccount (_materialityAccount decision)]- | otherwise = case _materialityTreatment decision of- NetAgainst target- | _materialityAccount decision `S.member` explicitRequired ->- [ConflictingPresentationInstruction- (_materialityAccount decision)]- | target == _materialityAccount decision ->- [ConflictingPresentationInstruction target]- | not (presentableTarget target) ->- [ConflictingPresentationInstruction target]- | M.notMember target balances ->- [MissingPresentationAccount target]- _ -> []- contraIssue rule = case rule of- PresentContraSeparately source rationale- | T.null (T.strip rationale) ->- [BlankPresentationRationale source]- | balanceFor source balances == TB.NoBalance ->- [MissingPresentationAccount source]- | otherwise -> []- NetContraAgainst source target rationale- | T.null (T.strip rationale) ->- [BlankPresentationRationale source]- | balanceFor source balances == TB.NoBalance ->- [MissingPresentationAccount source]- | source `S.member` explicitRequired ->- [ConflictingPresentationInstruction source]- | source == target ->- [ConflictingPresentationInstruction source]- | not (presentableTarget target) ->- [ConflictingPresentationInstruction target]- | M.notMember target balances ->- [MissingPresentationAccount target]- | otherwise -> []- relabelTitles rule = [_relabelSource rule, _relabelTarget rule]- materialityTitles decision = _materialityAccount decision : case- _materialityTreatment decision of- NetAgainst target -> [target]- PresentGross -> []- PresentSeparately -> []- contraTitles (PresentContraSeparately source _) = [source]- contraTitles (NetContraAgainst source target _) = [source, target]--duplicateTitles :: [AccountTitles] -> [AccountTitles]-duplicateTitles titles = S.toList- (S.fromList [title | title <- titles, count title titles > 1])- where- count needle = length . filter (== needle)--presentableTarget :: AccountTitles -> Bool-presentableTarget title = case Registry.accountSemantics title of- Just semantics -> asemReportingEligibility semantics `elem`- [StatementEligible, ContextualPresentation]- Nothing -> False--implicitMaturityTitles- :: Map AccountTitles (TB.AccountBalance v) -> Set AccountTitles-implicitMaturityTitles balances = S.fromList- [ title- | (title, balance) <- M.toList balances- , balanceSide balance /= Side- , fixedCurrent title == Other- , Just semantics <- [Registry.accountSemantics title]- , StatementDivision division <- [asemDivisionSemantics semantics]- , division `elem` [Assets, Liability]- ]--coverageIssues- :: Map AccountTitles (TB.AccountBalance v) -> [PresentationIssue v]-coverageIssues balances =- [ UnpresentableBalance title balance- | (title, balance) <- M.toList balances- , balanceSide balance /= Side- , not (presentableTarget title)- ]--reconciliationIssues- :: (Eq v, Num v) => [StatementLine v] -> [PresentationIssue v]-reconciliationIssues lines0 =- [ UnreconciledPresentation debit credit | debit /= credit ]- where- debit = sum [_lineAmount line | line <- lines0, _lineSide line == Debit]- credit = sum [_lineAmount line | line <- lines0, _lineSide line == Credit]--subtotalIssues- :: HatVal v- => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> Map AccountTitles (TB.AccountBalance v)- -> [PresentationIssue v]-subtotalIssues context initial balances = duplicateDefinitionIssues- ++ duplicateCustomLabelIssues- ++ concatMap check definitions- where- definitions = _subtotalDefinitions context- definitionMetrics = map _subtotalMetric definitions- duplicateDefinitionIssues =- [ DuplicateMetricIdentity metric- | metric <- duplicateMetrics definitionMetrics- ]- customLabelIds = map _customMetricIdentity (_customMetricLabels context)- duplicateCustomLabelIssues =- [ DuplicateMetricIdentity (CustomMetric metricId)- | metricId <- duplicateMetricIds customLabelIds- ]- check (SubtotalDefinition metric credits debits coverage)- | null credits && null debits = [InvalidSubtotalDefinition metric]- | not (null (duplicateTitles (credits ++ debits))) =- [InvalidSubtotalDefinition metric]- | customMetricUnlabelled metric = [customMetricIssue metric]- | coverage == RequireAllTitlesPresent- && any (`M.notMember` balances) (credits ++ debits) =- [InvalidSubtotalDefinition metric]- | coverage == TreatAbsentAsZero- && any removedNonZeroTitle (credits ++ debits) =- [InvalidSubtotalDefinition metric]- | any (wrongSide Credit coverage) credits =- [InvalidSubtotalDefinition metric]- | any (wrongSide Debit coverage) debits =- [InvalidSubtotalDefinition metric]- | otherwise = []- removedNonZeroTitle title =- balanceFor title initial /= TB.NoBalance && M.notMember title balances- wrongSide expected coverage title = case balanceFor title balances of- TB.NoBalance -> coverage == RequireAllTitlesPresent- balance -> balanceSide balance /= expected- customMetricUnlabelled metric = case metric of- CustomMetric metricId -> case customLabels metricId of- [label] -> T.null (T.strip (_customMetricLabelJapanese label))- || T.null (T.strip (_customMetricLabelEnglish label))- _ -> True- _ -> False- customMetricIssue metric = case metric of- CustomMetric metricId -> UnlabelledCustomMetric metricId- _ -> InvalidSubtotalDefinition metric- customLabels metricId = filter- ((== metricId) . _customMetricIdentity)- (_customMetricLabels context)--duplicateMetrics :: [DerivedMetric] -> [DerivedMetric]-duplicateMetrics metrics = S.toList- (S.fromList [metric | metric <- metrics, count metric metrics > 1])- where- count needle = length . filter (== needle)--duplicateMetricIds :: [MetricId] -> [MetricId]-duplicateMetricIds metricIds = S.toList- (S.fromList [metricId | metricId <- metricIds, count metricId metricIds > 1])- where- count needle = length . filter (== needle)--renderLines- :: (Eq v, Num v)- => ReportingContext v- -> Map AccountTitles (TB.AccountBalance v)- -> [StatementLine v]-renderLines context balances = concatMap renderOne (M.toList balances)- where- renderOne (_, TB.NoBalance) = []- renderOne (title, balance) = case allocationFor title of- Just allocation -> allocatedLines context title balance allocation- Nothing -> case reportingSection title balance of- Nothing -> []- Just section -> [lineFor context title section balance]- allocationFor title = case filter ((== title) . _allocationAccount)- (_presentationAllocations context) of- allocation : _ -> Just allocation- [] -> Nothing--allocatedLines- :: (Eq v, Num v)- => ReportingContext v -> AccountTitles -> TB.AccountBalance v- -> PresentationAllocation v -> [StatementLine v]-allocatedLines context title balance allocation =- [ StatementLine title (presentationLabel (_presentationProfile context) title)- section side amount (isContraTitle title)- | (section, amount) <-- [ (currentSection title balance, _allocationCurrent allocation)- , (noncurrentSection title balance, _allocationNoncurrent allocation)- ]- , amount /= 0- ]- where- side = balanceSide balance--lineFor- :: Num v => ReportingContext v -> AccountTitles -> StatementSection- -> TB.AccountBalance v -> StatementLine v-lineFor context title section balance = StatementLine- title (presentationLabel (_presentationProfile context) title)- section (balanceSide balance) (balanceAmount balance) (isContraTitle title)--isContraTitle :: AccountTitles -> Bool-isContraTitle title = case Registry.accountSemantics title of- Just semantics -> ContraAccount `elem` asemRoles semantics- Nothing -> False--reportingSection- :: AccountTitles -> TB.AccountBalance v -> Maybe StatementSection-reportingSection title balance = do- semantics <- Registry.accountSemantics title- case asemReportingEligibility semantics of- DerivedPresentation -> Nothing- NotPresented -> Nothing- ContextualPresentation -> Just (contextualSection balance)- StatementEligible -> case asemDivisionSemantics semantics of- StatementDivision division -> Just (divisionSection title division)- _ -> Nothing--divisionSection :: AccountTitles -> AccountDivision -> StatementSection-divisionSection title division = case division of- Assets -> case fixedCurrent title of- Fixed -> NoncurrentAssetsSection- _ -> CurrentAssetsSection- Liability -> case fixedCurrent title of- Fixed -> NoncurrentLiabilitiesSection- _ -> CurrentLiabilitiesSection- Equity -> EquitySection- Revenue -> RevenueSection- Cost -> ExpenseSection--fixedCurrent :: AccountTitles -> FixedCurrent-fixedCurrent title = case Registry.accountSpec title of- Just spec -> Registry.asFixedCurrent spec- Nothing -> Other--contextualSection :: TB.AccountBalance v -> StatementSection-contextualSection (TB.DebitBalance _) = ContextualDebitSection-contextualSection (TB.CreditBalance _) = ContextualCreditSection-contextualSection TB.NoBalance = ContextualDebitSection--currentSection :: AccountTitles -> TB.AccountBalance v -> StatementSection-currentSection title balance = case Registry.accountSemantics title of- Just semantics -> case asemDivisionSemantics semantics of- StatementDivision Liability -> CurrentLiabilitiesSection- StatementDivision Assets -> CurrentAssetsSection- _ -> contextualSection balance- Nothing -> contextualSection balance--noncurrentSection :: AccountTitles -> TB.AccountBalance v -> StatementSection-noncurrentSection title balance = case Registry.accountSemantics title of- Just semantics -> case asemDivisionSemantics semantics of- StatementDivision Liability -> NoncurrentLiabilitiesSection- StatementDivision Assets -> NoncurrentAssetsSection- _ -> contextualSection balance- Nothing -> contextualSection balance--subtotalBalance- :: (Ord v, Num v)- => Map AccountTitles (TB.AccountBalance v)- -> [AccountTitles] -> [AccountTitles] -> TB.AccountBalance v-subtotalBalance balances creditTitles debitTitles = netPair- ( sum [balanceAmount (balanceFor title balances) | title <- debitTitles]- , sum [balanceAmount (balanceFor title balances) | title <- creditTitles]- )+import ExchangeAlgebra.Accounting.Statements.Presentation
src/ExchangeAlgebra/Simulate.hs view
@@ -28,7 +28,7 @@ with a 'SpillDeletePolicy' (@'KeepRecentTerms' n@ for a sliding window, or 'DeleteSpilledChunk'), evict old terms so peak memory is independent of the term count. Spilled data is restored with 'readBinarySpillFile'. A runnable- example is @examples\/basic\/simulateEx2.hs@ (the @sim2@ executable).+ example is @examples\/basic\/fixedPriceIOSpill.hs@ (the @fixed-price-io-spill@ executable). == Application note @@ -41,10 +41,20 @@ /Economy./ Research Square, preprint (Version 1), posted 5 January 2026. <https://doi.org/10.21203/rs.3.rs-8485050/v1> - Runnable companions to the preprint live in the repository under- @examples\/deterministic\/ripple\/@- (<https://github.com/yakagika/ExchangeAlgebra/tree/master/examples/deterministic/ripple>).+ Runnable ripple examples are preserved in the+ <https://github.com/yakagika/ExchangeAlgebra/tree/archive/research-examples-pre-p4/examples archive/research-examples-pre-p4 tag>. + == Planned removal++ Version 0.7 is planned to move simulation into the separate+ @exchangealgebra-simulation@ package and to remove this module, together+ with "ExchangeAlgebra.Simulate.Engine", "ExchangeAlgebra.Simulate.Random",+ "ExchangeAlgebra.Simulate.Lite", and "ExchangeAlgebra.Simulate.Policy",+ without a compatibility module. The model evaluator in that package is the+ successor. The modules under @ExchangeAlgebra.Simulation@ (networks,+ analysis, optimization, output) and "ExchangeAlgebra.Simulate.Spill" are+ planned to move to that package. 0.6 emits no deprecation warning because+ that package has not been released. -} {-# LANGUAGE FlexibleContexts #-}@@ -119,7 +129,10 @@ import Data.STRef import qualified Control.Monad as CM import qualified Data.Map.Strict as M+import Data.Text (Text) import System.IO (IOMode(..), withFile)+import ExchangeAlgebra.Simulation.Array (modifyArray)+import ExchangeAlgebra.Simulation.Analysis (leontiefInverse, rippleEffect) import ExchangeAlgebra.Simulate.Spill ------------------------------------------------------------------@@ -137,7 +150,9 @@ -- -- nextTerm and prevTerm use the defaults (succ, pred) -- @ class (Eq t, Show t, Ord t, Enum t, Ix t) => StateTime t where+ -- | First simulation term. initTerm :: t+ -- | Last simulation term. lastTerm :: t -- | Return the next term. Defaults to @succ@. nextTerm :: t -> t@@ -183,18 +198,23 @@ -- (the random number generator is not being advanced). -- To use different random numbers per instance, use updateGen or similar. -- Planned: allow advancing a shared random number generator across instances.+ -- | Initialize one state component from a generator, term, and variables. initialize :: StdGen -> t -> v -> ST s (a s) + -- | Return the update operation for this component. updatePattern :: (a s) -> ST s UpdatePattern + -- | Apply the copy update; the default implementation raises an error. {-# INLINE copy #-} copy :: StdGen -> t -> v -> (a s) -> ST s () copy _ _ _ _ = error "Updatable.copy: default method not overridden" + -- | Apply the modifying update; the default implementation raises an error. {-# INLINE modify #-} modify :: StdGen -> t -> v -> (a s) -> ST s () modify _ _ _ _ = error "Updatable.modify: default method not overridden" + -- | Apply the operation selected by updatePattern. {-# INLINE update #-} update :: StdGen -> t -> v -> (a s) -> ST s () update g t v x = do@@ -252,17 +272,6 @@ --- | Modify the value at a given index of an array using a function. Evaluates strictly before writing back.------ Complexity: O(1)-{-# INLINE modifyArray #-}-modifyArray ::(MArray a t m, Ix i) => a i t -> i -> (t -> t) -> m ()-modifyArray ar e f = do- x <- readArray ar e- let y = f x- y `seq` writeArray ar e y-- -- | Type class for newtype wrappers around @STArray@. -- -- @_unwrapUArray@ and @_wrapUArray@ have @Coercible@-based default implementations,@@ -313,6 +322,7 @@ -- Through DefaultSignatures with Generic, initAll and updateAll can be automatically derived. class (StateTime t,InitVariables v, Event e) => StateSpace t v e (a :: Type -> Type) s | a s -> t v e where+ -- | Initialize the world state, using generic field initialization by default. {-# INLINE initAll #-} initAll :: StdGen -> t -> v -> ST s (a s) @@ -322,6 +332,7 @@ initAll g t v = GHC.Generics.to <$> gInitialize g t v -- DefaultSignatures extension+ -- | Update the world state, using generic field updates by default. {-# INLINE updateAll #-} updateAll :: StdGen -> t -> v -> a s -> ST s () default updateAll :: (Generic (a s), GUpdatable t v (Rep (a s)) s)@@ -433,15 +444,16 @@ -- -- Each scenario is a @(name, parameters)@ pair. All scenarios share the same -- random seed and are executed in parallel.+-- Enable @OverloadedStrings@ for the scenario-name literals in this example. -- -- @ -- let gen = mkStdGen 2025 -- scenarios = [(\"default\", defaultEnv), (\"plus\", plusEnv)] -- results <- runScenarios gen scenarios--- -- results :: Map String (World RealWorld)+-- -- results :: Map Text (World RealWorld) -- @ runScenarios :: (StateSpace t v e a RealWorld)- => StdGen -> [(String, v)] -> IO (M.Map String (a RealWorld))+ => StdGen -> [(Text, v)] -> IO (M.Map Text (a RealWorld)) runScenarios gen scenarios = do results <- mapConcurrently (\(_, v) -> runSimulation gen v) scenarios pure $ M.fromList $ zip (fmap fst scenarios) results@@ -453,15 +465,15 @@ -- -- @ -- results <- runScenariosWithSpill--- (\\name -> mkBinarySpillOptions 50 (dir ++ name ++ \".bin\") extract)+-- (\\name -> mkBinarySpillOptions 50 (dir ++ T.unpack name ++ \".bin\") extract) -- gen -- scenarios -- @ runScenariosWithSpill :: (StateSpace t v e a RealWorld)- => (String -> SpillOptions t a payload)+ => (Text -> SpillOptions t a payload) -> StdGen- -> [(String, v)]- -> IO (M.Map String (a RealWorld))+ -> [(Text, v)]+ -> IO (M.Map Text (a RealWorld)) runScenariosWithSpill buildOpts gen scenarios = do results <- mapConcurrently (\(name, v) -> runSimulationWithSpill (buildOpts name) gen v)@@ -505,74 +517,6 @@ => GUpdatable t v (M1 p l f) s where -- Metadata is ignored gInitialize g t v = M1 <$> gInitialize g t v gUpdate g t v (M1 f) = gUpdate g t v f----------------------------------------------------------------------- * Ripple Effect Analysis----------------------------------------------------------------------- | Generate Identity Matrix-identity :: Int -> IO (IOArray (Int, Int) Double)-identity n = newArray ((1, 1), (n, n)) 0 >>= \arr -> do- forM_ [1..n] $ \i -> writeArray arr (i, i) 1- return arr---- | Calculate inverse matrix with Gauss-Jordan Method-inverse :: IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)-inverse mat = do- bnds <- getBounds mat- -- 'inverse' is only ever called on a 1-indexed square matrix, so the bounds- -- are @((1,1),(n,n))@; matching @((1,1),(n,_))@ is intentionally partial- -- (audited invariant) — a non-1-indexed matrix is a programmer error here.- let ((1,1),(n,_)) = bnds- inv <- identity n-- forM_ [1..n] $ \i -> do- pivot <- readArray mat (i,i)- forM_ [1..n] $ \j -> do- modifyArray mat (i,j) (/pivot)- modifyArray inv (i,j) (/pivot)- forM_ [1..n] $ \k -> when (k /= i) $ do- factor <- readArray mat (k,i)- forM_ [1..n] $ \j -> do- mVal <- readArray mat (i,j)- iVal <- readArray inv (i,j)- modifyArray mat (k,j) (\x -> x - factor * mVal)- modifyArray inv (k,j) (\x -> x - factor * iVal)-- return inv--{- | Calculate Leontief's Inverse Matrix-ex.-main :: IO ()-main = do- mat <- newListArray ((1,1),(2,2)) [0.2, 0.3, 0.4, 0.1]- result <- leontiefInverse mat- putStrLn "Leontief Inverse (ripple effect matrix):"- writeLeontiefInverse "output.csv" result--}--leontiefInverse :: IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)-leontiefInverse a = do- bnds <- getBounds a- temp <- newArray bnds 0- forM_ (range bnds) $ \(i,j) -> do- val <- readArray a (i,j)- writeArray temp (i,j) (if i == j then 1 - val else -val)- inverse temp---- | Calculate the ripple effect of a demand increase in a specific industry using the inverse matrix.--- Returns the ripple effect on each industry when a one-unit demand increase occurs in the specified industry.------ Complexity: O(n) (n = number of industries)-rippleEffect :: Int -> IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)-rippleEffect industry inverseArr = do- ((r1,c1),(r2,c2)) <- getBounds inverseArr- result <- newArray ((r1,c1),(r2,c2)) 0- forM_ [r1..r2] $ \i -> do- val <- readArray inverseArr (i, industry)- writeArray result (i, industry) val- return result- ------------------------------------------------------------------ -- * Random number utilities
src/ExchangeAlgebra/Simulate/Analysis.hs view
@@ -5,21 +5,13 @@ Released under the OWL license - Ripple-effect analysis on input-coefficient matrices: the Leontief- inverse and the truncated ripple sum. Both names are re-exported- unchanged from "ExchangeAlgebra.Simulate"; this module only separates the- analysis utilities from the state-space engine- ("ExchangeAlgebra.Simulate.Engine").-- >>> import Data.Array.IO (newListArray, getElems, IOArray)- >>> a <- newListArray ((1,1),(2,2)) [0,0,0,0] :: IO (IOArray (Int,Int) Double)- >>> leontiefInverse a >>= getElems- [1.0,0.0,0.0,1.0]+ Compatibility entry point for matrix analysis. Import+ "ExchangeAlgebra.Simulation.Analysis" for new code. -}- module ExchangeAlgebra.Simulate.Analysis+ {-# DEPRECATED "Use ExchangeAlgebra.Simulation.Analysis instead." #-} ( leontiefInverse , rippleEffect ) where -import ExchangeAlgebra.Simulate (leontiefInverse, rippleEffect)+import ExchangeAlgebra.Simulation.Analysis (leontiefInverse, rippleEffect)
src/ExchangeAlgebra/Simulate/Engine.hs view
@@ -17,8 +17,13 @@ spill configuration and readers ('ExchangeAlgebra.Simulate.Spill.SpillOptions' and friends) are deliberately not re-exported here: import "ExchangeAlgebra.Simulate.Spill" for them. The ripple-effect utilities live- in "ExchangeAlgebra.Simulate.Analysis" and the random helpers in+ in "ExchangeAlgebra.Simulation.Analysis" and the random helpers in "ExchangeAlgebra.Simulate.Random".++ /Note:/ version 0.7 is planned to remove this module without a+ compatibility module. Its successor is the model evaluator in the+ separate @exchangealgebra-simulation@ package. 0.6 emits no deprecation+ warning because that package has not been released. -} module ExchangeAlgebra.Simulate.Engine
src/ExchangeAlgebra/Simulate/Lite.hs view
@@ -110,6 +110,11 @@ declaratively as a t'LedgerPolicy' ("ExchangeAlgebra.Simulate.Policy") and applied by 'runLiteWithPolicy'. Snapshot-dependent term-boundary recomputation and parallel speedup measurement remain out of scope.++ /Note:/ version 0.7 is planned to remove this module without a+ compatibility module. Its successor is the model evaluator in the+ separate @exchangealgebra-simulation@ package. 0.6 emits no deprecation+ warning because that package has not been released. -} module ExchangeAlgebra.Simulate.Lite@@ -155,6 +160,8 @@ ) where import GHC.Generics+import Data.Text (Text)+import qualified Data.Text as T import Data.Kind (Type) import Control.Monad (foldM, forM_, when) import Control.Monad.ST (ST, runST, RealWorld, stToIO)@@ -370,11 +377,11 @@ -- -- The two constructors are observationally interchangeable for a one-note -- stage: @'stageOf' tag as f@ produces the same messages as--- @'stageFor' (show tag) as (\\v t g a -> f v t g a '.|' (tag, t))@ (asserted by+-- @'stageFor' (T.pack (show tag)) as (\\v t g a -> f v t g a '.|' (tag, t))@ (asserted by -- the @stageOf auto-note@ sentinel). @StageTagged@ only moves the @'.|' (tag, t)@ -- from the stage body into the runner. data Stage w t n v b where- StageFor :: { stName :: String+ StageFor :: { stName :: Text , stAgents :: [a] , stRun :: w SnapT -> t -> StdGen -> a -> Journal n v b } -> Stage w t n v b@@ -386,7 +393,7 @@ -- | Build a stage that runs once per element of the given agent list, each -- agent emitting a t'Journal' directly.-stageFor :: String+stageFor :: Text -> [a] -> (w SnapT -> t -> StdGen -> a -> Journal n v b) -> Stage w t n v b@@ -395,7 +402,7 @@ -- | Build a singleton stage with no per-agent fan-out and no random draw — -- handy for aggregate/bookkeeping steps. Equivalent to -- @'stageFor' name [()] (\\v t _ () -> f v t)@.-stage :: String+stage :: Text -> (w SnapT -> t -> Journal n v b) -> Stage w t n v b stage name f = StageFor name [()] (\v t _g () -> f v t)@@ -425,9 +432,9 @@ -- | The display name of a stage: @stName@ for @StageFor@, @show stTag@ for -- @StageTagged@ (the tag's 'Show' comes from its 'Note' superclass). Use this -- instead of @stName@ when a stage may be either constructor.-stageName :: Stage w t n v b -> String+stageName :: Stage w t n v b -> Text stageName (StageFor nm _ _) = nm-stageName (StageTagged tg _ _) = show tg+stageName (StageTagged tg _ _) = T.pack (show tg) ------------------------------------------------------------------ -- * Simulation specification@@ -588,6 +595,8 @@ -- Forcing one message first materializes the shared structure in a single -- thread, so the sparks only evaluate agent-local work. Pure values: the -- result is unchanged (DET-2 asserts exact equality).+-- Canonical merging folds in fixed agent-index order, so ledgers are equal across+-- chunk widths even for floating-point values (tested by @testLiteDet2Double@). runStage :: forall w t n v b. (HatVal v, HatBaseClass b, Note n) => SimSpec w t n v b
src/ExchangeAlgebra/Simulate/Network.hs view
@@ -1,1229 +1,13 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}--{- |- Module : ExchangeAlgebra.Simulate.Network- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- == What this module is-- A small, additive front-end that separates two concepts that the older- examples conflated into a single dense @N×N@ coefficient matrix:-- 1. the __trade network__, t'TradeNetwork' — /who may trade with whom/, a- sparse directed relation; and- 2. the __input coefficients__, t'InputCoefficients' — /the technology/,- a sparse map of per-edge coefficients @a_{ij}@.-- In the dense-matrix style the support (non-zero cells) of the coefficient- matrix /was/ the trade relation, so sparsity was an accident of the data- representation rather than a modeling choice. Splitting them lets a model- pick its market structure (complete, @k@-regular, Erdős–Rényi, scale-free,- sectoral) independently of the coefficients, and lets the summation-- @- 'sigmaEdges' g f- @-- run the familiar \"Σ\" notation over the /edges/ of @g@ (cost @O(E)@) instead- of over all ordered pairs (cost @O(N²)@). With 'completeNetwork' the two- coincide, so an existing all-pairs model can be ported without changing the- notation (see 'sigmaEdges').-- The module also provides 'industrialNetwork' and 'industrialFlows': an- ordered block-triangular, power-law trade graph and its exact-integer,- demand-driven backward substitution.-- == Edge orientation-- An edge @(i, j)@ means \"@i@ is a /supplier/ of @j@\" (equivalently \"@j@ is a- /buyer/ from @i@\"). The coefficient @a_{ij}@ attached to that edge is \"the- amount of @i@ that one unit of @j@'s output requires\". This matches the- long-form table layout @(from, to, coef)@ and the @(supplier, buyer)@ index- order of the example input-coefficient tables.-- == Determinism-- Every generator is a pure function of an explicit 'StdGen' (it does not- return a generator; split one yourself with 'System.Random.split' if you- need an independent stream). The same seed always yields the same network,- and all read-outs ('nodes', 'edges', 'suppliersOf', 'buyersOf', 'inputsOf')- return their results in ascending 'Ord' order, never in hash-table order.-- == Internal representation is private-- t'TradeNetwork', t'InputCoefficients' and 'NetworkError' are abstract: their- constructors are not exported, so the invariants (out\/in adjacency agree,- @supp(A) ⊆ edges(G)@, no self-loops, non-negative coefficients) cannot be- broken from outside. Build values with the smart constructors and the- generators; read them with the accessors.-- == Using a network with the classic "ExchangeAlgebra.Simulate"-- This module deliberately provides /no/ @Updatable@ instance for the network- types (the @Updatable t v a s | a s -> t v@ functional dependency makes it- impossible for the library to fix the user's @(t, v)@). To carry a (read-only)- network in a classic simulation, wrap it in your own @UpdatableSTRef@ cell:-- @- newtype NetCell s = NetCell (Data.STRef.STRef s (TradeNetwork Int))- instance UpdatableSTRef NetCell s (TradeNetwork Int)- @-- and read it inside an event with @readURef@. In the newer- "ExchangeAlgebra.Simulate.Lite" front-end the network is simply a @carry@- field (it never changes during a run), with no instance at all.--}--module ExchangeAlgebra.Simulate.Network- ( -- * Types (abstract)- TradeNetwork- , InputCoefficients- , NetworkError(..)- -- * Smart constructors- , tradeNetwork- , inputCoefficients- -- * Read-outs (deterministic, ascending order)- , nodes- , edges- , suppliersOf- , buyersOf- , edgeCount- , coefficient- , inputsOf- -- * Summation over edges- , sigmaEdges- -- * Network generators (deterministic)- , completeNetwork- , circulant- , kRegular- , erdosRenyi- , scaleFree- , sectorBlock- -- * Ordered industrial networks and flows- , IndustrialEconomy(..)- , IndustrialOptions(..)- , defaultIndustrialOptions- , industrialNetwork- , industrialNetworkWith- , firms- , industrialEdges- , TaxRate(..)- , taxOf- , IndustrialFlows(..)- , FlowOptions(..)- , defaultFlowOptions- , industrialFlows- , industrialFlowsWith- -- * Coefficient generation- , CoefOptions(..)- , defaultCoefOptions- , randomCoefficients- -- * Long-form table / matrix ingestion- , networkFromTable- , coefficientsFromTable- , fromCoefficientMatrix- -- * CSV (fixed schema, minimal self-contained parser)- , parseEdgeCsv- , parseCoefCsv- , readEdgeCsv- , readCoefCsv- ) where--import Control.DeepSeq (NFData (..))-import Data.List (sortBy)-import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)-import Data.Maybe (fromMaybe)-import qualified Data.Set as S-import Data.Set (Set)-import qualified Data.Text as T-import Data.Text (Text)-import qualified Data.Text.IO as TIO-import qualified Data.Vector as V--- 'mkStdGen' is referenced only by the Haddock doctest examples (which run in--- this module's import scope); 'randomR' drives the generators.-import System.Random (StdGen, mkStdGen, randomR)--import ExchangeAlgebra.Convert.Csv ( splitTrim )-import ExchangeAlgebra.Journal ( Journal, Note, HatVal, HatBaseClass )-import qualified ExchangeAlgebra.Journal as EJ----------------------------------------------------------------------- * Errors----------------------------------------------------------------------- | Why a smart constructor refused to build a value. All cases are reported--- rather than silently repaired (in particular a duplicate edge is /not/ merged--- and a coefficient outside the network is /not/ dropped).-data NetworkError- = SelfLoop -- ^ An edge @(i, i)@ was supplied.- | DuplicateEdge -- ^ The same ordered pair @(i, j)@ appeared twice.- | CoefOutsideNetwork -- ^ A coefficient @(i, j, _)@ has no edge @(i, j)@ in the network.- | NegativeCoefficient -- ^ A coefficient was negative (or a non-finite error value).- | DuplicateCoefficient -- ^ The same @(i, j)@ coefficient appeared twice.- deriving (Eq, Show)--instance NFData NetworkError where- rnf x = x `seq` ()----------------------------------------------------------------------- * TradeNetwork----------------------------------------------------------------------- | A sparse directed trade relation over a fixed node set. Holds both the--- out-adjacency (@supplier ↦ buyers@) and the in-adjacency--- (@buyer ↦ suppliers@) so that 'suppliersOf' and 'buyersOf' are both--- @O(log N + deg)@. The two indices are kept mutually consistent by--- construction. Edges are stored as 'Data.Set.Set' so every read-out is in--- ascending 'Ord' order.-data TradeNetwork k = TradeNetwork- { tnNodes :: !(Set k)- , tnOut :: !(Map k (Set k)) -- ^ supplier ↦ set of buyers- , tnIn :: !(Map k (Set k)) -- ^ buyer ↦ set of suppliers- }--instance Eq k => Eq (TradeNetwork k) where- a == b = tnNodes a == tnNodes b && tnOut a == tnOut b && tnIn a == tnIn b--instance Show k => Show (TradeNetwork k) where- showsPrec d g = showParen (d > 10)- $ showString "TradeNetwork "- . showsPrec 11 (S.toAscList (tnNodes g))- . showString " "- . showsPrec 11 (edges g)--instance NFData k => NFData (TradeNetwork k) where- rnf (TradeNetwork ns o i) = rnf ns `seq` rnf o `seq` rnf i---- | Build a network from a node list and a directed-edge list.------ The node set is the union of the given nodes and every endpoint mentioned in--- the edges (so missing nodes are added rather than rejected). Fails with--- 'SelfLoop' on any @(i, i)@ edge and with 'DuplicateEdge' if the same ordered--- pair appears twice (duplicates are never coalesced — that would silently sum--- relations).------ >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> edges g--- [(1,2),(1,3),(2,3)]--- >>> suppliersOf g 3--- [1,2]--- >>> buyersOf g 1--- [2,3]-tradeNetwork :: Ord k => [k] -> [(k, k)] -> Either NetworkError (TradeNetwork k)-tradeNetwork ns es = do- seen <- foldM' insertEdge S.empty es- let nodeSet = S.unions- [ S.fromList ns- , S.fromList [ i | (i, _) <- es ]- , S.fromList [ j | (_, j) <- es ] ]- Right (buildNetwork nodeSet (S.toList seen))- where- insertEdge acc (i, j)- | i == j = Left SelfLoop- | (i, j) `S.member` acc = Left DuplicateEdge- | otherwise = Right (S.insert (i, j) acc)---- | Assemble both adjacency indices from a validated edge set.-buildNetwork :: Ord k => Set k -> [(k, k)] -> TradeNetwork k-buildNetwork nodeSet es = TradeNetwork nodeSet outM inM- where- outM = foldl' (\m (i, j) -> M.insertWith S.union i (S.singleton j) m) M.empty es- inM = foldl' (\m (i, j) -> M.insertWith S.union j (S.singleton i) m) M.empty es---- | The node set, ascending.------ >>> let Right g = tradeNetwork [3,1,2] [] :: Either NetworkError (TradeNetwork Int)--- >>> nodes g--- [1,2,3]-nodes :: TradeNetwork k -> [k]-nodes = S.toAscList . tnNodes---- | The edges @(supplier, buyer)@, ascending.------ >>> let Right g = tradeNetwork [1,2] [(2,1),(1,2)] :: Either NetworkError (TradeNetwork Int)--- >>> edges g--- [(1,2),(2,1)]-edges :: TradeNetwork k -> [(k, k)]-edges g = [ (i, j) | (i, js) <- M.toAscList (tnOut g), j <- S.toAscList js ]---- | The suppliers of a given buyer, ascending.------ >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> suppliersOf g 3--- [1,2]-suppliersOf :: Ord k => TradeNetwork k -> k -> [k]-suppliersOf g j = S.toAscList (M.findWithDefault S.empty j (tnIn g))---- | The buyers from a given supplier, ascending.------ >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3)] :: Either NetworkError (TradeNetwork Int)--- >>> buyersOf g 1--- [2,3]-buyersOf :: Ord k => TradeNetwork k -> k -> [k]-buyersOf g i = S.toAscList (M.findWithDefault S.empty i (tnOut g))---- | The number of edges.------ >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> edgeCount g--- 3-edgeCount :: TradeNetwork k -> Int-edgeCount = M.foldr' (\s acc -> S.size s + acc) 0 . tnOut----------------------------------------------------------------------- * InputCoefficients----------------------------------------------------------------------- | A sparse, buyer-major table of input coefficients: @buyer ↦ (supplier ↦--- coefficient)@. The buyer-major layout matches the dominant access pattern--- (enumerate a fixed buyer's suppliers) and makes the per-buyer column sum (the--- Hawkins–Simon productivity check) an @O(deg)@ scan. The invariant--- @supp(A) ⊆ edges(G)@ is enforced by 'inputCoefficients'.-data InputCoefficients k v = InputCoefficients- { icByBuyer :: !(Map k (Map k v)) -- ^ buyer ↦ supplier ↦ coefficient- }--instance (Eq k, Eq v) => Eq (InputCoefficients k v) where- a == b = icByBuyer a == icByBuyer b--instance (Show k, Show v) => Show (InputCoefficients k v) where- showsPrec d a = showParen (d > 10)- $ showString "InputCoefficients "- . showsPrec 11- [ (i, j, v)- | (j, sup) <- M.toAscList (icByBuyer a)- , (i, v) <- M.toAscList sup ]--instance (NFData k, NFData v) => NFData (InputCoefficients k v) where- rnf (InputCoefficients m) = rnf m---- | Build the coefficient table from a network and a long-form list of--- @(supplier, buyer, coef)@ triples.------ Fails with 'CoefOutsideNetwork' if a triple has no corresponding edge,--- 'NegativeCoefficient' if a value is negative or a non-finite error value, and--- 'DuplicateCoefficient' if the same @(supplier, buyer)@ pair appears twice--- (duplicates are not summed — the redundant-algebra convention forbids silent--- aggregation). Zero coefficients are kept as written (they do not create an--- edge, but if the edge exists they are recorded as @0@).------ >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> let Right a = inputCoefficients g [(1,3,0.2),(2,3,0.5)] :: Either NetworkError (InputCoefficients Int Double)--- >>> inputsOf a 3--- [(1,0.2),(2,0.5)]--- >>> coefficient a 1 3--- Just 0.2-inputCoefficients :: (Ord k, HatVal v)- => TradeNetwork k- -> [(k, k, v)]- -> Either NetworkError (InputCoefficients k v)-inputCoefficients g triples = do- m <- foldM' step M.empty triples- Right (InputCoefficients m)- where- edgeSet = S.fromList (edges g)- step acc (i, j, v)- | isBadValue v = Left NegativeCoefficient- | not ((i, j) `S.member` edgeSet) = Left CoefOutsideNetwork- | hasCoef j i acc = Left DuplicateCoefficient- | otherwise = Right (M.insertWith M.union j (M.singleton i v) acc)- hasCoef j i acc = maybe False (M.member i) (M.lookup j acc)---- | A value that must not enter a coefficient table: negative, or a non-finite--- error value (NaN\/Inf). Mirrors the non-negativity invariant enforced by the--- algebra's @(.\@)@ smart constructor.-isBadValue :: HatVal v => v -> Bool-isBadValue v = v < 0 || EJ.isErrorValue v---- | Look up the coefficient on edge @(supplier, buyer)@, if any.------ >>> let Right g = tradeNetwork [1,2,3] [(1,3)] :: Either NetworkError (TradeNetwork Int)--- >>> let Right a = inputCoefficients g [(1,3,0.7)] :: Either NetworkError (InputCoefficients Int Double)--- >>> coefficient a 1 3--- Just 0.7--- >>> coefficient a 2 3--- Nothing-coefficient :: Ord k => InputCoefficients k v -> k -> k -> Maybe v-coefficient a i j = M.lookup j (icByBuyer a) >>= M.lookup i---- | The @(supplier, coefficient)@ inputs of a fixed buyer, ascending by--- supplier.------ >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> let Right a = inputCoefficients g [(2,3,0.5),(1,3,0.2)] :: Either NetworkError (InputCoefficients Int Double)--- >>> inputsOf a 3--- [(1,0.2),(2,0.5)]-inputsOf :: Ord k => InputCoefficients k v -> k -> [(k, v)]-inputsOf a j = M.toAscList (M.findWithDefault M.empty j (icByBuyer a))----------------------------------------------------------------------- * Summation over edges----------------------------------------------------------------------- | Sum a per-edge journal builder over the edges of a network. This is the--- network analogue of an all-pairs @Σ@: the notation stays \"Σ over the--- relation\", but the set it runs over is the @O(E)@ edge list rather than the--- @O(N²)@ ordered pairs.------ @f i j@ is the journal contributed by the edge @(i, j)@ (supplier @i@, buyer--- @j@). Edges are visited in ascending order, so for an exact value type the--- result is order-independent and for 'Double' it is at least deterministic.------ With 'completeNetwork' this is exactly the all-pairs sum over distinct--- ordered pairs, i.e.------ @'sigmaEdges' ('completeNetwork' ks) f == 'EJ.sigma2When' ks ks (/=) f@------ so an all-pairs model ports to a sparse one by swapping the network, leaving--- the @Σ@ call site unchanged.------ >>> import ExchangeAlgebra.Journal--- >>> type J = Journal (Int,Int) Double (HatBase CountUnit)--- >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3)] :: Either NetworkError (TradeNetwork Int)--- >>> let f i j = (1.0 .@ Not:<Amount) .| (i,j) :: J--- >>> norm (sigmaEdges g f)--- 2.0-sigmaEdges :: (Note n, HatVal v, HatBaseClass b)- => TradeNetwork k- -> (k -> k -> Journal n v b)- -> Journal n v b-sigmaEdges g f = EJ.sigma (edges g) (\(i, j) -> f i j)----------------------------------------------------------------------- * Network generators----------------------------------------------------------------------- | The complete directed network: every distinct ordered pair @(i, j)@,--- @i /= j@, is an edge. @O(N²)@ edges — provided so an existing all-pairs model--- can be expressed without changing its @Σ@ (see 'sigmaEdges').------ >>> let g = completeNetwork [1,2,3] :: TradeNetwork Int--- >>> edgeCount g--- 6--- >>> edges g--- [(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]-completeNetwork :: Ord k => [k] -> TradeNetwork k-completeNetwork ks =- buildNetwork nodeSet [ (i, j) | i <- xs, j <- xs, i /= j ]- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet---- | A deterministic circulant (ring-lattice) network: the nodes are taken in--- ascending order and each buyer draws its @min k (N-1)@ suppliers from the @k@--- nodes that follow it cyclically (@j+1, …, j+k@ mod @N@). Unlike 'kRegular' \/--- 'erdosRenyi' it needs no 'StdGen' and is built in @O(kN)@ — it never scans the--- @O(N²)@ ordered pairs — so it stays usable at the @N@ a market-scale run needs.--- @|E| = min k (N-1) · N@ exactly, with no duplicate and no self edges.------ >>> let g = circulant [1..6] 2 :: TradeNetwork Int--- >>> edgeCount g--- 12--- >>> suppliersOf g 1--- [2,3]--- >>> suppliersOf g 6--- [1,2]--- >>> all (\j -> length (suppliersOf g j) == 2) (nodes g)--- True-circulant :: Ord k => [k] -> Int -> TradeNetwork k-circulant ks k =- buildNetwork nodeSet (concat [ zip (rot d xs) xs | d <- [1 .. deg] ])- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet- n = length xs- deg = max 0 (min k (n - 1))- rot d ys = drop d ys ++ take d ys---- | A @k@-regular-in network: each buyer draws @min k (N-1)@ distinct suppliers--- (sampling without replacement, excluding itself). Deterministic in the given--- 'StdGen'.------ >>> let g = kRegular (mkStdGen 1) [1..6] 2 :: TradeNetwork Int--- >>> all (\j -> length (suppliersOf g j) == 2) (nodes g)--- True-kRegular :: Ord k => StdGen -> [k] -> Int -> TradeNetwork k-kRegular gen ks k =- buildNetwork nodeSet (concat (snd (foldl' pick (gen, []) xs)))- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet- deg = max 0 (min k (length xs - 1))- pick (g0, acc) j =- let candidates = filter (/= j) xs- (chosen, g1) = sampleWithout g0 deg candidates- in (g1, [ (i, j) | i <- chosen ] : acc)---- | An Erdős–Rényi @G(n, p)@ directed network: each ordered pair @(i, j)@,--- @i /= j@, becomes an edge independently with probability @p@. @p <= 0@ yields--- the empty network, @p >= 1@ yields 'completeNetwork'. Deterministic in the--- given 'StdGen'.------ >>> edgeCount (erdosRenyi (mkStdGen 0) [1..5] 1.0 :: TradeNetwork Int)--- 20--- >>> edgeCount (erdosRenyi (mkStdGen 0) [1..5] 0.0 :: TradeNetwork Int)--- 0-erdosRenyi :: Ord k => StdGen -> [k] -> Double -> TradeNetwork k-erdosRenyi gen ks p- | p >= 1 = completeNetwork ks- | p <= 0 = buildNetwork nodeSet []- | otherwise = buildNetwork nodeSet (snd (foldl' step (gen, []) pairs))- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet- pairs = [ (i, j) | i <- xs, j <- xs, i /= j ]- step (g0, acc) e =- let (u, g1) = randomR (0, 1) g0 :: (Double, StdGen)- in if u < p then (g1, e : acc) else (g1, acc)---- | A scale-free network grown by Barabási–Albert preferential attachment: nodes--- are added in ascending order; each new node attaches @m@ edges to existing--- nodes chosen with probability proportional to their current degree (with a--- uniform fallback while the graph is still empty). Orientation: the new node is--- the /buyer/, the chosen existing nodes are its /suppliers/. Deterministic in--- the given 'StdGen'.------ The first @m@ nodes form a seed clique-ish core (each new seed node attaches--- to all already-present nodes), so for @N > m@ the edge count is--- @C(m+1, 2)·1 + (N - m - 1)·m@ counting the directed buyer→supplier edges as--- one per attachment; the property test checks the exact value.------ >>> let g = scaleFree (mkStdGen 7) [1..10] 2 :: TradeNetwork Int--- >>> edgeCount g--- 17-scaleFree :: Ord k => StdGen -> [k] -> Int -> TradeNetwork k-scaleFree gen ks m0 =- buildNetwork nodeSet builtEdges- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet- m = max 1 m0- -- State threads the generator, the edge accumulator and an incremental- -- degree table (@node ↦ current degree@). The degree table replaces the- -- O(E) rescan that the naive @degree i = length (filter …) acc@ performed- -- for every candidate at every node: each added edge bumps both endpoints'- -- degrees in O(log N), so the per-node weight list is built without ever- -- touching @acc@. The weights produced are bit-for-bit identical to the- -- rescan version, so the generated network is unchanged.- (_, builtEdges0, _) = foldl' addNode (gen, [], M.empty) (zip [0 ..] xs)- builtEdges = reverse builtEdges0-- -- addNode :: (StdGen, [(k,k)], Map k Int)- -- -> (Int, k) -> (StdGen, [(k,k)], Map k Int)- addNode st@(g0, acc, deg) (ix, j)- | ix == 0 = st -- first node: nothing to attach to- | ix <= m = -- seed phase: attach to all earlier nodes- let suppliers = take ix xs- newEdges = [ (i, j) | i <- suppliers ]- in (g0, newEdges ++ acc, bumpEdges newEdges deg)- | otherwise = -- preferential attachment- let present = take ix xs -- nodes already added- degree i = M.findWithDefault 0 i deg- weighted = [ (i, fromIntegral (1 + degree i) :: Double) | i <- present ]- (chosen, g1) = sampleWeightedWithout g0 m weighted- newEdges = [ (i, j) | i <- chosen ]- in (g1, newEdges ++ acc, bumpEdges newEdges deg)-- -- Increment both endpoints' degree counts for each newly added edge.- bumpEdges es d = foldl' (\d' (i, t) -> bump i (bump t d')) d es- bump k = M.insertWith (+) k 1---- | A stochastic block network: each node carries a sector label, and an--- ordered pair @(i, j)@ (@i /= j@) becomes an edge with probability--- @p (sector i, sector j)@. Deterministic in the given 'StdGen'. Generalizes--- 'erdosRenyi' (a single block) and lets intra-\/inter-sector densities differ.------ >>> let label n = if n <= 2 then 'A' else 'B'--- >>> let p (a,b) = if a == b then 1.0 else 0.0--- >>> let g = sectorBlock (mkStdGen 0) [(n, label n) | n <- [1..4]] p :: TradeNetwork Int--- >>> edges g--- [(1,2),(2,1),(3,4),(4,3)]-sectorBlock :: (Ord k, Ord s)- => StdGen -> [(k, s)] -> ((s, s) -> Double) -> TradeNetwork k-sectorBlock gen labelled p =- buildNetwork nodeSet (snd (foldl' step (gen, []) pairs))- where- secMap = M.fromList labelled- nodeSet = S.fromList (map fst labelled)- xs = S.toAscList nodeSet- pairs = [ (i, j) | i <- xs, j <- xs, i /= j ]- step (g0, acc) (i, j) =- let pr = fromMaybe 0 $ do- si <- M.lookup i secMap- sj <- M.lookup j secMap- pure (p (si, sj))- (u, g1) = randomR (0, 1) g0 :: (Double, StdGen)- in if pr >= 1 then (g1, (i, j) : acc)- else if pr <= 0 then (g1, acc)- else if u < pr then (g1, (i, j) : acc) else (g1, acc)----------------------------------------------------------------------- * Ordered industrial networks and demand-driven flows----------------------------------------------------------------------- | A block-triangular industrial economy. Sector @0@ is the most upstream--- sector and larger sector numbers are progressively downstream. Every edge is--- @(supplier, buyer)@; generated edges satisfy @sector supplier <= sector buyer@,--- and an intra-sector edge additionally satisfies @supplier < buyer@.-data IndustrialEconomy k = IndustrialEconomy- { ieNetwork :: !(TradeNetwork k)- , ieSector :: !(Map k Int)- , ieSize :: !(Map k Double)- } deriving (Eq, Show)--instance NFData k => NFData (IndustrialEconomy k) where- rnf (IndustrialEconomy g s w) = rnf g `seq` rnf s `seq` rnf w---- | Options for 'industrialNetworkWith'. The flow function is consulted only--- for ordered sector pairs @(upstream, downstream)@. Non-positive and--- non-finite values make that sector pair ineligible.-data IndustrialOptions = IndustrialOptions- { ioExponent :: !Double- -- ^ Pareto exponent @gamma > 1@.- , ioFlow :: Int -> Int -> Double- -- ^ Sector-flow weight @B[s,s']@.- }---- | Pareto exponent @2.5@ and a uniform positive sector-flow matrix.-defaultIndustrialOptions :: IndustrialOptions-defaultIndustrialOptions = IndustrialOptions- { ioExponent = 2.5- , ioFlow = \_ _ -> 1- }---- | Build the paper's deterministic block-triangular, power-law industrial--- network from @seed N K m@. Unlike 'sectorBlock', this generator is--- deterministic from an integer seed, ordered by sector, power-law weighted,--- and avoids an all-pairs scan: expected construction cost is--- @O(N*K + |E|*(K + log N))@, or @O(N*K + |E|*log N)@ for fixed @K@.------ The requested edge count is @m*N@. It is exact whenever the eligible--- supplier capacity is at least that large; otherwise all eligible pairs are--- used and @|E| < m*N@. @N <= 0@ produces an empty economy, @m <= 0@ produces--- no edges, and @K <= 0@ or an exponent not greater than @1@ is an error.------ >>> let e = industrialNetwork 7 6 2 1--- >>> firms e--- [1,2,3,4,5,6]--- >>> all (\(i,j) -> let s = ieSector e in s M.! i < s M.! j || (s M.! i == s M.! j && i < j)) (industrialEdges e)--- True-industrialNetwork :: Int -> Int -> Int -> Int -> IndustrialEconomy Int-industrialNetwork = industrialNetworkWith defaultIndustrialOptions---- | Configurable form of 'industrialNetwork'. Supplier selection is weighted--- by @B[sector i,sector j] * w_i@ without replacement. It first chooses a--- sector in @O(K)@ and then a firm by binary search over that sector's--- cumulative-size vector in @O(log N)@. Duplicate draws use bounded rejection;--- the deterministic fallback fills the remaining eligible candidates in--- sector/id order. Sector choice is @O(K)@ per draw; @K@ is normally a small--- fixed model parameter.-industrialNetworkWith- :: IndustrialOptions -> Int -> Int -> Int -> Int -> IndustrialEconomy Int-industrialNetworkWith opts seed n0 k m0- | k <= 0 = error "industrialNetworkWith: K must be positive"- | not (finitePositive gamma) || gamma <= 1 =- error "industrialNetworkWith: ioExponent must be finite and greater than 1"- | n <= 0 = IndustrialEconomy (buildNetwork S.empty []) M.empty M.empty- | otherwise = IndustrialEconomy network sectors sizes- where- n = max 0 n0- gamma = ioExponent opts- firmIds = [1 .. n]- (_, sectors, sizes, sectorRev) = foldl' drawFirm (mkStdGen seed, M.empty, M.empty, M.empty) firmIds- sectorLists = M.map reverse sectorRev- pools = M.fromList- [ (s, mkSectorPool (M.findWithDefault [] s sectorLists) sizes)- | s <- [0 .. k - 1] ]- ranks = M.fromList- [ (i, r)- | (_, pool) <- M.toAscList pools- , (r, i) <- zip [0 ..] (V.toList (spFirms pool)) ]- capacities =- [ (j, candidateCapacity opts sectors pools ranks j)- | j <- firmIds ]- capacityTotal = sum (map snd capacities)- requestedInteger = toInteger (max 0 m0) * toInteger n- target = fromInteger (min requestedInteger (toInteger capacityTotal))- degrees = apportionCapped target- [ (j, cap, M.findWithDefault 1 j sizes) | (j, cap) <- capacities ]- (_, edgeChunks) = foldl' drawBuyer (mkStdGen (seed + 104729), []) firmIds- network = buildNetwork (S.fromList firmIds) (concat (reverse edgeChunks))-- drawFirm (g0, sm, wm, groups) i =- let (s, g1) = randomR (0, k - 1) g0- (u0, g2) = randomR (0, 1) g1 :: (Double, StdGen)- -- randomR's interval is closed; cap its upper endpoint so Pareto- -- inversion remains finite while retaining the seed-derived draw.- u = min (1 - 2.220446049250313e-16) (max 0 u0)- logWeight = - log (1 - u) / (gamma - 1)- -- The mathematical Pareto draw can exceed Double's range as gamma- -- approaches 1. Saturate only that unrepresentable tail, keeping- -- ieSize and every sampling table finite and consistent.- w = exp (min (log maxIndustrialSize) logWeight)- in ( g2- , M.insert i s sm- , M.insert i w wm- , M.insertWith (++) s [i] groups )-- drawBuyer (g0, acc) j =- let d = M.findWithDefault 0 j degrees- (chosen, g1) = chooseIndustrialSuppliers opts sectors pools ranks j d g0- in (g1, [ (i, j) | i <- chosen ] : acc)---- | Firms in ascending order, equivalent to @nodes . ieNetwork@.-firms :: IndustrialEconomy k -> [k]-firms = nodes . ieNetwork---- | Industrial edges in ascending @(supplier,buyer)@ order, equivalent to--- @edges . ieNetwork@.-industrialEdges :: IndustrialEconomy k -> [(k, k)]-industrialEdges = edges . ieNetwork---- | An exact rational tax rate @numerator / denominator@.-data TaxRate = TaxRate- { taxNumerator :: !Integer- , taxDenominator :: !Integer- } deriving (Eq, Show)--instance NFData TaxRate where- rnf (TaxRate num den) = rnf num `seq` rnf den---- | Integer tax on an integer amount. Generated industrial flows are aligned--- to the denominator, so this division is exact for their amounts.-taxOf :: TaxRate -> Integer -> Integer-taxOf (TaxRate num den) amount- | den <= 0 = error "taxOf: denominator must be positive"- | num < 0 = error "taxOf: numerator must be non-negative"- | amount < 0 = error "taxOf: amount must be non-negative"- | otherwise = amount * num `div` den---- | One-period demand-driven monetary flows for an industrial economy.-data IndustrialFlows k = IndustrialFlows- { flowTrade :: !(Map (k, k) Integer)- , flowOutput :: !(Map k Integer)- , flowInput :: !(Map k Integer)- , flowValueAdded :: !(Map k Integer)- , flowFinalDemand :: !(Map k Integer)- } deriving (Eq, Show)--instance NFData k => NFData (IndustrialFlows k) where- rnf (IndustrialFlows z x inp va f) =- rnf z `seq` rnf x `seq` rnf inp `seq` rnf va `seq` rnf f---- | Options for the demand-driven backward substitution.-data FlowOptions = FlowOptions- { foMeanFinalDemand :: !Integer- , foInputShare :: !Double- } deriving (Eq, Show)--instance NFData FlowOptions where- rnf (FlowOptions f a) = rnf f `seq` rnf a---- | Mean final demand @1,000,000@ yen and intermediate-input share @0.5@.-defaultFlowOptions :: FlowOptions-defaultFlowOptions = FlowOptions- { foMeanFinalDemand = 1000000- , foInputShare = 0.5- }---- | Generate one-period flows with 'defaultFlowOptions'.-industrialFlows :: Ord k- => TaxRate -> IndustrialEconomy k -> IndustrialFlows k-industrialFlows = industrialFlowsWith defaultFlowOptions---- | Generate exact integer flows by a single downstream-to-upstream backward--- substitution. Final demand and every trade amount are positive-denominator--- multiples. Trade amounts may be zero when a buyer's input units are fewer--- than its suppliers. For an economy produced by 'industrialNetworkWith', the--- identities @x_j = sum_i z_ij + v_j = sum_m z_jm + f_j@ and--- @sum_j v_j = sum_j f_j@ hold exactly. Complexity is--- @O(N*log N + |E|*log N)@ with ordered 'Map' updates.-industrialFlowsWith- :: Ord k- => FlowOptions -> TaxRate -> IndustrialEconomy k -> IndustrialFlows k-industrialFlowsWith opts (TaxRate num den) economy- | den <= 0 = error "industrialFlowsWith: tax denominator must be positive"- | num < 0 = error "industrialFlowsWith: tax numerator must be non-negative"- | not (a >= 0 && a < 1) || isNaN a || isInfinite a =- error "industrialFlowsWith: foInputShare must be finite and in [0,1)"- | any (not . validOrderedEdge) (industrialEdges economy) =- error "industrialFlowsWith: economy contains an edge outside the ordered sector DAG"- | otherwise = IndustrialFlows zMap xMap inputMap vaMap finalMap- where- a = foInputShare opts- validOrderedEdge (i, j) =- case (M.lookup i (ieSector economy), M.lookup j (ieSector economy)) of- (Just si, Just sj) -> (si, i) < (sj, j)- _ -> False- ks = firms economy- count = length ks- sizeOf j = let w = M.findWithDefault 1 j (ieSize economy)- in if finitePositive w then w else 1- meanSize = if count == 0- then 1- else sum (map sizeOf ks) / fromIntegral count- meanFinal = max 0 (foMeanFinalDemand opts)- finalMap = M.fromList- [ (j, den * max 1 (round (fromIntegral meanFinal * sizeOf j- / meanSize / fromIntegral den)))- | j <- ks ]- order = sortBy downstreamFirst ks- downstreamFirst i j =- compare (M.findWithDefault 0 j (ieSector economy), j)- (M.findWithDefault 0 i (ieSector economy), i)- (_, zMap, xMap, inputMap, vaMap) =- foldl' solveFirm (M.empty, M.empty, M.empty, M.empty, M.empty) order-- solveFirm (orders, zs, xs, ins, vas) j =- let revenue = M.findWithDefault 0 j orders- finalD = M.findWithDefault den j finalMap- output = revenue + finalD- suppliers = suppliersOf (ieNetwork economy) j- input- | null suppliers = 0- | otherwise = den * floor (a * fromIntegral output / fromIntegral den)- units = input `div` den- allocations = apportionInteger units [ (i, sizeOf i) | i <- suppliers ]- zs' = foldl' (\m i -> M.insert (i, j) (den * M.findWithDefault 0 i allocations) m)- zs suppliers- orders' = foldl'- (\m i -> M.insertWith (+) i (den * M.findWithDefault 0 i allocations) m)- orders suppliers- valueAdded = output - input- in ( orders'- , zs'- , M.insert j output xs- , M.insert j input ins- , M.insert j valueAdded vas )---- | Per-sector cumulative weights used by two-level supplier sampling.-data SectorPool = SectorPool- { spFirms :: !(V.Vector Int)- , spCumulative :: !(V.Vector Double)- }--mkSectorPool :: [Int] -> Map Int Double -> SectorPool-mkSectorPool ids weights = SectorPool firmVector cumulative- where- firmVector = V.fromList ids- cumulative = V.fromList (drop 1 (scanl (+) 0 [ M.findWithDefault 1 i weights | i <- ids ]))--candidateCapacity- :: IndustrialOptions- -> Map Int Int- -> Map Int SectorPool- -> Map Int Int- -> Int- -> Int-candidateCapacity opts sectors pools ranks j =- sum [ eligibleCount s | s <- [0 .. buyerSector] ]- where- buyerSector = M.findWithDefault 0 j sectors- buyerRank = M.findWithDefault 0 j ranks- eligibleCount s- | not (finitePositive (ioFlow opts s buyerSector)) = 0- | s == buyerSector = buyerRank- | otherwise = maybe 0 (V.length . spFirms) (M.lookup s pools)--chooseIndustrialSuppliers- :: IndustrialOptions- -> Map Int Int- -> Map Int SectorPool- -> Map Int Int- -> Int- -> Int- -> StdGen- -> ([Int], StdGen)-chooseIndustrialSuppliers opts sectors pools ranks buyer wanted g0 =- go g0 S.empty [] 0- where- buyerSector = M.findWithDefault 0 buyer sectors- buyerRank = M.findWithDefault 0 buyer ranks- attemptLimit = max 64 (wanted * 32)- sectorChoices =- [ ((s, pool, limit), ioFlow opts s buyerSector * prefixWeight pool limit)- | s <- [0 .. buyerSector]- , finitePositive (ioFlow opts s buyerSector)- , Just pool <- [M.lookup s pools]- , let limit = if s == buyerSector then buyerRank else V.length (spFirms pool)- , limit > 0- , finitePositive (prefixWeight pool limit) ]-- go g selected acc attempts- | S.size selected >= wanted = (reverse acc, g)- | attempts >= attemptLimit =- let remaining = take (wanted - S.size selected)- [ i- | ((_, pool, limit), _) <- sectorChoices- , i <- V.toList (V.take limit (spFirms pool))- , i `S.notMember` selected ]- in (reverse acc ++ remaining, g)- | otherwise =- let ((_, pool, limit), g1) = weightedChoice g sectorChoices- total = prefixWeight pool limit- (u, g2) = randomR (0, total) g1 :: (Double, StdGen)- ix = cumulativeLowerBound (spCumulative pool) limit u- supplier = spFirms pool V.! ix- in if supplier `S.member` selected- then go g2 selected acc (attempts + 1)- else go g2 (S.insert supplier selected) (supplier : acc) (attempts + 1)--prefixWeight :: SectorPool -> Int -> Double-prefixWeight _ limit | limit <= 0 = 0-prefixWeight pool limit = spCumulative pool V.! (limit - 1)--cumulativeLowerBound :: V.Vector Double -> Int -> Double -> Int-cumulativeLowerBound cumulative limit target = go 0 (limit - 1)- where- go lo hi- | lo >= hi = lo- | cumulative V.! mid >= target = go lo mid- | otherwise = go (mid + 1) hi- where mid = (lo + hi) `div` 2--weightedChoice :: StdGen -> [(a, Double)] -> (a, StdGen)-weightedChoice _ [] = error "weightedChoice: empty positive-weight population"-weightedChoice g choices = (pick u choices, g1)- where- total = sum (map snd choices)- (u, g1) = randomR (0, total) g :: (Double, StdGen)- pick _ [(x, _)] = x- pick r ((x, w) : rest)- | r <= w = x- | otherwise = pick (r - w) rest- pick _ [] = error "weightedChoice: unreachable"--finitePositive :: Double -> Bool-finitePositive x = x > 0 && not (isNaN x) && not (isInfinite x)---- | Numerical ceiling for the unrepresentable far tail of a Pareto draw. It is--- far above any economically meaningful relative size while leaving--- market-scale sector sums finite.-maxIndustrialSize :: Double-maxIndustrialSize = 1e100---- | Largest-remainder allocation with per-recipient caps. Continuous weighted--- water-filling finds the cap-saturation threshold in one sorted pass, then a--- largest-remainder step integerizes the result. @O(N log N)@.-apportionCapped :: Int -> [(Int, Int, Double)] -> Map Int Int-apportionCapped requested rows- | target <= 0 = M.fromList [ (key, 0) | (key, _, _) <- rows ]- | remainder > length ranked =- error "apportionCapped: numerical instability in largest-remainder allocation"- | otherwise = foldl' addRemainder bases (take remainder ranked)- where- normalised =- [ (key, max 0 cap, if finitePositive weight then weight else 1)- | (key, cap, weight) <- rows ]- target = min (max 0 requested) (sum [ cap | (_, cap, _) <- normalised ])- active = sortBy compareThreshold [ row | row@(_, cap, _) <- normalised, cap > 0 ]- lambda = waterLevel target (sum [ weight | (_, _, weight) <- active ]) active- quotas =- [ (key, cap, min (fromIntegral cap) (lambda * weight))- | (key, cap, weight) <- normalised ]- floors =- [ (key, cap, floor quota, quota - fromIntegral (floor quota :: Int))- | (key, cap, quota) <- quotas ]- bases = M.fromList [ (key, base) | (key, _, base, _) <- floors ]- remainder = max 0 (target - sum [ base | (_, _, base, _) <- floors ])- ranked = map (\(key, _, _) -> key) $ sortBy compareRemainder- [ (key, cap, frac) | (key, cap, base, frac) <- floors, base < cap ]- compareThreshold (keyA, capA, weightA) (keyB, capB, weightB) =- compare (fromIntegral capA / weightA) (fromIntegral capB / weightB)- <> compare keyA keyB- compareRemainder (keyA, _, fracA) (keyB, _, fracB) =- compare fracB fracA <> compare keyA keyB- addRemainder m key = M.insertWith (+) key 1 m- waterLevel amount weightTotal candidates = case candidates of- [] -> 0- (_, cap, weight) : rest- | weightTotal <= 0 -> 0- | level <= fromIntegral cap / weight -> level- | otherwise -> waterLevel (amount - cap) (weightTotal - weight) rest- where level = fromIntegral amount / weightTotal--apportionInteger :: Ord k => Integer -> [(k, Double)] -> Map k Integer-apportionInteger amount rows- | amount <= 0 || null rows = M.fromList [ (key, 0) | (key, _) <- rows ]- | otherwise = foldl' addRemainder bases (take (fromInteger remainder) ranked)- where- positiveRows = [ (key, if finitePositive weight then weight else 1) | (key, weight) <- rows ]- total = sum (map snd positiveRows)- quotas = [ (key, fromIntegral amount * weight / total) | (key, weight) <- positiveRows ]- floors = [ (key, floor quota, quota - fromIntegral (floor quota :: Integer)) | (key, quota) <- quotas ]- bases = M.fromList [ (key, base) | (key, base, _) <- floors ]- remainder = max 0 (amount - sum [ base | (_, base, _) <- floors ])- ranked = map (\(key, _, _) -> key) $ sortBy compareRemainder floors- compareRemainder (keyA, _, fracA) (keyB, _, fracB) =- compare fracB fracA <> compare keyA keyB- addRemainder m key = M.insertWith (+) key 1 m----------------------------------------------------------------------- * Coefficient generation----------------------------------------------------------------------- | Options for 'randomCoefficients'.-data CoefOptions = CoefOptions- { coefRange :: !(Double, Double)- -- ^ Inclusive @(lo, hi)@ range each raw coefficient is drawn from.- , hawkinsSimon :: !Bool- -- ^ When 'True', each buyer's column (its suppliers' coefficients) is- -- kept strictly below a column sum of @1@, guaranteeing productivity (a- -- sufficient Hawkins–Simon condition). The rescaling is __shrink-only__:- -- a column whose raw sum is already @< 1@ is left untouched, and only a- -- column whose raw sum is @>= 1@ is scaled down (to @0.95@ of its sum).- -- This preserves the heterogeneity of the raw draws instead of forcing- -- every column to one common sum. When 'False' the raw draws are used- -- as-is.- } deriving (Eq, Show)--instance NFData CoefOptions where- rnf (CoefOptions r h) = rnf r `seq` rnf h---- | @(0, 1)@ range with the Hawkins–Simon rescaling on.-defaultCoefOptions :: CoefOptions-defaultCoefOptions = CoefOptions { coefRange = (0, 1), hawkinsSimon = True }---- | Draw a coefficient for every edge of a network, deterministically from the--- given 'StdGen'. Values are drawn in 'Double' and converted via 'realToFrac'--- (so the value type @v@ does not need a @Random@ instance). With--- 'hawkinsSimon' on, the rescaling is __shrink-only__: a column whose raw sum--- is already strictly below @1@ is kept as drawn, and only a column whose raw--- sum reaches @1@ is scaled down to @0.95@ of that sum. Either way every column--- sum ends strictly below @1@, a sufficient condition for the Leontief system--- to be productive, while the natural spread of the raw draws is preserved--- (columns are /not/ all forced to one common sum).------ The result satisfies @supp(A) = edges(G)@ by construction, so it always--- round-trips back through 'inputCoefficients' without error.------ >>> let g = completeNetwork [1,2,3] :: TradeNetwork Int--- >>> let a = randomCoefficients (mkStdGen 5) defaultCoefOptions g :: InputCoefficients Int Double--- >>> all (\j -> sum (map snd (inputsOf a j)) < 1.0) (nodes g)--- True-randomCoefficients :: forall k v. (Ord k, HatVal v)- => StdGen -> CoefOptions -> TradeNetwork k -> InputCoefficients k v-randomCoefficients gen opts g =- InputCoefficients (normalise (snd (foldl' drawCol (gen, M.empty) buyers)))- where- (lo, hi) = coefRange opts- buyers = nodes g- -- one column per buyer: draw a raw coefficient per supplier- drawCol (g0, acc) j =- let sup = suppliersOf g j- (row, g1) = foldl' drawOne ([], g0) sup- in (g1, if null row then acc else M.insert j (M.fromList row) acc)- drawOne (row, g0) i =- let (u, g1) = randomR (lo, hi) g0 :: (Double, StdGen)- in ((i, u) : row, g1)-- normalise :: Map k (Map k Double) -> Map k (Map k v)- normalise- | hawkinsSimon opts = M.map rescaleCol- | otherwise = M.map (M.map realToFrac)- -- Shrink-only Hawkins–Simon: leave a productive column (raw sum < 1)- -- untouched, and only scale a column down when its raw sum reaches 1.- rescaleCol col =- let total = sum (M.elems col)- in if total < 1- then M.map realToFrac col- else M.map (\x -> realToFrac (x / total * target)) col- target = 0.95 :: Double----------------------------------------------------------------------- * Table / matrix ingestion----------------------------------------------------------------------- | Build a network from a long-form edge list, deriving the node set from the--- rows. A thin alias for @'tradeNetwork' []@ (which already unions in every--- endpoint).------ >>> let Right g = networkFromTable [(1,2),(2,3)] :: Either NetworkError (TradeNetwork Int)--- >>> nodes g--- [1,2,3]--- >>> edges g--- [(1,2),(2,3)]-networkFromTable :: Ord k => [(k, k)] -> Either NetworkError (TradeNetwork k)-networkFromTable = tradeNetwork []---- | Build both the network and the coefficient table from a long-form--- @(supplier, buyer, coef)@ table in one step. The network's edges are exactly--- the table's @(supplier, buyer)@ pairs, so the @supp(A) ⊆ edges(G)@ invariant--- holds automatically.------ >>> let Right (g, a) = coefficientsFromTable [(1,3,0.2),(2,3,0.5)] :: Either NetworkError (TradeNetwork Int, InputCoefficients Int Double)--- >>> edges g--- [(1,3),(2,3)]--- >>> inputsOf a 3--- [(1,0.2),(2,0.5)]-coefficientsFromTable :: (Ord k, HatVal v)- => [(k, k, v)]- -> Either NetworkError (TradeNetwork k, InputCoefficients k v)-coefficientsFromTable triples = do- g <- tradeNetwork [] [ (i, j) | (i, j, _) <- triples ]- a <- inputCoefficients g triples- Right (g, a)---- | Build a network and coefficient table from a /dense/ matrix presented as a--- node list plus a lookup function @a i j@ (= the coefficient on edge--- @(supplier i, buyer j)@). The support is derived by dropping zero (and--- error-valued) cells, mirroring the older dense-matrix examples in one call.--- Self-cells @(i, i)@ are skipped. Total, so it returns the pair directly--- (no 'NetworkError': the support is consistent by construction).------ >>> let m i j = if i < j then fromIntegral (i + j) else 0 :: Double--- >>> let (g, a) = fromCoefficientMatrix [1,2,3] m--- >>> edges g--- [(1,2),(1,3),(2,3)]--- >>> coefficient a 1 3--- Just 4.0-fromCoefficientMatrix :: (Ord k, HatVal v)- => [k] -> (k -> k -> v) -> (TradeNetwork k, InputCoefficients k v)-fromCoefficientMatrix ks a =- (buildNetwork nodeSet es, InputCoefficients byBuyer)- where- nodeSet = S.fromList ks- xs = S.toAscList nodeSet- cells = [ (i, j, v)- | i <- xs, j <- xs, i /= j- , let v = a i j- , not (EJ.isZeroValue v), not (EJ.isErrorValue v) ]- es = [ (i, j) | (i, j, _) <- cells ]- byBuyer = foldl' (\m (i, j, v) -> M.insertWith M.union j (M.singleton i v) m) M.empty cells----------------------------------------------------------------------- * CSV (fixed schema, minimal self-contained parser)------------------------------------------------------------------------- A deliberately tiny CSV reader: comma-separated, no quoting, blank lines and--- lines whose first non-space character is @#@ are skipped, surrounding--- whitespace on each field is trimmed. The first non-skipped line must be the--- header. Avoids a @cassava@ dependency for these fixed schemas. Line splitting--- is shared with "ExchangeAlgebra.Convert.Csv" through 'splitTrim'.---- | Parse an edge CSV with header @from,to@ into @(from, to)@ pairs.------ >>> parseEdgeCsv (T.pack "from,to\na,b\nb,c\n")--- Right [("a","b"),("b","c")]-parseEdgeCsv :: Text -> Either String [(Text, Text)]-parseEdgeCsv txt =- case dataRows ["from", "to"] txt of- Left e -> Left e- Right rows -> traverse row rows- where- row [a, b] = Right (a, b)- row r = Left ("edge row expected 2 fields, got " ++ show (length r))---- | Parse a coefficient CSV with header @from,to,coef@ into--- @(from, to, coef)@ triples (coefficient read as 'Double').------ >>> parseCoefCsv (T.pack "from,to,coef\na,b,0.5\n")--- Right [("a","b",0.5)]-parseCoefCsv :: Text -> Either String [(Text, Text, Double)]-parseCoefCsv txt =- case dataRows ["from", "to", "coef"] txt of- Left e -> Left e- Right rows -> traverse row rows- where- row [a, b, c] = case reads (T.unpack c) of- [(d, "")] -> Right (a, b, d)- _ -> Left ("coef field not a number: " ++ show c)- row r = Left ("coef row expected 3 fields, got " ++ show (length r))---- | Read an edge CSV file into a t'TradeNetwork'. Combines parse and validation--- errors into the @Left@ string.-readEdgeCsv :: FilePath -> IO (Either String (TradeNetwork Text))-readEdgeCsv fp = do- txt <- TIO.readFile fp- pure $ case parseEdgeCsv txt of- Left e -> Left e- Right es -> either (Left . show) Right (networkFromTable es)---- | Read a coefficient CSV file into a pair of a t'TradeNetwork' and--- t'InputCoefficients'. Combines parse and validation errors into the @Left@ string.-readCoefCsv :: FilePath -> IO (Either String (TradeNetwork Text, InputCoefficients Text Double))-readCoefCsv fp = do- txt <- TIO.readFile fp- pure $ case parseCoefCsv txt of- Left e -> Left e- Right trips -> either (Left . show) Right (coefficientsFromTable trips)---- | Split CSV text into trimmed data-field rows, after checking the header.-dataRows :: [Text] -> Text -> Either String [[Text]]-dataRows expectedHeader txt =- case keptLines of- [] -> Left "empty CSV (no header)"- (h : body)- | splitTrim h == expectedHeader -> Right (map splitTrim body)- | otherwise -> Left ("unexpected header: " ++ show (splitTrim h)- ++ ", expected " ++ show expectedHeader)- where- keptLines = filter keep (T.lines txt)- keep l =- let s = T.strip l- in not (T.null s) && not ("#" `T.isPrefixOf` s)----------------------------------------------------------------------- * Internal sampling helpers----------------------------------------------------------------------- | Uniform sampling without replacement of @n@ elements from a list,--- deterministic in the 'StdGen'. Returns the chosen elements (in the order they--- were drawn) and the advanced generator. If @n >= length xs@ the whole list is--- returned.-sampleWithout :: StdGen -> Int -> [a] -> ([a], StdGen)-sampleWithout g0 n xs0 = go g0 (max 0 n) xs0 []- where- go g _ [] acc = (reverse acc, g)- go g k xs acc- | k <= 0 = (reverse acc, g)- | otherwise =- let (ix, g1) = randomR (0, length xs - 1) g- (chosen, rest) = pick ix xs- in go g1 (k - 1) rest (chosen : acc)- pick i ys = case splitAt i ys of- (pre, y : post) -> (y, pre ++ post)- (pre, []) -> (last pre, init pre) -- unreachable (ix in range)---- | Weighted sampling without replacement: pick @n@ elements, each draw--- proportional to its weight, deterministic in the 'StdGen'.-sampleWeightedWithout :: StdGen -> Int -> [(a, Double)] -> ([a], StdGen)-sampleWeightedWithout g0 n xs0 = go g0 (max 0 n) xs0 []- where- go g _ [] acc = (reverse acc, g)- go g k ws acc- | k <= 0 = (reverse acc, g)- | otherwise =- let total = sum (map snd ws)- (u, g1) = randomR (0, total) g :: (Double, StdGen)- (chosen, rest) = drawAt u ws- in go g1 (k - 1) rest (chosen : acc)- drawAt u ws = walk u ws []- where- walk _ [] seen = case reverse seen of- ((a, _) : _) -> (a, []) -- exhausted (shouldn't happen)- [] -> error "sampleWeightedWithout: empty"- walk acc ((a, w) : rest) seen- | acc <= w = (a, reverse seen ++ rest)- | otherwise = walk (acc - w) rest ((a, w) : seen)----------------------------------------------------------------------- * Small utilities----------------------------------------------------------------------- | Strict left @foldM@ over 'Either', short-circuiting on the first 'Left'.-foldM' :: (b -> a -> Either e b) -> b -> [a] -> Either e b-foldM' f = go- where- go !acc [] = Right acc- go !acc (x : xs) = case f acc x of- Left e -> Left e- Right b' -> go b' xs+-- | Compatibility entry point for the network API.+module ExchangeAlgebra.Simulate.Network+ {-# DEPRECATED "Use ExchangeAlgebra.Simulation.Network, ExchangeAlgebra.Simulation.Network.Flows, ExchangeAlgebra.Simulation.Network.Csv, and ExchangeAlgebra.Simulation.Network.Journal instead." #-}+ ( module ExchangeAlgebra.Simulation.Network+ , module ExchangeAlgebra.Simulation.Network.Flows+ , module ExchangeAlgebra.Simulation.Network.Csv+ , module ExchangeAlgebra.Simulation.Network.Journal+ ) where++import ExchangeAlgebra.Simulation.Network+import ExchangeAlgebra.Simulation.Network.Flows+import ExchangeAlgebra.Simulation.Network.Csv+import ExchangeAlgebra.Simulation.Network.Journal
src/ExchangeAlgebra/Simulate/Policy.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -84,6 +85,11 @@ convention is fixed by the 'HasTermAxis' class: __the last component of the Note is the term__. The library ships instances for @(e, t)@ and @(e1, e2, t)@; a bespoke Note needs a one-line instance.++ /Note:/ version 0.7 is planned to remove this module without a+ compatibility module. Its successor is the model evaluator in the+ separate @exchangealgebra-simulation@ package. 0.6 emits no deprecation+ warning because that package has not been released. -} module ExchangeAlgebra.Simulate.Policy@@ -106,6 +112,7 @@ , HatVal , HatBaseClass , filterWithNote )+import ExchangeAlgebra.Journal.Core (HasTermAxis(..)) import ExchangeAlgebra.Simulate (StateTime) import ExchangeAlgebra.Simulate.Spill ( SpillOptions(..) , SpillDeletePolicy(..)@@ -172,33 +179,8 @@ -- * Term axis convention ------------------------------------------------------------------ --- | The convention that fixes /which/ part of a 'Note' is the simulation term.------ A policy must map a Note to its term to decide which entries belong to a--- closed period. Since a tuple Note has no intrinsic "term" component, the rule--- is fixed here: __the term is the last component of the Note__. The shipped--- instances follow this rule; a custom Note type provides a one-line instance.------ >>> termOf ("buy", 7 :: Int)--- 7------ >>> termOf ("buy", "shopA", 3 :: Int)--- 3-class (Note n, Ord (TermOf n)) => HasTermAxis n where- -- | The term type extracted from the Note.- type TermOf n- -- | Extract the term (last Note component, by the library convention).- termOf :: n -> TermOf n---- | Pair Note: the term is the second (last) component.-instance (Note e, Note t) => HasTermAxis (e, t) where- type TermOf (e, t) = t- termOf = snd---- | Triple Note: the term is the third (last) component.-instance (Note e1, Note e2, Note t) => HasTermAxis (e1, e2, t) where- type TermOf (e1, e2, t) = t- termOf (_, _, t) = t+-- 'HasTermAxis' and 'TermOf' are defined in "ExchangeAlgebra.Journal.Core"+-- and re-exported here, so the 0.5.x import path keeps working. ------------------------------------------------------------------ -- * Bridges to the classic spill engine@@ -208,7 +190,7 @@ -- @runSimulationWithSpill@ from -- a t'LedgerPolicy', deriving the per-chunk extraction and the eviction range -- from the Note's term axis ('termOf'). This replaces the ~20 lines of--- hand-written 'filterWithNote' plumbing (cf. @simulateEx2@) with a single call.+-- hand-written 'filterWithNote' plumbing (cf. @fixedPriceIOSpill@) with a single call. -- -- The caller supplies @spillEvery@ (the chunk size in terms) and two accessors -- for the world's ledger field: @readLedger@ reads the current ledger, and
src/ExchangeAlgebra/Simulate/Random.hs view
@@ -14,6 +14,11 @@ >>> import System.Random (mkStdGen) >>> fst (normal' (5, 0) (mkStdGen 1)) :: Double 5.0++ /Note:/ version 0.7 is planned to remove this module without a+ compatibility module. Its successor is the model evaluator in the+ separate @exchangealgebra-simulation@ package. 0.6 emits no deprecation+ warning because that package has not been released. -} module ExchangeAlgebra.Simulate.Random
src/ExchangeAlgebra/Simulate/Spill.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE StrictData #-} {- | Module : ExchangeAlgebra.Simulate.Spill@@ -7,6 +8,9 @@ Description : Spill-to-disk codec and restore for long simulations: chunk writers, checked readers, and ledger restoration. Shared by the classic engine ("ExchangeAlgebra.Simulate"), the Lite runner and the ledger policy layer; depends only on Algebra and Journal. Released under the OWL license++ /Note:/ version 0.7 is planned to move this module to the separate+ @exchangealgebra-simulation@ package. -} module ExchangeAlgebra.Simulate.Spill@@ -32,7 +36,10 @@ import qualified Data.ByteString.Lazy as BL import Data.Int (Int64) import qualified Data.List as L-import System.IO (Handle, hPutStr, hPutStrLn)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import System.IO (Handle, hPutStrLn) import ExchangeAlgebra.Algebra ((.+), HatBaseClass, HatVal) import qualified ExchangeAlgebra.Journal as EJ@@ -101,8 +108,8 @@ mkSpillOptions :: Show t => Int -> FilePath- -> (a RealWorld -> ST RealWorld String)- -> SpillOptions t a String+ -> (a RealWorld -> ST RealWorld Text)+ -> SpillOptions t a Text mkSpillOptions interval path extractF = SpillOptions { spillEveryTerms = max 1 interval@@ -136,10 +143,10 @@ -- | Default text-format spill writer. -- Writes the chunk range and payload as text to the handle.-defaultSpillWriter :: Show t => Handle -> (t, t) -> String -> IO ()+defaultSpillWriter :: Show t => Handle -> (t, t) -> Text -> IO () defaultSpillWriter h (tStart, tEnd) payload = do hPutStrLn h ("# chunk " ++ show tStart ++ " " ++ show tEnd)- hPutStr h payload+ TIO.hPutStr h payload hPutStrLn h "\n# end-chunk" -- | Default binary-format spill writer.@@ -154,7 +161,7 @@ = SpillDecodeFailure { spillErrorOffset :: !Int64 -- ^ Byte offset at which decoding failed. , spillErrorChunks :: !Int -- ^ Chunks decoded successfully before the failure.- , spillErrorMessage :: String -- ^ Message from 'Binary.decodeOrFail'.+ , spillErrorMessage :: Text -- ^ Message from 'Binary.decodeOrFail'. } | SpillRangeError { spillRangeIssue :: !SpillRangeIssue@@ -178,7 +185,7 @@ renderSpillReadError err = case err of SpillDecodeFailure offset chunks message -> "binary spill decode failure at byte offset " ++ show offset- ++ " after " ++ show chunks ++ " chunks: " ++ message+ ++ " after " ++ show chunks ++ " chunks: " ++ T.unpack message SpillRangeError issue previous current -> "binary spill range error (" ++ show issue ++ "): chunk " ++ show current ++ " follows " ++ show previous@@ -226,13 +233,13 @@ decodeBinarySpillChunks :: (Binary.Binary t, Binary.Binary payload) => BL.ByteString- -> Either (Int64, Int, String) [((t, t), payload)]+ -> Either (Int64, Int, Text) [((t, t), payload)] decodeBinarySpillChunks = go 0 0 where go _ _ bs | BL.null bs = Right [] go offset decoded bs = case Binary.decodeOrFail bs of Left (_, localOffset, message) ->- Left (offset + localOffset, decoded, message)+ Left (offset + localOffset, decoded, T.pack message) Right (rest, consumed, entry) -> (entry :) <$> go (offset + consumed) (decoded + 1) rest
src/ExchangeAlgebra/Simulate/Visualize.hs view
@@ -17,6 +17,8 @@ <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf> + /Note:/ version 0.7 decides whether this module moves to the separate+ @exchangealgebra-simulation@ package behind Chart or is removed. -} @@ -37,10 +39,12 @@ ,writeFuncResultsWithContext) where import ExchangeAlgebra.Simulate-import ExchangeAlgebra.Render.Simulation+import ExchangeAlgebra.Simulation.Output ( Header , writeFuncResults , writeFuncResultsWithContext )+import Data.Text (Text)+import qualified Data.Text as T import qualified Data.List as L import Graphics.Rendering.Chart.Easy hiding ( (:<),(.~)) import Graphics.Rendering.Chart.Backend.Cairo@@ -52,13 +56,20 @@ import Data.Array.ST import qualified Data.Set as Set -type Title = String+-- | Chart title text.+type Title = Text+-- | Directory path for the PNG files written by plotLine. type FileName = String-type Label = String+-- | Time-series label text.+type Label = Text +-- | A label and collections of time-value points. type TimeSeries t = (Label, [[(t, Double)]])+-- | A collection of labeled time series. type TimeSerieses t = [(TimeSeries t)]+-- | Collections of time series arranged in grid columns. type GridColumns t = [TimeSerieses t]+-- | Rows of time-series grid columns. type GridMatrix t = [GridColumns t] chunkN :: Int -> [a] -> [[a]]@@ -97,7 +108,7 @@ plotLine f (start,end) wld fileDir titleStr = do gridMatrix <- funcArray f (start,end) wld let gridRenderable = createGrid gridMatrix- CM.void $ renderableToFile def (fileDir ++ "/" ++ titleStr ++ ".png")+ CM.void $ renderableToFile def (fileDir ++ "/" ++ T.unpack titleStr ++ ".png") $ fillBackground def $ gridToRenderable $ createTitle titleStr `wideAbove` gridRenderable@@ -117,12 +128,12 @@ -- | Render a single time series as a line plot linePlot seriesLabel seriesData = liftEC $ do plot_lines_values .= [concat seriesData]- plot_lines_title .= seriesLabel+ plot_lines_title .= T.unpack seriesLabel plot_lines_style . line_color .= opaque blue -- | Set the graph title createTitle name = setPickFn nullPickFn $- label titleStyle HTA_Centre VTA_Centre name+ label titleStyle HTA_Centre VTA_Centre (T.unpack name) -- | Define the title font style titleStyle :: FontStyle@@ -172,7 +183,7 @@ xs <- CM.forM [initTerm .. lastTerm] $ \t -> do v <- readArray arr (e, t) pure (t, v)- pure [(show e, [xs])]+ pure [(T.pack (show e), [xs])] pure (chunkN 3 cells) @@ -213,12 +224,12 @@ -> (i,i) -- ^ Range (start, end) -> [a RealWorld] -- ^ World states or similar -> FilePath -- ^ Output directory- -> String -- ^ Graph title+ -> Title -- ^ Graph title -> IO () plotMultiLines xs fs (start,end) wlds fileDir titleStr = do gridMatrix <- funcArrays xs fs (start,end) wlds let gridRenderable = createGrid gridMatrix- CM.void $ renderableToFile def (fileDir ++ "/" ++ titleStr ++ ".png")+ CM.void $ renderableToFile def (fileDir ++ "/" ++ T.unpack titleStr ++ ".png") $ fillBackground def $ gridToRenderable $ createTitle titleStr `wideAbove` gridRenderable@@ -237,12 +248,12 @@ -- | Render time series data as a colored line plot linePlot idx seriesLabel seriesData = liftEC $ do plot_lines_values .= [concat seriesData]- plot_lines_title .= seriesLabel+ plot_lines_title .= T.unpack seriesLabel plot_lines_style . line_color .= colorPalette !! (idx `mod` length colorPalette) -- | Set the graph title createTitle name = setPickFn nullPickFn $- label titleStyle HTA_Centre VTA_Centre name+ label titleStyle HTA_Centre VTA_Centre (T.unpack name) -- | Title font style titleStyle :: FontStyle@@ -277,14 +288,16 @@ -- | Extension of gridLine that--- groups multiple series into the same cell+-- groups multiple series into the same cell.+-- Invariant: the series list is non-empty; an empty list raises an error. gridLines :: (Ord a, Show a, Ix a, StateTime t) => [Label] -> [STArray s (a, t) Double] -> ST s (GridMatrix t)-gridLines xs arrs = do- idx <- getBounds (head arrs)+gridLines _ [] = error "gridLines: non-empty series invariant"+gridLines xs arrs@(first : _) = do+ idx <- getBounds first let as = Set.toAscList $ Set.fromList $ L.map fst@@ -298,9 +311,9 @@ return (t, v) return ts -- Group two series into the same cell- -- TimeSeries has type (String, [[(Term, Double)]])+ -- TimeSeries has type (Label, [[(Term, Double)]]) -- Multiple series form TimeSerieses = [TimeSeries]- let cell = map (\(l,vs) -> (show e ++ "_" ++ show l, [vs]))+ let cell = map (\(l,vs) -> (T.pack (show e ++ "_" ++ show l), [vs])) $ zip xs timeVals pure cell @@ -323,13 +336,13 @@ -> (i,i) -> (a RealWorld, a RealWorld) -> FilePath -- ^ Output directory- -> String -- ^ Graph title+ -> Title -- ^ Graph title -> IO () plotWldsDiffLine f (start,end) wlds fileDir titleStr = do gridMatrix <- funcDiffArray f (start,end) wlds let gridRenderable = createGrid gridMatrix- CM.void $ renderableToFile def (fileDir ++ "/" ++ titleStr ++ ".png")+ CM.void $ renderableToFile def (fileDir ++ "/" ++ T.unpack titleStr ++ ".png") $ fillBackground def $ gridToRenderable $ createTitle titleStr `wideAbove` gridRenderable@@ -349,12 +362,12 @@ -- | Render a single time series as a line plot linePlot seriesLabel seriesData = liftEC $ do plot_lines_values .= [concat seriesData]- plot_lines_title .= seriesLabel+ plot_lines_title .= T.unpack seriesLabel plot_lines_style . line_color .= opaque blue -- | Set the graph title createTitle name = setPickFn nullPickFn $- label titleStyle HTA_Centre VTA_Centre name+ label titleStyle HTA_Centre VTA_Centre (T.unpack name) -- | Define the title font style titleStyle :: FontStyle@@ -444,7 +457,7 @@ -- TimeSeries has type (Label, [[(t,Double)]]) so -- shape it into [[(t,Double)]] ts :: [(Label, [[(t,Double)]])]- ts = [(show iVal, [series])]+ ts = [(T.pack (show iVal), [series])] pure ts pure (chunkN 3 cells)@@ -468,7 +481,7 @@ -> ((i,t),(i,t)) -- ^ (start, end) -> a RealWorld -- ^ World state -> FilePath -- ^ Output directory- -> String -- ^ Graph title+ -> Title -- ^ Graph title -> IO () plotLineVector f idx wld outDir titleStr = do -- 2. Convert to GridMatrix (gridLineVector)@@ -476,7 +489,7 @@ -- 3. Render the chart let gridRenderable = createGrid gridMatrix- CM.void $ renderableToFile def (outDir ++ "/" ++ titleStr ++ ".png")+ CM.void $ renderableToFile def (outDir ++ "/" ++ T.unpack titleStr ++ ".png") $ fillBackground def $ gridToRenderable $ createTitle titleStr `wideAbove` gridRenderable@@ -495,12 +508,12 @@ linePlot seriesLabel seriesData = liftEC $ do plot_lines_values .= [concat seriesData]- plot_lines_title .= seriesLabel+ plot_lines_title .= T.unpack seriesLabel plot_lines_style . line_color .= opaque blue createTitle name = setPickFn nullPickFn $- label titleStyle HTA_Centre VTA_Centre name+ label titleStyle HTA_Centre VTA_Centre (T.unpack name) titleStyle :: FontStyle titleStyle = def
+ src/ExchangeAlgebra/Simulation/Analysis.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE Strict #-}+{-# LANGUAGE StrictData #-}++{- |+ Module : ExchangeAlgebra.Simulation.Analysis+ Copyright : (c) Kaya Akagi. 2018-2026+ Maintainer : yakagika@icloud.com++ Released under the OWL license++ Matrix analysis for ripple effects. Import this module to compute a+ Leontief inverse or extract the response to one industry's demand.++ == Operations++ +----------------------------+--------------------------------------------++ | operation | result |+ +============================+============================================++ | 'leontiefInverse' | Invert @I - A@ for input coefficients @A@. |+ +----------------------------+--------------------------------------------++ | 'rippleEffect' | Fill one response column of a zero matrix. |+ +----------------------------+--------------------------------------------+++ Arrays use one-based bounds starting at @(1,1)@. @inverse@ does not+ exchange pivot rows, so a mathematically invertible matrix such as+ @[[0,1],[1,0]]@ can produce non-finite results. 'rippleEffect' does+ not check the industry index before indexing the array.++ @inverse@ modifies its input array. 'leontiefInverse' makes a copy+ before inversion, leaving its input unchanged.++ The accounting state space and the analysis utilities are discussed in:++ Kaya Akagi. /Accounting State Space as the Minimal Unit for Economic/+ /Agent-Based Modeling: Advancing Ripple Effect Analysis in Real-Time/+ /Economy./ Research Square, preprint (Version 1), posted 5 January 2026.+ <https://doi.org/10.21203/rs.3.rs-8485050/v1>++ >>> import Data.Array.IO (newListArray, getElems, IOArray)+ >>> a <- newListArray ((1,1),(2,2)) [0,0,0,0] :: IO (IOArray (Int,Int) Double)+ >>> leontiefInverse a >>= getElems+ [1.0,0.0,0.0,1.0]++ Runnable ripple examples are preserved in the+ <https://github.com/yakagika/ExchangeAlgebra/tree/archive/research-examples-pre-p4/examples archive/research-examples-pre-p4 tag>.+-}+module ExchangeAlgebra.Simulation.Analysis+ ( leontiefInverse+ , rippleEffect+ ) where++import Control.Monad (forM_, when)+import Data.Array.IO (IOArray, getBounds, newArray, readArray, writeArray)+import Data.Ix (range)+import ExchangeAlgebra.Simulation.Array (modifyArray)++------------------------------------------------------------------+-- * Ripple Effect Analysis+------------------------------------------------------------------++-- | Allocate an identity matrix with bounds @((1,1),(n,n))@.+--+-- The caller supplies the dimension. This is an internal helper for+-- @inverse@; it does not validate a nonpositive dimension.+identity :: Int -> IO (IOArray (Int, Int) Double)+identity n = newArray ((1, 1), (n, n)) 0 >>= \arr -> do+ forM_ [1..n] $ \i -> writeArray arr (i, i) 1+ return arr++-- | Invert a one-based square matrix with Gauss-Jordan elimination.+--+-- This function changes the input array in place. It assumes bounds+-- @((1,1),(n,n))@; another origin causes a pattern-match failure.+-- It does not exchange pivot rows. A zero pivot, including one in an+-- invertible matrix such as @[[0,1],[1,0]]@, can yield non-finite values.+inverse :: IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)+inverse mat = do+ bnds <- getBounds mat+ -- @inverse@ is only ever called on a 1-indexed square matrix, so the bounds+ -- are @((1,1),(n,n))@; matching @((1,1),(n,_))@ is intentionally partial+ -- (audited invariant) — a non-1-indexed matrix is a programmer error here.+ let ((1,1),(n,_)) = bnds+ inv <- identity n++ forM_ [1..n] $ \i -> do+ pivot <- readArray mat (i,i)+ forM_ [1..n] $ \j -> do+ modifyArray mat (i,j) (/pivot)+ modifyArray inv (i,j) (/pivot)+ forM_ [1..n] $ \k -> when (k /= i) $ do+ factor <- readArray mat (k,i)+ forM_ [1..n] $ \j -> do+ mVal <- readArray mat (i,j)+ iVal <- readArray inv (i,j)+ modifyArray mat (k,j) (\x -> x - factor * mVal)+ modifyArray inv (k,j) (\x -> x - factor * iVal)++ return inv++{- | Compute the Leontief inverse @(I - A)^(-1)@ of an input-coefficient+matrix @A@.++The array must be square with origin @(1,1)@. This function copies @A@+before calling @inverse@, so the input array remains unchanged. The+current implementation does not swap pivot rows; a zero pivot can+produce non-finite values even when @I - A@ is invertible. A different+origin causes a pattern-match failure in @inverse@.+-}++leontiefInverse :: IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)+leontiefInverse a = do+ bnds <- getBounds a+ temp <- newArray bnds 0+ forM_ (range bnds) $ \(i,j) -> do+ val <- readArray a (i,j)+ writeArray temp (i,j) (if i == j then 1 - val else -val)+ inverse temp++-- | Copy the response to a one-unit demand increase from one column of+-- a Leontief inverse.+--+-- The second argument is the Leontief inverse, not the input-coefficient+-- matrix. The function allocates a matrix with the same bounds, initializes+-- it to zero, and fills only the selected industry's column. It does not+-- validate the industry index; an out-of-range index causes an array+-- indexing error. Allocation is O(n²) for an n-by-n matrix, with O(n)+-- column reads and writes.+rippleEffect :: Int -> IOArray (Int, Int) Double -> IO (IOArray (Int, Int) Double)+rippleEffect industry inverseArr = do+ ((r1,c1),(r2,c2)) <- getBounds inverseArr+ result <- newArray ((r1,c1),(r2,c2)) 0+ forM_ [r1..r2] $ \i -> do+ val <- readArray inverseArr (i, industry)+ writeArray result (i, industry) val+ return result
+ src/ExchangeAlgebra/Simulation/Array.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE Strict #-}+{-# LANGUAGE StrictData #-}++{- |+ Module : ExchangeAlgebra.Simulation.Array++ Shared mutable-array operation for the classic simulation engine and+ matrix analysis. This implementation is not a public entry point.+-}+module ExchangeAlgebra.Simulation.Array (modifyArray) where++import Data.Array.MArray (MArray, readArray, writeArray)+import Data.Ix (Ix)++-- | Modify the value at a given index of an array using a function. Evaluates strictly before writing back.+--+-- Complexity: O(1)+{-# INLINE modifyArray #-}+modifyArray ::(MArray a t m, Ix i) => a i t -> i -> (t -> t) -> m ()+modifyArray ar e f = do+ x <- readArray ar e+ let y = f x+ y `seq` writeArray ar e y
+ src/ExchangeAlgebra/Simulation/Network.hs view
@@ -0,0 +1,99 @@+{- | Construct and inspect trade networks in the Simulation layer.+The private representation stores nodes, directed edges, and sparse input+coefficients. Generators and accessors are available here; flow generation,+CSV input, and Journal summation live in separate adapters.++An t'InputCoefficients' value checks that coefficients are non-negative, finite,+and attached to network edges. It does not establish matrix invertibility.+Read a network with 'nodes' and its coefficients with 'coefficient' or+'inputsOf'.++@ +Task Import+Build and inspect networks ExchangeAlgebra.Simulation.Network+Generate industrial flows ExchangeAlgebra.Simulation.Network.Flows+Read network CSV files ExchangeAlgebra.Simulation.Network.Csv+Sum into a Journal ExchangeAlgebra.Simulation.Network.Journal+@++== What this module is++A small, additive front-end that separates two concepts that the older+examples conflated into a single dense @N×N@ coefficient matrix:++ 1. the __trade network__, t'TradeNetwork' — /who may trade with whom/, a+ sparse directed relation; and+ 2. the __input coefficients__, t'InputCoefficients' — /the technology/,+ a sparse map of per-edge coefficients @a_{ij}@.++In the dense-matrix style the support (non-zero cells) of the coefficient+matrix /was/ the trade relation, so sparsity was an accident of the data+representation rather than a modeling choice. Splitting them lets a model+pick its market structure (complete, @k@-regular, Erdős–Rényi, scale-free,+sectoral) independently of the coefficients, and lets the summation++@+'ExchangeAlgebra.Simulation.Network.Journal.sigmaEdges' g f+@++run the familiar \"Σ\" notation over the /edges/ of @g@ (cost @O(E)@) instead+of over all ordered pairs (cost @O(N²)@). With 'completeNetwork' the two+coincide, so an existing all-pairs model can be ported without changing the+notation (see "ExchangeAlgebra.Simulation.Network.Journal").++'industrialNetwork' builds an ordered block-triangular, power-law trade graph;+"ExchangeAlgebra.Simulation.Network.Flows" computes its exact-integer,+demand-driven backward substitution.++== Edge orientation++An edge @(i, j)@ means \"@i@ is a /supplier/ of @j@\" (equivalently \"@j@ is a+/buyer/ from @i@\"). The coefficient @a_{ij}@ attached to that edge is \"the+amount of @i@ that one unit of @j@'s output requires\". This matches the+long-form table layout @(from, to, coef)@ and the @(supplier, buyer)@ index+order of the example input-coefficient tables.++== Determinism++Every generator is a pure function of an explicit t'System.Random.StdGen' (it does not+return a generator; split one yourself with 'System.Random.split' if you+need an independent stream). The same seed always yields the same network,+and all read-outs ('nodes', 'edges', 'suppliersOf', 'buyersOf', 'inputsOf')+return their results in ascending 'Ord' order, never in hash-table order.++== Internal representation is private++t'TradeNetwork', t'InputCoefficients' and 'NetworkError' are abstract: their+constructors are not exported, so the invariants (out\/in adjacency agree,+@supp(A) ⊆ edges(G)@, no self-loops, non-negative coefficients) cannot be+broken from outside. Build values with the smart constructors and the+generators; read them with the accessors.++== Using a network with the classic "ExchangeAlgebra.Simulate"++This module deliberately provides /no/ @Updatable@ instance for the network+types (the @Updatable t v a s | a s -> t v@ functional dependency makes it+impossible for the library to fix the user's @(t, v)@). To carry a (read-only)+network in a classic simulation, wrap it in your own @UpdatableSTRef@ cell:++@+newtype NetCell s = NetCell (Data.STRef.STRef s (TradeNetwork Int))+instance UpdatableSTRef NetCell s (TradeNetwork Int)+@++and read it inside an event with @readURef@. In the newer+"ExchangeAlgebra.Simulate.Lite" front-end the network is simply a @carry@+field (it never changes during a run), with no instance at all.+-}+module ExchangeAlgebra.Simulation.Network+ ( TradeNetwork, InputCoefficients, NetworkError(..)+ , tradeNetwork, inputCoefficients+ , nodes, edges, suppliersOf, buyersOf, edgeCount, coefficient, inputsOf+ , completeNetwork, circulant, kRegular, erdosRenyi, scaleFree, sectorBlock+ , IndustrialEconomy(..), IndustrialOptions(..), defaultIndustrialOptions+ , industrialNetwork, industrialNetworkWith, firms, industrialEdges+ , CoefOptions(..), defaultCoefOptions, randomCoefficients+ , networkFromTable, coefficientsFromTable, fromCoefficientMatrix+ ) where++import ExchangeAlgebra.Simulation.Network.Representation
+ src/ExchangeAlgebra/Simulation/Network/Csv.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE OverloadedStrings #-}+{- | Parse fixed-schema network CSV files in the Simulation layer.+This adapter uses the network constructors and the shared input CSV splitter.+Import "ExchangeAlgebra.Simulation.Network" for network values.+-}+module ExchangeAlgebra.Simulation.Network.Csv+ ( parseEdgeCsv, parseCoefCsv, readEdgeCsv, readCoefCsv ) where++import qualified Data.Text as T+import Data.Text (Text)+import qualified Data.Text.IO as TIO+import ExchangeAlgebra.IO.Input.Csv (splitTrim)+import ExchangeAlgebra.Simulation.Network++-- * CSV (fixed schema, minimal self-contained parser)+------------------------------------------------------------------+--+-- A deliberately tiny CSV reader: comma-separated, no quoting, blank lines and+-- lines whose first non-space character is @#@ are skipped, surrounding+-- whitespace on each field is trimmed. The first non-skipped line must be the+-- header. Avoids a @cassava@ dependency for these fixed schemas. Line splitting+-- is shared with "ExchangeAlgebra.Convert.Csv" through 'splitTrim'.++-- | Parse an edge CSV with header @from,to@ into @(from, to)@ pairs.+--+-- >>> parseEdgeCsv (T.pack "from,to\na,b\nb,c\n")+-- Right [("a","b"),("b","c")]+parseEdgeCsv :: Text -> Either Text [(Text, Text)]+parseEdgeCsv txt =+ case dataRows ["from", "to"] txt of+ Left e -> Left e+ Right rows -> traverse row rows+ where+ row [a, b] = Right (a, b)+ row r = Left ("edge row expected 2 fields, got " <> T.pack (show (length r)))++-- | Parse a coefficient CSV with header @from,to,coef@ into+-- @(from, to, coef)@ triples (coefficient read as 'Double').+--+-- >>> parseCoefCsv (T.pack "from,to,coef\na,b,0.5\n")+-- Right [("a","b",0.5)]+parseCoefCsv :: Text -> Either Text [(Text, Text, Double)]+parseCoefCsv txt =+ case dataRows ["from", "to", "coef"] txt of+ Left e -> Left e+ Right rows -> traverse row rows+ where+ row [a, b, c] = case reads (T.unpack c) of+ [(d, "")] -> Right (a, b, d)+ _ -> Left ("coef field not a number: " <> T.pack (show c))+ row r = Left ("coef row expected 3 fields, got " <> T.pack (show (length r)))++-- | Read an edge CSV file into a t'TradeNetwork'. Combines parse and validation+-- errors into the @Left@ string.+readEdgeCsv :: FilePath -> IO (Either Text (TradeNetwork Text))+readEdgeCsv fp = do+ txt <- TIO.readFile fp+ pure $ case parseEdgeCsv txt of+ Left e -> Left e+ Right es -> either (Left . T.pack . show) Right (networkFromTable es)++-- | Read a coefficient CSV file into a pair of a t'TradeNetwork' and+-- t'InputCoefficients'. Combines parse and validation errors into the @Left@ string.+readCoefCsv :: FilePath -> IO (Either Text (TradeNetwork Text, InputCoefficients Text Double))+readCoefCsv fp = do+ txt <- TIO.readFile fp+ pure $ case parseCoefCsv txt of+ Left e -> Left e+ Right trips -> either (Left . T.pack . show) Right (coefficientsFromTable trips)++-- | Split CSV text into trimmed data-field rows, after checking the header.+dataRows :: [Text] -> Text -> Either Text [[Text]]+dataRows expectedHeader txt =+ case keptLines of+ [] -> Left "empty CSV (no header)"+ (h : body)+ | splitTrim h == expectedHeader -> Right (map splitTrim body)+ | otherwise -> Left ("unexpected header: " <> T.pack (show (splitTrim h))+ <> ", expected " <> T.pack (show expectedHeader))+ where+ keptLines = filter keep (T.lines txt)+ keep l =+ let s = T.strip l+ in not (T.null s) && not ("#" `T.isPrefixOf` s)++------------------------------------------------------------------
+ src/ExchangeAlgebra/Simulation/Network/Flows.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{- | Generate monetary flows for an ordered industrial network.+This Simulation module uses the network accessors and a private numerical+helper. Import "ExchangeAlgebra.Simulation.Network" for the network itself.+-}+module ExchangeAlgebra.Simulation.Network.Flows+ ( TaxRate(..), taxOf, IndustrialFlows(..), FlowOptions(..)+ , defaultFlowOptions, industrialFlows, industrialFlowsWith ) where++import Control.DeepSeq (NFData (..))+import Data.List (sortBy)+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import qualified Data.Set as S+import ExchangeAlgebra.Simulation.Network+import ExchangeAlgebra.Simulation.Network.Representation (finitePositive)++-- | A tax rate represented by an integer numerator and denominator.+data TaxRate = TaxRate+ { taxNumerator :: !Integer+ , taxDenominator :: !Integer+ } deriving (Eq, Show)++instance NFData TaxRate where+ rnf (TaxRate num den) = rnf num `seq` rnf den++-- | Integer tax on an integer amount. Generated industrial flows are aligned+-- to the denominator, so this division is exact for their amounts.+taxOf :: TaxRate -> Integer -> Integer+taxOf (TaxRate num den) amount+ | den <= 0 = error "taxOf: denominator must be positive"+ | num < 0 = error "taxOf: numerator must be non-negative"+ | amount < 0 = error "taxOf: amount must be non-negative"+ | otherwise = amount * num `div` den++-- | One-period demand-driven monetary flows for an industrial economy.+data IndustrialFlows k = IndustrialFlows+ { flowTrade :: !(Map (k, k) Integer)+ , flowOutput :: !(Map k Integer)+ , flowInput :: !(Map k Integer)+ , flowValueAdded :: !(Map k Integer)+ , flowFinalDemand :: !(Map k Integer)+ } deriving (Eq, Show)++instance NFData k => NFData (IndustrialFlows k) where+ rnf (IndustrialFlows z x inp va f) =+ rnf z `seq` rnf x `seq` rnf inp `seq` rnf va `seq` rnf f++-- | Options for the demand-driven backward substitution.+data FlowOptions = FlowOptions+ { foMeanFinalDemand :: !Integer+ , foInputShare :: !Double+ } deriving (Eq, Show)++instance NFData FlowOptions where+ rnf (FlowOptions f a) = rnf f `seq` rnf a++-- | Mean final demand @1,000,000@ yen and intermediate-input share @0.5@.+defaultFlowOptions :: FlowOptions+defaultFlowOptions = FlowOptions+ { foMeanFinalDemand = 1000000+ , foInputShare = 0.5+ }++-- | Generate one-period flows with 'defaultFlowOptions'.+industrialFlows :: Ord k+ => TaxRate -> IndustrialEconomy k -> IndustrialFlows k+industrialFlows = industrialFlowsWith defaultFlowOptions++-- | Generate exact integer flows by a single downstream-to-upstream backward+-- substitution. Final demand and every trade amount are positive-denominator+-- multiples. Trade amounts may be zero when a buyer's input units are fewer+-- than its suppliers. For an economy produced by 'industrialNetworkWith', the+-- identities @x_j = sum_i z_ij + v_j = sum_m z_jm + f_j@ and+-- @sum_j v_j = sum_j f_j@ hold exactly. Complexity is+-- @O(N*log N + |E|*log N)@ with ordered 'Map' updates.+industrialFlowsWith+ :: Ord k+ => FlowOptions -> TaxRate -> IndustrialEconomy k -> IndustrialFlows k+industrialFlowsWith opts (TaxRate num den) economy+ | den <= 0 = error "industrialFlowsWith: tax denominator must be positive"+ | num < 0 = error "industrialFlowsWith: tax numerator must be non-negative"+ | not (a >= 0 && a < 1) || isNaN a || isInfinite a =+ error "industrialFlowsWith: foInputShare must be finite and in [0,1)"+ | any (not . validOrderedEdge) (industrialEdges economy) =+ error "industrialFlowsWith: economy contains an edge outside the ordered sector DAG"+ | otherwise = IndustrialFlows zMap xMap inputMap vaMap finalMap+ where+ a = foInputShare opts+ validOrderedEdge (i, j) =+ case (M.lookup i (ieSector economy), M.lookup j (ieSector economy)) of+ (Just si, Just sj) -> (si, i) < (sj, j)+ _ -> False+ ks = firms economy+ count = length ks+ sizeOf j = let w = M.findWithDefault 1 j (ieSize economy)+ in if finitePositive w then w else 1+ meanSize = if count == 0+ then 1+ else sum (map sizeOf ks) / fromIntegral count+ meanFinal = max 0 (foMeanFinalDemand opts)+ finalMap = M.fromList+ [ (j, den * max 1 (round (fromIntegral meanFinal * sizeOf j+ / meanSize / fromIntegral den)))+ | j <- ks ]+ order = sortBy downstreamFirst ks+ downstreamFirst i j =+ compare (M.findWithDefault 0 j (ieSector economy), j)+ (M.findWithDefault 0 i (ieSector economy), i)+ (_, zMap, xMap, inputMap, vaMap) =+ foldl' solveFirm (M.empty, M.empty, M.empty, M.empty, M.empty) order++ solveFirm (orders, zs, xs, ins, vas) j =+ let revenue = M.findWithDefault 0 j orders+ finalD = M.findWithDefault den j finalMap+ output = revenue + finalD+ suppliers = suppliersOf (ieNetwork economy) j+ input+ | null suppliers = 0+ | otherwise = den * floor (a * fromIntegral output / fromIntegral den)+ units = input `div` den+ allocations = apportionInteger units [ (i, sizeOf i) | i <- suppliers ]+ zs' = foldl' (\m i -> M.insert (i, j) (den * M.findWithDefault 0 i allocations) m)+ zs suppliers+ orders' = foldl'+ (\m i -> M.insertWith (+) i (den * M.findWithDefault 0 i allocations) m)+ orders suppliers+ valueAdded = output - input+ in ( orders'+ , zs'+ , M.insert j output xs+ , M.insert j input ins+ , M.insert j valueAdded vas )++-- | Per-sector cumulative weights used by two-level supplier sampling.+apportionInteger :: Ord k => Integer -> [(k, Double)] -> Map k Integer+apportionInteger amount rows+ | amount <= 0 || null rows = M.fromList [ (key, 0) | (key, _) <- rows ]+ | otherwise = foldl' addRemainder bases (take (fromInteger remainder) ranked)+ where+ positiveRows = [ (key, if finitePositive weight then weight else 1) | (key, weight) <- rows ]+ total = sum (map snd positiveRows)+ quotas = [ (key, fromIntegral amount * weight / total) | (key, weight) <- positiveRows ]+ floors = [ (key, floor quota, quota - fromIntegral (floor quota :: Integer)) | (key, quota) <- quotas ]+ bases = M.fromList [ (key, base) | (key, base, _) <- floors ]+ remainder = max 0 (amount - sum [ base | (_, base, _) <- floors ])+ ranked = map (\(key, _, _) -> key) $ sortBy compareRemainder floors+ compareRemainder (keyA, _, fracA) (keyB, _, fracB) =+ compare fracB fracA <> compare keyA keyB+ addRemainder m key = M.insertWith (+) key 1 m++------------------------------------------------------------------
+ src/ExchangeAlgebra/Simulation/Network/Journal.hs view
@@ -0,0 +1,44 @@+{- | Sum network edges into a Journal in the Simulation layer.+This adapter uses the network edge accessor and generic Journal operations.+Import "ExchangeAlgebra.Simulation.Network" for network values.+-}+module ExchangeAlgebra.Simulation.Network.Journal (sigmaEdges) where++import ExchangeAlgebra.Simulation.Network+import ExchangeAlgebra.Journal.Core (Journal, Note)+import qualified ExchangeAlgebra.Journal.Core as EJ+import ExchangeAlgebra.Algebra.Base.Representation (HatBaseClass)+import ExchangeAlgebra.Algebra.Value.Class (HatVal)++-- * Summation over edges+------------------------------------------------------------------++-- | Sum a per-edge journal builder over the edges of a network. This is the+-- network analogue of an all-pairs @Σ@: the notation stays \"Σ over the+-- relation\", but the set it runs over is the @O(E)@ edge list rather than the+-- @O(N²)@ ordered pairs.+--+-- @f i j@ is the journal contributed by the edge @(i, j)@ (supplier @i@, buyer+-- @j@). Edges are visited in ascending order, so for an exact value type the+-- result is order-independent and for 'Double' it is at least deterministic.+--+-- With 'completeNetwork' this is exactly the all-pairs sum over distinct+-- ordered pairs, i.e.+--+-- @'sigmaEdges' ('completeNetwork' ks) f == 'EJ.sigma2When' ks ks (/=) f@+--+-- so an all-pairs model ports to a sparse one by swapping the network, leaving+-- the @Σ@ call site unchanged.+--+-- >>> import ExchangeAlgebra.Journal+-- >>> type J = Journal (Int,Int) Double (HatBase CountUnit)+-- >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> let f i j = (1.0 .@ Not:<Amount) .| (i,j) :: J+-- >>> norm (sigmaEdges g f)+-- 2.0+sigmaEdges :: (Note n, HatVal v, HatBaseClass b)+ => TradeNetwork k+ -> (k -> k -> Journal n v b)+ -> Journal n v b+sigmaEdges g f = EJ.sigma (edges g) (\(i, j) -> f i j)+
+ src/ExchangeAlgebra/Simulation/Network/Representation.hs view
@@ -0,0 +1,858 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{- | Network representation and constructors in the Simulation layer.+The public "ExchangeAlgebra.Simulation.Network" module re-exports this+implementation. Read the network accessors before using the generators.+-}+module ExchangeAlgebra.Simulation.Network.Representation where++import Control.DeepSeq (NFData (..))+import Data.List (sortBy)+import qualified Data.Map.Strict as M+import Data.Map.Strict (Map)+import Data.Maybe (fromMaybe)+import qualified Data.Set as S+import Data.Set (Set)+import qualified Data.Vector as V+import System.Random (StdGen, mkStdGen, randomR)+import ExchangeAlgebra.Algebra.Value.Class (HatVal(..), isErrorValue)+import qualified ExchangeAlgebra.Algebra.Value.Class as EJ+++------------------------------------------------------------------+-- * Errors+------------------------------------------------------------------++-- | Why a smart constructor refused to build a value. All cases are reported+-- rather than silently repaired (in particular a duplicate edge is /not/ merged+-- and a coefficient outside the network is /not/ dropped).+data NetworkError+ = SelfLoop -- ^ An edge @(i, i)@ was supplied.+ | DuplicateEdge -- ^ The same ordered pair @(i, j)@ appeared twice.+ | CoefOutsideNetwork -- ^ A coefficient @(i, j, _)@ has no edge @(i, j)@ in the network.+ | NegativeCoefficient -- ^ A coefficient was negative (or a non-finite error value).+ | DuplicateCoefficient -- ^ The same @(i, j)@ coefficient appeared twice.+ deriving (Eq, Show)++instance NFData NetworkError where+ rnf x = x `seq` ()++------------------------------------------------------------------+-- * TradeNetwork+------------------------------------------------------------------++-- | A sparse directed trade relation over a fixed node set. Holds both the+-- out-adjacency (@supplier ↦ buyers@) and the in-adjacency+-- (@buyer ↦ suppliers@) so that 'suppliersOf' and 'buyersOf' are both+-- @O(log N + deg)@. The two indices are kept mutually consistent by+-- construction. Edges are stored as 'Data.Set.Set' so every read-out is in+-- ascending 'Ord' order.+data TradeNetwork k = TradeNetwork+ { tnNodes :: !(Set k)+ , tnOut :: !(Map k (Set k)) -- ^ supplier ↦ set of buyers+ , tnIn :: !(Map k (Set k)) -- ^ buyer ↦ set of suppliers+ }++instance Eq k => Eq (TradeNetwork k) where+ a == b = tnNodes a == tnNodes b && tnOut a == tnOut b && tnIn a == tnIn b++instance Show k => Show (TradeNetwork k) where+ showsPrec d g = showParen (d > 10)+ $ showString "TradeNetwork "+ . showsPrec 11 (S.toAscList (tnNodes g))+ . showString " "+ . showsPrec 11 (edges g)++instance NFData k => NFData (TradeNetwork k) where+ rnf (TradeNetwork ns o i) = rnf ns `seq` rnf o `seq` rnf i++-- | Build a network from a node list and a directed-edge list.+--+-- The node set is the union of the given nodes and every endpoint mentioned in+-- the edges (so missing nodes are added rather than rejected). Fails with+-- 'SelfLoop' on any @(i, i)@ edge and with 'DuplicateEdge' if the same ordered+-- pair appears twice (duplicates are never coalesced — that would silently sum+-- relations).+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> edges g+-- [(1,2),(1,3),(2,3)]+-- >>> suppliersOf g 3+-- [1,2]+-- >>> buyersOf g 1+-- [2,3]+tradeNetwork :: Ord k => [k] -> [(k, k)] -> Either NetworkError (TradeNetwork k)+tradeNetwork ns es = do+ seen <- foldM' insertEdge S.empty es+ let nodeSet = S.unions+ [ S.fromList ns+ , S.fromList [ i | (i, _) <- es ]+ , S.fromList [ j | (_, j) <- es ] ]+ Right (buildNetwork nodeSet (S.toList seen))+ where+ insertEdge acc (i, j)+ | i == j = Left SelfLoop+ | (i, j) `S.member` acc = Left DuplicateEdge+ | otherwise = Right (S.insert (i, j) acc)++-- | Assemble both adjacency indices from a validated edge set.+buildNetwork :: Ord k => Set k -> [(k, k)] -> TradeNetwork k+buildNetwork nodeSet es = TradeNetwork nodeSet outM inM+ where+ outM = foldl' (\m (i, j) -> M.insertWith S.union i (S.singleton j) m) M.empty es+ inM = foldl' (\m (i, j) -> M.insertWith S.union j (S.singleton i) m) M.empty es++-- | The node set, ascending.+--+-- >>> let Right g = tradeNetwork [3,1,2] [] :: Either NetworkError (TradeNetwork Int)+-- >>> nodes g+-- [1,2,3]+nodes :: TradeNetwork k -> [k]+nodes = S.toAscList . tnNodes++-- | The edges @(supplier, buyer)@, ascending.+--+-- >>> let Right g = tradeNetwork [1,2] [(2,1),(1,2)] :: Either NetworkError (TradeNetwork Int)+-- >>> edges g+-- [(1,2),(2,1)]+edges :: TradeNetwork k -> [(k, k)]+edges g = [ (i, j) | (i, js) <- M.toAscList (tnOut g), j <- S.toAscList js ]++-- | The suppliers of a given buyer, ascending.+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> suppliersOf g 3+-- [1,2]+suppliersOf :: Ord k => TradeNetwork k -> k -> [k]+suppliersOf g j = S.toAscList (M.findWithDefault S.empty j (tnIn g))++-- | The buyers from a given supplier, ascending.+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> buyersOf g 1+-- [2,3]+buyersOf :: Ord k => TradeNetwork k -> k -> [k]+buyersOf g i = S.toAscList (M.findWithDefault S.empty i (tnOut g))++-- | The number of edges.+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,2),(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> edgeCount g+-- 3+edgeCount :: TradeNetwork k -> Int+edgeCount = M.foldr' (\s acc -> S.size s + acc) 0 . tnOut++------------------------------------------------------------------+-- * InputCoefficients+------------------------------------------------------------------++-- | A sparse, buyer-major table of input coefficients: @buyer ↦ (supplier ↦+-- coefficient)@. The buyer-major layout matches the dominant access pattern+-- (enumerate a fixed buyer's suppliers) and makes the per-buyer column sum (the+-- Hawkins–Simon productivity check) an @O(deg)@ scan. The invariant+-- @supp(A) ⊆ edges(G)@ is enforced by 'inputCoefficients'.+data InputCoefficients k v = InputCoefficients+ { icByBuyer :: !(Map k (Map k v)) -- ^ buyer ↦ supplier ↦ coefficient+ }++instance (Eq k, Eq v) => Eq (InputCoefficients k v) where+ a == b = icByBuyer a == icByBuyer b++instance (Show k, Show v) => Show (InputCoefficients k v) where+ showsPrec d a = showParen (d > 10)+ $ showString "InputCoefficients "+ . showsPrec 11+ [ (i, j, v)+ | (j, sup) <- M.toAscList (icByBuyer a)+ , (i, v) <- M.toAscList sup ]++instance (NFData k, NFData v) => NFData (InputCoefficients k v) where+ rnf (InputCoefficients m) = rnf m++-- | Build the coefficient table from a network and a long-form list of+-- @(supplier, buyer, coef)@ triples.+--+-- Fails with 'CoefOutsideNetwork' if a triple has no corresponding edge,+-- 'NegativeCoefficient' if a value is negative or a non-finite error value, and+-- 'DuplicateCoefficient' if the same @(supplier, buyer)@ pair appears twice+-- (duplicates are not summed — the redundant-algebra convention forbids silent+-- aggregation). Zero coefficients are kept as written (they do not create an+-- edge, but if the edge exists they are recorded as @0@).+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> let Right a = inputCoefficients g [(1,3,0.2),(2,3,0.5)] :: Either NetworkError (InputCoefficients Int Double)+-- >>> inputsOf a 3+-- [(1,0.2),(2,0.5)]+-- >>> coefficient a 1 3+-- Just 0.2+inputCoefficients :: (Ord k, HatVal v)+ => TradeNetwork k+ -> [(k, k, v)]+ -> Either NetworkError (InputCoefficients k v)+inputCoefficients g triples = do+ m <- foldM' step M.empty triples+ Right (InputCoefficients m)+ where+ edgeSet = S.fromList (edges g)+ step acc (i, j, v)+ | isBadValue v = Left NegativeCoefficient+ | not ((i, j) `S.member` edgeSet) = Left CoefOutsideNetwork+ | hasCoef j i acc = Left DuplicateCoefficient+ | otherwise = Right (M.insertWith M.union j (M.singleton i v) acc)+ hasCoef j i acc = maybe False (M.member i) (M.lookup j acc)++-- | A value that must not enter a coefficient table: negative, or a non-finite+-- error value (NaN\/Inf). Mirrors the non-negativity invariant enforced by the+-- algebra's @(.\@)@ smart constructor.+isBadValue :: HatVal v => v -> Bool+isBadValue v = v < 0 || EJ.isErrorValue v++-- | Look up the coefficient on edge @(supplier, buyer)@, if any.+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> let Right a = inputCoefficients g [(1,3,0.7)] :: Either NetworkError (InputCoefficients Int Double)+-- >>> coefficient a 1 3+-- Just 0.7+-- >>> coefficient a 2 3+-- Nothing+coefficient :: Ord k => InputCoefficients k v -> k -> k -> Maybe v+coefficient a i j = M.lookup j (icByBuyer a) >>= M.lookup i++-- | The @(supplier, coefficient)@ inputs of a fixed buyer, ascending by+-- supplier.+--+-- >>> let Right g = tradeNetwork [1,2,3] [(1,3),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> let Right a = inputCoefficients g [(2,3,0.5),(1,3,0.2)] :: Either NetworkError (InputCoefficients Int Double)+-- >>> inputsOf a 3+-- [(1,0.2),(2,0.5)]+inputsOf :: Ord k => InputCoefficients k v -> k -> [(k, v)]+inputsOf a j = M.toAscList (M.findWithDefault M.empty j (icByBuyer a))++------------------------------------------------------------------+------------------------------------------------------------------+-- * Network generators+------------------------------------------------------------------++-- | The complete directed network: every distinct ordered pair @(i, j)@,+-- @i /= j@, is an edge. @O(N²)@ edges — provided so an existing all-pairs model+-- can be expressed without changing its @Σ@ (see "ExchangeAlgebra.Simulation.Network.Journal").+--+-- >>> let g = completeNetwork [1,2,3] :: TradeNetwork Int+-- >>> edgeCount g+-- 6+-- >>> edges g+-- [(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]+completeNetwork :: Ord k => [k] -> TradeNetwork k+completeNetwork ks =+ buildNetwork nodeSet [ (i, j) | i <- xs, j <- xs, i /= j ]+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet++-- | A deterministic circulant (ring-lattice) network: the nodes are taken in+-- ascending order and each buyer draws its @min k (N-1)@ suppliers from the @k@+-- nodes that follow it cyclically (@j+1, …, j+k@ mod @N@). Unlike 'kRegular' \/+-- 'erdosRenyi' it needs no 'StdGen' and is built in @O(kN)@ — it never scans the+-- @O(N²)@ ordered pairs — so it stays usable at the @N@ a market-scale run needs.+-- @|E| = min k (N-1) · N@ exactly, with no duplicate and no self edges.+--+-- >>> let g = circulant [1..6] 2 :: TradeNetwork Int+-- >>> edgeCount g+-- 12+-- >>> suppliersOf g 1+-- [2,3]+-- >>> suppliersOf g 6+-- [1,2]+-- >>> all (\j -> length (suppliersOf g j) == 2) (nodes g)+-- True+circulant :: Ord k => [k] -> Int -> TradeNetwork k+circulant ks k =+ buildNetwork nodeSet (concat [ zip (rot d xs) xs | d <- [1 .. deg] ])+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet+ n = length xs+ deg = max 0 (min k (n - 1))+ rot d ys = drop d ys ++ take d ys++-- | A @k@-regular-in network: each buyer draws @min k (N-1)@ distinct suppliers+-- (sampling without replacement, excluding itself). Deterministic in the given+-- 'StdGen'.+--+-- >>> let g = kRegular (mkStdGen 1) [1..6] 2 :: TradeNetwork Int+-- >>> all (\j -> length (suppliersOf g j) == 2) (nodes g)+-- True+kRegular :: Ord k => StdGen -> [k] -> Int -> TradeNetwork k+kRegular gen ks k =+ buildNetwork nodeSet (concat (snd (foldl' pick (gen, []) xs)))+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet+ deg = max 0 (min k (length xs - 1))+ pick (g0, acc) j =+ let candidates = filter (/= j) xs+ (chosen, g1) = sampleWithout g0 deg candidates+ in (g1, [ (i, j) | i <- chosen ] : acc)++-- | An Erdős–Rényi @G(n, p)@ directed network: each ordered pair @(i, j)@,+-- @i /= j@, becomes an edge independently with probability @p@. @p <= 0@ yields+-- the empty network, @p >= 1@ yields 'completeNetwork'. Deterministic in the+-- given 'StdGen'.+--+-- >>> edgeCount (erdosRenyi (mkStdGen 0) [1..5] 1.0 :: TradeNetwork Int)+-- 20+-- >>> edgeCount (erdosRenyi (mkStdGen 0) [1..5] 0.0 :: TradeNetwork Int)+-- 0+erdosRenyi :: Ord k => StdGen -> [k] -> Double -> TradeNetwork k+erdosRenyi gen ks p+ | p >= 1 = completeNetwork ks+ | p <= 0 = buildNetwork nodeSet []+ | otherwise = buildNetwork nodeSet (snd (foldl' step (gen, []) pairs))+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet+ pairs = [ (i, j) | i <- xs, j <- xs, i /= j ]+ step (g0, acc) e =+ let (u, g1) = randomR (0, 1) g0 :: (Double, StdGen)+ in if u < p then (g1, e : acc) else (g1, acc)++-- | A scale-free network grown by Barabási–Albert preferential attachment: nodes+-- are added in ascending order; each new node attaches @m@ edges to existing+-- nodes chosen with probability proportional to their current degree (with a+-- uniform fallback while the graph is still empty). Orientation: the new node is+-- the /buyer/, the chosen existing nodes are its /suppliers/. Deterministic in+-- the given 'StdGen'.+--+-- The first @m@ nodes form a seed clique-ish core (each new seed node attaches+-- to all already-present nodes), so for @N > m@ the edge count is+-- @C(m+1, 2)·1 + (N - m - 1)·m@ counting the directed buyer→supplier edges as+-- one per attachment; the property test checks the exact value.+--+-- >>> let g = scaleFree (mkStdGen 7) [1..10] 2 :: TradeNetwork Int+-- >>> edgeCount g+-- 17+scaleFree :: Ord k => StdGen -> [k] -> Int -> TradeNetwork k+scaleFree gen ks m0 =+ buildNetwork nodeSet builtEdges+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet+ m = max 1 m0+ -- State threads the generator, the edge accumulator and an incremental+ -- degree table (@node ↦ current degree@). The degree table replaces the+ -- O(E) rescan that the naive @degree i = length (filter …) acc@ performed+ -- for every candidate at every node: each added edge bumps both endpoints'+ -- degrees in O(log N), so the per-node weight list is built without ever+ -- touching @acc@. The weights produced are bit-for-bit identical to the+ -- rescan version, so the generated network is unchanged.+ (_, builtEdges0, _) = foldl' addNode (gen, [], M.empty) (zip [0 ..] xs)+ builtEdges = reverse builtEdges0++ -- addNode :: (StdGen, [(k,k)], Map k Int)+ -- -> (Int, k) -> (StdGen, [(k,k)], Map k Int)+ addNode st@(g0, acc, deg) (ix, j)+ | ix == 0 = st -- first node: nothing to attach to+ | ix <= m = -- seed phase: attach to all earlier nodes+ let suppliers = take ix xs+ newEdges = [ (i, j) | i <- suppliers ]+ in (g0, newEdges ++ acc, bumpEdges newEdges deg)+ | otherwise = -- preferential attachment+ let present = take ix xs -- nodes already added+ degree i = M.findWithDefault 0 i deg+ weighted = [ (i, fromIntegral (1 + degree i) :: Double) | i <- present ]+ (chosen, g1) = sampleWeightedWithout g0 m weighted+ newEdges = [ (i, j) | i <- chosen ]+ in (g1, newEdges ++ acc, bumpEdges newEdges deg)++ -- Increment both endpoints' degree counts for each newly added edge.+ bumpEdges es d = foldl' (\d' (i, t) -> bump i (bump t d')) d es+ bump k = M.insertWith (+) k 1++-- | A stochastic block network: each node carries a sector label, and an+-- ordered pair @(i, j)@ (@i /= j@) becomes an edge with probability+-- @p (sector i, sector j)@. Deterministic in the given 'StdGen'. Generalizes+-- 'erdosRenyi' (a single block) and lets intra-\/inter-sector densities differ.+--+-- >>> let label n = if n <= 2 then 'A' else 'B'+-- >>> let p (a,b) = if a == b then 1.0 else 0.0+-- >>> let g = sectorBlock (mkStdGen 0) [(n, label n) | n <- [1..4]] p :: TradeNetwork Int+-- >>> edges g+-- [(1,2),(2,1),(3,4),(4,3)]+sectorBlock :: (Ord k, Ord s)+ => StdGen -> [(k, s)] -> ((s, s) -> Double) -> TradeNetwork k+sectorBlock gen labelled p =+ buildNetwork nodeSet (snd (foldl' step (gen, []) pairs))+ where+ secMap = M.fromList labelled+ nodeSet = S.fromList (map fst labelled)+ xs = S.toAscList nodeSet+ pairs = [ (i, j) | i <- xs, j <- xs, i /= j ]+ step (g0, acc) (i, j) =+ let pr = fromMaybe 0 $ do+ si <- M.lookup i secMap+ sj <- M.lookup j secMap+ pure (p (si, sj))+ (u, g1) = randomR (0, 1) g0 :: (Double, StdGen)+ in if pr >= 1 then (g1, (i, j) : acc)+ else if pr <= 0 then (g1, acc)+ else if u < pr then (g1, (i, j) : acc) else (g1, acc)++------------------------------------------------------------------+-- * Ordered industrial networks and demand-driven flows+------------------------------------------------------------------++-- | A block-triangular industrial economy. Sector @0@ is the most upstream+-- sector and larger sector numbers are progressively downstream. Every edge is+-- @(supplier, buyer)@; generated edges satisfy @sector supplier <= sector buyer@,+-- and an intra-sector edge additionally satisfies @supplier < buyer@.+data IndustrialEconomy k = IndustrialEconomy+ { ieNetwork :: !(TradeNetwork k)+ , ieSector :: !(Map k Int)+ , ieSize :: !(Map k Double)+ } deriving (Eq, Show)++instance NFData k => NFData (IndustrialEconomy k) where+ rnf (IndustrialEconomy g s w) = rnf g `seq` rnf s `seq` rnf w++-- | Options for 'industrialNetworkWith'. The flow function is consulted only+-- for ordered sector pairs @(upstream, downstream)@. Non-positive and+-- non-finite values make that sector pair ineligible.+data IndustrialOptions = IndustrialOptions+ { ioExponent :: !Double+ -- ^ Pareto exponent @gamma > 1@.+ , ioFlow :: Int -> Int -> Double+ -- ^ Sector-flow weight @B[s,s']@.+ }++-- | Pareto exponent @2.5@ and a uniform positive sector-flow matrix.+defaultIndustrialOptions :: IndustrialOptions+defaultIndustrialOptions = IndustrialOptions+ { ioExponent = 2.5+ , ioFlow = \_ _ -> 1+ }++-- | Build the paper's deterministic block-triangular, power-law industrial+-- network from @seed N K m@. Unlike 'sectorBlock', this generator is+-- deterministic from an integer seed, ordered by sector, power-law weighted,+-- and avoids an all-pairs scan: expected construction cost is+-- @O(N*K + |E|*(K + log N))@, or @O(N*K + |E|*log N)@ for fixed @K@.+--+-- The requested edge count is @m*N@. It is exact whenever the eligible+-- supplier capacity is at least that large; otherwise all eligible pairs are+-- used and @|E| < m*N@. @N <= 0@ produces an empty economy, @m <= 0@ produces+-- no edges, and @K <= 0@ or an exponent not greater than @1@ is an error.+--+-- >>> let e = industrialNetwork 7 6 2 1+-- >>> firms e+-- [1,2,3,4,5,6]+-- >>> all (\(i,j) -> let s = ieSector e in s M.! i < s M.! j || (s M.! i == s M.! j && i < j)) (industrialEdges e)+-- True+industrialNetwork :: Int -> Int -> Int -> Int -> IndustrialEconomy Int+industrialNetwork = industrialNetworkWith defaultIndustrialOptions++-- | Configurable form of 'industrialNetwork'. Supplier selection is weighted+-- by @B[sector i,sector j] * w_i@ without replacement. It first chooses a+-- sector in @O(K)@ and then a firm by binary search over that sector's+-- cumulative-size vector in @O(log N)@. Duplicate draws use bounded rejection;+-- the deterministic fallback fills the remaining eligible candidates in+-- sector/id order. Sector choice is @O(K)@ per draw; @K@ is normally a small+-- fixed model parameter.+industrialNetworkWith+ :: IndustrialOptions -> Int -> Int -> Int -> Int -> IndustrialEconomy Int+industrialNetworkWith opts seed n0 k m0+ | k <= 0 = error "industrialNetworkWith: K must be positive"+ | not (finitePositive gamma) || gamma <= 1 =+ error "industrialNetworkWith: ioExponent must be finite and greater than 1"+ | n <= 0 = IndustrialEconomy (buildNetwork S.empty []) M.empty M.empty+ | otherwise = IndustrialEconomy network sectors sizes+ where+ n = max 0 n0+ gamma = ioExponent opts+ firmIds = [1 .. n]+ (_, sectors, sizes, sectorRev) = foldl' drawFirm (mkStdGen seed, M.empty, M.empty, M.empty) firmIds+ sectorLists = M.map reverse sectorRev+ pools = M.fromList+ [ (s, mkSectorPool (M.findWithDefault [] s sectorLists) sizes)+ | s <- [0 .. k - 1] ]+ ranks = M.fromList+ [ (i, r)+ | (_, pool) <- M.toAscList pools+ , (r, i) <- zip [0 ..] (V.toList (spFirms pool)) ]+ capacities =+ [ (j, candidateCapacity opts sectors pools ranks j)+ | j <- firmIds ]+ capacityTotal = sum (map snd capacities)+ requestedInteger = toInteger (max 0 m0) * toInteger n+ target = fromInteger (min requestedInteger (toInteger capacityTotal))+ degrees = apportionCapped target+ [ (j, cap, M.findWithDefault 1 j sizes) | (j, cap) <- capacities ]+ (_, edgeChunks) = foldl' drawBuyer (mkStdGen (seed + 104729), []) firmIds+ network = buildNetwork (S.fromList firmIds) (concat (reverse edgeChunks))++ drawFirm (g0, sm, wm, groups) i =+ let (s, g1) = randomR (0, k - 1) g0+ (u0, g2) = randomR (0, 1) g1 :: (Double, StdGen)+ -- randomR's interval is closed; cap its upper endpoint so Pareto+ -- inversion remains finite while retaining the seed-derived draw.+ u = min (1 - 2.220446049250313e-16) (max 0 u0)+ logWeight = - log (1 - u) / (gamma - 1)+ -- The mathematical Pareto draw can exceed Double's range as gamma+ -- approaches 1. Saturate only that unrepresentable tail, keeping+ -- ieSize and every sampling table finite and consistent.+ w = exp (min (log maxIndustrialSize) logWeight)+ in ( g2+ , M.insert i s sm+ , M.insert i w wm+ , M.insertWith (++) s [i] groups )++ drawBuyer (g0, acc) j =+ let d = M.findWithDefault 0 j degrees+ (chosen, g1) = chooseIndustrialSuppliers opts sectors pools ranks j d g0+ in (g1, [ (i, j) | i <- chosen ] : acc)++-- | Firms in ascending order, equivalent to @nodes . ieNetwork@.+firms :: IndustrialEconomy k -> [k]+firms = nodes . ieNetwork++-- | Industrial edges in ascending @(supplier,buyer)@ order, equivalent to+-- @edges . ieNetwork@.+industrialEdges :: IndustrialEconomy k -> [(k, k)]+industrialEdges = edges . ieNetwork++-- | An exact rational tax rate @numerator / denominator@.+data SectorPool = SectorPool+ { spFirms :: !(V.Vector Int)+ , spCumulative :: !(V.Vector Double)+ }++mkSectorPool :: [Int] -> Map Int Double -> SectorPool+mkSectorPool ids weights = SectorPool firmVector cumulative+ where+ firmVector = V.fromList ids+ cumulative = V.fromList (drop 1 (scanl (+) 0 [ M.findWithDefault 1 i weights | i <- ids ]))++candidateCapacity+ :: IndustrialOptions+ -> Map Int Int+ -> Map Int SectorPool+ -> Map Int Int+ -> Int+ -> Int+candidateCapacity opts sectors pools ranks j =+ sum [ eligibleCount s | s <- [0 .. buyerSector] ]+ where+ buyerSector = M.findWithDefault 0 j sectors+ buyerRank = M.findWithDefault 0 j ranks+ eligibleCount s+ | not (finitePositive (ioFlow opts s buyerSector)) = 0+ | s == buyerSector = buyerRank+ | otherwise = maybe 0 (V.length . spFirms) (M.lookup s pools)++chooseIndustrialSuppliers+ :: IndustrialOptions+ -> Map Int Int+ -> Map Int SectorPool+ -> Map Int Int+ -> Int+ -> Int+ -> StdGen+ -> ([Int], StdGen)+chooseIndustrialSuppliers opts sectors pools ranks buyer wanted g0 =+ go g0 S.empty [] 0+ where+ buyerSector = M.findWithDefault 0 buyer sectors+ buyerRank = M.findWithDefault 0 buyer ranks+ attemptLimit = max 64 (wanted * 32)+ sectorChoices =+ [ ((s, pool, limit), ioFlow opts s buyerSector * prefixWeight pool limit)+ | s <- [0 .. buyerSector]+ , finitePositive (ioFlow opts s buyerSector)+ , Just pool <- [M.lookup s pools]+ , let limit = if s == buyerSector then buyerRank else V.length (spFirms pool)+ , limit > 0+ , finitePositive (prefixWeight pool limit) ]++ go g selected acc attempts+ | S.size selected >= wanted = (reverse acc, g)+ | attempts >= attemptLimit =+ let remaining = take (wanted - S.size selected)+ [ i+ | ((_, pool, limit), _) <- sectorChoices+ , i <- V.toList (V.take limit (spFirms pool))+ , i `S.notMember` selected ]+ in (reverse acc ++ remaining, g)+ | otherwise =+ let ((_, pool, limit), g1) = weightedChoice g sectorChoices+ total = prefixWeight pool limit+ (u, g2) = randomR (0, total) g1 :: (Double, StdGen)+ ix = cumulativeLowerBound (spCumulative pool) limit u+ supplier = spFirms pool V.! ix+ in if supplier `S.member` selected+ then go g2 selected acc (attempts + 1)+ else go g2 (S.insert supplier selected) (supplier : acc) (attempts + 1)++prefixWeight :: SectorPool -> Int -> Double+prefixWeight _ limit | limit <= 0 = 0+prefixWeight pool limit = spCumulative pool V.! (limit - 1)++cumulativeLowerBound :: V.Vector Double -> Int -> Double -> Int+cumulativeLowerBound cumulative limit target = go 0 (limit - 1)+ where+ go lo hi+ | lo >= hi = lo+ | cumulative V.! mid >= target = go lo mid+ | otherwise = go (mid + 1) hi+ where mid = (lo + hi) `div` 2++weightedChoice :: StdGen -> [(a, Double)] -> (a, StdGen)+weightedChoice _ [] = error "weightedChoice: empty positive-weight population"+weightedChoice g choices = (pick u choices, g1)+ where+ total = sum (map snd choices)+ (u, g1) = randomR (0, total) g :: (Double, StdGen)+ pick _ [(x, _)] = x+ pick r ((x, w) : rest)+ | r <= w = x+ | otherwise = pick (r - w) rest+ pick _ [] = error "weightedChoice: unreachable"++finitePositive :: Double -> Bool+finitePositive x = x > 0 && not (isNaN x) && not (isInfinite x)++-- | Numerical ceiling for the unrepresentable far tail of a Pareto draw. It is+-- far above any economically meaningful relative size while leaving+-- market-scale sector sums finite.+maxIndustrialSize :: Double+maxIndustrialSize = 1e100++-- | Largest-remainder allocation with per-recipient caps. Continuous weighted+-- water-filling finds the cap-saturation threshold in one sorted pass, then a+-- largest-remainder step integerizes the result. @O(N log N)@.+apportionCapped :: Int -> [(Int, Int, Double)] -> Map Int Int+apportionCapped requested rows+ | target <= 0 = M.fromList [ (key, 0) | (key, _, _) <- rows ]+ | remainder > length ranked =+ error "apportionCapped: numerical instability in largest-remainder allocation"+ | otherwise = foldl' addRemainder bases (take remainder ranked)+ where+ normalised =+ [ (key, max 0 cap, if finitePositive weight then weight else 1)+ | (key, cap, weight) <- rows ]+ target = min (max 0 requested) (sum [ cap | (_, cap, _) <- normalised ])+ active = sortBy compareThreshold [ row | row@(_, cap, _) <- normalised, cap > 0 ]+ lambda = waterLevel target (sum [ weight | (_, _, weight) <- active ]) active+ quotas =+ [ (key, cap, min (fromIntegral cap) (lambda * weight))+ | (key, cap, weight) <- normalised ]+ floors =+ [ (key, cap, floor quota, quota - fromIntegral (floor quota :: Int))+ | (key, cap, quota) <- quotas ]+ bases = M.fromList [ (key, base) | (key, _, base, _) <- floors ]+ remainder = max 0 (target - sum [ base | (_, _, base, _) <- floors ])+ ranked = map (\(key, _, _) -> key) $ sortBy compareRemainder+ [ (key, cap, frac) | (key, cap, base, frac) <- floors, base < cap ]+ compareThreshold (keyA, capA, weightA) (keyB, capB, weightB) =+ compare (fromIntegral capA / weightA) (fromIntegral capB / weightB)+ <> compare keyA keyB+ compareRemainder (keyA, _, fracA) (keyB, _, fracB) =+ compare fracB fracA <> compare keyA keyB+ addRemainder m key = M.insertWith (+) key 1 m+ waterLevel amount weightTotal candidates = case candidates of+ [] -> 0+ (_, cap, weight) : rest+ | weightTotal <= 0 -> 0+ | level <= fromIntegral cap / weight -> level+ | otherwise -> waterLevel (amount - cap) (weightTotal - weight) rest+ where level = fromIntegral amount / weightTotal++-- * Coefficient generation+------------------------------------------------------------------++-- | Options for 'randomCoefficients'.+data CoefOptions = CoefOptions+ { coefRange :: !(Double, Double)+ -- ^ Inclusive @(lo, hi)@ range each raw coefficient is drawn from.+ , hawkinsSimon :: !Bool+ -- ^ When 'True', each buyer's column (its suppliers' coefficients) is+ -- kept strictly below a column sum of @1@, guaranteeing productivity (a+ -- sufficient Hawkins–Simon condition). The rescaling is __shrink-only__:+ -- a column whose raw sum is already @< 1@ is left untouched, and only a+ -- column whose raw sum is @>= 1@ is scaled down (to @0.95@ of its sum).+ -- This preserves the heterogeneity of the raw draws instead of forcing+ -- every column to one common sum. When 'False' the raw draws are used+ -- as-is.+ } deriving (Eq, Show)++instance NFData CoefOptions where+ rnf (CoefOptions r h) = rnf r `seq` rnf h++-- | @(0, 1)@ range with the Hawkins–Simon rescaling on.+defaultCoefOptions :: CoefOptions+defaultCoefOptions = CoefOptions { coefRange = (0, 1), hawkinsSimon = True }++-- | Draw a coefficient for every edge of a network, deterministically from the+-- given 'StdGen'. Values are drawn in 'Double' and converted via 'realToFrac'+-- (so the value type @v@ does not need a @Random@ instance). With+-- 'hawkinsSimon' on, the rescaling is __shrink-only__: a column whose raw sum+-- is already strictly below @1@ is kept as drawn, and only a column whose raw+-- sum reaches @1@ is scaled down to @0.95@ of that sum. Either way every column+-- sum ends strictly below @1@, a sufficient condition for the Leontief system+-- to be productive, while the natural spread of the raw draws is preserved+-- (columns are /not/ all forced to one common sum).+--+-- The result satisfies @supp(A) = edges(G)@ by construction, so it always+-- round-trips back through 'inputCoefficients' without error.+--+-- >>> let g = completeNetwork [1,2,3] :: TradeNetwork Int+-- >>> let a = randomCoefficients (mkStdGen 5) defaultCoefOptions g :: InputCoefficients Int Double+-- >>> all (\j -> sum (map snd (inputsOf a j)) < 1.0) (nodes g)+-- True+randomCoefficients :: forall k v. (Ord k, HatVal v)+ => StdGen -> CoefOptions -> TradeNetwork k -> InputCoefficients k v+randomCoefficients gen opts g =+ InputCoefficients (normalise (snd (foldl' drawCol (gen, M.empty) buyers)))+ where+ (lo, hi) = coefRange opts+ buyers = nodes g+ -- one column per buyer: draw a raw coefficient per supplier+ drawCol (g0, acc) j =+ let sup = suppliersOf g j+ (row, g1) = foldl' drawOne ([], g0) sup+ in (g1, if null row then acc else M.insert j (M.fromList row) acc)+ drawOne (row, g0) i =+ let (u, g1) = randomR (lo, hi) g0 :: (Double, StdGen)+ in ((i, u) : row, g1)++ normalise :: Map k (Map k Double) -> Map k (Map k v)+ normalise+ | hawkinsSimon opts = M.map rescaleCol+ | otherwise = M.map (M.map realToFrac)+ -- Shrink-only Hawkins–Simon: leave a productive column (raw sum < 1)+ -- untouched, and only scale a column down when its raw sum reaches 1.+ rescaleCol col =+ let total = sum (M.elems col)+ in if total < 1+ then M.map realToFrac col+ else M.map (\x -> realToFrac (x / total * target)) col+ target = 0.95 :: Double++------------------------------------------------------------------+-- * Table / matrix ingestion+------------------------------------------------------------------++-- | Build a network from a long-form edge list, deriving the node set from the+-- rows. A thin alias for @'tradeNetwork' []@ (which already unions in every+-- endpoint).+--+-- >>> let Right g = networkFromTable [(1,2),(2,3)] :: Either NetworkError (TradeNetwork Int)+-- >>> nodes g+-- [1,2,3]+-- >>> edges g+-- [(1,2),(2,3)]+networkFromTable :: Ord k => [(k, k)] -> Either NetworkError (TradeNetwork k)+networkFromTable = tradeNetwork []++-- | Build both the network and the coefficient table from a long-form+-- @(supplier, buyer, coef)@ table in one step. The network's edges are exactly+-- the table's @(supplier, buyer)@ pairs, so the @supp(A) ⊆ edges(G)@ invariant+-- holds automatically.+--+-- >>> let Right (g, a) = coefficientsFromTable [(1,3,0.2),(2,3,0.5)] :: Either NetworkError (TradeNetwork Int, InputCoefficients Int Double)+-- >>> edges g+-- [(1,3),(2,3)]+-- >>> inputsOf a 3+-- [(1,0.2),(2,0.5)]+coefficientsFromTable :: (Ord k, HatVal v)+ => [(k, k, v)]+ -> Either NetworkError (TradeNetwork k, InputCoefficients k v)+coefficientsFromTable triples = do+ g <- tradeNetwork [] [ (i, j) | (i, j, _) <- triples ]+ a <- inputCoefficients g triples+ Right (g, a)++-- | Build a network and coefficient table from a /dense/ matrix presented as a+-- node list plus a lookup function @a i j@ (= the coefficient on edge+-- @(supplier i, buyer j)@). The support is derived by dropping zero (and+-- error-valued) cells, mirroring the older dense-matrix examples in one call.+-- Self-cells @(i, i)@ are skipped. Total, so it returns the pair directly+-- (no 'NetworkError': the support is consistent by construction).+--+-- >>> let m i j = if i < j then fromIntegral (i + j) else 0 :: Double+-- >>> let (g, a) = fromCoefficientMatrix [1,2,3] m+-- >>> edges g+-- [(1,2),(1,3),(2,3)]+-- >>> coefficient a 1 3+-- Just 4.0+fromCoefficientMatrix :: (Ord k, HatVal v)+ => [k] -> (k -> k -> v) -> (TradeNetwork k, InputCoefficients k v)+fromCoefficientMatrix ks a =+ (buildNetwork nodeSet es, InputCoefficients byBuyer)+ where+ nodeSet = S.fromList ks+ xs = S.toAscList nodeSet+ cells = [ (i, j, v)+ | i <- xs, j <- xs, i /= j+ , let v = a i j+ , not (EJ.isZeroValue v), not (EJ.isErrorValue v) ]+ es = [ (i, j) | (i, j, _) <- cells ]+ byBuyer = foldl' (\m (i, j, v) -> M.insertWith M.union j (M.singleton i v) m) M.empty cells++------------------------------------------------------------------+-- * Internal sampling helpers+------------------------------------------------------------------++-- | Uniform sampling without replacement of @n@ elements from a list,+-- deterministic in the 'StdGen'. Returns the chosen elements (in the order they+-- were drawn) and the advanced generator. If @n >= length xs@ the whole list is+-- returned.+sampleWithout :: StdGen -> Int -> [a] -> ([a], StdGen)+sampleWithout g0 n xs0 = go g0 (max 0 n) xs0 []+ where+ go g _ [] acc = (reverse acc, g)+ go g k xs acc+ | k <= 0 = (reverse acc, g)+ | otherwise =+ let (ix, g1) = randomR (0, length xs - 1) g+ (chosen, rest) = pick ix xs+ in go g1 (k - 1) rest (chosen : acc)+ pick i ys = case splitAt i ys of+ (pre, y : post) -> (y, pre ++ post)+ (pre, []) -> (last pre, init pre) -- unreachable (ix in range)++-- | Weighted sampling without replacement: pick @n@ elements, each draw+-- proportional to its weight, deterministic in the 'StdGen'.+sampleWeightedWithout :: StdGen -> Int -> [(a, Double)] -> ([a], StdGen)+sampleWeightedWithout g0 n xs0 = go g0 (max 0 n) xs0 []+ where+ go g _ [] acc = (reverse acc, g)+ go g k ws acc+ | k <= 0 = (reverse acc, g)+ | otherwise =+ let total = sum (map snd ws)+ (u, g1) = randomR (0, total) g :: (Double, StdGen)+ (chosen, rest) = drawAt u ws+ in go g1 (k - 1) rest (chosen : acc)+ drawAt u ws = walk u ws []+ where+ walk _ [] seen = case reverse seen of+ ((a, _) : _) -> (a, []) -- exhausted (shouldn't happen)+ [] -> error "sampleWeightedWithout: empty"+ walk acc ((a, w) : rest) seen+ | acc <= w = (a, reverse seen ++ rest)+ | otherwise = walk (acc - w) rest ((a, w) : seen)++------------------------------------------------------------------+-- * Small utilities+------------------------------------------------------------------++-- | Strict left @foldM@ over 'Either', short-circuiting on the first 'Left'.+foldM' :: (b -> a -> Either e b) -> b -> [a] -> Either e b+foldM' f = go+ where+ go !acc [] = Right acc+ go !acc (x : xs) = case f acc x of+ Left e -> Left e+ Right b' -> go b' xs
+ src/ExchangeAlgebra/Simulation/Optimize.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE TypeFamilies #-}++{- |+ Module : ExchangeAlgebra.Simulation.Optimize+ Copyright : (c) Kaya Akagi. 2026+ License : OtherLicense++ A pluggable optimization solver interface.++ Import this module for the strategy-agnostic 'Solver' class and+ 'Direction'. Concrete strategies live in sibling modules:++ * "ExchangeAlgebra.Simulation.Optimize.Annealing" — simulated annealing over an+ arbitrary candidate type.+ * "ExchangeAlgebra.Simulation.Optimize.GA" — a real-coded genetic algorithm over+ numeric vector chromosomes.++ == Entry points++ +----------------------+------------------------------------------++ | entry point | use |+ +======================+==========================================++ | 'Solver' | Implement or run an optimization method. |+ +----------------------+------------------------------------------++ | 'Direction' | Choose maximization or minimization. |+ +----------------------+------------------------------------------++ | 'orient' | Convert a score to minimization energy. |+ +----------------------+------------------------------------------+++ New strategies (differential evolution, particle swarm, CMA-ES, ...)+ are added by defining a new strategy type with a 'Solver' instance;+ the interface itself does not change.++ == Design notes++ * The objective is monadic (@'Candidate' strategy -> m Double@) so that+ it can run stateful simulations, e.g. the @ST s@ state spaces of+ "ExchangeAlgebra.Simulate". Solvers only require 'Monad', nothing+ stronger.+ * Randomness is threaded internally by each solver from a seed in its+ 'Config' (a pure generator, sequentially updated), so no random-monad+ constraint leaks into the objective. Runs are reproducible given the+ same seed /provided the objective itself is deterministic/ (same+ state transitions, no external randomness, clocks or concurrency).+ * The returned 'Double' is the objective value observed when the+ returned candidate was evaluated, in the user's orientation.+ Solvers never re-evaluate a candidate they have already scored+ (re-evaluation would be a visible side effect for stateful+ objectives such as @ST s@ simulations).+ * Objective values must be finite: solvers reject @NaN@ and+ infinities with a call to 'error' as soon as they are observed,+ because non-finite energies silently break comparisons, sorting+ and best-candidate tracking. Configurations are also validated+ up front (fail-fast; invalid settings are never clamped).+ * This subsystem is a generic numeric layer: it does not touch the+ redundant-algebra core ("ExchangeAlgebra.Algebra",+ "ExchangeAlgebra.Journal") and imposes no Hat\/Not or non-negativity+ semantics on candidates. Objective values are plain 'Double's in the+ user's orientation (see 'Direction').+ * Parallel evaluation of candidates is deliberately left to the caller+ (e.g. run several 'optimize' calls with different seeds and pick the+ best); solvers themselves evaluate sequentially.+-}+module ExchangeAlgebra.Simulation.Optimize+ ( -- * Solver interface+ Solver (..)+ -- * Optimization direction+ , Direction (..)+ , orient+ ) where++-- | Whether the objective is to be maximized or minimized.+--+-- Solvers convert objective values to an internal /minimization energy/+-- ('orient') and report the final score back in the user's orientation.+data Direction = Maximize | Minimize+ deriving (Show, Eq)++-- | Convert a score in the user's orientation to a minimization energy.+--+-- >>> orient Minimize 3.0+-- 3.0+--+-- >>> orient Maximize 3.0+-- -3.0+orient :: Direction -> Double -> Double+orient Minimize = id+orient Maximize = negate+{-# INLINE orient #-}++-- | A pluggable optimization strategy.+--+-- @strategy@ is a dispatch value carrying no data of its own — a+-- /proxy/ such as @GA@ ("ExchangeAlgebra.Simulation.Optimize.GA") or @Annealing@+-- ("ExchangeAlgebra.Simulation.Optimize.Annealing"). The associated types fix what+-- a candidate solution looks like and what configuration the strategy+-- needs.+class Solver strategy where+ -- | Candidate solution type searched by this strategy.+ type Candidate strategy+ -- | Strategy-specific configuration (schedules, rates, seed,+ -- 'Direction', ...).+ type Config strategy++ -- | Run the optimizer: given a configuration, a monadic objective and+ -- an initial candidate, return the best candidate found together+ -- with its objective value (in the user's orientation).+ optimize :: Monad m+ => strategy+ -> Config strategy+ -> (Candidate strategy -> m Double) -- ^ objective+ -> Candidate strategy -- ^ initial candidate+ -> m (Candidate strategy, Double)
+ src/ExchangeAlgebra/Simulation/Optimize/Annealing.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE BangPatterns #-}++{- |+ Module : ExchangeAlgebra.Simulation.Optimize.Annealing+ Copyright : (c) Kaya Akagi. 2026+ License : OtherLicense++ Simulated annealing over an arbitrary candidate type.++ The candidate type is free (@a@ in @t'Annealing' a@): investment+ schedules, parameter records, vectors, ... — anything the+ 'acNeighbor' move can perturb. The objective may be stateful+ (e.g. run an @ST s@ simulation); see "ExchangeAlgebra.Simulation.Optimize"+ for the interface contract.++ == Example++ Minimize @(x - 3)^2@ over 'Double':++ > import ExchangeAlgebra.Simulation.Optimize+ > import ExchangeAlgebra.Simulation.Optimize.Annealing+ > import System.Random (uniformR)+ > import Data.Functor.Identity (runIdentity)+ >+ > cfg :: AnnealingConfig Double+ > cfg = AnnealingConfig+ > { acDirection = Minimize+ > , acSteps = 2000+ > , acSchedule = geometricCooling 1.0 0.995+ > , acNeighbor = \g x -> let (d, g') = uniformR (-0.5, 0.5) g+ > in (x + d, g')+ > , acAccept = metropolis+ > , acSeed = 42+ > }+ >+ > best :: (Double, Double)+ > best = runIdentity (optimize Annealing cfg (\x -> pure ((x - 3)^2)) 0)+-}+module ExchangeAlgebra.Simulation.Optimize.Annealing+ ( -- * Strategy+ Annealing (..)+ , AnnealingConfig (..)+ -- * Standard building blocks+ , geometricCooling+ , metropolis+ ) where++import ExchangeAlgebra.Simulation.Optimize+import System.Random (StdGen, mkStdGen, uniformR)++-- | Simulated-annealing strategy proxy. The phantom @a@ is the candidate+-- type; it is inferred from the t'AnnealingConfig' at the call site, so+-- plain @'optimize' Annealing cfg obj x0@ needs no annotation.+data Annealing a = Annealing++-- | Configuration of one annealing run.+--+-- Energies passed to 'acAccept' are /minimization-oriented/ ('orient'+-- applied), regardless of 'acDirection'.+data AnnealingConfig a = AnnealingConfig+ { acDirection :: Direction+ -- ^ Whether the objective is maximized or minimized.+ , acSteps :: Int+ -- ^ Total number of proposal steps (@>= 0@; @0@ evaluates the+ -- initial candidate once and returns it).+ , acSchedule :: Int -> Double+ -- ^ Cooling schedule: 1-based step number to temperature.+ -- Each temperature must be finite and @>= 0@ (@0@ acts as greedy+ -- acceptance); anything else is rejected with 'error'.+ , acNeighbor :: StdGen -> a -> (a, StdGen)+ -- ^ Proposal move: perturb the current candidate, threading the+ -- solver's random generator.+ , acAccept :: Double -> Double -> Double -> Double+ -- ^ Acceptance rule: @temperature -> current energy -> proposed+ -- energy -> probability@. The probability must be finite and in+ -- @[0, 1]@ (validated at every step). See 'metropolis'.+ , acSeed :: Int+ -- ^ Seed of the solver's internal random generator.+ }++-- | Geometric cooling: @geometricCooling t0 alpha step = t0 * alpha^(step-1)@.+--+-- Typical use: @'geometricCooling' 1.0 0.995@.+geometricCooling :: Double -- ^ initial temperature @t0@+ -> Double -- ^ decay factor @alpha@ (usually just below 1)+ -> Int -- ^ 1-based step+ -> Double+geometricCooling t0 alpha step = t0 * alpha ^^ (step - 1)++-- | The Metropolis acceptance rule: always accept an improvement,+-- otherwise accept with probability @exp ((e - e') / t)@.+-- At @t = 0@ this degenerates to greedy acceptance.+metropolis :: Double -- ^ temperature+ -> Double -- ^ current energy+ -> Double -- ^ proposed energy+ -> Double+metropolis t e e'+ | e' <= e = 1+ | t <= 0 = 0+ | otherwise = exp ((e - e') / t)++instance Solver (Annealing a) where+ type Candidate (Annealing a) = a+ type Config (Annealing a) = AnnealingConfig a++ optimize _ cfg obj x0+ | acSteps cfg < 0+ = error ("ExchangeAlgebra.Optimize.Annealing: acSteps must be >= 0, got "+ ++ show (acSteps cfg))+ | otherwise+ = do e0 <- energyOf dir "initial candidate" (obj x0)+ go 1 (mkStdGen (acSeed cfg)) x0 e0 x0 e0+ where+ dir = acDirection cfg+ go !step !g !cur !eCur !best !eBest+ | step > acSteps cfg = return (best, orient dir eBest)+ -- orient is self-inverse: energy back to the user's score.+ | otherwise = do+ let t = checkTemperature step (acSchedule cfg step)+ (cand, g1) = acNeighbor cfg g cur+ eCand <- energyOf dir ("step " ++ show step) (obj cand)+ let p = checkProbability step (acAccept cfg t eCur eCand)+ (u, g2) = uniformR (0 :: Double, 1) g1+ accepted = p >= 1 || u < p+ (cur', eCur') | accepted = (cand, eCand)+ | otherwise = (cur, eCur)+ (best', eBest') | eCand < eBest = (cand, eCand)+ | otherwise = (best, eBest)+ go (step + 1) g2 cur' eCur' best' eBest'++-- | Evaluate the objective and convert to minimization energy, rejecting+-- non-finite scores (see the contract in "ExchangeAlgebra.Simulation.Optimize").+energyOf :: Monad m => Direction -> String -> m Double -> m Double+energyOf dir what mScore = do+ s <- mScore+ if isNaN s || isInfinite s+ then error ("ExchangeAlgebra.Optimize.Annealing: objective returned "+ ++ "a non-finite score (" ++ show s ++ ") at " ++ what)+ else return (orient dir s)++checkTemperature :: Int -> Double -> Double+checkTemperature step t+ | isNaN t || isInfinite t || t < 0+ = error ("ExchangeAlgebra.Optimize.Annealing: acSchedule returned an "+ ++ "invalid temperature (" ++ show t ++ ") at step " ++ show step)+ | otherwise = t++checkProbability :: Int -> Double -> Double+checkProbability step p+ | isNaN p || isInfinite p || p < 0 || p > 1+ = error ("ExchangeAlgebra.Optimize.Annealing: acAccept returned an "+ ++ "invalid probability (" ++ show p ++ ") at step " ++ show step)+ | otherwise = p
+ src/ExchangeAlgebra/Simulation/Optimize/GA.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE BangPatterns #-}++{- |+ Module : ExchangeAlgebra.Simulation.Optimize.GA+ Copyright : (c) Kaya Akagi. 2026+ License : OtherLicense++ A real-coded genetic algorithm over numeric vector chromosomes+ (@'U.Vector' 'Double'@).++ Generational scheme with elitism: tournament selection, uniform+ crossover and per-gene Gaussian mutation. The initial population is+ the (clamped) initial candidate plus Gaussian perturbations of it+ ('gaInitSpread'), so 'optimize' needs no separate population input.+ Elites carry their already-observed scores into the next generation+ and are never re-evaluated.++ Blend/simulated-binary crossover (BLX-alpha, SBX) are deliberately+ /not/ options of t'GAConfig'; if needed they should become separate+ strategies so each strategy's rates keep one clear meaning.++ == Example++ Minimize the 3-dimensional sphere function @sum (v - 1)^2@:++ > import ExchangeAlgebra.Simulation.Optimize+ > import ExchangeAlgebra.Simulation.Optimize.GA+ > import qualified Data.Vector.Unboxed as U+ > import Data.Functor.Identity (runIdentity)+ >+ > sphere :: U.Vector Double -> Double+ > sphere v = U.sum (U.map (\x -> (x - 1) ^ 2) v)+ >+ > best :: (U.Vector Double, Double)+ > best = runIdentity+ > (optimize GA defaultGAConfig (pure . sphere) (U.replicate 3 0))+-}+module ExchangeAlgebra.Simulation.Optimize.GA+ ( -- * Strategy+ GA (..)+ , GAConfig (..)+ , defaultGAConfig+ ) where++import ExchangeAlgebra.Simulation.Optimize+import qualified Data.List as L+import Data.Ord (comparing)+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as U+import System.Random (StdGen, mkStdGen, uniformR)++-- | Genetic-algorithm strategy proxy.+data GA = GA++-- | Configuration of one GA run. See 'defaultGAConfig' for a starting+-- point; all fields are validated up front (fail-fast 'error', never+-- clamped).+data GAConfig = GAConfig+ { gaDirection :: Direction+ -- ^ Whether the objective is maximized or minimized.+ , gaPopulationSize :: Int+ -- ^ Number of individuals per generation (@>= 1@).+ , gaGenerations :: Int+ -- ^ Number of generations (@>= 0@; @0@ evaluates only the initial+ -- population).+ , gaCrossoverRate :: Double+ -- ^ Probability that a selected pair undergoes uniform crossover+ -- (in @[0, 1]@); otherwise the first parent is copied.+ , gaMutationRate :: Double+ -- ^ Per-gene mutation probability (in @[0, 1]@).+ , gaMutationScale :: Double+ -- ^ Standard deviation of the Gaussian gene perturbation (@>= 0@).+ , gaTournamentSize :: Int+ -- ^ Tournament size for parent selection (@>= 1@; @1@ degenerates+ -- to uniform random selection, which barely selects at all).+ , gaEliteCount :: Int+ -- ^ Number of best individuals carried over unchanged+ -- (@0 <= gaEliteCount <= gaPopulationSize@).+ , gaInitSpread :: Double+ -- ^ Standard deviation of the Gaussian spread used to build the+ -- initial population around the initial candidate (@>= 0@).+ , gaBounds :: Maybe (U.Vector (Double, Double))+ -- ^ Optional per-gene @(low, high)@ box constraints. Must have the+ -- same length as the chromosome; every generated gene (initial+ -- spread and mutation) is clamped into its interval.+ , gaSeed :: Int+ -- ^ Seed of the solver's internal random generator.+ }++-- | A reasonable starting configuration: minimization, population 50,+-- 100 generations, crossover rate 0.9, per-gene mutation rate 0.1 with+-- scale 0.1, tournament size 3, 2 elites, initial spread 1.0, no+-- bounds, seed 42. Override fields as needed.+defaultGAConfig :: GAConfig+defaultGAConfig = GAConfig+ { gaDirection = Minimize+ , gaPopulationSize = 50+ , gaGenerations = 100+ , gaCrossoverRate = 0.9+ , gaMutationRate = 0.1+ , gaMutationScale = 0.1+ , gaTournamentSize = 3+ , gaEliteCount = 2+ , gaInitSpread = 1.0+ , gaBounds = Nothing+ , gaSeed = 42+ }++instance Solver GA where+ type Candidate GA = U.Vector Double+ type Config GA = GAConfig++ optimize _ cfg obj x0 = do+ validateConfig cfg x0+ let x0' = clampTo (gaBounds cfg) x0+ (rest, g1) = initialPopulation cfg x0' (mkStdGen (gaSeed cfg))+ pop0 <- mapM evalOne (x0' : rest)+ let best0 = L.minimumBy (comparing snd) pop0+ (bc, be) <- go 1 g1 pop0 best0+ return (bc, orient dir be)+ where+ dir = gaDirection cfg++ evalOne c = do+ s <- obj c+ if isNaN s || isInfinite s+ then error ("ExchangeAlgebra.Optimize.GA: objective returned "+ ++ "a non-finite score (" ++ show s ++ ")")+ else return (c, orient dir s)++ go !gen !g !pop !best+ | gen > gaGenerations cfg = return best+ | otherwise = do+ let sorted = L.sortBy (comparing snd) pop+ elites = take (gaEliteCount cfg) sorted+ nChildren = gaPopulationSize cfg - gaEliteCount cfg+ pool = V.fromList sorted+ (childs, g') = makeChildren cfg pool nChildren g+ children <- mapM evalOne childs+ let pop' = elites ++ children+ best' = L.minimumBy (comparing snd) (best : pop')+ go (gen + 1) g' pop' best'++-- ------------------------------------------------------------------+-- Pure genetics (random generator threaded explicitly)+-- ------------------------------------------------------------------++-- | Initial population (without the seed candidate itself): Gaussian+-- perturbations of the seed candidate, clamped to the bounds.+initialPopulation :: GAConfig -> U.Vector Double -> StdGen+ -> ([U.Vector Double], StdGen)+initialPopulation cfg x0 = goN (gaPopulationSize cfg - 1)+ where+ goN 0 g = ([], g)+ goN k g = let (v, g1) = perturbAll x0 g+ (vs, g2) = goN (k - 1) g1+ in (v : vs, g2)+ perturbAll v g =+ let (genes, g') = threadGenes step (U.toList v) g+ in (clampTo (gaBounds cfg) (U.fromList genes), g')+ where+ step x gg = let (z, gg') = gauss gg+ in (x + gaInitSpread cfg * z, gg')++-- | Produce @k@ children by tournament selection, uniform crossover and+-- Gaussian mutation.+makeChildren :: GAConfig -> V.Vector (U.Vector Double, Double) -> Int -> StdGen+ -> ([U.Vector Double], StdGen)+makeChildren cfg pool = goN+ where+ goN 0 g = ([], g)+ goN k g = let (c, g1) = makeOne g+ (cs, g2) = goN (k - 1) g1+ in (c : cs, g2)++ makeOne g0 =+ let (p1, g1) = tournament g0+ (p2, g2) = tournament g1+ (u, g3) = uniformR (0 :: Double, 1) g2+ (raw, g4) = if u < gaCrossoverRate cfg+ then crossover p1 p2 g3+ else (p1, g3)+ (mut, g5) = mutate raw g4+ in (clampTo (gaBounds cfg) mut, g5)++ -- Tournament selection: best (lowest energy) of k uniform picks.+ tournament g0 = go (gaTournamentSize cfg) g0 Nothing+ where+ go 0 g acc = case acc of+ Just (c, _) -> (c, g)+ Nothing -> error "ExchangeAlgebra.Optimize.GA: empty tournament"+ go k g acc =+ let (i, g') = uniformR (0, V.length pool - 1) g+ cand = pool V.! i+ acc' = case acc of+ Just (_, e) | e <= snd cand -> acc+ _ -> Just cand+ in go (k - 1 :: Int) g' acc'++ -- Uniform crossover: each gene comes from either parent with p = 1/2.+ crossover p1 p2 g0 =+ let (genes, g') = crossGenes (U.toList p1) (U.toList p2) g0+ in (U.fromList genes, g')+ crossGenes [] _ g = ([], g)+ crossGenes _ [] g = ([], g)+ crossGenes (a:as) (b:bs) g =+ let (u, g1) = uniformR (0 :: Double, 1) g+ (rest, g2) = crossGenes as bs g1+ in ((if u < 0.5 then a else b) : rest, g2)++ -- Per-gene Gaussian mutation.+ mutate v g0 =+ let (genes, g') = threadGenes step (U.toList v) g0+ in (U.fromList genes, g')+ where+ step x g =+ let (u, g1) = uniformR (0 :: Double, 1) g+ in if u < gaMutationRate cfg+ then let (z, g2) = gauss g1+ in (x + gaMutationScale cfg * z, g2)+ else (x, g1)++-- | Thread the generator through a per-gene transformation.+threadGenes :: (Double -> StdGen -> (Double, StdGen))+ -> [Double] -> StdGen -> ([Double], StdGen)+threadGenes _ [] g = ([], g)+threadGenes f (x:xs) g =+ let (y, g1) = f x g+ (ys, g2) = threadGenes f xs g1+ in (y : ys, g2)++-- | One standard-normal draw via Box–Muller. The first uniform is lifted+-- away from zero so @log u1@ stays finite. (The unguarded @normal@ in+-- "ExchangeAlgebra.Simulate" has a @log 0@ failure mode and must not be+-- copied here.)+gauss :: StdGen -> (Double, StdGen)+gauss g0 =+ let (u1raw, g1) = uniformR (0 :: Double, 1) g0+ u1 = max 2.2250738585072014e-308 u1raw -- smallest normal+ (u2, g2) = uniformR (0 :: Double, 1) g1+ in (sqrt (-2 * log u1) * cos (2 * pi * u2), g2)++-- | Clamp each gene into its bound interval (identity without bounds).+clampTo :: Maybe (U.Vector (Double, Double)) -> U.Vector Double -> U.Vector Double+clampTo Nothing v = v+clampTo (Just bs) v = U.zipWith (\(lo, hi) x -> max lo (min hi x)) bs v++-- ------------------------------------------------------------------+-- Fail-fast configuration validation (see ExchangeAlgebra.Simulation.Optimize)+-- ------------------------------------------------------------------++validateConfig :: Monad m => GAConfig -> U.Vector Double -> m ()+validateConfig cfg x0+ | gaPopulationSize cfg < 1+ = bad ("gaPopulationSize must be >= 1, got " ++ show (gaPopulationSize cfg))+ | gaGenerations cfg < 0+ = bad ("gaGenerations must be >= 0, got " ++ show (gaGenerations cfg))+ | gaEliteCount cfg < 0 || gaEliteCount cfg > gaPopulationSize cfg+ = bad ("gaEliteCount must be in [0, gaPopulationSize], got "+ ++ show (gaEliteCount cfg))+ | gaTournamentSize cfg < 1+ = bad ("gaTournamentSize must be >= 1, got " ++ show (gaTournamentSize cfg))+ | badRate (gaCrossoverRate cfg)+ = bad ("gaCrossoverRate must be a finite value in [0,1], got "+ ++ show (gaCrossoverRate cfg))+ | badRate (gaMutationRate cfg)+ = bad ("gaMutationRate must be a finite value in [0,1], got "+ ++ show (gaMutationRate cfg))+ | badScale (gaMutationScale cfg)+ = bad ("gaMutationScale must be finite and >= 0, got "+ ++ show (gaMutationScale cfg))+ | badScale (gaInitSpread cfg)+ = bad ("gaInitSpread must be finite and >= 0, got "+ ++ show (gaInitSpread cfg))+ | U.null x0+ = bad "the initial candidate (chromosome) must be non-empty"+ | Just bs <- gaBounds cfg, U.length bs /= U.length x0+ = bad ("gaBounds length (" ++ show (U.length bs)+ ++ ") does not match the chromosome length (" ++ show (U.length x0) ++ ")")+ | Just bs <- gaBounds cfg+ , Just (i, (lo, hi)) <- badBound bs+ = bad ("gaBounds at index " ++ show i ++ " is invalid: "+ ++ show (lo, hi) ++ " (need finite lo <= hi)")+ | otherwise = return ()+ where+ bad msg = error ("ExchangeAlgebra.Optimize.GA: " ++ msg)+ badRate r = isNaN r || isInfinite r || r < 0 || r > 1+ badScale s = isNaN s || isInfinite s || s < 0+ badBound bs = U.ifoldr+ (\i b acc -> if invalid b then Just (i, b) else acc) Nothing bs+ where invalid (lo, hi) =+ isNaN lo || isInfinite lo || isNaN hi || isInfinite hi || lo > hi
+ src/ExchangeAlgebra/Simulation/Output.hs view
@@ -0,0 +1,146 @@+{- |+ Module : ExchangeAlgebra.Simulation.Output+ Copyright : (c) Kaya Akagi. 2018-2026+ Maintainer : yakagika@icloud.com++ Released under the OWL license++ CSV output of simulation results. Import this module to stream function+ results as time-series CSV without requiring Chart.++ == Operations++ +---------------------------------+---------------------------------------++ | operation | CSV output |+ +=================================+=======================================++ | 'writeFuncResults' | Evaluate each function at each time. |+ +---------------------------------+---------------------------------------++ | 'writeFuncResultsWithContext' | Build one context per time and share |+ | | it across the functions. |+ +---------------------------------+---------------------------------------+++ Both operations use "ExchangeAlgebra.Simulate" for its 'StateTime'+ axis. Spill-file restoration remains in+ "ExchangeAlgebra.Simulate.Spill". Use 'writeTermIO' for term slices+ and 'writeIOMatrix' for two-dimensional matrices.+-}++module ExchangeAlgebra.Simulation.Output+ ( Header+ , writeFuncResults+ , writeFuncResultsWithContext+ , writeTermIO+ , writeIOMatrix+ ) where++import Control.Monad (forM)+import Data.Array.IO+import qualified Data.List as L+import ExchangeAlgebra.Algebra.Base.Representation (BaseClass)+import ExchangeAlgebra.Algebra.Value.Class (HatVal)+import ExchangeAlgebra.IO.Output.Csv (writeCSV)+import qualified Control.Monad as CM+import Control.Monad.ST+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import System.IO (IOMode(WriteMode), withFile)++import ExchangeAlgebra.Simulate (StateTime)++-- | CSV column header text.+type Header = T.Text++-- | Build a context for each term, then evaluate multiple functions together and output to CSV.+-- By sharing each term's context, expensive preprocessing (e.g., termJournal, transfer) is reduced to once per term.+-- Uses streaming output, so memory usage does not depend on the number of terms.+--+-- Complexity: O(T * (cost(buildCtx) + |funcs| * cost(f)))+writeFuncResultsWithContext+ :: ( StateTime t+ , Show x+ , Num x+ )+ => (a RealWorld -> t -> ST RealWorld c)+ -> [(Header, c -> ST RealWorld x)]+ -> (t,t)+ -> a RealWorld+ -> FilePath+ -> IO ()+writeFuncResultsWithContext buildCtx funcs (tStart,tEnd) wld path = do+ withFile path WriteMode $ \h -> do+ TIO.hPutStrLn h (toCsvRow (T.pack "Time" : map fst funcs))+ CM.forM_ [tStart .. tEnd] $ \t -> do+ vals <- stToIO $ do+ ctx <- buildCtx wld t+ CM.forM funcs $ \(_, f) -> f ctx+ let row = T.pack (show t) : map (T.pack . show) vals+ TIO.hPutStrLn h (toCsvRow row)++-- | Output the results of given functions as CSV time series data.+-- Internally uses streaming output via 'writeFuncResultsWithContext'.+--+-- Complexity: O(T * |funcs| * cost(f))+writeFuncResults+ :: ( StateTime t+ , Show x+ , Num x+ )+ => [(Header,(a RealWorld -> t -> ST RealWorld x))]+ -> (t,t)+ -> a RealWorld+ -> FilePath+ -> IO ()+writeFuncResults funcs termRange wld path =+ writeFuncResultsWithContext+ (\_ t -> return t)+ (map (\(header, f) -> (header, \t -> f wld t)) funcs)+ termRange+ wld+ path++{-# INLINE toCsvRow #-}+toCsvRow :: [T.Text] -> T.Text+toCsvRow = T.intercalate (T.pack ",") . map escapeCsv++{-# INLINE escapeCsv #-}+escapeCsv :: T.Text -> T.Text+escapeCsv t+ | T.any isSpecial t = T.concat [T.pack "\"", T.replace (T.pack "\"") (T.pack "\"\"") t, T.pack "\""]+ | otherwise = t+ where+ isSpecial c = c == ',' || c == '"' || c == '\n' || c == '\r'++-- | Convert a value to a text cell.+--+-- Complexity: O(show cost)+tshow :: (Show a) => a -> T.Text+tshow = T.pack . show+++-- | Output the Input-Output Table for a specified term in CSV format.+-- Outputs a slice of the specified term from a 3D array (term, row industry, column industry).+--+-- Complexity: O(r * c) (r = number of rows, c = number of columns)+writeTermIO :: (HatVal n,BaseClass b, Ix t, Ix b, Enum b)+ => FilePath -> t -> IOArray (t, b, b) n -> IO ()+writeTermIO path t arr = do+ ((_, c1Min, c2Min), (_, c1Max, c2Max)) <- getBounds arr+ let rows = [c1Min .. c1Max]+ let cols = [c2Min .. c2Max]+ body <- forM rows $ \r -> do+ cells <- forM cols $ \c -> tshow <$> readArray arr (t, r, c)+ pure (tshow r : cells)+ writeCSV path ((T.pack "" : L.map tshow cols) : body)++-- | Output a 2D IOArray (Input-Output Table or ripple effect matrix) in CSV format.+--+-- Complexity: O(r * c) (r = number of rows, c = number of columns)+writeIOMatrix :: FilePath -> IOArray (Int, Int) Double -> IO ()+writeIOMatrix path arr = do+ ((r1, c1), (r2, c2)) <- getBounds arr+ let rows = [r1 .. r2]+ let cols = [c1 .. c2]+ body <- forM rows $ \r -> do+ cells <- forM cols $ \c -> tshow <$> readArray arr (r, c)+ pure (tshow r : cells)+ writeCSV path ((T.pack "" : L.map tshow cols) : body)
src/ExchangeAlgebra/TrialBalance/Balance.hs view
@@ -1,14 +1,6 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module : ExchangeAlgebra.TrialBalance.Balance-Description : Shared account-balance representation and netting primitives for trial balances, presentation and consolidation. One type, one pair order (debit, credit), one netting rule.--Account balances retain their debit or credit direction while keeping scalar-values non-negative. All pair-valued functions in this module use debit first-and credit second.--}+-- | Compatibility exports for trial balance construction. module ExchangeAlgebra.TrialBalance.Balance+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.TrialBalance.Balance instead." #-} ( AccountBalance(..) , balancePair , addPair@@ -20,77 +12,4 @@ , accountBalances ) where -import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)--import ExchangeAlgebra.Algebra (Alg, HatVal, foldEntries)-import ExchangeAlgebra.Algebra.Base- ( AccountTitles- , ExBaseClass(getAccountTitle, whichSide)- , Side(..)- )---- | Net balance direction remains structural; values stay non-negative.-data AccountBalance v- = NoBalance- | DebitBalance v- | CreditBalance v- deriving (Show, Eq)---- | Convert a balance to a pair ordered as debit, then credit.-balancePair :: Num v => AccountBalance v -> (v, v)-balancePair NoBalance = (0, 0)-balancePair (DebitBalance value) = (value, 0)-balancePair (CreditBalance value) = (0, value)---- | Add two debit-credit pairs componentwise.-addPair :: Num v => (v, v) -> (v, v) -> (v, v)-addPair (leftDebit, leftCredit) (rightDebit, rightCredit) =- (leftDebit + rightDebit, leftCredit + rightCredit)---- | Net a debit-credit pair into one structural balance.-netPair :: (Ord v, Num v) => (v, v) -> AccountBalance v-netPair (debit, credit)- | debit == credit = NoBalance- | debit > credit = DebitBalance (debit - credit)- | otherwise = CreditBalance (credit - debit)---- | Combine two balances using the common netting rule.-combineBalances- :: (Ord v, Num v)- => AccountBalance v -> AccountBalance v -> AccountBalance v-combineBalances left right = netPair- (addPair (balancePair left) (balancePair right))---- | Look up an account balance, treating an absent account as balanced.-balanceFor :: AccountTitles -> Map AccountTitles (AccountBalance v)- -> AccountBalance v-balanceFor title = M.findWithDefault NoBalance title---- | Recover the structural side of a balance.-balanceSide :: AccountBalance v -> Side-balanceSide NoBalance = Side-balanceSide (DebitBalance _) = Debit-balanceSide (CreditBalance _) = Credit---- | Recover the non-negative scalar amount of a balance.-balanceAmount :: Num v => AccountBalance v -> v-balanceAmount NoBalance = 0-balanceAmount (DebitBalance value) = value-balanceAmount (CreditBalance value) = value---- | Aggregate postings by account and net each debit-credit total.-accountBalances- :: (HatVal v, ExBaseClass b)- => Alg v b- -> Map AccountTitles (AccountBalance v)-accountBalances = M.map netPair . foldEntries collect M.empty- where- collect totals value base =- M.insertWith addPair (getAccountTitle base)- (sidePair (whichSide base) value) totals--sidePair :: Num v => Side -> v -> (v, v)-sidePair Debit value = (value, 0)-sidePair Credit value = (0, value)-sidePair Side _ = (0, 0)+import ExchangeAlgebra.Accounting.TrialBalance.Balance
src/ExchangeAlgebra/TrialBalance/Validation.hs view
@@ -1,433 +1,12 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}--{- |-Module : ExchangeAlgebra.TrialBalance.Validation-Description : Explicit findings and policy gate between trial balances and reporting.--An exactly balanced trial balance can still be unsuitable for financial-statement preparation. This module detects reciprocal-account mismatches,-temporary-account residuals, closing-device residuals, abnormal-side balances,-and missing classification evidence independently of the double-entry balance-check.--Detection and acceptance are deliberately separate. 'trialBalanceFindings'-always reports the facts it can observe; 'validateTrialBalance' applies a-policy of type t'TrialBalancePolicy'. The module hides-the constructor v'ValidatedTrialBalance'. No-finding performs a reclassification automatically. A caller must record and-apply a transfer, then validate the resulting trial balance again.--Validation uses exact equality. Prefer an exact value type such as-@MoneyDecimal@ for deterministic accounting gates.--}+-- | Compatibility exports for trial balance validation. module ExchangeAlgebra.TrialBalance.Validation+{-# DEPRECATED "Import ExchangeAlgebra.Accounting.TrialBalance.Validation instead." #-} ( TrialBalanceStage(..)- , ReciprocalPolicy(..)- , TemporaryBalancePolicy(..)- , TrialBalancePolicy(..)- , strictTrialBalancePolicy- , standaloneTrialBalancePolicy , AccountBalance(..) , ReclassificationRule(..) , TrialBalanceInput(..) , TBFinding(..) , trialBalanceFindings- , findingBlocksPresentation- , ValidatedTrialBalance- , validateTrialBalance- , validatedTrialBalance- , validatedFindings- , validatedPolicy- , validatedStage- , validatedMaturityRequiredTitles ) where -import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List.NonEmpty as NE-import qualified Data.Map.Strict as M-import Data.Map.Strict (Map)-import qualified Data.Set as S-import Data.Set (Set)-import Data.Text (Text)-import qualified Data.Text as T--import ExchangeAlgebra.Algebra- ( Alg- , Exchange(decL, decR)- , HatVal(..)- , Redundant(norm)- , bases- , foldEntries- )-import ExchangeAlgebra.Algebra.Base- ( AccountDivision(..)- , AccountRole(..)- , AccountSemantics(asemDivisionSemantics,- asemHomeSideSemantics, asemRoles)- , AccountTitles(..)- , DivisionSemantics(..)- , Hat(..)- , HatBase((:<))- , HomeSideSemantics(..)- , Side(..)- , accountSemantics- , concreteAccountTitles- )-import ExchangeAlgebra.TrialBalance.Balance- ( AccountBalance(..)- , accountBalances- , balanceFor- , balanceSide- )---- | Processing point of the supplied trial balance.-data TrialBalanceStage- = BeforeClosing- | AfterClosing- deriving (Show, Eq)---- | Whether reciprocal accounts must tie in this validation context.-data ReciprocalPolicy- = RequireReciprocalMatch- | PermitStandaloneReciprocalBalance- deriving (Show, Eq)---- | Whether an explained temporary balance may pass the validation gate.-data TemporaryBalancePolicy- = RequireTemporaryAccountsCleared- | PermitExplainedTemporaryBalances- deriving (Show, Eq)---- | Policy applied after the complete finding list has been constructed.-data TrialBalancePolicy = TrialBalancePolicy- { _reciprocalPolicy :: ReciprocalPolicy- , _temporaryBalancePolicy :: TemporaryBalancePolicy- }- deriving (Show, Eq)---- | Combined/reporting default: reciprocal accounts must tie and temporary--- accounts must be cleared.-strictTrialBalancePolicy :: TrialBalancePolicy-strictTrialBalancePolicy = TrialBalancePolicy- RequireReciprocalMatch RequireTemporaryAccountsCleared---- | Standalone default: a branch or head-office control balance may remain,--- while an explained temporary balance is retained as an auditable finding.-standaloneTrialBalancePolicy :: TrialBalancePolicy-standaloneTrialBalancePolicy = TrialBalancePolicy- PermitStandaloneReciprocalBalance PermitExplainedTemporaryBalances---- | A rule identifies a condition and possible target titles. It supplies an--- instruction, not an automatic mutation.-data ReclassificationRule- = SideReclassificationRule- AccountTitles- Side- (NonEmpty AccountTitles)- | MaturityEvidenceRequired AccountTitles- deriving (Show, Eq)---- | Unvalidated trial-balance input and its external evidence.-data TrialBalanceInput v = TrialBalanceInput- { _trialBalanceElement :: Alg v (HatBase AccountTitles)- , _trialBalanceStage :: TrialBalanceStage- , _temporaryBalanceExplanations :: Map AccountTitles Text- , _reclassificationRules :: [ReclassificationRule]- , _maturityEvidenceTitles :: Set AccountTitles- }- deriving (Show)---- | Facts detected before financial-statement presentation.-data TBFinding v- = InvalidTrialBalanceValue v- | WildcardTrialBalanceAccount- | WildcardTrialBalanceSide- | UnbalancedTrialBalance v v- | ReciprocalMismatch- (AccountBalance v)- (AccountBalance v)- | StandaloneReciprocalBalance AccountTitles (AccountBalance v)- | UnresolvedTemporaryBalance AccountTitles (AccountBalance v)- | ExplainedTemporaryBalance AccountTitles (AccountBalance v) Text- | BlankTemporaryExplanation AccountTitles (AccountBalance v)- | ClosingDeviceResidual AccountTitles (AccountBalance v)- | DerivedCoordinateResidual AccountTitles (AccountBalance v)- | UnclosedNominalBalance AccountTitles (AccountBalance v)- | UnexplainedAbnormalBalance- AccountTitles Side (AccountBalance v)- | AbnormalBalanceWithReclassificationRule- AccountTitles (AccountBalance v) AccountTitles- | AmbiguousReclassification- AccountTitles (AccountBalance v) (NonEmpty AccountTitles)- | MissingMaturityEvidence AccountTitles- | InapplicableReclassificationRule ReclassificationRule- deriving (Show, Eq)---- | Detect every applicable finding. Structural errors suppress any operation--- that would call the partial legacy @whichSide@ function on wildcard bases.-trialBalanceFindings :: HatVal v => TrialBalanceInput v -> [TBFinding v]-trialBalanceFindings input- | not (null structuralFindings) = structuralFindings- | otherwise = balanceFindings- ++ reciprocalFindings balances- ++ temporaryFindings input balances- ++ closingFindings input balances- ++ abnormalFindings input balances- ++ maturityFindings input balances- ++ ruleConfigurationFindings input- where- alg = _trialBalanceElement input- structuralFindings =- [ InvalidTrialBalanceValue value- | value <- valuesOf alg- , isErrorValue value- ]- ++ [ WildcardTrialBalanceAccount- | _ :< AccountTitle <- bases alg- ]- ++ [ WildcardTrialBalanceSide- | HatNot :< _ <- bases alg- ]- debit = norm (decL alg)- credit = norm (decR alg)- balanceFindings =- [ UnbalancedTrialBalance debit credit | debit /= credit ]- balances = accountBalances alg---- | Decide whether one finding blocks presentation under a chosen policy.-findingBlocksPresentation :: TrialBalancePolicy -> TBFinding v -> Bool-findingBlocksPresentation policy finding = case finding of- ExplainedTemporaryBalance _ _ _ ->- _temporaryBalancePolicy policy == RequireTemporaryAccountsCleared- StandaloneReciprocalBalance _ _ ->- _reciprocalPolicy policy == RequireReciprocalMatch- ReciprocalMismatch _ _ -> True- InvalidTrialBalanceValue _ -> True- WildcardTrialBalanceAccount -> True- WildcardTrialBalanceSide -> True- UnbalancedTrialBalance _ _ -> True- UnresolvedTemporaryBalance _ _ -> True- BlankTemporaryExplanation _ _ -> True- ClosingDeviceResidual _ _ -> True- DerivedCoordinateResidual _ _ -> True- UnclosedNominalBalance _ _ -> True- UnexplainedAbnormalBalance _ _ _ -> True- AbnormalBalanceWithReclassificationRule _ _ _ -> True- AmbiguousReclassification _ _ _ -> True- MissingMaturityEvidence _ -> True- InapplicableReclassificationRule _ -> True---- | Trial balance accepted by a stated policy. The constructor is hidden.-data ValidatedTrialBalance v = ValidatedTrialBalance- (Alg v (HatBase AccountTitles))- [TBFinding v]- TrialBalancePolicy- TrialBalanceStage- (Set AccountTitles)---- | Apply a policy to the complete finding list.-validateTrialBalance- :: HatVal v- => TrialBalancePolicy- -> TrialBalanceInput v- -> Either (NonEmpty (TBFinding v)) (ValidatedTrialBalance v)-validateTrialBalance policy input =- case filter (findingBlocksPresentation policy) findings of- [] -> Right (ValidatedTrialBalance- (_trialBalanceElement input) findings policy- (_trialBalanceStage input) (maturityRequiredTitles input))- blocker : blockers -> Left (blocker :| blockers)- where- findings = trialBalanceFindings input---- | Recover the accepted algebra element.-validatedTrialBalance- :: ValidatedTrialBalance v -> Alg v (HatBase AccountTitles)-validatedTrialBalance (ValidatedTrialBalance alg _ _ _ _) = alg---- | Recover both blocking-policy-independent facts and permitted warnings.-validatedFindings :: ValidatedTrialBalance v -> [TBFinding v]-validatedFindings (ValidatedTrialBalance _ findings _ _ _) = findings---- | Recover the policy that admitted this trial balance.-validatedPolicy :: ValidatedTrialBalance v -> TrialBalancePolicy-validatedPolicy (ValidatedTrialBalance _ _ policy _ _) = policy---- | Recover the processing stage at which the trial balance was admitted.-validatedStage :: ValidatedTrialBalance v -> TrialBalanceStage-validatedStage (ValidatedTrialBalance _ _ _ stage _) = stage---- | Maturity-allocation obligations that crossed the validation boundary.-validatedMaturityRequiredTitles- :: ValidatedTrialBalance v -> Set AccountTitles-validatedMaturityRequiredTitles (ValidatedTrialBalance _ _ _ _ titles) = titles--maturityRequiredTitles :: TrialBalanceInput v -> Set AccountTitles-maturityRequiredTitles input = S.fromList- [ title- | MaturityEvidenceRequired title <- _reclassificationRules input- ]--valuesOf :: HatVal v => Alg v (HatBase AccountTitles) -> [v]-valuesOf = foldEntries (\values value _ -> value : values) []--reciprocalFindings- :: (Eq v)- => Map AccountTitles (AccountBalance v)- -> [TBFinding v]-reciprocalFindings balances =- case (branch, headOffice) of- (NoBalance, NoBalance) -> []- (NoBalance, balance) ->- [StandaloneReciprocalBalance HeadOfficeCurrentAccount balance]- (balance, NoBalance) ->- [StandaloneReciprocalBalance BranchCurrentAccount balance]- _ | reciprocalBalancesMatch branch headOffice -> []- | otherwise -> [ReciprocalMismatch branch headOffice]- where- branch = balanceFor BranchCurrentAccount balances- headOffice = balanceFor HeadOfficeCurrentAccount balances--reciprocalBalancesMatch :: Eq v => AccountBalance v -> AccountBalance v -> Bool-reciprocalBalancesMatch NoBalance NoBalance = True-reciprocalBalancesMatch (DebitBalance left) (CreditBalance right) = left == right-reciprocalBalancesMatch (CreditBalance left) (DebitBalance right) = left == right-reciprocalBalancesMatch _ _ = False--temporaryFindings- :: TrialBalanceInput v- -> Map AccountTitles (AccountBalance v)- -> [TBFinding v]-temporaryFindings input balances = concatMap finding temporaryTitles- where- temporaryTitles = case _trialBalanceStage input of- BeforeClosing -> titlesWithRole SuspenseOrClearingAccount- AfterClosing -> filter (/= CashOverShort)- (titlesWithRole SuspenseOrClearingAccount)- finding title = case balanceFor title balances of- NoBalance -> []- balance -> case M.lookup title (_temporaryBalanceExplanations input) of- Nothing -> [UnresolvedTemporaryBalance title balance]- Just explanation- | T.null (T.strip explanation) ->- [BlankTemporaryExplanation title balance]- | otherwise ->- [ExplainedTemporaryBalance title balance explanation]--closingFindings- :: TrialBalanceInput v- -> Map AccountTitles (AccountBalance v)- -> [TBFinding v]-closingFindings input balances = case _trialBalanceStage input of- BeforeClosing -> []- AfterClosing ->- concatMap residual (CashOverShort : titlesWithRole ClosingDevice)- ++ concatMap derivedResidual- (titlesWithRole PeriodResult ++ titlesWithRole ReportingSubtotal)- ++ [ UnclosedNominalBalance title balance- | (title, balance) <- M.toList balances- , hasBalance balance- , isNominal title- ]- where- residual title = case balanceFor title balances of- NoBalance -> []- balance -> [ClosingDeviceResidual title balance]- derivedResidual title = case balanceFor title balances of- NoBalance -> []- balance -> [DerivedCoordinateResidual title balance]- isNominal title = case accountSemantics title of- Just semantics -> case asemDivisionSemantics semantics of- StatementDivision Cost -> True- StatementDivision Revenue -> True- _ -> False- Nothing -> False- hasBalance NoBalance = False- hasBalance _ = True--abnormalFindings- :: TrialBalanceInput v- -> Map AccountTitles (AccountBalance v)- -> [TBFinding v]-abnormalFindings input balances = concatMap finding (M.toList balances)- where- finding (_, NoBalance) = []- finding (title, balance) = case expectedSide title of- Nothing -> []- Just expected- | balanceSide balance == expected -> []- | otherwise -> classifyRule title expected balance- classifyRule title expected balance = case ruleTargets title (balanceSide balance)- (_reclassificationRules input) of- [] -> [UnexplainedAbnormalBalance- title expected balance]- [target] -> [AbnormalBalanceWithReclassificationRule- title balance target]- target : targets -> [AmbiguousReclassification- title balance (target :| targets)]--maturityFindings- :: Eq v- => TrialBalanceInput v- -> Map AccountTitles (AccountBalance v)- -> [TBFinding v]-maturityFindings input balances =- [ MissingMaturityEvidence title- | MaturityEvidenceRequired title <- _reclassificationRules input- , balanceFor title balances /= NoBalance- , S.notMember title (_maturityEvidenceTitles input)- ]--expectedSide :: AccountTitles -> Maybe Side-expectedSide title = do- semantics <- accountSemantics title- if OrdinaryAccount `elem` asemRoles semantics- then case asemHomeSideSemantics semantics of- FixedHomeSide side -> Just side- ContextDependentHomeSide -> Nothing- NoFixedHomeSide -> Nothing- NoPostingSide -> Nothing- else Nothing--ruleTargets- :: AccountTitles- -> Side- -> [ReclassificationRule]- -> [AccountTitles]-ruleTargets title side = deduplicate S.empty . concatMap targets- where- deduplicate _ [] = []- deduplicate seen (candidate : candidates)- | candidate `S.member` seen = deduplicate seen candidates- | otherwise = candidate- : deduplicate (S.insert candidate seen) candidates- targets (SideReclassificationRule source trigger candidates)- | source == title && trigger == side = NE.toList candidates- | otherwise = []- targets (MaturityEvidenceRequired _) = []--ruleConfigurationFindings :: TrialBalanceInput v -> [TBFinding v]-ruleConfigurationFindings input =- [ InapplicableReclassificationRule rule- | rule <- _reclassificationRules input- , not (applicable rule)- ]- where- applicable (MaturityEvidenceRequired title) = title /= AccountTitle- applicable (SideReclassificationRule source trigger candidates) =- source /= AccountTitle- && trigger /= Side- && expectedSide source /= Nothing- && expectedSide source /= Just trigger- && all (targetAccepts trigger) (NE.toList candidates)- targetAccepts trigger target =- target /= AccountTitle && expectedSide target == Just trigger--titlesWithRole :: AccountRole -> [AccountTitles]-titlesWithRole role =- [ title- | title <- concreteAccountTitles- , Just semantics <- [accountSemantics title]- , role `elem` asemRoles semantics- ]+import ExchangeAlgebra.Accounting.TrialBalance.Validation
src/ExchangeAlgebra/Value.hs view
@@ -37,8 +37,11 @@ Numeric literals work without wrapping, because 'Num'/'Fractional' are derived: - > type Ledger = Journal Term MoneyDecimal (HatBase AccountTitles)- > entry = 10.5 :@ Hat:<Cash .+ 2 :@ Not:<Sales -- 10.5 and 2 are MoneyDecimal literals+ > import ExchangeAlgebra.Algebra+ > import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+ >+ > entry :: Alg MoneyDecimal (HatBase CountUnit)+ > entry = 10.5 .@ Hat :< Amount .+ 2 .@ Not :< Yen == Rounding @@ -47,140 +50,25 @@ proration, scalar product) at the point a monetary amount is /finalized/. Use 'bankersRound': it rounds half-to-even (the unbiased financial default; also GHC's 'Prelude.round' and IEEE-754's default mode). A ceiling variant ('ceilingRound') is- provided for the previous @rounding = ceiling@ behavior and for jurisdictions whose+ provided for jurisdictions whose rules differ. There is no single correct rule (e.g. Japanese consumption tax rounding varies by company), so the rounding function is explicit and swappable. -}-module ExchangeAlgebra.Value+module ExchangeAlgebra.Value {-# DEPRECATED "Use ExchangeAlgebra.Algebra.Value instead." #-} ( MoneyDecimal(..) , toDecimal , bankersRound , ceilingRound+ , MoneyParseError(..)+ , moneyDecimalFromText+ , moneyDecimalFromScientific , MoneyDouble(..) , toDouble ) where -import ExchangeAlgebra.Algebra (HatVal (..), Nearly (..))-import Data.Decimal (Decimal, DecimalRaw (Decimal), normalizeDecimal, roundTo')-import Data.Word (Word8)-import Data.Hashable (Hashable (..))-import Control.DeepSeq (NFData (..))-import qualified Data.Binary as Binary---- | A non-negative exact decimal value (wraps 'Data.Decimal.Decimal').------ Non-negativity is a /soft/ invariant, the same as for the @Double@ instance:--- it is not enforced by the constructor (intermediate subtraction inside @bar@/@(.-)@--- can produce negatives), but 'isErrorValue' reports @x < 0@ so the @(.@)@ smart--- constructor rejects negative postings.-newtype MoneyDecimal = MoneyDecimal Decimal- -- Num/Fractional are derived so numeric literals (@10.5@, @0.08@) work directly,- -- with no @MoneyDecimal@ wrapper at use sites. Show/Eq/Ord delegate to t'Decimal'.- -- 'Real' (and thus 'toRational') is derived so values can be converted to/from- -- @Double@ via 'realToFrac' at the simulation boundary: ABM parameters, input- -- coefficients and random draws stay 'Double', and are converted to t'MoneyDecimal'- -- only where they enter a ledger; final stock/profit amounts convert back for- -- reporting. The ledger arithmetic in between is exact.- deriving newtype (Eq, Ord, Show, Num, Fractional, Real)---- | Project out the underlying t'Decimal'.-toDecimal :: MoneyDecimal -> Decimal-toDecimal (MoneyDecimal d) = d---- 'Nearly': for an exact type there is no rounding noise to tolerate, so the--- tolerance argument is ignored and equality is exact. (Contrast the @Double@--- instance, which uses a scale-aware tolerance.)-instance Nearly MoneyDecimal where- {-# INLINE isNearly #-}- isNearly x y _ = x == y--instance HatVal MoneyDecimal where- {-# INLINE zeroValue #-}- zeroValue = MoneyDecimal 0- -- Exact decimals have no NaN/Infinity; the only "error value" is a negative- -- amount, which violates the non-negativity invariant of the algebra.- {-# INLINE isErrorValue #-}- isErrorValue (MoneyDecimal x) = x < 0- -- Render exactly (e.g. "0.3", "12.34"); unlike the Double instance there is no- -- fixed-2-decimal formatting, because the decimal value is already exact.- {-# INLINE showValue #-}- showValue (MoneyDecimal x) = show x---- 'Binary'/'Hashable' are defined here (not orphan) because 'Data.Decimal' ships--- neither, and 'Alg'/t'Journal' serialization and the binary spill path require--- @Binary v@. 'Binary' keeps the (places, mantissa) structure of t'Decimal'.--- 'Hashable' hashes the normalized form (trailing zeros removed), so values that--- are equal under 'Eq' (@1.0 == 1.00@) have the same hash.-instance Binary.Binary MoneyDecimal where- {-# INLINE put #-}- put (MoneyDecimal (Decimal places mantissa)) = do- Binary.put (places :: Word8)- Binary.put (mantissa :: Integer)- {-# INLINE get #-}- get = do- places <- Binary.get :: Binary.Get Word8- mantissa <- Binary.get :: Binary.Get Integer- pure (MoneyDecimal (Decimal places mantissa))--instance Hashable MoneyDecimal where- {-# INLINE hashWithSalt #-}- hashWithSalt s (MoneyDecimal d) = case normalizeDecimal d of- Decimal places mantissa -> s `hashWithSalt` places `hashWithSalt` mantissa--instance NFData MoneyDecimal where- {-# INLINE rnf #-}- rnf (MoneyDecimal (Decimal places mantissa)) = rnf places `seq` rnf mantissa----------------------------------------------------------------------- * MoneyDouble — fast IEEE-754 value type----------------------------------------------------------------------- | A fast IEEE-754 money value (wraps 'Prelude.Double').------ This is the @newtype@ counterpart of the bare-@Double@ instance: a dedicated,--- domain-specific money type so a ledger value cannot be silently confused with--- an ABM coefficient, a random draw, or any other raw 'Double'. Every instance--- it needs is owned here (via @deriving newtype@), so — exactly like--- t'MoneyDecimal' — there are no orphan instances. Use t'MoneyDouble' for the same--- speed as bare 'Double' while keeping the value type distinct in signatures.------ Trade-off vs t'MoneyDecimal': addition is /non-associative/ (FP), so @norm@ \/--- @bar@ can differ in the last ULP depending on construction order. It is fast--- and runs everywhere bare 'Double' does (subtraction is signed, so the--- intermediate negatives that arise inside @bar@\/@(.-)@ are fine — unlike--- @Number.NonNegative.Double@, whose @(-)@ /errors/ on a negative result).------ Non-negativity is the same /soft/ invariant as for t'MoneyDecimal' and bare--- 'Double': not enforced by the constructor, but 'isErrorValue' reports--- @isNaN x || isInfinite x || x < 0@, so the @(.\@)@ smart constructor and--- @(.*)@ reject negative\/non-finite values.-newtype MoneyDouble = MoneyDouble Double- -- All instances are coerced from the existing bare-'Double' instances- -- ('Nearly'/'HatVal' live in "ExchangeAlgebra.Algebra"; 'Binary'/'Hashable'/- -- 'NFData' come from the binary/hashable/deepseq packages), so t'MoneyDouble'- -- is a zero-cost wrapper with identical numeric behavior and 2-decimal- -- 'showValue' formatting.- deriving newtype ( Eq, Ord, Show, Num, Fractional, Real, RealFrac- , Nearly, HatVal, Hashable, NFData, Binary.Binary )---- | Project out the underlying 'Prelude.Double'.-toDouble :: MoneyDouble -> Double-toDouble (MoneyDouble d) = d---- | Round a value to @n@ decimal places using /banker's rounding/--- (round-half-to-even): the unbiased financial default. Ties go to the nearest--- even digit (@2.5 -> 2@, @3.5 -> 4@, @0.125 -> 0.12@), so repeated rounding over--- many transactions does not drift the total upward the way half-up does. This is--- 'Prelude.round' applied per 'Data.Decimal.roundTo''.------ Apply at the point a monetary amount is finalized after multiplication/division--- (tax, proration, scalar product). The core algebra (add/subtract) is exact and--- needs no rounding.-bankersRound :: Word8 -> MoneyDecimal -> MoneyDecimal-bankersRound places (MoneyDecimal d) = MoneyDecimal (roundTo' round places d)---- | Round a value to @n@ decimal places by rounding /up/ (ceiling). This preserves--- the previous library default (@rounding = ceiling@) and suits jurisdictions whose--- rules round up. Prefer 'bankersRound' unless a ceiling rule is specifically required.-ceilingRound :: Word8 -> MoneyDecimal -> MoneyDecimal-ceilingRound places (MoneyDecimal d) = MoneyDecimal (roundTo' ceiling places d)+import ExchangeAlgebra.Algebra.Value+ ( MoneyDecimal(..), toDecimal, bankersRound, ceilingRound+ , MoneyParseError(..), moneyDecimalFromText, moneyDecimalFromScientific+ , MoneyDouble(..), toDouble )+-- Preserve the existing Hashable Day and TimeOfDay instance visibility.+import ExchangeAlgebra.Algebra.Element.Representation ()
− src/ExchangeAlgebra/Write.hs
@@ -1,1044 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE BangPatterns #-}-{- |- Module : ExchangeAlgebra.Write- Copyright : (c) Kaya Akagi. 2018-2026- Maintainer : yakagika@icloud.com-- Released under the OWL license-- Package for Exchange Algebra defined by Hiroshi Deguchi.-- Exchange Algebra is an algebraic description of bookkeeping system.- Details are below.-- <https://www.springer.com/gp/book/9784431209850>-- <https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf>---}--module ExchangeAlgebra.Write- ( -- * CSV utilities- writeCSV- , csvTranspose- -- * Balance Sheet / P&L / Journal output- , writeBS- , bsRows- , writePL- , plRows- , writeJournal- , journalRows- , writeAccountOf- , writeAccountOfJournal- , accountLedgerRowsJournal- , writeCompoundTrialBalance- , compoundTrialBalanceRows- -- * Closing documents (決算書類)- , writeWorksheet- , worksheetRows- , writePostClosingTrialBalance- , postClosingTrialBalanceRows- , accountLedgerRows- -- * Simulation output- , writeTermIO- , writeIOMatrix- -- * Spill / Restore- --- -- | The canonical import location is "ExchangeAlgebra.Simulate.Spill".- -- This compatibility re-export is scheduled for removal in 0.6.0.0.- , restoreJournalFromBinarySpill- , restoreJournalFromBinarySpillChecked- -- * Helpers- , balanceOf- , tshow- , toSameLength- ) where--import qualified ExchangeAlgebra.Algebra as EA-import ExchangeAlgebra.Algebra-import qualified ExchangeAlgebra.Journal as EJ-import ExchangeAlgebra.Journal ((.|))--import qualified ExchangeAlgebra.Algebra.Transfer as ET-import qualified ExchangeAlgebra.Reporting.Group as RG--import ExchangeAlgebra.Simulate.Spill- ( restoreJournalFromBinarySpill- , restoreJournalFromBinarySpillChecked )--import qualified Data.List as L-import qualified Data.Text as T--import Control.Monad-import qualified Data.Set as Set-import qualified Data.HashMap.Strict as Map-import qualified Data.Map.Strict as OMap-import Data.Array.IO-import Data.Time (Day)-import System.IO (openFile, IOMode(WriteMode), hClose)-import qualified Data.Text.IO as TIO---- | Transpose a matrix of Text, padding shorter rows with empty Text.-csvTranspose :: [[T.Text]] -> [[T.Text]]-csvTranspose [] = []-csvTranspose mx = [ [ getCell r i | r <- mx ] | i <- [0 .. maxLen - 1] ]- where- maxLen = L.maximum (L.map L.length mx)- getCell row i- | i < L.length row = row !! i- | otherwise = T.empty---- | Write a matrix of Text as a CSV file. Each cell is quoted.-writeCSV :: FilePath -> [[T.Text]] -> IO ()-writeCSV path rows = do- h <- openFile path WriteMode- mapM_ (TIO.hPutStrLn h . toCsvLine) rows- hClose h- where- toCsvLine = T.intercalate (T.pack ",") . L.map quoteCell- quoteCell t = T.concat [T.pack "\"", T.replace (T.pack "\"") (T.pack "\"\"") t, T.pack "\""]---- | Helper to convert from Show to Text.------ Complexity: O(show cost)-tshow :: (Show a) => a -> T.Text-tshow = T.pack . show---- | Render one 'RG.RelativeAmount' as a statement cell. The magnitude is--- always non-negative (see "ExchangeAlgebra.Reporting.Group"); a deduction, or--- a net that its deductions pushed past zero, is shown with a leading @-@.--- The minus sign exists only here, in the rendered text — never in a value.------ Complexity: O(show cost)-renderRelative :: (HatVal n) => RG.RelativeAmount n -> T.Text-renderRelative amount- | RG.raBelowZero amount = T.cons '-' magnitude- | otherwise = magnitude- where- magnitude = tshow (RG.raMagnitude amount)---- | Flatten presentation blocks into @(label, value)@ statement cells.--- Gross and deduction rows are labeled by their account title, subtotal and--- net rows by the group's label.------ Complexity: O(r) (r = number of rows in the given blocks)-groupCells :: (HatVal n)- => [RG.PresentationGroupDef]- -> [(RG.PresentationGroupDef, [RG.GroupRow n])]- -> [(T.Text, T.Text)]-groupCells defs blocks =- [ (label (RG.grKind row), renderRelative (RG.grAmount row))- | (_, rows) <- blocks, row <- rows ]- where- label kind = case kind of- RG.GrossRow t -> tshow t- RG.DeductionRow t -> tshow t- RG.SubgroupRow key -> groupLabel key- RG.NetRow key -> groupLabel key- groupLabel key = maybe (tshow key) RG.pgLabel (RG.lookupGroupDef defs key)---- | Build the rows of a Balance Sheet, as a pure value (the part 'writeBS'--- renders to CSV).------ Internally applies @'ET.finalStockTransfer'@ -- the only netting on the--- /algebra/ this function does -- which closes every 'Cost'\/'Revenue'--- account into 'RetainedEarnings' via @('.-')@\/@bar@. The closed algebra is--- then partitioned by 'whichSide'\/'whatDiv' into assets (debit side) and--- liability\/equity (credit side, further split by division);--- 'decL'\/'decR'\/'EA.filter' only select entries, they do not aggregate them.------ __Contra accounts (Definition 7 amendment, Land 3).__ Contra assets--- (@whatDiv == Assets && isContra@, e.g. 貸倒引当金\/減価償却累計額) sit on--- the credit side, so a plain side partition would either drop them from the--- statement or file them under Liability. They are instead collected into the--- presentation groups of "ExchangeAlgebra.Reporting.Group" and shown as a--- real deduction — @gross lines → deduction lines → net line@ — inside the--- Asset column, replacing Land 2's temporary placement in the Liability--- column. A--- group is formed only when one of its contra accounts actually carries gross--- activity, so a chart that contains no contra posting keeps its ordinary--- rows. Deduction--- and negative-net cells carry a leading @-@; values themselves stay in--- \(\mathbb{R}_0^+\).------ Column totals use the ordinary cells plus each outermost group's net, so--- nested subtotal rows are not counted twice. __Known limitation:__ an--- ungrouped non-contra asset whose net balance is--- on the credit side (an abnormal balance) is still not displayed, and is now--- excluded from the totals as well; the side-versus-division placement of--- abnormal balances is a separate pre-existing issue, not part of the contra--- amendment.--- Presentation groups are financial-statement aggregates keyed by account--- title. With a multi-axis base, grouped titles therefore aggregate across--- the remaining axes; ungrouped titles retain the legacy per-entry layout.------ Layout:------ > Asset | <titles...> | Total--- > | <values...> | <asset total>--- > Liability | <titles...> | Equity | <titles...> | Total--- > | <values...> | | <values...> | <liability+equity total>------ ==== __Examples__------ Cash 100 (asset), a loan 60 (liability), capital 40 (equity); no--- cost\/revenue accounts, so @'ET.finalStockTransfer'@ is a no-op, and no--- contra account is present, so no presentation group is formed:------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = (100 .@ Not:<Cash) .+ (60 .@ Not:<LoansPayable) .+ (40 .@ Not:<CapitalStock) :: T--- >>> mapM_ print (bsRows alg)--- ["Asset","","Liability",""]--- ["Cash","100.0","LoansPayable","60.0"]--- ["Total","100.0","Equity",""]--- ["","","CapitalStock","40.0"]--- ["","","Total","100.0"]------ Receivables 1000 with an allowance of 100 against them, and capital 900.--- The allowance is deducted from the receivables and the asset total is the--- net 900, not the gross 1000:------ >>> let contra = (1000 .@ Not:<AccountsReceivable) .+ (100 .@ Not:<AllowanceForDoubtfulAccounts) .+ (900 .@ Not:<CapitalStock) :: T--- >>> mapM_ print (bsRows contra)--- ["Asset","","Liability",""]--- ["AccountsReceivable","1000.0","Equity",""]--- ["AllowanceForDoubtfulAccounts","-100.0","CapitalStock","900.0"]--- ["TradeReceivablesNet","900.0","Total","900.0"]--- ["Total","900.0","",""]------ Complexity: O(s) (s = total number of scalar entries)-bsRows :: (HatVal n, HatBaseClass b, ExBaseClass b) => Alg n b -> [[T.Text]]-bsRows alg = result- where- transferred = ET.finalStockTransfer alg- grouping = RG.groupingForDivisions [Assets, Liability, Equity]- RG.defaultPresentationGrouping- grouped = RG.presentGroups grouping (accountGrossTotals transferred)- consumed = RG.gpConsumed grouped- ungrouped = EA.filter- (\x -> not (Set.member (getAccountTitle (_hatBase x)) consumed)) transferred- creditSide = decR ungrouped- debitSide = decL ungrouped- assets = debitSide- liability = EA.filter (\x -> whatDiv (_hatBase x) == Liability) creditSide- equity = EA.filter (\x -> whatDiv (_hatBase x) == Equity) creditSide- blocksIn divisions =- [block | block@(def, _) <- RG.gpBlocks grouped- , RG.pgDivision def `elem` divisions]- rootOf division = OMap.findWithDefault (zeroValue, zeroValue) division- (RG.gpRootTotals grouped)- -- Totals are the sum of the displayed cells: the ungrouped entries of the- -- column plus each group's net (counted once, at its outermost block).- assetGross = RG.addGross (EA.norm assets, zeroValue) (rootOf Assets)- creditGross = RG.addGross- (zeroValue, EA.norm liability + EA.norm equity)- (RG.addGross (rootOf Liability) (rootOf Equity))- assetTotal = renderRelative (RG.relativeTo Debit assetGross)- creditTotal = renderRelative (RG.relativeTo Credit creditGross)- (assetGroupText, assetGroupValue) =- unzip (groupCells grouping (blocksIn [Assets]))- (liabilityGroupText, liabilityGroupValue) =- unzip (groupCells grouping (blocksIn [Liability]))- (equityGroupText, equityGroupValue) =- unzip (groupCells grouping (blocksIn [Equity]))- assetsText = L.map (tshow . getAccountTitle . _hatBase) (EA.toList assets)- ++ assetGroupText- assetsValue = L.map (tshow . _val) (EA.toList assets) ++ assetGroupValue- liabilityText = L.map (tshow . getAccountTitle . _hatBase) (EA.toList liability)- ++ liabilityGroupText- liabilityValue = L.map (tshow . _val) (EA.toList liability) ++ liabilityGroupValue- equityText = L.map (tshow . getAccountTitle . _hatBase) (EA.toList equity)- ++ equityGroupText- equityValue = L.map (tshow . _val) (EA.toList equity) ++ equityGroupValue- result = csvTranspose- [ [T.pack "Asset"] ++ assetsText ++ [T.pack "Total"]- , [T.empty] ++ assetsValue ++ [assetTotal]- , [T.pack "Liability"] ++ liabilityText ++ [T.pack "Equity"] ++ equityText ++ [T.pack "Total"]- , [T.empty] ++ liabilityValue ++ [T.empty] ++ equityValue ++ [creditTotal]- ]---- | Output a Balance Sheet in CSV format. Pure layout is delegated to--- 'bsRows'; this function only writes the file. See 'bsRows' for the--- @'ET.finalStockTransfer'@ closing semantics and the column layout.------ Complexity: O(s) (s = total number of scalar entries; see 'bsRows')-writeBS :: (HatVal n, HatBaseClass b, ExBaseClass b) => FilePath -> Alg n b -> IO ()-writeBS path alg = writeCSV path (bsRows alg)---- | Build the rows of a Profit and Loss Statement, as a pure value (the part--- 'writePL' renders to CSV).------ No closing is applied here (contrast 'bsRows', which applies--- @'ET.finalStockTransfer'@) -- this decomposes the algebra /as given/ into--- cost and revenue entries by 'whichSide'\/'whatDiv' ('decL'\/'decR'\/'EA.filter'--- only select, they do not aggregate).------ __Contra accounts (Definition 7 amendment, Land 3).__ A contra revenue--- (売上割戻) sits on the debit side and a contra cost (仕入割戻, 還付法人税等)--- on the credit side, so the side partition above would drop all three from--- the statement entirely. They are instead collected into the presentation--- groups of "ExchangeAlgebra.Reporting.Group" and shown as a real deduction--- (@gross lines → deduction lines → net line@) inside their own column, on--- the same terms as 'bsRows'. A group is formed only when one of its contra--- accounts carries gross activity, so a statement containing no contra--- posting keeps its ordinary rows. The column totals keep their historical cross-placement--- (the Cost column's total cell states the revenue total and vice versa), but--- are now the sum of the cells displayed in the other column.------ Layout:------ > Cost | <titles...> | Total--- > | <values...> | <revenue total>--- > Revenue | <titles...> | Total--- > | <values...> | <cost total>------ ==== __Examples__------ A single sale of 500 (revenue) against its cost of 300; no contra account,--- so no presentation group is formed:------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = (500 .@ Not:<Sales) .+ (300 .@ Not:<SalesCost) :: T--- >>> mapM_ print (plRows alg)--- ["Cost","","Revenue",""]--- ["SalesCost","300.0","Sales","500.0"]--- ["Total","500.0","Total","300.0"]------ The same sale with a rebate of 50 granted on it. Gross sales stay visible,--- the rebate is deducted, and net sales carry into the total:------ >>> let rebated = alg .+ (50 .@ Not:<SalesRebates) :: T--- >>> mapM_ print (plRows rebated)--- ["Cost","","Revenue",""]--- ["SalesCost","300.0","Sales","500.0"]--- ["","","SalesRebates","-50.0"]--- ["","","NetSales","450.0"]--- ["Total","450.0","Total","300.0"]------ Complexity: O(s) (s = total number of scalar entries)-plRows :: (HatVal n, HatBaseClass b, ExBaseClass b) => Alg n b -> [[T.Text]]-plRows alg = result- where- grouping = RG.groupingForDivisions [Cost, Revenue] RG.defaultPresentationGrouping- grouped = RG.presentGroups grouping (accountGrossTotals alg)- consumed = RG.gpConsumed grouped- ungrouped = EA.filter- (\x -> not (Set.member (getAccountTitle (_hatBase x)) consumed)) alg- creditSide = decR ungrouped- debitSide = decL ungrouped- cost = EA.filter (\x -> whatDiv (_hatBase x) == Cost) debitSide- revenue = EA.filter (\x -> whatDiv (_hatBase x) == Revenue) creditSide- blocksIn division =- [block | block@(def, _) <- RG.gpBlocks grouped- , RG.pgDivision def == division]- rootOf division = OMap.findWithDefault (zeroValue, zeroValue) division- (RG.gpRootTotals grouped)- costGross = RG.addGross (EA.norm cost, zeroValue) (rootOf Cost)- revenueGross = RG.addGross (zeroValue, EA.norm revenue) (rootOf Revenue)- -- Historical cross-placement preserved: the Cost column's total cell- -- states the revenue total, and the Revenue column's the cost total.- debitTotal = renderRelative (RG.relativeTo Debit costGross)- creditTotal = renderRelative (RG.relativeTo Credit revenueGross)- (costGroupText, costGroupValue) = unzip (groupCells grouping (blocksIn Cost))- (revenueGroupText, revenueGroupValue) =- unzip (groupCells grouping (blocksIn Revenue))- costText = L.map (tshow . getAccountTitle . _hatBase) (EA.toList cost)- ++ costGroupText- costValue = L.map (tshow . _val) (EA.toList cost) ++ costGroupValue- revenueText = L.map (tshow . getAccountTitle . _hatBase) (EA.toList revenue)- ++ revenueGroupText- revenueValue = L.map (tshow . _val) (EA.toList revenue) ++ revenueGroupValue- (ct, rt) = toSameLength costText revenueText- (cv, rv) = toSameLength costValue revenueValue- result = csvTranspose- [ [T.pack "Cost"] ++ ct ++ [T.pack "Total"]- , [T.empty] ++ cv ++ [creditTotal]- , [T.pack "Revenue"] ++ rt ++ [T.pack "Total"]- , [T.empty] ++ rv ++ [debitTotal]- ]---- | Output a Profit and Loss Statement in CSV format. Pure layout is--- delegated to 'plRows'; this function only writes the file.------ Complexity: O(s) (s = total number of scalar entries; see 'plRows')-writePL :: (HatVal n, HatBaseClass b, ExBaseClass b) => FilePath -> Alg n b -> IO ()-writePL path alg = writeCSV path (plRows alg)---- | Pad two lists to the same length. Appends empty text to the shorter list.------ Complexity: O(max(|xs|, |ys|))-toSameLength :: [T.Text] -> [T.Text] -> ([T.Text],[T.Text])-toSameLength xs ys =- case compare lx ly of- EQ -> (xs, ys)- LT -> (xs ++ replicate (ly - lx) T.empty, ys)- GT -> (xs, ys ++ replicate (lx - ly) T.empty)- where- lx = Prelude.length xs- ly = Prelude.length ys---- | Build the rows of a journal (仕訳帳), as a pure value (the part--- 'writeJournal' renders to CSV).------ Entries are grouped by date (via @f@) into a deterministic, deduplicated,--- ascending day sequence; within a day the debit and credit postings are--- listed by 'decL'\/'decR' and padded to equal length with 'toSameLength' ----- no aggregation\/@bar@ is applied, so a day with more debit than credit--- postings (or vice versa) simply gets blank cells on the shorter side.------ ==== __Examples__------ Three days: a capital contribution, a cash sale, and a day with two debit--- postings against one credit posting (the debit column is one line longer,--- and the credit/date cells of that extra line are padded blank):------ >>> import Data.Time (fromGregorian)--- >>> type T = Alg Double (HatBase (AccountTitles, Day))--- >>> let d1 = fromGregorian 2024 4 1--- >>> let d2 = fromGregorian 2024 4 2--- >>> let d3 = fromGregorian 2024 4 3--- >>> let getDay (_ :< (_, d)) = d--- >>> let alg = (100 .@ Not:<(Cash,d1)) .+ (100 .@ Not:<(CapitalStock,d1)) .+ (50 .@ Not:<(Cash,d2)) .+ (50 .@ Not:<(Sales,d2)) .+ (30 .@ Not:<(Cash,d3)) .+ (10 .@ Not:<(AccountsReceivable,d3)) .+ (40 .@ Not:<(Sales,d3)) :: T--- >>> mapM_ print (journalRows alg getDay)--- ["Day","Debit","Amount","Credit","Amount"]--- ["2024-04-01","Cash","100.0","CapitalStock","100.0"]--- ["2024-04-02","Cash","50.0","Sales","50.0"]--- ["2024-04-03","AccountsReceivable","10.0","Sales","40.0"]--- ["","Cash","30.0","",""]------ Complexity: O(s * log d) (s = number of entries, d = number of distinct dates)-journalRows :: (HatVal n, HatBaseClass b, ExBaseClass b)- => Alg n b- -> (b -> Day)- -> [[T.Text]]-journalRows alg f = csvTranspose [ds, dt, dv, ct, cv]- where- days = L.sort $ Set.toList . Set.fromList $ L.map (f . _hatBase) $ EA.toList alg- rows = L.map perDay days- perDay d =- let da = EA.filter (\y -> (f . _hatBase) y == d) alg- dl = decL da- dr = decR da- dlTexts = L.map (tshow . getAccountTitle . _hatBase) (EA.toList dl)- drTexts = L.map (tshow . getAccountTitle . _hatBase) (EA.toList dr)- dlValues = L.map (tshow . _val) (EA.toList dl)- drValues = L.map (tshow . _val) (EA.toList dr)- (dt', ct') = toSameLength dlTexts drTexts- (dv', cv') = toSameLength dlValues drValues- (ds', _) = toSameLength [tshow d] cv'- in (ds', dt', dv', ct', cv')- ds = [T.pack "Day"] ++ concatMap (\(a,_,_,_,_) -> a) rows- dt = [T.pack "Debit"] ++ concatMap (\(_,a,_,_,_) -> a) rows- dv = [T.pack "Amount"] ++ concatMap (\(_,_,a,_,_) -> a) rows- ct = [T.pack "Credit"] ++ concatMap (\(_,_,_,a,_) -> a) rows- cv = [T.pack "Amount"] ++ concatMap (\(_,_,_,_,a) -> a) rows---- | Output journal entries in CSV format.--- Groups by date and records the debit/credit account titles and amounts for each day.--- Pure layout is delegated to 'journalRows'; this function only writes the file.------ Complexity: O(s * log d) (s = number of entries, d = number of distinct dates; see 'journalRows')-writeJournal :: (HatVal n, HatBaseClass b, ExBaseClass b)- => FilePath- -> Alg n b- -> (b -> Day)- -> IO ()-writeJournal path alg f = writeCSV path (journalRows alg f)----- | Build the rows of a general ledger (総勘定元帳 / T-account) for the given--- account titles, as a pure value (the part 'writeAccountOf' renders to CSV).------ The ledger is the audit-trail view of an algebra: for each requested account--- title, every posting touching that title is listed __individually in date--- order, with no aggregation__ — the redundant sequence (seq) of the algebra is--- preserved verbatim. (Contrast with 'writeCompoundTrialBalance', which is the--- aggregated, @bar@\/@norm@-netted view.) This is the showcase of the algebra's--- redundancy: each historical posting is kept as a separate ledger line.------ For each title the layout is a two-sided T-account:------ > <Title>--- > Date | Debit | Date | Credit------ Debit-side postings (@'whichSide' == 'Debit'@) go on the left, credit-side--- postings on the right; the two columns are padded to equal length. Within a--- side, postings are sorted by date and otherwise keep their original order--- (so the line count equals the number of postings — no netting).------ ==== __Examples__------ Two separate debit postings to Cash are kept as two ledger lines (the seq is--- /not/ aggregated into a single 30; the count of postings is preserved):------ >>> import Data.Time (fromGregorian)--- >>> type T = Alg Double (HatBase AccountTitles)--- >>> let led = (10 .@ Not:<Cash) .+ (20 .@ Not:<Cash) .+ (5 .@ Hat:<Cash) :: T--- >>> let d = fromGregorian 2024 4 1--- >>> mapM_ print (accountLedgerRows [Cash] led (const d))--- ["Cash","","",""]--- ["Date","Debit","Date","Credit"]--- ["2024-04-01","20.0","2024-04-01","5.0"]--- ["2024-04-01","10.0","",""]------ Complexity: O(t * s) (t = number of titles, s = number of entries).-accountLedgerRows :: (HatVal n, HatBaseClass b, ExBaseClass b)- => [AccountTitles]- -> Alg n b- -> (b -> Day)- -> [[T.Text]]-accountLedgerRows titles alg f =- concatMap titleBlock titles- where- titleBlock t =- let xs = projByAccountTitle t alg- debits = sortByDay (EA.toList (decL xs))- credits = sortByDay (EA.toList (decR xs))- dDates = L.map (tshow . f . _hatBase) debits- dVals = L.map (tshow . _val) debits- cDates = L.map (tshow . f . _hatBase) credits- cVals = L.map (tshow . _val) credits- (dDates', cDates') = toSameLength dDates cDates- (dVals', cVals') = toSameLength dVals cVals- header = [tshow t, T.empty, T.empty, T.empty]- sub = [T.pack "Date", T.pack "Debit", T.pack "Date", T.pack "Credit"]- body = L.zipWith4 (\a b c d -> [a,b,c,d]) dDates' dVals' cDates' cVals'- in header : sub : body- sortByDay = L.sortBy (\x y -> compare ((f . _hatBase) x) ((f . _hatBase) y))---- | Output general ledgers (総勘定元帳) for the given account titles in CSV--- format. Pure layout is delegated to 'accountLedgerRows'; this function only--- performs the file write.------ Each requested title is rendered as a two-sided T-account whose postings are--- listed __individually, without aggregation__ (the seq redundancy is the audit--- trail — see 'accountLedgerRows').------ Complexity: O(t * s) (t = number of titles, s = number of entries).-writeAccountOf :: (HatVal n, HatBaseClass b, ExBaseClass b)- => [AccountTitles]- -> FilePath- -> Alg n b- -> (b -> Day)- -> IO ()-writeAccountOf titles path alg f = writeCSV path (accountLedgerRows titles alg f)---- | Build the rows of general ledgers (総勘定元帳) from a 'EJ.Journal', as a--- pure value (the part 'writeAccountOfJournal' renders to CSV), carrying the--- per-posting note (摘要) as an extra column.------ As with 'accountLedgerRows', postings are listed __individually, without--- aggregation__ — the redundant sequence is the audit trail. Because each--- posting carries its own note, this version uses a flat detail layout rather--- than the two-sided T-account:------ > <Title>--- > Note | Debit | Credit--- > <note> | <amount> | -- debit-side posting--- > <note> | | <amount> -- credit-side posting------ The note is rendered with 'show'. Postings are emitted in note order--- (the 'EJ.Journal' is keyed by note); within a note the algebra's own seq--- order is preserved. No 'EA.bar' \/ aggregation is applied.------ ==== __Examples__------ Two Cash postings under different notes; note order ("pay" < "sale")--- determines the row order, not posting order:------ >>> let jrn = ((100 .@ Not:<Cash) .| "sale") .+ ((40 .@ Hat:<Cash) .| "pay") :: EJ.Journal String Double (HatBase AccountTitles)--- >>> mapM_ print (accountLedgerRowsJournal [Cash] jrn)--- ["Cash","",""]--- ["Note","Debit","Credit"]--- ["\"pay\"","","40.0"]--- ["\"sale\"","100.0",""]------ Complexity: O(t * s) (t = number of titles, s = number of postings).-accountLedgerRowsJournal :: (HatVal n, HatBaseClass b, ExBaseClass b, EJ.Note note)- => [AccountTitles]- -> EJ.Journal note n b- -> [[T.Text]]-accountLedgerRowsJournal titles j =- concatMap titleBlock titles- where- pairs = L.sortBy (\(a,_) (b,_) -> compare a b) (Map.toList (EJ.toMap j))- titleBlock t =- let header = [tshow t, T.empty, T.empty]- sub = [T.pack "Note", T.pack "Debit", T.pack "Credit"]- body = concatMap (noteRows t) pairs- in header : sub : body- noteRows t (note, alg) =- let xs = projByAccountTitle t alg- mkRow x =- let amt = tshow (_val x)- in if (whichSide . _hatBase) x == Debit- then [tshow note, amt, T.empty]- else [tshow note, T.empty, amt]- in L.map mkRow (EA.toList (EA.filter (\x -> x /= EA.Zero) xs))---- | Output general ledgers (総勘定元帳) from a 'EJ.Journal' in CSV format,--- carrying the per-posting note (摘要) as an extra column. Pure layout is--- delegated to 'accountLedgerRowsJournal'; this function only writes the--- file. See 'accountLedgerRowsJournal' for the flat note-detail layout.------ Complexity: O(t * s) (t = number of titles, s = number of postings; see--- 'accountLedgerRowsJournal').-writeAccountOfJournal :: (HatVal n, HatBaseClass b, ExBaseClass b, EJ.Note note)- => [AccountTitles]- -> FilePath- -> EJ.Journal note n b- -> IO ()-writeAccountOfJournal titles path j =- writeCSV path (accountLedgerRowsJournal titles j)------------------------------------------------------------------------ Trial-balance aggregation (non-negative single-pass)----------------------------------------------------------------------- | Per-account gross debit/credit totals, accumulated in a __single pass__ over--- the algebra's scalar entries (O(s)), keyed by 'AccountTitles' in 'Ord' order.------ Each entry contributes its (non-negative) value to either the debit or the--- credit total of its account title, classified by 'whichSide' (which already--- folds in the Hat\/Not reversal) — exactly the @Debit@\/@Credit@ partition that--- 'decL'\/'decR' produce. Summing per side reproduces @'norm' . 'decL'@ (debit--- gross) and @'norm' . 'decR'@ (credit gross) without any signed netting, so the--- value-domain invariant ($\\mathbb{R}_0^+$) is preserved: only non-negative--- magnitudes are stored.------ 'foldEntries' already skips zero values, matching the @x /= 'Zero'@ guard in--- 'decL'\/'decR'. The resulting 'OMap.Map' iterates titles in ascending order,--- matching the previous @L.sort . Set.toList . Set.fromList@ title enumeration.------ Complexity: O(s) (s = number of scalar entries).-accountGrossTotals :: (HatVal n, HatBaseClass b, ExBaseClass b)- => Alg n b -> OMap.Map AccountTitles (n, n)-accountGrossTotals = EA.foldEntries step OMap.empty- where- -- (debit gross, credit gross)- step acc v b =- let !t = getAccountTitle b- !pair = case whichSide b of- Debit -> (v, zeroValue)- Credit -> (zeroValue, v)- Side -> (zeroValue, zeroValue)- in OMap.insertWith addPair t pair acc- addPair (d1, c1) (d2, c2) = (d1 + d2, c1 + c2)---- | Net a @(debit gross, credit gross)@ pair into a @(Side, magnitude)@ balance,--- reproducing 'diffRL' exactly. 'diffRL' compares @r = 'norm' . 'decR'@ (credit)--- against @l = 'norm' . 'decL'@ (debit) with the scale-aware tolerance, so the--- same comparison is applied here: near-equal sides report v'Side' with zero--- magnitude, otherwise the larger side wins with the non-negative difference.--- This function compares two supplied totals; it does not sum postings. If--- floating-point addition has already rounded those totals, use--- @ExchangeAlgebra.Journal.Exact.accountBalancesExact@ or--- @ExchangeAlgebra.Algebra.Exact.diffRLExact@ upstream on the original postings.------ Complexity: O(1).-netGross :: (HatVal n) => (n, n) -> (Side, n)-netGross (l, r) -- l = debit gross, r = credit gross- | nearlyEqScaled r l = (Side, zeroValue)- | r > l = (Credit, r - l)- | otherwise = (Debit, l - r)---- | Build the rows of a Compound Trial Balance (合計残高試算表), as a pure--- value (the part 'writeCompoundTrialBalance' renders to CSV). Calculates the--- debit total, credit total, and balance for each account title, aggregated--- via the single-pass @accountGrossTotals@\/@netGross@ (the same--- @diffRL@-equivalent netting as 'balanceOf'; no implicit @bar@). Layout:------ > Debit Balance | Debit Total | Account Title | Credit Total | Credit Balance------ __Legacy column-placement quirk (preserved verbatim):__ unlike--- 'worksheetRows'\/'postClosingTrialBalanceRows' (which route a @(side,mag)@--- balance through @sideCells@, putting a Debit balance in the Debit cell and--- a Credit balance in the Credit cell), this layout places the balance--- figure in the column pair /opposite/ the netted side: a debit-heavy--- account's balance lands in the __Credit Balance__ (rightmost) column, and a--- credit-heavy account's balance lands in the __Debit Balance__ (leftmost)--- column — see the example below. Reusing @sideCells@ here would require--- flipping 'Debit'\/'Credit' first, which is no clearer than the explicit--- case analysis in @step@ below, so this was kept as-is rather than--- consolidated (design-review C7) to guarantee output is unchanged.------ ==== __Examples__------ Cash is debit-heavy (gross debit 100, credit 0); CapitalStock and--- LoansPayable are credit-heavy. Note where each balance figure lands:------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let alg = (100 .@ Not:<Cash) .+ (60 .@ Not:<LoansPayable) .+ (40 .@ Not:<CapitalStock) :: T--- >>> mapM_ print (compoundTrialBalanceRows alg)--- ["Debit Balance","Debit Total","Account Title","Credit Total","Credit Balance"]--- ["","100.0","Cash","0.0","100.0"]--- ["40.0","0.0","CapitalStock","40.0",""]--- ["60.0","0.0","LoansPayable","60.0",""]--- ["100.0","100.0","Total","100.0","100.0"]------ Complexity: O(s) (single pass over s scalar entries; see @accountGrossTotals@)-compoundTrialBalanceRows :: (HatVal n, HatBaseClass b, ExBaseClass b)- => Alg n b -> [[T.Text]]-compoundTrialBalanceRows alg =- header : lines' ++ [totalLine]- where- header = [T.pack "Debit Balance"- ,T.pack "Debit Total"- ,T.pack "Account Title"- ,T.pack "Credit Total"- ,T.pack "Credit Balance"]- -- Single pass (O(s)): gross debit/credit totals per title, in Ord order.- accounts = OMap.toList (accountGrossTotals alg)- (lines', debitBalanceTotal, debitTotal, creditBalanceTotal, creditTotal) =- L.foldl' step ([], zeroValue, zeroValue, zeroValue, zeroValue) accounts- totalLine = [ tshow debitBalanceTotal- , tshow creditTotal- , T.pack "Total"- , tshow debitTotal- , tshow creditBalanceTotal- ]- step (accLines, dbt, dt, cbt, ct) (a, gross) =- let xl = fst gross -- norm (decL xs) : debit gross- xr = snd gross -- norm (decR xs) : credit gross- (dc, diff) = netGross gross- -- 'netGross'/'diffRL' returns the wildcard v'Side' with a zero- -- difference when an account nets to zero (e.g. a fully-cleared- -- suspense account). Treat that as no balance on either side- -- (cf. 'sideCells').- (dbt', cbt') = case dc of- Credit -> (dbt + diff, cbt)- Debit -> (dbt, cbt + diff)- Side -> (dbt, cbt)- -- See the Haddock above: the Credit case places 'diff' in the- -- *Debit Balance* column (position 0) and the Debit case places- -- it in the *Credit Balance* column (position 4) -- opposite of- -- 'sideCells' -- a legacy layout kept verbatim.- line = case dc of- Credit -> [ tshow diff- , tshow xl- , tshow a- , tshow xr- , T.empty- ]- Debit -> [ T.empty- , tshow xl- , tshow a- , tshow xr- , tshow diff- ]- Side -> [ T.empty- , tshow xl- , tshow a- , tshow xr- , T.empty- ]- in (accLines ++ [line], dbt', dt + xr, cbt', ct + xl)---- | Output a Compound Trial Balance in CSV format.--- Calculates the debit total, credit total, and balance for each account--- title and outputs as a table. Pure layout is delegated to--- 'compoundTrialBalanceRows'; this function only writes the file. See--- 'compoundTrialBalanceRows' for the column layout (including the legacy--- Debit\/Credit Balance placement quirk).------ Complexity: O(s) (single pass over s scalar entries; see--- 'compoundTrialBalanceRows')-writeCompoundTrialBalance :: (HatVal n, HatBaseClass b, ExBaseClass b)- => FilePath- -> Alg n b- -> IO ()-writeCompoundTrialBalance path alg =- writeCSV path (compoundTrialBalanceRows alg)------------------------------------------------------------------------ Closing documents (決算書類): worksheet & post-closing trial balance----------------------------------------------------------------------- | Per-account net balance: @('diffRL' . 'projByAccountTitle' title)@ on a--- single-title restriction returns @(side, magnitude)@ where @side@ is the--- balance side (Debit or Credit) after netting and @magnitude@ is the--- (non-negative) amount. This is the explicit aggregation used by both the--- worksheet trial-balance column and the post-closing trial balance (the--- analogous netting that 'writeCompoundTrialBalance' performs).-balanceOf :: (HatVal n, HatBaseClass b, ExBaseClass b)- => AccountTitles -> Alg n b -> (Side, n)-balanceOf t = diffRL . projByAccountTitle t---- | Place a @(side, magnitude)@ balance into a (debit, credit) text column pair.--- A zero magnitude produces two empty cells.-sideCells :: (HatVal n) => (Side, n) -> (T.Text, T.Text)-sideCells (side, mag)- | mag == zeroValue = (T.empty, T.empty)- -- 'diffRL' returns the wildcard v'Side' only when the net magnitude is zero,- -- which the guard above has already handled; a non-zero balance is always- -- 'Debit' or 'Credit'. The non-exhaustive @case@ is by design (audited).- | otherwise = case side of- Debit -> (tshow mag, T.empty)- Credit -> (T.empty, tshow mag)---- | Build the rows of an 8-column worksheet (8 桁精算表), as a pure value--- (the part 'writeWorksheet' renders to CSV).------ Columns (matching the lecture's 精算表): per account title there are four--- debit\/credit column pairs —------ > Account Title--- > | Trial Balance (Debit, Credit) -- 残高試算表--- > | Adjustments (Debit, Credit) -- 整理記入--- > | Profit & Loss (Debit, Credit) -- 損益計算書--- > | Balance Sheet (Debit, Credit) -- 貸借対照表------ For each account title:------ * the __Trial Balance__ pair is the net balance of the--- /pre-adjustment ledger/ (@'balanceOf' title pre@ — an explicit--- @diffRL@ netting, no implicit @bar@);--- * the __Adjustments__ pair is the net balance of the /adjustment entries/--- (@'balanceOf' title adj@);--- * the __final balance__ is the net balance of @pre '.+' adj@, routed by--- 'whatDiv': 'Cost'\/'Revenue' titles go to the __Profit & Loss__ columns,--- 'Assets'\/'Liability'\/'Equity' titles go to the __Balance Sheet__--- columns.------ The penultimate row is the column totals of the trial-balance, P\/L and B\/S--- column pairs. The final row is the profit\/loss balancing figure--- (当期純利益\/純損失): it is the amount that makes each of the P\/L and B\/S--- column pairs balance. By construction (the homomorphism @norm@) the P\/L--- imbalance equals the B\/S imbalance — that equality is the worksheet's own--- self-check (精算表の貸借差額の一致). __This function does not enforce the--- equality__; if the inputs are inconsistent it still emits both figures so the--- discrepancy is visible (rather than raising an error).------ Account titles are listed in 'Ord' order so the output is deterministic.------ ==== __Examples__------ Opening capital 100 + a cash sale of 50, with no adjustments. Cash (150) and--- CapitalStock (100) go to the Balance Sheet; Sales (50) goes to Profit & Loss.--- The net income of 50 appears on the P\/L debit and B\/S credit, making both--- statement column pairs balance at 50 and 150 respectively:------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let pre = (150 .@ Not:<Cash) .+ (100 .@ Not:<CapitalStock) .+ (50 .@ Not:<Sales) :: T--- >>> mapM_ print (worksheetRows pre (zeroValue .@ Not:<Cash))--- ["Account Title","Trial Balance","","Adjustments","","Profit & Loss","","Balance Sheet",""]--- ["","Debit","Credit","Debit","Credit","Debit","Credit","Debit","Credit"]--- ["Cash","150.0","","","","","","150.0",""]--- ["CapitalStock","","100.0","","","","","","100.0"]--- ["Sales","","50.0","","","","50.0","",""]--- ["Subtotal","150.0","150.0","","","0.0","50.0","150.0","100.0"]--- ["Net Income","","","","","50.0","","","50.0"]--- ["Total","","","","","50.0","50.0","150.0","150.0"]------ Complexity: O(s) (single pass per algebra over s scalar entries;--- see @accountGrossTotals@).-worksheetRows :: (HatVal n, HatBaseClass b, ExBaseClass b)- => Alg n b -- ^ pre-adjustment ledger (決算整理前残高)- -> Alg n b -- ^ adjustment entries (決算整理仕訳)- -> [[T.Text]]-worksheetRows pre adj =- header : sub : bodyRows ++ [tbTotalRow, netRow, grandTotalRow]- where- header =- [ T.pack "Account Title"- , T.pack "Trial Balance", T.empty- , T.pack "Adjustments", T.empty- , T.pack "Profit & Loss", T.empty- , T.pack "Balance Sheet", T.empty ]- sub =- [ T.empty- , T.pack "Debit", T.pack "Credit"- , T.pack "Debit", T.pack "Credit"- , T.pack "Debit", T.pack "Credit"- , T.pack "Debit", T.pack "Credit" ]- combined = pre .+ adj- -- Single pass per algebra (O(s)): gross debit/credit totals per title.- -- 'balanceOf t alg' = 'diffRL (projByAccountTitle t alg)' is reproduced as- -- 'netGross' of the per-title gross pair; a title absent from a map (e.g.- -- only in 'adj', not 'pre') yields '(0,0)', i.e. 'diffRL Zero = (Side, 0)'.- preTotals = accountGrossTotals pre- adjTotals = accountGrossTotals adj- combTotals = accountGrossTotals combined- grossOf m t = OMap.findWithDefault (zeroValue, zeroValue) t m- -- Title enumeration: the keys of 'combined' in Ord order (= the previous- -- 'L.sort . Set.toList . Set.fromList' over 'EA.toList combined').- titles = OMap.keys combTotals- -- per-title row + accumulate the four debit/credit column totals- (bodyRows, tbD, tbC, plD, plC, bsD, bsC) =- L.foldl' step ([], zeroValue, zeroValue, zeroValue, zeroValue, zeroValue, zeroValue) titles- step (rows, tbd, tbc, pld, plc, bsd, bsc) t =- let tb = netGross (grossOf preTotals t)- (tbDc, tbCc) = sideCells tb- adjPair = netGross (grossOf adjTotals t)- (adjDc, adjCc) = sideCells adjPair- finalPair@(finalSide, finalMag) = netGross (grossOf combTotals t)- div' = classifyAccountDivision t- isPL = div' == Cost || div' == Revenue- (plDc, plCc, bsDc, bsCc)- | isPL = let (d,c) = sideCells finalPair in (d, c, T.empty, T.empty)- | otherwise = let (d,c) = sideCells finalPair in (T.empty, T.empty, d, c)- row = [ tshow t, tbDc, tbCc, adjDc, adjCc, plDc, plCc, bsDc, bsCc ]- -- column totals- addCol side mag d c = case side of- Debit -> (d + mag, c)- Credit -> (d, c + mag)- _ -> (d, c) -- wildcard v'Side' (balanced to zero): adds nothing- (ntbd, ntbc) = let (s,m) = tb in addCol s m tbd tbc- (npld, nplc) | isPL = addCol finalSide finalMag pld plc- | otherwise = (pld, plc)- (nbsd, nbsc) | not isPL = addCol finalSide finalMag bsd bsc- | otherwise = (bsd, bsc)- in (rows ++ [row], ntbd, ntbc, npld, nplc, nbsd, nbsc)- -- trial-balance totals row (整理記入 totals are intentionally omitted: the- -- adjustment column does not have a meaningful single grand total in the- -- lecture's layout; the self-check is on P/L and B/S).- tbTotalRow =- [ T.pack "Subtotal"- , tshow tbD, tshow tbC- , T.empty, T.empty- , tshow plD, tshow plC- , tshow bsD, tshow bsC ]- -- profit/loss balancing figure (当期純利益 / 純損失).- -- P/L imbalance: if credit (revenue) side exceeds debit (cost) side, the- -- difference is net income, recorded on the P/L debit side and the B/S- -- credit side (it increases equity). The reverse is a net loss.- plDiff = absDiff plD plC- bsDiff = absDiff bsD bsC- netProfit = plC > plD -- revenue side larger => profit- (netLabel, netPlD, netPlC, netBsD, netBsC)- | plC == plD = (T.pack "Net Income/Loss", T.empty, T.empty, T.empty, T.empty)- | netProfit = (T.pack "Net Income", tshow plDiff, T.empty, T.empty, tshow bsDiff)- | otherwise = (T.pack "Net Loss", T.empty, tshow plDiff, tshow bsDiff, T.empty)- netRow =- [ netLabel- , T.empty, T.empty- , T.empty, T.empty- , netPlD, netPlC- , netBsD, netBsC ]- -- grand totals after adding the balancing figure (both sides now equal).- plTotal = max plD plC- bsTotal = max bsD bsC- grandTotalRow =- [ T.pack "Total"- , T.empty, T.empty- , T.empty, T.empty- , tshow plTotal, tshow plTotal- , tshow bsTotal, tshow bsTotal ]- absDiff a b = if a >= b then a - b else b - a---- | Output an 8-column worksheet (8 桁精算表) in CSV format.--- Pure layout is delegated to 'worksheetRows'; this function only writes the--- file. See 'worksheetRows' for the column structure and the self-check--- (P\/L vs B\/S balancing figure) semantics.------ Complexity: O(s) (single pass per algebra; see 'worksheetRows').-writeWorksheet :: (HatVal n, HatBaseClass b, ExBaseClass b)- => FilePath- -> Alg n b -- ^ pre-adjustment ledger- -> Alg n b -- ^ adjustment entries- -> IO ()-writeWorksheet path pre adj = writeCSV path (worksheetRows pre adj)---- | Build the rows of a post-closing trial balance (繰越試算表), as a pure--- value (the part 'writePostClosingTrialBalance' renders to CSV).------ After the closing transfers, only the /real/ (permanent) accounts remain on--- the ledger. This lists the net balance of every 'Assets'\/'Liability'\/--- 'Equity' account (the carried-forward balances, 次期繰越); 'Cost'\/'Revenue'--- (nominal) accounts are __excluded by construction__, since they have been--- closed out to the income summary. Layout:------ > Debit | Account Title | Credit------ The net balance per title is computed with an explicit @diffRL@ netting--- (the same aggregation as 'writeCompoundTrialBalance'); no implicit @bar@.--- The final row gives the debit and credit totals (which must agree).------ Titles are listed in 'Ord' order for deterministic output.------ ==== __Examples__------ A ledger with cash, a loan (liability) and a sale (revenue). Only the real--- accounts (Cash, LoansPayable) survive; the nominal Sales account is dropped.------ >>> type T = Alg Double (HatBase AccountTitles)--- >>> let led = (100 .@ Not:<Cash) .+ (40 .@ Hat:<Cash) .+ (60 .@ Not:<LoansPayable) .+ (100 .@ Not:<Sales) :: T--- >>> mapM_ print (postClosingTrialBalanceRows led)--- ["Debit","Account Title","Credit"]--- ["60.0","Cash",""]--- ["","LoansPayable","60.0"]--- ["60.0","Total","60.0"]------ Complexity: O(s) (single pass over s scalar entries; see @accountGrossTotals@).-postClosingTrialBalanceRows :: (HatVal n, HatBaseClass b, ExBaseClass b)- => Alg n b -> [[T.Text]]-postClosingTrialBalanceRows alg =- header : bodyRows ++ [totalRow]- where- header = [T.pack "Debit", T.pack "Account Title", T.pack "Credit"]- -- Single pass (O(s)): gross debit/credit totals per title, in Ord order;- -- keep only real (Assets/Liability/Equity) titles.- totals = accountGrossTotals alg- titles = L.filter isReal (OMap.keys totals)- isReal t = let d = classifyAccountDivision t- in d == Assets || d == Liability || d == Equity- (bodyRows, debitTotal, creditTotal) =- L.foldl' step ([], zeroValue, zeroValue) titles- step (rows, dt, ct) t =- let (side, mag) = netGross (OMap.findWithDefault (zeroValue, zeroValue) t totals)- (dCell, cCell) = sideCells (side, mag)- (dt', ct') = case side of- Debit | mag /= zeroValue -> (dt + mag, ct)- Credit | mag /= zeroValue -> (dt, ct + mag)- _ -> (dt, ct)- row = [dCell, tshow t, cCell]- in (rows ++ [row], dt', ct')- totalRow = [tshow debitTotal, T.pack "Total", tshow creditTotal]---- | Output a post-closing trial balance (繰越試算表) in CSV format.--- Pure layout is delegated to 'postClosingTrialBalanceRows'; this function only--- writes the file. Only 'Assets'\/'Liability'\/'Equity' (real) accounts appear;--- 'Cost'\/'Revenue' accounts are excluded — see 'postClosingTrialBalanceRows'.------ Complexity: O(s) (single pass; see 'postClosingTrialBalanceRows').-writePostClosingTrialBalance :: (HatVal n, HatBaseClass b, ExBaseClass b)- => FilePath- -> Alg n b- -> IO ()-writePostClosingTrialBalance path alg =- writeCSV path (postClosingTrialBalanceRows alg)----------------------------------------------------------------------- Write Functions for Simulation----------------------------------------------------------------------- | Output the Input-Output Table for a specified term in CSV format.--- Outputs a slice of the specified term from a 3D array (term, row industry, column industry).------ Complexity: O(r * c) (r = number of rows, c = number of columns)-writeTermIO :: (HatVal n,BaseClass b, Ix t, Ix b, Enum b)- => FilePath -> t -> IOArray (t, b, b) n -> IO ()-writeTermIO path t arr = do- ((_, c1Min, c2Min), (_, c1Max, c2Max)) <- getBounds arr- let rows = [c1Min .. c1Max]- let cols = [c2Min .. c2Max]- body <- forM rows $ \r -> do- cells <- forM cols $ \c -> tshow <$> readArray arr (t, r, c)- pure (tshow r : cells)- writeCSV path ((T.pack "" : L.map tshow cols) : body)---- | Output a 2D IOArray (Input-Output Table or ripple effect matrix) in CSV format.------ Complexity: O(r * c) (r = number of rows, c = number of columns)-writeIOMatrix :: FilePath -> IOArray (Int, Int) Double -> IO ()-writeIOMatrix path arr = do- ((r1, c1), (r2, c2)) <- getBounds arr- let rows = [r1 .. r2]- let cols = [c1 .. c2]- body <- forM rows $ \r -> do- cells <- forM cols $ \c -> tshow <$> readArray arr (r, c)- pure (tshow r : cells)- writeCSV path ((T.pack "" : L.map tshow cols) : body)
+ test/Accounting/AdjustmentSpec.hs view
@@ -0,0 +1,455 @@+-- | Hand-computed closing adjustments, rounding, sources, and schedules.+module Accounting.AdjustmentSpec (runTests) where++import Control.Monad (forM_)+import Data.List.NonEmpty (NonEmpty(..))+import Numeric.Natural (Natural)+import ExchangeAlgebra.Accounting.Account (AccountDivision(..), AccountTitles(..), Side(..))+import ExchangeAlgebra.Accounting.Adjustment+import ExchangeAlgebra.Accounting.Exchange (Exchange(..), projByAccountTitle)+import ExchangeAlgebra.Accounting.Entries (reversingEntry)+import ExchangeAlgebra.Accounting.Transaction (Entry)+import ExchangeAlgebra.Algebra.Base (Hat(..), HatBase(..))+import ExchangeAlgebra.Algebra.Core (Redundant(..), (.@), toList)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import Support (assertEqual)++-- | Check the expected net entry, debit and credit totals, and nonnegative postings.+expectEntry :: String -> Either AdjustmentProblem Entry -> Entry -> IO ()+expectEntry label result expected = case result of+ Left problem -> fail (label ++ ": " ++ show problem)+ Right entry -> do+ assertEqual label (bar expected) (bar entry)+ assertEqual (label ++ " balanced") (norm (decL entry)) (norm (decR entry))+ assertEqual (label ++ " nonnegative postings") True (all ((>= 0) . norm) (toList entry))++-- | Check rounding, source selection, schedules, and the named adjustments.+runTests :: IO ()+runTests = do+ unit <- either (fail . show) pure (roundingUnit 1)+ hundred <- either (fail . show) pure (roundingUnit 100)+ rate <- either (fail . show) pure (basisPoints 200)+ forty <- either (fail . show) pure (basisPoints 4000)+ full <- either (fail . show) pure (basisPoints 10000)+ let down = RoundTo unit RoundDown+ upHundred = RoundTo hundred RoundUp+ directions :: [RoundingDirection]+ directions = [RoundDown, RoundUp, RoundHalfUp, RoundHalfEven]+ roundingCases :: [(Rational, [MoneyDecimal])]+ roundingCases = [(24 / 10, [2, 3, 2, 2]), (25 / 10, [2, 3, 3, 2])+ ,(26 / 10, [2, 3, 3, 3]), (35 / 10, [3, 4, 4, 4])]+ forM_ roundingCases $ \(amount, expected) ->+ assertEqual ("round " ++ show amount) (Right expected)+ (traverse (\direction -> roundAmount (RoundTo unit direction) amount) directions)+ forM_ [(250, [200, 300, 300, 200]), (350, [300, 400, 400, 400])] $+ \(amount, expected) -> assertEqual ("round hundreds " ++ show amount) (Right expected)+ (traverse (\direction -> roundAmount (RoundTo hundred direction) amount) directions)+ assertEqual "nonterminating" (Left (InexactAmount (1 / 3))) (roundAmount ExactOnly (1 / 3))+ assertEqual "exact quarter" (Right 0.25) (roundAmount ExactOnly (1 / 4))+ assertEqual "exact 128 places" (Right (fromRational (1 / 10^128)))+ (roundAmount ExactOnly (1 / 10^128))+ assertEqual "exact 255 places" (Right (fromRational (1 / 10^255)))+ (roundAmount ExactOnly (1 / 10^255))+ assertEqual "inexact 256 places" (Left (InexactAmount (1 / 10^256)))+ (roundAmount ExactOnly (1 / 10^256))+ assertEqual "negative amount" (Left (NegativeAmount (-1))) (roundAmount down (-1))+ assertEqual "zero unit" (Left (NonPositiveRoundingUnit 0)) (roundingUnit 0)+ assertEqual "negative unit" (Left (NonPositiveRoundingUnit (-1))) (roundingUnit (-1))+ assertEqual "unit value" 100 (roundingUnitValue hundred)++ let rentOffice :: Entry+ rentOffice = 12000 .@ Not :< RentExpense+ .+ 12000 .@ Hat :< Cash+ rentStore :: Entry+ rentStore = 6000 .@ Not :< RentExpense+ .+ 6000 .@ Hat :< Cash+ desk :: Entry+ desk = 1200 .@ Not :< Fixtures+ .+ 1200 .@ Hat :< Cash+ shelf :: Entry+ shelf = 600 .@ Not :< Fixtures+ .+ 600 .@ Hat :< Cash+ assertEqual "two rent contracts" (Right 18000)+ (sourceAmount (AmountIn RentExpense (rentOffice .+ rentStore)))+ assertEqual "selected contract" (Right 12000)+ (sourceAmount (AmountIn RentExpense rentOffice))+ assertEqual "two assets" (Right 1800)+ (sourceAmount (AmountIn Fixtures (desk .+ shelf)))+ expectEntry "selected asset"+ (straightLineDepreciation down IndirectMethod Fixtures (AmountIn Fixtures desk)+ (DepreciationTerms 0 60 [] 12))+ ( 240 .@ Not :< Depreciation+ .+ 240 .@ Not :< AccumulatedDepreciation)+ expectEntry "selected contract deferral"+ (prepaidExpense down RentExpense (AmountIn RentExpense rentOffice) 8 12)+ ( 8000 .@ Not :< PrepaidExpenses+ .+ 8000 .@ Hat :< RentExpense)+ assertEqual "no increase" (Left (NoSourceAmount Cash))+ (sourceAmount (AmountIn Cash rentOffice))+ assertEqual "wildcard source" (Left (NoSourceAmount AccountTitle))+ (sourceAmount (AmountIn AccountTitle rentOffice))+ assertEqual "net source" (Right 11000)+ (sourceAmount (AmountIn RentExpense (rentOffice .+ 1000 .@ Hat :< RentExpense)))+ assertEqual "nonpositive stated" (Left (NonPositiveArgument 0))+ (sourceAmount (StatedAmount 0))++ let straight :: [Natural] -> Natural -> Either AdjustmentProblem Entry+ straight prior current = straightLineDepreciation down IndirectMethod Fixtures+ (StatedAmount 100) (DepreciationTerms 0 36 prior current)+ schedule :: [Either AdjustmentProblem Entry]+ schedule = [straight [] 12, straight [12] 12, straight [12, 12] 12]+ forM_ (zip schedule [33, 33, 34]) $ \(entry, amount) ->+ expectEntry "straight schedule" entry+ ( amount .@ Not :< Depreciation+ .+ amount .@ Not :< AccumulatedDepreciation)+ assertEqual "schedule sums to cost" (Right 100) (sum <$> traverse (fmap (norm . decL)) schedule)+ expectEntry "prior periods [1,1]" (straight [1, 1] 34)+ ( 96 .@ Not :< Depreciation+ .+ 96 .@ Not :< AccumulatedDepreciation)+ expectEntry "prior periods [2]" (straight [2] 34)+ ( 95 .@ Not :< Depreciation+ .+ 95 .@ Not :< AccumulatedDepreciation)+ expectEntry "direct year two uses cost"+ (straightLineDepreciation down DirectMethod Fixtures (StatedAmount 100)+ (DepreciationTerms 0 60 [12] 12))+ ( 20 .@ Not :< Depreciation+ .+ 20 .@ Hat :< Fixtures)+ expectEntry "clip final months" (straight [24] 24)+ ( 34 .@ Not :< Depreciation+ .+ 34 .@ Not :< AccumulatedDepreciation)+ assertEqual "finished" (Left (ScheduleFinished 36 36)) (straight [36] 12)+ assertEqual "residual equals cost" (Left (ResidualNotBelowCost 100 100))+ (straightLineDepreciation down IndirectMethod Fixtures (StatedAmount 100)+ (DepreciationTerms 100 36 [] 12))+ forM_ [(0, 12, []), (36, 0, []), (36, 12, [0])] $ \(useful, current, prior) ->+ assertEqual "empty schedule months" (Left EmptyMonths)+ (straightLineDepreciation down IndirectMethod Fixtures (StatedAmount 100)+ (DepreciationTerms 0 useful prior current))+ let declining :: [Natural] -> Natural -> Either AdjustmentProblem Entry+ declining prior current = decliningBalanceDepreciation down IndirectMethod Fixtures+ (StatedAmount 1000) forty (DepreciationTerms 0 60 prior current)+ forM_ [([], 12, 400), ([12], 12, 240), ([], 6, 200), ([12, 12, 12, 12], 12, 130)] $+ \(prior, current, amount) -> expectEntry "declining schedule" (declining prior current)+ ( amount .@ Not :< Depreciation+ .+ amount .@ Not :< AccumulatedDepreciation)+ forM_ [([], 100), ([12], 0), ([12, 12], 0)] $ \(prior, amount) ->+ expectEntry "straight cap"+ (straightLineDepreciation upHundred IndirectMethod Fixtures (StatedAmount 100)+ (DepreciationTerms 0 36 prior 12))+ ( amount .@ Not :< Depreciation+ .+ amount .@ Not :< AccumulatedDepreciation)+ expectEntry "declining cap is not rounded again"+ (decliningBalanceDepreciation upHundred DirectMethod Fixtures+ (StatedAmount 100) full (DepreciationTerms 20 60 [] 12))+ ( 80 .@ Not :< Depreciation+ .+ 80 .@ Hat :< Fixtures)+ assertEqual "contra asset" (Left (ContraAccount AccumulatedDepreciation))+ (straightLineDepreciation down DirectMethod AccumulatedDepreciation+ (StatedAmount 100) (DepreciationTerms 0 60 [] 12))+ assertEqual "contra expense" (Left (ContraAccount PurchaseRebates))+ (prepaidExpense down PurchaseRebates (StatedAmount 100) 1 12)++ let inventory :: Entry+ inventory = 100 .@ Not :< MerchandiseInventory+ .+ 400 .@ Not :< Purchases+ expectEntry "periodic cogs" (cogsPeriodic inventory 150)+ ( 100 .@ Not :< Purchases+ .+ 100 .@ Hat :< MerchandiseInventory+ .+ 150 .@ Not :< MerchandiseInventory+ .+ 150 .@ Hat :< Purchases)+ expectEntry "sales cost cogs" (cogsThroughSalesCost inventory 150)+ ( 100 .@ Not :< SalesCost+ .+ 100 .@ Hat :< MerchandiseInventory+ .+ 400 .@ Not :< SalesCost+ .+ 400 .@ Hat :< Purchases+ .+ 150 .@ Not :< MerchandiseInventory+ .+ 150 .@ Hat :< SalesCost)+ let receivables :: Entry+ receivables = 5000 .@ Not :< AccountsReceivable+ .+ 3000 .@ Not :< NotesReceivable+ allowance :: Entry+ allowance = receivables+ .+ 30 .@ Not :< AllowanceForDoubtfulAccounts+ expectEntry "provide allowance"+ (allowanceReplenishment down rate (AccountsReceivable :| []) allowance)+ ( 70 .@ Not :< ProvisionForDoubtfulAccounts+ .+ 70 .@ Not :< AllowanceForDoubtfulAccounts)+ expectEntry "release allowance"+ (allowanceReplenishment down rate (AccountsReceivable :| [])+ (receivables+ .+ 130 .@ Not :< AllowanceForDoubtfulAccounts))+ (30 .@ Hat :< AllowanceForDoubtfulAccounts+ .+ 30 .@ Not :< ReversalOfAllowanceForDoubtfulAccounts)+ expectEntry "reset allowance"+ (allowanceReset down rate (AccountsReceivable :| []) allowance)+ (30 .@ Hat :< AllowanceForDoubtfulAccounts+ .+ 30 .@ Not :< ReversalOfAllowanceForDoubtfulAccounts+ .+ 100 .@ Not :< ProvisionForDoubtfulAccounts+ .+ 100 .@ Not :< AllowanceForDoubtfulAccounts)+ forM_ [AccountsReceivable :| [NotesReceivable],+ AccountsReceivable :| [NotesReceivable, AccountsReceivable]] $ \accounts ->+ expectEntry "two receivables, deduplicated"+ (allowanceReplenishment down rate accounts allowance)+ (130 .@ Not :< ProvisionForDoubtfulAccounts+ .+ 130 .@ Not :< AllowanceForDoubtfulAccounts)+ expectEntry "allowance replenishment capped at receivables"+ (allowanceReplenishment upHundred rate (AccountsReceivable :| [])+ (50 .@ Not :< AccountsReceivable))+ ( 50 .@ Not :< ProvisionForDoubtfulAccounts+ .+ 50 .@ Not :< AllowanceForDoubtfulAccounts)+ expectEntry "allowance reset capped at receivables"+ (allowanceReset upHundred rate (AccountsReceivable :| [])+ (50 .@ Not :< AccountsReceivable))+ ( 50 .@ Not :< ProvisionForDoubtfulAccounts+ .+ 50 .@ Not :< AllowanceForDoubtfulAccounts)+ expectEntry "prepaid capped at payment"+ (prepaidExpense upHundred RentExpense (StatedAmount 50) 1 2)+ ( 50 .@ Not :< PrepaidExpenses+ .+ 50 .@ Hat :< RentExpense)+ expectEntry "unearned capped at receipt"+ (unearnedRevenue upHundred InterestEarned (StatedAmount 50) 1 2)+ ( 50 .@ Hat :< InterestEarned+ .+ 50 .@ Not :< UnearnedRevenue)+ expectEntry "prepaid" (prepaidExpense down RentExpense (StatedAmount 12000) 3 12)+ ( 3000 .@ Not :< PrepaidExpenses+ .+ 3000 .@ Hat :< RentExpense)+ expectEntry "unearned" (unearnedRevenue down InterestEarned (StatedAmount 12000) 3 12)+ ( 3000 .@ Hat :< InterestEarned+ .+ 3000 .@ Not :< UnearnedRevenue)+ assertEqual "prepaid months exceed term" (Left (MonthsExceedTerm 13 12))+ (prepaidExpense down RentExpense (StatedAmount 12000) 13 12)+ assertEqual "unearned months exceed term" (Left (MonthsExceedTerm 13 12))+ (unearnedRevenue down InterestEarned (StatedAmount 12000) 13 12)+ forM_ [StatedAmount 12000, AmountIn LoansReceivable (12000 .@ Not :< LoansReceivable)] $+ \source -> expectEntry "accrued interest revenue"+ (accruedInterestRevenue down source rate 3)+ ( 60 .@ Not :< AccruedRevenue+ .+ 60 .@ Not :< InterestEarned)+ forM_ [StatedAmount 12000, AmountIn LoansPayable (12000 .@ Not :< LoansPayable)] $+ \source -> expectEntry "accrued interest expense"+ (accruedInterestExpense down source rate 3)+ ( 60 .@ Not :< InterestExpense+ .+ 60 .@ Not :< AccruedExpenses)+ expectEntry "unused supplies" (suppliesOnHand 30 (100 .@ Not :< SuppliesExpenses))+ ( 30 .@ Not :< SuppliesOnHand+ .+ 30 .@ Hat :< SuppliesExpenses)+ assertEqual "supplies exceed balance" (Left (ExceedsBalance SuppliesExpenses 101 100))+ (suppliesOnHand 101 (100 .@ Not :< SuppliesExpenses))+ expectEntry "cash shortage" (cashOverShortSettlement (30 .@ Not :< CashOverShort))+ ( 30 .@ Not :< MiscellaneousLoss+ .+ 30 .@ Hat :< CashOverShort)+ expectEntry "cash overage" (cashOverShortSettlement (40 .@ Hat :< CashOverShort))+ ( 40 .@ Not :< CashOverShort+ .+ 40 .@ Not :< MiscellaneousIncome)+ expectEntry "cash balanced" (cashOverShortSettlement mempty) mempty+ expectEntry "overdraft"+ (overdraftReclassification CurrentDeposits (50 .@ Hat :< CurrentDeposits))+ ( 50 .@ Not :< CurrentDeposits+ .+ 50 .@ Not :< BankOverdraft)+ expectEntry "positive deposit"+ (overdraftReclassification CurrentDeposits (50 .@ Not :< CurrentDeposits)) mempty+ expectEntry "tax payable" (consumptionTaxSettlement+ ( 70 .@ Not :< ConsumptionTaxPaid+ .+ 100 .@ Not :< ConsumptionTaxReceived))+ ( 100 .@ Hat :< ConsumptionTaxReceived+ .+ 70 .@ Hat :< ConsumptionTaxPaid+ .+ 30 .@ Not :< AccruedConsumptionTax)+ expectEntry "tax refund" (consumptionTaxSettlement+ ( 100 .@ Not :< ConsumptionTaxPaid+ .+ 70 .@ Not :< ConsumptionTaxReceived))+ ( 70 .@ Hat :< ConsumptionTaxReceived+ .+ 30 .@ Not :< ConsumptionTaxRefundReceivable+ .+ 100 .@ Hat :< ConsumptionTaxPaid)+ expectEntry "corporate tax"+ (corporateTaxSettlement 800 (500 .@ Not :< PrepaidCorporateIncomeTaxes))+ ( 800 .@ Not :< CorporateIncomeTaxes+ .+ 500 .@ Hat :< PrepaidCorporateIncomeTaxes+ .+ 300 .@ Not :< AccruedCorporateIncomeTaxes)+ assertEqual "interim exceeds total" (Left (ExceedsBalance PrepaidCorporateIncomeTaxes 900 800))+ (corporateTaxSettlement 800 (900 .@ Not :< PrepaidCorporateIncomeTaxes))+ assertEqual "inventory wrong side" (Left (UnexpectedBalanceSide MerchandiseInventory Credit))+ (cogsPeriodic (1 .@ Hat :< MerchandiseInventory) 0)+ assertEqual "wrong expense division" (Left (NotInDivision Cash Cost))+ (prepaidExpense down Cash (StatedAmount 12000) 1 12)+ assertEqual "negative ending inventory" (Left (NegativeArgument (-1)))+ (cogsPeriodic mempty (-1))+ assertEqual "nonpositive tax total" (Left (NonPositiveArgument 0))+ (corporateTaxSettlement 0 mempty)++ -- Units-of-production periods retain rounded history and the final remainder.+ let production :: [Natural] -> Natural -> Either AdjustmentProblem Entry+ production prior current = unitsOfProductionDepreciation down IndirectMethod Vehicle+ (StatedAmount 110) (ProductionTerms 10 3 prior current)+ productionSchedule = [production [] 1, production [1] 1, production [1, 1] 1]+ forM_ (zip productionSchedule [33, 33, 34]) $ \(entry, amount) ->+ expectEntry "production schedule" entry+ ( amount .@ Not :< Depreciation+ .+ amount .@ Not :< AccumulatedDepreciation)+ assertEqual "production total cost less residual" (Right 100)+ (sum <$> traverse (fmap (norm . decL)) productionSchedule)+ expectEntry "production passes estimated total" (production [1, 1] 2)+ ( 34 .@ Not :< Depreciation+ .+ 34 .@ Not :< AccumulatedDepreciation)+ expectEntry "zero current production" (production [1] 0) mempty+ expectEntry "zero production skips inexact prior charges"+ (unitsOfProductionDepreciation ExactOnly IndirectMethod Vehicle+ (StatedAmount 110) (ProductionTerms 10 3 [1] 0)) mempty+ expectEntry "direct production"+ (unitsOfProductionDepreciation down DirectMethod Vehicle+ (StatedAmount 110) (ProductionTerms 10 3 [] 1))+ ( 33 .@ Not :< Depreciation+ .+ 33 .@ Hat :< Vehicle)+ assertEqual "zero estimated production" (Left (NonPositiveArgument 0))+ (unitsOfProductionDepreciation down IndirectMethod Vehicle+ (StatedAmount 110) (ProductionTerms 10 0 [] 1))+ assertEqual "production schedule finished" (Left (ScheduleFinished 3 3))+ (production [1, 2] 1)++ -- Both discounts and premiums amortize 100 over three periods: 33, 33, 34.+ let amortization :: MoneyDecimal -> [Natural] -> Natural -> Either AdjustmentProblem Entry+ amortization cost prior current = amortizedCostAdjustment down (StatedAmount cost)+ 1000 (AmortizationTerms 36 prior current)+ discountedSchedule = [amortization 900 [] 12, amortization 900 [12] 12,+ amortization 900 [12, 12] 12]+ premiumSchedule = [amortization 1100 [] 12, amortization 1100 [12] 12,+ amortization 1100 [12, 12] 12]+ forM_ (zip discountedSchedule [33, 33, 34]) $ \(entry, amount) ->+ expectEntry "bond discount schedule" entry+ ( amount .@ Not :< HeldToMaturityBonds+ .+ amount .@ Not :< InterestOnSecurities)+ forM_ (zip premiumSchedule [33, 33, 34]) $ \(entry, amount) ->+ expectEntry "bond premium schedule" entry+ ( amount .@ Hat :< InterestOnSecurities+ .+ amount .@ Hat :< HeldToMaturityBonds)+ forM_ [discountedSchedule, premiumSchedule] $ \entries ->+ assertEqual "amortization total face-cost difference" (Right 100)+ (sum <$> traverse (fmap (norm . decL)) entries)+ expectEntry "bond at face value" (amortization 1000 [] 12) mempty+ assertEqual "zero bond term" (Left (NonPositiveArgument 0))+ (amortizedCostAdjustment down (StatedAmount 900) 1000 (AmortizationTerms 0 [] 12))+ assertEqual "zero bond period" (Left EmptyMonths) (amortization 900 [] 0)++ -- Each allowance uses its own provision and reversal, including bonuses.+ let allowanceCases =+ [("bonuses", bonusAllowanceReplenishment, AllowanceForBonuses,+ ProvisionForBonuses, ReversalOfAllowanceForBonuses)+ ,("repairs", repairAllowanceReplenishment, AllowanceForRepairs,+ ProvisionForRepairs, ReversalOfAllowanceForRepairs)+ ,("warranties", productWarrantyAllowanceReplenishment, AllowanceForProductWarranties,+ ProvisionForProductWarranties, ReversalOfAllowanceForProductWarranties)]+ forM_ allowanceCases $ \(label, replenish, account, provision, reversal) -> do+ let balance = 300 .@ Not :< account+ expectEntry (label ++ " provision") (replenish 500 balance)+ ( 200 .@ Not :< provision+ .+ 200 .@ Not :< account)+ expectEntry (label ++ " reversal") (replenish 100 balance)+ ( 200 .@ Hat :< account+ .+ 200 .@ Not :< reversal)+ expectEntry (label ++ " unchanged") (replenish 300 balance) mempty+ assertEqual (label ++ " negative estimate") (Left (NegativeArgument (-1)))+ (replenish (-1) balance)+ assertEqual (label ++ " debit balance") (Left (UnexpectedBalanceSide account Debit))+ (replenish 500 (300 .@ Hat :< account))+ expectEntry "retirement provision" (retirementBenefitProvision 800)+ ( 800 .@ Not :< RetirementBenefitExpenses+ .+ 800 .@ Not :< AllowanceForRetirementBenefits)+ assertEqual "zero retirement provision" (Left (NonPositiveArgument 0))+ (retirementBenefitProvision 0)++ let trading = 1000 .@ Not :< TradingSecurities+ expectEntry "trading gain" (tradingSecuritiesValuation 1100 trading)+ ( 100 .@ Not :< TradingSecurities+ .+ 100 .@ Not :< GainOnValuationOfSecurities)+ expectEntry "trading loss" (tradingSecuritiesValuation 900 trading)+ ( 100 .@ Not :< LossOnValuationOfSecurities+ .+ 100 .@ Hat :< TradingSecurities)+ expectEntry "trading unchanged" (tradingSecuritiesValuation 1000 trading) mempty+ assertEqual "negative trading fair value" (Left (NegativeArgument (-1)))+ (tradingSecuritiesValuation (-1) trading)++ tax <- either (fail . show) pure (basisPoints 3000)+ let securities = 2000 .@ Not :< AvailableForSaleSecurities+ gainWithoutTax = 300 .@ Not :< AvailableForSaleSecurities+ .+ 300 .@ Not :< ValuationDifferenceOnOtherSecurities+ lossWithoutTax = 300 .@ Hat :< ValuationDifferenceOnOtherSecurities+ .+ 300 .@ Hat :< AvailableForSaleSecurities+ gainWithTax = 300 .@ Not :< AvailableForSaleSecurities+ .+ 90 .@ Not :< DeferredTaxLiabilities+ .+ 210 .@ Not :< ValuationDifferenceOnOtherSecurities+ lossWithTax = 90 .@ Not :< DeferredTaxAssets+ .+ 210 .@ Hat :< ValuationDifferenceOnOtherSecurities+ .+ 300 .@ Hat :< AvailableForSaleSecurities+ valuationCases = [(Nothing, 2300, gainWithoutTax), (Nothing, 1700, lossWithoutTax),+ (Just tax, 2300, gainWithTax), (Just tax, 1700, lossWithTax)]+ forM_ valuationCases $ \(taxRate, fair, expected) -> do+ let result = availableForSaleValuation down taxRate fair securities+ expectEntry "available-for-sale valuation" result expected+ entry <- either (fail . show) pure result+ forM_ [(AvailableForSaleSecurities, 2000), (ValuationDifferenceOnOtherSecurities, 0),+ (DeferredTaxAssets, 0), (DeferredTaxLiabilities, 0)] $+ \(account, expectedBalance) -> do+ let restored = projByAccountTitle account+ (securities .+ entry .+ reversingEntry entry)+ assertEqual ("reversal restores " ++ show account) expectedBalance+ (norm (decL restored) - norm (decR restored))+ expectEntry "available-for-sale unchanged"+ (availableForSaleValuation down (Just tax) 2000 securities) mempty+ assertEqual "tax rounding exceeds valuation difference" (Left (TaxExceedsDifference 100 50))+ (availableForSaleValuation upHundred (Just tax) 2050 securities)++ let stock = 1000 .@ Not :< MerchandiseInventory+ expectEntry "inventory shrinkage only"+ (inventoryShrinkageAndValuation (PhysicalInventory 900 900 :| []) stock)+ ( 100 .@ Not :< InventoryShrinkageLoss+ .+ 100 .@ Hat :< MerchandiseInventory)+ expectEntry "inventory write-down only"+ (inventoryShrinkageAndValuation (PhysicalInventory 1000 800 :| []) stock)+ ( 200 .@ Not :< LossOnValuationOfMerchandise+ .+ 200 .@ Hat :< MerchandiseInventory)+ expectEntry "inventory shrinkage and item losses, without offsetting gains"+ (inventoryShrinkageAndValuation+ (PhysicalInventory 600 500 :| [PhysicalInventory 300 350]) stock)+ ( 100 .@ Not :< InventoryShrinkageLoss+ .+ 100 .@ Not :< LossOnValuationOfMerchandise+ .+ 200 .@ Hat :< MerchandiseInventory)+ assertEqual "physical inventory exceeds books"+ (Left (ExceedsBalance MerchandiseInventory 1100 1000))+ (inventoryShrinkageAndValuation (PhysicalInventory 1100 1100 :| []) stock)++ let translate :: AccountTitles -> MoneyDecimal -> Either AdjustmentProblem Entry+ translate account rateYen = foreignCurrencyTranslation down account+ (StatedAmount 11000) 100 rateYen+ expectEntry "foreign asset gain" (translate AccountsReceivable 115)+ ( 500 .@ Not :< AccountsReceivable+ .+ 500 .@ Not :< ForeignExchangeGains)+ expectEntry "foreign asset loss" (translate AccountsReceivable 105)+ ( 500 .@ Not :< ForeignExchangeLosses+ .+ 500 .@ Hat :< AccountsReceivable)+ expectEntry "foreign liability loss" (translate AccountsPayable 115)+ ( 500 .@ Not :< AccountsPayable+ .+ 500 .@ Not :< ForeignExchangeLosses)+ expectEntry "foreign liability gain" (translate AccountsPayable 105)+ ( 500 .@ Not :< ForeignExchangeGains+ .+ 500 .@ Hat :< AccountsPayable)+ forM_ [AccountsReceivable, AccountsPayable] $ \account ->+ expectEntry "foreign unchanged" (translate account 110) mempty+ assertEqual "foreign revenue account" (Left (NotInDivision Sales Assets))+ (translate Sales 115)+ assertEqual "foreign contra asset" (Left (ContraAccount AccumulatedDepreciation))+ (translate AccumulatedDepreciation 115)+ expectEntry "foreign product rounded once"+ (foreignCurrencyTranslation down AccountsReceivable (StatedAmount 3600) 33.33 110.5)+ ( 82 .@ Not :< AccountsReceivable+ .+ 82 .@ Not :< ForeignExchangeGains)++ let deferred = 150 .@ Not :< DeferredTaxAssets+ expectEntry "deferred tax increase" (deferredTaxAssetAdjustment down tax 600 deferred)+ ( 30 .@ Not :< DeferredTaxAssets+ .+ 30 .@ Hat :< IncomeTaxesAdjustment)+ expectEntry "deferred tax decrease" (deferredTaxAssetAdjustment down tax 400 deferred)+ ( 30 .@ Not :< IncomeTaxesAdjustment+ .+ 30 .@ Hat :< DeferredTaxAssets)+ expectEntry "deferred tax unchanged" (deferredTaxAssetAdjustment down tax 500 deferred) mempty+ assertEqual "deferred tax credit balance"+ (Left (UnexpectedBalanceSide DeferredTaxAssets Credit))+ (deferredTaxAssetAdjustment down tax 500 (150 .@ Hat :< DeferredTaxAssets))
+ test/Accounting/RegistrySpec.hs view
@@ -0,0 +1,3119 @@+-- | Account semantics, registries, statements, and accounting fixtures.+-- Tests exercise the Accounting layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Accounting.RegistrySpec (runTests) where++import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import qualified ExchangeAlgebra.Algebra.Internal as Internal+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.IO.Output.Statements as StatementOutput+import qualified ExchangeAlgebra.IO.Input as EC+import qualified ExchangeAlgebra.Accounting.Account as PP+import qualified ExchangeAlgebra.Accounting.Consolidation as CW+import ExchangeAlgebra.Accounting.Closing (ClosingSide(..), closingSide)+import qualified ExchangeAlgebra.Accounting.TrialBalance.Balance as TBB+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as TB+import qualified ExchangeAlgebra.Accounting.Statements.Presentation as RP+import qualified ExchangeAlgebra.Accounting.Statements.Metric as RM+import qualified ExchangeAlgebra.Accounting.Statements.Group as RG+import qualified ExchangeAlgebra.IO.Input.Assist as Assist+import qualified ExchangeAlgebra.Accounting.Account as Registry+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Accounting.Entries as EB+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified ExchangeAlgebra.IO.Output.Statements as EW+import qualified Data.Map.Strict as M+import qualified Data.List as L+import qualified Data.List.NonEmpty as NE+import qualified Data.Set as Set+import Data.Char (isAlpha, isAscii)+import qualified Data.Binary as Binary+import qualified Data.Binary.Put as BinaryPut+import qualified Data.Binary.Get as BinaryGet+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Golden.WriteRows (writeRowsFixtureDir, writeRowsFixtures)+import qualified Golden.ReadoutBaseline as ReadoutBaseline+import qualified Golden.ExampleNumbers.RippleFixture as RippleFixture+import qualified Golden.AdmissionBaseline as AdmissionBaseline+import System.Environment (lookupEnv)+import Numeric (showHex)+import Control.Monad (forM_)+import System.Exit (exitFailure)+import Data.Time (Day, TimeOfDay(..), fromGregorian)+import Control.Exception (try, evaluate, SomeException)+import Test.QuickCheck hiding (Fixed)+import Support (assertEqual+ , assertNear+ , SimHatBase2+ , quickProp+ , genNNDouble+ , epsEq+ , CheckedAlgM+ , exactBalancedForTest+ )++decodeAccountTitleOrFail :: BL.ByteString -> Either String AccountTitles+decodeAccountTitleOrFail bytes = case Binary.decodeOrFail bytes of+ Left (_, _, message) -> Left message+ Right (_, _, title) -> Right title++testAccountTitlesBinary :: IO ()+testAccountTitlesBinary = do+ let titles = [minBound .. maxBound] :: [AccountTitles]+ roundTripped = L.map (Binary.decode . Binary.encode) titles+ codes = L.map (BinaryGet.runGet BinaryGet.getWord16be . Binary.encode) titles+ invalidCodes = [0x0000, 0x0001, 0x00F2, 0x7FFF, 0x8000 + fromIntegral (length titles)]+ assertEqual "AccountTitles Binary covers all 244 constructors"+ 244 (L.length titles)+ assertEqual "AccountTitles Binary Word16be roundtrip"+ titles roundTripped+ assertEqual "B4 AccountTitles codes are injective" 244 (Set.size (Set.fromList codes))+ assertEqual "B4 AccountTitles codes carry the version bit" True (all (>= 0x8000) codes)+ assertEqual "B4 wildcard code is fixed" (BinaryPut.runPut (BinaryPut.putWord16be 0xFFFF))+ (Binary.encode AccountTitle)+ forM_ invalidCodes $ \code ->+ assertEqual "B4 AccountTitles rejects old and unused codes"+ True (case decodeAccountTitleOrFail (BinaryPut.runPut (BinaryPut.putWord16be code)) of+ Left _ -> True+ Right _ -> False)++-- | Check the appended allowances and the additional Japanese aliases.+testAllowanceAccountTitles :: IO ()+testAllowanceAccountTitles = do+ let titles = [SalesAllowances, PurchaseAllowances]+ assertEqual "allowance account titles Binary roundtrip"+ titles (L.map (Binary.decode . Binary.encode) titles)+ assertEqual "allowance account titles division"+ [Revenue, Cost] (L.map classifyAccountDivision titles)+ assertEqual "allowance account titles home side"+ [Debit, Credit]+ [whichSide (Not :< title :: HatBase AccountTitles) | title <- titles]+ assertEqual "allowance names and additional Japanese aliases parse"+ (L.map Right [SalesAllowances, PurchaseAllowances, DeliveryExpenses, Fixtures])+ (L.map EC.parseAccountTitle (L.map T.pack+ ["売上値引", "仕入値引", "荷造運賃", "器具備品"]))++-- ================================================================+-- AccountTitles classification exhaustiveness (Phase A)+-- ================================================================+--+-- Pins the (whatDiv, whichSide, fixedCurrent) classification of every+-- AccountTitles constructor against an explicit expected table that encodes+-- the Phase A design table. Any new constructor that is not added here makes+-- the test fail (the [minBound .. maxBound] traversal will hit a title absent+-- from the table), forcing the table to be kept in sync and guarding against+-- classifyAccountDivision's wildcard silently classifying a title as Assets.+--+-- whichSide is evaluated on the @Not :< title@ base (no Hat reversal), so it+-- equals the "home side" implied by whatDiv: Debit for Assets/Cost,+-- Credit for Liability/Equity/Revenue.++-- | Expected classification for every non-wildcard AccountTitles constructor.+-- (title, expected whatDiv, expected whichSide on Not-base, expected fixedCurrent)+accountTitleClassTable :: [(AccountTitles, AccountDivision, Side, FixedCurrent)]+accountTitleClassTable =+ -- Pre-existing titles+ [ (Cash, Assets, Debit, Current)+ , (Deposits, Assets, Debit, Current)+ , (CurrentDeposits, Assets, Debit, Current)+ , (Securities, Assets, Debit, Current)+ , (InvestmentSecurities, Assets, Debit, Fixed)+ , (LongTermNationalBonds, Assets, Debit, Fixed)+ , (ShortTermNationalBonds, Assets, Debit, Current)+ , (Products, Assets, Debit, Current)+ , (Machinery, Assets, Debit, Fixed)+ , (Building, Assets, Debit, Fixed)+ , (Vehicle, Assets, Debit, Fixed)+ , (StockInvestment, Assets, Debit, Other)+ , (EquipmentInvestment, Assets, Debit, Fixed)+ , (LongTermLoansReceivable, Assets, Debit, Fixed)+ , (AccountsReceivable, Assets, Debit, Current)+ , (ShortTermLoansReceivable, Assets, Debit, Current)+ , (ReserveDepositReceivable, Assets, Debit, Current)+ , (Gold, Assets, Debit, Fixed)+ , (GovernmentService, Assets, Debit, Current)+ , (CapitalStock, Equity, Credit, Other)+ , (RetainedEarnings, Equity, Credit, Other)+ , (LongTermLoansPayable, Liability, Credit, Fixed)+ , (ShortTermLoansPayable, Liability, Credit, Current)+ , (LoansPayable, Liability, Credit, Current)+ , (ReserveForDepreciation, Liability, Credit, Current)+ , (DepositPayable, Liability, Credit, Current)+ , (LongTermNationalBondsPayable, Liability, Credit, Fixed)+ , (ShortTermNationalBondsPayable, Liability, Credit, Current)+ , (ReserveDepositPayable, Liability, Credit, Current)+ , (CentralBankNotePayable, Liability, Credit, Current)+ , (Depreciation, Cost, Debit, Other)+ , (AmortizationExpense, Cost, Debit, Other)+ , (SalesCost, Cost, Debit, Other)+ , (BusinessTrip, Cost, Debit, Other)+ , (Commutation, Cost, Debit, Other)+ , (UtilitiesExpense, Cost, Debit, Other)+ , (RentExpense, Cost, Debit, Other)+ , (AdvertisingExpense, Cost, Debit, Other)+ , (DeliveryExpenses, Cost, Debit, Other)+ , (SuppliesExpenses, Cost, Debit, Other)+ , (MiscellaneousExpenses, Cost, Debit, Other)+ , (WageExpenditure, Cost, Debit, Other)+ , (InterestExpense, Cost, Debit, Other)+ , (TaxesExpense, Cost, Debit, Other)+ , (ConsumptionExpenditure, Cost, Debit, Other)+ , (SubsidyExpense, Cost, Debit, Other)+ , (CentralBankPaymentExpense, Cost, Debit, Other)+ , (Purchases, Cost, Debit, Other)+ , (NetIncome, Cost, Debit, Other)+ , (ValueAdded, Revenue, Credit, Other)+ , (SubsidyIncome, Revenue, Credit, Other)+ , (NationalBondInterestEarned, Revenue, Credit, Other)+ , (DepositInterestEarned, Revenue, Credit, Other)+ , (GrossProfit, Revenue, Credit, Other)+ , (OrdinaryProfit, Revenue, Credit, Other)+ , (InterestEarned, Revenue, Credit, Other)+ , (ReceiptFee, Revenue, Credit, Other)+ , (RentalIncome, Revenue, Credit, Other)+ , (WageEarned, Revenue, Credit, Other)+ , (TaxesRevenue, Revenue, Credit, Other)+ , (CentralBankPaymentIncome, Revenue, Credit, Other)+ , (Sales, Revenue, Credit, Other)+ , (NetLoss, Revenue, Credit, Other)+ -- Phase A additions: Assets (資産)+ , (PettyCash, Assets, Debit, Current)+ , (NotesReceivable, Assets, Debit, Current)+ , (ElectronicallyRecordedReceivable, Assets, Debit, Current)+ , (CreditCardReceivable, Assets, Debit, Current)+ , (NotesLoansReceivable, Assets, Debit, Current)+ , (MerchandiseInventory, Assets, Debit, Current)+ , (AdvancesPaid, Assets, Debit, Current)+ , (PrepaidExpenses, Assets, Debit, Current)+ , (AccruedRevenue, Assets, Debit, Current)+ , (OtherReceivables, Assets, Debit, Current)+ , (PaymentsOnBehalf, Assets, Debit, Current)+ , (SuspensePayments, Assets, Debit, Current)+ , (ConsumptionTaxPaid, Assets, Debit, Current)+ , (PrepaidCorporateIncomeTaxes, Assets, Debit, Current)+ , (Land, Assets, Debit, Fixed)+ , (Fixtures, Assets, Debit, Fixed)+ , (Patent, Assets, Debit, Fixed)+ , (Trademark, Assets, Debit, Fixed)+ , (Software, Assets, Debit, Fixed)+ , (CashOverShort, Assets, Debit, Other)+ -- Phase A additions: Liability (負債)+ , (AccountsPayable, Liability, Credit, Current)+ , (NotesPayable, Liability, Credit, Current)+ , (ElectronicallyRecordedObligations, Liability, Credit, Current)+ , (NotesLoansPayable, Liability, Credit, Current)+ , (BankOverdraft, Liability, Credit, Current)+ , (AdvancesReceived, Liability, Credit, Current)+ , (UnearnedRevenue, Liability, Credit, Current)+ , (AccruedExpenses, Liability, Credit, Current)+ , (OtherPayables, Liability, Credit, Current)+ , (DepositsReceived, Liability, Credit, Current)+ , (SuspenseReceipts, Liability, Credit, Current)+ , (ConsumptionTaxReceived, Liability, Credit, Current)+ , (AccruedConsumptionTax, Liability, Credit, Current)+ , (AccruedCorporateIncomeTaxes, Liability, Credit, Current)+ , (UnpaidDividends, Liability, Credit, Current)+ , (AllowanceForDoubtfulAccounts, Assets, Credit, Current) -- contra asset (isContra)+ , (AccumulatedDepreciation, Assets, Credit, Fixed) -- contra asset (isContra)+ -- Phase A additions: Equity (資本)+ , (LegalRetainedEarnings, Equity, Credit, Other)+ -- Phase A additions: Cost (費用)+ , (ProvisionForDoubtfulAccounts, Cost, Debit, Other)+ , (BadDebtLoss, Cost, Debit, Other)+ , (LossOnSalesOfFixedAssets, Cost, Debit, Other)+ , (LossOnSalesOfNotesReceivable, Cost, Debit, Other)+ , (PaymentFees, Cost, Debit, Other)+ , (MiscellaneousLoss, Cost, Debit, Other)+ , (CorporateIncomeTaxes, Cost, Debit, Other)+ , (CommunicationExpenses, Cost, Debit, Other)+ -- Phase A additions: Revenue (収益)+ , (GainOnSalesOfFixedAssets, Revenue, Credit, Other)+ , (RecoveryOfBadDebts, Revenue, Credit, Other)+ , (MiscellaneousIncome, Revenue, Credit, Other)+ -- Phase B addition: Revenue (収益)+ , (ReversalOfAllowanceForDoubtfulAccounts, Revenue, Credit, Other)+ -- T4b additions: equity-method accounts+ , (InvestmentInAssociate, Assets, Debit, Fixed)+ , (EquityInEarningsOfInvestee, Revenue, Credit, Other)+ -- FX library additions: OCI/capital accounts+ , (CumulativeTranslationAdjustment, Equity, Credit, Other)+ -- V-Land 2 additions+ , (TimeDeposits, Assets, Debit, Current)+ , (LoansReceivable, Assets, Debit, Current)+ , (GiftCertificatesReceived, Assets, Debit, Current)+ , (SecurityDepositsPaid, Assets, Debit, Fixed)+ , (SuppliesOnHand, Assets, Debit, Current)+ , (ContractAssets, Assets, Debit, Current)+ , (IncomeTaxesRefundReceivable, Assets, Debit, Current)+ , (WorkInProcess, Assets, Debit, Current)+ , (DeferredTaxAssets, Assets, Debit, Fixed)+ , (LeasedAssets, Assets, Debit, Fixed)+ , (ToolsAndInstruments, Assets, Debit, Fixed)+ , (ConstructionInProgress, Assets, Debit, Fixed)+ , (Goodwill, Assets, Debit, Fixed)+ , (SoftwareInProgress, Assets, Debit, Fixed)+ , (LongTermPrepaidExpenses, Assets, Debit, Fixed)+ , (DishonoredNotesReceivable, Assets, Debit, Current)+ , (PrepaidPensionCost, Assets, Debit, Fixed)+ , (NetDefinedBenefitAsset, Assets, Debit, Fixed)+ , (DepositsInSpecialAccounts, Assets, Debit, Current)+ , (Structures, Assets, Debit, Fixed)+ , (LeaseholdRights, Assets, Debit, Fixed)+ , (NonOperatingNotesReceivable, Assets, Debit, Current)+ , (NonOperatingElectronicallyRecordedReceivable, Assets, Debit, Current)+ , (RefundLiabilities, Liability, Credit, Current)+ , (NonOperatingNotesPayable, Liability, Credit, Current)+ , (NonOperatingElectronicallyRecordedObligations, Liability, Credit, Current)+ , (BonusesPayable, Liability, Credit, Current)+ , (AllowanceForRepairs, Liability, Credit, Current)+ , (AllowanceForProductWarranties, Liability, Credit, Current)+ , (AllowanceForBonuses, Liability, Credit, Current)+ , (DeferredTaxLiabilities, Liability, Credit, Fixed)+ , (LeaseObligations, Liability, Credit, Fixed)+ , (GuaranteeDepositsReceived, Liability, Credit, Fixed)+ , (AllowanceForRetirementBenefits, Liability, Credit, Fixed)+ , (LongTermOtherPayables, Liability, Credit, Fixed)+ , (NetDefinedBenefitLiability, Liability, Credit, Fixed)+ , (StockSubscriptionDeposits, Equity, Credit, Other)+ , (LegalCapitalSurplus, Equity, Credit, Other)+ , (OtherCapitalSurplus, Equity, Credit, Other)+ , (DividendEqualizationReserve, Equity, Credit, Other)+ , (RepairFundReserve, Equity, Credit, Other)+ , (ConstructionFundReserve, Equity, Credit, Other)+ , (GeneralReserve, Equity, Credit, Other)+ , (ValuationDifferenceOnOtherSecurities, Equity, Credit, Other)+ , (NonControllingInterests, Equity, Credit, Other)+ , (CapitalSurplus, Equity, Credit, Other)+ , (EarnedSurplus, Equity, Credit, Other)+ , (ServiceRevenue, Revenue, Credit, Other)+ , (OperatingRevenue, Revenue, Credit, Other)+ , (GainOnSalesOfSecurities, Revenue, Credit, Other)+ , (GainOnValuationOfSecurities, Revenue, Credit, Other)+ , (DividendsReceived, Revenue, Credit, Other)+ , (InterestOnSecurities, Revenue, Credit, Other)+ , (GainOnSalesOfInvestmentSecurities, Revenue, Credit, Other)+ , (InsuranceGain, Revenue, Credit, Other)+ , (GainOnBargainPurchase, Revenue, Credit, Other)+ , (ReversalOfAllowanceForRepairs, Revenue, Credit, Other)+ , (ReversalOfAllowanceForBonuses, Revenue, Credit, Other)+ , (ReversalOfAllowanceForProductWarranties, Revenue, Credit, Other)+ , (GainOnDonationOfFixedAssets, Revenue, Credit, Other)+ , (GainOnNationalSubsidies, Revenue, Credit, Other)+ , (GainOnConstructionGrants, Revenue, Credit, Other)+ , (LandRentReceived, Revenue, Credit, Other)+ , (SalesRebates, Revenue, Debit, Other)+ , (CostOfServices, Cost, Debit, Other)+ , (OperatingExpenses, Cost, Debit, Other)+ , (InventoryShrinkageLoss, Cost, Debit, Other)+ , (LossOnValuationOfMerchandise, Cost, Debit, Other)+ , (Bonuses, Cost, Debit, Other)+ , (RetirementBenefitExpenses, Cost, Debit, Other)+ , (ProvisionForRepairs, Cost, Debit, Other)+ , (ProvisionForBonuses, Cost, Debit, Other)+ , (ProvisionForProductWarranties, Cost, Debit, Other)+ , (ResearchAndDevelopmentExpenses, Cost, Debit, Other)+ , (AmortizationOfGoodwill, Cost, Debit, Other)+ , (AmortizationOfSoftware, Cost, Debit, Other)+ , (AmortizationOfPatents, Cost, Debit, Other)+ , (LeaseExpenses, Cost, Debit, Other)+ , (IncorporationExpenses, Cost, Debit, Other)+ , (StockIssuanceCosts, Cost, Debit, Other)+ , (BusinessCommencementExpenses, Cost, Debit, Other)+ , (DevelopmentExpenses, Cost, Debit, Other)+ , (LossOnSalesOfElectronicallyRecordedReceivables, Cost, Debit, Other)+ , (LossOnSalesOfReceivables, Cost, Debit, Other)+ , (LossOnSalesOfSecurities, Cost, Debit, Other)+ , (LossOnValuationOfSecurities, Cost, Debit, Other)+ , (LossOnSalesOfInvestmentSecurities, Cost, Debit, Other)+ , (LossOnFire, Cost, Debit, Other)+ , (LossOnRetirementOfFixedAssets, Cost, Debit, Other)+ , (LossOnReductionOfFixedAssets, Cost, Debit, Other)+ , (AdditionalIncomeTaxesForPriorPeriods, Cost, Debit, Other)+ , (RefundOfIncomeTaxes, Cost, Credit, Other)+ , (PurchaseRebates, Cost, Credit, Other)+ , (WelfareExpenses, Cost, Debit, Other)+ , (MaintenanceExpenses, Cost, Debit, Other)+ , (StatutoryWelfareExpenses, Cost, Debit, Other)+ , (LandRentPaid, Cost, Debit, Other)+ , (InsuranceExpense, Cost, Debit, Other)+ , (RepairsExpense, Cost, Debit, Other)+ , (StorageExpenses, Cost, Debit, Other)+ , (MembershipFees, Cost, Debit, Other)+ , (IncomeSummary, Assets, Debit, Other)+ , (SuspenseAccount, Assets, Debit, Current)+ , (ForeignExchangeGains, Revenue, Credit, Other)+ , (ForeignExchangeLosses, Cost, Debit, Other)+ , (ContraAccountForGuaranteeObligations, Assets, Debit, Other)+ , (GuaranteeObligations, Liability, Credit, Other)+ , (IncomeTaxesAdjustment, Cost, Debit, Other)+ , (BranchCurrentAccount, Assets, Debit, Other)+ , (HeadOfficeCurrentAccount, Liability, Credit, Other)+ , (NetIncomeAttributableToNCI, Cost, Debit, Other)+ , (NetLossAttributableToNCI, Revenue, Credit, Other)+ , (TradingSecurities, Assets, Debit, Current)+ , (HeldToMaturityBonds, Assets, Debit, Fixed)+ , (SubsidiaryStocks, Assets, Debit, Fixed)+ , (AffiliateStocks, Assets, Debit, Fixed)+ , (AvailableForSaleSecurities, Assets, Debit, Fixed)+ , (ConsumptionTaxRefundReceivable, Assets, Debit, Current)+ , (PropertyTaxPayable, Liability, Credit, Current)+ , (DepositsReceivedFromOfficers, Liability, Credit, Current)+ , (EntertainmentExpenses, Cost, Debit, Other)+ , (MeetingExpenses, Cost, Debit, Other)+ , (NewspaperBooksExpenses, Cost, Debit, Other)+ , (RawMaterials, Assets, Debit, Current)+ , (GoodsInTransit, Assets, Debit, Current)+ , (SalesAllowances, Revenue, Debit, Other)+ , (PurchaseAllowances, Cost, Credit, Other)+ ]++testAccountTitleClassification :: IO ()+testAccountTitleClassification = do+ -- All non-wildcard constructors, derived from Bounded/Enum.+ let allTitles = [ t | t <- [minBound .. maxBound], t /= AccountTitle ]+ tableMap = M.fromList [ (t, (d, s, fc)) | (t, d, s, fc) <- accountTitleClassTable ]+ -- A title is "covered" iff it appears in the expected table.+ missing = [ t | t <- allTitles, not (M.member t tableMap) ]+ extra = [ t | (t, _, _, _) <- accountTitleClassTable, t `notElem` allTitles ]+ -- Guard: the table must list exactly the non-wildcard constructors.+ assertEqual "AccountTitles class table covers every constructor (no missing)"+ ([] :: [AccountTitles]) missing+ assertEqual "AccountTitles class table has no stale entry (no extra)"+ ([] :: [AccountTitles]) extra+ -- Per-title classification must match the expected table.+ forM_ allTitles $ \t -> do+ let base = Not :< t :: HatBase AccountTitles+ actual = (whatDiv base, whichSide base, fixedCurrent base)+ case M.lookup t tableMap of+ Just expected ->+ assertEqual ("classification of " ++ show t) expected actual+ Nothing -> return () -- already reported by the "missing" guard+++-- | Classify closing directions while preserving the frozen probe vocabulary.+finalStockProbeRule :: AccountTitles -> String+finalStockProbeRule RetainedEarnings = "SELF"+finalStockProbeRule title = case closingSide title of+ Nothing -> "Nothing"+ Just ClosingKeep -> "Keep"+ Just ClosingFlip -> "Flip"++-- V-Land 1: finalStockRule の全域を独立参照式 (division + contra を明示分岐)+-- と突き合わせ, 方向 (Keep/Flip) まで固定する。contra P/L (将来の売上割戻等)+-- では division 基準と逆になることをこの式が明文化する。+testFinalStockRuleReference :: IO ()+testFinalStockRuleReference = mapM_ check Registry.concreteAccountTitles+ where+ check RetainedEarnings = pure ()+ check t = assertEqual ("finalStockRule reference: " ++ show t)+ (expected t) (finalStockProbeRule t)+ expected t+ | t `L.elem`+ [ NetIncome+ , NetLoss+ , IncomeSummary+ , NetIncomeAttributableToNCI+ , NetLossAttributableToNCI+ ] = "Nothing"+ | contra && div_ == Revenue = "Flip"+ | contra && div_ == Cost = "Keep"+ | div_ == Revenue = "Keep"+ | div_ == Cost = "Flip"+ | otherwise = "Nothing"+ where+ div_ = classifyAccountDivision t+ contra = Registry.classifyAccountContra t++-- ================================================================+-- V-Land 2 scaffolding (語彙拡張の受理条件, レビュー非依存):+-- pre-vland2 fixture (tools/DumpVocabGolden.hs で生成, commit 85d6a7f に pin)+-- に対する Enum 挿入規律 pin と意味関数 closed-diff。+-- ================================================================++-- | 挿入規律 pin: 語彙拡張は「既存 concrete constructor の Enum 序数を 1 つも+-- 動かさず, 新規は最大既存 concrete 序数と wildcard の間にのみ挿入し,+-- wildcard ('AccountTitle') は maxBound のまま」でなければならない+-- (Binary Word16 直列化互換と既存 fixture 世代の解釈可能性の要)。+testVocabOrdinalPin :: IO ()+testVocabOrdinalPin = do+ fixture <- TIO.readFile "test/fixtures/pre-vland2/ordinals.tsv"+ let parseOrd line = case T.splitOn (T.pack "\t") line of+ [name, ordText] -> (name, read (T.unpack ordText) :: Int)+ fields -> error ("invalid pre-vland2 ordinals row: " ++ show fields)+ rows =+ [ parseOrd line+ | line <- T.lines fixture+ , not (T.null line)+ , not (T.isPrefixOf (T.pack "#") line)+ ]+ current = M.fromList+ [ (T.pack (show t), fromEnum t)+ | t <- [minBound .. maxBound] :: [AccountTitles] ]+ wildcardName = T.pack (show (AccountTitle :: AccountTitles))+ pinnedConcrete = [ r | r@(n, _) <- rows, n /= wildcardName ]+ moved =+ [ (n, o, M.lookup n current)+ | (n, o) <- pinnedConcrete+ , M.lookup n current /= Just o ]+ assertEqual "vocab ordinal pin: fixture rows = 117 (116 concrete + wildcard)"+ 117 (L.length rows)+ assertEqual "vocab ordinal pin: no pinned concrete ordinal moved" [] moved+ assertEqual "vocab ordinal pin: wildcard is maxBound"+ (fromEnum (maxBound :: AccountTitles))+ (fromEnum (AccountTitle :: AccountTitles))+ let maxPinned = L.maximum [ o | (_, o) <- pinnedConcrete ]+ pinnedNames = M.fromList [ (n, ()) | (n, _) <- rows ]+ misplaced =+ [ (n, o)+ | (n, o) <- M.toList current+ , not (M.member n pinnedNames)+ , not (o > maxPinned && o < fromEnum (maxBound :: AccountTitles)) ]+ assertEqual "vocab ordinal pin: new constructors sit between max pinned and wildcard"+ [] misplaced+ assertEqual "vocab ordinal pin: appended constructor ordinals and wildcard"+ [232..243]+ (L.map fromEnum+ [ ConsumptionTaxRefundReceivable+ , PropertyTaxPayable+ , DepositsReceivedFromOfficers+ , EntertainmentExpenses+ , MeetingExpenses+ , NewspaperBooksExpenses+ , RawMaterials+ , GoodsInTransit+ , SalesAllowances+ , PurchaseAllowances+ , ReversalOfAllowanceForBonuses+ , AccountTitle+ ])+ -- concreteAccountTitles は wildcard 以外の全 constructor を被覆すること。+ -- 現行の hardcoded 上限 ([Cash .. ReversalOfAllowanceForDoubtfulAccounts]) は+ -- 挿入後に新規科目が漏れるため, この assert が V-Land 2 に+ -- filter (/= wildcard) [minBound ..] への導出化を強制する。+ assertEqual "vocab ordinal pin: concreteAccountTitles covers all non-wildcard constructors"+ (L.filter (/= (AccountTitle :: AccountTitles)) [minBound .. maxBound])+ Registry.concreteAccountTitles++testAssistAllAccountInfos :: IO ()+testAssistAllAccountInfos = do+ assertEqual "Assist.allAccountInfos length" 243 (length Assist.allAccountInfos)+ assertEqual "Assist.allAccountInfos follows concreteAccountTitles order"+ PP.concreteAccountTitles (L.map Assist.aiTitle Assist.allAccountInfos)+ forM_ Assist.allAccountInfos $ \info -> do+ let title = Assist.aiTitle info+ case Registry.accountSemantics title of+ Nothing -> do+ putStrLn ("[FAIL] missing account semantics: " ++ show title)+ exitFailure+ Just semantics -> do+ assertEqual ("Assist.aiRoles " ++ show title)+ (Registry.asemRoles semantics) (Assist.aiRoles info)+ assertEqual ("Assist.aiPostingCapability " ++ show title)+ (Registry.asemPostingCapability semantics)+ (Assist.aiPostingCapability info)+ assertEqual ("Assist.aiDivisionSemantics " ++ show title)+ (Registry.asemDivisionSemantics semantics)+ (Assist.aiDivisionSemantics info)+ assertEqual ("Assist.aiHomeSideSemantics " ++ show title)+ (Registry.asemHomeSideSemantics semantics)+ (Assist.aiHomeSideSemantics info)+ assertEqual ("Assist.aiReportingEligibility " ++ show title)+ (Registry.asemReportingEligibility semantics)+ (Assist.aiReportingEligibility info)++testAccountSemanticsRegistryInvariants :: IO ()+testAccountSemanticsRegistryInvariants = do+ let semantics =+ [ (title, value)+ | title <- Registry.concreteAccountTitles+ , Just value <- [Registry.accountSemantics title]+ ]+ exceptional =+ [ NetIncome, NetLoss, GrossProfit, OrdinaryProfit, IncomeSummary+ , SuspensePayments, SuspenseReceipts, CashOverShort, SuspenseAccount+ , BranchCurrentAccount, HeadOfficeCurrentAccount+ , NetIncomeAttributableToNCI, NetLossAttributableToNCI+ ]+ lookupSem title = Registry.accountSemantics title+ lookupInfo title = Assist.describeAccount title+ nonStatementTitles =+ [ title+ | (title, value) <- semantics+ , case Registry.asemDivisionSemantics value of+ StatementDivision _ -> False+ _ -> True+ ]+ assertEqual "metadata covers all 243 concrete titles"+ 243 (L.length semantics)+ assertEqual "metadata rejects wildcard AccountTitle"+ Nothing (Registry.accountSemantics AccountTitle)+ assertEqual "non-statement metadata is exactly the reviewed exception set"+ (L.sort exceptional) (L.sort nonStatementTitles)+ forM_ semantics $ \(title, value) -> do+ assertEqual ("roles are non-empty: " ++ show title)+ True (not (L.null (Registry.asemRoles value)))+ -- Land 4a: rolesFor no longer consults asIsContra (explicit+ -- enumeration), so pin the contra role to the registry flag.+ assertEqual ("contra role matches registry isContra: " ++ show title)+ (Registry.classifyAccountContra title)+ (ContraAccount `elem` Registry.asemRoles value)+ case Registry.asemDivisionSemantics value of+ StatementDivision division -> do+ assertEqual ("statement division preserves legacy value: " ++ show title)+ (classifyAccountDivision title) division+ _ -> assertEqual ("exceptional title is closed-listed: " ++ show title)+ True (title `L.elem` exceptional)+ case Registry.asemHomeSideSemantics value of+ FixedHomeSide side ->+ assertEqual ("fixed home side preserves legacy value: " ++ show title)+ (whichSide (Not :< title)) side+ _ -> pure ()+ assertEqual "Cash semantics"+ (Just ( [OrdinaryAccount], OrdinaryPosting+ , StatementDivision Assets, FixedHomeSide Debit, StatementEligible ))+ (fmap semanticsTuple (lookupSem Cash))+ assertEqual "IncomeSummary semantics"+ (Just ( [ClosingDevice], ClosingOnly+ , DirectionEncoding Assets, ContextDependentHomeSide, NotPresented ))+ (fmap semanticsTuple (lookupSem IncomeSummary))+ assertEqual "NetIncome semantics"+ (Just ( [PeriodResult], EngineGeneratedOnly+ , DirectionEncoding Cost, FixedHomeSide Debit, DerivedPresentation ))+ (fmap semanticsTuple (lookupSem NetIncome))+ assertEqual "GrossProfit is an engine-generated coordinate"+ (Just ( [ReportingSubtotal], EngineGeneratedOnly+ , DirectionEncoding Revenue, FixedHomeSide Credit+ , DerivedPresentation ))+ (fmap semanticsTuple (lookupSem GrossProfit))+ assertEqual "NCI profit is distinct from bare net income"+ (Just ( [AttributionAccount, PeriodResult], ConsolidationOnly+ , DirectionEncoding Cost, FixedHomeSide Debit+ , ContextualPresentation ))+ (fmap semanticsTuple (lookupSem NetIncomeAttributableToNCI))+ assertEqual "NCI equity is consolidation-only"+ (Just ( [AttributionAccount], ConsolidationOnly+ , StatementDivision Equity, FixedHomeSide Credit+ , ContextualPresentation ))+ (fmap semanticsTuple (lookupSem NonControllingInterests))+ assertEqual "branch account semantics"+ (Just ( [ReciprocalAccount], OrdinaryPosting+ , BookkeepingControlClass Assets, FixedHomeSide Debit+ , ContextualPresentation ))+ (fmap semanticsTuple (lookupSem BranchCurrentAccount))+ assertEqual "IncomeSummary LLM description does not classify it as an asset"+ True (case lookupInfo IncomeSummary of+ Just info -> not (T.isPrefixOf (T.pack "Asset") (Assist.aiDesc info))+ && T.isInfixOf (T.pack "not a balance-sheet classification")+ (Assist.aiDesc info)+ Nothing -> False)+ assertEqual "NetIncome LLM name drops legacy Expense wording"+ (Just (T.pack "当期純利益")) (fmap Assist.aiNameJa (lookupInfo NetIncome))+ where+ semanticsTuple value =+ ( Registry.asemRoles value+ , Registry.asemPostingCapability value+ , Registry.asemDivisionSemantics value+ , Registry.asemHomeSideSemantics value+ , Registry.asemReportingEligibility value+ )++accountRegistryHeader :: T.Text -> T.Text+accountRegistryHeader what =+ T.pack "# account-semantics-050 Land 1 " <> what+ <> T.pack "; schema 1; base 09c8a60c0bfb1a7fedb01689ceee789b8b4e6084\n"++accountRegistryRow :: AccountTitles -> T.Text+accountRegistryRow title = case Registry.accountSemantics title of+ Nothing -> error ("missing AccountSemantics for " ++ show title)+ Just semantics -> T.intercalate (T.pack "\t")+ [ goldenShow title+ , goldenShow (Registry.asemRoles semantics)+ , goldenShow (Registry.asemPostingCapability semantics)+ , goldenShow (Registry.asemDivisionSemantics semantics)+ , goldenShow (Registry.asemHomeSideSemantics semantics)+ , goldenShow (Registry.asemReportingEligibility semantics)+ ]++accountRegistryInfoRow :: Assist.AccountInfo -> T.Text+accountRegistryInfoRow info = T.intercalate (T.pack "\t")+ [ goldenShow (Assist.aiTitle info)+ , goldenShow (Assist.aiRoles info)+ , goldenShow (Assist.aiPostingCapability info)+ , goldenShow (Assist.aiDivisionSemantics info)+ , goldenShow (Assist.aiHomeSideSemantics info)+ , goldenShow (Assist.aiReportingEligibility info)+ , goldenEsc (Assist.aiNameEn info)+ , goldenEsc (Assist.aiNameJa info)+ , goldenEsc (Assist.aiDesc info)+ ]++accountRegistrySuggestions :: T.Text+accountRegistrySuggestions =+ accountRegistryHeader+ (T.pack "LLM suggestAccounts (query, total matches, top-10 titles)")+ <> T.unlines (L.map row corpus)+ where+ infos = Assist.allAccountInfos+ fields = L.concat+ [ [goldenShow (Assist.aiTitle info), Assist.aiNameEn info, Assist.aiNameJa info]+ | info <- infos+ ]+ descTokens = L.concatMap (T.words . Assist.aiDesc) infos+ corpus = goldenDedupSort+ (L.concatMap (\value -> [value, T.toLower value]) fields <> descTokens)+ row query =+ let matches = L.map Assist.aiTitle (Assist.suggestAccounts query)+ in goldenEsc query <> T.pack "\t" <> goldenShow (L.length matches)+ <> T.pack "\t"+ <> T.intercalate (T.pack ",") (L.map goldenShow (L.take 10 matches))++testAccountSemanticsGolden :: IO ()+testAccountSemanticsGolden = do+ metadata <- TIO.readFile "test/fixtures/account-semantics-050/metadata.tsv"+ info <- TIO.readFile "test/fixtures/account-semantics-050/account-info.tsv"+ suggest <- TIO.readFile "test/fixtures/account-semantics-050/suggest.tsv"+ let expectedMetadata =+ accountRegistryHeader+ (T.pack "registry (title, roles, posting, divisionSemantics, homeSideSemantics, reportingEligibility)")+ <> T.unlines (L.map accountRegistryRow Registry.concreteAccountTitles)+ expectedInfo =+ accountRegistryHeader+ (T.pack "LLM AccountInfo (title, roles, posting, divisionSemantics, homeSideSemantics, reportingEligibility, nameEn, nameJa, description)")+ <> T.unlines (L.map accountRegistryInfoRow Assist.allAccountInfos)+ assertEqual "metadata fixture has 243 rows"+ 243 (L.length (L.drop 1 (T.lines metadata)))+ assertEqual "metadata fixture" metadata expectedMetadata+ assertEqual "LLM AccountInfo fixture" info expectedInfo+ assertEqual "LLM suggestion fixture"+ suggest accountRegistrySuggestions++testAssistSuggestAccounts :: IO ()+testAssistSuggestAccounts = do+ assertEqual "Assist.suggestAccounts cash contains Cash"+ True (Cash `elem` L.map Assist.aiTitle (Assist.suggestAccounts (T.pack "cash")))+ assertEqual "Assist.suggestAccounts 現金 contains Cash"+ True (Cash `elem` L.map Assist.aiTitle (Assist.suggestAccounts (T.pack "現金")))+ assertEqual "Assist.suggestAccounts empty query"+ [] (Assist.suggestAccounts T.empty)+ assertEqual "Assist.suggestAccounts no match"+ [] (Assist.suggestAccounts (T.pack "zzzznomatch"))++-- ================================================================+-- Registry goldens: alias resolution and account behavior.+-- ================================================================++goldenHeader :: T.Text -> T.Text+goldenHeader what = T.pack "# registry-aliases " <> what <> T.pack "; schema 1\n"++-- | Compare a rendered golden with its fixture. @EA_REGEN_GOLDEN=1@ rewrites+-- the fixture from the current output instead.+goldenCheck :: String -> FilePath -> T.Text -> IO ()+goldenCheck label path expected = do+ regen <- lookupEnv "EA_REGEN_GOLDEN"+ case regen of+ Just "1" -> TIO.writeFile path expected+ _ -> do+ actual <- TIO.readFile path+ assertEqual label actual expected++goldenShow :: Show a => a -> T.Text+goldenShow = T.pack . show++goldenEsc :: T.Text -> T.Text+goldenEsc = T.replace (T.pack "\t") (T.pack "\\t")+ . T.replace (T.pack "\n") (T.pack "\\n")++goldenDedupSort :: [T.Text] -> [T.Text]+goldenDedupSort = L.map L.head . L.group . L.sort++legacySuggestAccounts :: [Assist.AccountInfo] -> T.Text -> [Assist.AccountInfo]+legacySuggestAccounts infos query+ | L.null tokens = []+ | otherwise = L.map snd+ . L.sortOn (\(rank, info) -> (negate rank, fromEnum (Assist.aiTitle info)))+ . L.filter ((> 0) . fst)+ $ [ (matchRank info, info) | info <- infos ]+ where+ tokens = L.map T.toCaseFold (T.words query)+ matchRank info = L.length+ [ token+ | token <- tokens+ , L.any (T.isInfixOf token) (legacySearchFields info)+ ]+ legacySearchFields info = L.map T.toCaseFold $ case Registry.accountSpec (Assist.aiTitle info) of+ Just spec ->+ [ goldenShow (Assist.aiTitle info)+ , Registry.asNameEn spec+ , Registry.asNameJa spec+ , Registry.asDescription spec+ ]+ Nothing -> []++goldenInfoRow :: Assist.AccountInfo -> T.Text+-- Historical schema reconstruction. The live Land 1 AccountInfo projection is+-- pinned separately by accountRegistryInfoRow.+goldenInfoRow info = case Registry.accountSpec (Assist.aiTitle info) of+ Nothing -> error "goldenInfoRow: wildcard AccountTitle"+ Just spec -> T.intercalate (T.pack "\t")+ [ goldenShow (Assist.aiTitle info)+ , goldenShow (Registry.asDivision spec)+ , goldenShow (whichSide (Not :< Assist.aiTitle info))+ , goldenEsc (Registry.asNameEn spec)+ , goldenEsc (Registry.asNameJa spec)+ , goldenEsc (Registry.asDescription spec)+ ]++goldenAliasResolution :: T.Text -> T.Text+goldenAliasResolution fixture =+ goldenHeader (T.pack "parseAccountTitle over corpus (query, show(Either ConvError AccountTitles))")+ <> T.unlines (L.map row queries)+ where+ queries = L.map (T.takeWhile (/= '\t')) (L.drop 1 (T.lines fixture))+ row query = goldenEsc query <> T.pack "\t"+ <> goldenEsc (goldenShow (EC.parseAccountTitle query))+++postVocabHeader :: T.Text -> T.Text+postVocabHeader what = T.pack "# post-vocab " <> what <> T.pack "; schema 1\n"++postVocabInfoGolden :: T.Text+postVocabInfoGolden =+ postVocabHeader (T.pack "AccountInfo (title, division, homeSide, nameEn, nameJa, description)")+ <> T.unlines (L.map goldenInfoRow Assist.allAccountInfos)++postVocabSuggestionsGolden :: T.Text+postVocabSuggestionsGolden =+ postVocabHeader (T.pack "suggestAccounts (query, total matches, top-10 titles)")+ <> T.unlines (L.map row corpus)+ where+ infos = Assist.allAccountInfos+ nameFields = L.concatMap legacyNameFields infos+ descTokens = L.concatMap legacyDescTokens infos+ corpus = goldenDedupSort+ (L.concatMap (\q -> [q, T.toLower q]) nameFields <> descTokens)+ row query =+ let matches = L.map Assist.aiTitle (legacySuggestAccounts infos query)+ in goldenEsc query <> T.pack "\t"+ <> goldenShow (L.length matches) <> T.pack "\t"+ <> T.intercalate (T.pack ",") (L.map goldenShow (L.take 10 matches))+ legacyNameFields info = case Registry.accountSpec (Assist.aiTitle info) of+ Just spec ->+ [ goldenShow (Assist.aiTitle info)+ , Registry.asNameEn spec+ , Registry.asNameJa spec+ ]+ Nothing -> []+ legacyDescTokens info = case Registry.accountSpec (Assist.aiTitle info) of+ Just spec -> T.words (Registry.asDescription spec)+ Nothing -> []++postVocabOrdinalsGolden :: T.Text+postVocabOrdinalsGolden =+ postVocabHeader (T.pack "Enum ordinals (constructor, fromEnum)")+ <> T.unlines+ [ goldenShow title <> T.pack "\t" <> goldenShow (fromEnum title)+ | title <- [minBound .. maxBound] :: [AccountTitles]+ ]++postVocabSemanticsGolden :: T.Text+postVocabSemanticsGolden =+ postVocabHeader (T.pack "semantics (title, whatDiv, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe)")+ <> T.unlines (L.map row Registry.concreteAccountTitles)+ where+ row title =+ let nb = Not :< title :: HatBase AccountTitles+ hb = Hat :< title :: HatBase AccountTitles+ in T.intercalate (T.pack "\t")+ [ goldenShow title+ , goldenShow (whatDiv nb)+ , goldenShow (Registry.classifyAccountContra title)+ , goldenShow (whichSide nb)+ , goldenShow (whichSide hb)+ , goldenShow (whatPIMO nb)+ , goldenShow (fixedCurrent nb)+ , T.pack (finalStockProbeRule title)+ ]++testPostVocabGolden :: IO ()+testPostVocabGolden = do+ ordinals <- TIO.readFile "test/fixtures/post-vocab/ordinals.tsv"+ semantics <- TIO.readFile "test/fixtures/post-vocab/semantics.tsv"+ info <- TIO.readFile "test/fixtures/post-vocab/account-info.tsv"+ suggestions <- TIO.readFile "test/fixtures/post-vocab/suggest.tsv"+ assertEqual "post-vocab ordinal fixture" ordinals postVocabOrdinalsGolden+ assertEqual "post-vocab semantics fixture" semantics postVocabSemanticsGolden+ assertEqual "post-vocab account-info fixture" info postVocabInfoGolden+ assertEqual "post-vocab suggest fixture" suggestions postVocabSuggestionsGolden++-- ================================================================+-- Account behavior over every concrete title: closing, projection membership,+-- and the one-posting statement rows.+-- ================================================================++accountSemanticsHeader :: T.Text -> T.Text+accountSemanticsHeader what =+ T.pack "# account-algebra-behavior " <> what <> T.pack "; schema 1\n"++accountSemanticsBinaryHex :: AccountTitles -> T.Text+accountSemanticsBinaryHex = T.pack . concatMap hexByte . BL.unpack . Binary.encode+ where+ hexByte byte = case showHex byte "" of+ [digit] -> ['0', digit]+ digits -> digits++accountSemanticsBaselineTitles :: [AccountTitles]+accountSemanticsBaselineTitles = Registry.concreteAccountTitles++accountSemanticsSemanticsGolden :: T.Text+accountSemanticsSemanticsGolden =+ accountSemanticsHeader (T.pack "semantics (title, enum, binaryHex, division, closing, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe)")+ <> T.unlines (L.map row accountSemanticsBaselineTitles)+ where+ row title =+ let nb = Not :< title :: HatBase AccountTitles+ hb = Hat :< title :: HatBase AccountTitles+ spec = case Registry.accountSpec title of+ Just value -> value+ Nothing -> error ("missing AccountSpec for " ++ show title)+ in T.intercalate (T.pack "\t")+ [ goldenShow title+ , goldenShow (fromEnum title)+ , accountSemanticsBinaryHex title+ , goldenShow (Registry.asDivision spec)+ , goldenShow (Registry.asClosing spec)+ , goldenShow (Registry.asIsContra spec)+ , goldenShow (whichSide nb)+ , goldenShow (whichSide hb)+ , goldenShow (whatPIMO nb)+ , goldenShow (fixedCurrent nb)+ , T.pack (finalStockProbeRule title)+ ]++accountSemanticsProjectionGolden :: T.Text+accountSemanticsProjectionGolden =+ accountSemanticsHeader (T.pack "projection flags for Not then Hat (currentAssets, fixedAssets, deferredAssets, currentLiability, fixedLiability, capitalStock, contraAssets, contra)")+ <> T.unlines (L.map row accountSemanticsBaselineTitles)+ where+ kept :: (EA.Alg Double (HatBase AccountTitles)+ -> EA.Alg Double (HatBase AccountTitles))+ -> EA.Alg Double (HatBase AccountTitles)+ -> T.Text+ kept projection value = if norm (projection value) == (1 :: Double)+ then T.pack "1" else T.pack "0"+ row title = T.intercalate (T.pack "\t")+ (goldenShow title : L.concatMap (probe title) [Not, Hat])+ probe title hat =+ let value = 1 .@ hat :< title :: EA.Alg Double (HatBase AccountTitles)+ in [ kept Exchange.projCurrentAssets value+ , kept Exchange.projFixedAssets value+ , kept Exchange.projDeferredAssets value+ , kept Exchange.projCurrentLiability value+ , kept Exchange.projFixedLiability value+ , kept Exchange.projCapitalStock value+ , kept Exchange.projContraAssets value+ , kept Exchange.projContra value+ ]++accountSemanticsPresentationGolden :: T.Text+accountSemanticsPresentationGolden =+ accountSemanticsHeader (T.pack "presentation probe (title)")+ <> T.unlines (L.map row accountSemanticsBaselineTitles)+ where+ row title = goldenShow title++testAccountAlgebraBehaviorGolden :: IO ()+testAccountAlgebraBehaviorGolden = do+ let dir = "test/fixtures/account-algebra-behavior/"+ goldenCheck "account algebra behavior: closing and semantics"+ (dir ++ "semantics.tsv") accountSemanticsSemanticsGolden+ goldenCheck "account algebra behavior: projection membership"+ (dir ++ "projection-membership.tsv") accountSemanticsProjectionGolden+ goldenCheck "account algebra behavior: one-posting statement rows"+ (dir ++ "presentation.tsv") accountSemanticsPresentationGolden+ semantics <- TIO.readFile (dir ++ "semantics.tsv")+ assertEqual "account algebra behavior covers every concrete title"+ (L.length Registry.concreteAccountTitles)+ (L.length (L.filter (not . T.null) (L.drop 1 (T.lines semantics))))++testWriteRowsGolden :: IO ()+testWriteRowsGolden = do+ assertEqual "write-rows-0510 fixture count"+ 8 (L.length writeRowsFixtures)+ forM_ writeRowsFixtures $ \(name, expected) -> do+ actualFile <- TIO.readFile (writeRowsFixtureDir ++ "/" ++ name)+ assertEqual ("write-rows-0510 fixture " ++ name) actualFile expected++testReadoutBaselineGolden :: IO ()+testReadoutBaselineGolden = do+ assertEqual "readout-baseline-p1 fixture count"+ 21 (L.length fixtures)+ regen <- lookupEnv "EA_REGEN_GOLDEN"+ forM_ fixtures $ \(name, expected) -> do+ let path = ReadoutBaseline.readoutFixtureDir ++ "/" ++ name+ case regen of+ Just "1" -> TIO.writeFile path expected+ _ -> do+ actualFile <- TIO.readFile path+ assertEqual ("readout-baseline-p1 fixture " ++ name) actualFile expected+ where+ fixtures = ReadoutBaseline.readoutFixtures ++ ReadoutBaseline.keyedFixtures++testExampleNumbersGolden :: IO ()+testExampleNumbersGolden = do+ expected <- RippleFixture.renderFixture+ let path = RippleFixture.fixtureDir ++ "/" ++ RippleFixture.fixtureName+ numericRows = L.length (T.lines expected) - 1+ assertEqual "example-numbers-p1 ripple numeric row count" 243 numericRows+ regen <- lookupEnv "EA_REGEN_GOLDEN"+ case regen of+ Just "1" -> TIO.writeFile path expected+ _ -> do+ actual <- TIO.readFile path+ assertEqual "example-numbers-p1 ripple fixture" actual expected++testAdmissionBaselineGolden :: IO ()+testAdmissionBaselineGolden = do+ assertEqual "admission-baseline-p1 fixture count"+ 1 (L.length AdmissionBaseline.admissionFixtures)+ regen <- lookupEnv "EA_REGEN_GOLDEN"+ forM_ AdmissionBaseline.admissionFixtures $ \(name, expected, caseCount) -> do+ let path = AdmissionBaseline.admissionFixtureDir ++ "/" ++ name+ expectedCount = case name of+ "equivalence.tsv" -> 12+ _ -> 0+ assertEqual ("admission-baseline-p1 case count " ++ name)+ expectedCount caseCount+ assertEqual ("admission-baseline-p1 rendered rows " ++ name)+ expectedCount (L.length (T.lines expected) - 2)+ case regen of+ Just "1" -> TIO.writeFile path expected+ _ -> do+ actual <- TIO.readFile path+ assertEqual ("admission-baseline-p1 fixture " ++ name) actual expected++-- | Every historical alias query resolves as pinned. The queries come from the+-- fixture itself; regenerate with @EA_REGEN_GOLDEN=1@ after an intended change.+testAliasResolutionGolden :: IO ()+testAliasResolutionGolden = do+ let path = "test/fixtures/registry-aliases/alias-resolution.tsv"+ aliases <- TIO.readFile path+ assertEqual "alias resolution golden: query count"+ 703 (L.length (L.filter (not . T.null) (L.drop 1 (T.lines aliases))))+ goldenCheck "alias resolution golden" path (goldenAliasResolution aliases)++-- | JCCI 2022 A欄/B欄の全 distinct query は, 一意のRightかfixtureで候補を+-- 閉じたAmbiguousのどちらかでなければならない. Unknown/first-matchは不可.+testJcciAccountNameCoverage :: IO ()+testJcciAccountNameCoverage = do+ assertEqual "new account titles parse Japanese names and material alias"+ (L.map Right+ [ EntertainmentExpenses+ , MeetingExpenses+ , NewspaperBooksExpenses+ , RawMaterials+ , GoodsInTransit+ , RawMaterials+ ])+ (L.map EC.parseAccountTitle (L.map T.pack+ [ "交際費", "会議費", "新聞図書費", "原材料", "未着品", "材料" ]))+ source <- TIO.readFile "test/fixtures/jcci-2022/source.tsv"+ fixture <- TIO.readFile "test/fixtures/jcci-2022/queries.tsv"+ let sourceRows = L.filter (not . T.null) (L.drop 1 (T.lines source))+ rows = L.filter (not . T.null) (L.drop 1 (T.lines fixture))+ outcomes = [field L.!! 4 | row <- rows, let field = T.splitOn (T.pack "\t") row]+ sourceFields =+ [ fields+ | row <- sourceRows+ , let fields = T.splitOn (T.pack "\t") row+ , L.length fields == 6+ ]+ sourceEntries =+ [ (EC.normalizeTitle label, standardName)+ | fields <- sourceFields+ , let standardName = fields L.!! 2+ , label <- standardName : T.splitOn (T.pack "|") (fields L.!! 3)+ , not (T.null (T.strip label))+ ]+ sourceLabels = goldenDedupSort (L.map fst sourceEntries)+ fixtureLabels = goldenDedupSort+ [ EC.normalizeTitle (fields L.!! 3)+ | row <- rows+ , let fields = T.splitOn (T.pack "\t") row+ , L.length fields == 7+ ]+ assertEqual "JCCI source A-row count" 215 (L.length sourceRows)+ assertEqual "JCCI source rows have six columns"+ (L.length sourceRows) (L.length sourceFields)+ assertEqual "JCCI distinct normalized A/B query count" 316 (L.length rows)+ assertEqual "JCCI fixture covers exactly the source A/B labels"+ sourceLabels fixtureLabels+ assertEqual "JCCI unique resolutions" 295 (L.length (L.filter (== T.pack "right") outcomes))+ assertEqual "JCCI policy ambiguities" 21 (L.length (L.filter (== T.pack "ambiguous") outcomes))+ forM_ rows $ \row -> case T.splitOn (T.pack "\t") row of+ [_, _, _, query, _, _, standardNames] ->+ assertEqual ("JCCI source provenance: " ++ T.unpack query)+ (goldenDedupSort+ [ standardName+ | (label, standardName) <- sourceEntries+ , label == EC.normalizeTitle query+ ])+ (goldenDedupSort (T.splitOn (T.pack "|") standardNames))+ fields -> assertEqual "JCCI fixture provenance row has seven columns"+ (7 :: Int) (L.length fields)+ mapM_ check rows+ where+ check row = case T.splitOn (T.pack "\t") row of+ [_, _, _, query, outcome, candidateText, _] ->+ let names = T.splitOn (T.pack "|") candidateText+ in case traverse (`M.lookup` statementTitleMap) names of+ Nothing -> assertEqual "JCCI fixture names only real constructors"+ (T.pack "known constructors") candidateText+ Just candidates -> case candidates of+ [candidate] | outcome == T.pack "right" ->+ assertEqual ("JCCI Right: " ++ T.unpack query)+ (Right candidate) (EC.parseAccountTitle query)+ _ : _ : _ | outcome == T.pack "ambiguous" ->+ assertEqual ("JCCI Ambiguous: " ++ T.unpack query)+ (Left (EC.AmbiguousAccount query candidates))+ (EC.parseAccountTitle query)+ _ -> assertEqual "JCCI fixture outcome/candidate arity"+ (T.pack "right=1 or ambiguous>=2")+ (outcome <> T.pack ":" <> candidateText)+ _ -> assertEqual "JCCI fixture row has seven columns" (7 :: Int)+ (L.length (T.splitOn (T.pack "\t") row))++-- | Match the generated aliases to the frozen fixture, preserving query order+-- and removing duplicates. Additional aliases remain in the account registry.+-- The expectation re-implements the rule of @tools/gen-jcci-aliases.py@+-- (candidates split on @|@, queries in file order without duplicates); keep+-- the two in step when either changes.+testJcciAliasesMatchFixture :: IO ()+testJcciAliasesMatchFixture = do+ fixture <- TIO.readFile "test/fixtures/jcci-2022/queries.tsv"+ let rows = L.filter (not . T.null) (L.drop 1 (T.lines fixture))+ entries =+ [ (query, T.splitOn (T.pack "|") candidates)+ | row <- rows+ , [_, _, _, query, _, candidates, _] <- [T.splitOn (T.pack "\t") row]+ ]+ assertEqual "every JCCI fixture row has the seven expected columns"+ (L.length rows) (L.length entries)+ forM_ ([minBound .. maxBound] :: [AccountTitles]) $ \title -> do+ let expected = L.nub+ [ query+ | (query, candidates) <- entries+ , T.pack (show title) `L.elem` candidates+ ]+ assertEqual ("JCCI generated aliases: " ++ show title)+ expected (Registry.jcciAliases title)+ forM_+ [ (DeliveryExpenses, "荷造運賃")+ , (Fixtures, "器具備品")+ , (SalesAllowances, "売上値引")+ , (PurchaseAllowances, "仕入値引")+ ] $ \(title, alias) ->+ assertEqual ("registry retains alias: " ++ alias)+ True (T.pack alias `L.elem` Registry.accountAliases title)++-- | Every cleaned Japanese label is a bare account name, and every level-2+-- JCCI A-column name that resolves uniquely is the profile display label.+testJapaneseAccountLabels :: IO ()+testJapaneseAccountLabels = do+ let labels =+ [ (title, Registry.asLabelJa spec)+ | title <- Registry.concreteAccountTitles+ , Just spec <- [Registry.accountSpec title]+ ]+ forbidden = L.map T.pack ["。", "—", "\\/", "'"]+ invalid =+ [ (title, label)+ | (title, label) <- labels+ , any (`T.isInfixOf` label) forbidden+ || T.any (\c -> isAscii c && isAlpha c) label+ ]+ assertEqual "asLabelJa covers all 243 concrete titles"+ 243 (L.length labels)+ assertEqual "asLabelJa contains only cleaned Japanese account names"+ ([] :: [(AccountTitles, T.Text)]) invalid++ source <- TIO.readFile "test/fixtures/jcci-2022/source.tsv"+ let rows =+ [ fields+ | row <- L.drop 1 (T.lines source)+ , not (T.null row)+ , let fields = T.splitOn (T.pack "\t") row+ , L.length fields == 6+ , T.pack "2" `T.isInfixOf` (fields L.!! 0)+ ]+ aNames = L.map (L.!! 2) rows+ resolved =+ [ (name, title, RP.presentationLabel RP.JcciSecondGradeReport title)+ | name <- aNames+ , Right title <- [EC.parseAccountTitle name]+ ]+ skipped =+ [ name+ | name <- aNames+ , Left (EC.AmbiguousAccount _ _) <- [EC.parseAccountTitle name]+ ]+ unexpected =+ [ (name, show err)+ | name <- aNames+ , Left err <- [EC.parseAccountTitle name]+ , case err of EC.AmbiguousAccount _ _ -> False; _ -> True+ ]+ mismatches =+ [ (name, title, label)+ | (name, title, label) <- resolved+ , label /= name+ ]+ assertEqual "JCCI level-2 A-column rows" 117 (L.length aNames)+ assertEqual "JCCI A-column MATCH count" 113 (L.length resolved)+ assertEqual "JCCI A-column skip set"+ (L.sort (L.map T.pack+ ["営業収益", "営業費用", "為替差損益", "有価証券評価損益"]))+ (L.sort skipped)+ assertEqual "JCCI A-column has no unexpected parse failures"+ ([] :: [(T.Text, String)]) unexpected+ assertEqual "JCCI A-column labels MATCH 113/113"+ ([] :: [(T.Text, AccountTitles, T.Text)]) mismatches++testRegistryWildcards :: IO ()+testRegistryWildcards = do+ divisionResult <- try (evaluate (classifyAccountDivision AccountTitle))+ :: IO (Either SomeException AccountDivision)+ assertEqual "registry wildcard: classifyAccountDivision errors"+ True (case divisionResult of Left _ -> True; Right _ -> False)+ assertEqual "registry wildcard: fixedCurrent is Other"+ Other (fixedCurrent (Not :< AccountTitle))+ assertEqual "registry wildcard: describeAccount is Nothing"+ Nothing (Assist.describeAccount AccountTitle)+++-- ================================================================+-- Contra accounts (Definition 7 contra amendment): contract and relation+-- properties, presentation invariance.+-- ================================================================++contraAssetTitles :: [AccountTitles]+contraAssetTitles = [AllowanceForDoubtfulAccounts, AccumulatedDepreciation]++allContra :: [AccountTitles]+allContra = contraAssetTitles+ <> [SalesRebates, RefundOfIncomeTaxes, PurchaseRebates, SalesAllowances, PurchaseAllowances]++statementTitleMap :: M.Map T.Text AccountTitles+statementTitleMap = M.fromList+ [ (T.pack (show t), t) | t <- Registry.concreteAccountTitles ]++-- T2: 契約 isContra(b) ⇔ homeSide(b) ≠ defaultSide(whatDiv b)+testContraIffReversedHomeSide :: IO ()+testContraIffReversedHomeSide = mapM_ check Registry.concreteAccountTitles+ where+ check t =+ let nb = Not :< t :: HatBase AccountTitles+ in assertEqual ("contract (isContra = reversed home side): " ++ show t)+ (isContra nb) (whichSide nb /= defaultSide (whatDiv nb))++-- T3: pimoFlip は自己逆で, 原典の交換関係を保つ+testPimoFlipInvolution :: IO ()+testPimoFlipInvolution = do+ let pimoAll = [PS, IN, MS, OUT]+ mapM_ (\x -> assertEqual ("pimoFlip involution: " ++ show x)+ x (pimoFlip (pimoFlip x))) pimoAll+ mapM_ (\(x, y) -> assertEqual ("pimoFlip preserves (<=>): " ++ show (x, y))+ (x <=> y) (pimoFlip x <=> pimoFlip y))+ [ (x, y) | x <- pimoAll, y <- pimoAll ]++-- | Check the PIMO relation and its account-division extension (Prop 5.3.8).+testExchangeRelationProp538 :: IO ()+testExchangeRelationProp538 = do+ let pimoAll = [PS, IN, MS, OUT]+ divAll = [Assets, Equity, Liability, Cost, Revenue]+ assertEqual "(<=>) PIMO instance = Prop 5.3.8 pairs"+ [ (PS,IN), (PS,MS), (IN,PS), (IN,OUT), (MS,PS), (MS,OUT), (OUT,IN), (OUT,MS) ]+ [ (x, y) | x <- pimoAll, y <- pimoAll, x <=> y ]+ mapM_ (\(a, b) -> assertEqual ("(<=>) division = via pimoFromDivision: " ++ show (a, b))+ (pimoFromDivision a <=> pimoFromDivision b) (a <=> b))+ [ (a, b) | a <- divAll, b <- divAll ]+++-- T9: 8 組込み instance + custom instance (SimHatBase2) の全科目 sweepで+-- isContra のTrue集合が既存評価勘定2件 + V-Land 2 P/L控除3件になること。+testIsContraSweepAcrossBaseInstances :: IO ()+testIsContraSweepAcrossBaseInstances = do+ let day0 = fromGregorian 2026 1 1+ tod0 = TimeOfDay 0 0 0+ nm = T.pack "spec"+ sweep :: ExBaseClass b => String -> (AccountTitles -> b) -> IO ()+ sweep label mk = assertEqual ("isContra sweep: " ++ label)+ allContra+ [ t | t <- Registry.concreteAccountTitles, isContra (mk t) ]+ sweep "HatBase AccountTitles" (\t -> Not :< t :: HatBase AccountTitles)+ sweep "HatBase (AccountTitles, Day)" (\t -> Not :< (t, day0))+ sweep "HatBase (AccountTitles, Name)" (\t -> Not :< (t, nm))+ sweep "HatBase (CountUnit, AccountTitles)" (\t -> Not :< (Yen, t))+ sweep "HatBase (AccountTitles, Name, CountUnit)" (\t -> Not :< (t, nm, Yen))+ sweep "HatBase (AccountTitles, Name, CountUnit, Subject)"+ (\t -> Not :< (t, nm, Yen, nm))+ sweep "HatBase (AccountTitles, Name, CountUnit, Subject, Day)"+ (\t -> Not :< (t, nm, Yen, nm, day0))+ sweep "HatBase (AccountTitles, Name, CountUnit, Subject, Day, TimeOfDay)"+ (\t -> Not :< (t, nm, Yen, nm, day0, tod0))+ sweep "SimHatBase2 (custom instance)" (\t -> Not :< (t, 1, 2, Yen) :: SimHatBase2)+++-- | Check that allowance deductions preserve presentations without allowance activity.+testPresentationGroupsWithoutAllowances :: IO ()+testPresentationGroupsWithoutAllowances = do+ let defaultDefs = RG.defaultPresentationGrouping+ oldDefs = L.map restoreDeductions defaultDefs+ restoreDeductions definition = case RG.pgKey definition of+ RG.NetSalesGroup -> definition { RG.pgDeductions = [SalesRebates] }+ RG.NetPurchasesGroup -> definition { RG.pgDeductions = [PurchaseRebates] }+ _ -> definition+ displayedBlocks presentation =+ [(RG.pgKey definition, rows) | (definition, rows) <- RG.gpBlocks presentation]+ allowanceTitles = Set.fromList [SalesAllowances, PurchaseAllowances]+ check :: (HatVal v, Show v) => String -> M.Map AccountTitles (v, v) -> IO ()+ check valueType ordinary = do+ let rebates = M.union ordinary (M.fromList+ [ (SalesRebates, (100, 0))+ , (PurchaseRebates, (0, 50))+ ])+ zeroAllowances = M.union rebates (M.fromList+ [ (SalesAllowances, (0, 0))+ , (PurchaseAllowances, (0, 0))+ ])+ forM_ [("a", ordinary), ("b", rebates), ("c", zeroAllowances)] $+ \(scenario, balances) -> do+ let current = RG.presentGroups defaultDefs balances+ previous = RG.presentGroups oldDefs balances+ label = valueType ++ " allowance compatibility " ++ scenario+ -- Active groups consume all members, including absent allowances.+ expectedConsumed = case scenario of+ "a" -> RG.gpConsumed previous+ _ -> Set.union (RG.gpConsumed previous) allowanceTitles+ assertEqual (label ++ " displayed blocks")+ (displayedBlocks previous) (displayedBlocks current)+ assertEqual (label ++ " root totals")+ (RG.gpRootTotals previous) (RG.gpRootTotals current)+ assertEqual (label ++ " consumed titles")+ expectedConsumed (RG.gpConsumed current)+ let withAllowances = M.insert SalesAllowances (30, 0) rebates+ netSales =+ [ RG.grAmount row+ | (definition, rows) <- RG.gpBlocks+ (RG.presentGroups defaultDefs withAllowances)+ , RG.pgKey definition == RG.NetSalesGroup+ , row <- rows+ , RG.grKind row == RG.NetRow RG.NetSalesGroup+ ]+ -- Net sales: gross sales 1000 minus rebates 100 and allowances 30.+ assertEqual (valueType ++ " d: net sales deduct rebates and allowances")+ [RG.RelativeAmount False 870] netSales+ check "Double" (M.fromList+ [ (Sales, (0, 1000))+ , (Purchases, (600, 0))+ , (Cash, (400, 0))+ ] :: M.Map AccountTitles (Double, Double))+ check "MoneyDecimal" (M.fromList+ [ (Sales, (0, 1000))+ , (Purchases, (600, 0))+ , (Cash, (400, 0))+ ] :: M.Map AccountTitles (MoneyDecimal, MoneyDecimal))++testPresentationGroups :: IO ()+testPresentationGroups = do+ let defaultDefs = RG.defaultPresentationGrouping+ tradeDef = maybe (error "missing TradeReceivablesGroup") id+ (RG.lookupGroupDef defaultDefs RG.TradeReceivablesGroup)+ amount below magnitude = RG.RelativeAmount below (magnitude :: Double)+ present defs entries = RG.presentGroups defs (M.fromList entries)+ contraTitles =+ [ title+ | title <- Registry.concreteAccountTitles+ , maybe False Registry.asIsContra (Registry.accountSpec title)+ ]+ deductionTitles = L.concatMap RG.pgDeductions defaultDefs+ allMembers def = RG.pgGross def ++ RG.pgDeductions def+ assertEqual "default groups cover each registry contra exactly once"+ (L.sort contraTitles) (L.sort deductionTitles)+ assertEqual "net sales group includes sales allowances as a deduction"+ (Just [SalesRebates, SalesAllowances])+ (RG.pgDeductions <$> RG.lookupGroupDef defaultDefs RG.NetSalesGroup)+ assertEqual "net purchases group includes purchase allowances as a deduction"+ (Just [PurchaseRebates, PurchaseAllowances])+ (RG.pgDeductions <$> RG.lookupGroupDef defaultDefs RG.NetPurchasesGroup)+ assertEqual "default group memberships are disjoint"+ (L.length (L.concatMap allMembers defaultDefs))+ (Set.size (Set.fromList (L.concatMap allMembers defaultDefs)))+ forM_ defaultDefs $ \def ->+ forM_ (allMembers def) $ \title ->+ assertEqual ("default title lookup: " ++ show title)+ (Just (RG.pgKey def)) (RG.presentationGroupOf title)+ forM_ defaultDefs $ \def -> do+ forM_ (RG.pgGross def) $ \title ->+ assertEqual ("gross member is non-contra: " ++ show title)+ (Just (RG.pgDivision def, False))+ ((\spec -> (Registry.asDivision spec, Registry.asIsContra spec))+ <$> Registry.accountSpec title)+ forM_ (RG.pgDeductions def) $ \title ->+ assertEqual ("deduction member is same-division contra: " ++ show title)+ (Just (RG.pgDivision def, True))+ ((\spec -> (Registry.asDivision spec, Registry.asIsContra spec))+ <$> Registry.accountSpec title)++ let exceeded = present [tradeDef]+ [ (AccountsReceivable, (100, 0))+ , (AllowanceForDoubtfulAccounts, (0, 150))+ ]+ assertEqual "edge: contra exceeding gross yields a negative net"+ [(tradeDef,+ [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 100)+ , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 150)+ , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount True 50)+ ])]+ (RG.gpBlocks exceeded)++ let parentAbsent = present [tradeDef]+ [(AllowanceForDoubtfulAccounts, (0, 30))]+ assertEqual "edge: parent absent still renders deduction and net"+ [(tradeDef,+ [ RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 30)+ , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount True 30)+ ])]+ (RG.gpBlocks parentAbsent)++ let multiContraDef = tradeDef+ { RG.pgGross = [AccountsReceivable]+ , RG.pgDeductions =+ [AllowanceForDoubtfulAccounts, AccumulatedDepreciation]+ }+ multipleContra = present [multiContraDef]+ [ (AccountsReceivable, (1000, 0))+ , (AllowanceForDoubtfulAccounts, (0, 100))+ , (AccumulatedDepreciation, (0, 200))+ ]+ assertEqual "edge: multiple contra rows deduct exactly once"+ [(multiContraDef,+ [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 1000)+ , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 100)+ , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)+ , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount False 700)+ ])]+ (RG.gpBlocks multipleContra)++ let child = tradeDef { RG.pgParent = Just RG.DepreciableAssetsGroup }+ parent = maybe (error "missing DepreciableAssetsGroup") id+ (RG.lookupGroupDef defaultDefs RG.DepreciableAssetsGroup)+ nested = present [parent, child]+ [ (AccountsReceivable, (1000, 0))+ , (AllowanceForDoubtfulAccounts, (0, 100))+ , (Building, (800, 0))+ , (AccumulatedDepreciation, (0, 200))+ ]+ assertEqual "edge: nested child precedes and rolls into parent"+ [ (child,+ [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 1000)+ , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 100)+ , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount False 900)+ ])+ , (parent,+ [ RG.GroupRow (RG.GrossRow Building) (amount False 800)+ , RG.GroupRow (RG.SubgroupRow RG.TradeReceivablesGroup) (amount False 900)+ , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)+ , RG.GroupRow (RG.NetRow RG.DepreciableAssetsGroup) (amount False 1500)+ ])+ ]+ (RG.gpBlocks nested)+ assertEqual "edge: only the nested root contributes to totals"+ (M.singleton Assets (1800, 300)) (RG.gpRootTotals nested)+ assertEqual "edge: all nested definition members are consumed"+ (Set.fromList (L.concatMap allMembers [parent, child]))+ (RG.gpConsumed nested)++ let inactiveChild = present [parent, child]+ [ (AccountsReceivable, (1000, 0))+ , (Building, (800, 0))+ , (AccumulatedDepreciation, (0, 200))+ ]+ assertEqual "edge: inactive child gross is not rolled into its parent"+ [(parent,+ [ RG.GroupRow (RG.GrossRow Building) (amount False 800)+ , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)+ , RG.GroupRow (RG.NetRow RG.DepreciableAssetsGroup) (amount False 600)+ ])]+ (RG.gpBlocks inactiveChild)+ assertEqual "edge: inactive child gross remains unconsumed"+ (Set.fromList (allMembers parent)) (RG.gpConsumed inactiveChild)+ assertEqual "edge: inactive child cannot inflate the parent total"+ (M.singleton Assets (800, 200)) (RG.gpRootTotals inactiveChild)++ let salesDef = maybe (error "missing NetSalesGroup") id+ (RG.lookupGroupDef defaultDefs RG.NetSalesGroup)+ offsetContra = present [salesDef]+ [ (Sales, (0, 500))+ , (SalesRebates, (50, 50))+ ]+ assertEqual "edge: fully offset contra activity still activates its group"+ [(salesDef,+ [ RG.GroupRow (RG.GrossRow Sales) (amount False 500)+ , RG.GroupRow (RG.NetRow RG.NetSalesGroup) (amount False 500)+ ])]+ (RG.gpBlocks offsetContra)+ assertEqual "edge: fully offset contra title cannot leak to ordinary rows"+ (Set.fromList [Sales, SalesRebates, SalesAllowances]) (RG.gpConsumed offsetContra)++ let grouped = present defaultDefs+ [ (Purchases, (500, 0))+ , (PurchaseRebates, (0, 50))+ , (CorporateIncomeTaxes, (300, 0))+ , (RefundOfIncomeTaxes, (0, 40))+ ]+ groupRows = concatMap snd (RG.gpBlocks grouped)+ assertEqual "grouping: purchase rebate and net amount are pinned"+ True+ (RG.GroupRow (RG.DeductionRow PurchaseRebates) (amount True 50) `elem` groupRows+ && RG.GroupRow (RG.NetRow RG.NetPurchasesGroup) (amount False 450) `elem` groupRows)+ assertEqual "grouping: tax refund and net amount are pinned"+ True+ (RG.GroupRow (RG.DeductionRow RefundOfIncomeTaxes) (amount True 40) `elem` groupRows+ && RG.GroupRow (RG.NetRow RG.IncomeTaxesGroup) (amount False 260) `elem` groupRows)++-- T5/T6: projection の literal 期待値。+-- division projection は contra を含まず, contra は projContraAssets のみが選ぶ+-- (意図的差分: projCurrentLiability/projFixedLiability から当該 2 件が消えた)。+statementFixtureB1, statementFixtureB2, statementFixtureB3, statementFixtureB4, statementFixtureB5 :: BAlg+statementFixtureB1 = 100 .@ Not:<Cash .+ 60 .@ Not:<LoansPayable .+ 40 .@ Not:<CapitalStock+statementFixtureB2 = 500 .@ Not:<Sales .+ 300 .@ Not:<SalesCost+statementFixtureB3 = 1000 .@ Not:<AccountsReceivable .+ 900 .@ Not:<Cash .+ 800 .@ Not:<Building+ .+ 100 .@ Not:<AllowanceForDoubtfulAccounts .+ 200 .@ Not:<AccumulatedDepreciation+ .+ 2000 .@ Not:<CapitalStock .+ 400 .@ Not:<LoansPayable+statementFixtureB4 = 30 .@ Not:<Cash .+ 80 .@ Hat:<Cash+ .+ 100 .@ Not:<AllowanceForDoubtfulAccounts .+ 120 .@ Hat:<AllowanceForDoubtfulAccounts+ .+ 500 .@ Not:<Building .+ 200 .@ Not:<LoansPayable .+ 300 .@ Hat:<LoansPayable+ .+ 250 .@ Not:<AccumulatedDepreciation .+ 50 .@ Hat:<AccumulatedDepreciation+statementFixtureB5 = statementFixtureB3 .+ 300 .@ Not:<SalesCost .+ 500 .@ Not:<Sales .+ 200 .@ Not:<Cash++++testStatementContraAssetsLiteral :: IO ()+testStatementContraAssetsLiteral = do+ -- division projection (projCurrentAssets など) の値は presentation.txt の golden が+ -- 固定する. contra は projContraAssets のみが Hat/Not 双方を保持して選び, golden の+ -- projList に入っていないので literal で固定する。+ assertEqual "projContraAssets b3"+ "100.00:@Not:<AllowanceForDoubtfulAccounts .+ 200.00:@Not:<AccumulatedDepreciation"+ (show (Exchange.projContraAssets statementFixtureB3))+ assertEqual "projContraAssets b4 keeps Hat and Not, excludes Cash"+ "120.00:@Hat:<AllowanceForDoubtfulAccounts .+ 100.00:@Not:<AllowanceForDoubtfulAccounts .+ 50.00:@Hat:<AccumulatedDepreciation .+ 250.00:@Not:<AccumulatedDepreciation"+ (show (Exchange.projContraAssets statementFixtureB4))+ assertEqual "projContraAssets b5"+ "100.00:@Not:<AllowanceForDoubtfulAccounts .+ 200.00:@Not:<AccumulatedDepreciation"+ (show (Exchange.projContraAssets statementFixtureB5))++-- Projection output from the statement fixtures is pinned byte for byte.+statementPresentationLines :: [T.Text]+statementPresentationLines = L.concatMap sect+ [ ("b1-basic", statementFixtureB1), ("b2-pl", statementFixtureB2), ("b3-contra", statementFixtureB3)+ , ("b4-abnormal", statementFixtureB4), ("b5-closing", statementFixtureB5) ]+ where+ sect (n, a) =+ (T.pack ("## " ++ n))+ : L.concat [ [T.pack ("-- " ++ pn), T.pack (show (pf a))] | (pn, pf) <- projList ]+ projList =+ [ ("projCurrentAssets", Exchange.projCurrentAssets)+ , ("projFixedAssets", Exchange.projFixedAssets)+ , ("projDeferredAssets", Exchange.projDeferredAssets)+ , ("projCurrentLiability", Exchange.projCurrentLiability)+ , ("projFixedLiability", Exchange.projFixedLiability)+ , ("projCapitalStock", Exchange.projCapitalStock)+ ]++testStatementPresentationGolden :: IO ()+testStatementPresentationGolden =+ goldenCheck "statement presentation golden"+ "test/fixtures/statement-presentation/presentation.txt"+ (T.pack "# statement-presentation battery b1-b5; schema 1\n"+ <> T.unlines statementPresentationLines)++-- HatNot は whichSide で明示 error (規約の regression 固定)+testWhichSideHatNotErrors :: IO ()+testWhichSideHatNotErrors = do+ r <- try (evaluate (whichSide (HatNot :< Cash :: HatBase AccountTitles)))+ :: IO (Either SomeException Side)+ assertEqual "whichSide HatNot policy: explicit error"+ True (case r of Left _ -> True; Right _ -> False)++data ConsolidationFixtureRow+ = FixturePosting String String [String]+ AccountTitles AccountTitles MoneyDecimal+ | FixtureLink String String MoneyDecimal+ deriving (Show, Eq)++parseConsolidationFixtureRow :: T.Text -> Either String ConsolidationFixtureRow+parseConsolidationFixtureRow line = case T.splitOn (T.pack "\t") line of+ [kind, rowId, sourceIdsText, debitText, creditText, amountText]+ | kind == T.pack "source" || kind == T.pack "adjustment" -> do+ debitAccount <- firstShow (EC.parseAccountTitle debitText)+ creditAccount <- firstShow (EC.parseAccountTitle creditText)+ amount <- parseAmount amountText+ let sourceIds+ | sourceIdsText == T.pack "-" = []+ | otherwise = L.map T.unpack+ (T.splitOn (T.pack "|") sourceIdsText)+ Right (FixturePosting (T.unpack kind) (T.unpack rowId) sourceIds+ debitAccount creditAccount amount)+ | kind == T.pack "link" -> do+ amount <- parseAmount amountText+ Right (FixtureLink (T.unpack rowId) (T.unpack sourceIdsText) amount)+ fields -> Left ("invalid consolidation fixture row: " ++ show fields)+ where+ firstShow (Left err) = Left (show err)+ firstShow (Right value) = Right value+ parseAmount amountText = case reads (T.unpack amountText) of+ [(amount, "")] -> Right (fromInteger amount)+ _ -> Left ("invalid fixture amount: " ++ T.unpack amountText)++loadMinimalConsolidationFixture+ :: IO (CW.WorksheetInput String String MoneyDecimal)+loadMinimalConsolidationFixture = do+ contents <- TIO.readFile+ "test/fixtures/consolidation-worksheet-050/minimal.tsv"+ rows <- case traverse parseConsolidationFixtureRow+ (filter (not . T.null) (drop 1 (T.lines contents))) of+ Left err -> fail err+ Right parsed -> pure parsed+ let postings =+ [ (kind, rowId, sourceIds, debitAccount, creditAccount, amount)+ | FixturePosting kind rowId sourceIds debitAccount creditAccount amount+ <- rows+ ]+ sources =+ [ CW.TrialBalanceSource rowId+ (EC.journalFromSides+ [ (Debit, debitAccount, amount)+ , (Credit, creditAccount, amount)+ ] :: CheckedAlgM)+ | (kind, rowId, _, debitAccount, creditAccount, amount) <- postings+ , kind == "source"+ ]+ adjustments =+ [ CW.WorksheetAdjustment rowId (sourceId NE.:| sourceIdsTail)+ (EC.journalFromSides+ [ (Debit, debitAccount, amount)+ , (Credit, creditAccount, amount)+ ] :: CheckedAlgM)+ | (kind, rowId, sourceId : sourceIdsTail,+ debitAccount, creditAccount, amount) <- postings+ , kind == "adjustment"+ ]+ links = M.fromList+ [ (rowId, (direction, amount))+ | FixtureLink rowId direction amount <- rows+ ]+ sourceList <- case NE.nonEmpty sources of+ Nothing -> fail "minimal consolidation fixture has no sources"+ Just nonEmptySources -> pure nonEmptySources+ plResult <- fixtureResult links "pl-net-income"+ ownersPlResult <- fixtureResult links "pl-parent-net-income"+ ownersSsResult <- fixtureResult links "ss-parent-net-income"+ nciResult <- fixtureResult links "nci-period-share"+ openingRetained <- fixtureBalance links "opening-retained-earnings"+ retainedDividends <- fixtureAmount links "retained-earnings-dividends"+ ssClosingRetained <- fixtureBalance links "ss-closing-retained-earnings"+ bsRetained <- fixtureBalance links "bs-retained-earnings"+ openingNci <- fixtureBalance links "opening-nci"+ nciDividends <- fixtureAmount links "nci-dividends"+ closingNci <- fixtureBalance links "closing-nci"+ bsNci <- fixtureBalance links "bs-nci"+ pure (CW.WorksheetInput sourceList adjustments+ (CW.WorksheetLinkage+ { CW._profitOrLossNetIncome = plResult+ , CW._profitOrLossNetIncomeAttributableToOwners = ownersPlResult+ , CW._statementOfChangesNetIncomeAttributableToOwners =+ ownersSsResult+ , CW._openingRetainedEarnings = openingRetained+ , CW._retainedEarningsDividends = retainedDividends+ , CW._statementOfChangesClosingRetainedEarnings = ssClosingRetained+ , CW._balanceSheetRetainedEarnings = bsRetained+ , CW._openingNonControllingInterests = openingNci+ , CW._nonControllingInterestsPeriodShare = nciResult+ , CW._nonControllingInterestsDividends = nciDividends+ , CW._statementOfChangesClosingNonControllingInterests = closingNci+ , CW._balanceSheetNonControllingInterests = bsNci+ }))+ where+ fixtureAmount links key = case M.lookup key links of+ Just ("amount", amount) -> pure amount+ Just (direction, _) -> fail+ ("expected amount direction for " ++ key ++ ", got " ++ direction)+ Nothing -> fail ("missing fixture link: " ++ key)+ fixtureResult links key = case M.lookup key links of+ Just ("profit", amount) -> pure (RM.PeriodProfit amount)+ Just ("loss", amount) -> pure (RM.PeriodLoss amount)+ Just ("break-even", _) -> pure RM.PeriodBreakEven+ Just (direction, _) -> fail+ ("invalid period-result direction for " ++ key ++ ": " ++ direction)+ Nothing -> fail ("missing fixture link: " ++ key)+ fixtureBalance links key = case M.lookup key links of+ Just ("credit", amount) -> pure (TBB.CreditBalance amount)+ Just ("debit", amount) -> pure (TBB.DebitBalance amount)+ Just (direction, _) -> fail+ ("invalid balance direction for " ++ key ++ ": " ++ direction)+ Nothing -> fail ("missing fixture link: " ++ key)++leftErrors :: Either (NE.NonEmpty e) a -> Maybe (NE.NonEmpty e)+leftErrors (Left errors) = Just errors+leftErrors (Right _) = Nothing++testConsolidationWorksheet :: IO ()+testConsolidationWorksheet = do+ fixture <- loadMinimalConsolidationFixture+ validated <- case CW.validateConsolidationWorksheet fixture of+ Left errors -> fail ("valid consolidation fixture rejected: " ++ show errors)+ Right value -> pure value+ assertEqual "consolidation worksheet: source provenance retained"+ ["parent", "subsidiary"]+ [ CW._sourceId source+ | source <- NE.toList (CW.validatedSources validated)+ ]+ assertEqual "consolidation worksheet: adjustment provenance retained"+ [("nci-attribution", ["parent", "subsidiary"])]+ [ ( CW._adjustmentId adjustment+ , NE.toList (CW._adjustmentSourceIds adjustment)+ )+ | adjustment <- CW.validatedAdjustments validated+ ]+ let combined = CW.combinedWorksheet validated+ assertEqual "consolidation worksheet: combined fixture stays balanced"+ True (exactBalancedForTest combined)+ assertEqual "consolidation worksheet: combined debit total"+ (200 :: MoneyDecimal) (EA.norm (Exchange.decL combined))+ assertEqual "consolidation worksheet: combined credit total"+ (200 :: MoneyDecimal) (EA.norm (Exchange.decR combined))+ assertEqual "consolidation worksheet: combination preserves all sequences"+ 6 (L.length (EA.vals combined))++ let rawAdjustment =+ (10 EA..@ (Not :< Cash)) EA..++ (5 EA..@ (Not :< Cash)) EA..++ (15 EA..@ (Not :< Sales)) :: CheckedAlgM+ rawInput = CW.WorksheetInput (CW._worksheetSources fixture)+ [CW.WorksheetAdjustment "raw-three-posting"+ ("parent" NE.:| []) rawAdjustment]+ (CW._worksheetLinkage fixture)+ assertEqual "consolidation worksheet: accepts non-journal-shaped raw Alg"+ True+ (case CW.validateConsolidationWorksheet rawInput of+ Right _ -> True+ Left _ -> False)++ let sources = CW._worksheetSources fixture+ links = CW._worksheetLinkage fixture+ debitOnly = EC.journalFromSides+ [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM+ creditOnly = EC.journalFromSides+ [(Credit, Sales, 10 :: MoneyDecimal)] :: CheckedAlgM+ cancellingSet = debitOnly EA..+ creditOnly+ cancellingInput = CW.WorksheetInput sources+ [ CW.WorksheetAdjustment "bad-debit" ("parent" NE.:| []) debitOnly+ , CW.WorksheetAdjustment "bad-credit" ("parent" NE.:| []) creditOnly+ ] links+ assertEqual "consolidation worksheet: malformed set can balance in aggregate"+ True (exactBalancedForTest cancellingSet)+ assertEqual "consolidation worksheet: atomic gate rejects both malformed adjustments"+ (Just+ ( CW.UnbalancedAdjustment "bad-debit" 10 0 NE.:|+ [CW.UnbalancedAdjustment "bad-credit" 0 10]+ ))+ (leftErrors (CW.validateConsolidationWorksheet cancellingInput))++ let forbidden = EC.journalFromSides+ [ (Debit, NetIncome, 10 :: MoneyDecimal)+ , (Credit, RetainedEarnings, 10)+ ] :: CheckedAlgM+ forbiddenInput = CW.WorksheetInput sources+ [CW.WorksheetAdjustment "forbidden" ("parent" NE.:| []) forbidden]+ links+ assertEqual "consolidation worksheet: context capability applies to raw Alg"+ (Just (CW.AdjustmentPostingNotAllowed "forbidden" NetIncome+ EngineGeneratedOnly NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet forbiddenInput))++ let provenanceInput = CW.WorksheetInput sources+ [CW.WorksheetAdjustment "unknown"+ ("ghost" NE.:| ["ghost"]) (mempty :: CheckedAlgM)] links+ assertEqual "consolidation worksheet: provenance rejects duplicate and unknown source"+ (Just+ ( CW.DuplicateAdjustmentSource "unknown" "ghost" NE.:|+ [ CW.UnknownAdjustmentSource "unknown" "ghost"+ , CW.EmptyAdjustment "unknown"+ ]+ ))+ (leftErrors (CW.validateConsolidationWorksheet provenanceInput))++ let mismatchedLinks = links+ { CW._statementOfChangesNetIncomeAttributableToOwners =+ RM.PeriodProfit 40 }+ mismatchedInput = fixture { CW._worksheetLinkage = mismatchedLinks }+ assertEqual "consolidation worksheet: P/L to S/S mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet mismatchedInput of+ Left errors -> CW.OwnersPeriodResultLinkMismatch+ (RM.PeriodProfit 50) (RM.PeriodProfit 40) `elem` NE.toList errors+ Right _ -> False)++ let attributionLinks = links+ { CW._profitOrLossNetIncome = RM.PeriodProfit 60 }+ attributionInput = fixture { CW._worksheetLinkage = attributionLinks }+ assertEqual "consolidation worksheet: total attribution mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet attributionInput of+ Left errors -> CW.NetIncomeAttributionMismatch 60 70+ `elem` NE.toList errors+ Right _ -> False)++ let retainedLinks = links+ { CW._retainedEarningsDividends = 11 }+ retainedInput = fixture { CW._worksheetLinkage = retainedLinks }+ assertEqual "consolidation worksheet: retained-earnings mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet retainedInput of+ Left errors -> CW.RetainedEarningsRollForwardMismatch 150 151+ `elem` NE.toList errors+ Right _ -> False)++ let balanceSheetLinks = links+ { CW._balanceSheetRetainedEarnings = TBB.CreditBalance 139 }+ balanceSheetInput = fixture { CW._worksheetLinkage = balanceSheetLinks }+ assertEqual "consolidation worksheet: S/S to B/S mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet balanceSheetInput of+ Left errors -> CW.BalanceSheetRetainedEarningsMismatch+ (TBB.CreditBalance 140) (TBB.CreditBalance 139)+ `elem` NE.toList errors+ Right _ -> False)++ let nciLinks = links { CW._nonControllingInterestsDividends = 6 }+ nciInput = fixture { CW._worksheetLinkage = nciLinks }+ assertEqual "consolidation worksheet: NCI roll-forward mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet nciInput of+ Left errors -> CW.NonControllingInterestsRollForwardMismatch 50 51+ `elem` NE.toList errors+ Right _ -> False)++ let nciBalanceSheetLinks = links+ { CW._balanceSheetNonControllingInterests = TBB.CreditBalance 44 }+ nciBalanceSheetInput = fixture+ { CW._worksheetLinkage = nciBalanceSheetLinks }+ assertEqual "consolidation worksheet: NCI S/S to B/S mismatch is explicit"+ True+ (case CW.validateConsolidationWorksheet nciBalanceSheetInput of+ Left errors -> CW.BalanceSheetNonControllingInterestsMismatch+ (TBB.CreditBalance 45) (TBB.CreditBalance 44)+ `elem` NE.toList errors+ Right _ -> False)++ let lossLinks = links+ { CW._profitOrLossNetIncome = RM.PeriodLoss 25+ , CW._profitOrLossNetIncomeAttributableToOwners = RM.PeriodLoss 20+ , CW._statementOfChangesNetIncomeAttributableToOwners =+ RM.PeriodLoss 20+ , CW._openingRetainedEarnings = TBB.CreditBalance 100+ , CW._retainedEarningsDividends = 10+ , CW._statementOfChangesClosingRetainedEarnings =+ TBB.CreditBalance 70+ , CW._balanceSheetRetainedEarnings = TBB.CreditBalance 70+ , CW._openingNonControllingInterests = TBB.CreditBalance 30+ , CW._nonControllingInterestsPeriodShare = RM.PeriodLoss 5+ , CW._nonControllingInterestsDividends = 5+ , CW._statementOfChangesClosingNonControllingInterests =+ TBB.CreditBalance 20+ , CW._balanceSheetNonControllingInterests = TBB.CreditBalance 20+ }+ lossInput = fixture { CW._worksheetLinkage = lossLinks }+ assertEqual "consolidation worksheet: loss roll-forwards preserve direction"+ True+ (case CW.validateConsolidationWorksheet lossInput of+ Right _ -> True+ Left _ -> False)++ let deficitLinks = links+ { CW._profitOrLossNetIncome = RM.PeriodLoss 50+ , CW._profitOrLossNetIncomeAttributableToOwners = RM.PeriodLoss 50+ , CW._statementOfChangesNetIncomeAttributableToOwners =+ RM.PeriodLoss 50+ , CW._openingRetainedEarnings = TBB.CreditBalance 10+ , CW._retainedEarningsDividends = 0+ , CW._statementOfChangesClosingRetainedEarnings =+ TBB.DebitBalance 40+ , CW._balanceSheetRetainedEarnings = TBB.DebitBalance 40+ , CW._openingNonControllingInterests = TBB.CreditBalance 0+ , CW._nonControllingInterestsPeriodShare = RM.PeriodBreakEven+ , CW._nonControllingInterestsDividends = 0+ , CW._statementOfChangesClosingNonControllingInterests =+ TBB.CreditBalance 0+ , CW._balanceSheetNonControllingInterests = TBB.CreditBalance 0+ }+ deficitInput = fixture { CW._worksheetLinkage = deficitLinks }+ assertEqual "consolidation worksheet: accumulated deficit is structural"+ True+ (case CW.validateConsolidationWorksheet deficitInput of+ Right _ -> True+ Left _ -> False)++ let invalidLinks = links+ { CW._openingRetainedEarnings = TBB.CreditBalance (-1) }+ invalidInput = fixture { CW._worksheetLinkage = invalidLinks }+ assertEqual "consolidation worksheet: negative linkage amount rejected"+ (Just (CW.InvalidLinkAmount CW.OpeningRetainedEarnings (-1) NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet invalidInput))++ let wildcardAccount =+ (10 EA..@ (Not :< AccountTitle)) EA..++ (10 EA..@ (Hat :< Cash)) :: CheckedAlgM+ wildcardInput = CW.WorksheetInput sources+ [CW.WorksheetAdjustment "wildcard" ("parent" NE.:| [])+ wildcardAccount] links+ assertEqual "consolidation worksheet: wildcard error list is total"+ (Just (CW.WildcardAdjustmentAccount "wildcard" NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet wildcardInput))++ let duplicateSources = case sources of+ source NE.:| rest -> source NE.:| (source : rest)+ duplicateAdjustments =+ [ CW.WorksheetAdjustment "same" ("parent" NE.:| [])+ rawAdjustment+ , CW.WorksheetAdjustment "same" ("parent" NE.:| [])+ rawAdjustment+ ]+ duplicateInput = CW.WorksheetInput duplicateSources+ duplicateAdjustments links+ assertEqual "consolidation worksheet: duplicate stable IDs rejected"+ True+ (case CW.validateConsolidationWorksheet duplicateInput of+ Left errors -> CW.DuplicateSourceId "parent" `elem` NE.toList errors+ && CW.DuplicateAdjustmentId "same" `elem` NE.toList errors+ Right _ -> False)++ let wildcardSource = CW.TrialBalanceSource "wild-source"+ (10 `Internal.Single` (HatNot :< Cash) :: CheckedAlgM)+ wildcardSourceInput+ :: CW.WorksheetInput String String MoneyDecimal+ wildcardSourceInput = CW.WorksheetInput+ (wildcardSource NE.:| []) [] links+ assertEqual "consolidation worksheet: wildcard source side is total"+ (Just (CW.WildcardSourceSide "wild-source" NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet wildcardSourceInput))++ let wildcardSide =+ 10 `Internal.Single` (HatNot :< Cash) :: CheckedAlgM+ wildcardSideInput = CW.WorksheetInput sources+ [CW.WorksheetAdjustment "wild-side" ("parent" NE.:| [])+ wildcardSide] links+ assertEqual "consolidation worksheet: wildcard adjustment side is total"+ (Just (CW.WildcardAdjustmentSide "wild-side" NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet wildcardSideInput))++ let unbalancedSource = CW.TrialBalanceSource "unbalanced-source"+ (EC.journalFromSides+ [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM)+ unbalancedSourceInput+ :: CW.WorksheetInput String String MoneyDecimal+ unbalancedSourceInput = CW.WorksheetInput+ (unbalancedSource NE.:| []) [] links+ assertEqual "consolidation worksheet: unbalanced source rejected"+ (Just (CW.UnbalancedSourceTrialBalance+ "unbalanced-source" 10 0 NE.:| []))+ (leftErrors (CW.validateConsolidationWorksheet unbalancedSourceInput))++trialBalanceInput+ :: CheckedAlgM+ -> TB.TrialBalanceStage+ -> TB.TrialBalanceInput MoneyDecimal+trialBalanceInput alg stage = TB.TrialBalanceInput+ { TB._trialBalanceElement = alg+ , TB._trialBalanceStage = stage+ , TB._temporaryBalanceExplanations = M.empty+ , TB._reclassificationRules = []+ , TB._maturityEvidenceTitles = Set.empty+ }++testSharedAccountBalancePrimitives :: IO ()+testSharedAccountBalancePrimitives = do+ let balances =+ [ TBB.NoBalance+ , TBB.DebitBalance 7+ , TBB.CreditBalance 11+ ] :: [TBB.AccountBalance Int]+ assertEqual "account balance: pair netting round trip"+ balances (fmap (TBB.netPair . TBB.balancePair) balances)+ assertEqual "account balance: structural sides"+ [Side, Debit, Credit] (fmap TBB.balanceSide balances)++testTrialBalanceValidation :: IO ()+testTrialBalanceValidation = do+ let reciprocalMismatch = EC.journalFromSides+ [ (Debit, BranchCurrentAccount, 40 :: MoneyDecimal)+ , (Credit, HeadOfficeCurrentAccount, 30)+ , (Credit, CapitalStock, 10)+ ] :: CheckedAlgM+ reciprocalInput = trialBalanceInput reciprocalMismatch TB.BeforeClosing+ expectedReciprocal = TB.ReciprocalMismatch+ (TB.DebitBalance 40) (TB.CreditBalance 30)+ assertEqual "trial balance: reciprocal mismatch independent of global balance"+ (Just (expectedReciprocal NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (reciprocalInput)))+ let standaloneReciprocal = EC.journalFromSides+ [ (Debit, BranchCurrentAccount, 40 :: MoneyDecimal)+ , (Credit, CapitalStock, 40)+ ] :: CheckedAlgM+ standaloneInput = trialBalanceInput standaloneReciprocal TB.BeforeClosing+ expectedStandalone = TB.StandaloneReciprocalBalance+ BranchCurrentAccount (TB.DebitBalance 40)+ assertEqual "trial balance: standalone reciprocal balance is a finding"+ [expectedStandalone] (TB.trialBalanceFindings standaloneInput)++ let explainedSuspense = EC.journalFromSides+ [ (Debit, SuspensePayments, 10 :: MoneyDecimal)+ , (Credit, CapitalStock, 10)+ ] :: CheckedAlgM+ explanation = T.pack "invoice received after reporting date"+ explainedInput = (trialBalanceInput explainedSuspense TB.BeforeClosing)+ { TB._temporaryBalanceExplanations =+ M.singleton SuspensePayments explanation }+ expectedExplained = TB.ExplainedTemporaryBalance SuspensePayments+ (TB.DebitBalance 10) explanation+ assertEqual "trial balance: explained temporary balance retained"+ [expectedExplained] (TB.trialBalanceFindings explainedInput)+ let unresolvedInput = trialBalanceInput explainedSuspense TB.BeforeClosing+ assertEqual "trial balance: unexplained temporary balance is a finding"+ (Just (TB.UnresolvedTemporaryBalance SuspensePayments+ (TB.DebitBalance 10) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (unresolvedInput)))+ let blankInput = explainedInput+ { TB._temporaryBalanceExplanations =+ M.singleton SuspensePayments (T.pack " ") }+ assertEqual "trial balance: blank explanation is a finding"+ (Just (TB.BlankTemporaryExplanation SuspensePayments+ (TB.DebitBalance 10) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (blankInput)))++ let closingResidual = EC.journalFromSides+ [ (Debit, CashOverShort, 10 :: MoneyDecimal)+ , (Credit, IncomeSummary, 10)+ ] :: CheckedAlgM+ closingInput = trialBalanceInput closingResidual TB.AfterClosing+ assertEqual "trial balance: closing devices must be zero after closing"+ (Just+ ( TB.ClosingDeviceResidual CashOverShort (TB.DebitBalance 10)+ NE.:|+ [TB.ClosingDeviceResidual IncomeSummary (TB.CreditBalance 10)]+ ))+ (NE.nonEmpty (TB.trialBalanceFindings (closingInput)))+ let unclosedNominal = EC.journalFromSides+ [ (Debit, Cash, 10 :: MoneyDecimal)+ , (Credit, Sales, 10)+ ] :: CheckedAlgM+ assertEqual "trial balance: nominal balances must close after closing"+ (Just (TB.UnclosedNominalBalance Sales+ (TB.CreditBalance 10) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings ((trialBalanceInput unclosedNominal TB.AfterClosing))))++ let derivedResidual = EC.journalFromSides+ [ (Debit, NetIncome, 10 :: MoneyDecimal)+ , (Credit, CapitalStock, 10)+ ] :: CheckedAlgM+ assertEqual "trial balance: derived coordinates are residuals after closing"+ (Just (TB.DerivedCoordinateResidual NetIncome+ (TB.DebitBalance 10) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings ((trialBalanceInput derivedResidual TB.AfterClosing))))++ let abnormalDeposit = EC.journalFromSides+ [ (Debit, Cash, 10 :: MoneyDecimal)+ , (Credit, CurrentDeposits, 10)+ ] :: CheckedAlgM+ abnormalInput = trialBalanceInput abnormalDeposit TB.BeforeClosing+ oneRule = TB.SideReclassificationRule CurrentDeposits Credit+ (ShortTermLoansPayable NE.:| [])+ twoRules = TB.SideReclassificationRule CurrentDeposits Credit+ (BankOverdraft NE.:| [ShortTermLoansPayable])+ assertEqual "trial balance: unexplained abnormal side is explicit"+ (Just (TB.UnexplainedAbnormalBalance CurrentDeposits Debit+ (TB.CreditBalance 10) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (abnormalInput)))+ assertEqual "trial balance: unique reclassification is an instruction"+ (Just (TB.AbnormalBalanceWithReclassificationRule CurrentDeposits+ (TB.CreditBalance 10) ShortTermLoansPayable NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (abnormalInput { TB._reclassificationRules = [oneRule] })))+ assertEqual "trial balance: ambiguous reclassification is never automatic"+ (Just (TB.AmbiguousReclassification CurrentDeposits+ (TB.CreditBalance 10)+ (BankOverdraft NE.:| [ShortTermLoansPayable]) NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (abnormalInput { TB._reclassificationRules = [twoRules] })))++ let recordedTransfer = EC.journalFromSides+ [ (Debit, CurrentDeposits, 10 :: MoneyDecimal)+ , (Credit, ShortTermLoansPayable, 10)+ ] :: CheckedAlgM+ transferred = abnormalDeposit .+ recordedTransfer+ transferredInput = (trialBalanceInput transferred TB.BeforeClosing)+ { TB._reclassificationRules = [oneRule] }+ assertEqual "trial balance: recorded transfer clears abnormal finding"+ [] (TB.trialBalanceFindings transferredInput)++ let invalidRule = TB.SideReclassificationRule CashOverShort Credit+ (MiscellaneousIncome NE.:| [])+ invalidRuleInput = (trialBalanceInput mempty TB.BeforeClosing)+ { TB._reclassificationRules = [invalidRule] }+ assertEqual "trial balance: inapplicable rules are explicit"+ (Just (TB.InapplicableReclassificationRule invalidRule NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (invalidRuleInput)))+ let deadRule = TB.SideReclassificationRule CurrentDeposits Debit+ (Cash NE.:| [])+ deadRuleInput = (trialBalanceInput mempty TB.BeforeClosing)+ { TB._reclassificationRules = [deadRule] }+ assertEqual "trial balance: normal-side trigger is a dead rule"+ (Just (TB.InapplicableReclassificationRule deadRule NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (deadRuleInput)))++ let maturityBalance = EC.journalFromSides+ [ (Debit, LoansReceivable, 25 :: MoneyDecimal)+ , (Credit, CapitalStock, 25)+ ] :: CheckedAlgM+ maturityRule = TB.MaturityEvidenceRequired LoansReceivable+ missingMaturity = (trialBalanceInput maturityBalance TB.BeforeClosing)+ { TB._reclassificationRules = [maturityRule] }+ suppliedMaturity = missingMaturity+ { TB._maturityEvidenceTitles = Set.singleton LoansReceivable }+ assertEqual "trial balance: maturity-sensitive title requires evidence"+ (Just (TB.MissingMaturityEvidence LoansReceivable NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (missingMaturity)))+ assertEqual "trial balance: supplied maturity evidence clears finding"+ [] (TB.trialBalanceFindings suppliedMaturity)++ let unbalanced = EC.journalFromSides+ [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM+ unbalancedInput = trialBalanceInput unbalanced TB.BeforeClosing+ assertEqual "trial balance: exact global imbalance is a finding"+ (Just (TB.UnbalancedTrialBalance 10 0 NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (unbalancedInput)))++ let wildcard = 10 `Internal.Single` (HatNot :< Cash) :: CheckedAlgM+ wildcardInput = trialBalanceInput wildcard TB.BeforeClosing+ assertEqual "trial balance: wildcard side reports without whichSide crash"+ (Just (TB.WildcardTrialBalanceSide NE.:| []))+ (NE.nonEmpty (TB.trialBalanceFindings (wildcardInput)))++ forM_ [reciprocalInput, standaloneInput, unresolvedInput, blankInput,+ explainedInput, abnormalInput,+ abnormalInput { TB._reclassificationRules = [oneRule] },+ abnormalInput { TB._reclassificationRules = [twoRules] }] $ \input ->+ assertEqual "audit findings do not block a presentable trial balance"+ True (case RP.present (RP.jcciSecondGradeContext RP.Standalone)+ (TB._trialBalanceStage input) (TB._trialBalanceElement input) of+ Right _ -> True+ Left _ -> False)+ let maturityContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._maturitySensitiveTitles = Set.singleton LoansReceivable+ , RP._presentationAllocations =+ [RP.PresentationAllocation LoansReceivable 10 15 (T.pack "schedule")] }+ assertEqual "missing maturity audit evidence does not block supplied presentation split"+ True (case RP.present maturityContext TB.BeforeClosing maturityBalance of+ Right _ -> True+ Left _ -> False)++ let titlesFor role =+ [ title+ | title <- Registry.concreteAccountTitles+ , Just semantics <- [Registry.accountSemantics title]+ , role `elem` Registry.asemRoles semantics+ ]+ assertEqual "trial balance: reciprocal registry role is pinned"+ [BranchCurrentAccount, HeadOfficeCurrentAccount]+ (titlesFor ReciprocalAccount)+ assertEqual "trial balance: suspense registry role is pinned"+ [SuspensePayments, CashOverShort, SuspenseReceipts, SuspenseAccount]+ (titlesFor SuspenseOrClearingAccount)+ assertEqual "trial balance: closing-device registry role is pinned"+ [IncomeSummary] (titlesFor ClosingDevice)++testReportingPresentation :: IO ()+testReportingPresentation = do+ let reportingBalance = EC.journalFromSides+ [ (Debit, Cash, 100 :: MoneyDecimal)+ , (Debit, LoansReceivable, 30)+ , (Debit, Purchases, 40)+ , (Debit, BranchCurrentAccount, 10)+ , (Debit, IncomeTaxesRefundReceivable, 5)+ , (Credit, CapitalStock, 90)+ , (Credit, Sales, 70)+ , (Credit, AdvancesReceived, 15)+ , (Credit, HeadOfficeCurrentAccount, 10)+ ] :: CheckedAlgM+ rationale = T.pack "material under the documented tax review"+ context scope = (RP.jcciSecondGradeContext scope)+ { RP._maturitySensitiveTitles = Set.singleton LoansReceivable+ , RP._presentationAllocations =+ [RP.PresentationAllocation LoansReceivable 10 20+ (T.pack "contract maturity schedule")]+ , RP._presentationRelabels =+ [RP.PresentationRelabel Purchases SalesCost+ (T.pack "JCCI report cost-of-sales label")]+ , RP._materialityDecisions =+ [RP.MaterialityDecision IncomeTaxesRefundReceivable+ RP.PresentSeparately rationale]+ , RP._subtotalDefinitions =+ [ RP.SubtotalDefinition RM.GrossProfitMetric [Sales] [SalesCost]+ RP.RequireAllTitlesPresent+ , RP.SubtotalDefinition RM.OrdinaryProfitMetric [Sales] [SalesCost]+ RP.RequireAllTitlesPresent+ ]+ }+ standalone = rightStatements (RP.present (context RP.Standalone) TB.BeforeClosing reportingBalance)+ combined = rightStatements (RP.present (context RP.Combined) TB.BeforeClosing reportingBalance)+ standaloneTitles = L.map RP._lineAccount (RP._statementLines standalone)+ combinedTitles = L.map RP._lineAccount (RP._statementLines combined)+ assertEqual "reporting: same validated TB changes with scope"+ True (RP._statementLines standalone /= RP._statementLines combined)+ assertEqual "reporting: standalone retains reciprocal lines"+ True (BranchCurrentAccount `elem` standaloneTitles+ && HeadOfficeCurrentAccount `elem` standaloneTitles)+ assertEqual "reporting: combined eliminates reciprocal lines"+ True (BranchCurrentAccount `notElem` combinedTitles+ && HeadOfficeCurrentAccount `notElem` combinedTitles+ && any isElimination (RP._presentationAudit combined))+ assertEqual "reporting: maturity evidence splits one title"+ [ (RP.CurrentAssetsSection, 10)+ , (RP.NoncurrentAssetsSection, 20)+ ]+ [ (RP._lineSection line, RP._lineAmount line)+ | line <- RP._statementLines standalone+ , RP._lineAccount line == LoansReceivable+ ]+ assertEqual "reporting: JCCI profile uses contract-liability label"+ [T.pack "契約負債"]+ [ RP._lineLabel line+ | line <- RP._statementLines standalone+ , RP._lineAccount line == AdvancesReceived+ ]+ assertEqual "reporting: Purchases is relabeled to SalesCost"+ True (Purchases `notElem` standaloneTitles && SalesCost `elem` standaloneTitles)+ assertEqual "reporting: GrossProfit is a subtotal, not a basis line"+ ( [ RP.StatementSubtotal RM.PeriodResultMetric+ (T.pack "当期純利益") (TB.CreditBalance 30)+ , RP.StatementSubtotal RM.GrossProfitMetric+ (T.pack "売上総利益") (TB.CreditBalance 30)+ , RP.StatementSubtotal RM.OrdinaryProfitMetric+ (T.pack "経常利益") (TB.CreditBalance 30)+ ]+ , False+ )+ ( RP._statementSubtotals standalone+ , GrossProfit `elem` standaloneTitles+ || OrdinaryProfit `elem` standaloneTitles+ )+ assertEqual "reporting: materiality rationale survives in audit"+ True (RP.MaterialityApplied IncomeTaxesRefundReceivable+ RP.PresentSeparately (TB.DebitBalance 5) rationale+ `elem` RP._presentationAudit standalone)+ assertEqual "reporting: relabel never mutates validated bookkeeping coordinates"+ True (Purchases `elem` basesAccountTitles reportingBalance+ && SalesCost `notElem`+ basesAccountTitles reportingBalance)++ let missingAllocation = (context RP.Standalone)+ { RP._presentationAllocations = [] }+ assertEqual "reporting: missing maturity evidence blocks presentation"+ True (case RP.present missingAllocation TB.BeforeClosing reportingBalance of+ Left issues -> RP.MissingPresentationAllocation LoansReceivable+ `elem` NE.toList issues+ Right _ -> False)++ let explainedSuspense = EC.journalFromSides+ [ (Debit, SuspensePayments, 10 :: MoneyDecimal)+ , (Credit, CapitalStock, 10)+ ] :: CheckedAlgM+ explainedInput = (trialBalanceInput explainedSuspense TB.BeforeClosing)+ { TB._temporaryBalanceExplanations = M.singleton SuspensePayments+ (T.pack "pending invoice") }+ assertEqual "reporting: explained temporary finding is separate from presentation"+ [TB.ExplainedTemporaryBalance SuspensePayments (TB.DebitBalance 10)+ (T.pack "pending invoice")]+ (TB.trialBalanceFindings explainedInput)+ assertEqual "reporting: combined context presents an explained temporary balance"+ True (case RP.present (RP.jcciSecondGradeContext RP.Combined)+ TB.BeforeClosing explainedSuspense of+ Left _ -> False+ Right _ -> True)++ let contraBalance = EC.journalFromSides+ [ (Debit, AccountsReceivable, 100 :: MoneyDecimal)+ , (Credit, AllowanceForDoubtfulAccounts, 10)+ , (Credit, CapitalStock, 90)+ ] :: CheckedAlgM+ separateContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._contraPresentationRules =+ [RP.PresentContraSeparately AllowanceForDoubtfulAccounts+ (T.pack "show allowance as deduction")] }+ netContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._contraPresentationRules =+ [RP.NetContraAgainst AllowanceForDoubtfulAccounts+ AccountsReceivable (T.pack "net receivables policy")] }+ separateLines = RP._statementLines+ (rightStatements (RP.present separateContext TB.BeforeClosing contraBalance))+ netLines = RP._statementLines+ (rightStatements (RP.present netContext TB.BeforeClosing contraBalance))+ assertEqual "reporting: contra policy supports separate presentation"+ True (any (\line -> RP._lineAccount line == AllowanceForDoubtfulAccounts+ && RP._lineIsDeduction line) separateLines)+ assertEqual "reporting: contra policy supports net presentation"+ [(Debit, 90)]+ [ (RP._lineSide line, RP._lineAmount line)+ | line <- netLines, RP._lineAccount line == AccountsReceivable+ ]++ let taxBalance = EC.journalFromSides+ [ (Debit, Cash, 85 :: MoneyDecimal)+ , (Debit, CorporateIncomeTaxes, 20)+ , (Credit, CapitalStock, 100)+ , (Credit, RefundOfIncomeTaxes, 5)+ ] :: CheckedAlgM+ netTaxContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._materialityDecisions =+ [RP.MaterialityDecision RefundOfIncomeTaxes+ (RP.NetAgainst CorporateIncomeTaxes)+ (T.pack "immaterial refund netted under tax policy")] }+ taxLines = RP._statementLines+ (rightStatements (RP.present netTaxContext TB.BeforeClosing taxBalance))+ assertEqual "reporting: materiality policy supports tax netting"+ (False, [(Debit, 15)])+ ( RefundOfIncomeTaxes `elem` L.map RP._lineAccount taxLines+ , [ (RP._lineSide line, RP._lineAmount line)+ | line <- taxLines, RP._lineAccount line == CorporateIncomeTaxes+ ]+ )+ let badAllocation current noncurrent evidence = (context RP.Standalone)+ { RP._presentationAllocations =+ [RP.PresentationAllocation LoansReceivable current noncurrent evidence] }+ assertEqual "reporting: blank allocation evidence blocks"+ True (hasPresentationIssue isBlankEvidence (RP.present+ (badAllocation 10 20 (T.pack " ")) TB.BeforeClosing reportingBalance))+ assertEqual "reporting: negative allocation blocks"+ True (hasPresentationIssue isInvalidAllocation (RP.present+ (badAllocation (-1) 31 (T.pack "schedule")) TB.BeforeClosing reportingBalance))+ assertEqual "reporting: non-summing allocation blocks"+ True (hasPresentationIssue isInvalidAllocation (RP.present+ (badAllocation 10 19 (T.pack "schedule")) TB.BeforeClosing reportingBalance))+ let duplicateAllocation = (context RP.Standalone)+ { RP._presentationAllocations =+ [ RP.PresentationAllocation LoansReceivable 10 20+ (T.pack "schedule A")+ , RP.PresentationAllocation LoansReceivable 10 20+ (T.pack "schedule B")+ ] }+ assertEqual "reporting: duplicate allocation blocks"+ True (hasPresentationIssue isDuplicateAllocation+ (RP.present duplicateAllocation TB.BeforeClosing reportingBalance))+ let unexpectedAllocation = (context RP.Standalone)+ { RP._presentationAllocations =+ [ RP.PresentationAllocation LoansReceivable 10 20+ (T.pack "contract maturity schedule")+ , RP.PresentationAllocation Cash 100 0 (T.pack "none")+ ] }+ assertEqual "reporting: unrequired allocation blocks"+ True (hasPresentationIssue isUnexpectedAllocation+ (RP.present unexpectedAllocation TB.BeforeClosing reportingBalance))++ let netAllocatedContext = netContext+ { RP._maturitySensitiveTitles = Set.singleton AccountsReceivable+ , RP._presentationAllocations =+ [RP.PresentationAllocation AccountsReceivable 60 30+ (T.pack "receivable maturity schedule")] }+ assertEqual "reporting: required net target can be allocated after netting"+ [(RP.CurrentAssetsSection, 60), (RP.NoncurrentAssetsSection, 30)]+ [ (RP._lineSection line, RP._lineAmount line)+ | line <- RP._statementLines+ (rightStatements (RP.present netAllocatedContext TB.BeforeClosing contraBalance))+ , RP._lineAccount line == AccountsReceivable+ ]+ let consumeRequired = (context RP.Standalone)+ { RP._materialityDecisions =+ [RP.MaterialityDecision LoansReceivable (RP.NetAgainst Cash)+ (T.pack "must not erase maturity obligation")] }+ assertEqual "reporting: explicit maturity obligation cannot be consumed"+ True (hasPresentationIssue isConflictingInstruction+ (RP.present consumeRequired TB.BeforeClosing reportingBalance))++ let emptyStatements = rightStatements (RP.present+ (RP.jcciSecondGradeContext RP.Combined) TB.BeforeClosing (mempty :: CheckedAlgM))+ assertEqual "reporting: empty combined TB has no fabricated elimination"+ ([], [RP.SubtotalCalculated RM.PeriodResultMetric TB.NoBalance])+ (RP._statementLines emptyStatements, RP._presentationAudit emptyStatements)+ where+ rightStatements (Right statements) = statements+ rightStatements (Left issues) = error ("presentation failed: " ++ show issues)+ isElimination (RP.ReciprocalAccountsEliminated _ _) = True+ isElimination _ = False+ hasPresentationIssue predicate (Left issues) = any predicate (NE.toList issues)+ hasPresentationIssue _ (Right _) = False+ isBlankEvidence (RP.BlankPresentationEvidence LoansReceivable) = True+ isBlankEvidence _ = False+ isInvalidAllocation (RP.InvalidPresentationAllocation LoansReceivable _ _ _) = True+ isInvalidAllocation _ = False+ isDuplicateAllocation (RP.DuplicatePresentationAllocation LoansReceivable) = True+ isDuplicateAllocation _ = False+ isUnexpectedAllocation (RP.UnexpectedPresentationAllocation Cash) = True+ isUnexpectedAllocation _ = False+ isConflictingInstruction+ (RP.ConflictingPresentationInstruction LoansReceivable) = True+ isConflictingInstruction _ = False+ basesAccountTitles alg =+ [ title | _ :< title <- EA.bases alg ]++-- | D1 section 7: integer MoneyDecimal observations avoid row-rounding error.+-- Generators reuse finite non-negative amounts and bound them to whole units.+testStatementLaws :: IO ()+testStatementLaws = do+ let context = RP.jcciSecondGradeContext RP.Standalone+ amount = (\value -> fromInteger (round value `mod` 1000)) <$> genNNDouble+ ledger a b c d = EC.journalFromSides+ [ (Debit, Cash, a), (Credit, Sales, a)+ , (Debit, Cash, b), (Credit, Sales, b)+ , (Debit, Sales, c), (Credit, Cash, c)+ , (Debit, RentExpense, d), (Credit, Cash, d)+ ] :: CheckedAlgM+ signed line = case RP._lineSide line of+ Debit -> RP._lineAmount line+ Credit -> negate (RP._lineAmount line)+ Side -> 0+ resultBalance result = case result of+ RM.PeriodProfit value -> TB.CreditBalance value+ RM.PeriodLoss value -> TB.DebitBalance value+ RM.PeriodBreakEven -> TB.NoBalance+ quickProp "D1 law 1: one net nominal row per nonzero title" $+ forAll ((,,,) <$> amount <*> amount <*> amount <*> amount) $ \(a, b, c, d) ->+ case RP.present context TB.BeforeClosing (ledger a b c d) of+ Left _ -> False+ Right statements ->+ let nominal = filter (\line -> RP._lineSection line `elem`+ [RP.RevenueSection, RP.ExpenseSection]) (RP._statementLines statements)+ sales = [line | line <- nominal, RP._lineAccount line == Sales]+ rent = [line | line <- nominal, RP._lineAccount line == RentExpense]+ in sum (L.map signed sales) == c - a - b+ && length sales == length [() | a + b /= c]+ && sum (L.map signed rent) == d+ && length rent == length [() | d /= 0]+ && all ((>= 0) . RP._lineAmount) nominal+ quickProp "D1 laws 2 and 3: exact net and period result survive instructions" $+ forAll ((,,,) <$> amount <*> amount <*> amount <*> amount) $ \(a, b, c, d) ->+ let alg = ledger a b c d+ relabeled = context+ { RP._presentationRelabels =+ [RP.PresentationRelabel Cash AccountsReceivable (T.pack "scope")+ | a + b /= c + d] }+ allocated = context+ { RP._maturitySensitiveTitles = Set.singleton Cash+ , RP._presentationAllocations =+ [RP.PresentationAllocation Cash (abs (a + b - c - d)) 0+ (T.pack "within one year") | a + b /= c + d] }+ netted = context+ { RP._materialityDecisions =+ [RP.MaterialityDecision RentExpense (RP.NetAgainst Sales)+ (T.pack "nominal netting") | d /= 0 && a + b /= c] }+ in case RM.periodResultOf TB.BeforeClosing alg of+ Left _ -> False+ Right result -> all (\settings -> case RP.present settings TB.BeforeClosing alg of+ Left _ -> False+ Right statements ->+ sum (L.map signed (RP._statementLines statements)) == 0+ && [RP._statementSubtotalBalance subtotal+ | subtotal <- RP._statementSubtotals statements+ , RP._statementSubtotalMetric subtotal == RM.PeriodResultMetric]+ == [resultBalance result]) [context, relabeled, allocated, netted]+ let deficit = EC.journalFromSides+ [(Debit, RetainedEarnings, 20), (Credit, CapitalStock, 20)] :: CheckedAlgM+ suspense = EC.journalFromSides+ [(Debit, SuspensePayments, 10), (Credit, CapitalStock, 10)] :: CheckedAlgM+ unclosed = ledger 30 20 10 15+ forM_ [(deficit, TB.BeforeClosing), (suspense, TB.BeforeClosing),+ (unclosed, TB.AfterClosing)] $ \(alg, stage) -> do+ assertEqual "D1 law 4: accounting finding remains observable" True+ (not (null (TB.trialBalanceFindings (trialBalanceInput alg stage))))+ assertEqual "D1 law 4: accounting finding does not block presentation" True+ (case RP.present context stage alg of Right _ -> True; Left _ -> False)+ assertEqual "D1 law 5: deficit keeps equity section and actual debit side"+ (Right [(RP.EquitySection, Debit, 20)])+ (fmap (\statements -> [(RP._lineSection line, RP._lineSide line, RP._lineAmount line)+ | line <- RP._statementLines statements, RP._lineAccount line == RetainedEarnings])+ (RP.present context TB.BeforeClosing deficit))+ let concealed = EC.journalFromSides+ [(Debit, Cash, 100), (Credit, CapitalStock, 100),+ (Debit, BranchCurrentAccount, 10)] :: CheckedAlgM+ assertEqual "D1 counterexample 1: elimination cannot hide input imbalance"+ (Left (RP.UnbalancedStatementInput 110 100 NE.:| []))+ (fmap (const ()) (RP.present (RP.jcciSecondGradeContext RP.Combined)+ TB.BeforeClosing concealed))+ forM_ [(HatNot :< Sales, RM.WildcardMetricSide Sales),+ (Not :< AccountTitle, RM.WildcardMetricAccount)] $ \(base, expected) -> do+ let alg = 10 `Internal.Single` base :: CheckedAlgM+ assertEqual "D1 counterexample 2: metric rejects wildcard before accumulation"+ (Left expected) (RM.periodResultOf TB.BeforeClosing alg)+ assertEqual "statement rejects wildcard before accumulation"+ (Left (RP.WildcardStatementBase NE.:| []))+ (fmap (const ()) (RP.present context TB.BeforeClosing alg))+ let nominal = ledger 100 0 0 0+ salesAllocation = context+ { RP._maturitySensitiveTitles = Set.singleton Sales+ , RP._presentationAllocations =+ [RP.PresentationAllocation Sales 100 0 (T.pack "not an asset")] }+ assertEqual "D1 counterexample 3: maturity cannot move Sales into balance sheet" True+ (case RP.present salesAllocation TB.BeforeClosing nominal of+ Left issues -> RP.ConflictingPresentationInstruction Sales `elem` NE.toList issues+ Right _ -> False)+ let receivable = EC.journalFromSides+ [(Debit, AccountsReceivable, 30), (Credit, CapitalStock, 30)] :: CheckedAlgM+ allocation = RP.PresentationAllocation AccountsReceivable 10 20 (T.pack "schedule")+ migrated = context+ { RP._maturitySensitiveTitles = Set.singleton AccountsReceivable+ , RP._presentationAllocations = [allocation] }+ expectedLines = Right [(RP.CurrentAssetsSection, Debit, 10),+ (RP.NoncurrentAssetsSection, Debit, 20)]+ assertEqual "D1 counterexample 5: explicit maturity requirement preserves allocation"+ expectedLines (fmap (\statements ->+ [(RP._lineSection line, RP._lineSide line, RP._lineAmount line)+ | line <- RP._statementLines statements,+ RP._lineAccount line == AccountsReceivable])+ (RP.present migrated TB.BeforeClosing receivable))+ forM_ [context { RP._presentationRelabels =+ [RP.PresentationRelabel Sales CapitalStock (T.pack "cross division")] },+ context { RP._materialityDecisions =+ [RP.MaterialityDecision Sales (RP.NetAgainst Cash) (T.pack "cross division")] },+ context { RP._contraPresentationRules =+ [RP.NetContraAgainst Sales Cash (T.pack "cross division")] }] $ \settings ->+ assertEqual "period result: cross statement instructions are rejected" True+ (case RP.present settings TB.BeforeClosing nominal of+ Left issues -> RP.ConflictingPresentationInstruction Sales `elem` NE.toList issues+ Right _ -> False)+ assertEqual "period result: caller cannot redefine the period result" True+ (case RP.present context { RP._subtotalDefinitions =+ [RP.SubtotalDefinition RM.PeriodResultMetric [Sales] [] RP.TreatAbsentAsZero] }+ TB.BeforeClosing nominal of+ Left issues -> RP.InvalidSubtotalDefinition RM.PeriodResultMetric `elem` NE.toList issues+ Right _ -> False)+ let large = 9007199254740992 :: Double+ exactNominal = EC.journalFromSides+ [(Credit, Sales, large), (Credit, Sales, 1),+ (Debit, SalesCost, large), (Debit, Cash, 1)]+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "period result: subtotal retains exact one despite rounded nominal rows"+ (Right [TB.CreditBalance 1])+ (fmap (L.map RP._statementSubtotalBalance . RP._statementSubtotals)+ (RP.present (RP.jcciSecondGradeContext RP.Standalone) TB.BeforeClosing exactNominal))+ forM_ [(0 / 0, Cash), (-1, Sales), (1 / 0, CapitalStock)] $ \(value, title) -> do+ let alg = value `Internal.Single` (Not :< title)+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "statement structure: invalid posting is typed"+ (Left (RP.InvalidStatementValue title NE.:| []))+ (fmap (const ()) (RP.present context TB.BeforeClosing alg))+ assertEqual "metric structure: invalid posting is typed"+ (Left (RM.InvalidMetricValue title)) (RM.periodResultOf TB.BeforeClosing alg)+ closed <- either (fail . show) pure (RP.present context TB.AfterClosing deficit)+ assertEqual "statement rows: income section requires period result"+ (Left (StatementOutput.PeriodResultMissing TB.AfterClosing))+ (StatementOutput.financialStatementRows [RP.RevenueSection] closed)+ assertEqual "statement writer: failed rows do not open the path"+ (Left (StatementOutput.PeriodResultMissing TB.AfterClosing))+ =<< StatementOutput.writeFinancialStatements+ "/nonexistent/ea-w4-must-not-be-opened.csv" [RP.ExpenseSection] closed++testDerivedMetricsAndLegacyCoordinates :: IO ()+testDerivedMetricsAndLegacyCoordinates = do+ assertEqual "legacy derived-coordinate ordinals and Binary bytes"+ [ (NetIncome, 49, T.pack "8031")+ , (GrossProfit, 54, T.pack "8036")+ , (OrdinaryProfit, 55, T.pack "8037")+ , (NetLoss, 64, T.pack "8040")+ , (IncomeSummary, 216, T.pack "80d8")+ ]+ [ (title, fromEnum title, accountSemanticsBinaryHex title)+ | title <- [NetIncome, GrossProfit, OrdinaryProfit, NetLoss, IncomeSummary]+ ]+ assertEqual "exactly four legacy coordinates map to typed metrics"+ [ (NetIncome, RM.PeriodResultMetric)+ , (GrossProfit, RM.GrossProfitMetric)+ , (OrdinaryProfit, RM.OrdinaryProfitMetric)+ , (NetLoss, RM.PeriodResultMetric)+ ]+ [ (title, metric)+ | title <- Registry.concreteAccountTitles+ , Just metric <- [RM.metricForLegacyTitle title]+ ]++ let salesOnly = 100 .@ Not :< Sales :: CheckedAlgM+ afterLegacyBalancer = salesOnly .+ (100 .@ Not :< NetIncome)+ hatSales = 25 .@ Hat :< Sales :: CheckedAlgM+ assertEqual "period metric ignores an inserted legacy balancer"+ (Right (RM.PeriodProfit 100), Right (RM.PeriodProfit 100))+ ( RM.periodResultOfAlg salesOnly+ , RM.periodResultOfAlg afterLegacyBalancer+ )+ assertEqual "Hat is interpreted through account side, not as a scalar sign"+ (Right (RM.PeriodLoss 25)) (RM.periodResultOfAlg hatSales)+ assertEqual "empty nominal basis is break-even"+ (Right RM.PeriodBreakEven :: Either RM.MetricError (RM.PeriodResult MoneyDecimal))+ (RM.periodResultOfAlg (mempty :: CheckedAlgM))+ assertEqual "raw metric boundary rejects wildcard sides"+ (Left (RM.WildcardMetricSide Sales))+ (RM.periodResultOfAlg (10 `Internal.Single` HatNot :< Sales :: CheckedAlgM))++ let ordinaryLedger = EC.journalFromSides+ [ (Debit, Cash, 100 :: MoneyDecimal)+ , (Credit, Sales, 100)+ ] :: CheckedAlgM+ assertEqual "validated before-closing TB derives one period-result identity"+ (Right (RM.PeriodProfit 100))+ (RM.periodResultOf TB.BeforeClosing ordinaryLedger)++ let legacyLedger = EC.journalFromSides+ [ (Debit, NetIncome, 10 :: MoneyDecimal)+ , (Credit, CapitalStock, 10)+ ] :: CheckedAlgM+ assertEqual "typed metric rejects a residual legacy coordinate"+ (Left (RM.ResidualDerivedCoordinate NetIncome))+ (RM.periodResultOf TB.BeforeClosing legacyLedger)+ assertEqual "legacy intermediate and presentation paths are explicit alternatives"+ True (case RP.present+ (RP.jcciSecondGradeContext RP.Standalone) TB.BeforeClosing legacyLedger of+ Left issues -> RP.UnpresentableBalance NetIncome+ (TB.DebitBalance 10) `elem` NE.toList issues+ Right _ -> False)++ let subtotal = RP.SubtotalDefinition RM.GrossProfitMetric+ [Sales] [SalesCost] RP.TreatAbsentAsZero+ duplicateContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._subtotalDefinitions = [subtotal, subtotal] }+ assertEqual "duplicate metric identity blocks presentation"+ True (case RP.present duplicateContext TB.BeforeClosing (mempty :: CheckedAlgM) of+ Left issues -> RP.DuplicateMetricIdentity RM.GrossProfitMetric+ `elem` NE.toList issues+ Right _ -> False)+ let absentAsZeroContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._subtotalDefinitions = [subtotal] }+ assertEqual "canonical subtotal may treat unposted titles as zero"+ [ RP.StatementSubtotal RM.PeriodResultMetric (T.pack "当期純損益") TB.NoBalance+ , RP.StatementSubtotal RM.GrossProfitMetric (T.pack "売上総損益") TB.NoBalance ]+ (RP._statementSubtotals (case RP.present absentAsZeroContext TB.BeforeClosing (mempty :: CheckedAlgM) of+ Right statements -> statements+ Left issues -> error ("absent-as-zero subtotal rejected: " ++ show issues)))++ let relabelledLedger = EC.journalFromSides+ [ (Debit, Purchases, 30 :: MoneyDecimal)+ , (Credit, Sales, 30)+ ] :: CheckedAlgM+ removedTitleContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._presentationRelabels =+ [RP.PresentationRelabel Purchases SalesCost (T.pack "policy")]+ , RP._subtotalDefinitions =+ [RP.SubtotalDefinition RM.GrossProfitMetric+ [Sales] [Purchases] RP.TreatAbsentAsZero]+ }+ assertEqual "absent-as-zero does not hide a relabelled non-zero title"+ True (case RP.present removedTitleContext TB.BeforeClosing relabelledLedger of+ Left issues -> RP.InvalidSubtotalDefinition RM.GrossProfitMetric+ `elem` NE.toList issues+ Right _ -> False)++ let Just customId = RM.mkMetricId (T.pack "ebitda-adjusted")+ customDefinition = RP.SubtotalDefinition (RM.CustomMetric customId)+ [Sales] [] RP.TreatAbsentAsZero+ unlabelledContext = (RP.jcciSecondGradeContext RP.Standalone)+ { RP._subtotalDefinitions = [customDefinition] }+ assertEqual "custom metric identity requires separate labels"+ True (case RP.present unlabelledContext TB.BeforeClosing (mempty :: CheckedAlgM) of+ Left issues -> RP.UnlabelledCustomMetric customId+ `elem` NE.toList issues+ Right _ -> False)+ let labelledContext = unlabelledContext+ { RP._presentationProfile = RP.CanonicalEnglish+ , RP._customMetricLabels =+ [RP.CustomMetricLabel customId+ (T.pack "調整後EBITDA") (T.pack "Adjusted EBITDA")]+ }+ assertEqual "custom metric identity and profile label are separate"+ [ RP.StatementSubtotal RM.PeriodResultMetric (T.pack "Net result") TB.NoBalance+ , RP.StatementSubtotal (RM.CustomMetric customId)+ (T.pack "Adjusted EBITDA") TB.NoBalance ]+ (RP._statementSubtotals (case RP.present labelledContext TB.BeforeClosing (mempty :: CheckedAlgM) of+ Right statements -> statements+ Left issues -> error ("labeled custom metric rejected: " ++ show issues)))+ let duplicateLabelContext = labelledContext+ { RP._customMetricLabels = RP._customMetricLabels labelledContext+ ++ RP._customMetricLabels labelledContext+ }+ assertEqual "standalone label lookup rejects duplicate identities"+ Nothing+ (RP.metricLabel duplicateLabelContext (RM.CustomMetric customId)+ TB.NoBalance)+-- ================================================================+-- Bookkeeping closing-adjustment builders (Phase B)+-- ================================================================++type BAlg = EA.Alg Double (HatBase AccountTitles)+type BAlgM = EA.Alg MoneyDecimal (HatBase AccountTitles)++mkA :: EB.MkBase (HatBase AccountTitles)+mkA = (:<)++-- balanced-ness: debit-side norm equals credit-side norm (貸借一致)+isBalancedD :: BAlg -> Bool+isBalancedD x = epsEq (norm (Exchange.decL x)) (norm (Exchange.decR x))++bookkeepingProperties :: IO ()+bookkeepingProperties = do+ -- (1) balanced property: every builder produces a debit=credit entry+ let balancedCases :: [(String, Property)]+ balancedCases =+ [ ("bookkeeping: cogsAdjustmentEntries balanced"+ , forAll genNNDouble $ \beg -> forAll genNNDouble $ \end ->+ isBalancedD (EB.cogsAdjustmentEntries mkA beg end)+ )+ , ("bookkeeping: depreciationIndirectEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.depreciationIndirectEntry mkA amt)+ )+ , ("bookkeeping: depreciationDirectEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.depreciationDirectEntry mkA amt Fixtures)+ )+ , ("bookkeeping: allowanceReplenishmentEntry balanced"+ , forAll genNNDouble $ \est -> forAll genNNDouble $ \cur ->+ isBalancedD (EB.allowanceReplenishmentEntry mkA est cur)+ )+ , ("bookkeeping: allowanceResetEntries balanced"+ , forAll genNNDouble $ \est -> forAll genNNDouble $ \cur ->+ isBalancedD (EB.allowanceResetEntries mkA est cur)+ )+ , ("bookkeeping: prepaidExpenseEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.prepaidExpenseEntry mkA amt RentExpense)+ )+ , ("bookkeeping: unearnedRevenueEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.unearnedRevenueEntry mkA amt RentalIncome)+ )+ , ("bookkeeping: accruedRevenueEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.accruedRevenueEntry mkA amt InterestEarned)+ )+ , ("bookkeeping: accruedExpenseEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.accruedExpenseEntry mkA amt InterestExpense)+ )+ , ("bookkeeping: corporateTaxInterimEntry balanced"+ , forAll genNNDouble $ \amt -> isBalancedD (EB.corporateTaxInterimEntry mkA amt)+ )+ , ("bookkeeping: consumptionTaxSettlementEntry balanced (received>=paid)"+ , forAll genNNDouble $ \paid -> forAll genNNDouble $ \extra ->+ isBalancedD (EB.consumptionTaxSettlementEntry mkA paid (paid + extra))+ )+ , ("bookkeeping: corporateTaxSettlementEntries balanced (total>=interim)"+ , forAll genNNDouble $ \interim -> forAll genNNDouble $ \extra ->+ isBalancedD (EB.corporateTaxSettlementEntries mkA (interim + extra) interim)+ )+ , ("bookkeeping: priorPeriodErrorCorrection balanced"+ , forAll genNNDouble $ \curr -> forAll genNNDouble $ \prior ->+ isBalancedD (EB.priorPeriodErrorCorrection mkA curr prior Depreciation Land)+ )+ ]+ quickProp "bookkeeping: every adjustment builder is balanced" $+ conjoin+ [ counterexample label checkedProperty+ | (label, checkedProperty) <- balancedCases+ ]++ -- (2) unit tests: expected bases/amounts on representative lecture figures+ -- COGS (ch.24): beg 100,000 / end 50,000. In isolation this entry's+ -- Purchases net = beg - end = +50,000 (so the Purchases balance becomes COGS),+ -- and MerchandiseInventory net = end - beg = -50,000 (it replaces the opening+ -- balance with the closing one, i.e. 100,000 - 50,000 leaves on the ledger).+ let cogs = EB.cogsAdjustmentEntries mkA 100000 50000 :: BAlg+ assertNear "cogs: Purchases net = beg - end (= cost of goods sold)"+ 50000 (signedNet Purchases cogs)+ assertNear "cogs: MerchandiseInventory net = end - beg"+ (-50000) (signedNet MerchandiseInventory cogs)+ -- 差額補充法, estimate>current (ch.16): 1,400 - 1,000 -> provide 400+ let repl1 = EB.allowanceReplenishmentEntry mkA 1400 1000 :: BAlg+ assertNear "allowance(差額補充, shortfall): ProvisionForDoubtfulAccounts = 400"+ 400 (norm (Exchange.projByAccountTitle ProvisionForDoubtfulAccounts repl1))+ assertNear "allowance(差額補充, shortfall): AllowanceForDoubtfulAccounts = 400"+ 400 (norm (Exchange.projByAccountTitle AllowanceForDoubtfulAccounts repl1))+ -- 差額補充法, estimate<current (ch.16): 1,800 - 2,000 -> release 200+ let repl2 = EB.allowanceReplenishmentEntry mkA 1800 2000 :: BAlg+ assertNear "allowance(差額補充, excess): ReversalOfAllowanceForDoubtfulAccounts = 200"+ 200 (norm (Exchange.projByAccountTitle ReversalOfAllowanceForDoubtfulAccounts repl2))+ -- estimate==current -> no entry+ assertEqual "allowance(差額補充, equal): Zero"+ True (EA.isZero (EB.allowanceReplenishmentEntry mkA 1500 1500 :: BAlg))+ -- consumption tax (ch.23): paid 1,000 / received 20,000 -> unpaid 19,000+ let ctax = EB.consumptionTaxSettlementEntry mkA 1000 20000 :: BAlg+ assertNear "consumptionTax: AccruedConsumptionTax = received - paid = 19000"+ 19000 (norm (Exchange.projByAccountTitle AccruedConsumptionTax ctax))+ -- corporate tax (ch.23): total 800,000 / interim 500,000 -> unpaid 300,000+ let crp = EB.corporateTaxSettlementEntries mkA 800000 500000 :: BAlg+ assertNear "corporateTax: AccruedCorporateIncomeTaxes = total - interim = 300000"+ 300000 (norm (Exchange.projByAccountTitle AccruedCorporateIncomeTaxes crp))+ -- prior-period error correction (#15 anchor): patent 55,000/10yr discovered 2028+ -- current 5,500 / prior 2yr 11,000 -> debit=credit=16,500 (Patent credit)+ let ppec = EB.priorPeriodErrorCorrection mkA 5500 11000 AmortizationExpense Patent :: BAlg+ assertNear "priorPeriodErrorCorrection (#15): Patent credit = 16500"+ 16500 (norm (Exchange.projByAccountTitle Patent ppec))+ assertNear "priorPeriodErrorCorrection (#15): balanced (decL == decR)"+ (norm (Exchange.decL ppec)) (norm (Exchange.decR ppec))+ -- consumption-tax refund (received<paid) is rejected (out of 3-級 scope)+ rRefund <- try (evaluate (norm (EB.consumptionTaxSettlementEntry mkA 5000 1000 :: BAlg)))+ :: IO (Either SomeException Double)+ case rRefund of+ Left _ -> putStrLn "[PASS] consumptionTaxSettlementEntry rejects received<paid"+ Right _ -> do putStrLn "[FAIL] consumptionTaxSettlementEntry accepted refund"; exitFailure++ -- (3) reversingEntry: involution + exact cancellation (MoneyDecimal exact)+ quickProp "bookkeeping: reversingEntry is involution (MoneyDecimal)" $+ forAll genBAlgM $ \x -> EB.reversingEntry (EB.reversingEntry x) == x+ quickProp "bookkeeping: bar (x .+ reversingEntry x) == Zero (MoneyDecimal)" $+ forAll genBAlgM $ \x -> bar (x .+ EB.reversingEntry x) == EA.Zero+ where+ -- exact per-account signed net (Not +, Hat -) for Double-based unit checks+ signedNet :: AccountTitles -> BAlg -> Double+ signedNet t = EA.foldEntries step 0+ where step acc v b+ | getAccountTitle b == t = if isHat b then acc - v else acc + v+ | otherwise = acc++-- small exact MoneyDecimal algebra over AccountTitles bases (closing-adjustment+-- shaped: a few postings on bookkeeping titles), for the reversal properties.+genBAlgM :: Gen BAlgM+genBAlgM = sized $ \n -> do+ k <- choose (0, min 8 n)+ ps <- vectorOf k ((,) <$> genSmallMoney <*> genBookBase)+ pure (EA.fromList [ v .@ b | (v, b) <- ps ])+ where+ genSmallMoney :: Gen MoneyDecimal+ genSmallMoney = fromInteger <$> choose (1, 9999)+ genBookBase :: Gen (HatBase AccountTitles)+ genBookBase = (:<) <$> elements [Hat, Not]+ <*> elements [ Purchases, MerchandiseInventory, Depreciation+ , AccumulatedDepreciation, PrepaidExpenses+ , AccruedExpenses, AccruedConsumptionTax+ , Cash, InterestExpense ]++-- ================================================================+-- Closing-document Write functions (Phase D): worksheet,+-- post-closing trial balance, account ledger+-- ================================================================++closingDocsTests :: IO ()+closingDocsTests = do+ -- A small balanced pre-adjustment ledger (ebex1-shaped):+ -- opening capital 2,000,000; a cash sale of 500,000;+ -- wages (cost) 140,000 paid in cash.+ -- Pre-adjustment trial balance balances by construction.+ let pre = (2000000 .@ (Not :< Cash)) -- 現金 (asset, debit)+ .+ (2000000 .@ (Not :< CapitalStock)) -- 資本金 (equity, credit)+ .+ (500000 .@ (Not :< Cash)) -- cash from sale (debit)+ .+ (500000 .@ (Not :< Sales)) -- 売上 (revenue, credit)+ .+ (140000 .@ (Hat :< Cash)) -- cash paid out (credit)+ .+ (140000 .@ (Not :< WageExpenditure)) -- 給料 (cost, debit)+ :: BAlg+ -- one adjustment: accrue 10,000 of unpaid wages (費用の見越し)+ let adj = (10000 .@ (Not :< WageExpenditure)) -- cost debit+ .+ (10000 .@ (Not :< AccruedExpenses)) -- liability credit+ :: BAlg+ let combined = pre .+ adj++ -- (1) Worksheet self-check: the P/L column imbalance must equal the+ -- B/S column imbalance, and both equal the net income.+ -- Each account's *net* balance (diffRL) is routed by division:+ -- P/L: Sales net 500,000 (credit) vs WageExpenditure net 150,000+ -- (debit) => net income 350,000.+ -- B/S: Cash net 2,360,000 (debit) vs CapitalStock 2,000,000 ++ -- AccruedExpenses 10,000 (credit) = 2,010,000 => 350,000.+ -- We compute the column sums the same way worksheetRows does: per+ -- account title, place the *net* balance into the debit or credit+ -- column according to its balance side.+ let titles = L.nub (EA.foldEntries (\acc _ b -> getAccountTitle b : acc) [] combined) :: [AccountTitles]+ netSide t = Exchange.diffRL (Exchange.projByAccountTitle t combined) :: (Side, Double)+ colSums divs =+ L.foldl' (\(d,c) t ->+ if classifyAccountDivision t `elem` divs+ then case netSide t of+ (Debit, m) -> (d + m, c)+ (Credit, m) -> (d, c + m)+ _ -> (d, c)+ else (d, c)) (0,0) titles+ (plD, plC) = colSums [Cost, Revenue]+ (bsD, bsC) = colSums [Assets, Liability, Equity]+ plDiff = abs (plD - plC)+ bsDiff = abs (bsD - bsC)+ assertNear "worksheet self-check: P/L diff = 350000" 350000 plDiff+ assertNear "worksheet self-check: B/S diff = 350000" 350000 bsDiff+ assertNear "worksheet self-check: P/L diff == B/S diff (= net income)" plDiff bsDiff++ -- the rendered worksheet's net-income row must carry the same figure on+ -- the P/L debit and B/S credit columns (positions 6 and 9, 1-based).+ let wrows = EW.worksheetRows pre adj+ netRow' = last (init wrows) -- penultimate-from-end: the Net row+ assertEqual "worksheet: net row label is Net Income"+ (T.pack "Net Income") (head netRow')+ assertEqual "worksheet: net income on P/L debit column = 350000.0"+ (T.pack "350000.0") (netRow' !! 5)+ assertEqual "worksheet: net income on B/S credit column = 350000.0"+ (T.pack "350000.0") (netRow' !! 8)++ -- (2) Post-closing trial balance must contain only real accounts+ -- (Assets/Liability/Equity) — no Cost/Revenue titles.+ let pcrows = EW.postClosingTrialBalanceRows combined+ titleCells = [ row !! 1 | row <- drop 1 pcrows ] -- middle column = title+ forbidden = L.map (T.pack . show) [Sales, WageExpenditure]+ assertEqual "post-closing TB excludes Cost/Revenue titles"+ True (not (any (`elem` forbidden) titleCells))+ assertEqual "post-closing TB includes Cash"+ True (T.pack (show Cash) `elem` titleCells)+ assertEqual "post-closing TB includes AccruedExpenses (liability)"+ True (T.pack (show AccruedExpenses) `elem` titleCells)++ -- (3) Account ledger preserves the seq: the number of posting lines for a+ -- title equals the number of postings on that title (no aggregation).+ -- Cash has 3 postings (2 debit, 1 credit) in `pre`.+ let lrows = EW.accountLedgerRows [Cash] pre (const dummyDay)+ -- drop the 2 header rows (title + sub-header); the rest are postings.+ bodyLines = drop 2 lrows+ cashPostings = EA.foldEntries (\acc _ b -> if getAccountTitle b == Cash then acc + 1 else acc) (0 :: Int) pre+ -- each body row holds at most one debit + one credit cell; count+ -- non-empty value cells (debit col=1, credit col=3).+ nonEmptyVals = Prelude.length+ [ () | row <- bodyLines, c <- [1,3], not (T.null (row !! c)) ]+ assertEqual "account ledger: Cash posting count preserved (= 3, no aggregation)"+ cashPostings nonEmptyVals+ where+ dummyDay :: Day+ dummyDay = fromGregorian 2024 4 1++-- | Pin both directions of the exact-versus-rounded accounting gates.+testAccumulatorAccounting :: IO ()+testAccumulatorAccounting = do+ let large = 9007199254740992 :: Double+ entry debits credits = EC.journalFromSides+ ([(Debit, Cash, value) | value <- debits]+ ++ [(Credit, CapitalStock, value) | value <- credits])+ :: EA.Alg Double (HatBase AccountTitles)+ accepted = entry [large, 1, 1] [large + 2]+ rejected = entry [large, 1] [large]+ isRight (Right _) = True+ isRight _ = False+ context = RP.jcciSecondGradeContext RP.Standalone+ linkage = CW.WorksheetLinkage+ RM.PeriodBreakEven RM.PeriodBreakEven RM.PeriodBreakEven+ TBB.NoBalance 0 TBB.NoBalance TBB.NoBalance+ TBB.NoBalance RM.PeriodBreakEven 0 TBB.NoBalance TBB.NoBalance+ worksheet alg adjustments = CW.validateConsolidationWorksheet+ (CW.WorksheetInput (CW.TrialBalanceSource "source" alg NE.:| [])+ adjustments linkage :: CW.WorksheetInput String String Double)+ assertEqual "exact oracle: accepted fixture has equal Rational sums"+ (sum (L.map toRational [large, 1, 1])) (toRational (large + 2))+ assertEqual "exact oracle: rejected fixture has residual one"+ 1 (sum (L.map toRational [large, 1]) - toRational large)+ assertEqual "account balances: one rounding of the exact account sum"+ (TBB.DebitBalance (large + 2))+ (TBB.balanceFor Cash (TBB.accountBalances accepted))+ assertEqual "account balances: gross rounding cannot erase net one"+ (TBB.DebitBalance 1)+ (TBB.balanceFor Cash (TBB.accountBalances+ (rejected EA..+ (large EA..@ (Hat :< Cash)))))+ forM_ [(accepted, True), (rejected, False)] $ \(alg, expected) -> do+ assertEqual "trial balance: exact balance findings in both directions" expected+ $ null+ $ TB.trialBalanceFindings+ $ TB.TrialBalanceInput alg TB.BeforeClosing M.empty [] Set.empty+ assertEqual "consolidation source: exact acceptance in both directions"+ expected (isRight (worksheet alg []))+ assertEqual "consolidation adjustment: exact acceptance in both directions"+ expected (isRight (worksheet (entry [2] [2])+ [CW.WorksheetAdjustment "adjustment" ("source" NE.:| []) alg]))+ let reportingInput = EC.journalFromSides+ [(Debit, Cash, large), (Debit, Cash, 1),+ (Debit, AccountsReceivable, 1), (Credit, CapitalStock, large + 2)]+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "trial balance: exact reporting input has no findings" []+ $ TB.trialBalanceFindings+ $ TB.TrialBalanceInput reportingInput TB.BeforeClosing M.empty [] Set.empty+ case RP.present context TB.BeforeClosing reportingInput of+ Left issues -> fail (show issues)+ Right statements -> assertEqual+ "presentation accepts exact balance despite rounded row mismatch"+ [large, 1, large + 2]+ (L.map RP._lineAmount (RP._statementLines statements))+ let relabeled = context+ { RP._presentationRelabels =+ [RP.PresentationRelabel Cash AccountsReceivable (T.pack "combine")]+ , RP._subtotalDefinitions =+ [RP.SubtotalDefinition RM.GrossProfitMetric [] [AccountsReceivable]+ RP.RequireAllTitlesPresent] }+ case RP.present relabeled TB.BeforeClosing reportingInput of+ Left issues -> fail (show issues)+ Right statements -> do+ assertEqual "presentation relabel rounds combined postings once"+ [large + 2, large + 2]+ (L.map RP._lineAmount (RP._statementLines statements))+ assertEqual "presentation subtotal retains exact transformed balance"+ [TBB.NoBalance, TBB.DebitBalance (large + 2)]+ (L.map RP._statementSubtotalBalance (RP._statementSubtotals statements))+ assertEqual "trial balance: exact maturity input has no findings" []+ $ TB.trialBalanceFindings+ $ TB.TrialBalanceInput (entry [large, 1] [large, 1])+ TB.BeforeClosing M.empty [] Set.empty+ forM_ [(1, True), (0, False)] $ \(remainder, expected) -> do+ let allocated = context+ { RP._maturitySensitiveTitles = Set.singleton Cash+ , RP._presentationAllocations =+ [RP.PresentationAllocation Cash large remainder+ (T.pack "exact maturity split")] }+ assertEqual "presentation allocation compares before account rounding"+ expected (isRight (RP.present allocated TB.BeforeClosing (entry [large, 1] [large, 1])))+ let nominal revenues costs = EC.journalFromSides+ ([(Credit, Sales, value) | value <- revenues]+ ++ [(Debit, SalesCost, value) | value <- costs])+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "metric: exact break-even despite sequential rounding"+ (Right RM.PeriodBreakEven)+ (RM.periodResultOfAlg (nominal [large, 1, 1] [large + 2]))+ assertEqual "metric: rounded gross equality cannot erase profit"+ (Right (RM.PeriodProfit 1))+ (RM.periodResultOfAlg (nominal [large, 1] [large]))+ assertEqual "relativeTo consumes a directional net without tolerance"+ (RG.RelativeAmount True 0.5)+ (RG.relativeTo Debit (Credit, 0.5 :: Double))+ let groups = RG.presentGroups RG.defaultPresentationGrouping+ (M.fromList [(AccountsReceivable, (large, 0)),+ (AllowanceForDoubtfulAccounts, (0, 0.5))])+ assertEqual "group activity keeps a real small deduction"+ True (Set.member AllowanceForDoubtfulAccounts (RG.gpConsumed groups))+ let tinyGroups = RG.presentGroups RG.defaultPresentationGrouping+ (M.singleton AllowanceForDoubtfulAccounts (0, 1e-14 :: Double))+ assertEqual "group activity retains a deduction below the old tolerance"+ True (Set.member AllowanceForDoubtfulAccounts (RG.gpConsumed tinyGroups))+ let residualGroups = RG.presentGroups RG.defaultPresentationGrouping+ (M.fromList [(AccountsReceivable, (1, 1 + 2 ** (-52) :: Double)),+ (AllowanceForDoubtfulAccounts, (0, 1))])+ assertEqual "group gross row retains a one-ulp residual"+ True (any ((== RG.GrossRow AccountsReceivable) . RG.grKind)+ (concatMap snd (RG.gpBlocks residualGroups)))+ let omitted = EC.journalFromSides+ [(Debit, Cash, 100), (Credit, CapitalStock, 90), (Credit, CashOverShort, 10)]+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "trial balance: omitted balance retains only its explained finding"+ [TB.ExplainedTemporaryBalance CashOverShort (TB.CreditBalance 10)+ (T.pack "investigation pending")]+ $ TB.trialBalanceFindings+ $ TB.TrialBalanceInput omitted TB.BeforeClosing+ (M.singleton CashOverShort (T.pack "investigation pending")) [] Set.empty+ assertEqual "presentation reconciles only the rendered balances"+ (Left (RP.UnpresentableBalance CashOverShort (TB.CreditBalance 10)+ NE.:| [RP.UnreconciledPresentation 100 90]))+ (fmap (const ()) (RP.present context TB.BeforeClosing omitted))+ let reciprocal branchAmounts headAmounts = EC.journalFromSides+ ([(Debit, BranchCurrentAccount, value) | value <- branchAmounts]+ ++ [(Credit, HeadOfficeCurrentAccount, value) | value <- headAmounts])+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "reciprocal comparison finds no mismatch for exact equal sums" []+ $ TB.trialBalanceFindings+ $ TB.TrialBalanceInput (reciprocal [large, 1, 1] [large + 2])+ TB.BeforeClosing M.empty [] Set.empty+ let mismatch = reciprocal [large, 1] [large]+ EA..+ (1 EA..@ (Not :< CapitalStock))+ assertEqual "reciprocal comparison rejects rounded equal sums"+ True (any isReciprocal (TB.trialBalanceFindings+ (TB.TrialBalanceInput mismatch TB.BeforeClosing M.empty [] Set.empty)))+ let offset = EC.journalFromSides+ [(Debit, Cash, large), (Debit, Cash, 1), (Credit, Cash, large)]+ :: EA.Alg Double (HatBase AccountTitles)+ assertEqual "compound trial balance keeps exact net one"+ [T.pack "1.0", T.pack "9.007199254740992e15", T.pack "Cash",+ T.pack "9.007199254740992e15", T.pack ""]+ (concat (take 1 (drop 1 (EW.compoundTrialBalanceRows offset))))+ assertEqual "post-closing trial balance keeps exact net one"+ [T.pack "1.0", T.pack "Cash", T.pack ""]+ (concat (take 1 (drop 1 (EW.postClosingTrialBalanceRows offset))))+ where+ isReciprocal TB.ReciprocalMismatch{} = True+ isReciprocal _ = False++-- | Keep the default BS groups independent of the closing transfer.+testBalanceSheetGroupsStayUnclosed :: IO ()+testBalanceSheetGroupsStayUnclosed = do+ let grouping = RG.groupingForDivisions [Assets, Liability, Equity]+ RG.defaultPresentationGrouping+ titles = concatMap (\def -> RG.pgGross def ++ RG.pgDeductions def) grouping+ -- NoClose accounts stay outside the public closing classification.+ assertEqual "BS default group accounts have no final-stock closing rule"+ [] [title | title <- titles, closingSide title /= Nothing]++-- | NaN is directionless in relative amounts but retains debit placement in cells.+testWriterNaNDirections :: IO ()+testWriterNaNDirections = do+ -- Invalid postings deliberately exercise the legacy nonfinite readout contract.+ let ledger = ((0 / 0) `Internal.Single` (Not :< Sales))+ EA..+ (1 EA..@ (Not :< SalesRebates))+ :: EA.Alg Double (HatBase AccountTitles)+ let worksheet = EW.worksheetRows ledger EA.Zero+ assertEqual "worksheet NaN remains in the debit cell"+ [L.map T.pack ["Sales", "NaN", "", "", "", "NaN", "", "", ""]]+ [row | row@(title : _) <- worksheet, title == T.pack "Sales"]+ assertEqual "worksheet subtotal propagates the debit cell NaN"+ [L.map T.pack ["Subtotal", "NaN", "0.0", "", "", "NaN", "0.0", "0.0", "0.0"]]+ [row | row@(title : _) <- worksheet, title == T.pack "Subtotal"]++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testAccumulatorAccounting+ testBalanceSheetGroupsStayUnclosed+ testWriterNaNDirections+ testAccountTitlesBinary+ testAllowanceAccountTitles+ testAccountTitleClassification+ testFinalStockRuleReference+ testVocabOrdinalPin+ testAssistAllAccountInfos+ testAccountSemanticsRegistryInvariants+ testAccountSemanticsGolden+ testAssistSuggestAccounts+ testPostVocabGolden+ testAccountAlgebraBehaviorGolden+ testWriteRowsGolden+ testReadoutBaselineGolden+ testExampleNumbersGolden+ testAdmissionBaselineGolden+ testAliasResolutionGolden+ testJcciAccountNameCoverage+ testJcciAliasesMatchFixture+ testJapaneseAccountLabels+ testRegistryWildcards+ testContraIffReversedHomeSide+ testPimoFlipInvolution+ testExchangeRelationProp538+ testIsContraSweepAcrossBaseInstances+ testPresentationGroups+ testPresentationGroupsWithoutAllowances+ testStatementContraAssetsLiteral+ testStatementPresentationGolden+ testWhichSideHatNotErrors+ testConsolidationWorksheet+ testSharedAccountBalancePrimitives+ testTrialBalanceValidation+ testReportingPresentation+ testStatementLaws+ testDerivedMetricsAndLegacyCoordinates+ bookkeepingProperties+ closingDocsTests
+ test/Admission/Spec.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wincomplete-patterns -Werror=incomplete-patterns #-}++-- | Per-transaction admission and independent evidence laws.+module Admission.Spec (runTests) where++import Data.Either (isRight)+import qualified Data.List.NonEmpty as NonEmpty+import Test.QuickCheck (chooseInteger, elements, forAll, listOf)++import ExchangeAlgebra+ ( AccountTitles(..)+ , HatBase((:<))+ , Hat(..)+ , Alg+ , MoneyDecimal+ , Redundant((.+), bar)+ , (.@)+ )+import qualified ExchangeAlgebra.Algebra.Core as Algebra+import ExchangeAlgebra.Algebra.Internal (Alg(Single))+import qualified ExchangeAlgebra.Journal.Core as Journal+import ExchangeAlgebra.Journal.Core ((.|))+import ExchangeAlgebra.IO.Input.Admission+import Support (assertEqual, quickProp)++-- | MoneyDecimal journal using polymorphic String notes.+type Ledger = Journal.Journal String MoneyDecimal (HatBase AccountTitles)++-- | Balanced cash sale; both postings have Not, but opposite accounting sides.+sale :: MoneyDecimal -> Alg MoneyDecimal (HatBase AccountTitles)+sale amount = amount .@ Not :< Cash+ .+ amount .@ Not :< Sales++-- | Observe every error without needing equality on the opaque success type.+errors :: Ledger -> [AdmissionError String (HatBase AccountTitles)]+errors journal = case admit journal of+ Left failure -> NonEmpty.toList (failureErrors failure)+ Right _ -> []++-- | Require admission before checking independent evidence.+accepted :: Ledger -> IO (Admitted String (HatBase AccountTitles))+accepted journal = case admit journal of+ Right result -> pure result+ Left failure -> fail (show failure)++-- | Check fixed counterexamples and the laws with generated positive amounts.+runTests :: IO ()+runTests = do+ -- (1), (6): preserve every original posting and disjoint note addition.+ quickProp "admit preserves generated balanced transactions" $+ forAll (listOf (chooseInteger (1, 1000000))) $ \amounts ->+ let journal = Journal.fromList+ [sale (fromInteger amount / 100) .| show index+ | (index, amount) <- zip [0 :: Int ..] amounts]+ in case admit journal of+ Right result -> Journal.toMap (admittedJournal result) == Journal.toMap journal+ Left _ -> False+ quickProp "admit succeeds on the sum of disjoint admitted journals" $+ forAll (chooseInteger (1, 1000000)) $ \x ->+ forAll (chooseInteger (1, 1000000)) $ \y ->+ let first = sale (fromInteger x / 100) .| "first" :: Ledger+ second = sale (fromInteger y / 100) .| "second" :: Ledger+ in isRight (admit first) && isRight (admit second)+ && isRight (admit (first .+ second))+ let cash = Not :< Cash+ sales = Not :< Sales+ wildcard = HatNot :< Cash+ account = Not :< AccountTitle+ -- (2): all four error kinds, including zero and negative values.+ assertEqual "(2) zero" [NonPositiveAmount "zero" cash 0]+ (errors (Single 0 cash .| "zero"))+ assertEqual "(2) negative"+ [NonPositiveAmount "negative" cash (-1), Imbalanced "negative" (-1) 0]+ (errors (Single (-1) cash .| "negative"))+ assertEqual "(2,4) wildcard side" [WildcardSide "side" wildcard]+ (errors (Single 1 wildcard .| "side"))+ assertEqual "(2,4) unpostable account" [NotPostableAccount "account" account]+ (errors (1 .@ account .| "account"))+ assertEqual "(2) debit differs from credit" [Imbalanced "bad" 100 90]+ (errors ((100 .@ cash .+ 90 .@ sales) .| "bad"))+ assertEqual "(2) cash sale uses decL/decR, not Hat/Not" []+ (errors (sale 100 .| "sale"))+ -- (3): note, base, per-posting checks, then imbalance.+ let invalid = Single (-1) (HatNot :< AccountTitle)+ mixed = Single (-2) cash .+ Single (-3) sales+ assertEqual "(3) all errors and order"+ ([NonPositiveAmount "a" (HatNot :< AccountTitle) (-1)+ , NotPostableAccount "a" (HatNot :< AccountTitle)+ , WildcardSide "a" (HatNot :< AccountTitle)]+ ++ [NonPositiveAmount "b" cash (-2)+ , NonPositiveAmount "b" sales (-3)+ , Imbalanced "b" (-2) (-3)])+ (errors ((mixed .| "b") .+ (invalid .| "a")))+ assertEqual "(5) Zero" True (isRight (admit (mempty :: Ledger)))+ -- TxKey is optional; the generated properties above use String notes.+ let key = TxKey (EntityId "shop") (PeriodId "2026") (TxId "sale")+ assertEqual "(1) TxKey note" True (isRight (admit (sale 100 .| key)))+ a <- accepted (sale 100 .| "sale")+ let cashOnly = Journal.projWithBase [cash]+ expected = 100 .@ cash .| "sale" :: Ledger+ assertEqual "(7) evidence matches" (Right ()) (checkEvidence a cashOnly expected)+ assertEqual "(8,13) other note missing"+ (Left (NonEmpty.singleton (EvidenceMissing "rent" cash 100)))+ (checkEvidence a cashOnly (100 .@ cash .| "rent"))+ assertEqual "(9) one posting differs"+ (Left (NonEmpty.singleton (EvidenceDiffers "sale" cash 90 100)))+ (checkEvidence a cashOnly (90 .@ cash .| "sale"))+ repeated <- accepted ((sale 40 .+ sale 60) .| "sale")+ let observed = map Algebra._val $ filter ((== cash) . Algebra._hatBase)+ (Algebra.toList (Journal.toAlg (admittedJournal repeated)))+ assertEqual "(10) count repeated base postings separately"+ (Left (NonEmpty.singleton (EvidenceAmbiguous "sale" cash observed)))+ (checkEvidence repeated cashOnly expected)+ assertEqual "(11) caller aggregates with bar" (Right ())+ (checkEvidence repeated (Journal.map bar . cashOnly) expected)+ assertEqual "(12,13) plank matches sale" (Right ())+ (checkEvidence a cashOnly (100 .@ cash .| Journal.plank))+ assertEqual "(14) wildcard account matches both accounts"+ (Left (NonEmpty.singleton (EvidenceAmbiguous "sale" account [100,100])))+ (checkEvidence a id (100 .@ account .| "sale"))+ -- (15): all three evidence laws and the plank counterexample.+ quickProp "(15) evidence laws: empty, unique matching, addition" $+ forAll (chooseInteger (1, 1000000)) $ \x ->+ forAll (chooseInteger (1, 1000000)) $ \y ->+ let first = fromInteger x / 100 .@ cash .| "first" :: Ledger+ second = fromInteger y / 100 .@ cash .| "second" :: Ledger+ journal = (sale (fromInteger x / 100) .| "first")+ .+ (sale (fromInteger y / 100) .| "second")+ in case admit journal of+ Left _ -> False+ Right result ->+ checkEvidence result id mempty == Right ()+ && checkEvidence result id (first .+ second) == Right ()+ && (isRight (checkEvidence result id (first .+ second))+ == (isRight (checkEvidence result id first)+ && isRight (checkEvidence result id second)))+ plankJournal <- accepted ((sale 100 .| Journal.plank) .+ (sale 200 .| "sale"))+ assertEqual "(15) plank expectation is ambiguous across notes"+ (Left (NonEmpty.singleton (EvidenceAmbiguous "" cash [100,200])))+ (checkEvidence plankJournal cashOnly (cashOnly (admittedJournal plankJournal)))+ -- (16): repeated expectations are independent, not combined or consumed.+ assertEqual "(16) duplicate expectations both match" (Right ())+ (checkEvidence a id (expected .+ expected))+ let differs = EvidenceDiffers "sale" cash 90 100+ wrong = 90 .@ cash .| "sale" :: Ledger+ assertEqual "(16) duplicate expectations both report differences"+ (Left (differs NonEmpty.:| [differs]))+ (checkEvidence a id (wrong .+ wrong))+ quickProp "(15) evidence addition law including failures" $+ forAll (elements [90, 100, 110]) $ \x ->+ forAll (elements [90, 100, 110]) $ \y ->+ let first = x .@ cash .| "sale" :: Ledger+ second = y .@ cash .| "sale" :: Ledger+ in isRight (checkEvidence a id (first .+ second))+ == (isRight (checkEvidence a id first) && isRight (checkEvidence a id second))
+ test/Algebra/AccumSpec.hs view
@@ -0,0 +1,507 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | Exact accumulator laws in the Algebra layer. The public HatVal methods+-- are observed against Rational, with no tolerance and no readout operations.+-- Generators cover binary64 boundaries and decimal scales before the fixed cases.+module Algebra.AccumSpec (runTests) where++import Control.Monad (forM_, unless)+import qualified Data.Decimal as Decimal+import Data.Hashable (hashWithSalt)+import qualified Data.List as List+import Data.Maybe (isJust)+import Data.Ratio ((%), denominator)+import GHC.Float (castDoubleToWord64, castWord64ToDouble)+import System.Exit (exitFailure)+import Test.QuickCheck hiding (label)++import ExchangeAlgebra.Algebra.Value+ ( HatVal(..)+ , MoneyDecimal(..)+ , MoneyDouble(..)+ , RationalAccum(..)+ , Rounding(..)+ , ValueError(..)+ , isErrorValue+ , rationalAddAccum+ , rationalRoundAccum+ , roundScaled+ , sumValues+ )+import ExchangeAlgebra.Algebra.Posting+ ( PostedError(..)+ , posted+ , unPosted+ )++-- * Inputs and observations++-- | Largest finite binary64 value.+maximumFinite :: Double+maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971++-- | Smallest positive binary64 value.+subnormal :: Double+subnormal = encodeFloat 1 (-1074)++-- | First integer whose successor is not representable in binary64.+threshold :: Double+threshold = 2 ^ (53 :: Int)++-- | Accumulate without intermediate rounding; signed inputs are intentional.+accumulate :: HatVal n => [n] -> Accum n+accumulate = List.foldl' (flip addAccum) emptyAccum++-- | Bias toward zero, subnormals, 2^53, maximum values, and two decimal places.+genFinite :: Gen Double+genFinite = frequency+ [ (2, elements [0, -0])+ , (3, do+ coefficient <- chooseInteger (1, 2 ^ (52 :: Int) - 1)+ sign <- elements [1, -1]+ pure (sign * encodeFloat coefficient (-1074)))+ , (3, do+ offset <- chooseInteger (-8, 8)+ sign <- elements [1, -1]+ pure (sign * fromRational (toRational threshold + fromInteger offset)))+ , (3, do+ offset <- chooseInteger (0, 8)+ sign <- elements [1, -1]+ pure (sign * encodeFloat (2 ^ (53 :: Int) - 1 - offset) 971))+ , (5, do+ cents <- chooseInteger (-1000000, 1000000)+ pure (fromRational (cents % 100)))+ , (4, do+ coefficient <- chooseInteger (1, 2 ^ (53 :: Int) - 1)+ power <- chooseInt (-1074, 971)+ sign <- elements [1, -1]+ pure (sign * encodeFloat coefficient power))+ ]++-- | Include the scale limit and large exact coefficients as well as cents.+genDecimal :: Gen MoneyDecimal+genDecimal = do+ places <- frequency [(5, pure 2), (2, elements [0, 1, 254, 255]), (1, choose (0, 255))]+ coefficient <- frequency+ [ (6, chooseInteger (-1000000, 1000000))+ , (1, elements [0, 1, -1, 10 ^ (300 :: Int), negate (10 ^ (300 :: Int))])+ ]+ pure (MoneyDecimal (Decimal.Decimal places coefficient))++-- | Rational inputs include nonbinary fractions and floating overflow boundaries.+genRatio :: Gen Rational+genRatio = frequency+ [ (5, do+ numerator <- chooseInteger (-1000000, 1000000)+ divisor <- chooseInteger (1, 10000)+ pure (numerator % divisor))+ , (3, do+ value <- genFinite+ divisor <- chooseInteger (1, 17)+ pure (toRational value / fromInteger divisor))+ , (2, elements+ [ toRational maximumFinite + toRational (encodeFloat 1 970 :: Double)+ , 2 * toRational maximumFinite+ , toRational subnormal / 2+ , 3 * toRational subnormal / 2+ ])+ ]++-- | Report a fixed-case failure with the exact observed values.+check :: (Eq a, Show a) => String -> a -> a -> IO ()+check label expected actual = unless (expected == actual) $ do+ putStrLn ("[FAIL] accum " ++ label ++ ": expected " ++ show expected ++ ", got " ++ show actual)+ exitFailure++-- | Run enough cases to repeatedly exercise the biased exponent and scale ranges.+propertyCheck :: String -> Property -> IO ()+propertyCheck label proposition = do+ result <- quickCheckWithResult stdArgs { maxSuccess = 600, chatty = False } proposition+ unless (isSuccess result) $ do+ putStrLn ("[FAIL] accum " ++ label ++ ": " ++ output result)+ exitFailure+ putStrLn ("[PASS] accum " ++ label ++ " (600 cases)")++-- * Laws++-- | A1-A6, A5', E1, E2, T3, S1, V1-V3, B1, and P1 for each finite value type.+-- Exact sums and signs use Rational; rounded observations use the value type's ==.+propFiniteLaws :: forall n. (HatVal n, Real n) => Gen n -> Property+propFiniteLaws generator =+ forAll (chooseInt (0, 20) >>= flip vectorOf generator) $ \values ->+ forAll (shuffle values) $ \permuted ->+ forAll generator $ \value ->+ forAll (chooseInt (0, length values)) $ \firstCut ->+ forAll (chooseInt (firstCut, length values)) $ \secondCut ->+ let (left, rest) = splitAt firstCut values+ (middle, right) = splitAt (secondCut - firstCut) rest+ first = accumulate left+ second = accumulate middle+ third = accumulate right+ total = accumulate values+ expected = sum (map toRational values)+ merged = mergeAccum first (mergeAccum second third)+ rounded = roundAccum total+ roundedFinite = isJust (exactAccum (addAccum rounded emptyAccum))+ successfulSplit = expected > 0 && roundedFinite+ checked = fmap unPosted (posted value)+ expectedPosted = case valueError value of+ Just failure -> Left (InvalidValue failure)+ Nothing -> case postingUpperBound :: Maybe n of+ Just bound | value > bound -> Left AboveBound+ _ | value == zeroValue -> Right zeroValue+ | otherwise -> Right value+ in conjoin+ [ counterexample "A1 commutative" $+ exactAccum (mergeAccum first second) === exactAccum (mergeAccum second first)+ , counterexample "A1 associative" $+ exactAccum (mergeAccum (mergeAccum first second) third) === exactAccum merged+ , counterexample "A1 left identity" $+ exactAccum (mergeAccum emptyAccum total) === Just expected+ , counterexample "A1 right identity" $+ exactAccum (mergeAccum total emptyAccum) === Just expected+ , counterexample "A2 singleton merge" $+ exactAccum (addAccum value total)+ === exactAccum (mergeAccum total (addAccum value emptyAccum))+ , counterexample "A3 involution" $+ exactAccum (negateAccum (negateAccum total)) === Just expected+ , counterexample "A3/A6 cancellation" $+ exactAccum (mergeAccum total (negateAccum total)) === Just 0+ , counterexample "A3 signed oracle" $+ exactAccum (negateAccum total) === Just (negate expected)+ , counterexample "A4 singleton" $+ roundAccum (addAccum value emptyAccum) === value+ , counterexample "A5/A5' domain and reconstruction" $ case splitAccum total of+ Nothing -> property (not successfulSplit)+ Just components -> conjoin+ [ property successfulSplit+ , property (all (\x -> x > 0 && valueError x == Nothing) components)+ , sum (map toRational components) === expected+ ]+ , counterexample "A5 path independence" $+ splitAccum total === splitAccum (accumulate permuted)+ , counterexample "A5 merge independence" $+ splitAccum total === splitAccum merged+ , counterexample "E1 exact value" $ exactAccum total === Just expected+ , counterexample "E1 permutation" $+ exactAccum (accumulate permuted) === Just expected+ , counterexample "E1 merge tree" $ exactAccum merged === Just expected+ , counterexample "exact sign" $ signAccum total === Just (compare expected 0)+ , counterexample "E2 finite rounding" $ case roundRational Nearest expected of+ Just result -> rounded === result+ Nothing -> property (not roundedFinite)+ , counterexample "T3 representable input" $+ roundRational Nearest (toRational value) === Just value+ , counterexample "S1 definition" $+ sumValues values === roundAccum (foldr addAccum emptyAccum values)+ , counterexample "S1 permutation" $ sumValues values === sumValues permuted+ , counterexample "V1 zero" $ valueError (zeroValue :: n) === Nothing+ , counterexample "V2 valid is finite and nonnegative" $+ property (valueError value /= Nothing ||+ (value >= zeroValue && isJust (exactAccum (addAccum value emptyAccum))))+ , counterexample "V3 finite signed values" $+ valueError value === case compare value zeroValue of+ LT -> Just NegativeValue+ _ -> Nothing+ , counterexample "isErrorValue is a function of valueError" $+ isErrorValue value === isJust (valueError value)+ , counterexample "B1 bound" $ case postingUpperBound :: Maybe n of+ Nothing -> property True+ Just bound -> property (bound > zeroValue && valueError bound == Nothing)+ , counterexample "P1 priority and accepted value" $ checked === expectedPosted+ ]++-- | A5 additionally fixes each Double component to the largest value below the remainder.+propGreedySplit :: Property+propGreedySplit = forAll (chooseInt (0, 20) >>= flip vectorOf genFinite) $ \values ->+ let expected = sum (map toRational values)+ verify _ [] = True+ verify residual (component : rest) =+ toRational component <= residual+ && (isInfinite successor || toRational successor > residual)+ && verify (residual - toRational component) rest+ where+ successor = castWord64ToDouble (castDoubleToWord64 component + 1)+ in case splitAccum (accumulate values) of+ Nothing -> property (expected <= 0 || isInfinite (fromRational expected :: Double))+ Just components -> property (verify expected components)++-- | T1 and T3 for a positive exact unit, including ties and negative multiples.+propDecimalUnit :: Property+propDecimalUnit = forAll genDecimal $ \rawUnit ->+ forAll genRatio $ \value ->+ forAll (chooseInteger (-10000, 10000)) $ \multiple ->+ let unit = abs rawUnit + MoneyDecimal (Decimal.Decimal 255 1)+ rationalUnit = toRational unit+ tied = (fromInteger multiple + 1 % 2) * rationalUnit+ evenMultiple = multiple + multiple `mod` 2+ checkUnit rational = case roundRational (ToMultipleOf unit) rational of+ Nothing -> counterexample "positive exact unit rejected" False+ Just rounded ->+ let quotient = toRational rounded / rationalUnit+ in conjoin+ [ denominator quotient === 1+ , property (abs (toRational rounded - rational) <= rationalUnit / 2)+ , property (abs (toRational rounded - rational) /= rationalUnit / 2+ || even (round quotient :: Integer))+ ]+ in conjoin+ [ checkUnit value+ , checkUnit tied+ , fmap toRational (roundRational (ToMultipleOf unit) tied)+ === Just (fromInteger evenMultiple * rationalUnit)+ , roundRational (ToMultipleOf unit) (fromInteger multiple * rationalUnit)+ === Just (fromInteger multiple * unit)+ , roundRational (ToMultipleOf (negate unit)) value === Nothing+ , roundRational (ToMultipleOf (0 :: MoneyDecimal)) value === Nothing+ ]++-- | T2: a reduced denominator is representable precisely when it divides 10^255.+-- This oracle does not repeat the implementation's stepwise factorization.+propDecimalNearest :: Property+propDecimalNearest =+ forAll (chooseInteger (-10000, 10000)) $ \coefficient ->+ forAll (chooseInt (0, 260)) $ \twos ->+ forAll (chooseInt (0, 260)) $ \fives ->+ forAll (elements [1, 3, 7, 11 :: Integer]) $ \other ->+ let value = coefficient % (2 ^ twos * 5 ^ fives * other)+ result = roundRational Nearest value :: Maybe MoneyDecimal+ representable = 10 ^ (255 :: Int) `mod` denominator value == 0+ in conjoin+ [ isJust result === representable+ , case result of+ Nothing -> property True+ Just rounded -> toRational rounded === value+ ]++-- | T4 and T5 for both floating types; the oracle rounds Rational directly.+propFloatingRounding :: Property+propFloatingRounding = forAll genRatio $ \value -> forAll genFinite $ \unit ->+ let rounded = fromRational value :: Double+ expected+ | isInfinite rounded = Nothing+ | otherwise = Just rounded+ in conjoin+ [ (roundRational Nearest value :: Maybe Double) === expected+ , roundRational Nearest value === fmap MoneyDouble expected+ , roundRational (ToMultipleOf unit) value === Nothing+ , roundRational (ToMultipleOf (MoneyDouble unit)) value === Nothing+ ]++-- | The Rational helper preserves finite values, signs, and the supplied conversion.+propRationalHelper :: Property+propRationalHelper = forAll (chooseInt (0, 20) >>= flip vectorOf genFinite) $ \values ->+ let state = List.foldl' (flip rationalAddAccum) (RationalAccum 0) values+ expected = sum (map toRational values)+ in conjoin+ [ state === RationalAccum expected+ , rationalRoundAccum id state === expected+ , rationalRoundAccum negate state === negate expected+ ]++-- * Fixed boundaries++-- | Integer overflow, cancellation, two decimal places, subnormals, and greedy splits.+-- Each case also checks the MoneyDouble delegation against the same Rational oracle.+testFloatingBoundaries :: IO ()+testFloatingBoundaries = do+ let machineHalf = encodeFloat 1 (finiteBitSizeInt - 2)+ machineMinimum = fromIntegral (minBound :: Int) :: Double+ belowMidpoint = encodeFloat (2 ^ (53 :: Int) - 1) 917+ midpoint = encodeFloat 1 970+ aboveMidpoint = encodeFloat (2 ^ (52 :: Int) + 1) 918+ cases =+ [ ("Int positive overflow", [machineHalf, machineHalf])+ , ("Int negative overflow", [machineMinimum, -1])+ , ("above 2^53 then fraction", [threshold, 1, 0.25])+ , ("above 2^53 then fraction then cancel", [threshold, 1, 0.25, -threshold])+ , ("two decimal places", [12.34, 56.78, -12.34, 0.01])+ , ("0.1+0.2", [0.1, 0.2])+ , ("M+M-M", [maximumFinite, maximumFinite, -maximumFinite])+ , ("M+s", [maximumFinite, subnormal])+ , ("M+M", [maximumFinite, maximumFinite])+ , ("subnormal cancellation", [maximumFinite, subnormal, -maximumFinite])+ , ("smallest normal minus subnormal", [encodeFloat 1 (-1022), -subnormal])+ , ("three residuals", [0.1, encodeFloat 1 (-60), subnormal])+ , ("tie down", [threshold, 1])+ , ("tie up", [threshold + 2, 1])+ , ("beyond tie", [threshold, 1, subnormal])+ , ("overflow midpoint cancellation", [maximumFinite, midpoint, -midpoint])+ , ("zero", [-0, 0])+ ]+ forM_ cases $ \(label, values) -> do+ let expected = sum (map toRational values)+ state = accumulate values+ wrapped = accumulate (map MoneyDouble values)+ check (label ++ " exact") (Just expected) (exactAccum state)+ check (label ++ " round") (fromRational expected :: Double) (roundAccum state)+ check (label ++ " sign") (Just (compare expected 0)) (signAccum state)+ check (label ++ " wrapped exact") (Just expected) (exactAccum wrapped)+ check (label ++ " wrapped round") (MoneyDouble (fromRational expected)) (roundAccum wrapped)+ check (label ++ " negate") (Just (negate expected)) (exactAccum (negateAccum state))+ check (label ++ " split delegation") (fmap (map MoneyDouble) (splitAccum state))+ (splitAccum wrapped)+ check "minBound negation" (Just (negate (toRational machineMinimum)))+ (exactAccum (negateAccum (accumulate [machineMinimum])))+ check "minBound negation rounds" (negate machineMinimum)+ (roundAccum (negateAccum (accumulate [machineMinimum])))+ check "negative zero normalized" False (isNegativeZero (sumValues [-0 :: Double]))+ check "0.1+0.2 greedy" (Just [0.3, encodeFloat 1 (-55)])+ (splitAccum (accumulate [0.1 :: Double, 0.2]))+ check "M+s greedy" (Just [maximumFinite, subnormal])+ (splitAccum (accumulate [maximumFinite, subnormal]))+ check "below overflow midpoint split" (Just [maximumFinite, belowMidpoint])+ (splitAccum (accumulate [maximumFinite, belowMidpoint]))+ check "at overflow midpoint split" Nothing+ (splitAccum (accumulate [maximumFinite, midpoint]))+ check "above overflow midpoint split" Nothing+ (splitAccum (accumulate [maximumFinite, aboveMidpoint]))+ check "M+M split" Nothing (splitAccum (accumulate [maximumFinite, maximumFinite]))+ check "M+M scaled once" (Just maximumFinite)+ (roundScaled Nearest (1 % 2) (accumulate [maximumFinite, maximumFinite]))+ check "M+M scaled once wrapped" (Just (MoneyDouble maximumFinite))+ (roundScaled Nearest (1 % 2) (accumulate (map MoneyDouble [maximumFinite, maximumFinite])))+ check "positive subnormal split" (Just [subnormal]) (splitAccum (accumulate [subnormal]))+ check "negative state split" Nothing (splitAccum (accumulate [-subnormal]))+ check "zero state split" Nothing (splitAccum (accumulate [0 :: Double]))+ check "subnormal tie to zero" (Just 0 :: Maybe Double)+ (roundRational Nearest (toRational subnormal / 2))+ check "subnormal tie to even" (Just (2 * subnormal))+ (roundRational Nearest (3 * toRational subnormal / 2))+ where+ finiteBitSizeInt = length (takeWhile (> 0) (iterate (`div` 2) (maxBound :: Int))) + 1++-- | A6 and V3 over the IEEE table, for add, merge, negation, and both floating types.+testSpecialValues :: IO ()+testSpecialValues = do+ let values = [0, 1, -1, 1 / 0, -1 / 0, 0 / 0 :: Double]+ observe value+ | isNaN value = Nothing+ | otherwise = Just value+ forM_ values $ \left -> do+ let singleton = accumulate [left]+ expectedError+ | isNaN left || isInfinite left = Just NonFiniteValue+ | left < 0 = Just NegativeValue+ | otherwise = Nothing+ check "V3 Double" expectedError (valueError left)+ check "V3 MoneyDouble" expectedError (valueError (MoneyDouble left))+ check "special negation" (observe (negate left))+ (observe (roundAccum (negateAccum singleton)))+ forM_ values $ \right -> do+ let expected = left + right+ states = [addAccum right singleton, mergeAccum singleton (accumulate [right])]+ wrapped = accumulate (map MoneyDouble [left, right])+ forM_ states $ \state -> do+ check "A6 addition table" (observe expected) (observe (roundAccum state))+ check "A6 exact classification" (not (isNaN expected || isInfinite expected))+ (isJust (exactAccum state))+ check "A6 sign" (fmap (`compare` 0) (observe expected)) (signAccum state)+ check "A6 wrapped table" (fmap MoneyDouble (observe expected))+ (case roundAccum wrapped of+ MoneyDouble value -> fmap MoneyDouble (observe value))+ check "A6 wrapped merge" (observe expected)+ (case roundAccum (mergeAccum (accumulate [MoneyDouble left])+ (accumulate [MoneyDouble right])) of+ MoneyDouble value -> observe value)+ forM_ [1 / 0, -1 / 0, 0 / 0 :: Double] $ \special -> do+ let state = accumulate [special]+ check "nonfinite split" Nothing (splitAccum state)+ check "nonfinite exact" Nothing (exactAccum state)+ check "nonfinite scaled" Nothing (roundScaled Nearest 0 state)+ check "P1 nonfinite before bound or sign" (Left (InvalidValue NonFiniteValue))+ (fmap unPosted (posted special))+ check "P1 wrapped nonfinite before bound or sign" (Left (InvalidValue NonFiniteValue))+ (fmap unPosted (posted (MoneyDouble special)))+ forM_ [[], [0.1], [0.1, 0.2], [maximumFinite, maximumFinite]] $ \finite -> do+ let finiteState = accumulate finite+ check "special absorbs finite state" (observe special)+ (observe (roundAccum (mergeAccum state finiteState)))+ check "finite state absorbs special" (observe special)+ (observe (roundAccum (mergeAccum finiteState state)))+ forM_ [0, -1, 0.01, 1 / 0, -1 / 0, 0 / 0 :: Double] $ \unit -> do+ check "T5 Double all units" Nothing (roundRational (ToMultipleOf unit) (1 % 3))+ check "T5 MoneyDouble all units" Nothing+ (roundRational (ToMultipleOf (MoneyDouble unit)) (1 % 3))++-- | T1-T3 at exact decimal scale boundaries and the Eq/Hashable contract.+testDecimalBoundaries :: IO ()+testDecimalBoundaries = do+ let tiny = MoneyDecimal (Decimal.Decimal 255 1)+ cents = MoneyDecimal (Decimal.Decimal 2 1)+ one = MoneyDecimal (Decimal.Decimal 1 10)+ sameOne = MoneyDecimal (Decimal.Decimal 2 100)+ check "T2 10^-255" (Just tiny) (roundRational Nearest (1 % 10 ^ (255 :: Int)))+ check "T2 10^-256" (Nothing :: Maybe MoneyDecimal)+ (roundRational Nearest (1 % 10 ^ (256 :: Int)))+ check "T2 reduced denominator" (Just tiny)+ (roundRational Nearest (10 % 10 ^ (256 :: Int)))+ check "T2 1/3" (Nothing :: Maybe MoneyDecimal) (roundRational Nearest (1 % 3))+ check "T2 2^-255" (Just (1 % 2 ^ (255 :: Int)))+ (toRational <$> (roundRational Nearest (1 % 2 ^ (255 :: Int)) :: Maybe MoneyDecimal))+ check "T2 5^-255" (Just (1 % 5 ^ (255 :: Int)))+ (toRational <$> (roundRational Nearest (1 % 5 ^ (255 :: Int)) :: Maybe MoneyDecimal))+ check "T2 signed boundary" (Just (negate tiny))+ (roundRational Nearest ((-1) % 10 ^ (255 :: Int)))+ check "A5' decimal positive" (Just [1 + tiny]) (splitAccum (accumulate [1, tiny]))+ check "A5' decimal zero" Nothing (splitAccum (accumulate [tiny, negate tiny]))+ check "A5' decimal negative" Nothing (splitAccum (accumulate [negate tiny]))+ check "S1 decimal cents" (3 % 10)+ (toRational (sumValues [MoneyDecimal (Decimal.Decimal 1 1),+ MoneyDecimal (Decimal.Decimal 1 2)]))+ check "A6 decimal exact cancellation" (Just 0)+ (exactAccum (mergeAccum (accumulate [1, tiny]) (negateAccum (accumulate [1, tiny]))))+ forM_ [(1 % 200, 0), (3 % 200, 2), ((-1) % 200, 0), ((-3) % 200, -2)] $+ \(value, multiple) ->+ check "T1 ties to even cents" (Just (fromInteger multiple * cents))+ (roundRational (ToMultipleOf cents) value)+ check "T1 nonpositive unit" Nothing (roundRational (ToMultipleOf (0 :: MoneyDecimal)) 1)+ check "decimal representations equal" True (one == sameOne)+ forM_ [0, 1, -1, maxBound, minBound :: Int] $ \salt -> do+ check "equal decimal hash" (hashWithSalt salt one) (hashWithSalt salt sameOne)+ check "decimal zero hash" (hashWithSalt salt (MoneyDecimal (Decimal.Decimal 0 0)))+ (hashWithSalt salt (MoneyDecimal (Decimal.Decimal 255 0)))++-- | B1 and P1 at adjacent floating values and an unbounded decimal magnitude.+testPostingBoundaries :: IO ()+testPostingBoundaries = do+ let bound = 2 ^ (900 :: Int) :: Double+ below = castWord64ToDouble (castDoubleToWord64 bound - 1)+ above = castWord64ToDouble (castDoubleToWord64 bound + 1)+ huge = MoneyDecimal (Decimal.Decimal 0 (10 ^ (1000 :: Int)))+ check "B1 Double bound" (Just bound) (postingUpperBound :: Maybe Double)+ check "B1 MoneyDouble bound" (Just (MoneyDouble bound)) (postingUpperBound :: Maybe MoneyDouble)+ check "B1 MoneyDecimal unbounded" Nothing (postingUpperBound :: Maybe MoneyDecimal)+ forM_ [0, -0, below, bound] $ \value -> do+ check "P1 accepted Double" (Right value) (fmap unPosted (posted value))+ check "P1 accepted MoneyDouble" (Right (MoneyDouble value))+ (fmap unPosted (posted (MoneyDouble value)))+ check "P1 above Double bound" (Left AboveBound) (fmap unPosted (posted above))+ check "P1 above MoneyDouble bound" (Left AboveBound)+ (fmap unPosted (posted (MoneyDouble above)))+ check "P1 decimal has no bound" (Right huge) (fmap unPosted (posted huge))+ check "P1 negative decimal" (Left (InvalidValue NegativeValue))+ (fmap unPosted (posted (negate huge)))+ check "P1 positive zero Double" (Right False)+ (fmap (isNegativeZero . unPosted) (posted (-0 :: Double)))+ check "P1 positive zero MoneyDouble" (Right False)+ (fmap (\checked -> case unPosted checked of MoneyDouble value -> isNegativeZero value)+ (posted (MoneyDouble (-0))))++-- | Run the laws and fixed cases from the package's test suite.+runTests :: IO ()+runTests = do+ testFloatingBoundaries+ testSpecialValues+ testDecimalBoundaries+ testPostingBoundaries+ propertyCheck "Double A1-A6/A5'/E1/E2/T3/S1/V1-V3/B1/P1" (propFiniteLaws genFinite)+ propertyCheck "MoneyDouble A1-A6/A5'/E1/E2/T3/S1/V1-V3/B1/P1"+ (propFiniteLaws (MoneyDouble <$> genFinite))+ propertyCheck "MoneyDecimal A1-A6/A5'/E1/T3/S1/V1-V3/B1/P1" (propFiniteLaws genDecimal)+ propertyCheck "A5 greedy Double split" propGreedySplit+ propertyCheck "T1/T3 decimal unit and ties" propDecimalUnit+ propertyCheck "T2 decimal representability" propDecimalNearest+ propertyCheck "T4/T5 floating rounding" propFloatingRounding+ propertyCheck "RationalAccum helper" propRationalHelper+ putStrLn "[PASS] accumulator boundaries and IEEE table"
+ test/Algebra/CoreSpec.hs view
@@ -0,0 +1,642 @@+-- | Algebra projections, redundant representations, and law properties.+-- Tests exercise the Algebra layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Algebra.CoreSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Algebra.Internal as EAI+import qualified ExchangeAlgebra.Journal as EJ+import ExchangeAlgebra.Algebra.Value (MoneyDecimal, bankersRound)+import qualified Data.Map.Strict as M+import qualified Data.List as L+import System.Exit (exitFailure)+import Control.Exception (try, evaluate, SomeException)+import Test.QuickCheck hiding (Fixed)+import Support (assertEqual+ , assertNear+ , TestAlg+ , SimHatBase2+ , quickProp+ , genBase+ , genNNDouble+ , netByBase+ , epsEq+ )++testMapPosting :: IO ()+testMapPosting = do+ let source = (1 .@ (Hat :< Yen))+ .+ (2 .@ (Hat :< Yen))+ .+ (3 .@ (Not :< Amount))+ .+ (4 .@ (Hat :< Amount))+ :: TestAlg+ actual = EA.mapPosting (\v b -> (2 * v, b)) source+ assertNear "Algebra.mapPosting doubles norm"+ (2 * norm source) (norm actual)+ assertEqual "Algebra.mapPosting preserves posting count"+ (length (EA.toList source)) (length (EA.toList actual))+ assertEqual "Algebra.mapPosting preserves posting order"+ (EA.toList (2 .* source)) (EA.toList actual)++testMapMaybePosting :: IO ()+testMapMaybePosting = do+ let droppedBase = Not :< Amount :: HatBase CountUnit+ zeroedBase = Hat :< Yen :: HatBase CountUnit+ source = (1 .@ (Hat :< Yen))+ .+ (2 .@ (Hat :< Yen))+ .+ (3 .@ droppedBase)+ .+ (4 .@ (Hat :< Amount))+ :: TestAlg+ dropped = EA.mapMaybePosting+ (\v b -> if b == droppedBase then Nothing else Just (v, b))+ source+ zeroed = EA.mapMaybePosting+ (\v b -> Just (if b == zeroedBase then 0 else v, b))+ source+ without b = L.filter (\posting -> case posting of+ _ :@ b' -> b' /= b+ _ -> False)+ assertNear "Algebra.mapMaybePosting drop decreases norm"+ (norm source - 3) (norm dropped)+ assertEqual "Algebra.mapMaybePosting drop preserves remaining order"+ (without droppedBase (EA.toList source)) (EA.toList dropped)+ assertEqual "Algebra.mapMaybePosting normalizes zero values away"+ (without zeroedBase (EA.toList source)) (EA.toList zeroed)+++-- | Regression test for the `bases` typo bug.+--+-- Before the fix at Algebra.hs:868, `bases` ignored the `_notSide` Seq and+-- iterated `_hatSide` twice (with `Hat` and `Not` labels). As a result,+-- `length (bases x) != length (vals x)` whenever Hat/Not Seq lengths differed.+--+-- This test constructs an Alg where the Hat Seq for `Yen` has length 1 and+-- the Not Seq has length 2, plus a separate basis whose Hat Seq is empty.+-- That makes the divergence detectable in both directions.+testBasesNotSideRegression :: IO ()+testBasesNotSideRegression = do+ let alg :: TestAlg+ alg = (100 .@ (Hat :< Yen)) -- Yen: hatSide = [100]+ .+ (50 .@ (Not :< Yen)) -- Yen: notSide = [50]+ .+ (30 .@ (Not :< Yen)) -- Yen: notSide = [50, 30]+ .+ (20 .@ (Not :< Amount)) -- Amount: notSide = [20], hatSide = []+ vs = EA.vals alg+ bs = EA.bases alg+ hatCount = length (L.filter isHat bs)+ notCount = length (L.filter (not . isHat) bs)+ -- vals and bases must agree on total count (one label per scalar entry)+ assertEqual "bases/vals same length (regression for hs/ns typo)"+ (length vs) (length bs)+ -- Expected: 1 Hat label (Hat:<Yen) and 3 Not labels (50:<Yen, 30:<Yen, 20:<Amount)+ assertEqual "bases Hat label count" 1 hatCount+ assertEqual "bases Not label count" 3 notCount+++-- | Characterization: the same-base 'Seq' order is __construction-path+-- dependent__. The pairwise-union path ('EA.fromList' = 'mconcat') and the+-- bulk-merge path ('EA.sigma' \/ 'EA.unionsMerge') produce the same /multiset/+-- of postings but in different sequence orders, which 'Eq' \/ @Binary@ observe+-- (and 'Double' observes through the last ULP of 'norm'\/'bar' association).+-- This test pins the current orders so any change to either path is a+-- conscious decision; unifying the paths is tracked in the 0.5.0.0 cleanup+-- plan. For order-independent comparison use `MoneyDecimal` (exact) or compare+-- after 'EA.compress'\/'EA.bar'.+testSameBaseSeqOrderPathDependence :: IO ()+testSameBaseSeqOrderPathDependence = do+ let f :: Int -> TestAlg+ f i = fromIntegral i .@ (Hat :< Yen)+ xs = L.map f [1, 2, 3]+ viaFromList = EA.fromList xs+ viaSigma = EA.sigma [1, 2, 3] f+ viaMerge = EA.unionsMerge xs+ assertEqual "fromList same-base seq order (pairwise-union path)"+ [3, 1, 2] (EA.vals viaFromList)+ assertEqual "sigma same-base seq order (bulk-merge path)"+ [3, 2, 1] (EA.vals viaSigma)+ assertEqual "unionsMerge order matches sigma (same merge path)"+ (EA.vals viaSigma) (EA.vals viaMerge)+ -- same multiset, different order: Eq observes the redundancy order+ assertEqual "fromList /= sigma under Eq (order is observable)"+ False (viaFromList == viaSigma)+ -- the algebraic content is nevertheless identical+ assertNear "norm agrees across construction paths"+ (norm viaFromList) (norm viaSigma)+ assertEqual "bar agrees across construction paths"+ (EA.bar viaFromList) (EA.bar viaSigma)+++-- | Regression for audit divergence C: scalar product (.*) must reject a+-- negative / non-finite scalar instead of silently producing negative+-- (out-of-domain) postings. (Pre-fix, (.*) used raw (:@) and bypassed the+-- isErrorValue check that (.@) performs.)+testScalarRejectsNegative :: IO ()+testScalarRejectsNegative = do+ let xD = 10 .@ (Not :< Yen) :: TestAlg+ rD <- try (evaluate (norm ((-1) .* xD))) :: IO (Either SomeException Double)+ case rD of+ Left _ -> putStrLn "[PASS] (.*) rejects negative scalar (Double)"+ Right v -> do putStrLn ("[FAIL] (.*) negative scalar leaked (Double): " ++ show v); exitFailure+ let xN = 10 .@ (Not :< Yen) :: EA.Alg MoneyDecimal (HatBase CountUnit)+ rN <- try (evaluate (norm ((-1) .* xN))) :: IO (Either SomeException MoneyDecimal)+ case rN of+ Left _ -> putStrLn "[PASS] (.*) rejects negative scalar (MoneyDecimal)"+ Right v -> do putStrLn ("[FAIL] (.*) negative scalar leaked (MoneyDecimal): " ++ show v); exitFailure+ -- non-negative scalar still works+ assertNear "(.*) non-negative scalar works" 20.0 (norm (2 .* xD))+ -- C1: wildcard sides remain queries, but cannot construct postings.+ let wildcardBase = HatNot :< Cash :: HatBase AccountTitles+ concrete = (1 .@ (Not :< Cash)) .+ (2 .@ (Hat :< Cash))+ :: EA.Alg Double (HatBase AccountTitles)+ mapM_ (\value -> do+ result <- try (evaluate (value .@ wildcardBase))+ :: IO (Either SomeException (EA.Alg Double (HatBase AccountTitles)))+ case result of+ Left _ -> putStrLn ("[PASS] (.@) rejects HatNot at " ++ show value)+ Right _ -> do+ putStrLn ("[FAIL] (.@) accepted HatNot at " ++ show value)+ exitFailure) [0, 1]+ assertEqual "HatBase retains standalone HatNot" HatNot (EA.hat wildcardBase)+ assertEqual "proj accepts HatNot query"+ (EA.toASCList concrete) (EA.toASCList (EA.proj [wildcardBase] concrete))++-- Step 1 (concrete projection keeps the axis index lazy): the module is compiled+-- @Strict@, so a concrete (non-wildcard) 'projNetNorm' must NOT force the lazy+-- @_axisPosting@ index (it should be a plain 'Map.lookup'); a wildcard 'projNetNorm'+-- must use (force) it. We poison the index fields with 'error' and check which+-- projection crashes. Guards the projExactMap/projWildMap split.+testProjConcreteNoIndexForce :: IO ()+testProjConcreteNoIndexForce = do+ let alg :: EA.Alg Double SimHatBase2+ alg = EA.fromList [ 10 .@ Not :< (Cash, 1, 1, Yen)+ , 20 .@ Not :< (Products, 2, 2, Amount)+ , 30 .@ Hat :< (Cash, 3, 3, Yen) ]+ assertNear "wildcard projNetNorm green with reserved fields unmaintained"+ 10.0 (EA.projNetNorm [Not :< (Cash, (.#), 1, Yen)] alg)+ assertNear "concrete projNetNorm green with reserved fields unmaintained"+ 10.0 (EA.projNetNorm [Not :< (Cash, 1, 1, Yen)] alg)+ case alg of+ EAI.Liner m _ _ _ _ _ -> do+ let poison = EAI.Liner m (error "POISON") (error "POISON")+ (error "POISON") (error "POISON") (error "POISON")+ rc <- try (evaluate (EA.projNetNorm [Not :< (Cash, 1, 1, Yen)] poison))+ :: IO (Either SomeException Double)+ case rc of+ Right v | v == 10.0 -> putStrLn "[PASS] concrete projNetNorm does not force the axis index"+ Right v -> do putStrLn ("[FAIL] concrete projNetNorm wrong value: " ++ show v); exitFailure+ Left _ -> do putStrLn "[FAIL] concrete projNetNorm forced the (poisoned) axis index"; exitFailure+ rw <- try (evaluate (EA.projNetNorm [Not :< (Cash, (.#), 1, Yen)] poison))+ :: IO (Either SomeException Double)+ case rw of+ Left _ -> putStrLn "[PASS] wildcard projNetNorm uses the axis index (forced, as required)"+ Right v -> do putStrLn ("[FAIL] wildcard projNetNorm did not use the index: " ++ show v); exitFailure+ _ -> do putStrLn "[FAIL] expected a Liner"; exitFailure+++-- | Regression tests for scale-aware numeric tolerance (WI-11/12/14).+-- These exercise large magnitudes that the previous fixed @1e-13@ absolute+-- tolerance handled incorrectly (retaining pure rounding noise as a residual);+-- small-scale behavior is unchanged. See plans LAZY_EVAL_AUDIT.md s4.6.+testNumericToleranceScaleAware :: IO ()+testNumericToleranceScaleAware = do+ assertEqual "isNearlyNum 1e-13: large-scale rounding rejected (documents old flaw)"+ False (EA.isNearlyNum (1e10 + 0.1 + 0.2) (1e10 + 0.3) (1e-13 :: Double))+ let big = (1e10 .@ (Hat :< Yen)) .+ (0.1 .@ (Hat :< Yen)) .+ (0.2 .@ (Hat :< Yen))+ .+ (1e10 .@ (Not :< Yen)) .+ (0.3 .@ (Not :< Yen)) :: TestAlg+ assertEqual "barWithin preserves legacy large-scale cancellation"+ True (EA.isZero (barWithin EA.defaultTolerance big))+ assertEqual "bar preserves the exact large-scale residual"+ [encodeFloat 1 (-55) .@ (Hat :< Yen)] (EA.toASCList (bar big))++-- ================================================================+-- Redundant-algebra axiom property tests (QuickCheck)+--+-- Encodes the Definition 6 axioms (paper Appendix A) + derived lemmas as+-- QuickCheck properties, plus regression generalizations for the union+-- zero-base bug and construction-order independence. Property suite, additive.+-- ================================================================++type NNAlg = EA.Alg MoneyDecimal (HatBase CountUnit)++genAlgD :: Gen TestAlg+genAlgD = sized $ \n -> do+ k <- choose (0, min 40 n)+ ps <- vectorOf k ((,) <$> genNNDouble <*> genBase)+ pure (EA.fromList [ v .@ b | (v, b) <- ps ])++genAlgN :: Gen NNAlg+genAlgN = sized $ \n -> do+ k <- choose (0, min 40 n)+ ps <- vectorOf k ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)+ pure (EA.fromList [ v .@ b | (v, b) <- ps ])++-- ℘ observation: retain each full Hat/Not base and its value multiset, while+-- forgetting the representation-level order of the per-side Seq.+observe :: (Ord b, Ord v, HatVal v, HatBaseClass b)+ => EA.Alg v b -> M.Map b [v]+observe = fmap L.sort . EA.foldEntries step M.empty+ where+ step m v b = M.insertWith (++) b [v] m++axiomProperties :: IO ()+axiomProperties = do+ let unitForParity i+ | even i = Yen+ | otherwise = Amount+ assertEqual "MoneyDecimal: 0.1 + 0.2 == 0.3 exactly"+ True (0.1 + 0.2 == (0.3 :: MoneyDecimal))+ let mk i = ((fromIntegral (i `mod` 7 + 1) :: MoneyDecimal)+ .@ ((if even i then Hat else Not) :< (unitForParity i)))+ .| show (i `mod` 150)+ xs :: [Journal String MoneyDecimal (HatBase CountUnit)]+ xs = [ mk i | i <- [1 .. 400 :: Int] ]+ viaFoldr = foldr (.+) mempty xs+ viaFoldl = L.foldl' (.+) mempty xs+ -- exact ⇒ norm is identical for the two construction orders+ assertEqual "MoneyDecimal Journal: norm is construction-order-independent"+ (norm viaFoldr) (norm viaFoldl)+ -- banker's rounding (round half to even)+ assertEqual "bankersRound 0 2.5 = 2 (half to even)" (2 :: MoneyDecimal) (bankersRound 0 2.5)+ assertEqual "bankersRound 0 3.5 = 4 (half to even)" (4 :: MoneyDecimal) (bankersRound 0 3.5)+ assertEqual "bankersRound 2 0.125 = 0.12 (half to even)" (0.12 :: MoneyDecimal) (bankersRound 2 0.125)+ let zb = 0 `EAI.Single` (Hat :< Yen) :: TestAlg -- zero value, base Yen+ rb = 5 .@ (Hat :< Amount) :: TestAlg -- real value, base Amount+ -- both fold directions of the singleton/singleton union+ assertEqual "union zero(.+)real keeps real value on its own base"+ rb (EA.proj [Hat :< Amount] (zb .+ rb))+ assertEqual "union real(.+)zero keeps real value on its own base"+ rb (EA.proj [Hat :< Amount] (rb .+ zb))+ -- the real value must NOT appear on the zero posting's base+ assertEqual "union zero(.+)real: nothing relabeled onto the zero's base"+ (EA.Zero :: TestAlg) (EA.proj [Hat :< Yen] (zb .+ rb))+ assertEqual "union real(.+)zero: nothing relabeled onto the zero's base"+ (EA.Zero :: TestAlg) (EA.proj [Hat :< Yen] (rb .+ zb))+ -- Definition 6 axioms (Double; semantic equality via exact per-base nets)+ quickProp "axiom: Hat involution (x^^ = x)" $+ forAll genAlgD $ \x -> netByBase ((.^) ((.^) x)) == netByBase x+ quickProp "axiom: scalar on singleton (a*(v:@b) = (a*v):@b)" $+ forAll genNNDouble $ \a -> forAll genNNDouble $ \v -> forAll genBase $ \b ->+ netByBase (a .* (v .@ b)) == netByBase (((a * v) .@ b) :: TestAlg)+ quickProp "axiom: scalar distributes over (.+)" $+ forAll genNNDouble $ \a -> forAll genAlgD $ \x -> forAll genAlgD $ \y ->+ netByBase (a .* (x .+ y)) == netByBase ((a .* x) .+ (a .* y))+ quickProp "axiom: norm additivity (norm(x+y) = norm x + norm y)" $+ forAll genAlgD $ \x -> forAll genAlgD $ \y ->+ epsEq (norm (x .+ y)) (norm x + norm y)+ quickProp "axiom: norm homogeneity (norm(a*x) = a*norm x, a>=0)" $+ forAll genNNDouble $ \a -> forAll genAlgD $ \x ->+ epsEq (norm (a .* x)) (a * norm x)+ -- derived lemmas+ quickProp "lemma: bar idempotent (bar(bar x) = bar x)" $+ forAll genAlgD $ \x -> netByBase (bar (bar x)) == netByBase (bar x)+ quickProp "lemma: zero identity (x .+ Zero = x)" $+ forAll genAlgD $ \x -> netByBase (x .+ EA.Zero) == netByBase x+ quickProp "lemma: (.+) associative" $+ forAll genAlgD $ \x -> forAll genAlgD $ \y -> forAll genAlgD $ \z ->+ netByBase ((x .+ y) .+ z) == netByBase (x .+ (y .+ z))+ -- regression: union must not relabel a value onto a zero posting's base+ -- (the 0.4.1.1 bug; raw (:@) so zero-valued singletons are exercised)+ quickProp "regression: union preserves per-base net (zero-base bug)" $+ forAll genNNDouble $ \v1 -> forAll genBase $ \b1 ->+ forAll genNNDouble $ \v2 -> forAll genBase $ \b2 ->+ let s1 = v1 `EAI.Single` b1 :: TestAlg+ s2 = v2 `EAI.Single` b2 :: TestAlg+ in netByBase (s1 .+ s2)+ == M.unionWith (+) (netByBase s1) (netByBase s2)+ -- construction-order independence for the exact value type (MoneyDecimal)+ quickProp "MoneyDecimal: fromList per-base net is construction-order independent" $+ forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->+ let singles = [ v .@ b | (v, b) <- ps ] :: [NNAlg]+ viaList = EA.fromList singles+ viaFoldr = foldr (.+) EA.Zero singles+ viaFoldl = L.foldl' (.+) EA.Zero singles+ in netByBase viaList == netByBase viaFoldr+ && netByBase viaFoldr == netByBase viaFoldl+ -- mapBasePart (Phase 3): identity + norm preservation (no value lost on collision)+ quickProp "mapBasePart id preserves per-base net (MoneyDecimal)" $+ forAll genAlgN $ \x -> netByBase (EA.mapBasePart id x :: NNAlg) == netByBase x+ quickProp "mapBasePart preserves norm under base collapse (MoneyDecimal)" $+ forAll genAlgN $ \x -> norm (EA.mapBasePart (const Amount) x :: NNAlg) == norm x+ -- S-4: functoriality of the base-relabel map pi_kappa (Prop 2.8(4)):+ -- mapBasePart (kappa' . kappa) x ~=_pi mapBasePart kappa' (mapBasePart kappa x)+ -- The two kappa are non-identity, non-injective relabelers on CountUnit so the+ -- composite collapses bases (Yen -> Dollar -> Amount), exercising the value+ -- merge on both sides. There is no dedicated ~=_pi comparator in this suite;+ -- we use 'netByBase' (per-base signed net), which is the same bar/order-robust+ -- observational equality the other mapBasePart / axiom properties use -- i.e.+ -- "equal after bar, compared per base". The 'kappa' relabels the BasePart+ -- (= CountUnit here), with mapBasePart re-merging colliding sides, so this is+ -- exactly the bar-then-map equivalence the audit note specifies.+ quickProp "S-4: mapBasePart is functorial (pi_{k'.k} ~=_pi pi_k' . pi_k, MoneyDecimal)" $+ forAll genAlgN $ \x ->+ let kappa, kappa' :: CountUnit -> CountUnit+ kappa u = if u == Yen then Dollar else u -- Yen -> Dollar+ kappa' u = if u == Dollar then Amount else u -- Dollar -> Amount+ lhs = EA.mapBasePart (kappa' . kappa) x :: NNAlg+ inner = EA.mapBasePart kappa x :: NNAlg+ rhs = EA.mapBasePart kappa' inner :: NNAlg+ in netByBase lhs == netByBase rhs+ -- netPairMapBy (ν_κ pair read-out): three properties from the+ -- easp-2026-06-11-netpairmapby handoff.+ -- (a) signed-diff consistency: balanceMapBy == n - h of the pair.+ -- n - h can be negative, so this is checked on the SIGNED value type+ -- (Double); a non-negative-only type would break the n-h component.+ quickProp "netPairMapBy: balanceMapBy x == n - h of netPairMapBy x (Double, signed)" $+ forAll genAlgD $ \x ->+ let bm = EA.balanceMapBy Just x :: M.Map CountUnit Double+ np = EA.netPairMapBy Just x :: M.Map CountUnit (Double, Double)+ diff = fmap (\(n, h) -> n - h) np+ -- balanceMapBy keeps zero-net keys; netPairMapBy drops them.+ -- Compare on the union: a key absent from one side reads as 0.+ in all (\k -> epsEq (M.findWithDefault 0 k bm)+ (M.findWithDefault 0 k diff))+ (M.keys bm ++ M.keys diff)+ -- (b) both pair components are non-negative (value-domain regularity).+ -- Exact value type so the >= 0 check has no tolerance ambiguity.+ quickProp "netPairMapBy: both components non-negative (MoneyDecimal)" $+ forAll genAlgN $ \x ->+ all (\(n, h) -> n >= 0 && h >= 0)+ (M.elems (EA.netPairMapBy Just x :: M.Map CountUnit (MoneyDecimal, MoneyDecimal)))+ -- (c) ~=_pi invariance: like the S-4 / netByBase observational equality,+ -- the pair read-out is construction-order independent (bar-then-net is+ -- robust to seq order and reassociation). Exact MoneyDecimal.+ quickProp "netPairMapBy: ~=_pi invariant (construction-order independent, MoneyDecimal)" $+ forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->+ let singles = [ v .@ b | (v, b) <- ps ] :: [NNAlg]+ viaList = EA.netPairMapBy Just (EA.fromList singles)+ viaFoldr = EA.netPairMapBy Just (foldr (.+) EA.Zero singles)+ viaFoldl = EA.netPairMapBy Just (L.foldl' (.+) EA.Zero singles)+ in viaList == (viaFoldr :: M.Map CountUnit (MoneyDecimal, MoneyDecimal))+ && viaFoldr == viaFoldl++-- ================================================================+-- Category-theory phase 1 laws and layer boundaries (P2c)+-- ================================================================++functorialityLawProperties :: IO ()+functorialityLawProperties = do+ quickProp "mapBasePart: identity holds through per-base multisets" $+ forAll genAlgN $ \x -> observe (EA.mapBasePart id x) == observe x+ quickProp "mapBasePart: composition holds through per-base multisets" $+ forAll genAlgN $ \x ->+ let f, g :: CountUnit -> CountUnit+ f u = if u == Yen || u == Dollar then Yen else u+ g u = if u == Yen || u == Amount then Amount else u+ in observe (EA.mapBasePart (g . f) x :: NNAlg)+ == observe (EA.mapBasePart g (EA.mapBasePart f x :: NNAlg) :: NNAlg)+ quickProp "mapBasePart: (.+) homomorphism holds through per-base multisets" $+ forAll genAlgN $ \x -> forAll genAlgN $ \y ->+ let f u = if u == Yen || u == Dollar then Amount else u+ in observe (EA.mapBasePart f (x .+ y) :: NNAlg)+ == observe ((EA.mapBasePart f x .+ EA.mapBasePart f y) :: NNAlg)+ quickProp "mapBasePart: norm is preserved" $+ forAll genAlgN $ \x ->+ norm (EA.mapBasePart (const Amount) x :: NNAlg) == norm x+ quickProp "mapBasePart: Hat commutes under raw Eq" $+ forAll genAlgN $ \x ->+ EA.mapBasePart (const Amount) ((.^) x)+ == ((.^) (EA.mapBasePart (const Amount) x) :: NNAlg)+ -- bar keeps the Liner constructor even when cancellation leaves one key+ -- with one value; mapBasePart rebuilds that map as a singleton. Eq treats+ -- the two constructors as distinct, although ℘ observes the same entry.+ let identitySource = (1 .@ (Not :< Yen))+ .+ (1 .@ (Hat :< Yen))+ .+ (2 .@ (Not :< Dollar)) :: NNAlg+ oneKeyLiner = bar identitySource+ identityMapped = EA.mapBasePart id oneKeyLiner :: NNAlg+ assertEqual "mapBasePart: identity counterexample has equal multisets"+ (observe oneKeyLiner) (observe identityMapped)+ assertEqual "mapBasePart: identity fails under raw Eq for one-key Liner"+ False (identityMapped == oneKeyLiner)++ let sandwichLeft = bar+ (EA.mapBasePart (const Amount) oneKeyLiner :: NNAlg)+ sandwichRight = bar+ (EA.mapBasePart (const Amount) identitySource :: NNAlg)+ assertEqual "mapBasePart: bar sandwich counterexample has equal multisets"+ (observe sandwichLeft) (observe sandwichRight)+ assertEqual "mapBasePart: bar sandwich can fail under raw Eq"+ False (sandwichLeft == sandwichRight)++ -- Three distinct source keys are read back in whatever order the source+ -- HashMap traverses them (a representation detail, so it is observed at+ -- run time rather than pinned). The first and last keys collide under f+ -- while the middle key survives as a separate intermediate key; g then+ -- merges everything. The two-pass route keeps the collision block+ -- contiguous, whereas the direct pass interleaves the middle key, so the+ -- raw Seq orders differ while the per-base multisets agree.+ let rawX = (10 .@ (Hat :< Yen))+ .+ (20 .@ (Hat :< Dollar))+ .+ (30 .@ (Hat :< Euro)) :: NNAlg+ sourceTraversal = EA.foldEntries+ (\acc _ (_ :< u) -> acc ++ [u]) [] rawX+ (firstU, lastU) = case sourceTraversal of+ [a, _, c] -> (a, c)+ other -> error ("P2c: unexpected source traversal " ++ show other)+ f u = if u == lastU then firstU else u+ g _ = Amount+ direct = EA.mapBasePart (g . f) rawX :: NNAlg+ staged = EA.mapBasePart g (EA.mapBasePart f rawX :: NNAlg) :: NNAlg+ assertEqual "mapBasePart: composition counterexample traverses three source keys"+ 3 (length sourceTraversal)+ assertEqual "mapBasePart: composition raw counterexample has equal multisets"+ (observe direct) (observe staged)+ assertEqual "mapBasePart: composition fails under raw Eq after collision"+ False (direct == staged)++ let barX = (100 .@ (Not :< Yen))+ .+ (100 .@ (Hat :< Dollar)) :: NNAlg+ mappedAfterBar = EA.mapBasePart (const Amount) (bar barX) :: NNAlg+ barAfterMapped = bar (EA.mapBasePart (const Amount) barX :: NNAlg)+ assertEqual "mapBasePart: does not commute with bar under collision"+ False (mappedAfterBar == barAfterMapped)+ assertEqual "mapBasePart: map after bar retains both source residuals"+ 200 (norm mappedAfterBar)+ assertEqual "mapBasePart: bar after map cancels collided residuals"+ 0 (norm barAfterMapped)++ quickProp "foldEntries: commutative sum is construction-order independent" $+ forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->+ let singles = [ v .@ b | (v, b) <- ps ] :: [NNAlg]+ pairwise = EA.fromList singles+ bulk = EA.sigma ps (\(v, b) -> v .@ b :: NNAlg)+ sumEntries = EA.foldEntries (\acc v _ -> acc + v) 0+ in sumEntries pairwise == sumEntries bulk+ let entries = [1 .@ (Hat :< Yen), 2 .@ (Hat :< Yen), 3 .@ (Hat :< Yen)]+ :: [NNAlg]+ pairwise = EA.fromList entries+ bulk = EA.sigma [1, 2, 3 :: Int]+ (\i -> fromIntegral i .@ (Hat :< Yen) :: NNAlg)+ collect = EA.foldEntries (\acc v _ -> acc ++ [v]) []+ assertEqual "foldEntries: non-commutative list append observes Seq order"+ False (collect pairwise == collect bulk)++ quickProp "postFromNetBy: definition equation" $+ forAll genAlgN $ \x ->+ let keyOf (_ :< u) = Just u+ post u v = v .@ (Not :< u) :: NNAlg+ collectEntry v b = (\k -> (k, v)) <$> keyOf b+ rhs = EA.sigmaFromMap+ (EA.foldEntriesToMap collectEntry (bar x)) post+ in EA.postFromNetBy keyOf post x == rhs+ quickProp "postFromNetBy: factors through bar" $+ forAll genAlgN $ \x ->+ let keyOf (_ :< u) = Just u+ post u v = v .@ (Not :< u) :: NNAlg+ in EA.postFromNetBy keyOf post x+ == EA.postFromNetBy keyOf post (bar x)+ quickProp "bar: idempotence holds under raw Eq" $+ forAll genAlgN $ \x -> bar (bar x) == bar x++ -- extendBy (0.5.1.0 A1): the free extension in the redundant layer.+ let dup :: MoneyDecimal -> HatBase CountUnit -> NNAlg+ dup v b = (v .@ b) .+ (v .@ b)+ relabel :: (CountUnit -> CountUnit) -> MoneyDecimal -> HatBase CountUnit -> NNAlg+ relabel f v b = v .@ EA.merge (EA.hat b) (f (EA.base b))+ quickProp "extendBy: (.+) homomorphism holds through per-base multisets" $+ forAll genAlgN $ \x -> forAll genAlgN $ \y ->+ observe (EA.extendBy dup (x .+ y) :: NNAlg)+ == observe ((EA.extendBy dup x .+ EA.extendBy dup y) :: NNAlg)+ quickProp "extendBy: (:@) is the unit through per-base multisets" $+ forAll genAlgN $ \x -> observe (EA.extendBy (.@) x :: NNAlg) == observe x+ quickProp "extendBy: mapBasePart is the relabelling special case" $+ forAll genAlgN $ \x ->+ observe (EA.mapBasePart (const Amount) x :: NNAlg)+ == observe (EA.extendBy (relabel (const Amount)) x :: NNAlg)+ quickProp "extendBy: norm is the sum of the substituted norms" $+ forAll genAlgN $ \x -> norm (EA.extendBy dup x :: NNAlg) == 2 * norm x+ quickProp "extendBy: substituting Zero everywhere yields Zero" $+ forAll genAlgN $ \x -> EA.isZero (EA.extendBy (\_ _ -> EA.Zero) x :: NNAlg)++ -- Raw counterexample for the unit law: a one-key Liner left by bar is+ -- rebuilt as a singleton (:@), which Eq distinguishes although ℘ agrees.+ let unitSource = (1 .@ (Not :< Yen))+ .+ (1 .@ (Hat :< Yen))+ .+ (2 .@ (Not :< Dollar)) :: NNAlg+ unitOneKey = bar unitSource+ unitRebuilt = EA.extendBy (.@) unitOneKey :: NNAlg+ assertEqual "extendBy: unit counterexample has equal multisets"+ (observe unitOneKey) (observe unitRebuilt)+ assertEqual "extendBy: unit fails under raw Eq for one-key Liner"+ False (unitRebuilt == unitOneKey)++-- ================================================================+-- Quotient decomposition properties (Phase 1, feat/quotient-decomposition)+--+-- Encodes the dec_κ / π_κ axioms of the scaling formalization+-- (agent-notes/drafts/scaling-formalization.md §2, §7) as QuickCheck+-- properties, plus fixed sentinels for the side-sensitive non-commutation+-- cases that MUST NOT silently start commuting (they encode a semantic+-- choice, not a bug).+-- ================================================================++-- proper classifier: factors through the base part (never sees Hat/Not)+properKf :: HatBase CountUnit -> Maybe CountUnit+properKf (_ :< u) = Just u++-- partial proper classifier: Yen entries fall into the residual+partialKf :: HatBase CountUnit -> Maybe CountUnit+partialKf (_ :< Yen) = Nothing+partialKf (_ :< u) = Just u++-- side-sensitive classifier: sees the Hat/Not state (like decP/decM)+sideKf :: HatBase CountUnit -> Maybe Bool+sideKf b = Just (isHat b)++-- residual of a partial classifier (reference implementation via filter)+residualOf :: (HatBase CountUnit -> Maybe CountUnit) -> NNAlg -> NNAlg+residualOf kf = EA.filter (\s -> s /= EA.Zero && kf (EA._hatBase s) == Nothing)++-- per-base nets with exact-zero entries dropped (bar drops zero-net bases,+-- so commutation properties are compared modulo zero nets)+nonZeroNet :: NNAlg -> M.Map CountUnit Rational+nonZeroNet = M.filter (/= 0) . netByBase++quotientProperties :: IO ()+quotientProperties = do+ -- reconstruction: Σ_k x_k (+ residual) = x (formalization Prop 2.3)+ quickProp "decBy: reconstruction, total classifier (MoneyDecimal)" $+ forAll genAlgN $ \x ->+ netByBase (mconcat (M.elems (EA.decBy properKf x))) == netByBase x+ quickProp "decBy: reconstruction with residual, partial classifier" $+ forAll genAlgN $ \x ->+ netByBase (mconcat (M.elems (EA.decBy partialKf x)) .+ residualOf partialKf x)+ == netByBase x+ -- norm additivity over classes (formalization Prop 2.4(1))+ quickProp "decBy: norm additivity over classes + residual (MoneyDecimal)" $+ forAll genAlgN $ \x ->+ norm x == L.foldl' (+) 0 (L.map norm (M.elems (EA.decBy partialKf x)))+ + norm (residualOf partialKf x)+ -- proper classifier commutes with bar componentwise (Prop 2.4(4))+ quickProp "decBy: bar commutes componentwise (proper classifier)" $+ forAll genAlgN $ \x ->+ M.filter (not . M.null) (M.map nonZeroNet (EA.decBy properKf (bar x)))+ == M.filter (not . M.null) (M.map (nonZeroNet . bar) (EA.decBy properKf x))+ -- decBy equals the naive per-class filter loop (semantics check)+ quickProp "decBy: equals naive per-class filter (MoneyDecimal)" $+ forAll genAlgN $ \x ->+ let d = EA.decBy properKf x+ naive k = EA.filter+ (\s -> s /= EA.Zero && properKf (EA._hatBase s) == Just k) x+ in all (\(k, alg) -> netByBase alg == netByBase (naive k)) (M.toList d)+ -- postFromNetBy equals an independent per-key projNetNorm pipeline+ quickProp "postFromNetBy: equals per-key projNetNorm reference (MoneyDecimal)" $+ forAll genAlgN $ \x ->+ let kf b = if isHat b then Just (unitOf b) else Nothing+ unitOf (_ :< u) = u+ post u v = v .@ (Not :< u) :: NNAlg+ viaApi = EA.postFromNetBy kf post x+ viaRef = mconcat+ [ post u s+ | u <- [Yen, Dollar, Amount]+ , let s = EA.projNetNorm [Hat :< u] (bar x)+ , s /= 0 ]+ in netByBase viaApi == netByBase viaRef+ -- decTo: flatten reconstructs and norm is preserved (total classifier)+ quickProp "decTo: toAlg . decTo reconstructs (total classifier, MoneyDecimal)" $+ forAll genAlgN $ \x ->+ let j = EJ.decTo (\(_ :< u) -> Just (show u)) x+ :: EJ.Journal String MoneyDecimal (HatBase CountUnit)+ in netByBase (EJ.toAlg j) == netByBase x && norm j == norm x+ -- sentinel: side-sensitive classifier does NOT commute with bar+ -- (decP/decM-style split; x = v:@Not:<Yen .+ v:@Hat:<Yen nets to zero+ -- globally but each side survives within its own class)+ let xCancel = (5 .@ (Not :< Yen)) .+ (5 .@ (Hat :< Yen)) :: NNAlg+ lhs = M.filter (not . EA.isZero) (M.map bar (EA.decBy sideKf xCancel))+ rhs = EA.decBy sideKf (bar xCancel)+ assertEqual "sentinel: side-sensitive decBy does not commute with bar"+ True (M.keys lhs /= M.keys rhs)+ -- sentinel: whichSide-style classifier is also side-sensitive+ -- (Cash homeSide = Debit, so Hat flips it to Credit: the two sides of one+ -- base land in different classes — Deguchi Def 2.13)+ let xCash = (100 .@ (Not :< Cash)) .+ (100 .@ (Hat :< Cash))+ :: EA.Alg MoneyDecimal (HatBase AccountTitles)+ bySide = EA.decBy (\b -> Just (whichSide b)) xCash+ assertEqual "sentinel: whichSide splits one base across classes (side-sensitive)"+ [Credit, Debit] (L.sort (M.keys bySide))+ assertEqual "sentinel: whichSide decBy does not commute with bar"+ True (M.filter (not . EA.isZero) (M.map bar bySide)+ /= EA.decBy (\b -> Just (whichSide b)) (bar xCash))+ -- sentinel: π_κ (mapBasePart, non-injective) does not commute with bar+ -- (formalization §2.8: coarsen-then-net /= net-then-coarsen)+ let xPi = (100 .@ (Not :< Yen)) .+ (100 .@ (Hat :< Dollar)) :: NNAlg+ assertEqual "sentinel: bar (mapBasePart const) nets across the class"+ 0 (norm (bar (EA.mapBasePart (const Amount) xPi :: NNAlg)))+ assertEqual "sentinel: mapBasePart (bar x) keeps both sides (no cross-base netting)"+ 200 (norm (EA.mapBasePart (const Amount) (bar xPi) :: NNAlg))++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testMapPosting+ testMapMaybePosting+ testBasesNotSideRegression+ testNumericToleranceScaleAware+ testSameBaseSeqOrderPathDependence+ testScalarRejectsNegative+ testProjConcreteNoIndexForce+ axiomProperties+ functorialityLawProperties+ quotientProperties
+ test/Algebra/ExactReadoutSpec.hs view
@@ -0,0 +1,372 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | Posting readouts against Rational oracles. These tests cover the rounding+-- boundaries of the algebra and journal contracts, including legacy tolerance.+module Algebra.ExactReadoutSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import Prelude hiding (map)+import Control.Monad (forM_, unless)+import qualified Data.Text as T+import qualified Data.List as List+import qualified Data.Sequence as Seq+import qualified Data.Map.Strict as Map+import qualified Data.HashMap.Strict as HashMap+import GHC.Float (castDoubleToWord64)+import Test.QuickCheck hiding (label)++import ExchangeAlgebra.Algebra+import ExchangeAlgebra.Algebra.Value (MoneyDecimal, MoneyDouble)+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Algebra.Internal as Internal+import qualified ExchangeAlgebra.Journal as Journal++-- | Complete base: an account title and an independent owner coordinate.+type Base = HatBase (AccountTitles, T.Text)++-- | Synthetic posting before construction, also used by the independent oracle.+type Row = (Double, Base)++-- | Exact signed residuals by complete base, before any rounding.+residuals :: [Row] -> Map.Map (AccountTitles, T.Text) Rational+residuals rows = Map.fromListWith (+)+ [(base b, signed value b) | (value, b) <- rows, value /= 0]+ where+ signed value b+ | isHat b = negate (toRational value)+ | otherwise = toRational value++-- | Construct without aggregating, using the public entry boundary.+ledger :: HatVal n => [(n, Base)] -> Alg n Base+ledger rows = EA.fromList [value .@ b | (value, b) <- rows]++-- | Stop at the first failed fixed observation.+check :: (Eq a, Show a) => String -> a -> a -> IO ()+check label expected actual = unless (expected == actual) $+ fail (label ++ ": expected " ++ show expected ++ ", got " ++ show actual)++-- | Include cent amounts, large integers, and widely separated binary exponents.+genRows :: Gen [Row]+genRows = resize 24 $ listOf $ do+ value <- frequency+ [ (5, (\n -> fromIntegral (n :: Int) / 100) <$> chooseInt (1, 100000))+ , (3, elements [0.1, 0.2, 0.3, 1, 2^53, 1e12, 0.5])+ , (2, elements [encodeFloat 1 (-1074), encodeFloat 1 (-1022), 1e100, 1e-100])+ ]+ side <- elements [Not, Hat]+ title <- elements [Cash, Sales]+ owner <- elements (List.map T.pack ["1", "2", "3", "4"])+ pure (value, side :< (title, owner))++-- | The key deliberately groups distinct complete bases and both accounting sides.+keyOf :: (AccountTitles, T.Text) -> Maybe Int+keyOf (_, owner) = Just (T.length owner)++-- | Round each output exactly once, following the distinct grouping boundaries.+readoutProperty :: Property+readoutProperty = forAll genRows $ \rows -> forAll (shuffle rows) $ \permuted ->+ let algebra = ledger rows+ reordered = ledger permuted+ nets = residuals rows+ gross = sum [toRational value | (value, _) <- rows]+ signedKeys = Map.fromListWith (+)+ [(T.length owner, value) | ((_, owner), value) <- Map.toList nets]+ pairs = Map.fromListWith (\(a,b) (c,d) -> (a+c,b+d))+ [(T.length owner, (max 0 value, max 0 (negate value)))+ | ((_, owner), value) <- Map.toList nets, value /= 0]+ roundedPairs = fmap (\(a,b) -> (fromRational a, fromRational b)) pairs+ surviving = Map.map fromRational (Map.filter (/= 0) nets)+ sides = Map.fromListWith (+)+ [((hat b, base b), toRational value) | (value,b) <- rows]+ compressed = Map.fromList+ [((hat b, base b), value) | (value,b) <- entries (compress algebra)]+ accounting = sum+ [case whichSide b of+ Credit -> toRational value+ Debit -> negate (toRational value)+ Side -> 0+ | (value,b) <- rows]+ expectedSide+ | accounting > 0 = Credit+ | accounting < 0 = Debit+ | otherwise = Side+ query = [HatNot :< (AccountTitle, T.empty)]+ selected = sum (List.map abs (Map.elems nets))+ plus = sum [value | ((side,_),value) <- Map.toList sides, side == Not]+ minus = sum [value | ((side,_),value) <- Map.toList sides, side == Hat]+ callback _ value = value .@ (Not :< (Cash,T.pack "1"))+ posted = EA.postFromNetBy (keyOf . base) callback algebra+ postedExpected = sum+ [toRational (fromRational value :: Double)+ | value <- Map.elems (Map.fromListWith (+)+ [(T.length owner, abs value) | ((_,owner),value) <- Map.toList nets+ , value /= 0])]+ in conjoin+ [ counterexample "norm" $ norm algebra === fromRational gross+ , counterexample "bar" $ EA.balanceMapBy Just (bar algebra) === surviving+ , counterexample "compress" $ Map.filter (/=0) compressed === fmap fromRational sides+ , counterexample "balanceMapBy" $ EA.balanceMapBy keyOf algebra === fmap fromRational signedKeys+ , counterexample "netPairMapBy" $ EA.netPairMapBy keyOf algebra === roundedPairs+ , counterexample "balance" $ balance algebra === (accounting == 0)+ , counterexample "diffRL" $ diffRL algebra === (expectedSide, fromRational (abs accounting))+ , counterexample "projection" $ EA.projNetNorm query algebra === fromRational selected+ , counterexample "balanceBy" $+ EA.balanceBy [Not :< (AccountTitle,T.empty)] [Hat :< (AccountTitle,T.empty)] algebra+ === fromRational (plus - minus)+ , counterexample "postFromNetBy" $ norm posted === fromRational postedExpected+ , counterexample "permutation" $ observations algebra === observations reordered+ ]+ where+ entries = EA.foldEntries (\out value b -> (value,b):out) []+ observations algebra =+ (castDoubleToWord64 (norm algebra), EA.balanceMapBy Just (bar algebra),+ List.sort (entries (compress algebra)), EA.balanceMapBy keyOf algebra,+ EA.netPairMapBy keyOf algebra, (balance algebra, diffRL algebra),+ (EA.projNetNorm [HatNot :< (AccountTitle,T.empty)] algebra,+ EA.balanceBy [Not :< (AccountTitle,T.empty)] [Hat :< (AccountTitle,T.empty)] algebra),+ EA.balanceMapBy Just (EA.postFromNetBy (keyOf . base)+ (\_ value -> value .@ (Not :< (Cash,T.pack "1"))) algebra))++-- | Note-local cancellation and global rounding are distinct from gathering notes.+journalProperty :: Property+journalProperty = forAll genRows $ \rows ->+ forAll (shuffle (zip (cycle ["a", "b", "c"]) rows)) $ \permuted ->+ let annotated = zip (cycle ["a", "b", "c"]) rows+ journal = Journal.fromList [value .@ b Journal..| note | (note,(value,b)) <- annotated]+ reordered = Journal.fromList [value .@ b Journal..| note | (note,(value,b)) <- permuted]+ byNote = Map.fromListWith (++) [(note, [row]) | (note,row) <- annotated]+ magnitude = sum [sum (List.map abs (Map.elems (residuals rs))) | rs <- Map.elems byNote]+ chosen = sum [sum (List.map abs (Map.elems (residuals rs)))+ | (note,rs) <- Map.toList byNote, note /= "c"]+ query = [HatNot :< (AccountTitle,T.empty)]+ callback key value = value .@ (Not :< (Cash,T.pack (show key))) Journal..| "callback"+ output = Journal.postFromNetBy (keyOf . base) callback journal+ gross = sum [toRational value | (value,_) <- rows]+ nets = residuals rows+ accounting = sum+ [case whichSide b of+ Credit -> toRational value+ Debit -> negate (toRational value)+ Side -> 0+ | (value,b) <- rows]+ expectedSide+ | accounting > 0 = Credit+ | accounting < 0 = Debit+ | otherwise = Side+ posted = Map.fromList+ [((Cash,T.pack (show key)), fromRational value)+ | (key,value) <- Map.toList (Map.fromListWith (+)+ [(T.length owner, abs value) | ((_,owner),value) <- Map.toList nets+ , value /= 0])]+ compressed = fmap (fmap fromRational . Map.fromListWith (+) . List.map+ (\(value,b) -> ((hat b,base b), toRational value))) byNote+ observed = EA.foldEntries (\out value b ->+ Map.insert (base b) (if isHat b then negate value else value) out) Map.empty+ in conjoin+ [ norm journal === fromRational gross+ , observed (Journal.toAlg (bar journal))+ === fmap fromRational (Map.filter (/=0) nets)+ , balance journal === (accounting == 0)+ , diffRL journal === (expectedSide, fromRational (abs accounting))+ , Journal.projWithBaseNetNorm query journal === fromRational magnitude+ , Journal.projWithNoteBaseNetNorm ["a","b","a"] query journal === fromRational chosen+ , observed (Journal.toAlg output) === posted+ , counterexample "journal permutation" $ observations journal === observations reordered+ , fmap (EA.foldEntries (\out value b -> Map.insert (hat b,base b) value out) Map.empty)+ (Map.fromList (HashMap.toList (Journal.toMap (compress journal)))) === compressed+ ]++ where+ observations journal =+ (norm journal, EA.balanceMapBy Just (Journal.toAlg (bar journal)),+ fmap (EA.foldEntries (\out value b -> Map.insert (hat b,base b) value out) Map.empty)+ (Journal.toMap (compress journal)),+ (balance journal,diffRL journal),+ Journal.projWithBaseNetNorm [HatNot :< (AccountTitle,T.empty)] journal,+ Journal.projWithNoteBaseNetNorm ["a","b","a"] [HatNot :< (AccountTitle,T.empty)] journal,+ Journal.toMap (Journal.postFromNetBy (keyOf . base)+ (\key value -> value .@ (Not :< (Cash,T.pack (show key))) Journal..| "callback") journal))++-- | Compare classifications and exact finite residuals, then the stored sequences.+pairOrderProperty :: Property+pairOrderProperty = forAll genPair $ \(hats,nots) ->+ forAll genPair $ \(otherHats,otherNots) ->+ let oracle hs ns+ | any isNaN (hs ++ ns) || (any isInfinite hs && any isInfinite ns) = (3 :: Int, 0)+ | any isInfinite hs = (2, 0)+ | any isInfinite ns = (0, 0)+ | otherwise = (1, sum (List.map toRational hs) - sum (List.map toRational ns))+ first = Internal.Pair (Seq.fromList hats) (Seq.fromList nots)+ second = Internal.Pair (Seq.fromList otherHats) (Seq.fromList otherNots)+ expected = compare (oracle hats nots) (oracle otherHats otherNots)+ <> compare (hats,nots) (otherHats,otherNots)+ in compare first second === expected+ where+ genPair = do+ rows <- genRows+ let hats = [value | (value,b) <- rows, isHat b]+ nots = [value | (value,b) <- rows, not (isHat b)]+ elements [(hats,nots), (1/0:hats,nots), (hats,1/0:nots),+ (1/0:hats,1/0:nots), (0/0:hats,nots)]++-- | Fixed binary64 boundaries and the six migration examples.+fixedCases :: IO ()+fixedCases = do+ let t = 2^(53 :: Int) :: Double+ maximumFinite = encodeFloat (2^(53 :: Int)-1) 971 :: Double+ tiny = encodeFloat 1 (-1074) :: Double+ a = Not :< (Cash,T.pack "1")+ ah = Hat :< (Cash,T.pack "1")+ b = Not :< (Cash,T.pack "2")+ bh = Hat :< (Cash,T.pack "2")+ allBases = [HatNot :< (AccountTitle,T.empty)]+ cents = ledger [(0.1,a),(0.2,a),(0.3,ah)] :: Alg Double Base+ centsOracle = toRational (0.1 :: Double) + toRational (0.2 :: Double)+ - toRational (0.3 :: Double)+ check "example 1: binary decimal residual" (fromRational centsOracle) (norm (bar cents))+ check "example 2: genuine small residual" (0.5 :: Double)+ (norm (bar (ledger [(1e12,a),(0.5,a),(1e12,ah)])))+ let x = ledger [(t,a),(1,a)]+ y = ledger [(1,b)]+ check "example 3: one gross rounding" (t+2) (norm (x .+ y))+ check "example 3: separately rounded norms" t (norm x + norm y)+ check "example 4: unrounded projection" (t+2) (EA.projNetNorm allBases (x .+ y))+ check "example 4: rounded bar first" t (norm (bar (x .+ y)))+ let lost = ledger [(t,a),(1,a),(t,ah)]+ check "example 5: original net" 1 (norm (bar lost))+ check "example 5: compress loses net" 0 (norm (bar (compress lost)))+ -- Example 6 observes the acceptance boundary through balance. The separate+ -- validation layer is intentionally outside this work item's write scope.+ check "example 6: rounded equality is insufficient" False (balance lost)+ check "example 6: legacy tolerance" True (balanceWithin defaultTolerance lost)+ forM_ [ [(maximumFinite,a),(maximumFinite,a),(maximumFinite,ah)]+ , [(tiny,a),(tiny,a),(tiny,ah)]+ , [(1e100,a),(1,a),(1e100,ah)]+ ] $ \rows -> do+ let expected = fromRational (sum (Map.elems (residuals rows)))+ forM_ (List.permutations rows) $ \order ->+ check "boundary permutations" expected (norm (bar (ledger order)))+ check "positive zero" 0 (castDoubleToWord64 (norm ((-0) `Internal.Single` a)))+ let grouped = ledger [(t,a),(1,a),(t,bh)]+ exactN = toRational t + 1+ exactH = toRational t+ check "unrounded key identity" 1 (exactN - exactH)+ check "Double signed key" (Map.singleton () 1) (EA.balanceMapBy (const (Just ())) grouped)+ check "Double rounded pair" (Map.singleton () (t,t)) (EA.netPairMapBy (const (Just ())) grouped)+ let decimal = ledger [(2^53,a),(1,a),(2^53,bh)] :: Alg MoneyDecimal Base+ check "MoneyDecimal signed oracle" (Map.singleton () (fromRational (exactN-exactH)))+ (EA.balanceMapBy (const (Just ())) decimal)+ check "MoneyDecimal pair-difference oracle" (Map.singleton () (fromRational (exactN-exactH)))+ (fmap (uncurry (-)) (EA.netPairMapBy (const (Just ())) decimal))+ let first = x .+ y+ second = ledger [(t,a),(1,b)]+ post _ value = value .@ a+ check "same rounded bar" (bar first) (bar second)+ check "callback first" (t+2) (norm (EA.postFromNetBy (const (Just ())) post first))+ check "callback second" t (norm (EA.postFromNetBy (const (Just ())) post second))+ check "Rational first callback oracle"+ (fromRational (sum (List.map abs (Map.elems (residuals [(t,a),(1,a),(1,b)])))))+ (norm (EA.postFromNetBy (const (Just ())) post first))+ check "Rational second callback oracle"+ (fromRational (sum (List.map abs (Map.elems (residuals [(t,a),(1,b)])))))+ (norm (EA.postFromNetBy (const (Just ())) post second))+ let decimalFirst = ledger [(2^53,a),(1,a),(1,b)] :: Alg MoneyDecimal Base+ decimalSecond = ledger [(2^53,a),(1,b)] :: Alg MoneyDecimal Base+ check "MoneyDecimal first callback" (2^53+2)+ (norm (EA.postFromNetBy (const (Just ())) (\_ value -> value .@ a) decimalFirst))+ check "MoneyDecimal second callback" (2^53+1)+ (norm (EA.postFromNetBy (const (Just ())) (\_ value -> value .@ a) decimalSecond))+ let money = ledger [(0.1,a),(0.2,a),(0.3,ah)] :: Alg MoneyDouble Base+ check "MoneyDouble rounds like Double" (fromRational centsOracle) (norm (bar money))+ let infinity = 1/0 :: Double+ special = EA.fromList [infinity `Internal.Single` a, 1 `Internal.Single` a,+ infinity `Internal.Single` ah]+ specialValues = EA.vals (bar special)+ check "nonfinite bar retains postings" 3 (length specialValues)+ check "nonfinite balance" False (balance special)+ check "nonfinite norm" True (isInfinite (norm special))+ case diffRL special of+ (Side, value) -> check "NaN diffRL" True (isNaN value)+ result -> fail ("unexpected nonfinite direction: " ++ show result)+ case Map.lookup () (EA.netPairMapBy (const (Just ())) special) of+ Just (notValue, hatValue) -> check "NaN pair" True+ (isNaN notValue && isNaN hatValue)+ Nothing -> fail "missing NaN key"+ check "nonfinite callback bypass" 3+ (length (EA.vals (EA.postFromNetBy (const (Just ()))+ (\_ _ -> error "nonfinite callback invoked") special)))+ let pair :: [Double] -> [Double] -> Internal.Pair Double+ pair hats nots = Internal.Pair (Seq.fromList hats) (Seq.fromList nots)+ check "Pair exact net before structural tie" GT+ (compare (pair [1,t] []) (pair [t] []))+ check "Pair equal net uses structure" (compare ([1,2] :: [Double]) [3])+ (compare (pair [1,2] []) (pair [3] []))+ check "Pair finite overflow below infinity" LT+ (compare (pair [maximumFinite,maximumFinite] []) (pair [infinity] []))+ check "Pair negative infinity below finite" LT+ (compare (pair [] [infinity]) (pair [] [maximumFinite,maximumFinite]))+ check "Pair NaN above positive infinity" GT+ (compare (pair [infinity] [infinity]) (pair [infinity] []))+ let classify value+ | isNaN value = "NaN"+ | value == infinity = "+Infinity"+ | value == negate infinity = "-Infinity"+ | otherwise = show value+ forM_ [(infinity,a,Debit,"+Infinity",("+Infinity","0.0")),+ (infinity,ah,Credit,"-Infinity",("0.0","+Infinity")),+ (0/0,a,Side,"NaN",("NaN","NaN"))] $+ \(value,postingBase,direction,signed,pairValues) -> do+ let input = value `Internal.Single` postingBase+ (actualDirection,actualDifference) = diffRL input+ check "nonfinite diffRL direction" direction actualDirection+ check "nonfinite diffRL magnitude" (if isNaN value then "NaN" else "+Infinity")+ (classify actualDifference)+ check "nonfinite net pair side" (Map.singleton () pairValues)+ (fmap (\(n,h) -> (classify n,classify h)) (EA.netPairMapBy (const (Just ())) input))+ check "nonfinite signed key" (Map.singleton () signed)+ (fmap classify (EA.balanceMapBy (const (Just ())) input))+ check "nonfinite projection" (classify value) (classify (EA.projNetNorm allBases input))+ check "nonfinite compress" [classify value] (List.map classify (EA.vals (compress input)))+ check "compress rounds nonfinite sides independently" ["+Infinity","+Infinity"]+ (List.map classify (EA.vals (compress special)))+ check "opposite infinities signed key" (Map.singleton () "NaN")+ (fmap classify (EA.balanceMapBy (const (Just ())) special))+ check "opposite infinities projection" "NaN" (classify (EA.projNetNorm allBases special))+ let specialJournal = (infinity `Internal.Single` a Journal..| "first")+ .+ (1 .@ a Journal..| "second")+ .+ (infinity `Internal.Single` ah Journal..| "third")+ .+ (2 .@ b Journal..| "first")+ journalOutput = Journal.postFromNetBy (const (Just ()))+ (\_ value -> value .@ b Journal..| "callback") specialJournal+ check "Journal preserves nonfinite bases and original notes"+ (Map.fromList [("first",[("+Infinity",a)]), ("second",[("1.0",a)]),+ ("third",[("+Infinity",ah)]), ("callback",[("2.0",b)])])+ (Map.fromList [(note, List.sort (EA.foldEntries (\out value postingBase ->+ (classify value,postingBase):out) [] alg))+ | (note,alg) <- HashMap.toList (Journal.toMap journalOutput)])+ -- Golden values obtained from the Hackage 0.5.3.0 package, not the new bar.+ forM_ [(cents, [], True, (Side,0)),+ (ledger [(1e12,a),(0.5,a),(1e12,ah)], [], True, (Side,0)),+ (ledger [(12,a),(3,ah)], [(Not,9)], False, (Debit,9)),+ (ledger [(3,a),(12,ah)], [(Hat,9)], False, (Credit,9)),+ (ledger [(1e10,a),(0.1,a),(0.2,a),(1e10,ah),(0.3,ah)], [], True, (Side,0))]+ $ \(input,expected,expectedBalance,expectedDifference) -> do+ let observe = EA.foldEntries (\rows value postingBase -> (hat postingBase,value):rows) []+ check "W1 Alg golden" expected (observe (barWithin defaultTolerance input))+ check "W1 Journal golden" expected+ (observe (Journal.toAlg (barWithin defaultTolerance (input Journal..| ("golden" :: String)))))+ check "W1 Alg balance golden" expectedBalance (balanceWithin defaultTolerance input)+ check "W1 Alg difference golden" expectedDifference (diffRLWithin defaultTolerance input)+ check "W1 Journal balance golden" expectedBalance+ (balanceWithin defaultTolerance (input Journal..| ("golden" :: String)))+ check "W1 Journal difference golden" expectedDifference+ (diffRLWithin defaultTolerance (input Journal..| ("golden" :: String)))++-- | Run the requested properties and fixed migration cases without tolerances.+runTests :: IO ()+runTests = do+ fixedCases+ forM_ [readoutProperty, journalProperty, pairOrderProperty] $ \propertyToRun -> do+ result <- quickCheckWithResult stdArgs {maxSuccess = 200} propertyToRun+ unless (isSuccess result) (fail "exact readout property failed")
test/Algebra/ExactSumSpec.hs view
@@ -1,26 +1,41 @@ {-# LANGUAGE ScopedTypeVariables #-} --- | Rational-oracle and bit-level acceptance tests for checked exact readouts.+-- | Rational-oracle and bit-level acceptance tests for exact readouts. module Algebra.ExactSumSpec (runTests) where +import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange import Control.Monad (unless)+import Control.Exception (SomeException, evaluate, try) import qualified Data.Decimal as Decimal import qualified Data.List as List import qualified Data.Map.Strict as Map import Data.Word (Word64) import GHC.Float (castDoubleToWord64)-import qualified Number.NonNegative as NN import System.Exit (exitFailure) import Test.QuickCheck hiding (collect) import ExchangeAlgebra.Algebra.Base hiding (equal)-import ExchangeAlgebra.Algebra (Alg, (.@))+import ExchangeAlgebra.Algebra+ ( Alg+ , (.@)+ , norm+ , bar+ , balanceMapBy+ , netPairMapBy+ , postFromNetBy+ , projNetNorm+ ) import qualified ExchangeAlgebra.Algebra as Algebra-import ExchangeAlgebra.Algebra.Exact import qualified ExchangeAlgebra.Journal as Journal-import qualified ExchangeAlgebra.Journal.Exact as JournalExact-import ExchangeAlgebra.TrialBalance.Balance (AccountBalance(..))-import ExchangeAlgebra.Value (MoneyDecimal(..), MoneyDouble(..))+import ExchangeAlgebra.Accounting.TrialBalance.Balance (AccountBalance(..), accountBalances)+import ExchangeAlgebra.Algebra.Value+ ( HatVal(..)+ , sumValues+ , MoneyDecimal(..)+ , MoneyDouble(..)+ ) -- | Concrete complete bases with no wildcard ordering in the oracle. type TestBase = HatBase AccountTitles@@ -44,17 +59,15 @@ threshold = 2 ^ (53 :: Int) -- | Compare successful outputs by all 64 bits, including the sign of zero.-bits :: Either ExactSumError Double -> Either ExactSumError Word64-bits = fmap castDoubleToWord64+bits :: Double -> Word64+bits = castDoubleToWord64 --- | Exact reference with a range check before nearest-even conversion.-oracle :: Rational -> Either ExactSumError Double-oracle value- | value > toRational maximumFinite = Left SumOutOfRange- | otherwise = Right (fromRational value)+-- | Independent nearest-even conversion, including IEEE overflow.+oracle :: Rational -> Double+oracle = fromRational -- | Build an accumulator without rounding its intermediate states.-accumulate :: ExactSum n => [n] -> Accum n+accumulate :: HatVal n => [n] -> Accum n accumulate = List.foldl' (flip addAccum) emptyAccum -- | Generate finite non-negative inputs across all binary64 exponent ranges.@@ -83,8 +96,8 @@ middleState = accumulate middle lastState = accumulate lastPart in conjoin- [ bits (sumExact values) === expected- , bits (sumExact permuted) === expected+ [ bits (sumValues values) === expected+ , bits (sumValues permuted) === expected , bits (roundAccum (mergeAccum first second)) === expected , bits (roundAccum (mergeAccum second first)) === expected , bits (roundAccum (mergeAccum emptyAccum (mergeAccum first second))) === expected@@ -93,30 +106,32 @@ , bits (roundAccum (mergeAccum (mergeAccum first middleState) lastState)) === expected ] --- | Netting checks both exact side ranges before subtracting, without tolerance.+-- | Subtraction rounds the exact signed difference, including overflowing sides. propNet :: Property propNet = forAll genValues $ \left -> forAll genValues $ \right -> let first = sum (fmap toRational left) second = sum (fmap toRational right)- expected- | max first second > toRational maximumFinite = Left SumOutOfRange- | otherwise = (,) (compare first second) <$> oracle (abs (first - second))- observe = fmap (\(direction, value) -> (direction, castDoubleToWord64 value))- in observe (netAccum (accumulate left) (accumulate right)) === observe expected+ difference = mergeAccum (accumulate left) (negateAccum (accumulate right))+ in bits (roundAccum difference) === bits (oracle (first - second)) --- | Merging signed residual expansions preserves their exact non-negative values.+-- | Merging residual states preserves their exact non-negative values. propResidualMerge :: Property propResidualMerge = forAll genValue $ \a -> forAll genValue $ \b -> forAll genValue $ \c -> forAll genValue $ \d ->- let result = do- (_, first) <- netAccumState (accumulate [a]) (accumulate [b])- (_, second) <- netAccumState (accumulate [c]) (accumulate [d])- let left = mergeAccum first second- right = mergeAccum second first- pure (bits (roundAccum left), bits (roundAccum right))+ let firstSigned = mergeAccum (accumulate [a]) (negateAccum (accumulate [b]))+ secondSigned = mergeAccum (accumulate [c]) (negateAccum (accumulate [d]))+ first = case signAccum firstSigned of+ Just LT -> negateAccum firstSigned+ _ -> firstSigned+ second = case signAccum secondSigned of+ Just LT -> negateAccum secondSigned+ _ -> secondSigned expected = bits (oracle (abs (toRational a - toRational b) + abs (toRational c - toRational d)))- in result === Right (expected, expected)+ in conjoin+ [ bits (roundAccum (mergeAccum first second)) === expected+ , bits (roundAccum (mergeAccum second first)) === expected+ ] -- | Bias toward high-exponent ties that create a negative low partial. genBoundarySide :: Gen [Double]@@ -128,28 +143,30 @@ pure [encodeFloat high 970, encodeFloat low 970]) ] --- | Both subtraction directions must stay finite for valid near-boundary states.+-- | Both subtraction directions stay finite for near-boundary states. propBoundaryNet :: Property propBoundaryNet = forAll genBoundarySide $ \left -> forAll genBoundarySide $ \right -> let first = sum (fmap toRational left) second = sum (fmap toRational right)- observe = fmap (\(direction, value) -> (direction, castDoubleToWord64 value))- expected = (,) (compare first second) <$> oracle (abs (first - second))- in observe (netAccum (accumulate left) (accumulate right)) === observe expected+ difference = mergeAccum (accumulate left) (negateAccum (accumulate right))+ in bits (roundAccum difference) === bits (oracle (first - second)) --- | Subtraction also accepts previously netted states with signed low components.+-- | Subtraction also accepts previously netted residual states. propNestedNet :: Property propNestedNet = forAll genValue $ \a -> forAll genValue $ \b -> forAll genValue $ \c -> forAll genValue $ \d -> let first = abs (toRational a - toRational b) second = abs (toRational c - toRational d)- result = do- (_, left) <- netAccumState (accumulate [a]) (accumulate [b])- (_, right) <- netAccumState (accumulate [c]) (accumulate [d])- netAccum left right- expected = (,) (compare first second) <$> oracle (abs (first - second))- observe = fmap (\(direction, value) -> (direction, castDoubleToWord64 value))- in observe result === observe expected+ leftSigned = mergeAccum (accumulate [a]) (negateAccum (accumulate [b]))+ rightSigned = mergeAccum (accumulate [c]) (negateAccum (accumulate [d]))+ left = case signAccum leftSigned of+ Just LT -> negateAccum leftSigned+ _ -> leftSigned+ right = case signAccum rightSigned of+ Just LT -> negateAccum rightSigned+ _ -> rightSigned+ difference = mergeAccum left (negateAccum right)+ in bits (roundAccum difference) === bits (oracle (first - second)) -- | Generate small ledgers with exact, independently calculable group residuals. genEntries :: Gen [(Double, TestBase)]@@ -187,19 +204,19 @@ reordered = mconcat (fmap (uncurry (.@)) permuted) gross = sum (fmap (toRational . fst) entries) residual = sum (fmap abs (Map.elems groups))- expectedBalances = traverse (\value ->- (,) (compare value 0) <$> oracle (abs value)) groups- expectedPair = (,)- <$> oracle (sum [value | value <- Map.elems groups, value > 0])- <*> oracle (sum [abs value | value <- Map.elems groups, value < 0])- pairs = netPairMapByExact (const (Just ())) original- actualPair = fmap (Map.findWithDefault (0, 0) ()) pairs+ expectedBalances = fmap oracle groups+ expectedPair =+ ( oracle (sum [value | value <- Map.elems groups, value > 0])+ , oracle (sum [abs value | value <- Map.elems groups, value < 0])+ )+ pairs = netPairMapBy (const (Just ())) original+ actualPair = Map.findWithDefault (0, 0) () pairs queries = [HatNot :< wildcard] in conjoin- [ fmap observeAlg (barExact original) === fmap observeAlg (barExact reordered)- , bits (normExact original) === bits (oracle gross)- , bits (projNetNormExact queries original) === bits (oracle residual)- , balanceMapByExact Just original === expectedBalances+ [ observeAlg (bar original) === observeAlg (bar reordered)+ , bits (norm original) === bits (oracle gross)+ , bits (projNetNorm queries original) === bits (oracle residual)+ , balanceMapBy Just original === expectedBalances , actualPair === expectedPair ] @@ -220,28 +237,26 @@ in forAll (shuffle annotated) $ \permuted -> let reordered = Journal.fromList (fmap make permuted) in conjoin- [ bits (JournalExact.projNetNormExact query original) === expected- , bits (JournalExact.projNetNormExact query reordered) === expected- , JournalExact.balanceMapByExact Just reordered- === JournalExact.balanceMapByExact Just original- , JournalExact.accountBalancesExact reordered- === JournalExact.accountBalancesExact original+ [ bits (Journal.projWithBaseNetNorm query original) === expected+ , bits (Journal.projWithBaseNetNorm query reordered) === expected+ , balanceMapBy Just (Journal.toAlg reordered)+ === balanceMapBy Just (Journal.toAlg original)+ , accountBalances (Journal.toAlg reordered)+ === accountBalances (Journal.toAlg original) ] -- | Handwritten wrappers preserve the Double oracle; Decimal merge is exact. propInstances :: Property-propInstances = forAll genValues $ \values -> forAll (shuffle values) $ \permuted ->- let expected = bits (sumExact values)+propInstances = forAll genValues $ \values ->+ let expected = bits (sumValues values) money = fmap MoneyDouble values- nonNegative = fmap NN.fromNumber permuted :: [NN.Double] unwrapMoney (MoneyDouble value) = value decimalValues = fmap (MoneyDecimal . Decimal.Decimal 2 . toInteger) [1 .. length values] decimalState = accumulate decimalValues in conjoin- [ bits (fmap unwrapMoney (sumExact money)) === expected- , bits (fmap NN.toNumber (sumExact nonNegative)) === expected- , roundAccum (mergeAccum decimalState emptyAccum) === Right (sum decimalValues)- , sumExact (reverse decimalValues) === Right (sum decimalValues)+ [ bits (unwrapMoney (sumValues money)) === expected+ , roundAccum (mergeAccum decimalState emptyAccum) === sum decimalValues+ , sumValues (reverse decimalValues) === sum decimalValues ] -- | Run a deterministic check and stop the suite on failure.@@ -259,26 +274,29 @@ exitFailure putStrLn ("[PASS] exact " ++ name ++ " (1000 cases)") --- | Floating boundaries and sticky validation errors, independent of algebra construction.+-- | Floating boundaries, exact cancellation, and invalid posting rejection. testAccumulator :: IO () testAccumulator = do- check "2^53,1,1" (Right (threshold + 2)) (sumExact [threshold, 1, 1])- check "subnormals" (Right (3 * subnormal)) (sumExact [subnormal, subnormal, subnormal])- check "tie even down" (Right threshold) (sumExact [threshold, 1])- check "tie even up" (Right (threshold + 4)) (sumExact [threshold + 2, 1])- check "beyond tie" (Right (threshold + 2)) (sumExact [threshold, 1, subnormal])- check "net below tie" (Right (GT, threshold))- (netAccum (accumulate [threshold + 2]) (accumulate [1, subnormal]))- check "zero sign" (Right 0) (bits (sumExact [-0, 0 :: Double]))- check "net zero sign" (Right (EQ, 0))- (fmap (\(direction, value) -> (direction, castDoubleToWord64 value))- (netAccum (accumulate [-0 :: Double]) (accumulate [0])))- check "M" (Right maximumFinite) (sumExact [maximumFinite])- check "M boundary inside" (Right maximumFinite)- (sumExact [encodeFloat (2 ^ (53 :: Int) - 2) 971, encodeFloat 1 971])- check "M plus smallest" (Left SumOutOfRange) (sumExact [maximumFinite, subnormal])- check "both sides overflow" (Left SumOutOfRange)- (netAccum (accumulate [1e308, 1e308 :: Double]) (accumulate [1e308, 1e308]))+ check "2^53,1,1" (threshold + 2) (sumValues [threshold, 1, 1])+ check "subnormals" (3 * subnormal) (sumValues [subnormal, subnormal, subnormal])+ check "tie even down" threshold (sumValues [threshold, 1])+ check "tie even up" (threshold + 4) (sumValues [threshold + 2, 1])+ check "beyond tie" (threshold + 2) (sumValues [threshold, 1, subnormal])+ check "net below tie" threshold+ (roundAccum (mergeAccum (accumulate [threshold + 2])+ (negateAccum (accumulate [1, subnormal]))))+ check "zero sign" 0 (bits (sumValues [-0, 0 :: Double]))+ check "net zero sign" 0+ (bits (roundAccum (mergeAccum (accumulate [-0 :: Double])+ (negateAccum (accumulate [0])))))+ check "M" maximumFinite (sumValues [maximumFinite])+ check "M boundary inside" maximumFinite+ (sumValues [encodeFloat (2 ^ (53 :: Int) - 2) 971, encodeFloat 1 971])+ check "M plus smallest rounds M" maximumFinite (sumValues [maximumFinite, subnormal])+ check "both sides overflow but cancel" 0+ (roundAccum (mergeAccum (accumulate [1e308, 1e308 :: Double])+ (negateAccum (accumulate [1e308, 1e308]))))+ check "overflow rounds to infinity" True (isInfinite (sumValues [maximumFinite, maximumFinite])) let boundaryValues = [encodeFloat 1 1023, 3 * encodeFloat 1 970] boundaryState = accumulate boundaryValues expectedDifference = fromRational@@ -286,48 +304,47 @@ boundaryLedger = build [(value, Not :< Cash) | value <- boundaryValues] <> build [(maximumFinite, Hat :< Cash)]- check "negative low versus M" (Right (LT, expectedDifference))- (netAccum boundaryState (accumulate [maximumFinite]))- check "M versus negative low" (Right (GT, expectedDifference))- (netAccum (accumulate [maximumFinite]) boundaryState)- check "boundary projection finite" (Right expectedDifference)- (projNetNormExact [HatNot :< Cash] boundaryLedger)- check "boundary bar does not throw" (Right expectedDifference)- (barExact boundaryLedger >>= normExact)- check "negative" (Left NegativeInput) (sumExact [-1 :: Double])- check "NaN" (Left NonFiniteInput) (sumExact [0 / 0 :: Double])- check "positive infinity" (Left NonFiniteInput) (sumExact [1 / 0 :: Double])- check "negative infinity" (Left NonFiniteInput) (sumExact [-1 / 0 :: Double])- let failed = accumulate [-1 :: Double]- check "sticky add" (Left NegativeInput) (roundAccum (addAccum 1 failed))- check "sticky merge left" (Left NegativeInput) (roundAccum (mergeAccum failed emptyAccum))- check "sticky merge right" (Left NegativeInput) (roundAccum (mergeAccum emptyAccum failed))- check "sticky net" (Left NegativeInput) (netAccum failed failed)+ check "negative low versus M" (negate expectedDifference)+ (roundAccum (mergeAccum boundaryState (negateAccum (accumulate [maximumFinite]))))+ check "M versus negative low" expectedDifference+ (roundAccum (mergeAccum (accumulate [maximumFinite]) (negateAccum boundaryState)))+ check "boundary projection finite" expectedDifference+ (projNetNorm [HatNot :< Cash] boundaryLedger)+ check "boundary bar does not throw" expectedDifference (norm (bar boundaryLedger))+ mapM_ (\value -> do+ result <- try (evaluate (norm (value .@ (Not :< Cash) :: TestAlg)))+ :: IO (Either SomeException Double)+ case result of+ Left _ -> pure ()+ Right _ -> fail "invalid posting accepted") [-1, 0 / 0, 1 / 0, -1 / 0]+ mapM_ (\value -> do+ result <- try (evaluate (norm (MoneyDouble value .@ (Not :< Cash)+ :: Alg MoneyDouble TestBase))) :: IO (Either SomeException MoneyDouble)+ case result of+ Left _ -> pure ()+ Right _ -> fail "invalid MoneyDouble posting accepted") [-1, 1 / 0]+ decimalResult <- try (evaluate (norm ((-1 :: MoneyDecimal) .@ (Not :< Cash))))+ :: IO (Either SomeException MoneyDecimal)+ case decimalResult of+ Left _ -> pure ()+ Right _ -> fail "negative MoneyDecimal posting accepted" let nearMaximum = accumulate [maximumFinite] tiny = accumulate [subnormal]- case netAccumState nearMaximum tiny of- Left failure -> check "M-small state" (Right ()) (Left failure)- Right (_, below) -> do- check "M-small rounds M" (Right maximumFinite) (roundAccum below)- check "M-small plus small" (Right maximumFinite) (roundAccum (mergeAccum below tiny))- check "small plus M-small" (Right maximumFinite) (roundAccum (mergeAccum tiny below))- check "nested residual" (Right (GT, subnormal)) (netAccum nearMaximum below)- check "MoneyDouble" (Right (MoneyDouble (threshold + 2)))- (sumExact [MoneyDouble threshold, 1, 1])- check "MoneyDouble nonfinite" (Left NonFiniteInput) (sumExact [MoneyDouble (1 / 0)])- check "MoneyDouble negative" (Left NegativeInput) (sumExact [MoneyDouble (-1)])- check "NN.Double" (Right (NN.fromNumber (threshold + 2)))- (sumExact [NN.fromNumber threshold, 1, 1] :: Either ExactSumError NN.Double)- check "NN magnitude only" (Right (LT, 7 :: NN.Double))- (netAccum (accumulate [3]) (accumulate [10]))- check "MoneyDecimal negative" (Left NegativeInput) (sumExact [-1 :: MoneyDecimal])+ below = mergeAccum nearMaximum (negateAccum tiny)+ check "M-small rounds M" maximumFinite (roundAccum below)+ check "M-small plus small" maximumFinite (roundAccum (mergeAccum below tiny))+ check "small plus M-small" maximumFinite (roundAccum (mergeAccum tiny below))+ check "nested residual" subnormal+ (roundAccum (mergeAccum nearMaximum (negateAccum below)))+ check "MoneyDouble" (MoneyDouble (threshold + 2))+ (sumValues [MoneyDouble threshold, 1, 1]) let decimal = MoneyDecimal (Decimal.Decimal 2 1)- check "MoneyDecimal scale" (Right (MoneyDecimal (Decimal.Decimal 2 101)))- (sumExact [1, decimal])- check "MoneyDecimal unlimited" (Right (2 * 10 ^ (400 :: Int) :: MoneyDecimal))- (sumExact [10 ^ (400 :: Int), 10 ^ (400 :: Int)])- check "MoneyDecimal net" (Right (LT, decimal))- (netAccum (accumulate [1]) (accumulate [1, decimal]))+ check "MoneyDecimal scale" (MoneyDecimal (Decimal.Decimal 2 101))+ (sumValues [1, decimal])+ check "MoneyDecimal unlimited" (2 * 10 ^ (400 :: Int) :: MoneyDecimal)+ (sumValues [10 ^ (400 :: Int), 10 ^ (400 :: Int)])+ check "MoneyDecimal net" (negate decimal)+ (roundAccum (mergeAccum (accumulate [1]) (negateAccum (accumulate [1, decimal])))) -- | Fixed grouping, projection, and accounting cases from the accepted contract. testReadouts :: IO ()@@ -342,66 +359,69 @@ large = build [(1e308, Not :< Cash), (1e308, Not :< Deposits)] roundExample = build [(threshold, Not :< Cash), (1, Not :< Cash), (1, Not :< Deposits)] equal = build [(10, Not :< Cash), (10, Hat :< Cash)]- check "pair retains base distinction" (Right (Map.singleton () (10, 7)))- (netPairMapByExact (const (Just ())) pairExample)- check "post selects after netting" (Right [])- (fmap observeAlg (postFromNetByExact selectHat post sameBase))- check "balance direction" (Right (Map.fromList [(Cash, (GT, 10)), (Deposits, (LT, 7))]))- (balanceMapByExact Just pairExample)- check "zero key retained" (Right (Map.singleton Cash (EQ, 0)))- (balanceMapByExact Just equal)- check "bar zero removed" (Right []) (fmap observeAlg (barExact equal))- check "gross overflow" (Left SumOutOfRange) (normExact large)- check "residual overflow" (Left SumOutOfRange) (projNetNormExact query large)- check "pair overflow" (Left SumOutOfRange) (netPairMapByExact (const (Just ())) large)- check "post overflow" (Left SumOutOfRange)- (fmap observeAlg (postFromNetByExact (const (Just ())) post large))- check "projection rounds once" (Right (threshold + 2)) (projNetNormExact query roundExample)- check "pair rounds once per key side" (Right (Map.singleton () (threshold + 2, 0)))- (netPairMapByExact (const (Just ())) roundExample)- check "post rounds once per key" (Right (threshold + 2))- (postFromNetByExact (const (Just ())) post roundExample >>= normExact)- check "two rounds differ" (Right threshold) (barExact roundExample >>= normExact)- check "duplicate base query" (projNetNormExact query roundExample)- (projNetNormExact (query ++ query) roundExample)- check "debit difference" (Right (Debit, 3)) (diffRLExact sameBase)- check "exact balance" (Right True) (balanceExact equal)- check "account balances" (Right (Map.singleton Cash (DebitBalance 3)))- (accountBalancesExact sameBase)+ check "pair retains base distinction" (Map.singleton () (10, 7))+ (netPairMapBy (const (Just ())) pairExample)+ check "post selects after netting" []+ (observeAlg (postFromNetBy selectHat post sameBase))+ check "balance direction" (Map.fromList [(Cash, 10), (Deposits, -7)])+ (balanceMapBy Just pairExample)+ check "zero key retained" (Map.singleton Cash 0)+ (balanceMapBy Just equal)+ check "bar zero removed" [] (observeAlg (bar equal))+ check "gross overflow" True (isInfinite (norm large))+ check "residual overflow" True (isInfinite (projNetNorm query large))+ check "pair overflow" (Map.singleton () (1 / 0, 0))+ (netPairMapBy (const (Just ())) large)+ check "post overflow reaches callback" (observeAlg sameBase)+ (observeAlg (postFromNetBy (const (Just ()))+ (\_ value -> case value == 1 / 0 of+ True -> sameBase+ False -> mempty)+ (build [(1e308, Not :< Cash), (1e308, Not :< Cash)])))+ check "projection rounds once" (threshold + 2) (projNetNorm query roundExample)+ check "pair rounds once per key side" (Map.singleton () (threshold + 2, 0))+ (netPairMapBy (const (Just ())) roundExample)+ check "post rounds once per key" (threshold + 2)+ (norm (postFromNetBy (const (Just ())) post roundExample))+ check "two rounds differ" threshold (norm (bar roundExample))+ check "duplicate base query" (projNetNorm query roundExample)+ (projNetNorm (query ++ query) roundExample)+ check "debit difference" (Debit, 3) (diffRL sameBase)+ check "exact balance" True (balance equal)+ check "account balances" (Map.singleton Cash (DebitBalance 3))+ (accountBalances sameBase) let journal = (10 .@ (Not :< Cash) Journal..| "a") <> (10 .@ (Hat :< Cash) Journal..| "b") :: TestJournal roundedJournal = (build [(threshold, Not :< Cash), (1, Not :< Cash)] Journal..| "a") <> (build [(1, Not :< Cash)] Journal..| "b")- check "journal separate notes" (Right 20) (JournalExact.projWithBaseNetNormExact query journal)- check "journal duplicate base queries" (Right 20)- (JournalExact.projWithBaseNetNormExact (query ++ query) journal)- check "journal note selection" (Right 10)- (JournalExact.projWithNoteBaseNetNormExact ["a", "a"] query journal)- check "journal empty notes" (Right 20)- (JournalExact.projWithNoteBaseNetNormExact [] query journal)- check "journal plank notes" (Right 20)- (JournalExact.projWithNoteBaseNetNormExact ["a", ""] query journal)- check "journal once across notes" (Right (threshold + 2))- (JournalExact.projWithBaseNetNormExact query roundedJournal)- check "journal bar gathers" (Right 0) (JournalExact.barExact journal >>= JournalExact.normExact)- check "journal balance" (Right True) (JournalExact.balanceExact journal)- check "journal diff" (Right (Side, 0)) (JournalExact.diffRLExact journal)- check "journal account" (Right (Map.singleton Cash NoBalance))- (JournalExact.accountBalancesExact journal)- check "journal pair gathers" (Right Map.empty)- (JournalExact.netPairMapByExact Just journal)- check "journal signed gathers" (Right (Map.singleton Cash (EQ, 0)))- (JournalExact.balanceMapByExact Just journal)- check "journal post gathers" (Right 0)- (JournalExact.postFromNetByExact selectHat- (\key value -> post key value Journal..| "new") journal >>= JournalExact.normExact)- check "journal post rounds once" (Right (threshold + 2))- (JournalExact.postFromNetByExact (const (Just ()))- (\key value -> post key value Journal..| "new") roundedJournal- >>= JournalExact.normExact)- check "journal alias" (Right 20) (JournalExact.projNetNormExact query journal)- check "journal residual overflow" (Left SumOutOfRange)- (JournalExact.projWithBaseNetNormExact query (large Journal..| "a"))+ check "journal separate notes" 20 (Journal.projWithBaseNetNorm query journal)+ check "journal duplicate base queries" 20+ (Journal.projWithBaseNetNorm (query ++ query) journal)+ check "journal note selection" 10+ (Journal.projWithNoteBaseNetNorm ["a", "a"] query journal)+ check "journal empty notes" 20+ (Journal.projWithNoteBaseNetNorm [] query journal)+ check "journal plank notes" 20+ (Journal.projWithNoteBaseNetNorm ["a", ""] query journal)+ check "journal once across notes" (threshold + 2)+ (Journal.projWithBaseNetNorm query roundedJournal)+ check "journal bar gathers" 0 (Journal.norm (Journal.bar journal))+ check "journal balance" True (Exchange.balance journal)+ check "journal diff" (Side, 0) (Exchange.diffRL journal)+ check "journal account" (Map.singleton Cash NoBalance)+ (accountBalances (Journal.toAlg journal))+ check "journal pair gathers" Map.empty+ (netPairMapBy Just (Journal.toAlg journal))+ check "journal signed gathers" (Map.singleton Cash 0)+ (balanceMapBy Just (Journal.toAlg journal))+ check "journal post gathers" 0+ (Journal.norm (Journal.postFromNetBy selectHat+ (\key value -> post key value Journal..| "new") journal))+ check "journal post rounds once" (threshold + 2)+ (Journal.norm (Journal.postFromNetBy (const (Just ()))+ (\key value -> post key value Journal..| "new") roundedJournal))+ check "journal residual overflow" True+ (isInfinite (Journal.projWithBaseNetNorm query (large Journal..| "a"))) -- | Execute all exact-readout acceptance checks in the main test suite. runTests :: IO ()
test/Algebra/ProjWildcardSpec.hs view
@@ -7,6 +7,8 @@ -- element or a 'Liner'). module Algebra.ProjWildcardSpec (runTests) where +import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange import Control.Monad (unless) import qualified Data.List as List import qualified Data.Map.Strict as Map@@ -67,7 +69,7 @@ symmetric = (.==) build :: [(Double, TestBase)] -> TestAlg-build entries = fromList [value :@ postingBase | (value, postingBase) <- entries]+build entries = fromList [value .@ postingBase | (value, postingBase) <- entries] -- | Raw postings keyed by the rendered base. 'HatBase' orders wildcards as -- equal to anything, so the base itself cannot be the key here.@@ -96,7 +98,7 @@ propSingle (ProjCase entries queries) = all agrees entries where agrees entry@(value, postingBase) =- observe (proj queries (value :@ postingBase))+ observe (proj queries (value .@ postingBase)) == observe (build (selected oneWay (ProjCase [entry] queries))) propNetNorm :: ProjCase -> Bool@@ -105,7 +107,7 @@ && all single entries where single entry@(value, postingBase) =- projNetNorm queries (value :@ postingBase)+ projNetNorm queries (value .@ postingBase) == netNorm (selected oneWay (ProjCase [entry] queries)) propConcreteRegression :: ProjCase -> Bool@@ -130,12 +132,12 @@ -- | The reported table: cash carries no unit axis in the ledger. testReportedTable :: IO () testReportedTable = do- let cash = 10 :@ Not :< (Cash, wildcard) :: TestAlg- both = cash .+ 3 :@ Not :< (Products, Yen)+ let cash = 10 .@ Not :< (Cash, wildcard) :: TestAlg+ both = cash .+ 3 .@ Not :< (Products, Yen) anyTitleYen = [Not :< (wildcard, Yen)] cashYen = [Not :< (Cash, Yen)] cashAny = [Not :< (Cash, wildcard)]- products = 3 :@ Not :< (Products, Yen) :: TestAlg+ products = 3 .@ Not :< (Products, Yen) :: TestAlg assertTest "proj: wildcard-title query skips ledger-wildcard unit (single)" (Algebra.isZero (proj anyTitleYen cash)) assertTest "proj: wildcard-title query skips ledger-wildcard unit (Liner)"@@ -157,8 +159,8 @@ -- | 'projByAccountTitle' follows the same one-way rule on the title axis. testProjByAccountTitle :: IO () testProjByAccountTitle = do- let noTitle = 7 :@ Not :< wildcard :: Alg Double (HatBase AccountTitles)- cash = 10 :@ Not :< Cash+ let noTitle = 7 .@ Not :< wildcard :: Alg Double (HatBase AccountTitles)+ cash = 10 .@ Not :< Cash ledger = noTitle .+ cash postings = List.sort . foldEntries (\result value postingBase -> (show postingBase, value) : result) []@@ -173,11 +175,11 @@ -- in both directions and in both ledger shapes. testMapRewritesWildcardAxis :: IO () testMapRewritesWildcardAxis = do- let cash = 10 :@ Not :< (Cash, wildcard) :: TestAlg- cashYen = 10 :@ Not :< (Cash, Yen) :: TestAlg- products = 3 :@ Not :< (Products, Amount) :: TestAlg+ let cash = 10 .@ Not :< (Cash, wildcard) :: TestAlg+ cashYen = 10 .@ Not :< (Cash, Yen) :: TestAlg+ products = 3 .@ Not :< (Products, Amount) :: TestAlg setCashUnit unit entry@(value :@ (h :< (title, _)))- | title == Cash = value :@ (h :< (title, unit))+ | title == Cash = value .@ (h :< (title, unit)) | otherwise = entry setCashUnit _ entry = entry assertTest "map: wildcard axis rewritten to a concrete value (single)"
+ test/Golden/AdmissionBaseline.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Deterministic observations of accounting equivalence policies.+module Golden.AdmissionBaseline+ ( admissionFixtureDir+ , admissionFixtures+ , renderRows+ ) where++import ExchangeAlgebra.Accounting.Account (AccountTitles(..))+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as Text++import ExchangeAlgebra.Algebra ((.@), Redundant((.+)), Hat(..))+import ExchangeAlgebra.Algebra.Base (HatBase((:<)))+import ExchangeAlgebra.Accounting.Transaction+ ( EntityId(..)+ , PeriodId(..)+ , TxId(..)+ , TxKey(..)+ , Entry+ )+import ExchangeAlgebra.Accounting.Equivalence (Equivalence(..), isEquivalentUpTo)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++-- | Directory containing the committed equivalence observations.+admissionFixtureDir :: FilePath+admissionFixtureDir = "test/fixtures/admission-baseline-p1"++-- | Develop revision whose equivalence behavior these rows record.+baselineCommit :: Text+baselineCommit = "2a83fe178d47a1024e0e566794f39533a12b4c20"++-- | Name, P3 category, and observed result of one fixed input.+data GoldenCase = GoldenCase Text Text Text++-- | Escape cells so each case occupies exactly one TSV row.+escapeCell :: Text -> Text+escapeCell = Text.replace "\\" "\\\\"+ . Text.replace "\n" "\\n"+ . Text.replace "\t" "\\t"++-- | Render the schema, revision, and observations in their declared order.+renderRows :: [GoldenCase] -> Text+renderRows cases =+ "# admission-baseline-p1; schema 1; commit " <> baselineCommit <> "\n"+ <> "case\tp3\tobservation\n"+ <> Text.unlines+ [ Text.intercalate "\t" (map escapeCell [name, category, result])+ | GoldenCase name category result <- cases+ ]++-- | Entity used in the fixed equivalence inputs.+entityA :: EntityId+entityA = EntityId "A"++-- | Reporting period used in the fixed equivalence inputs.+periodOne :: PeriodId+periodOne = PeriodId "2026"++-- | Construct a transaction key without parsing its identity.+key :: EntityId -> PeriodId -> Text -> TxKey+key entity period identity = TxKey entity period (TxId identity)++-- | Compare the three policies on fixed redundant and exact-residual inputs.+equivalenceCases :: [GoldenCase]+equivalenceCases =+ [ compare "cash-gross-transaction-net" NetWithinTransaction baseline withCash+ , compare "cash-gross-postings" PostingMultiset baseline withCash+ , compare "cash-gross-selected" selected baseline withCash+ , compare "retained-gross-postings" PostingMultiset baseline withGrossRetained+ , compare "retained-gross-transaction-net" NetWithinTransaction baseline withGrossRetained+ , compare "retained-gross-selected" selected baseline withGrossRetained+ , compare "near-cancellation-net" NetWithinTransaction nearCancellation zeroPosting+ , compare "near-cancellation-postings" PostingMultiset nearCancellation zeroPosting+ , compare "near-cancellation-selected" selected nearCancellation zeroPosting+ , compare "tiny-pair-net" NetWithinTransaction tinyPair zeroPosting+ , compare "tiny-pair-postings" PostingMultiset tinyPair zeroPosting+ , compare "tiny-single-net" NetWithinTransaction tinySingle zeroPosting+ ]+ where+ closing = key entityA periodOne "closing"+ base = 100 .@ Hat :< Sales+ .+ 80 .@ Not :< RetainedEarnings+ .+ 20 .@ Hat :< Depreciation :: Entry+ cashGross = base .+ 10 .@ Not :< Cash .+ 10 .@ Hat :< Cash+ retainedGross = 100 .@ Hat :< Sales+ .+ 100 .@ Not :< RetainedEarnings+ .+ 20 .@ Hat :< RetainedEarnings+ .+ 20 .@ Hat :< Depreciation :: Entry+ baseline = Map.singleton closing base+ withCash = Map.singleton closing cashGross+ withGrossRetained = Map.singleton closing retainedGross+ selected = NetAccountsInTransactions+ (Set.singleton closing) (Set.singleton RetainedEarnings)+ nearCancellation = Map.singleton closing+ (1000000000000 .@ Not :< Cash+ .+ (1000000000000 - 0.5) .@ Hat :< Cash :: Entry)+ zeroPosting = Map.singleton closing (mempty :: Entry)+ tiny = 0.00000000000001 :: MoneyDecimal+ tinyPair = Map.singleton closing+ ((2 * tiny) .@ Hat :< Cash .+ tiny .@ Not :< Cash :: Entry)+ tinySingle = Map.singleton closing (tiny .@ Hat :< Cash :: Entry)+ compare name policy first second = GoldenCase name category+ (Text.pack (show (isEquivalentUpTo policy first second)))+ where+ category = case policy of+ PostingMultiset -> "-"+ _ -> "exact-residual"++-- | The retained equivalence fixture and its case count.+admissionFixtures :: [(FilePath, Text, Int)]+admissionFixtures =+ [("equivalence.tsv", renderRows equivalenceCases, length equivalenceCases)]
+ test/Golden/ExampleNumbers/RippleAnalysis.hs view
@@ -0,0 +1,72 @@+-- | Reproduce the coefficient matrix and analysis calls from the ripple example.+module Golden.ExampleNumbers.RippleAnalysis (numericRows) where++import Data.Array.IO (IOArray, getAssocs, newListArray)+import Data.List (foldl')+import qualified Data.Map.Strict as M+import Data.Word (Word64)+import GHC.Float (castDoubleToWord64)+import Numeric (showHex)+import System.Random (RandomGen (genWord32), StdGen, mkStdGen, randomR)++import ExchangeAlgebra.Simulation.Analysis (leontiefInverse, rippleEffect)++-- | The example's nine producing industries, excluding final demand.+type Industry = Int++-- | One coefficient, Leontief inverse entry, or ripple entry.+type NumericRow = (String, Industry, Industry, Word64, String)++-- | Reproduce the example's first term with seed 42 and in-house ratio 0.4.+coefficientColumns :: M.Map Industry [Double]+coefficientColumns = fst $ foldl' addColumn (M.empty, mkStdGen 42) [1 .. 10]+ where+ addColumn (columns, generator) industry =+ let (samples, nextGenerator) = drawSamples generator+ total = sum samples+ coefficients = map (\sample -> sample / total * 0.4) samples+ in (M.insert industry coefficients columns, nextGenerator)++ drawSamples :: StdGen -> ([Double], StdGen)+ drawSamples generator =+ let (values, nextGenerator) = draw 9 (skipGenerator generator)+ clipped = map (\value -> if value < 0.1 then 0 else value) values+ in (clipped ++ [0], nextGenerator)++ draw 0 generator = ([], generator)+ draw remaining generator =+ let (value, nextGenerator) = randomR (0, 1.0) generator+ (values, finalGenerator) = draw (remaining - 1) nextGenerator+ in (value : values, finalGenerator)++ skipGenerator generator =+ foldl' (\current _ -> snd (genWord32 current)) generator [1 .. 1000]++-- | Calculate the first-term input matrix, inverse, and ninth-industry ripple.+numericRows :: IO [NumericRow]+numericRows = do+ let entries =+ [ coefficient row column+ | row <- [1 .. 9]+ , column <- [1 .. 9]+ ]+ matrix <- newListArray ((1, 1), (9, 9)) entries+ :: IO (IOArray (Industry, Industry) Double)+ inverse <- leontiefInverse matrix+ ripple <- rippleEffect 9 inverse+ coefficientRows <- pure $ zipWith (makeRow "inputCoefficient") indexes entries+ inverseRows <- map (uncurry (makeRow "leontiefInverse")) <$> getAssocs inverse+ rippleRows <- map (uncurry (makeRow "rippleEffect")) <$> getAssocs ripple+ pure (coefficientRows ++ inverseRows ++ rippleRows)+ where+ indexes = [(row, column) | row <- [1 .. 9], column <- [1 .. 9]]++ coefficient row column =+ case M.lookup column coefficientColumns of+ Just values -> case drop (row - 1) values of+ value : _ -> value+ [] -> 0+ Nothing -> 0++ makeRow name (row, column) value =+ (name, row, column, castDoubleToWord64 value, show value)
+ test/Golden/ExampleNumbers/RippleFixture.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Render the ripple analysis output as a stable numeric fixture.+module Golden.ExampleNumbers.RippleFixture+ ( fixtureDir+ , fixtureName+ , renderFixture+ ) where++import qualified Data.Text as T+import Numeric (showHex)++import Golden.ExampleNumbers.RippleAnalysis (numericRows)++-- | Directory holding the P1 example-number fixtures.+fixtureDir :: FilePath+fixtureDir = "test/fixtures/example-numbers-p1"++-- | File containing the ripple matrix readouts.+fixtureName :: FilePath+fixtureName = "ripple-analysis.tsv"++-- | Render schema, baseline revision, and exact Double bits.+renderFixture :: IO T.Text+renderFixture = do+ rows <- numericRows+ pure $ T.unlines (header : map renderRow rows)+ where+ header =+ "# example-numbers-p1 ripple-analysis; schema 1; commit "+ <> "2a83fe178d47a1024e0e566794f39533a12b4c20"++ renderRow (name, row, column, bits, value) =+ T.intercalate "\t"+ [ T.pack name+ , T.pack (show row)+ , T.pack (show column)+ , T.pack ("0x" ++ replicate (16 - length digits) '0' ++ digits)+ , T.pack value+ ]+ where+ digits = showHex bits ""
+ test/Golden/ReadoutBaseline.hs view
@@ -0,0 +1,395 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Freeze current read-outs before the exact-sum transition.+module Golden.ReadoutBaseline+ ( readoutFixtureDir+ , readoutFixtures+ , keyedFixtures+ , renderRows+ ) where++import qualified Data.HashMap.Strict as HM+import qualified Data.List as L+import qualified Data.Map.Strict as M+import qualified Data.Sequence as Seq+import qualified Data.Set as S+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Float (castDoubleToWord64)+import Numeric (showHex)++import ExchangeAlgebra+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Algebra.Internal as Internal+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.IO.Input as Checked+import qualified ExchangeAlgebra.Journal as EJ+import qualified ExchangeAlgebra.Journal.Transfer.Rule as JR+import qualified ExchangeAlgebra.Accounting.Statements.Metric as Metric+import qualified ExchangeAlgebra.Accounting.TrialBalance.Balance as TB+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as Validation+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++-- | Account-only bases for the main readout series.+type ReadoutBase = HatBase AccountTitles++-- | Account and integer-key bases for grouped readouts.+type KeyedBase = HatBase (AccountTitles, Int)++-- | Directory containing the committed TSV fixtures.+readoutFixtureDir :: FilePath+readoutFixtureDir = "test/fixtures/readout-baseline-p1"++-- | Revision whose current behavior is frozen by the fixtures.+baselineCommit :: Text+baselineCommit = "2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f"++-- | Render a fixture with an explicit schema and baseline revision.+renderRows :: Text -> [[Text]] -> Text+renderRows series rows =+ "# readout-baseline-p1 " <> series <> "; schema 1; commit "+ <> baselineCommit <> "\n"+ <> T.unlines (L.map (T.intercalate "\t" . L.map escapeCell) rows)+ where+ escapeCell = T.replace "\n" "\\n"+ . T.replace "\t" "\\t"+ . T.replace "\\" "\\\\"++-- | Render a value's exact representation next to its readable value.+class (Show v, HatVal v) => GoldenValue v where+ valueCells :: v -> [Text]++instance GoldenValue Double where+ valueCells value =+ [ T.pack ("0x" ++ L.replicate (16 - L.length digits) '0' ++ digits)+ , T.pack (show value)+ ]+ where+ digits = showHex (castDoubleToWord64 value) ""++instance GoldenValue MoneyDecimal where+ valueCells value = [T.pack (show value)]++-- | Record one posting per row and sort before comparison.+postingRows :: GoldenValue v => Text -> Text -> Alg v ReadoutBase -> [[Text]]+postingRows function argument algebra =+ L.sort+ [ [function, argument, "posting", "", T.pack (show (hat base))+ , T.pack (show (EA.base base))] ++ valueCells value+ | (value, base) <- EA.foldEntries (\xs v b -> (v, b) : xs) [] algebra+ ]++postingJournalRows :: GoldenValue v => Text -> Text -> EJ.Journal Int v ReadoutBase -> [[Text]]+postingJournalRows function argument journal =+ L.sort+ [ [function, argument, "posting", T.pack (show note)+ , T.pack (show (hat base)), T.pack (show (EA.base base))]+ ++ valueCells value+ | (note, algebra) <- HM.toList (EJ.toMap journal)+ , (value, base) <- EA.foldEntries (\xs v b -> (v, b) : xs) [] algebra+ ]++-- | Render a numeric result without dropping Double bits.+scalarRow :: GoldenValue v => Text -> Text -> v -> [[Text]]+scalarRow function argument value = [[function, argument, "scalar"] ++ valueCells value]++-- | Render a nonnumeric result or a public API's existing textual output.+shownRow :: Show a => Text -> Text -> a -> [[Text]]+shownRow function argument value = [[function, argument, "result", T.pack (show value)]]++-- | Keep only the constructor name of a checked failure.+errorName :: Show e => e -> Text+errorName = T.takeWhile (\character -> character /= ' ' && character /= '{') . T.pack . show++-- | Render a checked algebra result and its sorted postings.+algEitherRows+ :: (Show e, GoldenValue v)+ => Text -> Text -> Either e (Alg v ReadoutBase) -> [[Text]]+algEitherRows function argument result = case result of+ Left err -> [[function, argument, "Left", errorName err]]+ Right value -> [ [function, argument, "Right"] ]+ ++ postingRows function argument value++-- | Render a checked journal result and its sorted postings.+journalEitherRows+ :: (Show e, GoldenValue v)+ => Text -> Text -> Either e (EJ.Journal Int v ReadoutBase) -> [[Text]]+journalEitherRows function argument result = case result of+ Left err -> [[function, argument, "Left", errorName err]]+ Right value -> [[function, argument, "Right"]]+ ++ postingJournalRows function argument value++-- | Render a scalar map in ascending account order.+mapRows :: GoldenValue v => Text -> M.Map AccountTitles v -> [[Text]]+mapRows function values =+ [ [function, "key=account", "map", T.pack (show account)] ++ valueCells value+ | (account, value) <- M.toAscList values+ ]++-- | Render each pair's components in ascending account order.+pairMapRows :: GoldenValue v => Text -> M.Map AccountTitles (v, v) -> [[Text]]+pairMapRows function values =+ [ [function, "key=account", "map", T.pack (show account)]+ ++ valueCells notValue ++ valueCells hatValue+ | (account, (notValue, hatValue)) <- M.toAscList values+ ]++-- | Render trial balance values with their side and exact numeric encoding.+balanceRows :: GoldenValue v => M.Map AccountTitles (TB.AccountBalance v) -> [[Text]]+balanceRows values =+ [ ["accountBalances", "all", "map", T.pack (show account), side]+ ++ maybe [] valueCells amount+ | (account, balanceValue) <- M.toAscList values+ , let (side, amount) = case balanceValue of+ TB.NoBalance -> ("NoBalance", Nothing)+ TB.DebitBalance value -> ("DebitBalance", Just value)+ TB.CreditBalance value -> ("CreditBalance", Just value)+ ]++-- | Render a reporting metric's constructor and numeric value.+metricRows :: GoldenValue v => Either Metric.MetricError (Metric.PeriodResult v) -> [[Text]]+metricRows result = case result of+ Left err -> [["periodResultOfAlg", "all", "Left", errorName err]]+ Right Metric.PeriodBreakEven -> [["periodResultOfAlg", "all", "PeriodBreakEven"]]+ Right (Metric.PeriodProfit value) ->+ [["periodResultOfAlg", "all", "PeriodProfit"] ++ valueCells value]+ Right (Metric.PeriodLoss value) ->+ [["periodResultOfAlg", "all", "PeriodLoss"] ++ valueCells value]++-- | Exercise the checked-entry boundary at the 0.1 + 0.2 versus 0.3 case.+checkedBoundaryRows :: GoldenValue v => v -> [[Text]]+checkedBoundaryRows witness =+ shownRow "Convert.Checked.checkedEntry" "0.1+0.2 vs 0.3" result+ ++ scalarRow "Convert.Checked.debitInput" "0.1+0.2" (tenth + fifth)+ ++ scalarRow "Convert.Checked.creditInput" "0.3" threeTenths+ where+ tenth = asTypeOf 0.1 witness+ fifth = asTypeOf 0.2 witness+ threeTenths = asTypeOf 0.3 witness+ result = Checked.checkedEntry+ [(Debit, Cash, tenth), (Debit, Cash, fifth), (Credit, Cash, threeTenths)]++-- | One input scalar with its side, account, and journal note.+data Posting v = Posting v Hat AccountTitles Int++-- | Build each series from an explicit, deterministic posting list.+series :: (GoldenValue v, Fractional v) => Text -> [v] -> [Posting v]+series "f-int" [a, b, c, d, e] =+ [ Posting a Not Cash 1, Posting b Hat Cash 1, Posting c Not Sales 2+ , Posting d Hat Sales 2, Posting e Not AccountsReceivable 3+ , Posting a Hat AccountsReceivable 3, Posting b Not Purchases 1+ , Posting c Hat Purchases 2, Posting d Not RetainedEarnings 3+ , Posting e Hat RetainedEarnings 1, Posting a Not Cash 2+ , Posting c Hat Cash 3, Posting d Not Sales 1+ , Posting b Hat AccountsReceivable 2+ ]+series "f-dec2" [a, b, c, d, e, f] =+ [ Posting b Not Cash 1, Posting c Not Cash 1, Posting d Hat Cash 2+ , Posting a Not Sales 2, Posting e Hat Sales 2+ , Posting f Not AccountsReceivable 3, Posting e Hat AccountsReceivable 3+ , Posting a Not Purchases 1, Posting b Hat Purchases 1+ , Posting c Not RetainedEarnings 2, Posting d Hat RetainedEarnings 3+ , Posting f Hat Cash 3, Posting a Not Cash 2, Posting c Hat Sales 1+ ]+series "f-cancel" [a, b, c, d] =+ [ Posting a Not Cash 1, Posting b Not Cash 1, Posting a Hat Cash 2+ , Posting c Not Sales 2, Posting b Hat Sales 2, Posting c Hat Sales 3+ , Posting d Not AccountsReceivable 3, Posting b Hat AccountsReceivable 1+ , Posting d Hat AccountsReceivable 2, Posting b Not Purchases 1+ , Posting c Hat Purchases 3, Posting a Not RetainedEarnings 2+ , Posting a Hat RetainedEarnings 3+ ]+series "f-exp" [a, b, c, d] =+ [ Posting a Not Cash 1, Posting b Not Cash 1, Posting c Hat Cash 2+ , Posting d Not Sales 2, Posting d Not Sales 3+ , Posting b Hat AccountsReceivable 3, Posting c Not AccountsReceivable 1+ , Posting a Hat Purchases 1, Posting b Not Purchases 2+ , Posting c Hat RetainedEarnings 3, Posting b Not RetainedEarnings 1+ ]+series "f-sub" [a, b, c] =+ [ Posting a Not Cash 1, Posting a Not Cash 2, Posting b Hat Cash 3+ , Posting b Not Sales 2, Posting a Hat Sales 1+ , Posting c Not AccountsReceivable 3, Posting a Hat AccountsReceivable 2+ , Posting c Hat Purchases 1, Posting b Not Purchases 3+ , Posting a Not RetainedEarnings 2, Posting b Hat RetainedEarnings 1+ ]+series _ _ = []++-- | Build an account-only algebra without aggregating input postings.+algebra :: GoldenValue v => [Posting v] -> Alg v ReadoutBase+algebra = L.foldl' (.+) Zero . L.map make+ where+ make (Posting value side account _) = value .@ (side :< account)++-- | Keep the note number as a second basis coordinate.+keyedAlgebra :: (GoldenValue v, Element Int, BaseClass Int) => [Posting v] -> Alg v KeyedBase+keyedAlgebra = L.foldl' (.+) Zero . L.map make+ where+ make (Posting value side account key) = value .@ (side :< (account, key))++-- | Divide a series among three integer notes.+journal :: GoldenValue v => [Posting v] -> EJ.Journal Int v ReadoutBase+journal postings = EJ.fromMap (HM.fromList+ [ (note, algebra [posting | posting@(Posting _ _ _ index) <- postings, index == note])+ | note <- [1, 2, 3]+ ])++-- | Capture the account-only algebra readouts for one series.+algRows :: GoldenValue v => Text -> [Posting v] -> [[Text]]+algRows name postings =+ postingRows "bar" "all" (bar input)+ ++ scalarRow "norm" "all" (norm input)+ ++ shownRow "balance" "all" (balance input)+ ++ let (side, value) = diffRL input+ in [["diffRL", "all", T.pack (show side)] ++ valueCells value]+ ++ postingRows "compress" "all" (compress input)+ ++ mapRows "balanceMapBy" (EA.balanceMapBy Just input)+ ++ pairMapRows "netPairMapBy" (EA.netPairMapBy Just input)+ ++ postFromNetRows+ ++ scalarRow "projNetNorm" "Cash" (EA.projNetNorm [Not :< Cash] input)+ ++ scalarRow "projNetNorm" "Sales" (EA.projNetNorm [Not :< Sales] input)+ ++ scalarRow "projNetNorm" "Cash+Sales"+ (EA.projNetNorm [Not :< Cash, Not :< Sales] input)+ ++ scalarRow "balanceBy" "Cash Not-Hat"+ (EA.balanceBy [Not :< Cash] [Hat :< Cash] input)+ ++ scalarRow "balanceBy" "Sales Not-Hat"+ (EA.balanceBy [Not :< Sales] [Hat :< Sales] input)+ ++ shownRow "Pair.compare" "Cash notes=1 vs Cash notes=2,3"+ (compare (pair Cash [1]) (pair Cash [2, 3]))+ ++ shownRow "Pair.compare" "Sales notes=1,2 vs Sales note=3"+ (compare (pair Sales [1, 2]) (pair Sales [3]))+ ++ algEitherRows "closingEntries" "Alg" (Closing.closingEntries input)+ ++ balanceRows (TB.accountBalances input)+ ++ shownRow "trialBalanceFindings" "BeforeClosing" (Validation.trialBalanceFindings tbInput)+ ++ scalarRow "trialBalanceFindings.debitTotal" "BeforeClosing" (norm (decL input))+ ++ scalarRow "trialBalanceFindings.creditTotal" "BeforeClosing" (norm (decR input))+ ++ checkedRows+ ++ metricRows (Metric.periodResultOfAlg input)+ where+ input = algebra postings+ postFromNetRows+ | name == "f-exp" = []+ | otherwise = postingRows "postFromNetBy" "key=account; callback=identity-side-Not"+ (EA.postFromNetBy (Just . EA.base)+ (\account value -> value .@ (Not :< account)) input)+ pair account selectedNotes = Internal.Pair+ (Seq.fromList [value | Posting value Hat title note <- postings+ , title == account, note `elem` selectedNotes])+ (Seq.fromList [value | Posting value Not title note <- postings+ , title == account, note `elem` selectedNotes])+ tbInput = Validation.TrialBalanceInput input Validation.BeforeClosing M.empty [] S.empty+ checkedRows+ | name == "f-dec2" = checkedBoundaryRows (norm input)+ | otherwise = []++-- | Capture the journal readouts for one series.+journalReadoutRows :: GoldenValue v => [Posting v] -> [[Text]]+journalReadoutRows postings =+ postingJournalRows "bar" "all" (bar input)+ ++ scalarRow "norm" "all" (norm input)+ ++ scalarRow "projWithBaseNetNorm" "Cash"+ (EJ.projWithBaseNetNorm [Not :< Cash] input)+ ++ scalarRow "projWithBaseNetNorm" "Sales"+ (EJ.projWithBaseNetNorm [Not :< Sales] input)+ ++ scalarRow "projWithNoteBaseNetNorm" "note=1,Cash"+ (EJ.projWithNoteBaseNetNorm [1] [Not :< Cash] input)+ ++ scalarRow "projWithNoteBaseNetNorm" "notes=1,2,Sales"+ (EJ.projWithNoteBaseNetNorm [1, 2] [Not :< Sales] input)+ ++ postingJournalRows "compress" "all" (compress input)+ ++ algEitherRows "closingEntries" "Journal" (Closing.closingEntries (EJ.toAlg input))+ where+ input = journal postings++-- | Capture carry success and the overflow boundary for Double journals.+doubleCarryRows :: Text -> [Posting Double] -> [[Text]]+doubleCarryRows name postings =+ journalEitherRows "carryEntries" "notes=1,2;carry=4"+ (JR.carryEntries (`elem` [1, 2]) 4 input)+ ++ journalEitherRows "carryBefore" "notes=1,2;carry=4"+ (JR.carryBefore (`elem` [1, 2]) 4 input)+ ++ overflowRows+ where+ input = journal postings+ overflowRows+ | name == "f-exp" =+ journalEitherRows "carryEntries" "notes=2,3;carry=4;overflow"+ (JR.carryEntries (`elem` [2, 3]) 4 input)+ ++ journalEitherRows "carryBefore" "notes=2,3;carry=4;overflow"+ (JR.carryBefore (`elem` [2, 3]) 4 input)+ | otherwise = []++-- | Capture grouped readouts using the account and integer-key basis.+keyedRows :: (GoldenValue v, Element Int, BaseClass Int) => [Posting v] -> [[Text]]+keyedRows postings =+ mapRows "balanceMapBy.keyed" (EA.balanceMapBy (Just . fst) input)+ ++ pairMapRows "netPairMapBy.keyed" (EA.netPairMapBy (Just . fst) input)+ ++ scalarRow "projNetNorm.keyed" "Cash,1"+ (EA.projNetNorm [Not :< (Cash, 1)] input)+ where+ input = keyedAlgebra postings++-- | Keyed fixtures use the test suite's existing Int basis instances.+keyedFixtures :: (Element Int, BaseClass Int) => [(FilePath, Text)]+keyedFixtures =+ [ ("alg-keyed-double-" ++ T.unpack name ++ ".tsv"+ , renderRows ("alg-keyed-double-" <> name) (keyedRows (series name values)))+ | (name, values) <- doubleSeries+ ]+ ++ [ ("alg-keyed-decimal-" ++ T.unpack name ++ ".tsv"+ , renderRows ("alg-keyed-decimal-" <> name) (keyedRows (series name values)))+ | (name, values) <- decimalSeries+ ]+ where+ doubleSeries :: [(Text, [Double])]+ doubleSeries =+ [ ("f-int", [1, 2, 3, 100, 12345])+ , ("f-dec2", [0.01, 0.1, 0.2, 0.3, 19.99, 1234.56])+ , ("f-cancel", [1e16, 1, 9007199254740992, 9007199254740991])+ , ("f-exp", [1e300, 1e-300, 1, 1.7e308])+ , ("f-sub", [5e-324, 2.2250738585072014e-308, 1])+ ]+ decimalSeries :: [(Text, [MoneyDecimal])]+ decimalSeries =+ [ ("f-int", [1, 2, 3, 100, 12345])+ , ("f-dec2", [0.01, 0.1, 0.2, 0.3, 19.99, 1234.56])+ ]++-- | Enumerate all account-only and journal fixtures.+readoutFixtures :: [(FilePath, Text)]+readoutFixtures =+ [ ("alg-double-" ++ T.unpack name ++ ".tsv"+ , renderRows ("alg-double-" <> name) (algRows name postings))+ | (name, values) <- doubleSeries+ , let postings = series name values+ ]+ ++ [ ("journal-double-" ++ T.unpack name ++ ".tsv"+ , renderRows ("journal-double-" <> name)+ (journalReadoutRows postings ++ doubleCarryRows name postings))+ | (name, values) <- doubleSeries+ , let postings = series name values+ ]+ ++ [ ("alg-decimal-" ++ T.unpack name ++ ".tsv"+ , renderRows ("alg-decimal-" <> name) (algRows name postings))+ | (name, values) <- decimalSeries+ , let postings = series name values+ ]+ ++ [ ("journal-decimal-" ++ T.unpack name ++ ".tsv"+ , renderRows ("journal-decimal-" <> name) (journalReadoutRows postings))+ | (name, values) <- decimalSeries+ , let postings = series name values+ ]+ where+ doubleSeries :: [(Text, [Double])]+ doubleSeries =+ [ ("f-int", [1, 2, 3, 100, 12345])+ , ("f-dec2", [0.01, 0.1, 0.2, 0.3, 19.99, 1234.56])+ , ("f-cancel", [1e16, 1, 9007199254740992, 9007199254740991])+ , ("f-exp", [1e300, 1e-300, 1, 1.7e308])+ , ("f-sub", [5e-324, 2.2250738585072014e-308, 1])+ ]+ decimalSeries :: [(Text, [MoneyDecimal])]+ decimalSeries =+ [ ("f-int", [1, 2, 3, 100, 12345])+ , ("f-dec2", [0.01, 0.1, 0.2, 0.3, 19.99, 1234.56])+ ]
+ test/Golden/WireFormat.hs view
@@ -0,0 +1,371 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Freeze the public Binary encodings before the P2 module move.+module Golden.WireFormat+ ( fixtureDir+ , renderFixture+ , checkFixture+ ) where++import ExchangeAlgebra.Accounting.Account+import qualified Data.Binary as Binary+import qualified Data.Binary.Put as Put+import qualified Data.ByteString.Lazy as BL+import qualified Data.HashMap.Strict as HM+import qualified Data.Sequence as Seq+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Data.Text (Text)+import Data.Proxy (Proxy(..))+import GHC.Float (castWord64ToDouble)+import Numeric (readHex, showHex)+import System.Environment (lookupEnv)+import Control.Monad (forM_, unless)+import Data.Decimal (DecimalRaw(Decimal))++import ExchangeAlgebra.Algebra.Base+import qualified ExchangeAlgebra.Algebra.Internal as EA+import qualified ExchangeAlgebra.Algebra.Transfer.Rule as Rule+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Journal as Journal+import qualified ExchangeAlgebra.Journal.Core as JournalCore+import qualified ExchangeAlgebra.Simulate.Policy as Policy+import Test.QuickCheck ( choose+ , elements+ , forAll+ , listOf+ )+import Support (assertEqual, quickProp)+import ExchangeAlgebra.Algebra.Posting (Posted, PostSide(..), posted)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal(..), MoneyDouble(..))++-- | Revision whose encodings are recorded in the fixture.+baselineCommit :: Text+baselineCommit = "129c06772425522028eb1c91c986e530c6b00f09"++-- | Committed fixture directory.+fixtureDir :: FilePath+fixtureDir = "test/fixtures/wire-format-p2"++-- | A value and its decoder comparison. Journal has no Eq instance.+data WireCase = forall a. Binary.Binary a => WireCase Text Text a (a -> a -> Bool)++type WireBase = HatBase AccountTitles++-- | Representative values include each constructor and redundant sequences.+cases :: [WireCase]+cases =+ [ eq "Hat" "Hat" Hat+ , eq "Hat" "Not" Not+ , eq "Hat" "HatNot" HatNot+ , eq "BaseForSingleHat" "sole constructor" BaseForSingleHat+ , eq "HatBase" "Hat Cash" (Hat :< Cash)+ , eq "HatBase" "Not Sales" (Not :< Sales)+ , eq "HatBase" "wildcard account" (HatNot :< AccountTitle)+ ] ++ [eq "AccountTitles" (T.pack (show title)) title+ | title <- [Cash .. AccountTitle]] +++ [ eq "CountUnit" "Yen" Yen+ , eq "CountUnit" "Dollar" Dollar+ , eq "CountUnit" "Euro" Euro+ , eq "CountUnit" "CNY" CNY+ , eq "CountUnit" "Amount" Amount+ , eq "CountUnit" "wildcard" CountUnit+ , eq "Pair" "empty" (EA.Pair Seq.empty Seq.empty :: EA.Pair Double)+ , eq "Pair" "Hat only" (EA.Pair (Seq.singleton 1) Seq.empty :: EA.Pair Double)+ , eq "Pair" "Not only" (EA.Pair Seq.empty (Seq.singleton 2) :: EA.Pair Double)+ , eq "Pair" "both sides and repeated values"+ (EA.Pair (Seq.fromList [1, 1, 3]) (Seq.fromList [2, 4]) :: EA.Pair Double)+ , eq "Alg" "Zero" (EA.Zero :: EA.Alg Double WireBase)+ , eq "Alg" "singleton" (1 `EA.Single` (Hat :< Cash) :: EA.Alg Double WireBase)+ , eq "Alg" "repeated base on both sides"+ (EA.fromList [1 `EA.Single` (Hat :< Cash), 2 `EA.Single` (Hat :< Cash)+ , 3 `EA.Single` (Not :< Cash), 4 `EA.Single` (Not :< Sales)]+ :: EA.Alg Double WireBase)+ , eq "TransferScale" "Relabel" (Rule.Relabel :: Rule.TransferScale Double)+ , eq "TransferScale" "MulBy" (Rule.MulBy 2 :: Rule.TransferScale Double)+ , eq "TransferScale" "DivBy" (Rule.DivBy 0.5 :: Rule.TransferScale Double)+ , eq "TransferRule" "relabel" (Rule.relabel (Hat :< Cash) (Not :< Sales)+ :: Rule.TransferRule Double WireBase)+ , eq "TransferRule" "multiply" (Rule.TransferRule (Not :< Sales)+ (Hat :< Cash) (Rule.MulBy 2) :: Rule.TransferRule Double WireBase)+ , eq "TransferRules" "empty"+ (validated [] :: Rule.TransferRules Double WireBase)+ , eq "TransferRules" "one" (validated [ruleA])+ , eq "TransferRules" "two sorted rules" (validated [ruleB, ruleA])+ , eq "ClosingSide" "keep" Closing.ClosingKeep+ , eq "ClosingSide" "flip" Closing.ClosingFlip+ , journalCase "Journal" "empty"+ (Journal.fromMap HM.empty :: Journal.Journal Int Double WireBase)+ , journalCase "Journal" "two notes and repeated base"+ (Journal.fromMap (HM.fromList [(1, repeated), (2, singleton)])+ :: Journal.Journal Int Double WireBase)+ , eq "Posted" "zero" (checked 0)+ , eq "Posted" "subnormal" (checked 5e-324)+ , eq "Posted" "upper bound" (checked (2 ^ (900 :: Int)))+ , eq "PostSide" "HatSide" HatSide+ , eq "PostSide" "NotSide" NotSide+ , eq "MoneyDecimal" "integer" (MoneyDecimal (Decimal 0 12))+ , eq "MoneyDecimal" "two places" (MoneyDecimal (Decimal 2 1234))+ , eq "MoneyDecimal" "many places" (MoneyDecimal (Decimal 8 1))+ , eq "MoneyDouble" "zero" (MoneyDouble 0)+ , eq "MoneyDouble" "subnormal" (MoneyDouble 5e-324)+ , eq "MoneyDouble" "large finite" (MoneyDouble 1e300)+ ]+ where+ repeated = EA.fromList [1 `EA.Single` (Hat :< Cash), 2 `EA.Single` (Hat :< Cash)+ , 3 `EA.Single` (Not :< Cash)]+ singleton = EA.fromList [4 `EA.Single` (Not :< Sales)]+ ruleA = Rule.relabel (Hat :< Cash) (Hat :< Sales)+ ruleB = Rule.relabel (Not :< Cash) (Not :< Sales)+ validated :: [Rule.TransferRule Double WireBase] -> Rule.TransferRules Double WireBase+ validated rules = case Rule.mkTransferRules rules of+ Right value -> value+ Left _ -> error "wire fixture: expected valid rules"+ checked :: Double -> Posted Double+ checked value = case posted value of+ Right result -> result+ Left _ -> error "wire fixture: expected valid Posted"++eq :: (Eq a, Binary.Binary a) => Text -> Text -> a -> WireCase+eq name description value = WireCase name description value (==)++journalCase :: Binary.Binary a => Text -> Text -> a -> WireCase+journalCase name description value = WireCase name description value+ (\expected actual -> Binary.encode expected == Binary.encode actual)++-- | Encode a byte sequence as two lowercase hex digits per byte.+hex :: BL.ByteString -> Text+hex = T.pack . concatMap byteHex . BL.unpack+ where+ byteHex byte = case showHex byte "" of+ [digit] -> ['0', digit]+ digits -> digits++unhex :: Text -> Maybe BL.ByteString+unhex input+ | odd (T.length input) = Nothing+ | otherwise = BL.pack <$> traverse parseByte (pairs (T.unpack input))+ where+ pairs [] = []+ pairs (first:second:rest) = [first, second] : pairs rest+ pairs _ = []+ parseByte digits = case readHex digits of+ [(number, "")] | number <= 255 -> Just (fromIntegral (number :: Int))+ _ -> Nothing++-- | Schema row, followed by type, description, and encoded bytes.+renderFixture :: Text+renderFixture = T.unlines $ header : map render cases+ where+ header = "# wire-format-p2; schema 1; commit " <> baselineCommit+ render (WireCase name description value _) =+ T.intercalate "\t" [name, description, hex (Binary.encode value)]++-- | Compare committed bytes, decode them, and pin checked decoder failures.+checkFixture :: IO ()+checkFixture = do+ let path = fixtureDir ++ "/wire-format.tsv"+ boundaryPath = fixtureDir ++ "/decode-boundaries.tsv"+ unless (length cases == 287 && length rejected == 23 && length accepted == 1)+ (fail "wire-format-p2: case count differs")+ regen <- lookupEnv "EA_REGEN_GOLDEN"+ case regen of+ Just "1" -> do+ TIO.writeFile path renderFixture+ TIO.writeFile boundaryPath renderBoundaries+ _ -> do+ actual <- TIO.readFile path+ unless (actual == renderFixture) (fail "wire-format-p2: encoded bytes differ")+ let rows = drop 1 (T.lines actual)+ unless (length rows == length cases) (fail "wire-format-p2: row count differs")+ forM_ (zip cases rows) checkRow+ boundaries <- TIO.readFile boundaryPath+ unless (boundaries == renderBoundaries)+ (fail "wire-format-p2: decode boundary bytes differ")+ forM_ rejected checkRejected+ forM_ accepted checkAccepted+ checkWireLaws++checkRow :: (WireCase, Text) -> IO ()+checkRow (WireCase name description expected same, row) =+ case T.splitOn "\t" row of+ [rowName, rowDescription, bytesText]+ | rowName == name && rowDescription == description ->+ case unhex bytesText of+ Nothing -> fail "wire-format-p2: invalid hex"+ Just bytes -> case Binary.decodeOrFail bytes of+ Left (_, _, message) -> fail ("wire-format-p2: decode failed: " ++ message)+ Right (rest, _, actual) -> do+ unless (BL.null rest) (fail "wire-format-p2: trailing bytes")+ unless (same expected actual)+ (fail "wire-format-p2: decoded value differs")+ _ -> fail "wire-format-p2: invalid row"++data DecodeCase = forall a. Binary.Binary a => DecodeCase String BL.ByteString (Proxy a)++renderBoundaries :: Text+renderBoundaries = T.unlines $ header : map renderRejected rejected+ ++ map renderAccepted accepted+ where+ header = "# wire-format-p2 boundaries; schema 1; commit " <> baselineCommit+ renderRejected (DecodeCase name bytes _) = T.intercalate "\t"+ ["reject", T.pack name, hex bytes]+ renderAccepted (DecodeCase name bytes _) = T.intercalate "\t"+ ["accept", T.pack name, hex bytes]++-- Checked decoders reject invalid values and rule sets.+rejected :: [DecodeCase]+rejected =+ [ DecodeCase "Posted negative" (Binary.encode (-1 :: Double)) (Proxy :: Proxy (Posted Double))+ , DecodeCase "Posted infinity" (Binary.encode (1 / 0 :: Double)) (Proxy :: Proxy (Posted Double))+ , DecodeCase "Posted above bound" (Binary.encode (2 ^ (901 :: Int) :: Double))+ (Proxy :: Proxy (Posted Double))+ , DecodeCase "TransferRules zero coefficient"+ (Binary.encode [Rule.TransferRule (Hat :< Cash) (Hat :< Sales)+ (Rule.MulBy (0 :: Double))])+ (Proxy :: Proxy (Rule.TransferRules Double WireBase))+ , DecodeCase "TransferRules overlap"+ (Binary.encode ([ Rule.relabel (Hat :< Cash) (Hat :< Sales)+ , Rule.relabel (Hat :< Cash) (Hat :< Sales)]+ :: [Rule.TransferRule Double WireBase])+ :: BL.ByteString) (Proxy :: Proxy (Rule.TransferRules Double WireBase))+ , DecodeCase "AccountTitles unused code" (Put.runPut (Put.putWord16be 0x80F3))+ (Proxy :: Proxy AccountTitles)+ , DecodeCase "AccountTitles old code" (Put.runPut (Put.putWord16be 0x0000))+ (Proxy :: Proxy AccountTitles)+ , DecodeCase "Alg invalid tag" (Binary.encode (3 :: Int))+ (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg negative value"+ (Binary.encode (1 :: Int) <> Binary.encode (-1 :: Double)+ <> Binary.encode (Hat :< Cash)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Journal negative value"+ (Binary.encode (1 :: Int) <> Binary.encode (1 :: Int)+ <> Binary.encode (1 :: Int) <> Binary.encode (-1 :: Double)+ <> Binary.encode (Hat :< Cash))+ (Proxy :: Proxy (Journal.Journal Int Double WireBase))+ , DecodeCase "Alg NaN" (Binary.encode (1 :: Int) <> Binary.encode portableNaN+ <> Binary.encode (Hat :< Cash)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg infinity" (Binary.encode (1 :: Int) <> Binary.encode (1 / 0 :: Double)+ <> Binary.encode (Not :< Cash)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg HatNot" (Binary.encode (1 :: Int) <> Binary.encode (1 :: Double)+ <> Binary.encode (HatNot :< Cash)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg zero HatNot" (Binary.encode (1 :: Int) <> Binary.encode (0 :: Double)+ <> Binary.encode (HatNot :< Cash)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg repeated base key"+ (Binary.encode (2 :: Int) <> Binary.encode (2 :: Int)+ <> Binary.encode Cash <> Binary.encode (EA.Pair (Seq.singleton 1) Seq.empty+ :: EA.Pair Double)+ <> Binary.encode Cash <> Binary.encode (EA.Pair Seq.empty (Seq.singleton 2)+ :: EA.Pair Double)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Alg repeated dropped key"+ (Binary.encode (2 :: Int) <> Binary.encode (2 :: Int)+ <> Binary.encode Cash <> Binary.encode (EA.Pair (Seq.singleton 0) Seq.empty+ :: EA.Pair Double)+ <> Binary.encode Cash <> Binary.encode (EA.Pair Seq.empty Seq.empty+ :: EA.Pair Double)) (Proxy :: Proxy (EA.Alg Double WireBase))+ , DecodeCase "Journal repeated note"+ (Binary.encode (2 :: Int) <> Binary.encode (1 :: Int)+ <> Binary.encode (EA.Zero :: EA.Alg Double WireBase)+ <> Binary.encode (1 :: Int) <> Binary.encode (EA.Zero :: EA.Alg Double WireBase))+ (Proxy :: Proxy (Journal.Journal Int Double WireBase))+ ] +++ [ DecodeCase ("Alg Pair " ++ side ++ " " ++ description)+ (Binary.encode (2 :: Int) <> Binary.encode (1 :: Int)+ <> Binary.encode Cash <> Binary.encode pair)+ (Proxy :: Proxy (EA.Alg Double WireBase))+ | (description, value) <- [("negative", -1), ("NaN", portableNaN), ("infinity", 1 / 0)]+ , (side, pair) <-+ [ ("Hat", EA.Pair (Seq.fromList [0, value]) (Seq.singleton 0) :: EA.Pair Double)+ , ("Not", EA.Pair (Seq.singleton 0) (Seq.fromList [0, value]) :: EA.Pair Double)+ ]+ ]++-- A quiet NaN with a fixed bit pattern. @0 / 0@ sets the sign bit on x86_64+-- but not on arm64, and the sign changes the encoded bytes.+portableNaN :: Double+portableNaN = castWord64ToDouble 0x7FF8000000000000++checkRejected :: DecodeCase -> IO ()+checkRejected (DecodeCase name bytes (_ :: Proxy a)) = case Binary.decodeOrFail bytes of+ Left _ -> pure ()+ Right (_, _, (_ :: a)) -> fail ("wire-format-p2: unexpected acceptance: " ++ name)++-- The wildcard uses its fixed code rather than an ordinal.+accepted :: [DecodeCase]+accepted =+ [ DecodeCase "AccountTitles wildcard" (Put.runPut (Put.putWord16be 0xFFFF))+ (Proxy :: Proxy AccountTitles)+ ]++checkAccepted :: DecodeCase -> IO ()+checkAccepted (DecodeCase name bytes (_ :: Proxy a)) = case Binary.decodeOrFail bytes of+ Left _ -> fail ("wire-format-p2: unexpected rejection: " ++ name)+ Right (_, _, (_ :: a)) -> pure ()++-- | B1 uses valid Hat/Not postings only and observes structural equality,+-- including each sequence's order. B3 checks normalization before any sorting.+checkWireLaws :: IO ()+checkWireLaws = do+ quickProp "B1 Binary roundtrip preserves Alg sequences and Journal note maps" $+ forAll (listOf ((,,,,) <$> choose (0, 10000 :: Integer)+ <*> elements [Hat, Not]+ <*> elements [Cash, Sales, AccountTitle]+ <*> elements [Yen, Dollar, CountUnit]+ <*> choose (0, 3 :: Int))) $ \rows ->+ let doubleAccounts = [(n, fromIntegral v / 100 EA..@ h :< b)+ | (v, h, b, _, n) <- rows]+ :: [(Int, EA.Alg Double WireBase)]+ decimalAccounts = [(n, fromIntegral v / 100 EA..@ h :< b)+ | (v, h, b, _, n) <- rows]+ :: [(Int, EA.Alg MoneyDecimal WireBase)]+ doubleUnits = [(n, fromIntegral v / 100 EA..@ h :< b)+ | (v, h, _, b, n) <- rows]+ :: [(Int, EA.Alg Double (HatBase CountUnit))]+ decimalUnits = [(n, fromIntegral v / 100 EA..@ h :< b)+ | (v, h, _, b, n) <- rows]+ :: [(Int, EA.Alg MoneyDecimal (HatBase CountUnit))]+ in law doubleAccounts && law decimalAccounts && law doubleUnits && law decimalUnits+ let singletonZero = Binary.encode (1 :: Int) <> Binary.encode (0 :: Double)+ <> Binary.encode (Hat :< Cash)+ pairBytes = Binary.encode (2 :: Int) <> Binary.encode (2 :: Int)+ <> Binary.encode Cash <> Binary.encode+ (EA.Pair (Seq.fromList [3, 0, 1, 0, 2]) (Seq.fromList [0, 5, 4, 0])+ :: EA.Pair Double)+ <> Binary.encode Sales <> Binary.encode+ (EA.Pair (Seq.singleton 0) Seq.empty :: EA.Pair Double)+ expected = EA.linerFromMap $ HM.singleton Cash+ (EA.Pair (Seq.fromList [3, 1, 2]) (Seq.fromList [5, 4]) :: EA.Pair Double)+ emptyPair = Binary.encode (2 :: Int) <> Binary.encode (1 :: Int)+ <> Binary.encode Cash <> Binary.encode+ (EA.Pair (Seq.singleton 0) (Seq.singleton 0) :: EA.Pair Double)+ emptyLiner = Binary.encode (2 :: Int) <> Binary.encode (0 :: Int)+ journalZero = Binary.encode (1 :: Int) <> Binary.encode (7 :: Int) <> singletonZero+ assertEqual "B3 singleton zero" (EA.Zero :: EA.Alg Double WireBase)+ (Binary.decode singletonZero)+ assertEqual "B3 zeros removed, empty base dropped, sequence order preserved"+ (expected :: EA.Alg Double WireBase) (Binary.decode pairBytes)+ forM_ [emptyPair, emptyLiner] $ \bytes ->+ assertEqual "B3 empty tag 2 becomes Zero" (EA.Zero :: EA.Alg Double WireBase)+ (Binary.decode bytes)+ assertEqual "B3 Journal drops a zero note" HM.empty+ (Journal.toMap (Binary.decode journalZero :: Journal.Journal Int Double WireBase))+ assertEqual "HatNot standalone base roundtrip" (HatNot :< Cash)+ (Binary.decode (Binary.encode (HatNot :< Cash)))+ let note = ("buy" :: String, 7 :: Int)+ oldTerm :: JournalCore.HasTermAxis n => n -> JournalCore.TermOf n+ oldTerm = Policy.termOf+ assertEqual "HasTermAxis old and new paths share the same instance"+ (JournalCore.termOf note) (oldTerm note)+ where+ law :: ( EA.HatVal v, HatBaseClass b, Binary.Binary v+ , Binary.Binary b, Binary.Binary (BasePart b)) => [(Int, EA.Alg v b)] -> Bool+ law entries =+ let postings = map snd entries+ algebras = [EA.fromList postings, foldr (EA..+) EA.Zero postings]+ journal = Journal.fromList [alg Journal..| note | (note, alg) <- entries]+ in all (\alg -> Binary.decode (Binary.encode alg) == alg) algebras+ && Journal.toMap (Binary.decode (Binary.encode journal)) == Journal.toMap journal
test/Golden/WriteRows.hs view
@@ -13,15 +13,14 @@ import Data.Time (Day, fromGregorian) import ExchangeAlgebra-import ExchangeAlgebra.Bookkeeping+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import ExchangeAlgebra.Accounting.Entries import qualified ExchangeAlgebra.Journal as EJ-import ExchangeAlgebra.Write+import ExchangeAlgebra.IO.Output.Statements ( accountLedgerRows , accountLedgerRowsJournal- , bsRows , compoundTrialBalanceRows , journalRows- , plRows , postClosingTrialBalanceRows , worksheetRows )@@ -86,12 +85,6 @@ , fixture "postClosingTrialBalanceRows ex9AfterClosing" (postClosingTrialBalanceRows ex9AfterClosing) )- , ( "ebex9-bs.tsv"- , fixture "bsRows ex9AfterClosing" (bsRows ex9AfterClosing)- )- , ( "ebex9-pl.tsv"- , fixture "plRows ex9PostAdjustment" (plRows ex9PostAdjustment)- ) ] -- ebex6 第9-15章: 現金預金, 商品売買, 債権債務の全28仕訳 (元の順序を維持).@@ -416,21 +409,9 @@ ex9PostAdjustment :: MinTransaction ex9PostAdjustment = ex9PreAdjustment .+ ex9Adjustments -ex9NominalLedger :: MinTransaction-ex9NominalLedger = ExchangeAlgebra.filter isNominal ex9PostAdjustment- where- isNominal x = let division = (whatDiv . _hatBase) x- in division == Cost || division == Revenue--ex9IncomeSummary :: MinTransaction-ex9IncomeSummary = incomeSummaryAccount ex9NominalLedger-+-- | Add checked closing entries; the frozen post-closing TSV remains the oracle.+-- Invariant: the fixed fixture has finite non-negative values and representable balances. ex9AfterClosing :: MinTransaction-ex9AfterClosing = realLedger .+ netIncomeTransfer- ( projByAccountTitle NetIncome ex9IncomeSummary- .+ projByAccountTitle NetLoss ex9IncomeSummary- )- where- realLedger = ExchangeAlgebra.filter isReal ex9PostAdjustment- isReal x = let division = (whatDiv . _hatBase) x- in division == Assets || division == Liability || division == Equity+ex9AfterClosing = case Closing.closingEntries ex9PostAdjustment of+ Left failure -> error ("ex9 closing invariant: " ++ show failure)+ Right entries -> ex9PostAdjustment .+ entries
+ test/IO/InputOutputSpec.hs view
@@ -0,0 +1,830 @@+-- | CSV round trips, output bytes, and checked input construction.+-- Tests exercise the IO layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module IO.InputOutputSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.IO.Input as EC+import qualified ExchangeAlgebra.IO.Input as ECC+import qualified ExchangeAlgebra.Accounting.Account as PP+import qualified ExchangeAlgebra.IO.Input.Csv as ECsv+import qualified ExchangeAlgebra.Accounting.Account as Registry+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Journal as EJ+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified ExchangeAlgebra.Simulation.Output as RenderSimulation+import qualified ExchangeAlgebra.IO.Output.Statements as EW+import qualified ExchangeAlgebra.IO.Output.Csv as EW+import qualified Data.List as L+import qualified Data.List.NonEmpty as NE+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Char8 as BL8+import qualified Data.Text as T+import Control.Monad (forM_)+import Control.Monad.ST (stToIO)+import Data.STRef (STRef+ , newSTRef+ , readSTRef+ , modifySTRef'+ )+import System.Exit (exitFailure)+import Data.Time (Day, fromGregorian)+import Test.QuickCheck hiding (Fixed)+import Support (assertEqual+ , readFileStrict+ , quickProp+ , CheckedAlgM+ , exactBalancedForTest+ , withTestTemporaryFile+ )++-- ================================================================+-- CSV Write tests+-- ================================================================++testCsvTranspose :: IO ()+testCsvTranspose = do+ -- Square matrix+ let input1 = [ [T.pack "a", T.pack "b"]+ , [T.pack "c", T.pack "d"] ]+ expected1 = [ [T.pack "a", T.pack "c"]+ , [T.pack "b", T.pack "d"] ]+ assertEqual "CSV.transpose square matrix" expected1 (EW.csvTranspose input1)++ -- Ragged matrix (shorter rows padded with empty)+ let input2 = [ [T.pack "a", T.pack "b", T.pack "c"]+ , [T.pack "d"] ]+ expected2 = [ [T.pack "a", T.pack "d"]+ , [T.pack "b", T.empty]+ , [T.pack "c", T.empty] ]+ assertEqual "CSV.transpose ragged matrix" expected2 (EW.csvTranspose input2)++ -- Single row+ let input3 = [[T.pack "x", T.pack "y", T.pack "z"]]+ expected3 = [[T.pack "x"], [T.pack "y"], [T.pack "z"]]+ assertEqual "CSV.transpose single row" expected3 (EW.csvTranspose input3)++ -- Empty+ assertEqual "CSV.transpose empty" ([] :: [[T.Text]]) (EW.csvTranspose [])+++newtype FuncResultsWorld s = FuncResultsWorld (STRef s Int)++testWriteFuncResultsCsv :: IO ()+testWriteFuncResultsCsv =+ withTestTemporaryFile $ \directPath ->+ withTestTemporaryFile $ \contextPath ->+ withTestTemporaryFile $ \emptyPath -> do+ let headers = L.map T.pack ["plain", "a,b", "q\"x", "line\nbreak", "carriage\rreturn"]+ directFuncs =+ zip headers+ [ \_ t -> pure (t * 10 + 1 :: Int)+ , \_ t -> pure (t * 10 + 2 :: Int)+ , \_ t -> pure (t * 10 + 3 :: Int)+ , \_ t -> pure (t * 10 + 4 :: Int)+ , \_ t -> pure (t * 10 + 5 :: Int)+ ]+ contextFuncs =+ zip headers+ [ \t -> pure (t * 10 + 1 :: Int)+ , \t -> pure (t * 10 + 2 :: Int)+ , \t -> pure (t * 10 + 3 :: Int)+ , \t -> pure (t * 10 + 4 :: Int)+ , \t -> pure (t * 10 + 5 :: Int)+ ]+ expected = BL8.pack+ ( "Time,plain,\"a,b\",\"q\"\"x\",\"line\nbreak\",\"carriage\rreturn\"\n"+ ++ "1,11,12,13,14,15\n"+ ++ "2,21,22,23,24,25\n"+ )+ expectedHeader = BL8.pack+ "Time,plain,\"a,b\",\"q\"\"x\",\"line\nbreak\",\"carriage\rreturn\"\n"++ world@(FuncResultsWorld counter) <- stToIO (FuncResultsWorld <$> newSTRef 0)+ RenderSimulation.writeFuncResults directFuncs (1 :: Int, 2) world directPath+ RenderSimulation.writeFuncResultsWithContext+ (\(FuncResultsWorld ref) t -> modifySTRef' ref (+ 1) >> pure t)+ contextFuncs+ (1 :: Int, 2)+ world+ contextPath+ RenderSimulation.writeFuncResults directFuncs (2 :: Int, 1) world emptyPath++ directBytes <- BL.readFile directPath+ contextBytes <- BL.readFile contextPath+ emptyBytes <- BL.readFile emptyPath+ buildCount <- stToIO (readSTRef counter)+ assertEqual "writeFuncResults CSV bytes" expected directBytes+ assertEqual "writeFuncResultsWithContext CSV bytes" expected contextBytes+ assertEqual "writeFuncResults variants produce identical bytes" directBytes contextBytes+ assertEqual "writeFuncResults empty range writes only the header" expectedHeader emptyBytes+ assertEqual "writeFuncResultsWithContext builds one context per term" 2 buildCount+++testWriteJournalPinned :: IO ()+testWriteJournalPinned = withTestTemporaryFile $ \path -> do+ let d1 = fromGregorian 2024 4 1+ d2 = fromGregorian 2024 4 2+ d3 = fromGregorian 2024 4 3+ getDay' :: HatBase (AccountTitles, Day) -> Day+ getDay' (_ :< (_, d)) = d+ alg = (100 .@ Not :< (Cash, d1))+ .+ (100 .@ Not :< (CapitalStock, d1))+ .+ (50 .@ Not :< (Cash, d2))+ .+ (50 .@ Not :< (Sales, d2))+ .+ (30 .@ Not :< (Cash, d3))+ .+ (10 .@ Not :< (AccountsReceivable, d3))+ .+ (40 .@ Not :< (Sales, d3))+ :: EA.Alg Double (HatBase (AccountTitles, Day))+ EW.writeJournal path alg getDay'+ raw <- readFileStrict path+ let lns = lines raw+ assertEqual "writeJournal pinned: line count" 5 (length lns)+ assertEqual "writeJournal pinned: header"+ "\"Day\",\"Debit\",\"Amount\",\"Credit\",\"Amount\"" (lns !! 0)+ assertEqual "writeJournal pinned: day1"+ "\"2024-04-01\",\"Cash\",\"100.0\",\"CapitalStock\",\"100.0\"" (lns !! 1)+ assertEqual "writeJournal pinned: day2"+ "\"2024-04-02\",\"Cash\",\"50.0\",\"Sales\",\"50.0\"" (lns !! 2)+ assertEqual "writeJournal pinned: day3 line1 (2 debits vs 1 credit -> toSameLength padding)"+ "\"2024-04-03\",\"AccountsReceivable\",\"10.0\",\"Sales\",\"40.0\"" (lns !! 3)+ assertEqual "writeJournal pinned: day3 line2 (padded Day/Credit cells empty)"+ "\"\",\"Cash\",\"30.0\",\"\",\"\"" (lns !! 4)++-- ================================================================+-- ExchangeAlgebra.IO.Input.Csv: generic journal CSV reader.+-- Read-only round-trip property: a generated list of postings rendered to a+-- fixed-schema CSV string parses back to exactly the term built directly by+-- journalFromSides (MoneyDecimal = exact, so strict equality, no tolerance).+-- ================================================================++-- concrete account titles only (no wildcard); use canonical Show names so the+-- CSV round-trip does not exercise the ambiguous-alias path.+genAccountTitle :: Gen AccountTitles+genAccountTitle = elements PP.concreteAccountTitles++genSideCsv :: Gen Side+genSideCsv = elements [Debit, Credit]++-- non-negative MoneyDecimal with up to 2 decimal places, written exactly as a+-- decimal literal (terminating) so scientificAmount parses it back exactly.+genAmountMD :: Gen (MoneyDecimal, T.Text)+genAmountMD = do+ whole <- choose (0, 99999) :: Gen Integer+ cents <- choose (0, 99) :: Gen Integer+ let txt = T.pack (show whole) <> T.pack "." <>+ T.pack (let s = show cents in if length s == 1 then '0':s else s)+ val = fromRational (toRational whole + toRational cents / 100) :: MoneyDecimal+ pure (val, txt)++genPostingCsv :: Gen (Side, AccountTitles, MoneyDecimal, T.Text)+genPostingCsv = do+ s <- genSideCsv+ a <- genAccountTitle+ (v, vtx) <- genAmountMD+ pure (s, a, v, vtx)++renderCsv :: [(Side, AccountTitles, MoneyDecimal, T.Text)] -> T.Text+renderCsv rows =+ T.unlines (header : L.map line rows)+ where+ header = T.pack "side,account,amount"+ line (s, a, _, vtx) =+ T.intercalate (T.pack ",")+ [ sideText s, T.pack (show a), vtx ]+ sideText Debit = T.pack "debit"+ sideText Credit = T.pack "credit"+ sideText Side = T.pack "debit" -- unused (generator never yields wildcard)++testConvertCsvRoundTrip :: IO ()+testConvertCsvRoundTrip = do+ quickProp "convert-csv: render -> parse is exact (MoneyDecimal)" $+ forAll (resize 30 (listOf genPostingCsv)) $ \rows ->+ let csv = renderCsv rows+ expected = EC.journalFromSides+ [ (s, a, v) | (s, a, v, _) <- rows ]+ :: EA.Alg MoneyDecimal (HatBase AccountTitles)+ parsed = ECsv.parseJournalCsv csv+ :: Either EC.ConvError+ (EA.Alg MoneyDecimal (HatBase AccountTitles))+ in parsed == Right expected++ -- structural guards: bad header, unknown account, negative amount, bad arity.+ let badHeader = T.pack "s,a,amt\ndebit,Cash,1\n"+ badAcct = T.pack "side,account,amount\ndebit,Goodwill_X,1\n"+ badAmt = T.pack "side,account,amount\ndebit,Cash,-1\n"+ badArity = T.pack "side,account,amount\ndebit,Cash\n"+ run t = ECsv.parseJournalCsv t+ :: Either EC.ConvError+ (EA.Alg MoneyDecimal (HatBase AccountTitles))+ expectLeft label pat t = case run t of+ Left e | pat e -> putStrLn ("[PASS] " ++ label)+ | otherwise -> do putStrLn ("[FAIL] " ++ label ++ ": wrong error " ++ show e); exitFailure+ Right _ -> do putStrLn ("[FAIL] " ++ label ++ ": accepted bad input"); exitFailure+ expectLeft "convert-csv: rejects bad header"+ (\e -> case e of EC.MalformedCsv _ -> True; _ -> False) badHeader+ expectLeft "convert-csv: rejects unknown account"+ (\e -> case e of EC.UnknownAccount _ -> True; _ -> False) badAcct+ expectLeft "convert-csv: rejects negative amount"+ (\e -> case e of EC.BadAmount _ -> True; _ -> False) badAmt+ expectLeft "convert-csv: rejects wrong field count"+ (\e -> case e of EC.MalformedCsv _ -> True; _ -> False) badArity++type CheckedJournalM = EJ.Journal Int MoneyDecimal (HatBase AccountTitles)++checkedEntryM :: [(Side, AccountTitles, MoneyDecimal)]+ -> Either (NE.NonEmpty (ECC.EntryError MoneyDecimal)) CheckedAlgM+checkedEntryM = ECC.checkedEntry++checkedJournalM :: [(Int, [(Side, AccountTitles, MoneyDecimal)])]+ -> Either (NE.NonEmpty (ECC.JournalError Int MoneyDecimal)) CheckedJournalM+checkedJournalM = ECC.checkedJournal++genPositiveAmountMD :: Gen MoneyDecimal+genPositiveAmountMD = fromInteger <$> choose (1, 9999)++genCheckedAmountMD :: Gen MoneyDecimal+genCheckedAmountMD = fromInteger <$> choose (-5, 20)++genCheckedSide :: Gen Side+genCheckedSide = frequency+ [ (8, elements [Debit, Credit])+ , (1, pure Side)+ ]++genCheckedAccountTitle :: Gen AccountTitles+genCheckedAccountTitle = frequency+ [ (12, genAccountTitle)+ , (1, pure AccountTitle)+ ]++genCheckedPosting :: Gen (Side, AccountTitles, MoneyDecimal)+genCheckedPosting =+ (,,) <$> genCheckedSide <*> genCheckedAccountTitle <*> genCheckedAmountMD++genAcceptedEntryRows :: Gen [(Side, AccountTitles, MoneyDecimal)]+genAcceptedEntryRows = do+ amount <- genPositiveAmountMD+ debitAccount <- genOrdinaryPostingTitle+ creditAccount <- genOrdinaryPostingTitle+ pure [ (Debit, debitAccount, amount)+ , (Credit, creditAccount, amount)+ ]++genOrdinaryPostingTitle :: Gen AccountTitles+genOrdinaryPostingTitle = elements+ [ title+ | title <- Registry.concreteAccountTitles+ , Just semantics <- [Registry.accountSemantics title]+ , Registry.asemPostingCapability semantics == OrdinaryPosting+ ]++genCheckedEntryRows :: Gen [(Side, AccountTitles, MoneyDecimal)]+genCheckedEntryRows = frequency+ [ (5, resize 8 (listOf genCheckedPosting))+ , (3, genAcceptedEntryRows)+ , (1, pure [])+ ]++genKnownCertPosting :: Gen (Side, AccountTitles, MoneyDecimal)+genKnownCertPosting =+ (,,) <$> elements [Debit, Credit] <*> genAccountTitle <*> genCheckedAmountMD++genKnownCertJournal :: Gen [(Int, [(Side, AccountTitles, MoneyDecimal)])]+genKnownCertJournal = do+ rows <- resize 6 (listOf (resize 6 (listOf genKnownCertPosting)))+ pure (zip [1..] rows)++sideTextForCert :: Side -> T.Text+sideTextForCert Debit = T.pack "debit"+sideTextForCert Credit = T.pack "credit"+sideTextForCert Side = T.pack "Side"++textJournalForCert+ :: [(Int, [(Side, AccountTitles, MoneyDecimal)])]+ -> [(Int, [(T.Text, T.Text, MoneyDecimal)])]+textJournalForCert = L.map renderEntry+ where+ renderEntry (txid, rows) = (txid, L.map renderPosting rows)+ renderPosting (side, account, amount) =+ (sideTextForCert side, T.pack (show account), amount)++checkedJournalTextReference+ :: [(Int, [(T.Text, T.Text, MoneyDecimal)])]+ -> Either (NE.NonEmpty (ECC.JournalError Int MoneyDecimal)) CheckedJournalM+checkedJournalTextReference entries =+ case errors of+ [] -> Right (L.foldl' (.+) mempty journals)+ e : es -> Left (e NE.:| es)+ where+ checked =+ [ (txid, ECC.checkedEntryText rows)+ | (txid, rows) <- entries+ ]+ errors =+ [ ECC.EntryErrors txid errs+ | (txid, Left errs) <- checked+ ]+ journals =+ [ alg .| txid+ | (txid, Right alg) <- checked+ ]++prop_certifyKnownAccountsMatchesCheckedJournal :: Property+prop_certifyKnownAccountsMatchesCheckedJournal =+ forAll genKnownCertJournal $ \entries ->+ let textEntries = textJournalForCert entries+ in case ( ECC.certifyJournalText textEntries+ , checkedJournalTextReference textEntries+ , checkedJournalM entries+ ) of+ (ECC.FullyResolved actual, Right textExpected, Right expected) ->+ EJ.toMap actual == EJ.toMap textExpected+ && EJ.toMap actual == EJ.toMap expected+ (ECC.Rejected _, Left _, Left _) -> True+ _ -> False++prop_certifyUnknownAccountPreservesBalance :: Property+prop_certifyUnknownAccountPreservesBalance =+ forAll genAcceptedEntryRows $ \rows ->+ let replaceAccount (side, _, amount) =+ (sideTextForCert side, T.pack "NoSuchAccount_XYZ", amount)+ in case ECC.certifyJournalText [(1 :: Int, L.map replaceAccount rows)] of+ ECC.BalancedUnresolved {} -> True+ _ -> False++prop_certifyImbalancePrecedesUnknownAccount :: Property+prop_certifyImbalancePrecedesUnknownAccount =+ forAll genPositiveAmountMD $ \amount ->+ let input =+ [ (1 :: Int,+ [ (T.pack "debit", T.pack "NoSuchAccount_XYZ", amount)+ , (T.pack "credit", T.pack "Cash", amount + 1)+ ])+ ]+ in case ECC.certifyJournalText input of+ ECC.Rejected errs -> any journalHasImbalance (NE.toList errs)+ _ -> False+ where+ journalHasImbalance (ECC.EntryErrors _ errs) =+ any isImbalanced (NE.toList errs)+ journalHasImbalance _ = False++ isImbalanced ECC.Imbalanced {} = True+ isImbalanced _ = False++prop_certifyDuplicateTxIdAlwaysRejected :: Property+prop_certifyDuplicateTxIdAlwaysRejected =+ forAll genAcceptedEntryRows $ \rows ->+ let input = textJournalForCert [(1, rows), (1, rows)]+ in case ECC.certifyJournalText input of+ ECC.Rejected errs -> ECC.DuplicateTxId 1 `elem` NE.toList errs+ _ -> False++prop_certifyBalancedUnresolvedTotals :: Property+prop_certifyBalancedUnresolvedTotals =+ forAll genPositiveAmountMD $ \amount ->+ let input =+ [ (1 :: Int,+ [ (T.pack "debit", T.pack "NoSuchAccount_XYZ", amount)+ , (T.pack "credit", T.pack "Sales", amount)+ ])+ ]+ in case ECC.certifyJournalText input of+ ECC.BalancedUnresolved+ { ECC._certDebitTotal = debitTotal+ , ECC._certCreditTotal = creditTotal+ } -> debitTotal == creditTotal+ && debitTotal == amount+ && creditTotal == amount+ _ -> False++checkedEntryAcceptsSpec :: [(Side, AccountTitles, MoneyDecimal)] -> Bool+checkedEntryAcceptsSpec rows =+ not (null rows)+ && all validPosting rows+ && exactBalancedForTest (EC.journalFromSides rows :: CheckedAlgM)+ where+ validPosting (side, account, amount) =+ side /= Side+ && account /= AccountTitle+ && maybe False+ (PP.postingAllowedIn PP.OrdinaryJournal+ . Registry.asemPostingCapability)+ (Registry.accountSemantics account)+ && amount > 0+ && not (EA.isErrorValue amount)+++-- | Check the closed posting policy and every checked-conversion entry point.+testPostingCapabilityGate :: IO ()+testPostingCapabilityGate = do+ let contexts =+ [ PP.OrdinaryJournal+ , PP.ClosingProcess+ , PP.ConsolidationWorksheet+ , PP.EngineComputation+ ]+ capabilities =+ [ OrdinaryPosting+ , ClosingOnly+ , ConsolidationOnly+ , EngineGeneratedOnly+ , NotPostable+ ]+ truthTable =+ [ (PP.OrdinaryJournal, OrdinaryPosting, True)+ , (PP.OrdinaryJournal, ClosingOnly, False)+ , (PP.OrdinaryJournal, ConsolidationOnly, False)+ , (PP.OrdinaryJournal, EngineGeneratedOnly, False)+ , (PP.OrdinaryJournal, NotPostable, False)+ , (PP.ClosingProcess, OrdinaryPosting, True)+ , (PP.ClosingProcess, ClosingOnly, True)+ , (PP.ClosingProcess, ConsolidationOnly, False)+ , (PP.ClosingProcess, EngineGeneratedOnly, False)+ , (PP.ClosingProcess, NotPostable, False)+ , (PP.ConsolidationWorksheet, OrdinaryPosting, True)+ , (PP.ConsolidationWorksheet, ClosingOnly, False)+ , (PP.ConsolidationWorksheet, ConsolidationOnly, True)+ , (PP.ConsolidationWorksheet, EngineGeneratedOnly, False)+ , (PP.ConsolidationWorksheet, NotPostable, False)+ , (PP.EngineComputation, OrdinaryPosting, True)+ , (PP.EngineComputation, ClosingOnly, False)+ , (PP.EngineComputation, ConsolidationOnly, False)+ , (PP.EngineComputation, EngineGeneratedOnly, True)+ , (PP.EngineComputation, NotPostable, False)+ ]+ assertEqual "posting policy: truth table enumerates every context/capability pair"+ [ (context, capability)+ | context <- contexts+ , capability <- capabilities+ ]+ [ (context, capability) | (context, capability, _) <- truthTable ]+ assertEqual "posting policy: 4 x 5 truth table of postingAllowedIn"+ [ (context, capability, expected)+ | (context, capability, expected) <- truthTable+ ]+ [ (context, capability, PP.postingAllowedIn context capability)+ | (context, capability, _) <- truthTable+ ]+ assertEqual "posting policy: wildcard title is NotPostable"+ NotPostable (PP.postingCapabilityFor AccountTitle)+ assertEqual "posting policy: concrete titles report registry capability"+ [ Registry.asemPostingCapability <$> Registry.accountSemantics title+ | title <- Registry.concreteAccountTitles+ ]+ [ Just (PP.postingCapabilityFor title)+ | title <- Registry.concreteAccountTitles+ ]+ assertEqual "posting gate: all 243 titles follow the closed matrix"+ [ (context, title, PP.postingAllowedIn context capability)+ | context <- contexts+ , title <- Registry.concreteAccountTitles+ , Just semantics <- [Registry.accountSemantics title]+ , let capability = Registry.asemPostingCapability semantics+ ]+ [ (context, title, accepted context title)+ | context <- contexts+ , title <- Registry.concreteAccountTitles+ ]+ assertEqual "posting gate: derived profit coordinates stay engine-only"+ (replicate 4 (Just EngineGeneratedOnly))+ [ Registry.asemPostingCapability <$> Registry.accountSemantics title+ | title <- [GrossProfit, OrdinaryProfit, NetIncome, NetLoss]+ ]+ assertEqual "posting gate: consolidation-only set is closed"+ [ EquityInEarningsOfInvestee+ , CumulativeTranslationAdjustment+ , NonControllingInterests+ , NetIncomeAttributableToNCI+ , NetLossAttributableToNCI+ ]+ [ title+ | title <- Registry.concreteAccountTitles+ , Just semantics <- [Registry.accountSemantics title]+ , Registry.asemPostingCapability semantics == ConsolidationOnly+ ]++ assertEqual "posting gate: ordinary wrapper rejects engine-generated result"+ (Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly+ PP.OrdinaryJournal NE.:| []))+ (checkedEntryM+ [ (Debit, NetIncome, 10)+ , (Credit, RetainedEarnings, 10)+ ])++ assertEqual "posting gate: closing admits IncomeSummary"+ True+ (case ECC.checkedEntryIn PP.ClosingProcess+ [ (Debit, Sales, 10 :: MoneyDecimal)+ , (Credit, IncomeSummary, 10)+ ] of+ Right _ -> True+ Left _ -> False)+ assertEqual "posting gate: closing rejects engine-generated result"+ True+ (case ECC.checkedEntryIn PP.ClosingProcess+ [ (Debit, NetIncome, 10 :: MoneyDecimal)+ , (Credit, RetainedEarnings, 10)+ ] of+ Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly+ PP.ClosingProcess NE.:| []) -> True+ _ -> False)++ assertEqual "posting gate: consolidation admits NCI attribution"+ True+ (case ECC.checkedEntryIn PP.ConsolidationWorksheet+ [ (Debit, NetIncomeAttributableToNCI, 10 :: MoneyDecimal)+ , (Credit, NonControllingInterests, 10)+ ] of+ Right _ -> True+ Left _ -> False)+ assertEqual "posting gate: ordinary journal rejects NCI equity"+ True+ (case ECC.checkedEntry+ [ (Debit, Cash, 10 :: MoneyDecimal)+ , (Credit, NonControllingInterests, 10)+ ] of+ Left (ECC.PostingNotAllowed 1 NonControllingInterests+ ConsolidationOnly PP.OrdinaryJournal NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: engine admits period result"+ True+ (case ECC.checkedEntryIn PP.EngineComputation+ [ (Debit, NetIncome, 10 :: MoneyDecimal)+ , (Credit, RetainedEarnings, 10)+ ] of+ Right _ -> True+ Left _ -> False)++ assertEqual "posting gate: text path uses ordinary context"+ True+ (case ECC.checkedEntryText+ [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "RetainedEarnings", 10)+ ] of+ Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly+ PP.OrdinaryJournal NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: unknown account does not create false imbalance"+ True+ (case ECC.checkedEntryText+ [ (T.pack "debit", T.pack "UnknownAccount_X", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "Cash", 10)+ ] of+ Left (ECC.EntryParse 0 _ NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: consolidation text path admits NCI loss"+ True+ (case ECC.checkedEntryTextIn PP.ConsolidationWorksheet+ [ (T.pack "debit", T.pack "NonControllingInterests", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "NetLossAttributableToNCI", 10)+ ] of+ Right _ -> True+ Left _ -> False)+ assertEqual "posting gate: journal error retains txid"+ True+ (case checkedJournalM+ [ (7,+ [ (Debit, IncomeSummary, 10)+ , (Credit, RetainedEarnings, 10)+ ])+ ] of+ Left (ECC.EntryErrors 7+ (ECC.PostingNotAllowed 0 IncomeSummary ClosingOnly+ PP.OrdinaryJournal NE.:| []) NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: certification rejects known disallowed title"+ True+ (case ECC.certifyJournalText+ [ (9 :: Int,+ [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "RetainedEarnings", 10)+ ])+ ] of+ ECC.Rejected+ (ECC.EntryErrors 9+ (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly+ PP.OrdinaryJournal NE.:| []) NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: disallowed known title outranks unresolved title"+ True+ (case ECC.certifyJournalText+ [ (11 :: Int,+ [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "UnknownAccount_X", 10)+ ])+ ] of+ ECC.Rejected+ (ECC.EntryErrors 11+ (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly+ PP.OrdinaryJournal NE.:| []) NE.:| []) -> True+ _ -> False)+ assertEqual "posting gate: certification honors closing context"+ True+ (case ECC.certifyJournalTextIn PP.ClosingProcess+ [ (10 :: Int,+ [ (T.pack "debit", T.pack "Sales", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "IncomeSummary", 10)+ ])+ ] of+ ECC.FullyResolved _ -> True+ _ -> False)+ assertEqual "posting gate: certification honors engine context"+ True+ (case ECC.certifyJournalTextIn PP.EngineComputation+ [ (12 :: Int,+ [ (T.pack "debit", T.pack "NetLoss", 10 :: MoneyDecimal)+ , (T.pack "credit", T.pack "RetainedEarnings", 10)+ ])+ ] of+ ECC.FullyResolved _ -> True+ _ -> False)+ where+ accepted context title =+ case ECC.checkedEntryIn context+ [ (Debit, title, 1 :: MoneyDecimal)+ , (Credit, Cash, 1)+ ] of+ Right _ -> True+ Left _ -> False++checkedConvertProperties :: IO ()+checkedConvertProperties = do+ testPostingCapabilityGate++ quickProp "convert-checked: certify known accounts matches checkedJournal" $+ prop_certifyKnownAccountsMatchesCheckedJournal++ quickProp "convert-checked: unresolved vocabulary preserves balance" $+ prop_certifyUnknownAccountPreservesBalance++ quickProp "convert-checked: imbalance precedes unresolved vocabulary" $+ prop_certifyImbalancePrecedesUnknownAccount++ quickProp "convert-checked: certify duplicate txid always rejected" $+ prop_certifyDuplicateTxIdAlwaysRejected++ quickProp "convert-checked: balanced unresolved totals match input" $+ prop_certifyBalancedUnresolvedTotals++ quickProp "convert-checked: checkedEntry accepts iff checked predicate" $+ forAll genCheckedEntryRows $ \rows ->+ let expected = checkedEntryAcceptsSpec rows+ actual = case checkedEntryM rows of+ Right _ -> True+ Left _ -> False+ in actual == expected++ quickProp "convert-checked: checkedEntry equals journalFromSides on accept" $+ forAll genCheckedEntryRows $ \rows ->+ case checkedEntryM rows of+ Right alg -> alg == (EC.journalFromSides rows :: CheckedAlgM)+ Left _ -> True++ quickProp "convert-checked: accepted entries form exact-balanced submonoid" $+ forAll genAcceptedEntryRows $ \rows1 ->+ forAll genAcceptedEntryRows $ \rows2 ->+ case (checkedEntryM rows1, checkedEntryM rows2) of+ (Right alg1, Right alg2) -> exactBalancedForTest (alg1 .+ alg2)+ _ -> False++ quickProp "convert-checked: checkedJournal duplicate txid only DuplicateTxId" $+ forAll genAcceptedEntryRows $ \rows1 ->+ forAll genAcceptedEntryRows $ \rows2 ->+ case checkedJournalM [(1, rows1), (1, rows2)] of+ Left errs -> NE.toList errs == [ECC.DuplicateTxId 1]+ Right _ -> False++ quickProp "convert-checked: reconcileSources coverage and amount checks" $+ forAll genPositiveAmountMD $ \amount ->+ let entry amt = [(Debit, Cash, amt), (Credit, Sales, amt)]+ shifted = amount + 1+ journalResult = checkedJournalM [(1, entry amount)]+ unknownResult = checkedJournalM [(1, entry amount), (2, entry 5)]+ in case (journalResult, unknownResult) of+ (Right journal, Right journalWithUnknown) ->+ ECC.reconcileSources [(1, amount)] journal == []+ && ECC.reconcileSources [(1, amount), (2, 5)] journal+ == [ECC.MissingSource 2]+ && ECC.reconcileSources [(1, amount)] journalWithUnknown+ == [ECC.UnknownSource 2]+ && ECC.reconcileSources [(1, shifted)] journal+ == [ECC.AmountMismatch 1 shifted amount]+ _ -> False+++-- | Check CSV quoting and statement-writer wiring using the original input and output rows.+testCsvWriteCases :: IO ()+testCsvWriteCases = do+ let cases =+ [ let+ input = [ [T.pack "Name", T.pack "Value"]+ , [T.pack "Alice", T.pack "100"]+ , [T.pack "Bob", T.pack "200"] ]+ in+ ( \path -> EW.writeCSV path input+ , Just ("CSV writeCSV line count", 3)+ , [ ("CSV writeCSV header", "\"Name\",\"Value\"", 0)+ , ("CSV writeCSV row 1", "\"Alice\",\"100\"", 1)+ , ("CSV writeCSV row 2", "\"Bob\",\"200\"", 2)+ ]+ )+ , let+ input = [[T.pack "say \"hello\"", T.pack "a,b"]]+ in+ ( \path -> EW.writeCSV path input+ , Nothing+ , [ ("CSV writeCSV escapes quotes", "\"say \"\"hello\"\"\",\"a,b\"", 0)+ ]+ )+ , let+ input = [[T.pack "", T.pack "x"]]+ in+ ( \path -> EW.writeCSV path input+ , Nothing+ , [ ("CSV writeCSV empty cell", "\"\",\"x\"", 0)+ ]+ )+ , let+ alg = (100 .@ Not :< Cash)+ .+ (60 .@ Not :< LoansPayable)+ .+ (40 .@ Not :< CapitalStock)+ :: EA.Alg Double (HatBase AccountTitles)+ in+ ( \path -> EW.writeCompoundTrialBalance path alg+ , Just ("writeCompoundTrialBalance pinned: line count", 5)+ , [ ("writeCompoundTrialBalance pinned: header", "\"Debit Balance\",\"Debit Total\",\"Account Title\",\"Credit Total\",\"Credit Balance\"", 0)+ , ("writeCompoundTrialBalance pinned: Cash (debit-heavy -> Debit Balance col)", "\"100.0\",\"100.0\",\"Cash\",\"0.0\",\"\"", 1)+ , ("writeCompoundTrialBalance pinned: CapitalStock (credit-heavy -> Credit Balance col)", "\"\",\"0.0\",\"CapitalStock\",\"40.0\",\"40.0\"", 2)+ , ("writeCompoundTrialBalance pinned: LoansPayable (credit-heavy -> Credit Balance col)", "\"\",\"0.0\",\"LoansPayable\",\"60.0\",\"60.0\"", 3)+ , ("writeCompoundTrialBalance pinned: totals", "\"100.0\",\"100.0\",\"Total\",\"100.0\",\"100.0\"", 4)+ ]+ )+ , let+ jrn = ((100 .@ Not :< Cash) .| "sale")+ .+ ((40 .@ Hat :< Cash) .| "pay")+ :: Journal String Double (HatBase AccountTitles)+ in+ ( \path -> EW.writeAccountOfJournal [Cash] path jrn+ , Just ("writeAccountOfJournal pinned: line count", 4)+ , [ ("writeAccountOfJournal pinned: title header", "\"Cash\",\"\",\"\"", 0)+ , ("writeAccountOfJournal pinned: sub header", "\"Note\",\"Debit\",\"Credit\"", 1)+ , ("writeAccountOfJournal pinned: note order (\"pay\" < \"sale\")", "\"\"\"pay\"\"\",\"\",\"40.0\"", 2)+ , ("writeAccountOfJournal pinned: sale posting", "\"\"\"sale\"\"\",\"100.0\",\"\"", 3)+ ]+ )+ ]+ forM_ cases $ \(writeRows, expectedCount, expectedRows) ->+ withTestTemporaryFile $ \path -> do+ writeRows path+ observed <- lines <$> readFileStrict path+ case expectedCount of+ Just (label, count) -> assertEqual label count (length observed)+ Nothing -> pure ()+ forM_ expectedRows $ \(label, expected, index) ->+ case drop index observed of+ actual : _ -> assertEqual label expected actual+ [] -> do+ putStrLn ("[FAIL] " ++ label ++ ": missing output row")+ exitFailure++-- | Check the arithmetic boundary before account resolution or row rounding.+testAccumulatorAcceptance :: IO ()+testAccumulatorAcceptance = do+ let large = 9007199254740992 :: Double+ accepted = [(Debit, Cash, large), (Debit, Cash, 1),+ (Debit, Cash, 1), (Credit, Sales, large + 2)]+ rejected = [(Debit, Cash, large), (Debit, Cash, 1), (Credit, Sales, large)]+ isRight (Right _) = True+ isRight _ = False+ textRows rows = [(T.pack (if side == Debit then "debit" else "credit"),+ T.pack (show title), value) | (side, title, value) <- rows]+ unresolved rows = [(side, T.pack "UnknownAccumulatorAccount", value)+ | (side, _, value) <- textRows rows]+ forM_ [(accepted, True), (rejected, False)] $ \(rows, expected) -> do+ assertEqual "checked entry compares exact postings in both directions"+ expected (isRight (ECC.checkedEntry rows))+ assertEqual "checked text compares exact postings in both directions"+ expected (isRight (ECC.checkedEntryText (textRows rows)))+ assertEqual "unresolved text compares all exact amounts"+ expected (case ECC.certifyJournalText [("entry", unresolved rows)] of+ ECC.BalancedUnresolved{} -> True+ _ -> False)+ assertEqual "resolved journal certification compares exact postings"+ expected (case ECC.certifyJournalText [("entry", textRows rows)] of+ ECC.FullyResolved{} -> True+ _ -> False)++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testAccumulatorAcceptance+ testCsvWriteCases+ testCsvTranspose+ testWriteFuncResultsCsv+ testWriteJournalPinned+ testConvertCsvRoundTrip+ checkedConvertProperties
test/Journal/CarrySpec.hs view
@@ -1,23 +1,27 @@+{-# LANGUAGE ScopedTypeVariables #-}+ -- | Acceptance properties for explicit journal carryover. module Journal.CarrySpec (runTests) where +import ExchangeAlgebra.Accounting.Account (AccountTitles(..))+import qualified ExchangeAlgebra.Algebra.Internal as Internal import Control.Monad (unless) import qualified Data.HashMap.Strict as HashMap import qualified Data.Map.Strict as Map import System.Exit (exitFailure) import Test.QuickCheck -import ExchangeAlgebra.Algebra ((.@), foldEntries)+import ExchangeAlgebra.Algebra (Alg, (.@), foldEntries)+import ExchangeAlgebra.Algebra.Value (HatVal, MoneyDecimal)+import GHC.Float (castDoubleToWord64) import ExchangeAlgebra.Algebra.Base- ( AccountTitles(..)- , CountUnit(..)+ ( CountUnit(..) , Hat(..) , HatBase(..) ) import ExchangeAlgebra.Journal (Journal, (.|)) import qualified ExchangeAlgebra.Journal as Journal-import ExchangeAlgebra.Journal.Transfer.Rule (carryBefore, carryEntries)-import ExchangeAlgebra.Journal.Exact (ExactSumError(..))+import ExchangeAlgebra.Journal.Transfer.Rule (CarryError(..), carryBefore, carryEntries) -- | Test postings use two base axes and integer note labels. type Part = (CountUnit, AccountTitles)@@ -209,7 +213,7 @@ before = fmap (const ()) (carryBefore (< 3) 9 original) entries = fmap (const ()) (carryEntries (< 3) 9 original) in conjoin- [ before === Left SumOutOfRange+ [ before === Left ResultOutOfRange , entries === before ] @@ -233,7 +237,7 @@ , allEntries === Right (multiset expectedAll) ] --- | A selected complete base whose exact side sum exceeds Double fails.+-- | K4: a selected complete base whose rounded net exceeds Double fails. testOverflow :: IO () testOverflow = do let maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double@@ -246,10 +250,109 @@ unless (fmap (const ()) carried == fmap (const ()) additions) $ failTest "carryEntries and carryBefore disagree on overflow" case carried of- Left SumOutOfRange -> pure ()+ Left ResultOutOfRange -> pure () Left failure -> failTest ("unexpected overflow error: " ++ show failure) Right _ -> failTest "overflowing selected side sum succeeded" +-- | K1: side overflow and a tiny addend still succeed when the rounded net is finite.+testExpandedRange :: IO ()+testExpandedRange = do+ let maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double+ leastSubnormal = encodeFloat 1 (-1074) :: Double+ originals =+ [ [(0, (Yen, Cash), Not, maximumFinite)+ ,(1, (Yen, Cash), Not, maximumFinite)+ ,(2, (Yen, Cash), Hat, maximumFinite)]+ , [(0, (Yen, Cash), Not, maximumFinite)+ ,(1, (Yen, Cash), Not, leastSubnormal)]+ ]+ mapM_ (\original -> do+ let journal = build original+ case (carryBefore (< 3) 9 journal, carryEntries (< 3) 9 journal) of+ (Right carried, Right additions) -> do+ unless (fmap (castDoubleToWord64 . valueOf) (rows carried)+ == [castDoubleToWord64 maximumFinite]) $+ failTest "K1 expanded range has wrong rounded net"+ unless (all (\(_, _, _, value) -> not (isInfinite value)) (rows additions)) $+ failTest "K1 carry entries contain infinity"+ failures -> failTest (show failures)) originals+ where+ valueOf :: Row -> Double+ valueOf (_, _, _, value) = value++-- | K2-K3: decimal carry preserves exact balances and the replacement decomposition.+propDecimalCarry :: Property+propDecimalCarry = forAll rowsGen $ \original ->+ let journal = foldMap posting original+ posting :: Row -> Journal Int MoneyDecimal (HatBase Part)+ posting (note, coordinates, side, value) =+ (fromRational (toRational value) .@ (side :< coordinates)) .| note+ scalarRows :: HatVal v => Journal Int v (HatBase Part) -> [(Int, Part, Hat, v)]+ scalarRows input = concatMap entries+ $ HashMap.toList+ $ Journal.toMap input+ where+ entries :: HatVal v => (Int, Alg v (HatBase Part)) -> [(Int, Part, Hat, v)]+ entries (note, algebra) = foldEntries+ (\previous value (side :< coordinates) ->+ (note, coordinates, side, value) : previous) [] algebra+ exactBalances :: Journal Int MoneyDecimal (HatBase Part) -> Map.Map Part Rational+ exactBalances = Map.fromListWith (+) . fmap contribution . scalarRows+ contribution :: (Int, Part, Hat, MoneyDecimal) -> (Part, Rational)+ contribution (_, coordinates, side, value)+ | side == Hat = (coordinates, negate (toRational value))+ | otherwise = (coordinates, toRational value)+ in case (carryEntries (< 3) 9 journal, carryBefore (< 3) 9 journal) of+ (Right additions, Right replaced) ->+ let decomposed = Journal.filterWithNote (\note _ -> note >= 3)+ (journal <> additions)+ in conjoin+ [ property $ all (== 0) $ Map.elems $ Map.unionWith (-)+ (exactBalances journal) (exactBalances (journal <> additions))+ , property $ all (== 0) $ Map.elems $ Map.unionWith (-)+ (exactBalances journal) (exactBalances replaced)+ , multiset (scalarRows replaced) === multiset (scalarRows decomposed)+ ]+ failures -> counterexample (show failures) False++-- | K5: all selected inputs are checked before rounding, in a fixed priority.+propFailurePriority :: Property+propFailurePriority = forAll (shuffle [0 :: Int, 1, 2, 3, 4]) $ \order ->+ let maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double+ invalid = [(HatNot, 1), (Not, 0 / 0), (Hat, -1)+ ,(Not, maximumFinite), (Not, maximumFinite)]+ assemble :: [(Hat, Double)] -> TestJournal+ assemble postings = Journal.fromMap $ HashMap.fromList+ [ (note, value `Internal.Single` (side :< (Yen, Cash)))+ | (note, (side, value)) <- zip order postings ]+ check :: [(Hat, Double)] -> CarryError -> Property+ check postings expected = conjoin+ [ fmap (const ()) (carryBefore (const True) 9 (assemble postings)) === Left expected+ , fmap (const ()) (carryEntries (const True) 9 (assemble postings)) === Left expected+ ]+ in conjoin+ [ check invalid WildcardSide+ , check (drop 1 invalid) NonFiniteValue+ , check (drop 2 invalid) NegativeValue+ , check (drop 3 invalid) ResultOutOfRange+ ]++-- | K5: raw zero HatNot is absent from both the net and reversal.+testZeroWildcard :: IO ()+testZeroWildcard = do+ let wildcardJournal = Journal.fromMap $ HashMap.singleton 0+ (0 `Internal.Single` (HatNot :< (Yen, Cash))) :: TestJournal+ input = wildcardJournal <> build [(1, (Yen, Cash), Not, 3)]+ expected = build [(9, (Yen, Cash), Not, 3)]+ unless (fmap (multiset . rows) (carryBefore (< 3) 9 input)+ == Right (multiset (rows expected))) $ failTest "zero wildcard carryBefore"+ case carryEntries (< 3) 9 input of+ Left failure -> failTest (show failure)+ Right additions -> unless+ (multiset (rows additions) == multiset+ [(1, (Yen, Cash), Hat, 3), (9, (Yen, Cash), Not, 3)]) $+ failTest "zero wildcard reversal"+ -- | Report a deterministic acceptance failure and stop the suite. failTest :: String -> IO () failTest message = do@@ -268,6 +371,10 @@ quickProperty 1 "carry error parity" propCarryEntriesFailureParity quickProperty 1 "carry selection" propSelectionExtremes testOverflow+ testExpandedRange+ quickProperty 500 "K2-K3 decimal carry" propDecimalCarry+ quickProperty 200 "K5 failure priority" propFailurePriority+ testZeroWildcard putStrLn "[PASS] journal carry (retention, rounding, zero, and carry note)" -- | Run one named QuickCheck property and fail on a counterexample.
+ test/Journal/JournalSpec.hs view
@@ -0,0 +1,510 @@+-- | Journal notes, projections, summation, and transfer equivalence.+-- Tests exercise the Journal layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Journal.JournalSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Journal as EJ+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified Data.HashMap.Strict as HM+import qualified Data.Map.Strict as M+import qualified Data.List as L+import Control.Monad (forM_)+import Test.QuickCheck hiding (Fixed)+import Support (TestComparison(..)+ , assertEqual+ , runTestComparison+ , TestAlg+ , TransferAlg+ , TransferJournal+ , quickProp+ , genBase+ , genNNDouble+ , netByBase+ )++type TestJournal = EJ.Journal String Double (HatBase CountUnit)+type AxisJournal = EJ.Journal (String, Int) Double (HatBase CountUnit)++algSample :: TestAlg+algSample =+ (1 .@ (Hat :< Yen))+ .+ (1 .@ (Not :< Amount))+ .+ (2 .@ (Not :< Yen))+ .+ (2 .@ (Hat :< Amount))+ .+ (3 .@ (Hat :< Yen))++journalSample :: TestJournal+journalSample = EJ.fromList [x, y, z]+ where+ x = ((1 .@ (Hat :< Yen)) .+ (1 .@ (Not :< Amount))) .| "cat" :: TestJournal+ y = ((2 .@ (Not :< Yen)) .+ (2 .@ (Hat :< Amount))) .| "dog" :: TestJournal+ z = ((3 .@ (Hat :< Yen)) .+ (3 .@ (Not :< Amount))) .| "fish" :: TestJournal++testReplaceNotes :: IO ()+testReplaceNotes = do+ let x = (10.00 .@ (Not :< Cash)) .| "A"+ :: EJ.Journal String Double (HatBase AccountTitles)+ y = (20.00 .@ (Not :< Cash)) .| "B"+ :: EJ.Journal String Double (HatBase AccountTitles)+ z = (30.00 .@ (Hat :< Cash)) .| "A"+ :: EJ.Journal String Double (HatBase AccountTitles)+ source = x .+ y+ expected = z .+ y+ assertEqual "Journal.replaceNotes replaces the complete matching Note"+ (EJ.toMap expected)+ (EJ.toMap (EJ.replaceNotes z source))++transferAlgSample :: TransferAlg+transferAlgSample = EA.fromList+ [ 7 .@ Not :<(WageExpenditure, 1, 1, Yen)+ , 3 .@ Hat :<(Depreciation, 2, 2, Yen)+ , 11 .@ Not :<(Purchases, 3, 3, Yen)+ , 13 .@ Not :<(ValueAdded, 1, 2, Yen)+ , 17 .@ Hat :<(Sales, 2, 1, Yen)+ , 19 .@ Not :<(InterestEarned, 4, 4, Yen)+ , 23 .@ Hat :<(InterestExpense, 5, 5, Yen)+ , 29 .@ Not :<(TaxesRevenue, 2, 2, Yen)+ , 31 .@ Hat :<(TaxesExpense, 3, 3, Yen)+ , 37 .@ Not :<(WageEarned, 6, 6, Yen)+ , 41 .@ Hat :<(ConsumptionExpenditure, 6, 6, Yen)+ , 43 .@ Not :<(CentralBankPaymentIncome, 1, 1, Yen)+ , 47 .@ Hat :<(CentralBankPaymentExpense, 1, 1, Yen)+ , 53 .@ Not :<(GrossProfit, 7, 7, Yen)+ , 59 .@ Hat :<(OrdinaryProfit, 8, 8, Yen)+ , 61 .@ Not :<(Cash, 1, 1, Yen)+ ]++transferJournalSample :: TransferJournal+transferJournalSample = EJ.fromList+ [ transferAlgSample .| "A"+ , ((5 .@ Not :<(Sales, 2, 1, Yen)) .+ (2 .@ Hat :<(WageExpenditure, 1, 1, Yen))) .| "B"+ , ((3 .@ Hat :<(TaxesExpense, 3, 3, Yen)) .+ (4 .@ Not :<(InterestEarned, 4, 4, Yen))) .| "C"+ ]++-- ================================================================+-- Journal-algebra axiom properties (Phase 1.5)+-- ================================================================++type NNJournal = EJ.Journal String MoneyDecimal (HatBase CountUnit)++genNote :: Gen String+genNote = elements ["a", "b", "c"]++genPosNN :: Gen MoneyDecimal -- strictly positive (avoids zero-note drop)+genPosNN = (\d -> realToFrac (1 + d)) <$> genNNDouble++genJournalN :: Gen NNJournal+genJournalN = sized $ \n -> do+ k <- choose (0, min 30 n)+ ps <- vectorOf k ((,,) <$> genPosNN <*> genBase <*> genNote)+ pure (EJ.fromList [ (v .@ b) .| nt | (v, b, nt) <- ps ])++-- per-(note, base) signed net; exact (Rational)+netJournal :: NNJournal -> M.Map (String, CountUnit) Rational+netJournal j = M.fromList+ [ ((nt, u), r)+ | (nt, alg) <- HM.toList (EJ.toMap j)+ , (u, r) <- M.toList (netByBase alg) ]++journalProperties :: IO ()+journalProperties = do+ let unitForParity i+ | even i = Yen+ | otherwise = Amount+ -- fromList is now a strict left fold (L.foldl' (.+) mempty). Verify it still+ -- preserves the posting multiset by matching the old lazy right-fold reference+ -- (foldr (.+) mempty). Colliding note keys (i `mod` 30) force same-note/same-base+ -- postings into one Alg sequence, where the two folds accumulate in opposite+ -- order; with MoneyDecimal (exact, associative) the aggregate (norm) is identical.+ let mk i = ((fromIntegral (i `mod` 7 + 1) :: MoneyDecimal)+ .@ ((if even i then Hat else Not) :< (unitForParity i)))+ .| show (i `mod` 30)+ xs :: [Journal String MoneyDecimal (HatBase CountUnit)]+ xs = [ mk i | i <- [1 .. 400 :: Int] ]+ strict = EJ.fromList xs+ lazyRef = foldr (.+) mempty xs+ -- exact value type ⇒ norm identical regardless of seq order (multiset preserved)+ assertEqual "Journal.fromList (strict): norm matches lazy foldr reference (MoneyDecimal exact)"+ (norm strict) (norm lazyRef)+ -- distinct note keys ⇒ no seq collision ⇒ exact structural equality with foldr+ let ys :: [Journal String MoneyDecimal (HatBase CountUnit)]+ ys = [ ((fromIntegral i :: MoneyDecimal) .@ (Not :< Yen)) .| show i+ | i <- [1 .. 20 :: Int] ]+ assertEqual "Journal.fromList (strict): structurally equal to foldr for distinct notes"+ (EJ.toMap (EJ.fromList ys)) (EJ.toMap (foldr (.+) mempty ys))+ quickProp "journal: norm additivity (norm(j1.+j2) = norm j1 + norm j2, MoneyDecimal)" $+ forAll genJournalN $ \j1 -> forAll genJournalN $ \j2 ->+ norm (j1 .+ j2) == norm j1 + norm j2+ quickProp "journal: Hat preserves the note set" $+ forAll genJournalN $ \j ->+ L.sort (HM.keys (EJ.toMap ((.^) j))) == L.sort (HM.keys (EJ.toMap j))+ quickProp "journal: fromList per-(note,base) net is construction-order independent (MoneyDecimal)" $+ forAll (listOf ((,,) <$> genPosNN <*> genBase <*> genNote)) $ \ps ->+ let js = [ (v .@ b) .| nt | (v, b, nt) <- ps ] :: [NNJournal]+ in netJournal (EJ.fromList js) == netJournal (foldr (.+) mempty js)++-- | Check gross and net Journal projections, including the exact 6 / 14 sentinel.+testJournalProjectionNorms :: IO ()+testJournalProjectionNorms = do+ let cases = concat+ [ let+ qs, qsDedup :: [HatBase CountUnit]+ qs = [Hat :< Yen, HatNot :< Amount, Hat :< Yen] -- Hat:<Yen duplicated+ qsDedup = [Hat :< Yen, HatNot :< Amount]+ in+ [ EqualComparison "Alg.proj treats query list as a set (duplicate exact)"+ (EA.proj qsDedup algSample) (EA.proj qs algSample)+ , EqualComparison "Alg.proj no double counting (single Hat:<Yen)"+ (EA.proj [Hat :< Yen] algSample)+ (EA.proj [Hat :< Yen, Hat :< Yen] algSample)+ ]+ , let+ qs :: [HatBase CountUnit]+ qs = [Hat :< Yen, HatNot :< Amount, Hat :< Yen]+ expected = norm $ EA.bar $ EA.proj qs algSample+ actual = EA.projNetNorm qs algSample+ in+ [ NearComparison+ "Alg.projNetNorm == norm . bar . proj (set semantics)"+ expected actual+ ]+ , let+ alg :: EA.Alg MoneyDecimal (HatBase CountUnit)+ alg = (10 .@ (Hat :< Yen))+ .+ (3 .@ (Not :< Amount))+ b = Hat :< Yen :: HatBase CountUnit+ in+ [ EqualComparison "proj [b,b] == proj [b] (duplicate exact, MoneyDecimal)"+ (EA.proj [b] alg) (EA.proj [b, b] alg)+ , EqualComparison "projNetNorm [b,b] == projNetNorm [b] (duplicate exact)"+ (EA.projNetNorm [b] alg) (EA.projNetNorm [b, b] alg)+ ]+ , let+ alg :: EA.Alg MoneyDecimal (HatBase CountUnit)+ alg = (10 .@ (Hat :< Yen))+ .+ (5 .@ (Not :< Amount))+ exact = Hat :< Yen :: HatBase CountUnit+ wild = Hat :< (.#) :: HatBase CountUnit -- subsumes Hat:<Yen+ in+ [ EqualComparison "proj [exact,wild] == proj [wild] (overlap, no double count)"+ (EA.proj [wild] alg) (EA.proj [exact, wild] alg)+ , EqualComparison "projNetNorm [exact,wild] == projNetNorm [wild] (overlap)"+ (EA.projNetNorm [wild] alg) (EA.projNetNorm [exact, wild] alg)+ ]+ , let+ alg :: EA.Alg MoneyDecimal (HatBase CountUnit)+ alg = (10 .@ (Hat :< Yen)) -- Yen carries both sides+ .+ (4 .@ (Not :< Yen))+ .+ (7 .@ (Not :< Amount))+ bs = [HatNot :< Yen, HatNot :< Amount] :: [HatBase CountUnit]+ in+ [ EqualComparison "projNetNorm == norm . bar . proj (both-sided base, MoneyDecimal)"+ (norm (EA.bar (EA.proj bs alg))) (EA.projNetNorm bs alg)+ ]+ , let+ bs :: [HatBase CountUnit]+ bs = [Not :< Amount]+ expected = norm $ EJ.projWithBase bs journalSample+ actual = EJ.projWithBaseNetNorm bs journalSample+ in+ [ NearComparison+ "Journal.projWithBaseNetNorm matches norm . projWithBase"+ expected actual+ ]+ , let+ bs :: [HatBase CountUnit]+ bs = [HatNot :< Amount, Hat :< Yen]+ ns1 = ["dog", "cat"]+ ns2 = [plank]+ expected1 = norm $ EJ.projWithNoteBase ns1 bs journalSample+ actual1 = EJ.projWithNoteBaseNetNorm ns1 bs journalSample+ expected2 = norm $ EJ.projWithNoteBase ns2 bs journalSample+ actual2 = EJ.projWithNoteBaseNetNorm ns2 bs journalSample+ in+ [ NearComparison+ "Journal.projWithNoteBaseNetNorm (selected notes)"+ expected1 actual1+ , NearComparison+ "Journal.projWithNoteBaseNetNorm (plank wildcard)"+ expected2 actual2+ ]+ , let+ alg :: EA.Alg MoneyDecimal (HatBase CountUnit)+ alg = (10 .@ (Hat :< Yen)) -- Yen carries both sides+ .+ (4 .@ (Not :< Yen))+ .+ (7 .@ (Not :< Amount))+ js = alg .| "n" :: EJ.Journal String MoneyDecimal (HatBase CountUnit)+ bs = [HatNot :< Yen] :: [HatBase CountUnit]+ in+ [ EqualComparison "projWithBaseNetNorm nets both sides (HatNot query): |10-4|"+ 6 (EJ.projWithBaseNetNorm bs js)+ , EqualComparison "norm . projWithBase stays gross (no RULES rewrite): 10+4"+ 14 (norm (EJ.projWithBase bs js))+ , EqualComparison "projWithBaseNetNorm == norm . map bar . projWithBase"+ (norm (EJ.map EA.bar (EJ.projWithBase bs js)))+ (EJ.projWithBaseNetNorm bs js)+ , EqualComparison "projWithNoteBaseNetNorm nets both sides (HatNot query): |10-4|"+ 6 (EJ.projWithNoteBaseNetNorm ["n"] bs js)+ , EqualComparison "norm . projWithNoteBase stays gross (no RULES rewrite): 10+4"+ 14 (norm (EJ.projWithNoteBase ["n"] bs js))+ ]+ ]+ forM_ cases runTestComparison+++-- | Compare Algebra and Journal summation paths with their independent references.+testSigmaReferences :: IO ()+testSigmaReferences = do+ let zero :: TestJournal+ zero = (EA.Zero :: TestAlg) .| "A"+ matchesZero :: Bool+ matchesZero = case zero of+ EJ.Zero -> True+ _ -> False+ x = 1 .@ (Hat :< Yen) :: TestAlg+ assertEqual "Journal.(.|) Zero matches Journal.Zero" True matchesZero+ assertEqual "Journal.(.|) Zero has no notes" HM.empty (EJ.toMap zero)+ assertEqual "Journal.(.|) preserves a non-zero algebra element"+ (HM.singleton "A" x) (EJ.toMap (x .| "A"))+ let cases = concat+ [ let+ xs = [1 .. 5 :: Int]+ f :: Int -> TestAlg+ f i+ | i == 3 = EA.Zero+ | odd i = fromIntegral i .@ (Hat :< Yen)+ | otherwise = fromIntegral i .@ (Not :< Amount)+ expected :: TestAlg+ expected = EA.unionsMerge (L.map f xs)+ actual :: TestAlg+ actual = EA.sigma xs f+ in+ [ EqualComparison "Alg.sigma bulk-merge path matches unionsMerge" expected actual+ ]+ , let+ xs = [1 .. 3 :: Int]+ ys = [1 .. 4 :: Int]+ cond i j = i /= j && even (i + j)+ f :: Int -> Int -> TestAlg+ f i j =+ let v = fromIntegral (i * 10 + j)+ in if odd i+ then v .@ (Hat :< Yen)+ else v .@ (Not :< Amount)+ expected :: TestAlg+ expected =+ EA.unionsMerge+ [ f i j+ | i <- xs+ , j <- ys+ , cond i j+ ]+ actual :: TestAlg+ actual = EA.sigma2When xs ys cond f+ in+ [ EqualComparison "Alg.sigma2When matches list-comprehension sum" expected actual+ ]+ , let+ kvs = M.fromList+ [ ((1, 2), 5.0)+ , ((2, 3), 0.0)+ , ((3, 1), 7.0)+ ] :: M.Map (Int, Int) Double+ f :: (Int, Int) -> Double -> TestAlg+ f (i, j) v+ | i < j = v .@ (Hat :< Yen)+ | otherwise = v .@ (Not :< Amount)+ expected :: TestAlg+ expected = EA.unionsMerge+ [ f (1, 2) 5.0+ , f (3, 1) 7.0+ ]+ actual :: TestAlg+ actual = EA.sigmaFromMap kvs f+ in+ [ EqualComparison+ "Alg.sigmaFromMap iterates non-zero map entries only"+ expected actual+ ]+ , let+ xs = [1 .. 4 :: Int]+ f :: Int -> TestJournal+ f i = case i of+ 1 -> (1 .@ (Hat :< Yen)) .| "A"+ 2 -> EJ.Zero+ 3 -> EJ.fromMap (HM.singleton "A" (EA.Zero :: TestAlg))+ _ -> (2 .@ (Not :< Amount)) .| "B"+ expected :: TestJournal+ expected = EJ.fromMap $ HM.fromList+ [ ("A", 1 .@ (Hat :< Yen))+ , ("B", 2 .@ (Not :< Amount))+ ]+ actual = EJ.sigma xs f+ in+ [ EqualComparison+ "Journal.sigma bulk-merge path skips zero postings"+ (EJ.toMap expected) (EJ.toMap actual)+ ]+ , let+ xs = [1 .. 3 :: Int]+ ys = [1 .. 3 :: Int]+ cond i j = i < j+ f :: Int -> Int -> TestJournal+ f i j+ | i == 1 && j == 2 = EJ.fromMap (HM.singleton "N" (EA.Zero :: TestAlg))+ | odd (i + j) = (fromIntegral (i + j) .@ (Hat :< Yen)) .| "N"+ | otherwise = EJ.Zero+ expected :: TestJournal+ expected = EJ.fromMap $ HM.fromList [("N", 5 .@ (Hat :< Yen))]+ actual = EJ.sigma2When xs ys cond f+ in+ [ EqualComparison+ "Journal.sigma2When matches filtered pair sum"+ (EJ.toMap expected) (EJ.toMap actual)+ ]+ , let+ xs = [1 .. 4 :: Int]+ f :: Int -> TestAlg+ f i+ | i <= 2 = EA.Zero+ | otherwise = fromIntegral i .@ (Hat :< Yen)+ expected :: TestJournal+ expected = (EA.sigma xs f) .| "SalesPurchase"+ actual :: TestJournal+ actual = EJ.sigmaOn "SalesPurchase" xs f+ zeroExpected = EJ.Zero :: TestJournal+ zeroActual = EJ.sigmaOn "SalesPurchase" xs (\_ -> EA.Zero :: TestAlg)+ in+ [ EqualComparison+ "Journal.sigmaOn attaches note after EA.sigma"+ (EJ.toMap expected) (EJ.toMap actual)+ , EqualComparison+ "Journal.sigmaOn returns Zero when EA.sigma is Zero"+ (EJ.toMap zeroExpected) (EJ.toMap zeroActual)+ ]+ , let+ kvs = M.fromList+ [ ((1, 2), 4.0)+ , ((2, 3), 0.0)+ , ((2, 1), 6.0)+ ] :: M.Map (Int, Int) Double+ f :: (Int, Int) -> Double -> TestAlg+ f (i, j) v+ | i < j = v .@ (Hat :< Yen)+ | otherwise = v .@ (Not :< Amount)+ expected :: TestJournal+ expected = (EA.sigmaFromMap kvs f) .| "SalesPurchase"+ actual :: TestJournal+ actual = EJ.sigmaOnFromMap "SalesPurchase" kvs f+ zeroActual :: TestJournal+ zeroActual = EJ.sigmaOnFromMap "SalesPurchase" (M.singleton (1, 1) 0.0) f+ in+ [ EqualComparison+ "Journal.sigmaOnFromMap matches EA.sigmaFromMap + note"+ (EJ.toMap expected) (EJ.toMap actual)+ , EqualComparison+ "Journal.sigmaOnFromMap returns Zero for empty-effective map"+ (EJ.toMap (EJ.Zero :: TestJournal)) (EJ.toMap zeroActual)+ ]+ ]+ forM_ cases runTestComparison+++-- | Check axis filtering, type mismatch, and index updates after append.+testFilterByAxisCases :: IO ()+testFilterByAxisCases = do+ let cases = concat+ [ let+ ledger :: AxisJournal+ ledger = EJ.fromList+ [ (10 .@ (Hat :< Yen)) .| ("A", 1)+ , (20 .@ (Not :< Amount)) .| ("B", 1)+ , (30 .@ (Hat :< Yen)) .| ("A", 2)+ ]+ expected = EJ.filterWithNote (\(_, t') _ -> t' == 1) ledger+ actual = EJ.filterByAxis 1 (EJ.NoteAxisKey (1 :: Int)) ledger+ mismatch = EJ.filterByAxis 1 (EJ.NoteAxisKey ("1" :: String)) ledger+ in+ [ EqualComparison "Journal.filterByAxis matches filterWithNote on axis=1"+ (EJ.toMap expected)+ (EJ.toMap actual)+ , EqualComparison "Journal.filterByAxis type mismatch returns empty"+ (EJ.toMap (EJ.Zero :: AxisJournal))+ (EJ.toMap mismatch)+ ]+ , let+ base :: AxisJournal+ base = EJ.fromMap $ HM.fromList+ [ (("A", 1), 10 .@ (Hat :< Yen))+ , (("C", 2), 5 .@ (Not :< Amount))+ ]+ rhs :: AxisJournal+ rhs = EJ.fromMap $ HM.fromList+ [ (("A", 1), 3 .@ (Not :< Amount))+ , (("B", 1), 7 .@ (Hat :< Yen))+ ]+ ledger = base .+ rhs+ expected = EJ.filterWithNote (\(_, t') _ -> t' == 1) ledger+ actual = EJ.filterByAxis 1 (EJ.NoteAxisKey (1 :: Int)) ledger+ in+ [ EqualComparison "Journal.filterByAxis works after append updates"+ (EJ.toMap expected)+ (EJ.toMap actual)+ ]+ ]+ forM_ cases runTestComparison+++-- | Close against hand-calculated balances and keep the original transaction notes.+testFinalStockTransferEquivalence :: IO ()+testFinalStockTransferEquivalence = do+ let expected :: TransferAlg+ expected = EA.fromList+ [ 83 .@ Not :< (RetainedEarnings, 1, 1, Yen)+ , 32 .@ Not :< (RetainedEarnings, 2, 2, Yen)+ , 20 .@ Not :< (RetainedEarnings, 3, 3, Yen)+ , 13 .@ Not :< (RetainedEarnings, 1, 2, Yen)+ , 17 .@ Hat :< (RetainedEarnings, 2, 1, Yen)+ , 19 .@ Not :< (RetainedEarnings, 4, 4, Yen)+ , 23 .@ Not :< (RetainedEarnings, 5, 5, Yen)+ , 78 .@ Not :< (RetainedEarnings, 6, 6, Yen)+ , 53 .@ Not :< (RetainedEarnings, 7, 7, Yen)+ , 59 .@ Hat :< (RetainedEarnings, 8, 8, Yen)+ , 61 .@ Not :< (Cash, 1, 1, Yen)+ ]+ expectedJournal :: TransferAlg+ expectedJournal = EA.bar $ expected+ .+ 5 .@ Not :< (RetainedEarnings, 2, 1, Yen)+ .+ 2 .@ Not :< (RetainedEarnings, 1, 1, Yen)+ .+ 3 .@ Not :< (RetainedEarnings, 3, 3, Yen)+ .+ 4 .@ Not :< (RetainedEarnings, 4, 4, Yen)+ case Closing.closingEntries transferAlgSample of+ Left failure -> fail (show failure)+ Right entries -> assertEqual "Algebra closing retains axes and fixed balances"+ (EA.bar expected) (EA.bar (transferAlgSample .+ entries))+ case Closing.closingEntries (EJ.toAlg transferJournalSample) of+ Left failure -> fail (show failure)+ Right entries -> do+ let closed :: TransferJournal+ closed = transferJournalSample .+ (entries .| "closing")+ assertEqual "Journal closing has the fixed aggregate balances"+ expectedJournal (EA.bar (EJ.toAlg closed))+ assertEqual "Journal closing preserves transaction notes"+ (EJ.toMap transferJournalSample)+ (HM.delete "closing" (EJ.toMap closed))+ assertEqual "Journal closing records additions under the settlement note"+ (Just entries) (HM.lookup "closing" (EJ.toMap closed))++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testFinalStockTransferEquivalence+ testFilterByAxisCases+ testSigmaReferences+ testJournalProjectionNorms+ testReplaceNotes+ journalProperties
+ test/Journal/NormalFormSpec.hs view
@@ -0,0 +1,204 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | Journal normal-form laws for the Journal layer, using public Algebra inputs.+-- N1 checks both layers; N2 compares with the old map operations; N3 reads old bytes.+-- All observations use exact equality on small integral values, for Double and+-- MoneyDecimal with String and tuple notes. Start with 'runTests'.+module Journal.NormalFormSpec (runTests) where++import Control.Monad (forM_)+import qualified Data.Binary as Binary+import Data.Binary.Put (runPut)+import qualified Data.HashMap.Strict as Map+import qualified Data.List as List+import qualified ExchangeAlgebra.Algebra as EA+import ExchangeAlgebra.Algebra+ ( HatBase(..)+ , Hat(..)+ , CountUnit(..)+ , Redundant(..)+ , (.@)+ )+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified ExchangeAlgebra.Journal.Core as Journal+import Support (assertEqual, quickProp)+import Test.QuickCheck++-- | Recognize the structural Zero pattern, independently of Journal equality.+isEmpty :: (Journal.Note n, EA.HatVal v, EA.HatBaseClass b)+ => Journal.Journal n v b -> Bool+isEmpty Journal.Zero = True+isEmpty _ = False++-- | N1: merged entries and each layer contain no zero algebra; empty means Zero.+isNormal :: (Journal.Note n, EA.HatVal v)+ => Journal.Journal n v (HatBase CountUnit) -> Bool+isNormal journal = all (not . EA.isZero) (Map.elems entries)+ && isEmpty (Journal.filterWithNote (\_ algebra -> EA.isZero algebra) journal)+ && (not (Map.null entries) || isEmpty journal)+ where+ entries = Journal.toMap journal++-- | N1-N3 over positive integral amounts, arbitrary layer placement, and API choices.+-- N2 observes the posting multiset, norm, and net readout without numeric tolerance.+normalFormLaws :: forall n v. (Journal.Note n, Binary.Binary n, EA.HatVal v, Binary.Binary v)+ => n -> n -> n -> v -> Property+normalFormLaws first second emptyNote witness =+ forAll (chooseInt (1, 1000)) $ \amount ->+ forAll (chooseInt (0, 2)) $ \placement ->+ forAll (chooseInt (0, 2)) $ \operation ->+ let value = fromIntegral amount + 0 * witness+ positive = value .@ (Not :< Yen)+ balanced = positive .+ value .@ (Hat :< Yen)+ base = Map.fromList [(first, positive), (emptyNote, EA.Zero)]+ delta = Map.fromList [(first, positive), (second, balanced), (emptyNote, EA.Zero)]+ input = case placement of+ 0 -> Journal.mkJournal base Map.empty+ 1 -> Journal.mkJournal Map.empty delta+ _ -> Journal.mkJournal base delta+ transform :: EA.Alg v (HatBase CountUnit) -> EA.Alg v (HatBase CountUnit)+ transform = case operation of+ 0 -> const EA.Zero+ 1 -> bar+ _ -> (0 .*)+ query = [Not :< Dollar | odd amount] ++ [Not :< Yen | even amount]+ notes = [first, second]+ oldMap = Journal.toMap input+ oldResults =+ [ Map.map transform oldMap+ , Map.map (EA.proj query) oldMap+ , Map.map (EA.proj query) (Map.filterWithKey (\n _ -> n == first) oldMap)+ , Map.map (EA.proj query) (Map.filterWithKey (\n _ -> n `elem` notes) oldMap)+ ]+ newResults =+ [ Journal.map transform input+ , Journal.projWithBase query input+ , Journal.projWithNoteBase [first] query input+ , Journal.projWithNoteBase notes query input+ ]+ bytes = runPut $ do+ Binary.put (2 :: Int)+ Binary.put emptyNote+ Binary.put (EA.Zero :: EA.Alg v (HatBase CountUnit))+ Binary.put first+ Binary.put positive+ decoded = Binary.decode bytes :: Journal.Journal n v (HatBase CountUnit)+ candidates = newResults +++ [ input+ , Journal.fromMap base+ , decoded+ , 0 .* input+ , bar (balanced Journal..| first)+ , (.^) input+ , (.-) input+ , EA.compress input+ , Journal.projWithNote notes input+ , Journal.filterWithNote (\n _ -> n == first) input+ , Journal.filterByAxis 0 (Journal.NoteAxisKey first) input+ , Journal.replaceNotes input (positive Journal..| second)+ , Journal.fromList [input, input]+ , Journal.sigma [input, input] id+ , Journal.sigma2When [input] [input] (\_ _ -> True) (.+)+ , Journal.decTo (const (Just first)) balanced+ , Journal.gather first input+ , mempty+ , EA.Zero Journal..| first+ ]+ unchanged :: (Map.HashMap n (EA.Alg v (HatBase CountUnit)),+ Journal.Journal n v (HatBase CountUnit)) -> Bool+ unchanged (old, new) =+ Journal.toMap new == Map.filter (not . EA.isZero) old+ && List.sort (EA.toASCList (Journal.toAlg new))+ == List.sort (concatMap EA.toASCList (Map.elems old))+ && norm new == sum (Prelude.map norm (Map.elems old))+ && Journal.projWithBaseNetNorm [HatNot :< Yen] new+ == sum (Prelude.map (EA.projNetNorm [HatNot :< Yen]) (Map.elems old))+ in conjoin+ [ counterexample "N1 layers / merged map / Zero pattern"+ (all isNormal candidates)+ , counterexample "N2 content" (all unchanged (zip oldResults newResults))+ , counterexample "N3 legacy decode"+ (isNormal decoded && Journal.toMap decoded == Map.singleton first positive)+ ]++-- | A0: underflow drops zero postings and retains the sequence and projection indices.+-- Finite nonnegative Double products include tiny values and zero; nonzero sums+-- remain nonzero. Exact equality observes posting values, not a rounded norm.+underflowLaw :: Property+underflowLaw = forAll (elements [0, 1e-200, 1e-100, 1]) $ \scalar ->+ forAll (elements [1e-200, 1e-100, 1, 2]) $ \value ->+ let single = value .@ (Not :< Yen) :: EA.Alg Double (HatBase CountUnit)+ liner = single .+ 1 .@ (Hat :< Dollar) .+ single+ entries :: EA.Alg Double (HatBase CountUnit) -> [(Double, HatBase CountUnit)]+ entries = List.sort . EA.foldEntries (\rows v b -> (v, b) : rows) []+ tinyLiner = single .+ value .@ (Hat :< Dollar)+ products = Prelude.map (scalar .*) [single, liner, tinyLiner]+ expected = Prelude.map+ (Prelude.filter ((/= 0) . fst) . Prelude.map (\(v, b) -> (scalar * v, b))+ . entries) [single, liner, tinyLiner]+ in conjoin+ [ Prelude.map entries products === Prelude.map List.sort expected+ , property (all (all (not . EA.isZeroValue) . EA.vals) products)+ , property (and [not (null rows) || EA.isZero a+ | (a, rows) <- zip products expected])+ , property (and [EA.isZero a || EA.isZero b || not (EA.isZero (a .+ b))+ | a <- products, b <- products])+ , property (all (\a -> entries (EA.proj [HatNot :< wildcard] a)+ == entries a) products)+ ]+ where+ wildcard :: CountUnit+ wildcard = EA.wildcard++-- | Empty exact and wildcard projections are Zero for either side.+-- For finite positive values, the selected posting multiset is preserved exactly.+-- The opposite-side Journal regression is the former empty "dog" note.+projectionLaw :: forall v. EA.HatVal v => v -> Property+projectionLaw witness = forAll (chooseInt (1, 1000)) $ \amount ->+ let value = fromIntegral amount + 0 * witness+ inputs =+ [ value .@ (side :< Yen) .+ value .@ (side :< unit)+ | side <- [Hat, Not], unit <- [Yen, Dollar] ]+ queries = [[side :< unit] | side <- [Hat, Not], unit <- [Yen, EA.wildcard]]+ ++ [[side :< Yen, side :< Dollar] | side <- [Hat, Not]]+ postings :: EA.Alg v (HatBase CountUnit) -> [(v, HatBase CountUnit)]+ postings = List.sort . EA.foldEntries (\rows v b -> (v, b) : rows) []+ dog = value .@ (Not :< Yen) .+ value .@ (Hat :< Amount)+ regression = Journal.projWithBase [Not :< Amount] (dog Journal..| "dog")+ in conjoin+ [ counterexample "A0 projection emptiness / retained postings" $ conjoin+ [ let projected = EA.proj query input+ expected = Prelude.filter+ (\(_, side :< unit) -> any+ (\(querySide :< queryUnit) -> querySide == side+ && (queryUnit == EA.wildcard || queryUnit == unit)) query)+ (postings input)+ in property (EA.isZero projected == null expected+ && postings projected == expected)+ | input <- inputs, query <- queries ]+ , counterexample "N1 opposite-side projection drops the dog note"+ (isEmpty regression && isNormal regression)+ ]++-- | Run N1-N3 for both value types and note shapes, then the A0 regression.+runTests :: IO ()+runTests = do+ quickProp "N1-N3 Double / String" (normalFormLaws "a" "b" "zero" (0 :: Double))+ quickProp "N1-N3 MoneyDecimal / String"+ (normalFormLaws "a" "b" "zero" (0 :: MoneyDecimal))+ quickProp "N1-N3 Double / tuple"+ (normalFormLaws ("a", 1 :: Int) ("b", 2) ("zero", 3) (0 :: Double))+ quickProp "N1-N3 MoneyDecimal / tuple"+ (normalFormLaws ("a", 1 :: Int) ("b", 2) ("zero", 3) (0 :: MoneyDecimal))+ quickProp "A0 underflow / nonzero addition" underflowLaw+ quickProp "A0 projection / Double" (projectionLaw (0 :: Double))+ quickProp "A0 projection / MoneyDecimal" (projectionLaw (0 :: MoneyDecimal))+ let tiny = 1e-200 .* (1e-200 .@ (Not :< Yen)) :: EA.Alg Double (HatBase CountUnit)+ journal = Journal.sigma [tiny, tiny] (Journal..| "same")+ assertEqual "N1 underflow sigma same note" True (isNormal journal && isEmpty journal)+ forM_ [(Yen, 0), (Dollar, 1e-200)] $ \(unit, expected) ->+ assertEqual "A0 surviving projection after underflow"+ expected+ (norm $ EA.proj [HatNot :< unit]+ $ 1e-200 .* (1e-200 .@ (Not :< Yen) .+ 1 .@ (Hat :< Dollar)+ :: EA.Alg Double (HatBase CountUnit)))
+ test/Journal/SideTotalsSpec.hs view
@@ -0,0 +1,226 @@+-- | Acceptance properties for exact pre-cancellation journal side totals.+module Journal.SideTotalsSpec (runTests) where++import ExchangeAlgebra.Accounting.Account (AccountTitles(..))+import qualified ExchangeAlgebra.Algebra.Internal as Internal+import Control.Monad (unless)+import qualified Data.HashMap.Strict as HashMap+import qualified Data.List as List+import qualified Data.Map.Strict as Map+import Data.Word (Word64)+import GHC.Float (castDoubleToWord64)+import System.Exit (exitFailure)+import Test.QuickCheck++import ExchangeAlgebra.Algebra ((.@))+import ExchangeAlgebra.Algebra.Base+ ( CountUnit(..), Hat(..), HatBase(..) )+import ExchangeAlgebra.Algebra.Value (HatVal(..))+import ExchangeAlgebra.Journal (Journal, (.|))+import qualified ExchangeAlgebra.Journal as Journal+import ExchangeAlgebra.Journal.Transfer.Rule+ ( CarryError(..), SideTotals(..), sideTotal, sideTotalsBy )+import ExchangeAlgebra.Algebra.Posting (PostSide(..))++type Part = (CountUnit, AccountTitles)+type Row = (Int, Part, Hat, Double)+type TestJournal = Journal Int Double (HatBase Part)++-- | Keep the original scalar postings apart from the invalid raw-entry tests.+build :: [Row] -> TestJournal+build = foldMap (\(note, coordinates, side, value) ->+ (value .@ (side :< coordinates)) .| note)++buildLeft :: [Row] -> TestJournal+buildLeft = List.foldl' (\journal (note, coordinates, side, value) ->+ journal <> ((value .@ (side :< coordinates)) .| note)) mempty++-- | Exercise integer, decimal, ulp, subnormal, and widely separated exponents.+valueGen :: Gen Double+valueGen = elements+ [ 0, 1, 0.01, 0.1, 0.2, 0.3+ , 2 ^ (53 :: Int), 2 ^ (53 :: Int) + 2+ , encodeFloat 1 (-1074), encodeFloat 1 500, encodeFloat 1 900+ ]++rowsGen :: Gen [Row]+rowsGen = do+ count <- chooseInt (0, 16)+ vectorOf count $ do+ note <- chooseInt (0, 5)+ unit <- elements [Yen, Dollar]+ account <- elements [Cash, Deposits]+ side <- elements [Not, Hat]+ value <- valueGen+ pure (note, (unit, account), side, value)++-- | Compare the binary64 representations, including the sign of zero.+bits :: Double -> Word64+bits = castDoubleToWord64++sameBits :: SideTotals -> SideTotals -> Bool+sameBits left right = bits (notTotal left) == bits (notTotal right)+ && bits (hatTotal left) == bits (hatTotal right)++-- | A Rational oracle rounds each independent key and side once.+oracle :: [Row] -> Either CarryError (Map.Map Part SideTotals)+oracle original = traverse finish grouped+ where+ grouped = Map.fromListWith (++)+ [ (coordinates, [(side, toRational value)])+ | (_, coordinates, side, value) <- original, value /= 0 ]+ finish entries = do+ notValue <- rounded [amount | (Not, amount) <- entries]+ hatValue <- rounded [amount | (Hat, amount) <- entries]+ pure (SideTotals notValue hatValue)+ rounded amounts =+ let value = fromRational (sum amounts) :: Double+ in if isInfinite value then Left ResultOutOfRange+ else Right (if value == 0 then 0 else value)++-- | The output equals the Rational oracle for each generated journal.+propOracle :: Property+propOracle = forAll rowsGen $ \original ->+ case (sideTotalsBy id (build original), oracle original) of+ (Right actual, Right expected) ->+ property (Map.keys actual == Map.keys expected+ && and (Map.elems (Map.intersectionWith sameBits actual expected)))+ (Left actual, Left expected) -> actual === expected+ (actual, expected) -> counterexample+ ("actual " ++ show actual ++ ", expected " ++ show expected) False++-- | The same entries keep their bits when reordered or assigned new notes.+propRearrangement :: Property+propRearrangement = forAll rowsGen $ \original ->+ let renoted = zipWith (\note (_, coordinates, side, value) ->+ (note, coordinates, side, value)) (cycle [8, 9, 10]) (reverse original)+ first = sideTotalsBy id (build original)+ second = sideTotalsBy id (buildLeft renoted)+ in case (first, second) of+ (Right left, Right right) -> property (Map.keys left == Map.keys right+ && and (Map.elems (Map.intersectionWith sameBits left right)))+ (Left left, Left right) -> left === right+ _ -> counterexample "rearrangement changed success or failure" False++-- | The accumulator rounds each independent side to the same bits.+propExactParity :: Property+propExactParity = forAll rowsGen $ \original ->+ let checked = sideTotalsBy id (build original)+ grouped = Map.fromListWith (++)+ [ ((coordinates, side), [value])+ | (_, coordinates, side, value) <- original, value /= 0 ]+ compareSide ((coordinates, side), values) =+ let exact = roundAccum (foldr addAccum emptyAccum values)+ observed = do+ totals <- checked+ totalsForKey <- maybe (Left ResultOutOfRange) Right+ (Map.lookup coordinates totals)+ pure (sideTotal (if side == Hat then HatSide else NotSide)+ totalsForKey)+ in fmap bits observed == Right (bits exact)+ in counterexample (show checked) (property (all compareSide (Map.toList grouped)))++-- | Build one raw scalar whose invalid value bypasses the smart constructor.+raw :: Int -> Hat -> Double -> TestJournal+raw note side value = Journal.fromMap $ HashMap.singleton note+ (value `Internal.Single` (side :< (Yen, Cash)))++propBoundaries :: Property+propBoundaries =+ let maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double+ leastSubnormal = encodeFloat 1 (-1074) :: Double+ success = sideTotalsBy id (build+ [(0, (Yen, Cash), Not, maximumFinite),+ (1, (Yen, Cash), Not, leastSubnormal)])+ failure = sideTotalsBy id (build+ [(0, (Yen, Cash), Not, maximumFinite),+ (1, (Yen, Cash), Not, maximumFinite)])+ mixed = sideTotalsBy id (build+ [(0, (Yen, Cash), Not, maximumFinite),+ (1, (Yen, Cash), Not, leastSubnormal),+ (2, (Yen, Cash), Hat, 7),+ (3, (Yen, Deposits), Not, 3)])+ in conjoin+ [ fmap (fmap (bits . notTotal) . Map.lookup (Yen, Cash)) success+ === Right (Just (bits maximumFinite))+ , failure === Left ResultOutOfRange+ , sideTotalsBy id (build+ [(0, (Yen, Cash), Not, maximumFinite),+ (1, (Yen, Cash), Not, 2 ^ (970 :: Int))]) === Left ResultOutOfRange+ , fmap (fmap (\totals -> (bits (notTotal totals), bits (hatTotal totals)))+ . Map.lookup (Yen, Cash)) mixed+ === Right (Just (bits maximumFinite, bits 7))+ , fmap (Map.lookup (Yen, Deposits)) mixed+ === Right (Just (SideTotals 3 0))+ ]++propCases :: Property+propCases =+ let both = sideTotalsBy id (build+ [(0, (Yen, Cash), Not, 2), (1, (Yen, Cash), Hat, 3),+ (2, (Yen, Deposits), Not, 5)])+ merged = sideTotalsBy (const ()) (build+ [(0, (Yen, Cash), Not, 2), (1, (Dollar, Deposits), Not, 3)])+ only = sideTotalsBy id (build [(0, (Yen, Cash), Hat, 2)])+ in conjoin+ [ sideTotalsBy id (mempty :: TestJournal) === Right Map.empty+ , both === Right (Map.fromList+ [((Yen, Cash), SideTotals 2 3), ((Yen, Deposits), SideTotals 5 0)])+ , merged === Right (Map.singleton () (SideTotals 5 0))+ , only === Right (Map.singleton (Yen, Cash) (SideTotals 0 2))+ , fmap (\totals -> (bits (sideTotal NotSide totals),+ bits (sideTotal HatSide totals)))+ (Map.lookup (Yen, Cash) =<< either (const Nothing) Just only)+ === Just (bits 0, bits 2)+ ]++-- | Input errors outrank overflow and do not depend on note traversal.+propFailures :: Property+propFailures = forAll (shuffle invalid) $ \permutation ->+ let assemble :: [(Int, Hat, Double)] -> TestJournal+ assemble entries = Journal.fromMap (HashMap.fromList+ [ (note, value `Internal.Single` (side :< (Yen, Cash)))+ | (note, side, value) <- entries ])+ reassigned = zipWith (\note (_, side, value) -> (note, side, value))+ [11, 12, 13, 14] permutation+ in conjoin+ [ sideTotalsBy id (assemble invalid) === Left WildcardSide+ , sideTotalsBy id (assemble reassigned) === Left WildcardSide+ , sideTotalsBy id (raw 0 HatNot 0) === Left WildcardSide+ , sideTotalsBy id (raw 0 HatNot 1) === Left WildcardSide+ , sideTotalsBy id (raw 0 Not (0 / 0)) === Left NonFiniteValue+ , sideTotalsBy id (raw 0 Hat (1 / 0)) === Left NonFiniteValue+ , sideTotalsBy id (raw 0 Hat ((-1) / 0)) === Left NonFiniteValue+ , sideTotalsBy id (raw 0 Not (-1)) === Left NegativeValue+ , sideTotalsBy id (assemble+ [(1, Not, 0 / 0), (2, Hat, 1 / 0), (3, Not, -1)])+ === Left NonFiniteValue+ , sideTotalsBy id (assemble+ [(1, Not, maximumFinite), (2, Not, maximumFinite),+ (3, Hat, -1)]) === Left NegativeValue+ ]+ where+ maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double+ invalid :: [(Int, Hat, Double)]+ invalid =+ [ (0, HatNot, 0), (1, Not, 0 / 0), (2, Hat, 1 / 0),+ (3, Not, -1) ]++failTest :: String -> IO ()+failTest message = putStrLn ("[FAIL] journal side totals: " ++ message) >> exitFailure++quickProperty :: Testable property => Int -> String -> property -> IO ()+quickProperty count label proposition = do+ result <- quickCheckWithResult stdArgs { maxSuccess = count, chatty = False } proposition+ unless (isSuccess result) $ failTest (label ++ ": " ++ output result)++-- | Run the side-total properties in the package test suite.+runTests :: IO ()+runTests = do+ quickProperty 500 "Rational oracle" propOracle+ quickProperty 500 "reordering and renoting" propRearrangement+ quickProperty 500 "accumulator parity" propExactParity+ quickProperty 1 "range boundaries" propBoundaries+ quickProperty 1 "keys and side selection" propCases+ quickProperty 100 "input failures" propFailures+ putStrLn "[PASS] journal side totals"
test/Posting/NoNumPosted.hs view
@@ -5,24 +5,31 @@ ( literalPosted , addPosted , addPosting+ , coercePosted , genericPosted ) where -import ExchangeAlgebra.Posting (Posted, Posting)+import ExchangeAlgebra.Algebra.Posting (Posted, Posting) import GHC.Generics (from)+import Data.Coerce (coerce)+import ExchangeAlgebra.Algebra.Value (MoneyDouble(..)) -- | Demand an integer literal at the protected posting type.-literalPosted :: Posted+literalPosted :: Posted Double literalPosted = 1 -- | Demand arithmetic at the protected posting type.-addPosted :: Posted -> Posted+addPosted :: Posted Double -> Posted Double addPosted value = value + 1 -- | Demand arithmetic at the protected posting container type.-addPosting :: Posting b -> Posting b+addPosting :: Posting Double b -> Posting Double b addPosting value = value + 1 -- | Demand a Generic representation of the protected posting value.-genericPosted :: Posted -> ()+genericPosted :: Posted Double -> () genericPosted value = from value `seq` ()++-- | Reject bypassing the value type's posting checks through representational equality.+coercePosted :: Posted Double -> Posted MoneyDouble+coercePosted = coerce
test/Posting/PostingSpec.hs view
@@ -4,6 +4,7 @@ -- | Validation, serialization, and algebraic laws for checked ledger postings. module Posting.PostingSpec (runTests) where +import ExchangeAlgebra.Accounting.Account import Control.DeepSeq (force, rnf) import Control.Exception (TypeError, evaluate, try) import Control.Monad (unless)@@ -12,25 +13,32 @@ import System.Exit (exitFailure) import Test.QuickCheck hiding (label) import ExchangeAlgebra.Algebra hiding (map, filter)-import qualified ExchangeAlgebra.Posting as Posting-import ExchangeAlgebra.Posting+import qualified ExchangeAlgebra.Algebra.Posting as Posting+import ExchangeAlgebra.Algebra.Posting ( Posted , PostSide(..) , Posting , entry- , posted- , postedUpperBound , postingAlg , sideHat , unPosted ) import qualified Posting.NoNumPosted as NoNum+import ExchangeAlgebra.Algebra.Value (MoneyDouble) +-- | The 'Double' bound of the posting boundary (0.5.x @postedUpperBound@).+postedUpperBound :: Double+postedUpperBound = 2 ^ (900 :: Int)++-- | 'Posting.posted' at 'Double', so literals need no annotation.+posted :: Double -> Either Posting.PostedError (Posted Double)+posted = Posting.posted+ -- | Account and unit coordinates used for every algebraic property. type TestBase = HatBase (AccountTitles, CountUnit) -- | Values stay exact after all generated postings are added.-type TestEntry = (PostSide, Posted, BasePart TestBase)+type TestEntry = (PostSide, Posted Double, BasePart TestBase) -- | Generate accepted magnitudes across normal and subnormal exponents. genAccepted :: Gen Double@@ -61,7 +69,7 @@ Right checked -> posted (unPosted checked) === Right checked -- | Generate checked integer amounts without floating-point rounding in sums.-genExactPosted :: Gen Posted+genExactPosted :: Gen (Posted Double) genExactPosted = do value <- chooseInteger (0, 1000) case posted (fromInteger value) of@@ -115,7 +123,7 @@ pure (entries, queries) -- | Convert one checked entry through the public constructor.-single :: TestEntry -> Posting TestBase+single :: TestEntry -> Posting Double TestBase single (side, value, part) = entry side value part -- | Ignore only the internal sequence order, preserving each posting's value and base.@@ -140,7 +148,7 @@ x = foldMap single first y = foldMap single second z = foldMap single third- zero = mempty :: Posting TestBase+ zero = mempty :: Posting Double TestBase in counterexample (show entries) $ sameMultiset (postingAlg (zero <> x)) (postingAlg x) && sameMultiset (postingAlg (x <> zero)) (postingAlg x)@@ -178,13 +186,13 @@ nanValue = 0 / 0 :: Double nextAboveBound = postedUpperBound * (1 + 2 ** (-52)) smallestSubnormal = encodeFloat 1 (-1074) :: Double- assertTest "NaN rejected" (posted nanValue == Left Posting.NonFinite)+ assertTest "NaN rejected" (posted nanValue == Left (Posting.InvalidValue NonFiniteValue)) assertTest "positive Infinity rejected" $- posted positiveInfinity == Left Posting.NonFinite+ posted positiveInfinity == Left (Posting.InvalidValue NonFiniteValue) assertTest "negative Infinity rejected" $- posted negativeInfinity == Left Posting.NonFinite+ posted negativeInfinity == Left (Posting.InvalidValue NonFiniteValue) assertTest "negative finite value rejected" $- posted (-1e-300) == Left Posting.Negative+ posted (-1e-300) == Left (Posting.InvalidValue NegativeValue) assertTest "next representable value above bound rejected" $ posted nextAboveBound == Left Posting.AboveBound assertTest "2^1000 rejected" (posted (2 ** 1000) == Left Posting.AboveBound)@@ -209,7 +217,7 @@ sides = [HatSide, NotSide] rejects value = case Binary.decodeOrFail (Binary.encode (value :: Double)) of Left _ -> True- Right (_, _, (_ :: Posted)) -> False+ Right (_, _, (_ :: Posted Double)) -> False decodedNegativeZero = Binary.decodeOrFail (Binary.encode (-0.0 :: Double)) assertTest "Posted Binary round trip" $ all (\value -> Binary.decode (Binary.encode value) == value) checkedValues@@ -226,7 +234,7 @@ ] assertTest "Posted Binary normalizes negative zero" $ case decodedNegativeZero of Left _ -> False- Right (_, _, (value :: Posted)) ->+ Right (_, _, (value :: Posted Double)) -> unPosted value == 0 && 1 / unPosted value > 0 assertTest "NFData and Hashable instances" $ force checkedValues `seq` force sides `seq`@@ -266,9 +274,9 @@ exitFailure Right [first, second, third] -> do let part = (Cash, Yen)- x = entry HatSide first part :: Posting TestBase- y = entry HatSide second part :: Posting TestBase- z = entry HatSide third part :: Posting TestBase+ x = entry HatSide first part :: Posting Double TestBase+ y = entry HatSide second part :: Posting Double TestBase+ z = entry HatSide third part :: Posting Double TestBase left = postingAlg ((x <> y) <> z) right = postingAlg (x <> (y <> z)) assertTest "equal-base grouping changes structural order" (left /= right)@@ -301,21 +309,24 @@ -- | Deferred errors prove that clients cannot request Num for Posted. testNoNum :: IO () testNoNum = do- literal <- try (evaluate NoNum.literalPosted) :: IO (Either TypeError Posted)+ literal <- try (evaluate NoNum.literalPosted) :: IO (Either TypeError (Posted Double)) assertTest "Posted numeric literal is rejected" (isTypeError literal) case posted 1 of Left failure -> do putStrLn ("[FAIL] ledger posting fixture: " ++ show failure) exitFailure Right checked -> do+ coerced <- try (evaluate (NoNum.coercePosted checked))+ :: IO (Either TypeError (Posted MoneyDouble))+ assertTest "Posted value type coercion is rejected" (isTypeError coerced) arithmetic <- try (evaluate (NoNum.addPosted checked))- :: IO (Either TypeError Posted)+ :: IO (Either TypeError (Posted Double)) assertTest "Posted arithmetic is rejected" (isTypeError arithmetic) generic <- try (evaluate (NoNum.genericPosted checked)) :: IO (Either TypeError ()) assertTest "Posted Generic is rejected" (isTypeError generic)- container <- try (evaluate (NoNum.addPosting (mempty :: Posting TestBase)))- :: IO (Either TypeError (Posting TestBase))+ container <- try (evaluate (NoNum.addPosting (mempty :: Posting Double TestBase)))+ :: IO (Either TypeError (Posting Double TestBase)) assertTest "Posting arithmetic is rejected" (isTypeError container) where isTypeError (Left _) = True
test/Posting/SettleSpec.hs view
@@ -3,6 +3,8 @@ -- | Ordered settlement pairs preserve source nets and exact accounting balance. module Posting.SettleSpec (runTests) where +import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange import Control.Monad (forM_, unless) import Data.List (foldl', sort) import qualified Data.Map.Strict as Map@@ -10,7 +12,14 @@ import Test.QuickCheck hiding (label) import ExchangeAlgebra.Algebra hiding (filter, map)-import ExchangeAlgebra.Algebra.Transfer.Rule+import ExchangeAlgebra.Accounting.Closing ( ClosingSide(..)+ , closingSide+ , retainedEarningsRule+ , SettleError(..)+ , SettlementBatch+ , settleEntries+ , settlementSteps+ ) -- | Two coordinates exercise complete-base ordering and axis preservation. type TestBase = HatBase (CountUnit, AccountTitles)
+ test/Simulate/SimulateSpec.hs view
@@ -0,0 +1,1353 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++-- | Classic and Lite engines, ledger policies, spill, and market models.+-- Tests exercise the Simulation layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Simulate.SimulateSpec (runTests) where++import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Journal as EJ+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Accounting.Transfer as Transfer+import ExchangeAlgebra.Accounting.Transfer ((.->), (|%), TransferShare(Relabel))+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified ExchangeAlgebra.Simulate as ES+import ExchangeAlgebra.Simulate+import qualified ExchangeAlgebra.Simulate.Lite as Lite+import ExchangeAlgebra.Simulation.Network (TradeNetwork+ , InputCoefficients+ , suppliersOf+ , coefficient+ , completeNetwork+ , erdosRenyi+ , defaultCoefOptions+ , randomCoefficients+ )+import ExchangeAlgebra.Simulate.Lite (InitT+ , SnapT+ , HK+ , Field(..)+ , carry+ , resetEach+ , updateEach+ , Stage+ , stage+ , stageFor+ , stageOf+ , Par(..)+ , SimSpec+ , mkSimSpec+ , runLite+ , runLiteFold+ , runLiteWithPolicy+ , runLiteWithPolicyObs+ )+import qualified ExchangeAlgebra.Simulate.Policy as Policy+import ExchangeAlgebra.Algebra.Value (MoneyDouble)+import ExchangeAlgebra.Simulate.Spill (restoreJournalFromBinarySpill, restoreJournalFromBinarySpillChecked)+import qualified Data.HashMap.Strict as HM+import qualified Data.Map.Strict as M+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.List as L+import qualified Data.Binary as Binary+import qualified Data.ByteString.Lazy as BL+import Control.Monad (forM_)+import Control.Monad.ST (ST, stToIO)+import Data.Array.ST (STArray)+import Data.STRef (STRef+ , newSTRef+ , readSTRef+ , modifySTRef'+ )+import Data.IORef (newIORef, readIORef, modifyIORef')+import System.Exit (exitFailure)+import System.IO (IOMode(WriteMode)+ , withFile+ , hSetEncoding+ , utf8+ )+import System.Random (StdGen+ , mkStdGen+ , randomR+ , split+ )+import Control.Monad (replicateM)+import Control.Monad.State (runState, state)+import Control.Exception (try, ErrorCall)+import GHC.Generics (Generic)+import Support (TestComparison(..)+ , assertEqual+ , assertNear+ , runTestComparison+ , removeSpillTestFile+ , SimTerm+ , SimCompany+ , SimHatBase2+ , withTestTemporaryFile+ )++type SpillRestoreJournal = EJ.Journal (String, Int) Double (HatBase CountUnit)++-- | Design-review C4: the spill/eviction decision logic is single-sourced in+-- 'ES.stepBackWith' / 'ES.spillDeleteDecision' (previously duplicated inline+-- in the classic spill loop and in Lite's retention loop). Pin the decision+-- table and the equivalence with Lite's former @backByTerms@.+testSpillDecisionSingleSource :: IO ()+testSpillDecisionSingleSource = do+ assertEqual "stepBackWith pred 3 10" (7 :: Int) (ES.stepBackWith pred 3 10)+ assertEqual "stepBackWith is id for n <= 0" (10 :: Int) (ES.stepBackWith pred 0 10)+ assertEqual "stepBackWith is id for negative n" (10 :: Int) (ES.stepBackWith pred (-1) 10)+ assertEqual "NoDelete evicts nothing"+ Nothing (ES.spillDeleteDecision pred (ES.NoDelete :: ES.SpillDeletePolicy Int) (1, 10))+ assertEqual "DeleteSpilledChunk evicts exactly the chunk"+ (Just (1, 10)) (ES.spillDeleteDecision pred ES.DeleteSpilledChunk (1 :: Int, 10))+ assertEqual "KeepRecentTerms 3 keeps the trailing window"+ (Just (1, 7)) (ES.spillDeleteDecision pred (ES.KeepRecentTerms 3) (1 :: Int, 10))+ assertEqual "KeepRecentTerms covering the chunk evicts nothing"+ Nothing (ES.spillDeleteDecision pred (ES.KeepRecentTerms 12) (1 :: Int, 10))+ -- Lite boundary equivalence: former backByTerms w t == stepBackWith pred w t+ let backByTermsRef w t = let go n x | n <= (0 :: Int) = x+ | otherwise = go (n - 1) (pred x)+ in go w t+ forM_ [(0, 5), (1, 5), (3, 5), (7, 5)] $ \(w, t) ->+ assertEqual ("Lite boundary equivalence w=" ++ show w)+ (backByTermsRef w t) (ES.stepBackWith pred w (t :: Int))++writeSpillTestChunks+ :: FilePath+ -> [((Int, Int), SpillRestoreJournal)]+ -> IO ()+writeSpillTestChunks path chunks = do+ removeSpillTestFile path+ withFile path WriteMode $ \h ->+ forM_ chunks $ \(termRange, chunk) ->+ ES.defaultBinarySpillWriter h termRange chunk++spillCheckedChunk1 :: SpillRestoreJournal+spillCheckedChunk1 = EJ.fromList+ [ (1 .@ (Hat :< Yen)) .| ("A", 1)+ , (2 .@ (Not :< Amount)) .| ("B", 2)+ ]++spillCheckedChunk2 :: SpillRestoreJournal+spillCheckedChunk2 = (3 .@ (Hat :< Yen)) .| ("C", 3)++spillCheckedCurrent :: SpillRestoreJournal+spillCheckedCurrent = EJ.fromList+ [ (4 .@ (Not :< Amount)) .| ("Tail", 4)+ , (8 .@ (Hat :< Yen)) .| ("AlreadySpilled", 2)+ ]++spillCheckedExpected :: SpillRestoreJournal+spillCheckedExpected =+ spillCheckedChunk1 .+ spillCheckedChunk2+ .+ ((4 .@ (Not :< Amount)) .| ("Tail", 4))++data SimInitVar = SimInitVar+ { _simInitStock :: Double+ , _simSteadyProduction :: Double+ , _simInhouseRatio :: Double+ } deriving (Eq, Show)++instance InitVariables SimInitVar where++data SimEvent+ = SimSalesPurchase+ | SimProduction+ | SimPlank+ deriving (Ord, Show, Enum, Eq, Bounded, Generic)++instance Hashable SimEvent where++instance Note SimEvent where+ plank = SimPlank++instance Event SimEvent where++simFstC, simLastC :: SimCompany+simFstC = 1+simLastC = 6++simCompanies :: [SimCompany]+simCompanies = [simFstC .. simLastC]++-- Accounting value type is MoneyDecimal (exact): ledger arithmetic is exact and+-- construction-order-independent. ABM parameters / input coefficients / random+-- draws remain Double and are converted (realToFrac) at the boundary where they+-- enter the ledger; reported stock/profit convert back to Double.+type SimTransaction = EJ.Journal (SimEvent, SimTerm) MoneyDecimal SimHatBase2++simCompressPreviousTerm :: SimTerm -> SimTransaction -> SimTransaction+simCompressPreviousTerm t le =+ EJ.fromMap $+ L.foldl' (\acc ev -> HM.adjust compress (ev, t) acc)+ (EJ.toMap le)+ [fstEvent .. lastEvent]++newtype SimLedger s = SimLedger (STRef s SimTransaction)++instance UpdatableSTRef SimLedger s SimTransaction where+ _unwrapURef (SimLedger x) = x+ _wrapURef x = SimLedger x++simInitLedger :: Double -> ST s (SimLedger s)+simInitLedger d = newURef $ EJ.fromList+ [ realToFrac d .@ Not :<(Products, e, e, Amount) .| (plank, initTerm) -- Double param -> MoneyDecimal+ | e <- simCompanies+ ]++instance Updatable SimTerm SimInitVar SimLedger s where+ type Inner SimLedger s = STRef s SimTransaction+ unwrap = _unwrapURef+ initialize _ _ e = simInitLedger (_simInitStock e)+ updatePattern _ = return Modify+ modify _ t _ x = do+ le <- readURef x+ let previous = EJ.toAlg $ simTermJournal (t - 1) le+ -- Invariant: the fixture posts finite, nonnegative values.+ closing <- either (error . show) pure $ Closing.closingEntries previous+ let added = EA.bar (previous .+ closing) .| (plank, t)+ next = simCompressPreviousTerm (t - 1) (le .+ added)+ writeURef x next++type SimInputCoefficient = Double++newtype SimICTable s = SimICTable (STArray s (SimCompany, SimCompany) SimInputCoefficient)++instance UpdatableSTArray SimICTable s (SimCompany, SimCompany) SimInputCoefficient where+ _unwrapUArray (SimICTable arr) = arr+ _wrapUArray arr = SimICTable arr++simGenerateRandomList :: StdGen -> Int -> ([Double], StdGen)+simGenerateRandomList g n =+ let (xs, g') = runState (replicateM n (state (randomR (0, 1.0))))+ (updateGen g 1000)+ ys = L.map (\v -> if v < 0.1 then 0 else v) xs+ in (ys, g')++simInitTermCoefficients :: StdGen -> Double -> M.Map SimCompany [SimInputCoefficient]+simInitTermCoefficients g inhouseRatio =+ fst $ L.foldl' buildRow (M.empty, g) simCompanies+ where+ buildRow (acc, g0) c2 =+ let (row, g1) = generateRow g0+ in (M.insert c2 row acc, g1)+ generateRow g0 =+ let (vals, g1) = simGenerateRandomList g0 simLastC+ total = sum vals+ normalized = L.map (\v -> (v / total) * inhouseRatio) vals+ in (normalized, g1)++simInitICTables :: StdGen -> Double -> ST s (SimICTable s)+simInitICTables g inhouseRatio = do+ arr <- newUArray ((simFstC, simFstC), (simLastC, simLastC)) 0+ let termCoefficients = simInitTermCoefficients g inhouseRatio+ forM_ simCompanies $ \c2 -> do+ let row = termCoefficients M.! c2+ forM_ (zip simCompanies row) $ \(c1, coef) ->+ writeUArray arr (c1, c2) coef+ return arr++instance Updatable SimTerm SimInitVar SimICTable s where+ type Inner SimICTable s = STArray s (SimCompany, SimCompany) SimInputCoefficient+ unwrap (SimICTable a) = a+ initialize g _ e = simInitICTables g (_simInhouseRatio e)+ updatePattern _ = return DoNothing++type SimSteadyProd = Double++newtype SimSP s = SimSP (STRef s SimSteadyProd)++instance UpdatableSTRef SimSP s SimSteadyProd where+ _unwrapURef (SimSP x) = x+ _wrapURef x = SimSP x++instance Updatable SimTerm SimInitVar SimSP s where+ type Inner SimSP s = STRef s SimSteadyProd+ unwrap = _unwrapURef+ initialize _ _ e = newURef (_simSteadyProduction e)+ updatePattern _ = return DoNothing++data SimWorld s = SimWorld+ { _simLedger :: SimLedger s+ , _simIcs :: SimICTable s+ , _simSp :: SimSP s+ } deriving (Generic)++-- helper functions++simTermJournal :: SimTerm -> SimTransaction -> SimTransaction+simTermJournal t = EJ.filterWithNote (\(_, t') _ -> t' == t)++simGetOneProduction :: SimWorld s -> SimTerm -> SimCompany -> ST s SimTransaction+simGetOneProduction wld t c = do+ let arr = _simIcs wld+ inputs <- mapM (\c2 -> do+ coef <- readUArray arr (c2, c)+ return $ realToFrac coef .@ Hat :<(Products, c2, c, Amount) .| (SimProduction, t) -- Double coef -> MoneyDecimal+ ) simCompanies+ let totalInput = EJ.fromList inputs+ result = (1 .@ Not :<(Products, c, c, Amount) .| (SimProduction, t)) .+ totalInput+ return result++simJournal :: SimWorld s -> SimTransaction -> ST s ()+simJournal _ Zero = return ()+simJournal wld js = modifyURef (_simLedger wld) (\x -> x .+ js)++-- Values come from the MoneyDecimal ledger (via EA.toList), so the shortage map is+-- MoneyDecimal-valued; no conversion is needed and the amounts re-enter the ledger exactly.+simBuildShortageMap :: SimTerm -> SimTransaction -> M.Map (SimCompany, SimCompany) MoneyDecimal+simBuildShortageMap t le =+ let termAlg = EJ.toAlg $ (.-) $ simTermJournal t le+ in L.foldl' go M.empty (EA.toList termAlg)+ where+ go acc (v :@ (Hat :< (Products, j, i, Amount))) = M.insertWith (+) (i, j) v acc+ go acc _ = acc++simPurchases :: SimTerm -> SimWorld s -> ST s SimTransaction+simPurchases t wld = do+ le <- readURef (_simLedger wld)+ let shortageMap = simBuildShortageMap t le+ o i j = M.findWithDefault 0 (i, j) shortageMap+ return $ sigma simCompanies $ \i+ -> sigma (simCompanies L.\\ [i]) $ \j+ -> (o i j) .@ Not :<(Products, j, i, Amount)+ .+ (o i j) .@ Hat :<(Cash, (.#), i, Yen)+ .+ (o i j) .@ Not :<(Purchases, (.#), i, Yen)+ .+ (o i j) .@ Not :<(Cash, (.#), j, Yen)+ .+ (o i j) .@ Not :<(Sales, (.#), j, Yen)+ .+ (o i j) .@ Hat :<(Products, j, j, Amount)+ .| (SimSalesPurchase, t)++instance StateSpace SimTerm SimInitVar SimEvent SimWorld s where+ event = simEvent++simEvent :: SimWorld s -> SimTerm -> SimEvent -> ST s ()++simEvent wld t SimSalesPurchase = do+ toAdd <- simPurchases t wld+ simJournal wld toAdd++simEvent wld t SimProduction = do+ sp <- readURef (_simSp wld)+ forM_ simCompanies $ \e1 -> do+ op <- simGetOneProduction wld t e1+ simJournal wld (realToFrac sp .* op) -- Double steady-production multiplier -> MoneyDecimal scalar++simEvent _ _ SimPlank = return ()++simGetTermStock :: SimWorld s -> SimTerm -> SimCompany -> ST s Double+simGetTermStock wld t e = do+ le <- readURef (_simLedger wld)+ let tj = (.-) $ simTermJournal t le+ plusStock = norm $ EJ.projWithBase [Not :<(Products, e, e, Amount)] tj+ minusStock = norm $ EJ.projWithBase [Hat :<(Products, e, e, Amount)] tj+ return $ realToFrac (plusStock - minusStock) -- exact MoneyDecimal stock -> Double for reporting++simGetTermGrossProfit :: SimWorld s -> SimTerm -> SimCompany -> ST s Double+simGetTermGrossProfit wld t e = do+ le <- readURef (_simLedger wld)+ let termTr = simTermJournal t le+ -- Invariant: distinct, classified fixture accounts and finite, nonnegative postings.+ rules :: Transfer.TransferTable SimHatBase2+ rules = either (error . show) id $ Transfer.mkTransferTable+ [ (Sales, (.#), (.#), (.#)) .-> (GrossProfit, (.#), (.#), (.#)) |% Relabel+ , (Purchases, (.#), (.#), (.#)) .-> (GrossProfit, (.#), (.#), (.#)) |% Relabel+ , (WageExpenditure, (.#), (.#), (.#)) .-> (GrossProfit, (.#), (.#), (.#)) |% Relabel+ , (Depreciation, (.#), (.#), (.#)) .-> (GrossProfit, (.#), (.#), (.#)) |% Relabel+ , (ValueAdded, (.#), (.#), (.#)) .-> (GrossProfit, (.#), (.#), (.#)) |% Relabel+ ]+ entries :: SimTransaction+ entries = either (error . show) id $ Transfer.transferEntriesBy id rules termTr+ tr = termTr .+ entries+ plus = norm $ EJ.projWithBase [Not :<(GrossProfit, (.#), e, Yen)] tr+ minus = norm $ EJ.projWithBase [Hat :<(GrossProfit, (.#), e, Yen)] tr+ return $ realToFrac (plus - minus) -- exact MoneyDecimal -> Double for reporting++-- ================================================================+-- Simulation integration test+-- ================================================================++simEps :: Double+simEps = 1e-6++assertSimNear :: String -> Double -> Double -> IO ()+assertSimNear label expected actual+ | abs (expected - actual) <= simEps = putStrLn ("[PASS] " ++ label)+ | otherwise = do+ putStrLn ("[FAIL] " ++ label)+ putStrLn (" expected: " ++ show expected)+ putStrLn (" actual : " ++ show actual)+ exitFailure++testSimulateEx1Default :: IO ()+testSimulateEx1Default = do+ let gen = mkStdGen 2025+ defaultEnv = SimInitVar+ { _simInitStock = 20+ , _simInhouseRatio = 0.4+ , _simSteadyProduction = 10+ }++ wld <- ES.runSimulation gen defaultEnv++ -- Stock at term 1 for each company+ stocks1 <- stToIO $ mapM (simGetTermStock wld 1) simCompanies+ -- Stock at term 50 for each company+ stocks50 <- stToIO $ mapM (simGetTermStock wld 50) simCompanies+ -- Stock at term 100 for each company+ stocks100 <- stToIO $ mapM (simGetTermStock wld 100) simCompanies+ -- Gross profit at term 50 for each company+ profits50 <- stToIO $ mapM (simGetTermGrossProfit wld 50) simCompanies++ -- Stock at t=1+ assertSimNear "fixed-price-io stock(t=1,c=1)" 28.487224703666264 (stocks1 !! 0)+ assertSimNear "fixed-price-io stock(t=1,c=3)" 30.0 (stocks1 !! 2)+ assertSimNear "fixed-price-io stock(t=1,c=6)" 30.0 (stocks1 !! 5) -- re-baselined: union zero-base fix removed a phantom self-input+ -- Stock at t=50+ assertSimNear "fixed-price-io stock(t=50,c=1)" 304.9028131162567 (stocks50 !! 0)+ assertSimNear "fixed-price-io stock(t=50,c=4)" 292.4764622201871 (stocks50 !! 3)+ -- Stock at t=100+ assertSimNear "fixed-price-io stock(t=100,c=1)" 586.9595359862476 (stocks100 !! 0)+ assertSimNear "fixed-price-io stock(t=100,c=6)" 767.9605634804993 (stocks100 !! 5) -- re-baselined: union zero-base fix (bug compounded over terms)+ -- Gross profit at t=50+ assertSimNear "fixed-price-io profit(t=50,c=1)" 0.35886554260018855 (profits50 !! 0)+ assertSimNear "fixed-price-io profit(t=50,c=2)" 1.572544209772035 (profits50 !! 1)++-- ================================================================+-- Simulate.Lite tests (Phase 2, feat/simulate-lite)+-- ================================================================++-- A concrete Note type for the Lite models: (event tag, term index).+type LNote = (String, Int)+type LBaseD = HatBase AccountTitles+type LedgerD = Journal LNote MoneyDouble LBaseD -- IEEE-754 path (DET-1, BSP, equiv)+type LBaseM = HatBase AccountTitles+type LedgerM = Journal LNote MoneyDecimal LBaseM -- exact path (DET-2)++------------------------------------------------------------------+-- Lite test 1: boilerplate acceptance example (3 fields, 2 stages).+-- The body of this function (the World record, the two stages, the spec and+-- the run) is the "~20 line" boilerplate the design targets.+------------------------------------------------------------------++-- A product-only HKD world: a ledger, a scalar price, a scalar tax rate.+data MiniW f = MiniW+ { mwLedger :: HK f LedgerD+ , mwPrice :: HK f MoneyDouble+ , mwTax :: HK f Double+ } deriving Generic++-- stage A: each agent buys 1 unit at the snapshot price (a pure message).+buyStage :: Stage MiniW Int LNote MoneyDouble LBaseD+buyStage = stageFor (T.pack "buy") [1 .. 5 :: Int] $ \w t _g i ->+ let amt = mwPrice w * fromIntegral i+ in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("buy", t)++-- stage B: a single bookkeeping step paying tax on the snapshot price.+taxStage :: Stage MiniW Int LNote MoneyDouble LBaseD+taxStage = stage (T.pack "tax") $ \w t ->+ let amt = mwPrice w * realToFrac (mwTax w)+ in ((amt .@ Not :< Sales) .+ (amt .@ Hat :< Cash)) .| ("tax", t)++miniSpec :: SimSpec MiniW Int LNote MoneyDouble LBaseD+miniSpec = mkSimSpec (1, 3) 42 mwLedger [buyStage, taxStage]+++------------------------------------------------------------------+-- Lite test 2 (DET-2): MoneyDecimal Sequential vs ParChunk exact match.+------------------------------------------------------------------++data DecW f = DecW+ { dwLedger :: HK f LedgerM+ } deriving Generic++decStage :: Stage DecW Int LNote MoneyDecimal LBaseM+decStage = stageFor (T.pack "post") [1 .. 50 :: Int] $ \_w t g i ->+ let (k, _) = randomR (1, 9 :: Int) g+ amt = fromIntegral (i + k) :: MoneyDecimal+ in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)++decSpec :: Par -> SimSpec DecW Int LNote MoneyDecimal LBaseM+decSpec par = (mkSimSpec (1, 4) 7 dwLedger [decStage]) { Lite.specParallel = par }++testLiteDet2 :: IO ()+testLiteDet2 = do+ let w0 = DecW { dwLedger = carry mempty }+ runP par = runLite (decSpec par) w0 (toMap . dwLedger)+ seqMap = runP Sequential+ parMap = runP (ParChunk 8)+ assertEqual "Lite DET-2: Sequential and ParChunk produce identical ledgers (exact)"+ seqMap parMap+++------------------------------------------------------------------+-- Lite test 2 (DET-2): MoneyDouble with snapshot-dependent postings.+------------------------------------------------------------------++-- | Carry the uncompressed floating-point ledger across stages and terms.+data DoubleW f = DoubleW+ { doubleLedger :: HK f LedgerD -- ^ All postings committed before the current stage.+ } deriving Generic++-- | Post a positive fractional amount from each agent's stage generator.+doublePostStage :: Stage DoubleW Int LNote MoneyDouble LBaseD+doublePostStage = stageFor (T.pack "double-post") [1 .. 50 :: Int] $ \_world term generator agent ->+ let (draw, _) = randomR (1, 6 :: Int) generator+ amount = fromIntegral agent + fromIntegral draw / 7 :: MoneyDouble+ in ( (amount .@ Not :< Purchases)+ .+ (amount .@ Hat :< Cash)) .| ("double-post", term)++-- | Feed the committed snapshot's norm back into the next posting amount.+doubleSnapshotStage :: Stage DoubleW Int LNote MoneyDouble LBaseD+doubleSnapshotStage = stage (T.pack "double-snapshot") $ \world term ->+ let amount = norm (doubleLedger world)+ in ( (amount .@ Not :< Purchases)+ .+ (amount .@ Hat :< Cash)) .| ("double-snapshot", term)++-- | Compare ledger structure and final norm exactly for every tested chunk width.+testLiteDet2Double :: IO ()+testLiteDet2Double = do+ let initialWorld = DoubleW { doubleLedger = carry mempty }+ spec = mkSimSpec (1, 4) 7 doubleLedger [doublePostStage, doubleSnapshotStage]+ runParallel parallel = runLite (spec { Lite.specParallel = parallel })+ initialWorld doubleLedger+ sequentialLedger = runParallel Sequential+ forM_ [1, 2, 3, 8] $ \chunk -> do+ let parallelLedger = runParallel (ParChunk chunk)+ assertEqual ("Lite DET-2 MoneyDouble: ledger exact, chunk=" ++ show chunk)+ (toMap sequentialLedger) (toMap parallelLedger)+ assertEqual ("Lite DET-2 MoneyDouble: norm exact, chunk=" ++ show chunk)+ (norm sequentialLedger) (norm parallelLedger)+++------------------------------------------------------------------+-- Lite test 4 (BSP intra-stage invisibility sentinel).+-- Every agent in a stage reads the SAME snapshot. We encode the snapshot+-- ledger's norm into each agent's message; if a later agent could see an+-- earlier agent's write within the same stage, the encoded norms would differ+-- from the all-zero baseline (the ledger starts empty for term 1 stage 0).+------------------------------------------------------------------++data BspW f = BspW+ { bwLedger :: HK f LedgerD+ } deriving Generic++-- each agent posts (1 + norm-of-snapshot-ledger). On term 1, stage 0, the+-- snapshot ledger is empty for every agent, so each posts exactly 1.0.+bspStage :: Stage BspW Int LNote MoneyDouble LBaseD+bspStage = stageFor (T.pack "bsp") [1 .. 10 :: Int] $ \w t _g _i ->+ let seenNorm = norm (bwLedger w) -- must be 0 for ALL agents (BSP)+ amt = 1 + realToFrac seenNorm :: MoneyDouble+ in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("bsp", t)++bspSpec :: SimSpec BspW Int LNote MoneyDouble LBaseD+bspSpec = mkSimSpec (1, 1) 0 bwLedger [bspStage]++testLiteBspInvisibility :: IO ()+testLiteBspInvisibility = do+ let w0 = BspW { bwLedger = carry mempty }+ n = runLite bspSpec w0 (realToFrac . norm . bwLedger)+ -- 10 agents each post Not:<Purchases 1.0 + Hat:<Cash 1.0 = norm 20.+ -- If intra-stage writes were visible, later agents would post > 1.0 and the+ -- norm would exceed 20.+ assertNear "Lite BSP: intra-stage invisibility (all agents see empty ledger)"+ 20.0 n++------------------------------------------------------------------+-- Lite test 5: gate toy-model equivalence (3 terms, agents [1..10], norm 3300).+-- Rebuilds the gate-report.md prototype with the Lite API; same norm.+------------------------------------------------------------------++data GateW f = GateW+ { gwLedger :: HK f LedgerD+ , gwPrice :: HK f MoneyDouble+ } deriving Generic++gateStage :: Stage GateW Int LNote MoneyDouble LBaseD+gateStage = stageFor (T.pack "buy") [1 .. 10 :: Int] $ \w t _g i ->+ let amt = gwPrice w * fromIntegral i+ in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("buy", t)++gateSpec :: SimSpec GateW Int LNote MoneyDouble LBaseD+gateSpec = mkSimSpec (1, 3) 1 gwLedger [gateStage]+++------------------------------------------------------------------+-- Lite test 6: term-boundary Field rules (Carry / ResetEach / UpdateEach).+-- One agent posts (current price) each term; the three runs differ only in the+-- price field's boundary rule, exercising each Field constructor.+------------------------------------------------------------------++data RuleW f = RuleW+ { rwLedger :: HK f LedgerD+ , rwPrice :: HK f MoneyDouble+ } deriving Generic++ruleStage :: Stage RuleW Int LNote MoneyDouble LBaseD+ruleStage = stage (T.pack "post") $ \w t ->+ let amt = rwPrice w+ in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)++ruleSpec :: SimSpec RuleW Int LNote MoneyDouble LBaseD+ruleSpec = mkSimSpec (1, 3) 0 rwLedger [ruleStage]++runRule :: Field MoneyDouble -> Double+runRule priceField =+ let w0 = RuleW { rwLedger = carry mempty, rwPrice = priceField }+ in realToFrac (runLite ruleSpec w0 (norm . rwLedger))+ -- norm counts both Not:<Purchases and Hat:<Cash, hence 2 * price each term+++-- ================================================================+-- Simulate.Policy tests (Phase 4, feat/ledger-policy)+--+-- LedgerPolicy = declarative retention / spill / compaction, applied at the+-- term boundary by runLiteWithPolicy. The exact MoneyDecimal value type lets us+-- assert lossless round-trips and norm/compaction invariants by strict equality.+-- ================================================================++-- A one-field world whose stage posts a few distinct bases per term, so that a+-- closed term has redundant per-base sequences (exercising CompressClosedTerms)+-- and a multi-term history (exercising RetainRecent + spill).+data PolW f = PolW+ { pwLedger :: HK f LedgerM+ } deriving Generic++-- A single-field world for the classic-bridge test: a constructor @a@ of kind+-- @Type -> Type@ whose @a RealWorld@ is an @STRef RealWorld LedgerM@ (so it fits+-- the @SpillOptions t a payload@ shape, where @a@ is applied to the state token).+newtype LedgerRef s = LedgerRef (STRef s LedgerM)++-- Each agent posts twice to the SAME base within the term, so the term's per-base+-- posting sequence has length 2 before compress and length 1 after.+polStage :: Stage PolW Int LNote MoneyDecimal LBaseM+polStage = stageFor (T.pack "post") [1 .. 4 :: Int] $ \_w t _g i ->+ let amt = fromIntegral i :: MoneyDecimal+ one = 1 :: MoneyDecimal+ m1 = ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)+ m2 = ((one .@ Not :< Purchases) .+ (one .@ Hat :< Cash)) .| ("post", t)+ in m1 .+ m2 :: Journal LNote MoneyDecimal LBaseM++polSpec :: SimSpec PolW Int LNote MoneyDecimal LBaseM+polSpec = mkSimSpec (1, 5) 0 pwLedger [polStage]++polW0 :: PolW InitT+polW0 = PolW { pwLedger = carry mempty }++-- Test 1 (flagship): defaultLedgerPolicy is observationally equal to runLite.+testPolicyEquivalence :: IO ()+testPolicyEquivalence = do+ let pureLedger = runLite polSpec polW0 (toMap . pwLedger)+ polLedger <- runLiteWithPolicy Policy.defaultLedgerPolicy polSpec polW0 (toMap . pwLedger)+ assertEqual "Policy: runLiteWithPolicy defaultLedgerPolicy == runLite (exact)"+ pureLedger polLedger++-- Test 2 (flagship): RetainRecent w + spillTo gives an in-memory window AND a+-- lossless restore that equals the FullAudit ledger.+testPolicyWindowRoundTrip :: IO ()+testPolicyWindowRoundTrip = withTestTemporaryFile $ \path -> do+ let full = runLite polSpec polW0 (toMap . pwLedger) -- FullAudit reference+ pol = Policy.defaultLedgerPolicy+ { Policy.retain = Policy.RetainRecent 2+ , Policy.spillTo = Just path }+ -- ONE policy run (append-mode spill: a second run would double the file),+ -- projecting the live journal; we derive both checks from it.+ residentJournal <- runLiteWithPolicy pol polSpec polW0 pwLedger+ -- (a) in-memory ledger after the run contains ONLY the most recent 2 terms.+ let residentMap = toMap residentJournal+ residentTerms = L.sort (L.nub [ t | (_, t) <- HM.keys residentMap ])+ assertEqual "Policy: RetainRecent 2 leaves only the most recent 2 terms resident"+ [4, 5] residentTerms+ -- (b) restoreLedger (spill file + resident remainder) == FullAudit ledger.+ restored <- Policy.restoreLedger path residentJournal :: IO LedgerM+ assertEqual "Policy: restoreLedger (spill + remainder) == FullAudit ledger (lossless, exact)"+ full (toMap restored)++-- Test 3: CompressClosedTerms — norm/balance invariant, closed-term seq length 1,+-- in-progress term keeps full redundancy.+testPolicyCompressClosed :: IO ()+testPolicyCompressClosed = do+ let full = runLite polSpec polW0 (toMap . pwLedger)+ compactedJ <- runLiteWithPolicy+ (Policy.defaultLedgerPolicy { Policy.compaction = Policy.CompressClosedTerms })+ polSpec polW0 pwLedger+ let fullJ = runLite polSpec polW0 pwLedger+ -- (a) norm is invariant under compaction.+ assertEqual "Policy: CompressClosedTerms preserves norm (exact)"+ (norm fullJ) (norm compactedJ)+ -- (b) balance result unchanged (still balanced overall).+ assertEqual "Policy: CompressClosedTerms preserves balance"+ (Exchange.balance fullJ) (Exchange.balance compactedJ)+ -- (c) each CLOSED term (1..4) has at most one posting per base/side: its Alg+ -- compresses to itself (idempotent), so compress . entry == entry.+ let compactedMap = toMap compactedJ+ closedOk = all+ (\((_, t), alg) -> t == (5 :: Int) || EA.compress alg == alg)+ (HM.toList compactedMap)+ assertEqual "Policy: closed terms are already compressed (compress is a no-op on them)"+ True closedOk+ -- (d) the in-progress term (5) keeps its redundancy: in the FULL ledger term+ -- 5's entry has a length-2 sequence, and the compacted ledger keeps the+ -- SAME term-5 entry (untouched), i.e. it differs from its own compress.+ let term5Full = HM.lookup ("post", 5) (toMap fullJ)+ term5Comp = HM.lookup ("post", 5) compactedMap+ assertEqual "Policy: in-progress term is untouched by CompressClosedTerms"+ term5Full term5Comp+ case term5Comp of+ Just alg -> assertEqual "Policy: in-progress term retains its redundant sequence"+ False (EA.compress alg == alg)+ Nothing -> assertEqual "Policy: in-progress term present" True False++-- Test 4: deletion-only (spillTo Nothing + RetainRecent) narrows the ledger to+-- the window and reduces its norm by exactly the discarded terms' norm.+testPolicyDeleteOnly :: IO ()+testPolicyDeleteOnly = do+ let pol = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainRecent 2 }+ residentJournal <- runLiteWithPolicy pol polSpec polW0 pwLedger+ let residentMap = toMap residentJournal+ residentTerms = L.sort (L.nub [ t | (_, t) <- HM.keys residentMap ])+ -- the FullAudit ledger restricted to the same window must match exactly+ -- (deletion is just a filter; the kept terms are untouched).+ full = runLite polSpec polW0 pwLedger+ windowOfFull = EJ.filterWithNote (\(_, t) _ -> t >= 4) full+ assertEqual "Policy: delete-only leaves only the window terms" [4, 5] residentTerms+ assertEqual "Policy: delete-only window equals FullAudit restricted to the window (exact)"+ (toMap windowOfFull) residentMap+ -- norm strictly drops (terms 1..3 were discarded with no spill).+ assertEqual "Policy: discarding older terms strictly reduces norm"+ True (norm residentJournal < norm full)++-- Test 5: policy execution preserves exact Sequential / ParChunk parity.+testPolicyDeterminism :: IO ()+testPolicyDeterminism = withTestTemporaryFile $ \_ -> do+ let pol = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainRecent 3 }+ specPar p = polSpec { Lite.specParallel = p }+ r1 <- runLiteWithPolicy pol (specPar Sequential) polW0 (toMap . pwLedger)+ rP <- runLiteWithPolicy pol (specPar (ParChunk 2)) polW0 (toMap . pwLedger)+ assertEqual "Policy DET-2: Sequential == ParChunk under policy (exact)" r1 rP++-- Test 6 (classic bridge): policySpillOptions drives the classic engine and the+-- result restores losslessly, mirroring the existing binary-spill restore test.+-- We exercise the derived chunk extraction + eviction directly (no full+-- StateSpace needed) by checking the option fields it builds.+testPolicyClassicBridge :: IO ()+testPolicyClassicBridge = withTestTemporaryFile $ \path -> do+ -- Build a ledger spanning terms 1..3, spill terms 1..2 via the policy-derived+ -- chunk extractor, keep term 3 as the remainder, then restore == whole ledger.+ let pol = Policy.defaultLedgerPolicy+ { Policy.retain = Policy.RetainRecent 1, Policy.spillTo = Just path }+ whole :: LedgerM+ whole = EJ.fromList+ [ (1 .@ Not :< Purchases) .| ("post", 1)+ , (2 .@ Not :< Purchases) .| ("post", 2)+ , (3 .@ Not :< Purchases) .| ("post", 3) ]+ -- the option built by the bridge; we use its spillExtractChunk to carve+ -- terms 1..2 and write them, exactly as runSimulationWithSpill would.+ opts = Policy.policySpillOptions pol 2+ (\(LedgerRef r) -> readSTRef r)+ (\f (LedgerRef r) -> modifySTRef' r f)+ :: ES.SpillOptions Int LedgerRef LedgerM+ -- emulate a single spill of the [1,2] chunk + eviction of term <= 2.+ ref <- LedgerRef <$> stToIO (newSTRef whole)+ chunk <- case ES.spillExtractChunk opts of+ Just extract -> stToIO (extract (1, 2) ref)+ Nothing -> error "policySpillOptions must set spillExtractChunk"+ withFile path WriteMode $ \h -> ES.spillWriteChunk opts h (1, 2) chunk+ stToIO (ES.spillDeleteRange opts (1, 2) ref)+ let LedgerRef r0 = ref+ remainder <- stToIO (readSTRef r0)+ -- remainder is now only term 3; restore merges spill + remainder == whole.+ restored <- Policy.restoreLedger path remainder :: IO LedgerM+ assertEqual "Policy bridge: policySpillOptions chunk keeps spilled-range terms"+ (toMap (EJ.filterWithNote (\(_, t) _ -> t >= 1 && t <= 2) whole)) (toMap chunk)+ assertEqual "Policy bridge: after eviction the remainder is only the kept window"+ (toMap (EJ.filterWithNote (\(_, t) _ -> t > 2) whole)) (toMap remainder)+ assertEqual "Policy bridge: restore (spill + remainder) == whole ledger (lossless)"+ (toMap whole) (toMap restored)++-- Test 7 (HasTermAxis): termOf returns the LAST Note component for pair/triple.+testPolicyHasTermAxis :: IO ()+testPolicyHasTermAxis = do+ assertEqual "Policy HasTermAxis: pair termOf = snd" (7 :: Int) (Policy.termOf ("e", 7 :: Int))+ assertEqual "Policy HasTermAxis: triple termOf = 3rd" (9 :: Int)+ (Policy.termOf ("e1", "e2", 9 :: Int))++-- ================================================================+-- MarketModel equivalence tests (Phase 5, feat/market-scale-experiments)+--+-- The archived market model cannot be imported here (it declares+-- an orphan `instance StateTime Int` that would clash with the SICE harness's+-- `instance StateTime SimTerm`), so the trade simple/tuned stages and a small+-- BSP world are re-stated minimally (per the Phase 5 plan §2 commit 3 note).+-- We check the two properties the plan puts in CI:+-- (a) tradeStageSimple ≡ tradeStageTuned, EXACTLY, under MoneyDecimal;+-- (b) Sequential ≡ ParChunk (DET-2) for the whole 3-stage model, exactly.+-- (Perf ratios are out of CI; they live in run-market-experiments.sh.)+-- ================================================================++-- 4-axis base (AccountTitles, owner, counterparty, CountUnit), mirroring+-- MarketModel.MBase. It is exactly the SICE harness's SimHatBase2, so we reuse+-- that type (and its ExBaseClass / Element Int / BaseClass Int instances)+-- instead of re-declaring them.+type MktFirm = SimCompany -- = Int++-- ADT event tag mirroring MarketModel.MTag (typo'd tags become compile errors,+-- not silently-empty projections). 'MktPlank' is the explicit blank tag.+data MktTag = MktPlank | MktTrade | MktProduction | MktReport | MktClosing | MktCarryover+ deriving (Show, Eq, Ord, Enum, Bounded, Generic)+instance Hashable MktTag+-- needed so the spill / runLiteWithPolicy window-transparency test can serialize+-- a @Journal MktNote v b@ (derived structurally from Generic).+instance Binary.Binary MktTag+instance Note MktTag where+ plank = MktPlank++type MktNote = (MktTag, Int)+type MktBase = SimHatBase2 -- = HatBase (AccountTitles, Int, Int, CountUnit)+type MktLedgM = Journal MktNote MoneyDecimal MktBase++data MktW v f = MktW+ { mkLedger :: HK f (Journal MktNote v MktBase)+ , mkNet :: HK f (TradeNetwork MktFirm)+ , mkCoef :: HK f (InputCoefficients MktFirm v)+ } deriving Generic++-- own-product classifier shared by the mirror.+mktOwnerOfProduct :: BasePart MktBase -> Maybe MktFirm+mktOwnerOfProduct bp = case bp of+ (Products, o, c, _) | o == c -> Just o+ _ -> Nothing++-- opening stock read from the (MktCarryover, t) note (indexed per-note),+-- mirroring MarketModel.openingMap (carryover-based O(term) inventory).++-- single-firm opening read (indexed per-note + per-base), mirroring+-- MarketModel.openingOf: balanceBy over firm j's own-product base only.+mktOpeningOf :: (HatVal v, Real v)+ => Int -> MktFirm -> Journal MktNote v MktBase -> v+mktOpeningOf t j ledger =+ EA.balanceBy [Not :< (Products, j, j, Amount)]+ [Hat :< (Products, j, j, Amount)]+ (EJ.toAlg (EJ.projWithNote [(MktCarryover, t)] ledger))++-- single-firm inventory-connected demand, mirroring MarketModel.demandOf.+mktDemandOf :: (HatVal v, Real v)+ => Double -> Int -> MktFirm -> Journal MktNote v MktBase -> Double+mktDemandOf target t j ledger =+ max 0 (target - realToFrac (mktOpeningOf t j ledger))++mktPurchase :: (HatVal v) => v -> MktFirm -> MktFirm -> EA.Alg v MktBase+mktPurchase amt i j =+ amt .@ Not :< (Products, j, j, Amount)+ .+ amt .@ Hat :< (Cash, j, j, Yen)+ .+ amt .@ Not :< (Purchases, j, j, Yen)+ .+ amt .@ Not :< (Cash, i, i, Yen)+ .+ amt .@ Not :< (Sales, i, i, Yen)+ .+ amt .@ Hat :< (Products, i, i, Amount)++mktOrderAmt :: (HatVal v, Real v)+ => InputCoefficients MktFirm v -> Double -> MktFirm -> MktFirm -> Double+mktOrderAmt coef d i j =+ realToFrac (maybe 0 id (coefficient coef i j)) * d++-- per-firm trade stage (stageFor over the firm list), mirroring+-- MarketModel.tradeStageSimple: buyer j folds its in-edges (suppliersOf).+mktTradeSimple :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase+mktTradeSimple fs target = stageOf MktTrade fs $ \w t _g j ->+ -- single-note stage: emit the bare Alg; the runner attaches (MktTrade, t).+ let net = mkNet w; coef = mkCoef w+ d = mktDemandOf target t j (mkLedger w)+ sup = suppliersOf net j+ one i = let amt = realToFrac (mktOrderAmt coef d i j)+ in if amt <= 0 then mempty else mktPurchase amt i j+ in EA.sigma sup one++mktTradeTuned :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase+mktTradeTuned fs target = stageOf MktTrade fs $ \w t _g j ->+ -- single-note stage: see 'mktTradeSimple'.+ let net = mkNet w; coef = mkCoef w+ d = mktDemandOf target t j (mkLedger w)+ sup = suppliersOf net j+ accum = L.foldl' step M.empty sup+ step acc i =+ let amt = realToFrac (mktOrderAmt coef d i j)+ in if amt <= 0 then acc+ else L.foldl' (\m (b, v) -> M.insertWith (+) b v m) acc+ [ (Not :< (Products, j, j, Amount), amt)+ , (Hat :< (Cash, j, j, Yen), amt)+ , (Not :< (Purchases, j, j, Yen), amt)+ , (Not :< (Cash, i, i, Yen), amt)+ , (Not :< (Sales, i, i, Yen), amt)+ , (Hat :< (Products, i, i, Amount), amt) ]+ in EA.sigmaFromMap accum (\b v -> v .@ b)++mktProduction :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase+mktProduction fs target = stageOf MktProduction fs $ \w t _g j ->+ -- single-note stage: emit the bare Alg; the runner attaches (MktProduction, t).+ let amt = realToFrac (mktDemandOf target t j (mkLedger w))+ in if amt <= 0 then mempty+ else (amt .@ Hat :< (Products, j, j, Amount))+ .+ (amt .@ Not :< (SalesCost, j, j, Yen))++mktReport :: (HatVal v) => Stage (MktW v) Int MktNote v MktBase+mktReport = stageOf MktReport [()] $ \w t _g () ->+ -- single-note aggregate stage: emit the bare Alg; runner attaches (MktReport, t).+ let flow = EJ.toAlg (EJ.projWithNote [(MktTrade, t), (MktProduction, t)] (mkLedger w))+ shortageK b = case b of+ Hat :< (Products, o, c, _) | o == c -> Just o+ _ -> Nothing+ in EA.postFromNetBy shortageK (\j v -> v .@ Not :< (Products, j, j, Amount)) flow++-- carryover stage (mirror): net this term's own-product stock and roll the+-- positive surplus into (MktCarryover, t+1). Mirrors MarketModel.carryoverStage.+mktCarryover :: (HatVal v, Real v) => Stage (MktW v) Int MktNote v MktBase+mktCarryover = stage (T.pack "closing") $ \w t ->+ let termAlg = EJ.toAlg (EJ.filterByAxis 1 (NoteAxisKey (t :: Int)) (mkLedger w))+ netMap = EA.balanceMapBy mktOwnerOfProduct termAlg+ perFirm (j, v) =+ if v <= 0 then mempty+ else ((v .@ Hat :< (Products, j, j, Amount)) .| (MktClosing, t))+ <> ((v .@ Not :< (Products, j, j, Amount)) .| (MktCarryover, t + 1))+ in mconcat [ perFirm kv | kv <- M.toList netMap ]++-- a fixed small (G, A) used by both equivalence tests.+mktBuild :: (HatVal v) => Int -> (TradeNetwork MktFirm, InputCoefficients MktFirm v)+mktBuild n =+ let (gG, gA) = split (mkStdGen 2025)+ fs = [1 .. n]+ net = erdosRenyi gG fs 0.3+ a = randomCoefficients gA defaultCoefOptions net+ in (net, a)++mktSpec :: (HatVal v, Real v)+ => Bool -> Int -> Int -> Par -> SimSpec (MktW v) Int MktNote v MktBase+mktSpec tuned n lastT par =+ let fs = [1 .. n] in+ (mkSimSpec (1, lastT) 2025 mkLedger+ [ (if tuned then mktTradeTuned else mktTradeSimple) fs 10+ , mktProduction fs 10+ , mktReport+ , mktCarryover ])+ { Lite.specParallel = par }++mktW0 :: (HatVal v) => Int -> MktW v InitT+mktW0 n = let (net, a) = mktBuild n+ in MktW { mkLedger = carry mempty, mkNet = carry net, mkCoef = carry a }++-- (a) simple ≡ tuned, exactly, under MoneyDecimal (N=30, T=5).+-- the redundant-algebra-correct "same result": net each note's Alg per base+-- ('bar' drops the canceled part and any zero-padding), keeping the Hat/Not+-- side. simple and tuned differ ONLY in seq redundancy (simple keeps the+-- per-edge posting sequence; tuned pre-sums per base), so they are equal exactly+-- after netting. (norm additivity already holds; this is the stronger per-base+-- exact check.)+nettedMktMap :: Journal MktNote MoneyDecimal MktBase+ -> HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)+nettedMktMap = toMap . EJ.map EA.bar++testMarketSimpleTunedEqual :: IO ()+testMarketSimpleTunedEqual = do+ let simpleL = runLite (mktSpec False 30 5 Sequential) (mktW0 30) mkLedger+ :: MktLedgM+ tunedL = runLite (mktSpec True 30 5 Sequential) (mktW0 30) mkLedger+ assertEqual "Market: tradeStageSimple == tradeStageTuned (MoneyDecimal, exact per-base net)"+ (nettedMktMap simpleL) (nettedMktMap tunedL)+ -- gross volume must also agree: bar-equality alone cannot detect an+ -- accidental early Hat/Not netting in the tuned path (bar is idempotent,+ -- but the pre-bar norm would shrink). norm pins the gross posting volume.+ assertEqual "Market: simple/tuned gross volume (norm) agrees (no early netting)"+ (norm simpleL) (norm tunedL)++-- (b) DET-2: Sequential ≡ ParChunk, exactly, under MoneyDecimal (simple path).+testMarketSeqParEqual :: IO ()+testMarketSeqParEqual = do+ let seqM = runLite (mktSpec False 30 5 Sequential) (mktW0 30) (toMap . mkLedger)+ :: HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)+ parM = runLite (mktSpec False 30 5 (ParChunk 8)) (mktW0 30) (toMap . mkLedger)+ assertEqual "Market DET-2: Sequential == ParChunk (MoneyDecimal, exact)"+ seqM parM++-- (c) sanity: the report's net shortage is strictly positive (Hawkins-Simon),+-- and the complete-network setting also runs (a participating-set sanity).+testMarketShortagePositive :: IO ()+testMarketShortagePositive = do+ let finalSh = runLite (mktSpec False 24 4 Sequential) (mktW0 24)+ (\final -> norm (EJ.projWithNote [(MktReport, 4)] (mkLedger final)))+ :: MoneyDecimal+ assertEqual "Market: final-term net shortage is strictly positive (Hawkins-Simon)"+ True (finalSh > 0)+ -- complete network on a tiny N just exercises the dense edge set end-to-end.+ let (gG, gA) = split (mkStdGen 2025)+ cnet = completeNetwork [1 .. 8 :: MktFirm]+ ccoef = randomCoefficients gA defaultCoefOptions cnet :: InputCoefficients MktFirm MoneyDecimal+ cw0 = MktW { mkLedger = carry mempty, mkNet = carry cnet, mkCoef = carry ccoef }+ cfs = [1 .. 8 :: MktFirm]+ cspec = (mkSimSpec (1, 3) 2025 mkLedger+ [ mktTradeSimple cfs 10, mktProduction cfs 10, mktReport, mktCarryover ])+ cNorm = runLite cspec cw0 (norm . mkLedger) :: MoneyDecimal+ _ = gG+ assertEqual "Market: complete-network run produces a positive ledger norm"+ True (cNorm > 0)++-- (d) WINDOW-TRANSPARENCY SENTINEL (Phase 5 fix, modification 3):+-- the carryover bookkeeping makes the model self-contained per term, so a+-- RetainRecent window must NOT change the observable result. Assert that+-- RetainRecent 2 (+ spill) and RetainAll produce the EXACT SAME final-term+-- report norm AND final carryover map (MoneyDecimal, so equality is exact).+-- This permanently guards against the bug this round fixed (a full-ledger+-- inventory sweep silently re-reading a window-truncated net: 9974.74 vs+-- 9993.56). N=40, T=8 so the window (2) is strictly smaller than the history.+testMarketWindowTransparent :: IO ()+testMarketWindowTransparent = withTestTemporaryFile $ \path -> do+ let n = 40; lastT = 8+ spec = mktSpec False n lastT Sequential+ w0 = mktW0 n+ -- project the two observables we pin: the final-term report norm and the+ -- final carryover map (the next-term opening, keyed by firm).+ project final =+ let lj = mkLedger final :: MktLedgM+ reportN = norm (EJ.projWithNote [(MktReport, lastT)] lj) :: MoneyDecimal+ carryM = EA.balanceMapBy mktOwnerOfProduct+ (EJ.toAlg (EJ.projWithNote [(MktCarryover, lastT + 1)] lj))+ :: M.Map MktFirm MoneyDecimal+ in (reportN, carryM)+ polAll = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainAll }+ polWin = Policy.defaultLedgerPolicy+ { Policy.retain = Policy.RetainRecent 2, Policy.spillTo = Just path }+ (allN, allM) <- runLiteWithPolicy polAll spec w0 project+ (winN, winM) <- runLiteWithPolicy polWin spec w0 project+ assertEqual "Market window-transparency: final report norm equal under RetainAll vs RetainRecent 2 + spill"+ allN winN+ assertEqual "Market window-transparency: final carryover map equal under RetainAll vs RetainRecent 2 + spill"+ allM winM++-- (e) stageOf AUTO-NOTE SENTINEL: a 'stageOf' stage and the equivalent manual+-- 'stageFor' that writes @.| (tag, t)@ itself must produce the EXACT SAME ledger+-- (MoneyDecimal, so equality is exact). This pins the semantics of the runner's+-- single auto-attachment of @(stTag, t)@: moving the note from the stage body+-- into 'runStage' changes nothing observable (incl. the zero-drop at the sigma+-- commit). The manual stages below are byte-for-byte the bodies of the migrated+-- mirror stages, but tagged explicitly with the OLD @if isZero then mempty@ form.+mktTradeSimpleManual :: (HatVal v, Real v)+ => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase+mktTradeSimpleManual fs target = stageFor (T.pack "trade") fs $ \w t _g j ->+ let net = mkNet w; coef = mkCoef w+ d = mktDemandOf target t j (mkLedger w)+ sup = suppliersOf net j+ one i = let amt = realToFrac (mktOrderAmt coef d i j)+ in if amt <= 0 then mempty else mktPurchase amt i j+ alg = EA.sigma sup one+ in if EA.isZero alg then mempty else alg .| (MktTrade, t)++mktProductionManual :: (HatVal v, Real v)+ => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase+mktProductionManual fs target = stageFor (T.pack "production") fs $ \w t _g j ->+ let amt = realToFrac (mktDemandOf target t j (mkLedger w))+ in if amt <= 0 then mempty+ else ((amt .@ Hat :< (Products, j, j, Amount))+ .+ (amt .@ Not :< (SalesCost, j, j, Yen)))+ .| (MktProduction, t)++mktReportManual :: (HatVal v) => Stage (MktW v) Int MktNote v MktBase+mktReportManual = stage (T.pack "report") $ \w t ->+ let flow = EJ.toAlg (EJ.projWithNote [(MktTrade, t), (MktProduction, t)] (mkLedger w))+ shortageK b = case b of+ Hat :< (Products, o, c, _) | o == c -> Just o+ _ -> Nothing+ sh = EA.postFromNetBy shortageK (\j v -> v .@ Not :< (Products, j, j, Amount)) flow+ in if EA.isZero sh then mempty else sh .| (MktReport, t)++-- the same 4-stage spec as 'mktSpec' but with the three single-note stages+-- expressed via manual stageFor + explicit @.| (tag, t)@ (carryover unchanged).+mktSpecManual :: (HatVal v, Real v)+ => Int -> Int -> Par -> SimSpec (MktW v) Int MktNote v MktBase+mktSpecManual n lastT par =+ let fs = [1 .. n] in+ (mkSimSpec (1, lastT) 2025 mkLedger+ [ mktTradeSimpleManual fs 10+ , mktProductionManual fs 10+ , mktReportManual+ , mktCarryover ])+ { Lite.specParallel = par }++testMarketStageOfAutoNote :: IO ()+testMarketStageOfAutoNote = do+ let stageOfL = runLite (mktSpec False 30 5 Sequential) (mktW0 30) (toMap . mkLedger)+ :: HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)+ manualL = runLite (mktSpecManual 30 5 Sequential) (mktW0 30) (toMap . mkLedger)+ assertEqual "Market stageOf auto-note: stageOf ledger == manual stageFor + .| (tag, t) (MoneyDecimal, exact)"+ stageOfL manualL+++-- | Pin the two toy-model norms at 906 and 3300.+testLiteToyModels :: IO ()+testLiteToyModels = do+ let cases = concat+ [ let+ w0 = MiniW { mwLedger = carry mempty+ , mwPrice = carry 10+ , mwTax = carry 0.1 }+ n = runLite miniSpec w0 (realToFrac . norm . mwLedger)+ in+ [ NearComparison "Lite: boilerplate mini-model runs (norm)" 906.0 n+ ]+ , let+ w0 = GateW { gwLedger = carry mempty, gwPrice = carry 10 }+ n = runLite gateSpec w0 (realToFrac . norm . gwLedger)+ in+ [ NearComparison "Lite: gate toy-model equivalence (norm 3300)" 3300.0 n+ ]+ ]+ forM_ cases runTestComparison+++-- | Check Field rules and one boundary update per term across two stages.+testLiteFieldBoundaries :: IO ()+testLiteFieldBoundaries = do+ let cases = concat+ [ [ NearComparison "Lite Field: Carry keeps the value" 60.0 (runRule (carry 10))+ , NearComparison+ "Lite Field: ResetEach restores each term"+ 30.0 (runRule (resetEach 5))+ , NearComparison "Lite Field: UpdateEach applies the step each boundary"+ 140.0 (runRule (updateEach 10 (* 2)))+ ]+ , [ NearComparison "Lite: term boundary fires once per term (2 stages)" 280.0+ (let spec2 = mkSimSpec (1, 3) 0 rwLedger [ruleStage, ruleStage]+ w0 = RuleW { rwLedger = carry mempty, rwPrice = updateEach 10 (* 2) }+ in realToFrac (runLite spec2 w0 (norm . rwLedger)))+ ]+ ]+ forM_ cases runTestComparison+++-- | Check observer visits, committed postings, and pre-update Field values.+testLiteObserverBoundaries :: IO ()+testLiteObserverBoundaries = do+ let cases :: [(String, IO ())]+ cases =+ [ ("testLiteObserverTerms",+ forM_ [(1, 5), (3, 6), (4, 4), (4, 3)] $ \range@(lo, hi) -> do+ let spec = polSpec { Lite.specTerms = range }+ terms = runLiteFold (\t _ acc -> t : acc) [] spec polW0+ (\acc _ -> reverse acc)+ assertEqual "Lite fold observer: term order and count" [lo .. hi] terms+ seen <- newIORef []+ _ <- runLiteWithPolicyObs (\t _ -> modifyIORef' seen (t :))+ Policy.defaultLedgerPolicy spec polW0 (toMap . pwLedger)+ observed <- reverse <$> readIORef seen+ assertEqual "Lite IO observer: term order and count" [lo .. hi] observed+ )+ , ("testLiteObserverBoundary", do+ let w0 = MiniW { mwLedger = carry mempty+ , mwPrice = updateEach 10 (* 2)+ , mwTax = carry 0.1 }+ snapshots = runLiteFold (\t w acc -> (t, w) : acc) [] miniSpec w0+ (\acc _ -> reverse acc)+ check :: (Int, MiniW SnapT) -> IO ()+ check (t, w) = do+ let ledger = toMap (mwLedger w)+ price = 10 * (2 ^ (t - 1)) :: MoneyDouble+ keys = L.sort [(tag, u) | u <- [1 .. t], tag <- ["buy", "tax"]]+ assertEqual "Lite observer: all committed notes, no future notes"+ keys (L.sort (HM.keys ledger))+ assertEqual "Lite observer: price before Field update" price (mwPrice w)+ assertEqual "Lite observer: carried tax" 0.1 (mwTax w)+ assertNear "Lite observer: current buy stage committed"+ (realToFrac (30 * price))+ (maybe 0 (realToFrac . norm) (HM.lookup ("buy", t) ledger))+ assertNear "Lite observer: current final stage committed"+ (realToFrac (2 * price * realToFrac (mwTax w)))+ (maybe 0 (realToFrac . norm) (HM.lookup ("tax", t) ledger))+ forM_ snapshots check+ seen <- newIORef []+ finalPrice <- runLiteWithPolicyObs+ (\t w -> modifyIORef' seen ((t, w) :))+ Policy.defaultLedgerPolicy miniSpec w0 mwPrice+ ioSnapshots <- reverse <$> readIORef seen+ assertEqual "Lite IO observer: all boundaries saved" [1, 2, 3] (L.map fst ioSnapshots)+ forM_ ioSnapshots check+ assertEqual "Lite continuation: final Field update has fired" 80 finalPrice+ )+ ]+ forM_ cases $ \(_, check) -> check++-- | Check observer equivalence and streaming with retention windows zero and two.+testLiteObserverPolicyCases :: IO ()+testLiteObserverPolicyCases = do+ let cases :: [(String, IO ())]+ cases =+ [ ("testLiteObserverEquivalence", do+ let w0 = MiniW { mwLedger = carry mempty+ , mwPrice = updateEach 10 (* 2)+ , mwTax = carry 0.1 }+ project w = (toMap (mwLedger w), mwPrice w, mwTax w)+ legacy = runLite miniSpec w0 id+ folded = runLiteFold (\_ _ a -> a) () miniSpec w0 (\_ w -> w)+ assertEqual "Lite fold: unchanged final snapshot" (project legacy) (project folded)+ full <- runLiteWithPolicy Policy.defaultLedgerPolicy miniSpec w0 project+ observed <- runLiteWithPolicyObs (\_ _ -> pure ())+ Policy.defaultLedgerPolicy miniSpec w0 project+ assertEqual "Lite IO observer: unchanged full world" full observed+ forM_ [Policy.RetainAll, Policy.RetainRecent 2] $ \retention ->+ withTestTemporaryFile $ \oldPath ->+ withTestTemporaryFile $ \newPath -> do+ let policy path = Policy.defaultLedgerPolicy+ { Policy.retain = retention+ , Policy.spillTo = Just path }+ old <- runLiteWithPolicy (policy oldPath) polSpec polW0 pwLedger+ new <- runLiteWithPolicyObs (\_ _ -> pure ())+ (policy newPath) polSpec polW0 pwLedger+ assertEqual "Lite IO observer: unchanged final policy ledger"+ (toMap old) (toMap new)+ oldRestored <- Policy.restoreLedger oldPath old :: IO LedgerM+ newRestored <- Policy.restoreLedger newPath new :: IO LedgerM+ assertEqual "Lite IO observer: unchanged spill contents"+ (toMap oldRestored) (toMap newRestored)+ )+ , ("testLiteObserverStreamingSpill", do+ full <- runLiteWithPolicy Policy.defaultLedgerPolicy polSpec polW0 pwLedger+ forM_ [0, 2] $ \window -> withTestTemporaryFile $ \path -> do+ let policy = Policy.defaultLedgerPolicy+ { Policy.retain = Policy.RetainRecent window+ , Policy.spillTo = Just path }+ seen <- newIORef []+ resident <- runLiteWithPolicyObs+ (\t w -> modifyIORef' seen ((t, pwLedger w) :))+ policy polSpec polW0 pwLedger+ snapshots <- reverse <$> readIORef seen+ let entries = L.nub (concatMap (HM.toList . toMap . snd) snapshots)+ expected = HM.toList (toMap full)+ streamed = sigma snapshots $ \(t, ledger) ->+ EJ.filterWithNote (\(_, u) _ -> u == t) ledger+ assertEqual "Lite streaming: observer term order" [1 .. 5] (L.map fst snapshots)+ assertEqual "Lite streaming: snapshot entry set equals FullAudit"+ True (length entries == length expected && all (`elem` entries) expected)+ assertEqual "Lite streaming: current-term output equals FullAudit exactly"+ (toMap full) (toMap streamed)+ assertEqual "Lite streaming: final resident window"+ [6 - window .. 5] (L.sort (L.map snd (HM.keys (toMap resident))))+ restored <- Policy.restoreLedger path resident :: IO LedgerM+ assertEqual "Lite streaming: spill plus resident ledger is lossless"+ (toMap full) (toMap restored)+ )+ ]+ forM_ cases $ \(_, check) -> check++-- | Preserve UTF-8 payload bytes and the text spill chunk delimiters.+testDefaultSpillWriter :: IO ()+testDefaultSpillWriter = withTestTemporaryFile $ \path -> do+ let payload = T.pack "現金\n売上"+ expected = T.pack "# chunk 1 3\n" <> payload <> T.pack "\n# end-chunk\n"+ withFile path WriteMode $ \handle -> do+ hSetEncoding handle utf8+ ES.defaultSpillWriter handle (1 :: Int, 3) payload+ actual <- BL.readFile path+ assertEqual "defaultSpillWriter preserves UTF-8 text format"+ (BL.fromStrict (TE.encodeUtf8 expected)) actual++-- | Check decoded ranges and both restore paths on the original spill fixtures.+testSpillCheckedCases :: IO ()+testSpillCheckedCases = do+ let wellFormed = [((1, 2), spillCheckedChunk1), ((3, 3), spillCheckedChunk2)]+ rangeCases =+ [ ("checked spill reader rejects stale append"+ , wellFormed ++ [((1, 2), spillCheckedChunk1)]+ , Left (ES.SpillRangeError ES.ChunkOutOfOrder (3, 3) (1, 2)))+ , ("checked spill reader rejects overlap"+ , [((1, 3), spillCheckedChunk1), ((2, 4), spillCheckedChunk2)]+ , Left (ES.SpillRangeError ES.ChunkOverlap (1, 3) (2, 4)))+ , ("checked spill reader rejects gap"+ , [((1, 2), spillCheckedChunk1), ((4, 4), spillCheckedChunk2)]+ , Left (ES.SpillRangeError ES.ChunkGap (1, 2) (4, 4)))+ , ("checked spill reader rejects empty range"+ , [((3, 1), spillCheckedChunk1)]+ , Left (ES.SpillEmptyRange (3, 1)))+ , ("checked spill reader accepts empty file", [], Right [])+ ]+ cases :: [FilePath -> IO ()]+ cases =+ [ \path -> do+ writeSpillTestChunks path wellFormed+ readResult <- readChunks path+ case readResult of+ Left err -> assertEqual "checked spill reader accepts well-formed chunks"+ "Right with two chunks" (ES.renderSpillReadError err)+ Right decoded -> assertEqual "checked spill reader returns both chunks"+ 2 (L.length decoded)+ restored <- restoreJournalFromBinarySpillChecked path snd spillCheckedCurrent+ case restored of+ Left err -> assertEqual "checked spill restore accepts well-formed chunks"+ "Right restored ledger" (ES.renderSpillReadError err)+ Right actual -> assertEqual "checked spill restore merges spill + tail remainder"+ (EJ.toMap spillCheckedExpected) (EJ.toMap actual)+ actual <- restoreJournalFromBinarySpill path snd spillCheckedCurrent+ assertEqual "Spill.restoreJournalFromBinarySpill merges spill + tail remainder"+ (EJ.toMap spillCheckedExpected) (EJ.toMap actual)+ , \path -> do+ let encodedChunk2 = Binary.encode+ ((3 :: Int, 3 :: Int), spillCheckedChunk2)+ truncatedChunk2 = BL.take (BL.length encodedChunk2 `div` 2) encodedChunk2+ withFile path WriteMode $ \handle -> do+ ES.defaultBinarySpillWriter handle (1 :: Int, 2 :: Int) spillCheckedChunk1+ BL.hPut handle truncatedChunk2+ result <- readChunks path+ case result of+ Left (ES.SpillDecodeFailure offset chunks _) -> do+ assertEqual "truncated spill failure follows first chunk" True (offset > 0)+ assertEqual "truncated spill reports decoded chunk count" 1 chunks+ other -> assertEqual "truncated spill is a decode failure"+ "SpillDecodeFailure" (show other)+ caught <- try+ (restoreJournalFromBinarySpill path snd (mempty :: SpillRestoreJournal))+ :: IO (Either ErrorCall SpillRestoreJournal)+ case caught of+ Left _ -> putStrLn "[PASS] unchecked spill restore raises ErrorCall"+ Right _ -> assertEqual "unchecked spill restore raises ErrorCall" True False+ ] ++ L.map checkRanges rangeCases+ checkRanges (label, chunks, expected) path = do+ writeSpillTestChunks path chunks+ result <- readChunks path+ assertEqual label expected (fmap (fmap fst) result)+ forM_ cases withTestTemporaryFile+ where+ readChunks :: FilePath+ -> IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])+ readChunks = ES.readBinarySpillFileChecked++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testDefaultSpillWriter+ testSpillCheckedCases+ testLiteObserverPolicyCases+ testLiteObserverBoundaries+ testLiteFieldBoundaries+ testLiteToyModels+ testSpillDecisionSingleSource+ testSimulateEx1Default+ testLiteDet2+ testLiteDet2Double+ testLiteBspInvisibility+ testPolicyEquivalence+ testPolicyWindowRoundTrip+ testPolicyCompressClosed+ testPolicyDeleteOnly+ testPolicyDeterminism+ testPolicyClassicBridge+ testPolicyHasTermAxis+ testMarketSimpleTunedEqual+ testMarketSeqParEqual+ testMarketShortagePositive+ testMarketWindowTransparent+ testMarketStageOfAutoNote
+ test/Simulation/NetworkSpec.hs view
@@ -0,0 +1,306 @@+-- | Trade networks, industrial topology, and flow identities.+-- Tests exercise the Simulation layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Simulation.NetworkSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.Journal as EJ+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Simulation.Network (TradeNetwork+ , InputCoefficients+ , NetworkError(..)+ , tradeNetwork+ , inputCoefficients+ , nodes+ , edges+ , suppliersOf+ , buyersOf+ , edgeCount+ , coefficient+ , inputsOf+ , completeNetwork+ , kRegular+ , erdosRenyi+ , scaleFree+ , IndustrialEconomy(..)+ , IndustrialOptions(..)+ , defaultIndustrialOptions+ , industrialNetwork+ , industrialNetworkWith+ , firms+ , industrialEdges+ , defaultCoefOptions+ , randomCoefficients+ , networkFromTable+ , coefficientsFromTable+ , fromCoefficientMatrix+ )+import ExchangeAlgebra.Simulation.Network.Flows (TaxRate(..)+ , taxOf+ , IndustrialFlows(..)+ , FlowOptions(..)+ , industrialFlows+ , industrialFlowsWith+ )+import ExchangeAlgebra.Simulation.Network.Csv (parseEdgeCsv, parseCoefCsv)+import ExchangeAlgebra.Simulation.Network.Journal (sigmaEdges)+import qualified Data.Map.Strict as M+import qualified Data.List as L+import qualified Data.Set as Set+import qualified Data.Text as T+import Control.Monad (forM_, when)+import System.Random (mkStdGen)+import Control.Exception (try, evaluate, SomeException)+import Control.DeepSeq (force)+import Support (TestComparison(..), assertEqual, runTestComparison)++-- ================================================================+-- Simulate.Network tests (Phase 3, feat/trade-network)+--+-- Property + unit tests for the TradeNetwork / InputCoefficients separation,+-- the deterministic generators, the smart-constructor invariants, and the+-- edge-summation sigmaEdges. All read-outs are Ord-ascending (no hash order).+-- ================================================================++type NetJD = Journal (Int, Int) MoneyDecimal (HatBase CountUnit)++-- a tiny per-edge journal builder used by the sigmaEdges equivalence test+edgeJ :: Int -> Int -> NetJD+edgeJ i j = ((fromIntegral (i + 2 * j) :: MoneyDecimal) .@ Not :< Amount) .| (i, j)++-- Test 1: completeNetwork makes sigmaEdges coincide with the all-pairs sum.+-- "The notation is unchanged; only the set Σ runs over changes."+testNetCompleteEquiv :: IO ()+testNetCompleteEquiv = do+ let ks = [1 .. 6 :: Int]+ viaEdges = sigmaEdges (completeNetwork ks) edgeJ :: NetJD+ viaPairs = EJ.sigma2When ks ks (/=) edgeJ :: NetJD+ assertEqual "Network: sigmaEdges complete == all-pairs sigma2When (exact)"+ (toMap viaPairs) (toMap viaEdges)+++-- Test 4: Hawkins-Simon — every buyer's column sum is strictly below 1.+testNetHawkinsSimon :: IO ()+testNetHawkinsSimon = do+ let g = completeNetwork [1 .. 12 :: Int]+ a = randomCoefficients (mkStdGen 11) defaultCoefOptions g :: InputCoefficients Int Double+ ok = all (\j -> sum (Prelude.map snd (inputsOf a j)) < 1.0) (nodes g)+ assertEqual "Network: randomCoefficients (hawkinsSimon) all column sums < 1" True ok+++-- Test 7: CSV round-trip — parse . render == id (render is a test helper).+renderEdgeCsv :: [(T.Text, T.Text)] -> T.Text+renderEdgeCsv rows = T.unlines (T.pack "from,to" : [ a <> T.pack "," <> b | (a, b) <- rows ])++renderCoefCsv :: [(T.Text, T.Text, Double)] -> T.Text+renderCoefCsv rows =+ T.unlines (T.pack "from,to,coef" :+ [ a <> T.pack "," <> b <> T.pack "," <> T.pack (show c) | (a, b, c) <- rows ])++testNetCsvRoundTrip :: IO ()+testNetCsvRoundTrip = do+ let eRows = [(T.pack "a", T.pack "b"), (T.pack "b", T.pack "c"), (T.pack "a", T.pack "c")]+ assertEqual "Network: edge CSV parse . render == id"+ (Right eRows) (parseEdgeCsv (renderEdgeCsv eRows))+ let cRows = [(T.pack "a", T.pack "b", 0.25), (T.pack "b", T.pack "c", 0.5)]+ assertEqual "Network: coef CSV parse . render == id"+ (Right cRows) (parseCoefCsv (renderCoefCsv cRows))+ -- ingestion helpers agree with the network/coefficient invariants+ let Right (g, a) = coefficientsFromTable [(1,3,0.2),(2,3,0.5)]+ :: Either NetworkError (TradeNetwork Int, InputCoefficients Int Double)+ assertEqual "Network: coefficientsFromTable edges" [(1,3),(2,3)] (edges g)+ assertEqual "Network: coefficientsFromTable inputsOf" [(1,0.2),(2,0.5)] (inputsOf a 3)+ -- fromCoefficientMatrix drops zero cells from the support+ let m i j = if i < j then fromIntegral (i + j) else 0 :: Double+ (gm, am) = fromCoefficientMatrix [1,2,3 :: Int] m+ assertEqual "Network: fromCoefficientMatrix support drops zeros"+ [(1,2),(1,3),(2,3)] (edges gm)+ assertEqual "Network: fromCoefficientMatrix coefficient" (Just 4.0) (coefficient am 1 3)+ -- networkFromTable derives nodes from rows+ let Right gt = networkFromTable [(1,2),(2,3)] :: Either NetworkError (TradeNetwork Int)+ assertEqual "Network: networkFromTable derives node set" [1,2,3] (nodes gt)+++-- Test 12: market-scale construction smoke. There is deliberately no timing+-- assertion; forcing the full 1.28M-edge economy catches accidental all-pairs+-- construction and latent exceptions while remaining machine-independent.+testIndustrialNetworkLarge :: IO ()+testIndustrialNetworkLarge = do+ economy <- evaluate (force (industrialNetwork 2025 64000 5 20))+ assertEqual "Industrial network: N=64000 smoke exact |E|=mN"+ (64000 * 20) (edgeCount (ieNetwork economy))++-- Test 13: exact one-pass flow identities, divisibility, and tax cancellation.+testIndustrialFlowsIdentities :: IO ()+testIndustrialFlowsIdentities = do+ let rate = TaxRate 1 10+ den = taxDenominator rate+ economy = industrialNetwork 2025 300 5 12+ flows = industrialFlows rate economy+ net = ieNetwork economy+ js = firms economy+ z i j = M.findWithDefault 0 (i,j) (flowTrade flows)+ x j = flowOutput flows M.! j+ input j = flowInput flows M.! j+ va j = flowValueAdded flows M.! j+ f j = flowFinalDemand flows M.! j+ incoming j = sum [ z i j | i <- suppliersOf net j ]+ outgoing j = sum [ z j m | m <- buyersOf net j ]+ allAmounts = M.elems (flowTrade flows)+ ++ M.elems (flowOutput flows)+ ++ M.elems (flowInput flows)+ ++ M.elems (flowValueAdded flows)+ ++ M.elems (flowFinalDemand flows)+ taxReceivedTrade = sum+ [ taxOf rate (z i j) | i <- js, j <- buyersOf net i ]+ taxPaidTrade = sum+ [ taxOf rate (z i j) | j <- js, i <- suppliersOf net j ]+ finalTax = sum [ taxOf rate (f j) | j <- js ]+ netTax = taxReceivedTrade + finalTax - taxPaidTrade+ expectedTax = taxNumerator rate * sum (Prelude.map f js) `div` den+ assertEqual "Industrial flows: all final demand positive" True (all ((> 0) . f) js)+ assertEqual "Industrial flows: all value added non-negative" True (all ((>= 0) . va) js)+ assertEqual "Industrial flows: output = orders + final demand"+ True (all (\j -> x j == outgoing j + f j) js)+ assertEqual "Industrial flows: output = input + value added"+ True (all (\j -> x j == incoming j + va j && input j == incoming j) js)+ assertEqual "Industrial flows: every amount divisible by tax denominator"+ True (all (\amount -> amount `mod` den == 0) allAmounts)+ assertEqual "Industrial flows: sum value added = sum final demand"+ (sum (Prelude.map f js)) (sum (Prelude.map va js))+ assertEqual "Industrial flows: trade output tax equals trade input tax"+ taxReceivedTrade taxPaidTrade+ assertEqual "Industrial flows: trade tax cancels and net tax equals final-demand tax"+ expectedTax netTax++-- Test 14: zero allocations are retained per edge, and a hand-built economy+-- outside the ordered DAG is rejected before backward substitution.+testIndustrialFlowEdgeCases :: IO ()+testIndustrialFlowEdgeCases = do+ let rate = TaxRate 1 10+ economy = industrialNetwork 11 50 3 5+ zeroFlows = industrialFlowsWith (FlowOptions 10 0.5) rate economy+ assertEqual "Industrial flows: sub-denominator inputs permit z_ij=0"+ True (not (M.null (flowTrade zeroFlows)) && any (== 0) (M.elems (flowTrade zeroFlows)))+ let Right badNetwork = tradeNetwork [1,2] [(2,1)]+ :: Either NetworkError (TradeNetwork Int)+ badEconomy = IndustrialEconomy+ { ieNetwork = badNetwork+ , ieSector = M.fromList [(1,0),(2,0)]+ , ieSize = M.fromList [(1,1),(2,1)] }+ rejected <- try (evaluate (force (industrialFlows rate badEconomy)))+ :: IO (Either SomeException (IndustrialFlows Int))+ assertEqual "Industrial flows: unordered hand-built economy rejected"+ True (case rejected of Left _ -> True; Right _ -> False)++-- | Check constructor rejection, generator structure, and adjacency reconstruction.+-- Invariant: the fixed three-node, one-edge network passes its smart constructor.+testNetworkInvariants :: IO ()+testNetworkInvariants = do+ let cases = concat+ [ let+ -- Invariant: this fixed network fixture must pass the smart constructor.+ g :: TradeNetwork Int+ g = case tradeNetwork [1,2,3] [(1,3)] of+ Right graph -> graph+ Left err -> error ("Invariant: network fixture rejected: " ++ show err)+ in+ [ EqualComparison "Network: self-loop rejected"+ (Left SelfLoop) (tradeNetwork [1,2] [(1,1)] :: Either NetworkError (TradeNetwork Int))+ , EqualComparison "Network: duplicate edge rejected"+ (Left DuplicateEdge) (tradeNetwork [1,2] [(1,2),(1,2)] :: Either NetworkError (TradeNetwork Int))+ , EqualComparison "Network: coefficient outside network rejected"+ (Left CoefOutsideNetwork)+ (inputCoefficients g [(2,3,0.5)] :: Either NetworkError (InputCoefficients Int Double))+ , EqualComparison "Network: negative coefficient rejected"+ (Left NegativeCoefficient)+ (inputCoefficients g [(1,3,-0.5)] :: Either NetworkError (InputCoefficients Int Double))+ , EqualComparison "Network: duplicate coefficient rejected"+ (Left DuplicateCoefficient)+ (inputCoefficients g [(1,3,0.2),(1,3,0.3)] :: Either NetworkError (InputCoefficients Int Double))+ ]+ , let+ ks = [1 .. 8 :: Int]+ kr = kRegular (mkStdGen 3) ks 3 :: TradeNetwork Int+ n = length ks; m = 2+ expected = (m * (m + 1) `div` 2) + (n - m - 1) * m+ in+ [ EqualComparison "Network: kRegular in-degree = min k (N-1)"+ (replicate (length ks) 3)+ (Prelude.map (length . suppliersOf kr) (nodes kr))+ , EqualComparison "Network: erdosRenyi p=1 == completeNetwork edges"+ (edges (completeNetwork ks))+ (edges (erdosRenyi (mkStdGen 0) ks 1.0 :: TradeNetwork Int))+ , EqualComparison "Network: erdosRenyi p=0 has no edges"+ 0 (edgeCount (erdosRenyi (mkStdGen 0) ks 0.0 :: TradeNetwork Int))+ , EqualComparison "Network: scaleFree edge count matches preferential-attachment formula"+ expected (edgeCount (scaleFree (mkStdGen 9) ks m :: TradeNetwork Int))+ ]+ , let+ ks = [1 .. 25 :: Int]+ g = erdosRenyi (mkStdGen 77) ks 0.25 :: TradeNetwork Int+ es = edges g+ fwd = all (\(i,j) -> i `elem` suppliersOf g j && j `elem` buyersOf g i) es+ -- and the reverse: every (i,j) reconstructed from suppliersOf equals edges+ viaSuppliers = L.sort [ (i, j) | j <- nodes g, i <- suppliersOf g j ]+ viaBuyers = L.sort [ (i, j) | i <- nodes g, j <- buyersOf g i ]+ in+ [ EqualComparison "Network: edges <=> suppliersOf (forward)" True fwd+ , EqualComparison+ "Network: edges == reconstruction from suppliersOf"+ (L.sort es) viaSuppliers+ , EqualComparison+ "Network: edges == reconstruction from buyersOf"+ (L.sort es) viaBuyers+ ]+ ]+ forM_ cases runTestComparison++-- | Pin edge counts, finite sizes, and DAG structure on the original economy inputs.+testIndustrialNetworkCases :: IO ()+testIndustrialNetworkCases = do+ let cases =+ [ (defaultIndustrialOptions, 2025, 200, 5, 20, Just 4000, False, False, False)+ , (defaultIndustrialOptions, 2025, 1000, 4, 10, Just 10000, False, False, False)+ , (defaultIndustrialOptions, 1, 10, 1, 20, Just 45, False, False, False)+ , (defaultIndustrialOptions { ioExponent = 1.001 }+ , 1, 1000, 3, 5, Just 5000, True, False, False)+ , (defaultIndustrialOptions, 19, 500, 5, 12, Nothing, False, True, False)+ , (defaultIndustrialOptions, 3, 200, 1, 20, Just 4000, False, False, True)+ ]+ forM_ cases $ \(options, seed, n, k, m, expectedCount, finite, dag, increasing) -> do+ let economy = industrialNetworkWith options seed n k m+ networkEdges = industrialEdges economy+ label = "Industrial network " ++ show (seed, n, k, m)+ valid (i, j) = case (M.lookup i (ieSector economy), M.lookup j (ieSector economy)) of+ (Just si, Just sj) -> i /= j && (si < sj || (si == sj && i < j))+ _ -> False+ case expectedCount of+ Just expected -> assertEqual (label ++ ": edge count")+ expected (edgeCount (ieNetwork economy))+ Nothing -> pure ()+ when finite $ assertEqual "Industrial network: gamma near 1 keeps every size finite"+ True (all (\w -> w > 0 && not (isNaN w) && not (isInfinite w))+ (M.elems (ieSize economy)))+ when dag $ do+ assertEqual "Industrial network: no duplicate edges"+ (length networkEdges) (Set.size (Set.fromList networkEdges))+ assertEqual "Industrial network: sector order and intra-sector id DAG"+ True (all valid networkEdges)+ when increasing $ do+ assertEqual "Industrial network: K=1 exact |E|=mN" (m * n) (length networkEdges)+ assertEqual "Industrial network: K=1 edges are increasing ids"+ True (all (uncurry (<)) networkEdges)++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testIndustrialNetworkCases+ testNetworkInvariants+ testNetCompleteEquiv+ testNetHawkinsSimon+ testNetCsvRoundTrip+ testIndustrialNetworkLarge+ testIndustrialFlowsIdentities+ testIndustrialFlowEdgeCases
+ test/Simulation/OptimizeSpec.hs view
@@ -0,0 +1,130 @@+-- | Annealing and genetic algorithm objectives and deterministic runs.+-- Tests exercise the Simulation layer using the shared Support fixtures.+-- Start with 'runTests' for the suite's execution order.+module Simulation.OptimizeSpec (runTests) where++import qualified ExchangeAlgebra.Simulation.Optimize as O+import qualified ExchangeAlgebra.Simulation.Optimize.Annealing as OA+import qualified ExchangeAlgebra.Simulation.Optimize.GA as OG+import qualified Data.Vector.Unboxed as UV+import Control.Monad.ST (runST)+import Data.STRef (newSTRef, readSTRef, modifySTRef')+import System.Exit (exitFailure)+import System.Random (randomR)+import Control.Exception (try, SomeException)+import Support (assertEqual, assertNear)++-- ================================================================+-- ExchangeAlgebra.Optimize: pluggable solvers (Annealing / GA)+-- ================================================================++-- | Shared annealing configuration: minimize over 'Double' with a+-- uniform-step neighbor, geometric cooling and Metropolis acceptance.+optAnnealCfg :: OA.AnnealingConfig Double+optAnnealCfg = OA.AnnealingConfig+ { OA.acDirection = O.Minimize+ , OA.acSteps = 2000+ , OA.acSchedule = OA.geometricCooling 1.0 0.995+ , OA.acNeighbor = \g x -> let (d, g') = randomR (-0.5, 0.5) g in (x + d, g')+ , OA.acAccept = OA.metropolis+ , OA.acSeed = 20260717+ }++optSphere :: UV.Vector Double -> Double+optSphere v = UV.sum (UV.map (\x -> (x - 1) ** 2) v)++-- | Annealing locates the minimum of a 1-D quadratic (seed-deterministic),+-- and the reported score is the returned candidate's objective value+-- (score contract: no re-evaluation, user orientation).+testOptimizeAnnealingQuadratic :: IO ()+testOptimizeAnnealingQuadratic = do+ (best, score) <- O.optimize OA.Annealing optAnnealCfg+ (\x -> return ((x - 3) ** 2)) 0+ assertEqual "Optimize.Annealing: quadratic minimum located (|x-3| < 0.2)"+ True (abs (best - 3) < 0.2)+ assertNear "Optimize.Annealing: score equals returned candidate's value"+ ((best - 3) ** 2) score++-- | GA approaches the sphere minimum, keeps the dimension, and respects+-- per-gene bounds.+testOptimizeGASphere :: IO ()+testOptimizeGASphere = do+ let cfg = OG.defaultGAConfig { OG.gaSeed = 20260717, OG.gaGenerations = 80 }+ (best, score) <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)+ assertEqual "Optimize.GA: chromosome dimension preserved" 3 (UV.length best)+ assertEqual "Optimize.GA: sphere minimum approached (score < 0.05)"+ True (score < 0.05)+ let bs = UV.replicate 3 (-0.5, 0.5)+ bCfg = OG.defaultGAConfig { OG.gaSeed = 3+ , OG.gaGenerations = 40+ , OG.gaBounds = Just bs }+ (bBest, _) <- O.optimize OG.GA bCfg (return . optSphere) (UV.replicate 3 0)+ assertEqual "Optimize.GA: bounds respected by every gene"+ True (UV.all (\x -> x >= -0.5 && x <= 0.5) bBest)++-- | Maximize reports the score in the user's orientation.+testOptimizeGAMaximize :: IO ()+testOptimizeGAMaximize = do+ let cfg = OG.defaultGAConfig { OG.gaDirection = O.Maximize+ , OG.gaSeed = 7+ , OG.gaGenerations = 80 }+ (_, score) <- O.optimize OG.GA cfg (return . negate . optSphere)+ (UV.replicate 3 0)+ assertEqual "Optimize.GA: Maximize reports user-oriented score (> -0.05)"+ True (score > -0.05)++-- | Acceptance-criteria demo: the objective runs in @ST s@ (mutable+-- evaluation counter), and the solver evaluates exactly once per step+-- plus once for the initial candidate.+testOptimizeSTObjective :: IO ()+testOptimizeSTObjective = do+ let cfg = optAnnealCfg { OA.acSteps = 100 }+ (best, score, evals) = runST $ do+ ref <- newSTRef (0 :: Int)+ (b, s) <- O.optimize OA.Annealing cfg+ (\x -> modifySTRef' ref (+ 1) >> return ((x - 3) ** 2)) 0+ n <- readSTRef ref+ return (b, s, n)+ assertEqual "Optimize: ST objective evaluated once per step + initial"+ (OA.acSteps cfg + 1) evals+ assertEqual "Optimize: ST run returns finite (best, score)"+ True (not (isNaN best) && not (isNaN score) && not (isInfinite score))++-- | Same seed, same (deterministic) objective => identical result.+testOptimizeDeterminism :: IO ()+testOptimizeDeterminism = do+ r1 <- O.optimize OA.Annealing optAnnealCfg (\x -> return ((x - 3) ** 2)) 0+ r2 <- O.optimize OA.Annealing optAnnealCfg (\x -> return ((x - 3) ** 2)) 0+ assertEqual "Optimize.Annealing: same seed => identical result" r1 r2+ let cfg = OG.defaultGAConfig { OG.gaSeed = 11, OG.gaGenerations = 20 }+ s1 <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)+ s2 <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)+ assertEqual "Optimize.GA: same seed => identical result" s1 s2++-- | Fail-fast contract: non-finite objective scores and invalid+-- configurations are rejected with 'error', never silently absorbed.+testOptimizeFailFast :: IO ()+testOptimizeFailFast = do+ r <- try (O.optimize OA.Annealing optAnnealCfg (\_ -> return (0 / 0)) 0)+ case (r :: Either SomeException (Double, Double)) of+ Left _ -> putStrLn "[PASS] Optimize.Annealing: NaN objective rejected (fail-fast)"+ Right v -> do+ putStrLn ("[FAIL] Optimize.Annealing: NaN objective accepted: " ++ show v)+ exitFailure+ let badCfg = OG.defaultGAConfig { OG.gaEliteCount = 999 }+ r2 <- try (O.optimize OG.GA badCfg (return . optSphere) (UV.replicate 3 0))+ case (r2 :: Either SomeException (UV.Vector Double, Double)) of+ Left _ -> putStrLn "[PASS] Optimize.GA: invalid config rejected (fail-fast)"+ Right v -> do+ putStrLn ("[FAIL] Optimize.GA: invalid config accepted: " ++ show v)+ exitFailure++-- | Run this domain in its original relative test order.+runTests :: IO ()+runTests = do+ testOptimizeAnnealingQuadratic+ testOptimizeGASphere+ testOptimizeGAMaximize+ testOptimizeSTObjective+ testOptimizeDeterminism+ testOptimizeFailFast
test/Spec.hs view
@@ -1,7130 +1,50 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}--module Main (main) where--import ExchangeAlgebra.Journal-import qualified ExchangeAlgebra.Convert as EC-import qualified ExchangeAlgebra.Convert.Checked as ECC-import qualified ExchangeAlgebra.Accounting.PostingPolicy as PP-import qualified ExchangeAlgebra.Consolidation.Worksheet as CW-import qualified ExchangeAlgebra.TrialBalance.Balance as TBB-import qualified ExchangeAlgebra.TrialBalance.Validation as TB-import qualified ExchangeAlgebra.Reporting.Presentation as RP-import qualified ExchangeAlgebra.Reporting.Metric as RM-import qualified ExchangeAlgebra.Reporting.Group as RG-import qualified ExchangeAlgebra.Convert.Csv as ECsv-import qualified ExchangeAlgebra.Assist as Assist-import qualified ExchangeAlgebra.Assist.Descriptions as AssistDesc-import qualified ExchangeAlgebra.Algebra.Base.Account.Registry as Registry-import qualified ExchangeAlgebra.Algebra as EA-import qualified ExchangeAlgebra.Algebra.Internal as EAI-import qualified ExchangeAlgebra.Algebra.Transfer as EAT-import qualified ExchangeAlgebra.Journal as EJ-import qualified ExchangeAlgebra.Journal.Transfer as EJT-import qualified ExchangeAlgebra.Bookkeeping as EB-import ExchangeAlgebra.Value (MoneyDecimal (..), bankersRound)-import qualified ExchangeAlgebra.Simulate as ES-import ExchangeAlgebra.Simulate-import qualified ExchangeAlgebra.Simulate.Lite as Lite-import ExchangeAlgebra.Simulate.Network- ( TradeNetwork, InputCoefficients, NetworkError(..)- , tradeNetwork, inputCoefficients- , nodes, edges, suppliersOf, buyersOf, edgeCount- , coefficient, inputsOf, sigmaEdges- , completeNetwork, kRegular, erdosRenyi, scaleFree, sectorBlock- , IndustrialEconomy(..), IndustrialOptions(..)- , defaultIndustrialOptions, industrialNetwork, industrialNetworkWith- , firms, industrialEdges- , TaxRate(..), taxOf, IndustrialFlows(..)- , FlowOptions(..), industrialFlows, industrialFlowsWith- , CoefOptions(..), defaultCoefOptions, randomCoefficients- , networkFromTable, coefficientsFromTable, fromCoefficientMatrix- , parseEdgeCsv, parseCoefCsv )-import ExchangeAlgebra.Simulate.Lite- ( InitT, RefT, SnapT, HK- , Field(..), carry, resetEach, updateEach- , Stage, stage, stageFor, stageOf- , Par(..), SimSpec, mkSimSpec, runLite, runLiteFold- , runLiteWithPolicy, runLiteWithPolicyObs )-import qualified ExchangeAlgebra.Simulate.Policy as Policy-import qualified ExchangeAlgebra.Render.Simulation as RenderSimulation-import ExchangeAlgebra.Value (MoneyDouble)-import qualified ExchangeAlgebra.Write as EW-import ExchangeAlgebra.Write-import qualified ExchangeAlgebra.Optimize as O-import qualified ExchangeAlgebra.Optimize.Annealing as OA-import qualified ExchangeAlgebra.Optimize.GA as OG-import qualified Data.Vector.Unboxed as UV--import qualified Data.HashMap.Strict as HM-import Data.Hashable (hash)-import Data.Decimal (DecimalRaw (Decimal))-import qualified Data.Map.Strict as M-import qualified Data.List as L-import qualified Data.List.NonEmpty as NE-import qualified Data.Set as Set-import Data.Char (isAlpha, isAlphaNum, isAscii, isSpace)-import qualified Data.Binary as Binary-import qualified Data.Binary.Put as BinaryPut-import qualified Data.ByteString.Lazy as BL-import qualified Data.ByteString.Lazy.Char8 as BL8-import qualified Data.Text as T-import qualified Data.Text.IO as TIO-import Golden.WriteRows-import qualified Transfer.RuleSpec as TransferRuleSpec-import qualified Algebra.ProjWildcardSpec as ProjWildcardSpec-import qualified Algebra.ExactSumSpec as ExactSumSpec-import qualified Posting.PostingSpec as PostingSpec-import qualified Posting.SettleSpec as SettleSpec-import qualified Journal.CarrySpec as CarrySpec-import Numeric (showHex)-import Control.Monad (forM_)-import Control.Monad.ST-import Data.Array.ST-import Data.STRef-import Data.IORef (newIORef, readIORef, modifyIORef')-import System.Exit (exitFailure)-import System.IO (IOMode(WriteMode), withFile)-import Data.Time (Day, TimeOfDay(..), fromGregorian)-import System.Directory (removeFile)-import System.Random (StdGen, mkStdGen, randomR, split)-import Control.Monad (replicateM)-import Control.Monad.State (runState, state)-import Control.Exception (try, evaluate, ErrorCall, SomeException)-import Control.DeepSeq (force)-import Test.QuickCheck hiding (Fixed)-import GHC.Generics (Generic)-import System.Random (randomR)---- ================================================================--- Unit test helpers--- ================================================================--eps :: Double-eps = 1e-9--assertEqual :: (Eq a, Show a) => String -> a -> a -> IO ()-assertEqual label expected actual- | expected == actual = putStrLn ("[PASS] " ++ label)- | otherwise = do- putStrLn ("[FAIL] " ++ label)- putStrLn (" expected: " ++ show expected)- putStrLn (" actual : " ++ show actual)- exitFailure--assertNear :: String -> Double -> Double -> IO ()-assertNear label expected actual- | abs (expected - actual) <= eps = putStrLn ("[PASS] " ++ label)- | otherwise = do- putStrLn ("[FAIL] " ++ label)- putStrLn (" expected: " ++ show expected)- putStrLn (" actual : " ++ show actual)- exitFailure--decodeAccountTitleOrFail :: BL.ByteString -> Either String AccountTitles-decodeAccountTitleOrFail bytes = case Binary.decodeOrFail bytes of- Left (_, _, message) -> Left message- Right (_, _, title) -> Right title--testAccountTitlesBinary :: IO ()-testAccountTitlesBinary = do- let titles = [minBound .. maxBound] :: [AccountTitles]- roundTripped = L.map (Binary.decode . Binary.encode) titles- invalidTag = fromIntegral (fromEnum (maxBound :: AccountTitles) + 1)- invalidBytes = BinaryPut.runPut (BinaryPut.putWord16be invalidTag)- assertEqual "AccountTitles Binary covers all 241 constructors"- 241 (L.length titles)- assertEqual "AccountTitles Binary Word16be roundtrip"- titles roundTripped- assertEqual "AccountTitles Binary rejects out-of-range Word16"- True (case decodeAccountTitleOrFail invalidBytes of- Left _ -> True- Right _ -> False)--testPracticalAndManufacturingAccountTitles :: IO ()-testPracticalAndManufacturingAccountTitles = do- let titles =- [ EntertainmentExpenses- , MeetingExpenses- , NewspaperBooksExpenses- , RawMaterials- , GoodsInTransit- ]- divisions =- [ Cost- , Cost- , Cost- , Assets- , Assets- ]- assertEqual "new account titles Binary roundtrip"- titles (L.map (Binary.decode . Binary.encode) titles)- assertEqual "new account titles whichDivision"- divisions (L.map classifyAccountDivision titles)- assertEqual "new account titles parse Japanese names and material alias"- (L.map Right- [ EntertainmentExpenses- , MeetingExpenses- , NewspaperBooksExpenses- , RawMaterials- , GoodsInTransit- , RawMaterials- ])- (L.map EC.parseAccountTitle (L.map T.pack- [ "交際費", "会議費", "新聞図書費", "原材料", "未着品", "材料" ]))---- ================================================================--- AccountTitles classification exhaustiveness (Phase A)--- ================================================================------ Pins the (whatDiv, whichSide, fixedCurrent) classification of every--- AccountTitles constructor against an explicit expected table that encodes--- the Phase A design table. Any new constructor that is not added here makes--- the test fail (the [minBound .. maxBound] traversal will hit a title absent--- from the table), forcing the table to be kept in sync and guarding against--- classifyAccountDivision's wildcard silently classifying a title as Assets.------ whichSide is evaluated on the @Not :< title@ base (no Hat reversal), so it--- equals the "home side" implied by whatDiv: Debit for Assets/Cost,--- Credit for Liability/Equity/Revenue.---- | Expected classification for every non-wildcard AccountTitles constructor.--- (title, expected whatDiv, expected whichSide on Not-base, expected fixedCurrent)-accountTitleClassTable :: [(AccountTitles, AccountDivision, Side, FixedCurrent)]-accountTitleClassTable =- -- Pre-existing titles- [ (Cash, Assets, Debit, Current)- , (Deposits, Assets, Debit, Current)- , (CurrentDeposits, Assets, Debit, Current)- , (Securities, Assets, Debit, Current)- , (InvestmentSecurities, Assets, Debit, Fixed)- , (LongTermNationalBonds, Assets, Debit, Fixed)- , (ShortTermNationalBonds, Assets, Debit, Current)- , (Products, Assets, Debit, Current)- , (Machinery, Assets, Debit, Fixed)- , (Building, Assets, Debit, Fixed)- , (Vehicle, Assets, Debit, Fixed)- , (StockInvestment, Assets, Debit, Other)- , (EquipmentInvestment, Assets, Debit, Fixed)- , (LongTermLoansReceivable, Assets, Debit, Fixed)- , (AccountsReceivable, Assets, Debit, Current)- , (ShortTermLoansReceivable, Assets, Debit, Current)- , (ReserveDepositReceivable, Assets, Debit, Current)- , (Gold, Assets, Debit, Fixed)- , (GovernmentService, Assets, Debit, Current)- , (CapitalStock, Equity, Credit, Other)- , (RetainedEarnings, Equity, Credit, Other)- , (LongTermLoansPayable, Liability, Credit, Fixed)- , (ShortTermLoansPayable, Liability, Credit, Current)- , (LoansPayable, Liability, Credit, Current)- , (ReserveForDepreciation, Liability, Credit, Current)- , (DepositPayable, Liability, Credit, Current)- , (LongTermNationalBondsPayable, Liability, Credit, Fixed)- , (ShortTermNationalBondsPayable, Liability, Credit, Current)- , (ReserveDepositPayable, Liability, Credit, Current)- , (CentralBankNotePayable, Liability, Credit, Current)- , (Depreciation, Cost, Debit, Other)- , (AmortizationExpense, Cost, Debit, Other)- , (SalesCost, Cost, Debit, Other)- , (BusinessTrip, Cost, Debit, Other)- , (Commutation, Cost, Debit, Other)- , (UtilitiesExpense, Cost, Debit, Other)- , (RentExpense, Cost, Debit, Other)- , (AdvertisingExpense, Cost, Debit, Other)- , (DeliveryExpenses, Cost, Debit, Other)- , (SuppliesExpenses, Cost, Debit, Other)- , (MiscellaneousExpenses, Cost, Debit, Other)- , (WageExpenditure, Cost, Debit, Other)- , (InterestExpense, Cost, Debit, Other)- , (TaxesExpense, Cost, Debit, Other)- , (ConsumptionExpenditure, Cost, Debit, Other)- , (SubsidyExpense, Cost, Debit, Other)- , (CentralBankPaymentExpense, Cost, Debit, Other)- , (Purchases, Cost, Debit, Other)- , (NetIncome, Cost, Debit, Other)- , (ValueAdded, Revenue, Credit, Other)- , (SubsidyIncome, Revenue, Credit, Other)- , (NationalBondInterestEarned, Revenue, Credit, Other)- , (DepositInterestEarned, Revenue, Credit, Other)- , (GrossProfit, Revenue, Credit, Other)- , (OrdinaryProfit, Revenue, Credit, Other)- , (InterestEarned, Revenue, Credit, Other)- , (ReceiptFee, Revenue, Credit, Other)- , (RentalIncome, Revenue, Credit, Other)- , (WageEarned, Revenue, Credit, Other)- , (TaxesRevenue, Revenue, Credit, Other)- , (CentralBankPaymentIncome, Revenue, Credit, Other)- , (Sales, Revenue, Credit, Other)- , (NetLoss, Revenue, Credit, Other)- -- Phase A additions: Assets (資産)- , (PettyCash, Assets, Debit, Current)- , (NotesReceivable, Assets, Debit, Current)- , (ElectronicallyRecordedReceivable, Assets, Debit, Current)- , (CreditCardReceivable, Assets, Debit, Current)- , (NotesLoansReceivable, Assets, Debit, Current)- , (MerchandiseInventory, Assets, Debit, Current)- , (AdvancesPaid, Assets, Debit, Current)- , (PrepaidExpenses, Assets, Debit, Current)- , (AccruedRevenue, Assets, Debit, Current)- , (OtherReceivables, Assets, Debit, Current)- , (PaymentsOnBehalf, Assets, Debit, Current)- , (SuspensePayments, Assets, Debit, Current)- , (ConsumptionTaxPaid, Assets, Debit, Current)- , (PrepaidCorporateIncomeTaxes, Assets, Debit, Current)- , (Land, Assets, Debit, Fixed)- , (Fixtures, Assets, Debit, Fixed)- , (Patent, Assets, Debit, Fixed)- , (Trademark, Assets, Debit, Fixed)- , (Software, Assets, Debit, Fixed)- , (CashOverShort, Assets, Debit, Other)- -- Phase A additions: Liability (負債)- , (AccountsPayable, Liability, Credit, Current)- , (NotesPayable, Liability, Credit, Current)- , (ElectronicallyRecordedObligations, Liability, Credit, Current)- , (NotesLoansPayable, Liability, Credit, Current)- , (BankOverdraft, Liability, Credit, Current)- , (AdvancesReceived, Liability, Credit, Current)- , (UnearnedRevenue, Liability, Credit, Current)- , (AccruedExpenses, Liability, Credit, Current)- , (OtherPayables, Liability, Credit, Current)- , (DepositsReceived, Liability, Credit, Current)- , (SuspenseReceipts, Liability, Credit, Current)- , (ConsumptionTaxReceived, Liability, Credit, Current)- , (AccruedConsumptionTax, Liability, Credit, Current)- , (AccruedCorporateIncomeTaxes, Liability, Credit, Current)- , (UnpaidDividends, Liability, Credit, Current)- , (AllowanceForDoubtfulAccounts, Assets, Credit, Current) -- contra asset (isContra)- , (AccumulatedDepreciation, Assets, Credit, Fixed) -- contra asset (isContra)- -- Phase A additions: Equity (資本)- , (LegalRetainedEarnings, Equity, Credit, Other)- -- Phase A additions: Cost (費用)- , (ProvisionForDoubtfulAccounts, Cost, Debit, Other)- , (BadDebtLoss, Cost, Debit, Other)- , (LossOnSalesOfFixedAssets, Cost, Debit, Other)- , (LossOnSalesOfNotesReceivable, Cost, Debit, Other)- , (PaymentFees, Cost, Debit, Other)- , (MiscellaneousLoss, Cost, Debit, Other)- , (CorporateIncomeTaxes, Cost, Debit, Other)- , (CommunicationExpenses, Cost, Debit, Other)- -- Phase A additions: Revenue (収益)- , (GainOnSalesOfFixedAssets, Revenue, Credit, Other)- , (RecoveryOfBadDebts, Revenue, Credit, Other)- , (MiscellaneousIncome, Revenue, Credit, Other)- -- Phase B addition: Revenue (収益)- , (ReversalOfAllowanceForDoubtfulAccounts, Revenue, Credit, Other)- -- T4b additions: equity-method accounts- , (InvestmentInAssociate, Assets, Debit, Fixed)- , (EquityInEarningsOfInvestee, Revenue, Credit, Other)- -- FX library additions: OCI/capital accounts- , (CumulativeTranslationAdjustment, Equity, Credit, Other)- -- V-Land 2 additions- , (TimeDeposits, Assets, Debit, Current)- , (LoansReceivable, Assets, Debit, Current)- , (GiftCertificatesReceived, Assets, Debit, Current)- , (SecurityDepositsPaid, Assets, Debit, Fixed)- , (SuppliesOnHand, Assets, Debit, Current)- , (ContractAssets, Assets, Debit, Current)- , (IncomeTaxesRefundReceivable, Assets, Debit, Current)- , (WorkInProcess, Assets, Debit, Current)- , (DeferredTaxAssets, Assets, Debit, Fixed)- , (LeasedAssets, Assets, Debit, Fixed)- , (ToolsAndInstruments, Assets, Debit, Fixed)- , (ConstructionInProgress, Assets, Debit, Fixed)- , (Goodwill, Assets, Debit, Fixed)- , (SoftwareInProgress, Assets, Debit, Fixed)- , (LongTermPrepaidExpenses, Assets, Debit, Fixed)- , (DishonoredNotesReceivable, Assets, Debit, Current)- , (PrepaidPensionCost, Assets, Debit, Fixed)- , (NetDefinedBenefitAsset, Assets, Debit, Fixed)- , (DepositsInSpecialAccounts, Assets, Debit, Current)- , (Structures, Assets, Debit, Fixed)- , (LeaseholdRights, Assets, Debit, Fixed)- , (NonOperatingNotesReceivable, Assets, Debit, Current)- , (NonOperatingElectronicallyRecordedReceivable, Assets, Debit, Current)- , (RefundLiabilities, Liability, Credit, Current)- , (NonOperatingNotesPayable, Liability, Credit, Current)- , (NonOperatingElectronicallyRecordedObligations, Liability, Credit, Current)- , (BonusesPayable, Liability, Credit, Current)- , (AllowanceForRepairs, Liability, Credit, Current)- , (AllowanceForProductWarranties, Liability, Credit, Current)- , (AllowanceForBonuses, Liability, Credit, Current)- , (DeferredTaxLiabilities, Liability, Credit, Fixed)- , (LeaseObligations, Liability, Credit, Fixed)- , (GuaranteeDepositsReceived, Liability, Credit, Fixed)- , (AllowanceForRetirementBenefits, Liability, Credit, Fixed)- , (LongTermOtherPayables, Liability, Credit, Fixed)- , (NetDefinedBenefitLiability, Liability, Credit, Fixed)- , (StockSubscriptionDeposits, Equity, Credit, Other)- , (LegalCapitalSurplus, Equity, Credit, Other)- , (OtherCapitalSurplus, Equity, Credit, Other)- , (DividendEqualizationReserve, Equity, Credit, Other)- , (RepairFundReserve, Equity, Credit, Other)- , (ConstructionFundReserve, Equity, Credit, Other)- , (GeneralReserve, Equity, Credit, Other)- , (ValuationDifferenceOnOtherSecurities, Equity, Credit, Other)- , (NonControllingInterests, Equity, Credit, Other)- , (CapitalSurplus, Equity, Credit, Other)- , (EarnedSurplus, Equity, Credit, Other)- , (ServiceRevenue, Revenue, Credit, Other)- , (OperatingRevenue, Revenue, Credit, Other)- , (GainOnSalesOfSecurities, Revenue, Credit, Other)- , (GainOnValuationOfSecurities, Revenue, Credit, Other)- , (DividendsReceived, Revenue, Credit, Other)- , (InterestOnSecurities, Revenue, Credit, Other)- , (GainOnSalesOfInvestmentSecurities, Revenue, Credit, Other)- , (InsuranceGain, Revenue, Credit, Other)- , (GainOnBargainPurchase, Revenue, Credit, Other)- , (ReversalOfAllowanceForRepairs, Revenue, Credit, Other)- , (ReversalOfAllowanceForProductWarranties, Revenue, Credit, Other)- , (GainOnDonationOfFixedAssets, Revenue, Credit, Other)- , (GainOnNationalSubsidies, Revenue, Credit, Other)- , (GainOnConstructionGrants, Revenue, Credit, Other)- , (LandRentReceived, Revenue, Credit, Other)- , (SalesRebates, Revenue, Debit, Other)- , (CostOfServices, Cost, Debit, Other)- , (OperatingExpenses, Cost, Debit, Other)- , (InventoryShrinkageLoss, Cost, Debit, Other)- , (LossOnValuationOfMerchandise, Cost, Debit, Other)- , (Bonuses, Cost, Debit, Other)- , (RetirementBenefitExpenses, Cost, Debit, Other)- , (ProvisionForRepairs, Cost, Debit, Other)- , (ProvisionForBonuses, Cost, Debit, Other)- , (ProvisionForProductWarranties, Cost, Debit, Other)- , (ResearchAndDevelopmentExpenses, Cost, Debit, Other)- , (AmortizationOfGoodwill, Cost, Debit, Other)- , (AmortizationOfSoftware, Cost, Debit, Other)- , (AmortizationOfPatents, Cost, Debit, Other)- , (LeaseExpenses, Cost, Debit, Other)- , (IncorporationExpenses, Cost, Debit, Other)- , (StockIssuanceCosts, Cost, Debit, Other)- , (BusinessCommencementExpenses, Cost, Debit, Other)- , (DevelopmentExpenses, Cost, Debit, Other)- , (LossOnSalesOfElectronicallyRecordedReceivables, Cost, Debit, Other)- , (LossOnSalesOfReceivables, Cost, Debit, Other)- , (LossOnSalesOfSecurities, Cost, Debit, Other)- , (LossOnValuationOfSecurities, Cost, Debit, Other)- , (LossOnSalesOfInvestmentSecurities, Cost, Debit, Other)- , (LossOnFire, Cost, Debit, Other)- , (LossOnRetirementOfFixedAssets, Cost, Debit, Other)- , (LossOnReductionOfFixedAssets, Cost, Debit, Other)- , (AdditionalIncomeTaxesForPriorPeriods, Cost, Debit, Other)- , (RefundOfIncomeTaxes, Cost, Credit, Other)- , (PurchaseRebates, Cost, Credit, Other)- , (WelfareExpenses, Cost, Debit, Other)- , (MaintenanceExpenses, Cost, Debit, Other)- , (StatutoryWelfareExpenses, Cost, Debit, Other)- , (LandRentPaid, Cost, Debit, Other)- , (InsuranceExpense, Cost, Debit, Other)- , (RepairsExpense, Cost, Debit, Other)- , (StorageExpenses, Cost, Debit, Other)- , (MembershipFees, Cost, Debit, Other)- , (IncomeSummary, Assets, Debit, Other)- , (SuspenseAccount, Assets, Debit, Current)- , (ForeignExchangeGains, Revenue, Credit, Other)- , (ForeignExchangeLosses, Cost, Debit, Other)- , (ContraAccountForGuaranteeObligations, Assets, Debit, Other)- , (GuaranteeObligations, Liability, Credit, Other)- , (IncomeTaxesAdjustment, Cost, Debit, Other)- , (BranchCurrentAccount, Assets, Debit, Other)- , (HeadOfficeCurrentAccount, Liability, Credit, Other)- , (NetIncomeAttributableToNCI, Cost, Debit, Other)- , (NetLossAttributableToNCI, Revenue, Credit, Other)- , (TradingSecurities, Assets, Debit, Current)- , (HeldToMaturityBonds, Assets, Debit, Fixed)- , (SubsidiaryStocks, Assets, Debit, Fixed)- , (AffiliateStocks, Assets, Debit, Fixed)- , (AvailableForSaleSecurities, Assets, Debit, Fixed)- , (ConsumptionTaxRefundReceivable, Assets, Debit, Current)- , (PropertyTaxPayable, Liability, Credit, Current)- , (DepositsReceivedFromOfficers, Liability, Credit, Current)- , (EntertainmentExpenses, Cost, Debit, Other)- , (MeetingExpenses, Cost, Debit, Other)- , (NewspaperBooksExpenses, Cost, Debit, Other)- , (RawMaterials, Assets, Debit, Current)- , (GoodsInTransit, Assets, Debit, Current)- ]--testAccountTitleClassification :: IO ()-testAccountTitleClassification = do- -- All non-wildcard constructors, derived from Bounded/Enum.- let allTitles = [ t | t <- [minBound .. maxBound], t /= AccountTitle ]- tableMap = M.fromList [ (t, (d, s, fc)) | (t, d, s, fc) <- accountTitleClassTable ]- -- A title is "covered" iff it appears in the expected table.- missing = [ t | t <- allTitles, not (M.member t tableMap) ]- extra = [ t | (t, _, _, _) <- accountTitleClassTable, t `notElem` allTitles ]- -- Guard: the table must list exactly the non-wildcard constructors.- assertEqual "AccountTitles class table covers every constructor (no missing)"- ([] :: [AccountTitles]) missing- assertEqual "AccountTitles class table has no stale entry (no extra)"- ([] :: [AccountTitles]) extra- -- Per-title classification must match the expected table.- forM_ allTitles $ \t -> do- let base = Not :< t :: HatBase AccountTitles- actual = (whatDiv base, whichSide base, fixedCurrent base)- case M.lookup t tableMap of- Just expected ->- assertEqual ("classification of " ++ show t) expected actual- Nothing -> return () -- already reported by the "missing" guard--type TestAlg = EA.Alg Double (HatBase CountUnit)-type TestJournal = EJ.Journal String Double (HatBase CountUnit)-type AxisJournal = EJ.Journal (String, Int) Double (HatBase CountUnit)--algSample :: TestAlg-algSample =- (1 :@ (Hat :< Yen))- .+ (1 :@ (Not :< Amount))- .+ (2 :@ (Not :< Yen))- .+ (2 :@ (Hat :< Amount))- .+ (3 :@ (Hat :< Yen))--journalSample :: TestJournal-journalSample = EJ.fromList [x, y, z]- where- x = ((1 :@ (Hat :< Yen)) .+ (1 :@ (Not :< Amount))) .| "cat" :: TestJournal- y = ((2 :@ (Not :< Yen)) .+ (2 :@ (Hat :< Amount))) .| "dog" :: TestJournal- z = ((3 :@ (Hat :< Yen)) .+ (3 :@ (Not :< Amount))) .| "fish" :: TestJournal--testReplaceNotesMatchesInsert :: IO ()-testReplaceNotesMatchesInsert = do- let x = (10.00 .@ (Not :< Cash)) .| "A"- :: EJ.Journal String Double (HatBase AccountTitles)- y = (20.00 .@ (Not :< Cash)) .| "B"- :: EJ.Journal String Double (HatBase AccountTitles)- z = (30.00 .@ (Hat :< Cash)) .| "A"- :: EJ.Journal String Double (HatBase AccountTitles)- source = x .+ y- expected = z .+ y- assertEqual "Journal.replaceNotes replaces the complete matching Note"- (EJ.toMap expected)- (EJ.toMap (EJ.replaceNotes z source))- assertEqual "Journal.replaceNotes matches insert"- (EJ.toMap (EJ.insert z source))- (EJ.toMap (EJ.replaceNotes z source))--testMapPosting :: IO ()-testMapPosting = do- let source = (1 .@ (Hat :< Yen))- .+ (2 .@ (Hat :< Yen))- .+ (3 .@ (Not :< Amount))- .+ (4 .@ (Hat :< Amount))- :: TestAlg- actual = EA.mapPosting (\v b -> (2 * v, b)) source- assertNear "Algebra.mapPosting doubles norm"- (2 * norm source) (norm actual)- assertEqual "Algebra.mapPosting preserves posting count"- (length (EA.toList source)) (length (EA.toList actual))- assertEqual "Algebra.mapPosting preserves posting order"- (EA.toList (2 .* source)) (EA.toList actual)--testMapMaybePosting :: IO ()-testMapMaybePosting = do- let droppedBase = Not :< Amount :: HatBase CountUnit- zeroedBase = Hat :< Yen :: HatBase CountUnit- source = (1 .@ (Hat :< Yen))- .+ (2 .@ (Hat :< Yen))- .+ (3 .@ droppedBase)- .+ (4 .@ (Hat :< Amount))- :: TestAlg- dropped = EA.mapMaybePosting- (\v b -> if b == droppedBase then Nothing else Just (v, b))- source- zeroed = EA.mapMaybePosting- (\v b -> Just (if b == zeroedBase then 0 else v, b))- source- without b = L.filter (\posting -> case posting of- _ :@ b' -> b' /= b- _ -> False)- assertNear "Algebra.mapMaybePosting drop decreases norm"- (norm source - 3) (norm dropped)- assertEqual "Algebra.mapMaybePosting drop preserves remaining order"- (without droppedBase (EA.toList source)) (EA.toList dropped)- assertEqual "Algebra.mapMaybePosting normalizes zero values away"- (without zeroedBase (EA.toList source)) (EA.toList zeroed)---- | Multi-pattern 'proj' uses __set__ semantics: a duplicated query selects the--- same posting only once (no double counting). The de-duplicated query list and--- its de-duplicated counterpart must give identical results.-testProjMultiPatternOnePass :: IO ()-testProjMultiPatternOnePass = do- let qs, qsDedup :: [HatBase CountUnit]- qs = [Hat :< Yen, HatNot :< Amount, Hat :< Yen] -- Hat:<Yen duplicated- qsDedup = [Hat :< Yen, HatNot :< Amount]- -- duplicate query does not change the projection (set semantics)- assertEqual "Alg.proj treats query list as a set (duplicate exact)"- (EA.proj qsDedup algSample) (EA.proj qs algSample)- -- and the duplicated Hat:<Yen is counted once, not twice- assertEqual "Alg.proj no double counting (single Hat:<Yen)"- (EA.proj [Hat :< Yen] algSample)- (EA.proj [Hat :< Yen, Hat :< Yen] algSample)---- | 'projNetNorm' returns a bar-netted norm; the identity is--- @projNetNorm bs x == norm (bar (proj bs x))@ (not @norm (proj bs x)@), and the--- query list is a set (duplicates do not double count).-testProjNormFastPath :: IO ()-testProjNormFastPath = do- let qs :: [HatBase CountUnit]- qs = [Hat :< Yen, HatNot :< Amount, Hat :< Yen]- expected = norm $ EA.bar $ EA.proj qs algSample- actual = EA.projNetNorm qs algSample- assertNear "Alg.projNetNorm == norm . bar . proj (set semantics)" expected actual---- | 'MoneyDecimal' values equal under 'Eq' have equal hashes, whatever their--- number of decimal places (0.5.3.1: the hash used the raw places and mantissa).-testMoneyDecimalHashMatchesEq :: IO ()-testMoneyDecimalHashMatchesEq = do- let same :: [(MoneyDecimal, MoneyDecimal)]- same = [ (MoneyDecimal (Decimal 1 10), MoneyDecimal (Decimal 2 100))- , (MoneyDecimal (Decimal 0 0), MoneyDecimal (Decimal 3 0))- , (MoneyDecimal (Decimal 1 125), MoneyDecimal (Decimal 3 12500)) ]- assertEqual "equal MoneyDecimal values compare equal" True (all (uncurry (==)) same)- assertEqual "equal MoneyDecimal values hash equally" True- (all (\(a, b) -> hash a == hash b) same)---- | R7 sentinel (a): a duplicated exact base must project the same as a single--- copy (MoneyDecimal exact: no floating tolerance needed).-testProjDuplicateExact :: IO ()-testProjDuplicateExact = do- let alg :: EA.Alg MoneyDecimal (HatBase CountUnit)- alg = (10 :@ (Hat :< Yen))- .+ (3 :@ (Not :< Amount))- b = Hat :< Yen :: HatBase CountUnit- assertEqual "proj [b,b] == proj [b] (duplicate exact, MoneyDecimal)"- (EA.proj [b] alg) (EA.proj [b, b] alg)- assertEqual "projNetNorm [b,b] == projNetNorm [b] (duplicate exact)"- (EA.projNetNorm [b] alg) (EA.projNetNorm [b, b] alg)---- | R7 sentinel (b): an exact base together with a wildcard query that subsumes--- it must not double count the overlapping posting.-testProjExactWildcardOverlap :: IO ()-testProjExactWildcardOverlap = do- let alg :: EA.Alg MoneyDecimal (HatBase CountUnit)- alg = (10 :@ (Hat :< Yen))- .+ (5 :@ (Not :< Amount))- exact = Hat :< Yen :: HatBase CountUnit- wild = Hat :< (.#) :: HatBase CountUnit -- subsumes Hat:<Yen- -- the wildcard already selects everything the exact base does, so the union- -- equals the wildcard alone (overlap counted once)- assertEqual "proj [exact,wild] == proj [wild] (overlap, no double count)"- (EA.proj [wild] alg) (EA.proj [exact, wild] alg)- assertEqual "projNetNorm [exact,wild] == projNetNorm [wild] (overlap)"- (EA.projNetNorm [wild] alg) (EA.projNetNorm [exact, wild] alg)---- | R7 sentinel (c): the bar-netted identity @projNetNorm bs x == norm (bar (proj--- bs x))@ holds on a base carrying both hat and not sides (where it differs from--- @norm (proj bs x)@).-testProjNormBarIdentity :: IO ()-testProjNormBarIdentity = do- let alg :: EA.Alg MoneyDecimal (HatBase CountUnit)- alg = (10 :@ (Hat :< Yen)) -- Yen carries both sides- .+ (4 :@ (Not :< Yen))- .+ (7 :@ (Not :< Amount))- bs = [HatNot :< Yen, HatNot :< Amount] :: [HatBase CountUnit]- assertEqual "projNetNorm == norm . bar . proj (both-sided base, MoneyDecimal)"- (norm (EA.bar (EA.proj bs alg))) (EA.projNetNorm bs alg)--testProjWithBaseNorm :: IO ()-testProjWithBaseNorm = do- let bs :: [HatBase CountUnit]- bs = [Not :< Amount]- expected = norm $ EJ.projWithBase bs journalSample- actual = EJ.projWithBaseNetNorm bs journalSample- assertNear "Journal.projWithBaseNetNorm matches norm . projWithBase" expected actual--testProjWithNoteNorm :: IO ()-testProjWithNoteNorm = do- let bs :: [HatBase CountUnit]- bs = [HatNot :< Amount, Hat :< Yen]- ns1 = ["dog", "cat"]- ns2 = [plank]- expected1 = norm $ EJ.projWithNoteBase ns1 bs journalSample- actual1 = EJ.projWithNoteBaseNetNorm ns1 bs journalSample- expected2 = norm $ EJ.projWithNoteBase ns2 bs journalSample- actual2 = EJ.projWithNoteBaseNetNorm ns2 bs journalSample- assertNear "Journal.projWithNoteBaseNetNorm (selected notes)" expected1 actual1- assertNear "Journal.projWithNoteBaseNetNorm (plank wildcard)" expected2 actual2---- | Sentinel for the REMOVED RULES rewrite--- @norm (projWithBase bs js) = projWithBaseNetNorm bs js@ (and the note-base--- analogue): the equation is false when a query selects both sides of one--- base. 'EJ.projWithBaseNetNorm' \/ 'EJ.projWithNoteBaseNetNorm' are the /bar-netted/--- read-outs (per base @|not - hat|@), while @norm . projWithBase@ is the--- gross norm (sums both sides). Both values are pinned here so a future--- \"optimization\" that silently nets the gross path fails loudly.-testProjWithBaseNormBothSided :: IO ()-testProjWithBaseNormBothSided = do- let alg :: EA.Alg MoneyDecimal (HatBase CountUnit)- alg = (10 :@ (Hat :< Yen)) -- Yen carries both sides- .+ (4 :@ (Not :< Yen))- .+ (7 :@ (Not :< Amount))- js = alg .| "n" :: EJ.Journal String MoneyDecimal (HatBase CountUnit)- bs = [HatNot :< Yen] :: [HatBase CountUnit]- assertEqual "projWithBaseNetNorm nets both sides (HatNot query): |10-4|"- 6 (EJ.projWithBaseNetNorm bs js)- assertEqual "norm . projWithBase stays gross (no RULES rewrite): 10+4"- 14 (norm (EJ.projWithBase bs js))- assertEqual "projWithBaseNetNorm == norm . map bar . projWithBase"- (norm (EJ.map EA.bar (EJ.projWithBase bs js)))- (EJ.projWithBaseNetNorm bs js)- assertEqual "projWithNoteBaseNetNorm nets both sides (HatNot query): |10-4|"- 6 (EJ.projWithNoteBaseNetNorm ["n"] bs js)- assertEqual "norm . projWithNoteBase stays gross (no RULES rewrite): 10+4"- 14 (norm (EJ.projWithNoteBase ["n"] bs js))---- | Regression test for the `bases` typo bug.------ Before the fix at Algebra.hs:868, `bases` ignored the `_notSide` Seq and--- iterated `_hatSide` twice (with `Hat` and `Not` labels). As a result,--- `length (bases x) != length (vals x)` whenever Hat/Not Seq lengths differed.------ This test constructs an Alg where the Hat Seq for `Yen` has length 1 and--- the Not Seq has length 2, plus a separate basis whose Hat Seq is empty.--- That makes the divergence detectable in both directions.-testBasesNotSideRegression :: IO ()-testBasesNotSideRegression = do- let alg :: TestAlg- alg = (100 :@ (Hat :< Yen)) -- Yen: hatSide = [100]- .+ (50 :@ (Not :< Yen)) -- Yen: notSide = [50]- .+ (30 :@ (Not :< Yen)) -- Yen: notSide = [50, 30]- .+ (20 :@ (Not :< Amount)) -- Amount: notSide = [20], hatSide = []- vs = EA.vals alg- bs = EA.bases alg- hatCount = length (L.filter isHat bs)- notCount = length (L.filter (not . isHat) bs)- -- vals and bases must agree on total count (one label per scalar entry)- assertEqual "bases/vals same length (regression for hs/ns typo)"- (length vs) (length bs)- -- Expected: 1 Hat label (Hat:<Yen) and 3 Not labels (50:<Yen, 30:<Yen, 20:<Amount)- assertEqual "bases Hat label count" 1 hatCount- assertEqual "bases Not label count" 3 notCount--testSigmaMergePath :: IO ()-testSigmaMergePath = do- let xs = [1 .. 5 :: Int]- f :: Int -> TestAlg- f i- | i == 3 = EA.Zero- | odd i = fromIntegral i :@ (Hat :< Yen)- | otherwise = fromIntegral i :@ (Not :< Amount)- expected :: TestAlg- expected = EA.unionsMerge (L.map f xs)- actual :: TestAlg- actual = EA.sigma xs f- assertEqual "Alg.sigma bulk-merge path matches unionsMerge" expected actual---- | Characterization: the same-base 'Seq' order is __construction-path--- dependent__. The pairwise-union path ('EA.fromList' = 'mconcat') and the--- bulk-merge path ('EA.sigma' \/ 'EA.unionsMerge') produce the same /multiset/--- of postings but in different sequence orders, which 'Eq' \/ @Binary@ observe--- (and 'Double' observes through the last ULP of 'norm'\/'bar' association).--- This test pins the current orders so any change to either path is a--- conscious decision; unifying the paths is tracked in the 0.5.0.0 cleanup--- plan. For order-independent comparison use `MoneyDecimal` (exact) or compare--- after 'EA.compress'\/'EA.bar'.-testSameBaseSeqOrderPathDependence :: IO ()-testSameBaseSeqOrderPathDependence = do- let f :: Int -> TestAlg- f i = fromIntegral i :@ (Hat :< Yen)- xs = L.map f [1, 2, 3]- viaFromList = EA.fromList xs- viaSigma = EA.sigma [1, 2, 3] f- viaMerge = EA.unionsMerge xs- assertEqual "fromList same-base seq order (pairwise-union path)"- [3, 1, 2] (EA.vals viaFromList)- assertEqual "sigma same-base seq order (bulk-merge path)"- [3, 2, 1] (EA.vals viaSigma)- assertEqual "unionsMerge order matches sigma (same merge path)"- (EA.vals viaSigma) (EA.vals viaMerge)- -- same multiset, different order: Eq observes the redundancy order- assertEqual "fromList /= sigma under Eq (order is observable)"- False (viaFromList == viaSigma)- -- the algebraic content is nevertheless identical- assertNear "norm agrees across construction paths"- (norm viaFromList) (norm viaSigma)- assertEqual "bar agrees across construction paths"- (EA.bar viaFromList) (EA.bar viaSigma)--testSigma2When :: IO ()-testSigma2When = do- let xs = [1 .. 3 :: Int]- ys = [1 .. 4 :: Int]- cond i j = i /= j && even (i + j)- f :: Int -> Int -> TestAlg- f i j =- let v = fromIntegral (i * 10 + j)- in if odd i- then v :@ (Hat :< Yen)- else v :@ (Not :< Amount)- expected :: TestAlg- expected =- EA.unionsMerge- [ f i j- | i <- xs- , j <- ys- , cond i j- ]- actual :: TestAlg- actual = EA.sigma2When xs ys cond f- assertEqual "Alg.sigma2When matches list-comprehension sum" expected actual--testSigmaFromMap :: IO ()-testSigmaFromMap = do- let kvs = M.fromList- [ ((1, 2), 5.0)- , ((2, 3), 0.0)- , ((3, 1), 7.0)- ] :: M.Map (Int, Int) Double- f :: (Int, Int) -> Double -> TestAlg- f (i, j) v- | i < j = v :@ (Hat :< Yen)- | otherwise = v :@ (Not :< Amount)- expected :: TestAlg- expected = EA.unionsMerge- [ f (1, 2) 5.0- , f (3, 1) 7.0- ]- actual :: TestAlg- actual = EA.sigmaFromMap kvs f- assertEqual "Alg.sigmaFromMap iterates non-zero map entries only" expected actual--testJournalFromListStrict :: IO ()-testJournalFromListStrict = do- -- fromList is now a strict left fold (L.foldl' (.+) mempty). Verify it still- -- preserves the posting multiset by matching the old lazy right-fold reference- -- (foldr (.+) mempty). Colliding note keys (i `mod` 30) force same-note/same-base- -- postings into one Alg sequence, where the two folds accumulate in opposite- -- order; with MoneyDecimal (exact, associative) the aggregate (norm) is identical.- let mk i = ((fromIntegral (i `mod` 7 + 1) :: MoneyDecimal)- :@ ((if even i then Hat else Not) :< ([Yen, Amount] !! (i `mod` 2))))- .| show (i `mod` 30)- xs :: [Journal String MoneyDecimal (HatBase CountUnit)]- xs = [ mk i | i <- [1 .. 400 :: Int] ]- strict = EJ.fromList xs- lazyRef = foldr (.+) mempty xs- -- exact value type ⇒ norm identical regardless of seq order (multiset preserved)- assertEqual "Journal.fromList (strict): norm matches lazy foldr reference (MoneyDecimal exact)"- (norm strict) (norm lazyRef)- -- distinct note keys ⇒ no seq collision ⇒ exact structural equality with foldr- let ys :: [Journal String MoneyDecimal (HatBase CountUnit)]- ys = [ ((fromIntegral i :: MoneyDecimal) :@ (Not :< Yen)) .| show i- | i <- [1 .. 20 :: Int] ]- assertEqual "Journal.fromList (strict): structurally equal to foldr for distinct notes"- (EJ.toMap (EJ.fromList ys)) (EJ.toMap (foldr (.+) mempty ys))---- | Regression test for the @union@ zero-singleton base-relabel bug--- (Algebra.hs). When one operand of @(.+)@ is a /zero-valued/ singleton on base--- @b1@ and the other a /real/ singleton on a different base @b2@, the result must--- keep the real value on its OWN base (@v2:@b2@), not relabel it onto the zero--- posting's base. The old code returned @v2:@b1@ / @v1:@b2@, which silently moved--- a value to the wrong base. It preserved @norm@ (total unchanged) but corrupted--- per-base projection, and surfaced as construction-order-dependent simulation--- results (sparsified coefficients build explicit @0:@base@ singletons via raw--- @(:@)@). See plans/in-progress/SELECTABLE_VALUE_TYPE_PLAN.md (Stage D).-testUnionZeroSingletonBase :: IO ()-testUnionZeroSingletonBase = do- let zb = 0 :@ (Hat :< Yen) :: TestAlg -- zero value, base Yen- rb = 5 :@ (Hat :< Amount) :: TestAlg -- real value, base Amount- -- both fold directions of the singleton/singleton union- assertEqual "union zero(.+)real keeps real value on its own base"- rb (EA.proj [Hat :< Amount] (zb .+ rb))- assertEqual "union real(.+)zero keeps real value on its own base"- rb (EA.proj [Hat :< Amount] (rb .+ zb))- -- the real value must NOT appear on the zero posting's base- assertEqual "union zero(.+)real: nothing relabeled onto the zero's base"- (EA.Zero :: TestAlg) (EA.proj [Hat :< Yen] (zb .+ rb))- assertEqual "union real(.+)zero: nothing relabeled onto the zero's base"- (EA.Zero :: TestAlg) (EA.proj [Hat :< Yen] (rb .+ zb))---- | Regression for audit divergence C: scalar product (.*) must reject a--- negative / non-finite scalar instead of silently producing negative--- (out-of-domain) postings. (Pre-fix, (.*) used raw (:@) and bypassed the--- isErrorValue check that (.@) performs.)-testScalarRejectsNegative :: IO ()-testScalarRejectsNegative = do- let xD = 10 :@ (Not :< Yen) :: TestAlg- rD <- try (evaluate (norm ((-1) .* xD))) :: IO (Either SomeException Double)- case rD of- Left _ -> putStrLn "[PASS] (.*) rejects negative scalar (Double)"- Right v -> do putStrLn ("[FAIL] (.*) negative scalar leaked (Double): " ++ show v); exitFailure- let xN = 10 :@ (Not :< Yen) :: EA.Alg MoneyDecimal (HatBase CountUnit)- rN <- try (evaluate (norm ((-1) .* xN))) :: IO (Either SomeException MoneyDecimal)- case rN of- Left _ -> putStrLn "[PASS] (.*) rejects negative scalar (MoneyDecimal)"- Right v -> do putStrLn ("[FAIL] (.*) negative scalar leaked (MoneyDecimal): " ++ show v); exitFailure- -- non-negative scalar still works- assertNear "(.*) non-negative scalar works" 20.0 (norm (2 .* xD))---- Step 1 (concrete projection keeps the axis index lazy): the module is compiled--- @Strict@, so a concrete (non-wildcard) 'projNetNorm' must NOT force the lazy--- @_axisPosting@ index (it should be a plain 'Map.lookup'); a wildcard 'projNetNorm'--- must use (force) it. We poison the index fields with 'error' and check which--- projection crashes. Guards the projExactMap/projWildMap split.-testProjConcreteNoIndexForce :: IO ()-testProjConcreteNoIndexForce = do- let alg :: EA.Alg Double SimHatBase2- alg = EA.fromList [ 10 :@ Not :< (Cash, 1, 1, Yen)- , 20 :@ Not :< (Products, 2, 2, Amount)- , 30 :@ Hat :< (Cash, 3, 3, Yen) ]- case alg of- EAI.Liner m _ _ _ _ _ -> do- let poison = EAI.Liner m (error "POISON") (error "POISON")- (error "POISON") (error "POISON") (error "POISON")- rc <- try (evaluate (EA.projNetNorm [Not :< (Cash, 1, 1, Yen)] poison))- :: IO (Either SomeException Double)- case rc of- Right v | v == 10.0 -> putStrLn "[PASS] concrete projNetNorm does not force the axis index"- Right v -> do putStrLn ("[FAIL] concrete projNetNorm wrong value: " ++ show v); exitFailure- Left _ -> do putStrLn "[FAIL] concrete projNetNorm forced the (poisoned) axis index"; exitFailure- rw <- try (evaluate (EA.projNetNorm [Not :< (Cash, (.#), 1, Yen)] poison))- :: IO (Either SomeException Double)- case rw of- Left _ -> putStrLn "[PASS] wildcard projNetNorm uses the axis index (forced, as required)"- Right v -> do putStrLn ("[FAIL] wildcard projNetNorm did not use the index: " ++ show v); exitFailure- _ -> do putStrLn "[FAIL] expected a Liner"; exitFailure---- The Liner @_bpToId@ and @_nextBpId@ fields are reserved for the dormant P1a--- incremental-id scheme and are not maintained by 'linerFromMap' (it leaves them--- as lazy 'error' poison). This guards two invariants: (1) normal projection--- (concrete + wildcard) never forces those poisoned fields, so 'projWildMap'--- stays green; (2) forcing the unused fields fails loudly (as designed) rather--- than returning a stale/empty value.-testLinerReservedFieldsPoisoned :: IO ()-testLinerReservedFieldsPoisoned = do- let alg :: EA.Alg Double SimHatBase2- alg = EA.fromList [ 10 :@ Not :< (Cash, 1, 1, Yen)- , 20 :@ Not :< (Products, 2, 2, Amount)- , 30 :@ Hat :< (Cash, 3, 3, Yen) ]- -- projWildMap path (and concrete path) must stay green without forcing the- -- reserved fields.- assertNear "wildcard projNetNorm green with reserved fields unmaintained"- 10.0 (EA.projNetNorm [Not :< (Cash, (.#), 1, Yen)] alg)- assertNear "concrete projNetNorm green with reserved fields unmaintained"- 10.0 (EA.projNetNorm [Not :< (Cash, 1, 1, Yen)] alg)- -- Forcing _bpToId / _nextBpId must error (poison), proving they are not- -- silently maintained.- case alg of- EAI.Liner _ _ bpToId _ nextBpId _ -> do- rb <- (try (evaluate (HM.size bpToId)) :: IO (Either SomeException Int))- case rb of- Left _ -> putStrLn "[PASS] _bpToId is poisoned (forcing it errors as designed)"- Right _ -> do putStrLn "[FAIL] _bpToId was forced without error (unexpectedly maintained)"; exitFailure- rn <- (try (evaluate nextBpId) :: IO (Either SomeException Int))- case rn of- Left _ -> putStrLn "[PASS] _nextBpId is poisoned (forcing it errors as designed)"- Right _ -> do putStrLn "[FAIL] _nextBpId was forced without error (unexpectedly maintained)"; exitFailure- _ -> do putStrLn "[FAIL] expected a Liner"; exitFailure--testJournalSigmaMergePath :: IO ()-testJournalSigmaMergePath = do- let xs = [1 .. 4 :: Int]- f :: Int -> TestJournal- f i = case i of- 1 -> (1 :@ (Hat :< Yen)) .| "A"- 2 -> EJ.Zero- 3 -> (EA.Zero :: TestAlg) .| "A"- _ -> (2 :@ (Not :< Amount)) .| "B"- expected :: TestJournal- expected = EJ.fromMap $ HM.fromList- [ ("A", 1 :@ (Hat :< Yen))- , ("B", 2 :@ (Not :< Amount))- ]- actual = EJ.sigma xs f- assertEqual "Journal.sigma bulk-merge path skips zero postings" (EJ.toMap expected) (EJ.toMap actual)--testJournalSigma2When :: IO ()-testJournalSigma2When = do- let xs = [1 .. 3 :: Int]- ys = [1 .. 3 :: Int]- cond i j = i < j- f :: Int -> Int -> TestJournal- f i j- | i == 1 && j == 2 = (EA.Zero :: TestAlg) .| "N"- | odd (i + j) = (fromIntegral (i + j) :@ (Hat :< Yen)) .| "N"- | otherwise = EJ.Zero- expected :: TestJournal- expected = EJ.fromMap $ HM.fromList [("N", 5 :@ (Hat :< Yen))]- actual = EJ.sigma2When xs ys cond f- assertEqual "Journal.sigma2When matches filtered pair sum" (EJ.toMap expected) (EJ.toMap actual)--testJournalSigmaOn :: IO ()-testJournalSigmaOn = do- let xs = [1 .. 4 :: Int]- f :: Int -> TestAlg- f i- | i <= 2 = EA.Zero- | otherwise = fromIntegral i :@ (Hat :< Yen)- expected :: TestJournal- expected = (EA.sigma xs f) .| "SalesPurchase"- actual :: TestJournal- actual = EJ.sigmaOn "SalesPurchase" xs f- zeroExpected = EJ.Zero :: TestJournal- zeroActual = EJ.sigmaOn "SalesPurchase" xs (\_ -> EA.Zero :: TestAlg)- assertEqual "Journal.sigmaOn attaches note after EA.sigma" (EJ.toMap expected) (EJ.toMap actual)- assertEqual "Journal.sigmaOn returns Zero when EA.sigma is Zero" (EJ.toMap zeroExpected) (EJ.toMap zeroActual)--testJournalSigmaOnFromMap :: IO ()-testJournalSigmaOnFromMap = do- let kvs = M.fromList- [ ((1, 2), 4.0)- , ((2, 3), 0.0)- , ((2, 1), 6.0)- ] :: M.Map (Int, Int) Double- f :: (Int, Int) -> Double -> TestAlg- f (i, j) v- | i < j = v :@ (Hat :< Yen)- | otherwise = v :@ (Not :< Amount)- expected :: TestJournal- expected = (EA.sigmaFromMap kvs f) .| "SalesPurchase"- actual :: TestJournal- actual = EJ.sigmaOnFromMap "SalesPurchase" kvs f- zeroActual :: TestJournal- zeroActual = EJ.sigmaOnFromMap "SalesPurchase" (M.singleton (1, 1) 0.0) f- assertEqual "Journal.sigmaOnFromMap matches EA.sigmaFromMap + note" (EJ.toMap expected) (EJ.toMap actual)- assertEqual "Journal.sigmaOnFromMap returns Zero for empty-effective map" (EJ.toMap (EJ.Zero :: TestJournal)) (EJ.toMap zeroActual)--testFilterByAxisEquivalent :: IO ()-testFilterByAxisEquivalent = do- let ledger :: AxisJournal- ledger = EJ.fromList- [ (10 :@ (Hat :< Yen)) .| ("A", 1)- , (20 :@ (Not :< Amount)) .| ("B", 1)- , (30 :@ (Hat :< Yen)) .| ("A", 2)- ]- expected = EJ.filterWithNote (\(_, t') _ -> t' == 1) ledger- actual = EJ.filterByAxis 1 (EJ.NoteAxisKey (1 :: Int)) ledger- mismatch = EJ.filterByAxis 1 (EJ.NoteAxisKey ("1" :: String)) ledger- assertEqual "Journal.filterByAxis matches filterWithNote on axis=1"- (EJ.toMap expected)- (EJ.toMap actual)- assertEqual "Journal.filterByAxis type mismatch returns empty"- (EJ.toMap (EJ.Zero :: AxisJournal))- (EJ.toMap mismatch)--testFilterByAxisWithDeltaUpdates :: IO ()-testFilterByAxisWithDeltaUpdates = do- let base :: AxisJournal- base = EJ.fromMap $ HM.fromList- [ (("A", 1), 10 :@ (Hat :< Yen))- , (("C", 2), 5 :@ (Not :< Amount))- ]- rhs :: AxisJournal- rhs = EJ.fromMap $ HM.fromList- [ (("A", 1), 3 :@ (Not :< Amount))- , (("B", 1), 7 :@ (Hat :< Yen))- ]- ledger = base .+ rhs- expected = EJ.filterWithNote (\(_, t') _ -> t' == 1) ledger- actual = EJ.filterByAxis 1 (EJ.NoteAxisKey (1 :: Int)) ledger- assertEqual "Journal.filterByAxis works after append updates"- (EJ.toMap expected)- (EJ.toMap actual)---- ================================================================--- Transfer regression tests--- ================================================================--type TransferAlg = EA.Alg Double SimHatBase2-type TransferJournal = EJ.Journal String Double SimHatBase2--transferAlgSample :: TransferAlg-transferAlgSample = EA.fromList- [ 7 :@ Not :<(WageExpenditure, 1, 1, Yen)- , 3 :@ Hat :<(Depreciation, 2, 2, Yen)- , 11 :@ Not :<(Purchases, 3, 3, Yen)- , 13 :@ Not :<(ValueAdded, 1, 2, Yen)- , 17 :@ Hat :<(Sales, 2, 1, Yen)- , 19 :@ Not :<(InterestEarned, 4, 4, Yen)- , 23 :@ Hat :<(InterestExpense, 5, 5, Yen)- , 29 :@ Not :<(TaxesRevenue, 2, 2, Yen)- , 31 :@ Hat :<(TaxesExpense, 3, 3, Yen)- , 37 :@ Not :<(WageEarned, 6, 6, Yen)- , 41 :@ Hat :<(ConsumptionExpenditure, 6, 6, Yen)- , 43 :@ Not :<(CentralBankPaymentIncome, 1, 1, Yen)- , 47 :@ Hat :<(CentralBankPaymentExpense, 1, 1, Yen)- , 53 :@ Not :<(GrossProfit, 7, 7, Yen)- , 59 :@ Hat :<(OrdinaryProfit, 8, 8, Yen)- , 61 :@ Not :<(Cash, 1, 1, Yen)- ]--transferJournalSample :: TransferJournal-transferJournalSample = EJ.fromList- [ transferAlgSample .| "A"- , ((5 :@ Not :<(Sales, 2, 1, Yen)) .+ (2 :@ Hat :<(WageExpenditure, 1, 1, Yen))) .| "B"- , ((3 :@ Hat :<(TaxesExpense, 3, 3, Yen)) .+ (4 :@ Not :<(InterestEarned, 4, 4, Yen))) .| "C"- ]--testFinalStockTransferAlgEquivalence :: IO ()-testFinalStockTransferAlgEquivalence = do- let ref =- (.-)- . EAT.retainedEarningTransfer- . EAT.ordinaryProfitTransfer- . EAT.grossProfitTransfer- $ transferAlgSample- actual = EAT.finalStockTransfer transferAlgSample- assertEqual "Algebra.finalStockTransfer matches composed transfer" ref actual--testFinalStockTransferJournalEquivalence :: IO ()-testFinalStockTransferJournalEquivalence = do- let ref =- (.-)- . EJT.retainedEarningTransfer- . EJT.ordinaryProfitTransfer- . EJT.grossProfitTransfer- $ transferJournalSample- actual = EJT.finalStockTransfer transferJournalSample- assertEqual "Journal.finalStockTransfer matches composed transfer" (EJ.toMap ref) (EJ.toMap actual)--testFinalStockTransferAggregatedAlias :: IO ()-testFinalStockTransferAggregatedAlias = do- let twoNotes = EJ.fromList- [ ((5 .@ (Not :< (Sales, 2, 1, Yen)))- .+ (2 .@ (Hat :< (WageExpenditure, 1, 1, Yen)))) .| "A"- , ((7 .@ (Hat :< (InterestExpense, 5, 5, Yen)))- .+ (3 .@ (Not :< (InterestEarned, 4, 4, Yen)))) .| "B"- ] :: TransferJournal- assertEqual "Journal.finalStockTransferAggregated matches finalStockTransfer"- (EJ.toMap (EJT.finalStockTransfer twoNotes))- (EJ.toMap (EJT.finalStockTransferAggregated twoNotes))--type FinalStockProbe = EA.Alg Double (HatBase AccountTitles)---- | Classify the observable image of the same one-posting probe used to--- generate @test/fixtures/pre-vocab/finalstock.tsv@.------ Complexity: O(1)-finalStockProbeRule :: AccountTitles -> String-finalStockProbeRule RetainedEarnings = "SELF"-finalStockProbeRule title- | actual == show probe = "Nothing"- | actual == show (1 .@ Not :< RetainedEarnings :: FinalStockProbe) = "Keep"- | actual == show (1 .@ Hat :< RetainedEarnings :: FinalStockProbe) = "Flip"- | otherwise = "UNEXPECTED:" ++ actual- where- probe = 1 .@ Not :< title :: FinalStockProbe- actual = show (EAT.finalStockTransfer probe)---- | The pre-vocabulary fixture is frozen. The only permitted behavioral--- changes are the concrete Cost/Revenue accounts that the former SNA-era--- title case split omitted. Aggregate NetIncome/NetLoss remain explicit--- registry overrides and therefore do not occur in this list.-finalStockExpectedClosedDiff :: [AccountTitles]-finalStockExpectedClosedDiff =- [ AmortizationExpense- , SalesCost- , BusinessTrip- , Commutation- , UtilitiesExpense- , RentExpense- , AdvertisingExpense- , DeliveryExpenses- , SuppliesExpenses- , MiscellaneousExpenses- , NationalBondInterestEarned- , DepositInterestEarned- , ReceiptFee- , RentalIncome- , EquityInEarningsOfInvestee- , ProvisionForDoubtfulAccounts- , BadDebtLoss- , LossOnSalesOfFixedAssets- , LossOnSalesOfNotesReceivable- , PaymentFees- , MiscellaneousLoss- , CorporateIncomeTaxes- , CommunicationExpenses- , GainOnSalesOfFixedAssets- , RecoveryOfBadDebts- , MiscellaneousIncome- , ReversalOfAllowanceForDoubtfulAccounts- ]---- V-Land 1: finalStockRule の全域を独立参照式 (division + contra を明示分岐)--- と突き合わせ, 方向 (Keep/Flip) まで固定する。contra P/L (将来の売上割戻等)--- では division 基準と逆になることをこの式が明文化する。-testFinalStockRuleReference :: IO ()-testFinalStockRuleReference = mapM_ check Registry.concreteAccountTitles- where- check RetainedEarnings = pure ()- check t = assertEqual ("finalStockRule reference: " ++ show t)- (expected t) (finalStockProbeRule t)- expected t- | t `L.elem`- [ NetIncome- , NetLoss- , IncomeSummary- , NetIncomeAttributableToNCI- , NetLossAttributableToNCI- ] = "Nothing"- | contra && div_ == Revenue = "Flip"- | contra && div_ == Cost = "Keep"- | div_ == Revenue = "Keep"- | div_ == Cost = "Flip"- | otherwise = "Nothing"- where- div_ = classifyAccountDivision t- contra = Registry.classifyAccountContra t--testFinalStockRegistryClosedDiff :: IO ()-testFinalStockRegistryClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-vocab/finalstock.tsv"- let fixtureLines =- [ line- | line <- T.lines fixture- , not (T.null line)- , not (T.isPrefixOf (T.pack "#") line)- ]- parseFixtureLine line = case T.splitOn (T.pack "\t") line of- [titleText, oldRule, _division] -> case EC.parseAccountTitle titleText of- Right title -> (title, T.unpack oldRule)- Left err -> error ("invalid final-stock fixture title: " ++ show err)- fields -> error ("invalid final-stock fixture row: " ++ show fields)- fixtureRows = L.map parseFixtureLine fixtureLines- actualDiff =- [ title- | (title, oldRule) <- fixtureRows- , title /= RetainedEarnings- , finalStockProbeRule title /= oldRule- ]- assertEqual "final-stock fixture covers all 116 concrete account titles"- 116 (L.length fixtureRows)- assertEqual "final-stock registry closed diff = 27 formerly omitted accounts"- finalStockExpectedClosedDiff actualDiff- assertEqual "final-stock aggregate overrides remain open"- ["Nothing", "Nothing", "Nothing", "Nothing", "Nothing"]- [ finalStockProbeRule NetIncome- , finalStockProbeRule NetLoss- , finalStockProbeRule IncomeSummary- , finalStockProbeRule NetIncomeAttributableToNCI- , finalStockProbeRule NetLossAttributableToNCI- ]---- ================================================================--- V-Land 2 scaffolding (語彙拡張の受理条件, レビュー非依存):--- pre-vland2 fixture (tools/DumpVocabGolden.hs で生成, commit 85d6a7f に pin)--- に対する Enum 挿入規律 pin と意味関数 closed-diff。--- ================================================================---- | V-Land 2 で既存意味が変わってよい科目の閉リスト。--- scaffold 時点 (constructor 追加前) は空。外部レビュー裁定で既存科目の分類が--- 変わる場合 (例: 有価証券 4 分類分解に伴う 'Securities' の再定義) は--- ここに列挙して閉じる — 列挙外の意味変化は fail する。-vocabSemanticsExpectedClosedDiff :: [AccountTitles]-vocabSemanticsExpectedClosedDiff = []---- | 挿入規律 pin: 語彙拡張は「既存 concrete constructor の Enum 序数を 1 つも--- 動かさず, 新規は最大既存 concrete 序数と wildcard の間にのみ挿入し,--- wildcard ('AccountTitle') は maxBound のまま」でなければならない--- (Binary Word16 直列化互換と既存 fixture 世代の解釈可能性の要)。-testVocabOrdinalPin :: IO ()-testVocabOrdinalPin = do- fixture <- TIO.readFile "test/fixtures/pre-vland2/ordinals.tsv"- let parseOrd line = case T.splitOn (T.pack "\t") line of- [name, ordText] -> (name, read (T.unpack ordText) :: Int)- fields -> error ("invalid pre-vland2 ordinals row: " ++ show fields)- rows =- [ parseOrd line- | line <- T.lines fixture- , not (T.null line)- , not (T.isPrefixOf (T.pack "#") line)- ]- current = M.fromList- [ (T.pack (show t), fromEnum t)- | t <- [minBound .. maxBound] :: [AccountTitles] ]- wildcardName = T.pack (show (AccountTitle :: AccountTitles))- pinnedConcrete = [ r | r@(n, _) <- rows, n /= wildcardName ]- moved =- [ (n, o, M.lookup n current)- | (n, o) <- pinnedConcrete- , M.lookup n current /= Just o ]- assertEqual "vocab ordinal pin: fixture rows = 117 (116 concrete + wildcard)"- 117 (L.length rows)- assertEqual "vocab ordinal pin: no pinned concrete ordinal moved" [] moved- assertEqual "vocab ordinal pin: wildcard is maxBound"- (fromEnum (maxBound :: AccountTitles))- (fromEnum (AccountTitle :: AccountTitles))- let maxPinned = L.maximum [ o | (_, o) <- pinnedConcrete ]- pinnedNames = M.fromList [ (n, ()) | (n, _) <- rows ]- misplaced =- [ (n, o)- | (n, o) <- M.toList current- , not (M.member n pinnedNames)- , not (o > maxPinned && o < fromEnum (maxBound :: AccountTitles)) ]- assertEqual "vocab ordinal pin: new constructors sit between max pinned and wildcard"- [] misplaced- assertEqual "vocab ordinal pin: appended constructor ordinals and wildcard"- [232, 233, 234, 235, 236, 237, 238, 239, 240]- (L.map fromEnum- [ ConsumptionTaxRefundReceivable- , PropertyTaxPayable- , DepositsReceivedFromOfficers- , EntertainmentExpenses- , MeetingExpenses- , NewspaperBooksExpenses- , RawMaterials- , GoodsInTransit- , AccountTitle- ])- -- concreteAccountTitles は wildcard 以外の全 constructor を被覆すること。- -- 現行の hardcoded 上限 ([Cash .. ReversalOfAllowanceForDoubtfulAccounts]) は- -- 挿入後に新規科目が漏れるため, この assert が V-Land 2 に- -- filter (/= wildcard) [minBound ..] への導出化を強制する。- assertEqual "vocab ordinal pin: concreteAccountTitles covers all non-wildcard constructors"- (L.filter (/= (AccountTitle :: AccountTitles)) [minBound .. maxBound])- Registry.concreteAccountTitles---- | 意味関数 closed-diff: 既存 116 科目の (division / isContra / whichSide--- Not\/Hat / whatPIMO / fixedCurrent / finalStock probe) は,--- 'vocabSemanticsExpectedClosedDiff' に列挙された科目を除き--- pre-vland2 fixture と行単位で一致しなければならない。-testPreVland2SemanticsClosedDiff :: IO ()-testPreVland2SemanticsClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-vland2/semantics.tsv"- let byName = M.fromList- [ (T.pack (show t), t)- | t <- [minBound .. maxBound] :: [AccountTitles] ]- currentRow t =- let nb = Not :< t :: HatBase AccountTitles- hb = Hat :< t :: HatBase AccountTitles- in T.intercalate (T.pack "\t")- [ T.pack (show t)- , T.pack (show (whatDiv nb))- , T.pack (show (Registry.classifyAccountContra t))- , T.pack (show (whichSide nb))- , T.pack (show (whichSide hb))- , T.pack (show (whatPIMO nb))- , T.pack (show (fixedCurrent nb))- , T.pack (finalStockProbeRule t)- ]- rows =- [ line- | line <- T.lines fixture- , not (T.null line)- , not (T.isPrefixOf (T.pack "#") line) ]- titleOf line = case T.splitOn (T.pack "\t") line of- (name:_) -> case M.lookup name byName of- Just t -> t- Nothing -> error ("pre-vland2 semantics: unknown title " ++ T.unpack name)- [] -> error "pre-vland2 semantics: empty row"- actualDiff =- [ titleOf line | line <- rows, currentRow (titleOf line) /= line ]- assertEqual "pre-vland2 semantics fixture covers all 116 concrete account titles"- 116 (L.length rows)- assertEqual "pre-vland2 semantics closed diff"- vocabSemanticsExpectedClosedDiff actualDiff---- | R1 sentinel: a /balanced/ ledger (credit total == debit total, net income--- zero) makes 'diffRL' report the wildcard 'Side'. Before the fix,--- 'incomeSummaryAccount' matched only Credit/Debit and crashed with--- "Non-exhaustive patterns". Run every closing-transfer function (Alg and--- Journal) over a balanced ledger and force the result; none may throw.------ The ledger pairs equal Sales (Revenue/Credit) and WageExpenditure--- (Cost/Debit) amounts so @decR == decL@ (balanced).-balancedAlgSample :: TransferAlg-balancedAlgSample = EA.fromList- [ 50 :@ Not :<(Sales, 1, 1, Yen) -- credit (revenue)- , 50 :@ Not :<(WageExpenditure, 1, 1, Yen) -- debit (cost)- , 20 :@ Not :<(Purchases, 2, 2, Yen) -- debit (cost)- , 20 :@ Not :<(InterestEarned, 2, 2, Yen) -- credit (revenue)- ]--balancedJournalSample :: TransferJournal-balancedJournalSample = EJ.fromList- [ balancedAlgSample .| "A"- , ((10 :@ Not :<(Sales, 3, 3, Yen)) .+ (10 :@ Not :<(Purchases, 3, 3, Yen))) .| "B"- ]--testIncomeSummaryBalancedNoCrash :: IO ()-testIncomeSummaryBalancedNoCrash = do- -- confirm the ledger really is balanced (triggers the wildcard Side)- case EA.diffRL balancedAlgSample of- (Side, _) -> return ()- other -> do putStrLn ("[FAIL] balanced sample not balanced: " ++ show (fst other))- exitFailure- let algFns =- [ ("incomeSummaryAccount", EAT.incomeSummaryAccount)- , ("netIncomeTransfer", EAT.netIncomeTransfer)- , ("grossProfitTransfer", EAT.grossProfitTransfer)- , ("ordinaryProfitTransfer",EAT.ordinaryProfitTransfer)- , ("retainedEarningTransfer",EAT.retainedEarningTransfer)- , ("finalStockTransfer", EAT.finalStockTransfer)- ]- jFns =- [ ("incomeSummaryAccount", EJT.incomeSummaryAccount)- , ("netIncomeTransfer", EJT.netIncomeTransfer)- , ("grossProfitTransfer", EJT.grossProfitTransfer)- , ("ordinaryProfitTransfer",EJT.ordinaryProfitTransfer)- , ("retainedEarningTransfer",EJT.retainedEarningTransfer)- , ("finalStockTransfer", EJT.finalStockTransfer)- ]- forM_ algFns $ \(nm, f) -> do- r <- try (evaluate (EA.norm (f balancedAlgSample)))- :: IO (Either SomeException Double)- case r of- Right _ -> return ()- Left e -> do putStrLn ("[FAIL] Alg." ++ nm ++ " threw on balanced ledger: " ++ show e)- exitFailure- forM_ jFns $ \(nm, f) -> do- r <- try (evaluate (EA.norm (EJ.toAlg (f balancedJournalSample))))- :: IO (Either SomeException Double)- case r of- Right _ -> return ()- Left e -> do putStrLn ("[FAIL] Journal." ++ nm ++ " threw on balanced ledger: " ++ show e)- exitFailure- putStrLn "[PASS] all closing transfers identity-safe on balanced ledger (R1)"--type SpillRestoreJournal = EJ.Journal (String, Int) Double (HatBase CountUnit)---- | Design-review C4: the spill/eviction decision logic is single-sourced in--- 'ES.stepBackWith' / 'ES.spillDeleteDecision' (previously duplicated inline--- in the classic spill loop and in Lite's retention loop). Pin the decision--- table and the equivalence with Lite's former @backByTerms@.-testSpillDecisionSingleSource :: IO ()-testSpillDecisionSingleSource = do- assertEqual "stepBackWith pred 3 10" (7 :: Int) (ES.stepBackWith pred 3 10)- assertEqual "stepBackWith is id for n <= 0" (10 :: Int) (ES.stepBackWith pred 0 10)- assertEqual "stepBackWith is id for negative n" (10 :: Int) (ES.stepBackWith pred (-1) 10)- assertEqual "NoDelete evicts nothing"- Nothing (ES.spillDeleteDecision pred (ES.NoDelete :: ES.SpillDeletePolicy Int) (1, 10))- assertEqual "DeleteSpilledChunk evicts exactly the chunk"- (Just (1, 10)) (ES.spillDeleteDecision pred ES.DeleteSpilledChunk (1 :: Int, 10))- assertEqual "KeepRecentTerms 3 keeps the trailing window"- (Just (1, 7)) (ES.spillDeleteDecision pred (ES.KeepRecentTerms 3) (1 :: Int, 10))- assertEqual "KeepRecentTerms covering the chunk evicts nothing"- Nothing (ES.spillDeleteDecision pred (ES.KeepRecentTerms 12) (1 :: Int, 10))- -- Lite boundary equivalence: former backByTerms w t == stepBackWith pred w t- let backByTermsRef w t = let go n x | n <= (0 :: Int) = x- | otherwise = go (n - 1) (pred x)- in go w t- forM_ [(0, 5), (1, 5), (3, 5), (7, 5)] $ \(w, t) ->- assertEqual ("Lite boundary equivalence w=" ++ show w)- (backByTermsRef w t) (ES.stepBackWith pred w (t :: Int))--testRestoreJournalFromBinarySpill :: IO ()-testRestoreJournalFromBinarySpill = do- let spillPath = "/tmp/exchangealgebra_spill_restore_test.bin"- chunk1 :: SpillRestoreJournal- chunk1 = EJ.fromList- [ (1 .@ (Hat :< Yen)) .| ("A", 1)- , (2 .@ (Not :< Amount)) .| ("B", 2)- ]- chunk2 :: SpillRestoreJournal- chunk2 = (3 .@ (Hat :< Yen)) .| ("C", 3)- currentLedger :: SpillRestoreJournal- currentLedger = EJ.fromList- [ (4 .@ (Not :< Amount)) .| ("Tail", 4)- , (8 .@ (Hat :< Yen)) .| ("AlreadySpilled", 2)- ]- expected :: SpillRestoreJournal- expected = chunk1 .+ chunk2 .+ ((4 .@ (Not :< Amount)) .| ("Tail", 4))-- removeSpillTestFile spillPath- withFile spillPath WriteMode $ \h -> do- ES.defaultBinarySpillWriter h (1 :: Int, 2 :: Int) chunk1- ES.defaultBinarySpillWriter h (3 :: Int, 3 :: Int) chunk2-- actual <- restoreJournalFromBinarySpill spillPath snd currentLedger- assertEqual "Write.restoreJournalFromBinarySpill merges spill + tail remainder"- (EJ.toMap expected)- (EJ.toMap actual)- removeSpillTestFile spillPath--removeSpillTestFile :: FilePath -> IO ()-removeSpillTestFile path = do- _ <- try (removeFile path) :: IO (Either SomeException ())- pure ()--writeSpillTestChunks- :: FilePath- -> [((Int, Int), SpillRestoreJournal)]- -> IO ()-writeSpillTestChunks path chunks = do- removeSpillTestFile path- withFile path WriteMode $ \h ->- forM_ chunks $ \(termRange, chunk) ->- ES.defaultBinarySpillWriter h termRange chunk--spillCheckedChunk1 :: SpillRestoreJournal-spillCheckedChunk1 = EJ.fromList- [ (1 .@ (Hat :< Yen)) .| ("A", 1)- , (2 .@ (Not :< Amount)) .| ("B", 2)- ]--spillCheckedChunk2 :: SpillRestoreJournal-spillCheckedChunk2 = (3 .@ (Hat :< Yen)) .| ("C", 3)--spillCheckedCurrent :: SpillRestoreJournal-spillCheckedCurrent = EJ.fromList- [ (4 .@ (Not :< Amount)) .| ("Tail", 4)- , (8 .@ (Hat :< Yen)) .| ("AlreadySpilled", 2)- ]--spillCheckedExpected :: SpillRestoreJournal-spillCheckedExpected =- spillCheckedChunk1 .+ spillCheckedChunk2- .+ ((4 .@ (Not :< Amount)) .| ("Tail", 4))--testSpillCheckedReaderWellFormed :: IO ()-testSpillCheckedReaderWellFormed = do- let path = "/tmp/exchangealgebra_spill_checked_well_formed.bin"- chunks = [((1, 2), spillCheckedChunk1), ((3, 3), spillCheckedChunk2)]- writeSpillTestChunks path chunks- readResult <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- case readResult of- Left err -> assertEqual "checked spill reader accepts well-formed chunks"- "Right with two chunks" (ES.renderSpillReadError err)- Right decoded -> assertEqual "checked spill reader returns both chunks"- 2 (L.length decoded)- restored <- restoreJournalFromBinarySpillChecked path snd spillCheckedCurrent- case restored of- Left err -> assertEqual "checked spill restore accepts well-formed chunks"- "Right restored ledger" (ES.renderSpillReadError err)- Right actual -> assertEqual "checked spill restore merges spill + tail remainder"- (EJ.toMap spillCheckedExpected) (EJ.toMap actual)- removeSpillTestFile path--testSpillCheckedReaderTruncated :: IO ()-testSpillCheckedReaderTruncated = do- let path = "/tmp/exchangealgebra_spill_checked_truncated.bin"- encodedChunk2 = Binary.encode- ((3 :: Int, 3 :: Int), spillCheckedChunk2)- truncatedChunk2 = BL.take (BL.length encodedChunk2 `div` 2) encodedChunk2- removeSpillTestFile path- withFile path WriteMode $ \h -> do- ES.defaultBinarySpillWriter h (1 :: Int, 2 :: Int) spillCheckedChunk1- BL.hPut h truncatedChunk2- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- case result of- Left (ES.SpillDecodeFailure offset chunks _) -> do- assertEqual "truncated spill failure follows first chunk" True (offset > 0)- assertEqual "truncated spill reports decoded chunk count" 1 chunks- other -> assertEqual "truncated spill is a decode failure"- "SpillDecodeFailure" (show other)- caught <- try- (restoreJournalFromBinarySpill path snd (mempty :: SpillRestoreJournal))- :: IO (Either ErrorCall SpillRestoreJournal)- case caught of- Left _ -> putStrLn "[PASS] unchecked spill restore raises ErrorCall"- Right _ -> assertEqual "unchecked spill restore raises ErrorCall" True False- removeSpillTestFile path--testSpillCheckedReaderStaleAppend :: IO ()-testSpillCheckedReaderStaleAppend = do- let path = "/tmp/exchangealgebra_spill_checked_stale_append.bin"- writeSpillTestChunks path- [ ((1, 2), spillCheckedChunk1)- , ((3, 3), spillCheckedChunk2)- , ((1, 2), spillCheckedChunk1)- ]- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- assertEqual "checked spill reader rejects stale append"- (Left (ES.SpillRangeError ES.ChunkOutOfOrder (3, 3) (1, 2))) (fmap (fmap fst) result)- removeSpillTestFile path--testSpillCheckedReaderOverlap :: IO ()-testSpillCheckedReaderOverlap = do- let path = "/tmp/exchangealgebra_spill_checked_overlap.bin"- writeSpillTestChunks path- [((1, 3), spillCheckedChunk1), ((2, 4), spillCheckedChunk2)]- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- assertEqual "checked spill reader rejects overlap"- (Left (ES.SpillRangeError ES.ChunkOverlap (1, 3) (2, 4))) (fmap (fmap fst) result)- removeSpillTestFile path--testSpillCheckedReaderGap :: IO ()-testSpillCheckedReaderGap = do- let path = "/tmp/exchangealgebra_spill_checked_gap.bin"- writeSpillTestChunks path- [((1, 2), spillCheckedChunk1), ((4, 4), spillCheckedChunk2)]- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- assertEqual "checked spill reader rejects gap"- (Left (ES.SpillRangeError ES.ChunkGap (1, 2) (4, 4))) (fmap (fmap fst) result)- removeSpillTestFile path--testSpillCheckedReaderEmptyRange :: IO ()-testSpillCheckedReaderEmptyRange = do- let path = "/tmp/exchangealgebra_spill_checked_empty_range.bin"- writeSpillTestChunks path [((3, 1), spillCheckedChunk1)]- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- assertEqual "checked spill reader rejects empty range"- (Left (ES.SpillEmptyRange (3, 1))) (fmap (fmap fst) result)- removeSpillTestFile path--testSpillCheckedReaderEmptyFile :: IO ()-testSpillCheckedReaderEmptyFile = do- let path = "/tmp/exchangealgebra_spill_checked_empty_file.bin"- removeSpillTestFile path- withFile path WriteMode $ \_ -> pure ()- result <- ES.readBinarySpillFileChecked path- :: IO (Either (ES.SpillReadError Int) [((Int, Int), SpillRestoreJournal)])- assertEqual "checked spill reader accepts empty file" (Right []) (fmap (fmap fst) result)- removeSpillTestFile path---- ================================================================--- SimulateEx1 reproduction (default scenario only, no parallelism)--- ================================================================--type SimTerm = Int--instance StateTime SimTerm where- initTerm = 1- lastTerm = 100-data SimInitVar = SimInitVar- { _simInitStock :: Double- , _simSteadyProduction :: Double- , _simInhouseRatio :: Double- } deriving (Eq, Show)--instance InitVariables SimInitVar where--data SimEvent- = SimSalesPurchase- | SimProduction- | SimPlank- deriving (Ord, Show, Enum, Eq, Bounded, Generic)--instance Hashable SimEvent where--instance Note SimEvent where- plank = SimPlank--instance Event SimEvent where--type SimCompany = Int--instance Element SimCompany where- wildcard = -1--instance BaseClass SimCompany where--simFstC, simLastC :: SimCompany-simFstC = 1-simLastC = 6--simCompanies :: [SimCompany]-simCompanies = [simFstC .. simLastC]--type SimHatBase2 = HatBase (AccountTitles, SimCompany, SimCompany, CountUnit)--instance ExBaseClass SimHatBase2 where- getAccountTitle (h :< (a, _, _, _)) = a- setAccountTitle (h :< (_, c, e, u)) b = h :< (b, c, e, u)---- Accounting value type is MoneyDecimal (exact): ledger arithmetic is exact and--- construction-order-independent. ABM parameters / input coefficients / random--- draws remain Double and are converted (realToFrac) at the boundary where they--- enter the ledger; reported stock/profit convert back to Double.-type SimTransaction = EJ.Journal (SimEvent, SimTerm) MoneyDecimal SimHatBase2--simCompressPreviousTerm :: SimTerm -> SimTransaction -> SimTransaction-simCompressPreviousTerm t le =- EJ.fromMap $- L.foldl' (\acc ev -> HM.adjust compress (ev, t) acc)- (EJ.toMap le)- [fstEvent .. lastEvent]--newtype SimLedger s = SimLedger (STRef s SimTransaction)--instance UpdatableSTRef SimLedger s SimTransaction where- _unwrapURef (SimLedger x) = x- _wrapURef x = SimLedger x--simInitLedger :: Double -> ST s (SimLedger s)-simInitLedger d = newURef $ EJ.fromList- [ realToFrac d :@ Not :<(Products, e, e, Amount) .| (plank, initTerm) -- Double param -> MoneyDecimal- | e <- simCompanies- ]--instance Updatable SimTerm SimInitVar SimLedger s where- type Inner SimLedger s = STRef s SimTransaction- unwrap = _unwrapURef- initialize _ _ e = simInitLedger (_simInitStock e)- updatePattern _ = return Modify- modify _ t _ x = do- le <- readURef x- let added = EJ.gather (plank, t)- $ EJT.finalStockTransfer- $ (.-) $ simTermJournal (t - 1) le- next = simCompressPreviousTerm (t - 1) (le .+ added)- writeURef x next--type SimInputCoefficient = Double--newtype SimICTable s = SimICTable (STArray s (SimCompany, SimCompany) SimInputCoefficient)--instance UpdatableSTArray SimICTable s (SimCompany, SimCompany) SimInputCoefficient where- _unwrapUArray (SimICTable arr) = arr- _wrapUArray arr = SimICTable arr--simGenerateRandomList :: StdGen -> Int -> ([Double], StdGen)-simGenerateRandomList g n =- let (xs, g') = runState (replicateM n (state (randomR (0, 1.0))))- (updateGen g 1000)- ys = L.map (\v -> if v < 0.1 then 0 else v) xs- in (ys, g')--simInitTermCoefficients :: StdGen -> Double -> M.Map SimCompany [SimInputCoefficient]-simInitTermCoefficients g inhouseRatio =- fst $ L.foldl' buildRow (M.empty, g) simCompanies- where- buildRow (acc, g0) c2 =- let (row, g1) = generateRow g0- in (M.insert c2 row acc, g1)- generateRow g0 =- let (vals, g1) = simGenerateRandomList g0 simLastC- total = sum vals- normalized = L.map (\v -> (v / total) * inhouseRatio) vals- in (normalized, g1)--simInitICTables :: StdGen -> Double -> ST s (SimICTable s)-simInitICTables g inhouseRatio = do- arr <- newUArray ((simFstC, simFstC), (simLastC, simLastC)) 0- let termCoefficients = simInitTermCoefficients g inhouseRatio- forM_ simCompanies $ \c2 -> do- let row = termCoefficients M.! c2- forM_ (zip simCompanies row) $ \(c1, coef) ->- writeUArray arr (c1, c2) coef- return arr--instance Updatable SimTerm SimInitVar SimICTable s where- type Inner SimICTable s = STArray s (SimCompany, SimCompany) SimInputCoefficient- unwrap (SimICTable a) = a- initialize g _ e = simInitICTables g (_simInhouseRatio e)- updatePattern _ = return DoNothing--type SimSteadyProd = Double--newtype SimSP s = SimSP (STRef s SimSteadyProd)--instance UpdatableSTRef SimSP s SimSteadyProd where- _unwrapURef (SimSP x) = x- _wrapURef x = SimSP x--instance Updatable SimTerm SimInitVar SimSP s where- type Inner SimSP s = STRef s SimSteadyProd- unwrap = _unwrapURef- initialize _ _ e = newURef (_simSteadyProduction e)- updatePattern _ = return DoNothing--data SimWorld s = SimWorld- { _simLedger :: SimLedger s- , _simIcs :: SimICTable s- , _simSp :: SimSP s- } deriving (Generic)---- helper functions--simTermJournal :: SimTerm -> SimTransaction -> SimTransaction-simTermJournal t = EJ.filterWithNote (\(_, t') _ -> t' == t)--simGetOneProduction :: SimWorld s -> SimTerm -> SimCompany -> ST s SimTransaction-simGetOneProduction wld t c = do- let arr = _simIcs wld- inputs <- mapM (\c2 -> do- coef <- readUArray arr (c2, c)- return $ realToFrac coef :@ Hat :<(Products, c2, c, Amount) .| (SimProduction, t) -- Double coef -> MoneyDecimal- ) simCompanies- let totalInput = EJ.fromList inputs- result = (1 :@ Not :<(Products, c, c, Amount) .| (SimProduction, t)) .+ totalInput- return result--simJournal :: SimWorld s -> SimTransaction -> ST s ()-simJournal _ Zero = return ()-simJournal wld js = modifyURef (_simLedger wld) (\x -> x .+ js)---- Values come from the MoneyDecimal ledger (via EA.toList), so the shortage map is--- MoneyDecimal-valued; no conversion is needed and the amounts re-enter the ledger exactly.-simBuildShortageMap :: SimTerm -> SimTransaction -> M.Map (SimCompany, SimCompany) MoneyDecimal-simBuildShortageMap t le =- let termAlg = EJ.toAlg $ (.-) $ simTermJournal t le- in L.foldl' go M.empty (EA.toList termAlg)- where- go acc (v :@ (Hat :< (Products, j, i, Amount))) = M.insertWith (+) (i, j) v acc- go acc _ = acc--simPurchases :: SimTerm -> SimWorld s -> ST s SimTransaction-simPurchases t wld = do- le <- readURef (_simLedger wld)- let shortageMap = simBuildShortageMap t le- o i j = M.findWithDefault 0 (i, j) shortageMap- return $ sigma simCompanies $ \i- -> sigma (simCompanies L.\\ [i]) $ \j- -> (o i j) :@ Not :<(Products, j, i, Amount)- .+ (o i j) :@ Hat :<(Cash, (.#), i, Yen)- .+ (o i j) :@ Not :<(Purchases, (.#), i, Yen)- .+ (o i j) :@ Not :<(Cash, (.#), j, Yen)- .+ (o i j) :@ Not :<(Sales, (.#), j, Yen)- .+ (o i j) :@ Hat :<(Products, j, j, Amount)- .| (SimSalesPurchase, t)--instance StateSpace SimTerm SimInitVar SimEvent SimWorld s where- event = simEvent--simEvent :: SimWorld s -> SimTerm -> SimEvent -> ST s ()--simEvent wld t SimSalesPurchase = do- toAdd <- simPurchases t wld- simJournal wld toAdd--simEvent wld t SimProduction = do- sp <- readURef (_simSp wld)- forM_ simCompanies $ \e1 -> do- op <- simGetOneProduction wld t e1- simJournal wld (realToFrac sp .* op) -- Double steady-production multiplier -> MoneyDecimal scalar--simEvent _ _ SimPlank = return ()--simGetTermStock :: SimWorld s -> SimTerm -> SimCompany -> ST s Double-simGetTermStock wld t e = do- le <- readURef (_simLedger wld)- let tj = (.-) $ simTermJournal t le- plusStock = norm $ EJ.projWithBase [Not :<(Products, e, e, Amount)] tj- minusStock = norm $ EJ.projWithBase [Hat :<(Products, e, e, Amount)] tj- return $ realToFrac (plusStock - minusStock) -- exact MoneyDecimal stock -> Double for reporting--simGetTermGrossProfit :: SimWorld s -> SimTerm -> SimCompany -> ST s Double-simGetTermGrossProfit wld t e = do- le <- readURef (_simLedger wld)- let termTr = simTermJournal t le- tr = EJT.grossProfitTransfer termTr- plus = norm $ EJ.projWithBase [Not :<(GrossProfit, (.#), e, Yen)] tr- minus = norm $ EJ.projWithBase [Hat :<(GrossProfit, (.#), e, Yen)] tr- return $ realToFrac (plus - minus) -- exact MoneyDecimal -> Double for reporting---- ================================================================--- Simulation integration test--- ================================================================--simEps :: Double-simEps = 1e-6--assertSimNear :: String -> Double -> Double -> IO ()-assertSimNear label expected actual- | abs (expected - actual) <= simEps = putStrLn ("[PASS] " ++ label)- | otherwise = do- putStrLn ("[FAIL] " ++ label)- putStrLn (" expected: " ++ show expected)- putStrLn (" actual : " ++ show actual)- exitFailure--testSimulateEx1Default :: IO ()-testSimulateEx1Default = do- let gen = mkStdGen 2025- defaultEnv = SimInitVar- { _simInitStock = 20- , _simInhouseRatio = 0.4- , _simSteadyProduction = 10- }-- wld <- ES.runSimulation gen defaultEnv-- -- Stock at term 1 for each company- stocks1 <- stToIO $ mapM (simGetTermStock wld 1) simCompanies- -- Stock at term 50 for each company- stocks50 <- stToIO $ mapM (simGetTermStock wld 50) simCompanies- -- Stock at term 100 for each company- stocks100 <- stToIO $ mapM (simGetTermStock wld 100) simCompanies- -- Gross profit at term 50 for each company- profits50 <- stToIO $ mapM (simGetTermGrossProfit wld 50) simCompanies-- -- Stock at t=1- assertSimNear "sim1 stock(t=1,c=1)" 28.487224703666264 (stocks1 !! 0)- assertSimNear "sim1 stock(t=1,c=3)" 30.0 (stocks1 !! 2)- assertSimNear "sim1 stock(t=1,c=6)" 30.0 (stocks1 !! 5) -- re-baselined: union zero-base fix removed a phantom self-input- -- Stock at t=50- assertSimNear "sim1 stock(t=50,c=1)" 304.9028131162567 (stocks50 !! 0)- assertSimNear "sim1 stock(t=50,c=4)" 292.4764622201871 (stocks50 !! 3)- -- Stock at t=100- assertSimNear "sim1 stock(t=100,c=1)" 586.9595359862476 (stocks100 !! 0)- assertSimNear "sim1 stock(t=100,c=6)" 767.9605634804993 (stocks100 !! 5) -- re-baselined: union zero-base fix (bug compounded over terms)- -- Gross profit at t=50- assertSimNear "sim1 profit(t=50,c=1)" 0.35886554260018855 (profits50 !! 0)- assertSimNear "sim1 profit(t=50,c=2)" 1.572544209772035 (profits50 !! 1)---- ================================================================--- CSV Write tests--- ================================================================--testCsvTranspose :: IO ()-testCsvTranspose = do- -- Square matrix- let input1 = [ [T.pack "a", T.pack "b"]- , [T.pack "c", T.pack "d"] ]- expected1 = [ [T.pack "a", T.pack "c"]- , [T.pack "b", T.pack "d"] ]- assertEqual "CSV.transpose square matrix" expected1 (EW.csvTranspose input1)-- -- Ragged matrix (shorter rows padded with empty)- let input2 = [ [T.pack "a", T.pack "b", T.pack "c"]- , [T.pack "d"] ]- expected2 = [ [T.pack "a", T.pack "d"]- , [T.pack "b", T.empty]- , [T.pack "c", T.empty] ]- assertEqual "CSV.transpose ragged matrix" expected2 (EW.csvTranspose input2)-- -- Single row- let input3 = [[T.pack "x", T.pack "y", T.pack "z"]]- expected3 = [[T.pack "x"], [T.pack "y"], [T.pack "z"]]- assertEqual "CSV.transpose single row" expected3 (EW.csvTranspose input3)-- -- Empty- assertEqual "CSV.transpose empty" ([] :: [[T.Text]]) (EW.csvTranspose [])--testCsvWriteCSV :: IO ()-testCsvWriteCSV = do- let path = "/tmp/exchangealgebra_csv_test.csv"- input = [ [T.pack "Name", T.pack "Value"]- , [T.pack "Alice", T.pack "100"]- , [T.pack "Bob", T.pack "200"] ]- EW.writeCSV path input- raw <- readFileStrict path- -- Each cell should be quoted- let lns = lines raw- assertEqual "CSV writeCSV line count" 3 (length lns)- assertEqual "CSV writeCSV header" "\"Name\",\"Value\"" (lns !! 0)- assertEqual "CSV writeCSV row 1" "\"Alice\",\"100\"" (lns !! 1)- assertEqual "CSV writeCSV row 2" "\"Bob\",\"200\"" (lns !! 2)- removeFile path--testCsvWriteCSVWithQuotes :: IO ()-testCsvWriteCSVWithQuotes = do- let path = "/tmp/exchangealgebra_csv_quote_test.csv"- input = [[T.pack "say \"hello\"", T.pack "a,b"]]- EW.writeCSV path input- raw <- readFileStrict path- let lns = lines raw- -- Internal quotes should be escaped as ""- assertEqual "CSV writeCSV escapes quotes" "\"say \"\"hello\"\"\",\"a,b\"" (lns !! 0)- removeFile path--testCsvWriteCSVEmpty :: IO ()-testCsvWriteCSVEmpty = do- let path = "/tmp/exchangealgebra_csv_empty_test.csv"- input = [[T.pack "", T.pack "x"]]- EW.writeCSV path input- raw <- readFileStrict path- let lns = lines raw- assertEqual "CSV writeCSV empty cell" "\"\",\"x\"" (lns !! 0)- removeFile path--newtype FuncResultsWorld s = FuncResultsWorld (STRef s Int)--testWriteFuncResultsCsv :: IO ()-testWriteFuncResultsCsv = do- let directPath = "/tmp/exchangealgebra_write_func_results.csv"- contextPath = "/tmp/exchangealgebra_write_func_results_context.csv"- emptyPath = "/tmp/exchangealgebra_write_func_results_empty.csv"- headers = L.map T.pack ["plain", "a,b", "q\"x", "line\nbreak", "carriage\rreturn"]- directFuncs =- zip headers- [ \_ t -> pure (t * 10 + 1 :: Int)- , \_ t -> pure (t * 10 + 2 :: Int)- , \_ t -> pure (t * 10 + 3 :: Int)- , \_ t -> pure (t * 10 + 4 :: Int)- , \_ t -> pure (t * 10 + 5 :: Int)- ]- contextFuncs =- zip headers- [ \t -> pure (t * 10 + 1 :: Int)- , \t -> pure (t * 10 + 2 :: Int)- , \t -> pure (t * 10 + 3 :: Int)- , \t -> pure (t * 10 + 4 :: Int)- , \t -> pure (t * 10 + 5 :: Int)- ]- expected = BL8.pack- ( "Time,plain,\"a,b\",\"q\"\"x\",\"line\nbreak\",\"carriage\rreturn\"\n"- ++ "1,11,12,13,14,15\n"- ++ "2,21,22,23,24,25\n"- )- expectedHeader = BL8.pack- "Time,plain,\"a,b\",\"q\"\"x\",\"line\nbreak\",\"carriage\rreturn\"\n"- removeOutput path = do- _ <- try (removeFile path) :: IO (Either SomeException ())- pure ()-- forM_ [directPath, contextPath, emptyPath] removeOutput- world@(FuncResultsWorld counter) <- stToIO (FuncResultsWorld <$> newSTRef 0)- RenderSimulation.writeFuncResults directFuncs (1 :: Int, 2) world directPath- RenderSimulation.writeFuncResultsWithContext- (\(FuncResultsWorld ref) t -> modifySTRef' ref (+ 1) >> pure t)- contextFuncs- (1 :: Int, 2)- world- contextPath- RenderSimulation.writeFuncResults directFuncs (2 :: Int, 1) world emptyPath-- directBytes <- BL.readFile directPath- contextBytes <- BL.readFile contextPath- emptyBytes <- BL.readFile emptyPath- buildCount <- stToIO (readSTRef counter)- assertEqual "writeFuncResults CSV bytes" expected directBytes- assertEqual "writeFuncResultsWithContext CSV bytes" expected contextBytes- assertEqual "writeFuncResults variants produce identical bytes" directBytes contextBytes- assertEqual "writeFuncResults empty range writes only the header" expectedHeader emptyBytes- assertEqual "writeFuncResultsWithContext builds one context per term" 2 buildCount- forM_ [directPath, contextPath, emptyPath] removeOutput---- ================================================================--- Legacy-generation writer output-pinning tests (design-review C7)------ These pin the exact CSV bytes produced, for small fixed inputs, by the--- "legacy generation" writers (writeBS / writePL / writeJournal /--- writeCompoundTrialBalance / writeAccountOfJournal) as computed by the--- pre-refactor implementation. Their purpose is to let the "generation--- unification" refactor (pure *Rows builder + thin IO wrapper, matching the--- worksheetRows/postClosingTrialBalanceRows/accountLedgerRows style) be--- verified to leave output bit-for-bit unchanged: these must stay green,--- unmodified, across the refactor.--- ================================================================--testWriteBSPinned :: IO ()-testWriteBSPinned = do- let path = "/tmp/exchangealgebra_write_bs_pinned_test.csv"- alg = (100 .@ Not :< Cash)- .+ (60 .@ Not :< LoansPayable)- .+ (40 .@ Not :< CapitalStock)- :: EA.Alg Double (HatBase AccountTitles)- EW.writeBS path alg- raw <- readFileStrict path- removeFile path- let lns = lines raw- assertEqual "writeBS pinned: line count" 5 (length lns)- assertEqual "writeBS pinned: row0 (Asset/Liability headers)"- "\"Asset\",\"\",\"Liability\",\"\"" (lns !! 0)- assertEqual "writeBS pinned: row1 (Cash/LoansPayable)"- "\"Cash\",\"100.0\",\"LoansPayable\",\"60.0\"" (lns !! 1)- assertEqual "writeBS pinned: row2 (Total/Equity header)"- "\"Total\",\"100.0\",\"Equity\",\"\"" (lns !! 2)- assertEqual "writeBS pinned: row3 (CapitalStock)"- "\"\",\"\",\"CapitalStock\",\"40.0\"" (lns !! 3)- assertEqual "writeBS pinned: row4 (grand total)"- "\"\",\"\",\"Total\",\"100.0\"" (lns !! 4)--testWritePLPinned :: IO ()-testWritePLPinned = do- let path = "/tmp/exchangealgebra_write_pl_pinned_test.csv"- alg = (500 .@ Not :< Sales)- .+ (300 .@ Not :< SalesCost)- :: EA.Alg Double (HatBase AccountTitles)- EW.writePL path alg- raw <- readFileStrict path- removeFile path- let lns = lines raw- assertEqual "writePL pinned: line count" 3 (length lns)- assertEqual "writePL pinned: row0 (Cost/Revenue headers)"- "\"Cost\",\"\",\"Revenue\",\"\"" (lns !! 0)- assertEqual "writePL pinned: row1 (SalesCost/Sales)"- "\"SalesCost\",\"300.0\",\"Sales\",\"500.0\"" (lns !! 1)- assertEqual "writePL pinned: row2 (totals)"- "\"Total\",\"500.0\",\"Total\",\"300.0\"" (lns !! 2)--testWriteJournalPinned :: IO ()-testWriteJournalPinned = do- let path = "/tmp/exchangealgebra_write_journal_pinned_test.csv"- d1 = fromGregorian 2024 4 1- d2 = fromGregorian 2024 4 2- d3 = fromGregorian 2024 4 3- getDay' :: HatBase (AccountTitles, Day) -> Day- getDay' (_ :< (_, d)) = d- alg = (100 .@ Not :< (Cash, d1))- .+ (100 .@ Not :< (CapitalStock, d1))- .+ (50 .@ Not :< (Cash, d2))- .+ (50 .@ Not :< (Sales, d2))- .+ (30 .@ Not :< (Cash, d3))- .+ (10 .@ Not :< (AccountsReceivable, d3))- .+ (40 .@ Not :< (Sales, d3))- :: EA.Alg Double (HatBase (AccountTitles, Day))- EW.writeJournal path alg getDay'- raw <- readFileStrict path- removeFile path- let lns = lines raw- assertEqual "writeJournal pinned: line count" 5 (length lns)- assertEqual "writeJournal pinned: header"- "\"Day\",\"Debit\",\"Amount\",\"Credit\",\"Amount\"" (lns !! 0)- assertEqual "writeJournal pinned: day1"- "\"2024-04-01\",\"Cash\",\"100.0\",\"CapitalStock\",\"100.0\"" (lns !! 1)- assertEqual "writeJournal pinned: day2"- "\"2024-04-02\",\"Cash\",\"50.0\",\"Sales\",\"50.0\"" (lns !! 2)- assertEqual "writeJournal pinned: day3 line1 (2 debits vs 1 credit -> toSameLength padding)"- "\"2024-04-03\",\"AccountsReceivable\",\"10.0\",\"Sales\",\"40.0\"" (lns !! 3)- assertEqual "writeJournal pinned: day3 line2 (padded Day/Credit cells empty)"- "\"\",\"Cash\",\"30.0\",\"\",\"\"" (lns !! 4)--testWriteCompoundTrialBalancePinned :: IO ()-testWriteCompoundTrialBalancePinned = do- let path = "/tmp/exchangealgebra_write_ctb_pinned_test.csv"- alg = (100 .@ Not :< Cash)- .+ (60 .@ Not :< LoansPayable)- .+ (40 .@ Not :< CapitalStock)- :: EA.Alg Double (HatBase AccountTitles)- EW.writeCompoundTrialBalance path alg- raw <- readFileStrict path- removeFile path- let lns = lines raw- assertEqual "writeCompoundTrialBalance pinned: line count" 5 (length lns)- assertEqual "writeCompoundTrialBalance pinned: header"- "\"Debit Balance\",\"Debit Total\",\"Account Title\",\"Credit Total\",\"Credit Balance\""- (lns !! 0)- -- NOTE (legacy layout quirk, preserved verbatim): a debit-balance account- -- (Cash: gross debit 100 / credit 0) places its balance figure in the- -- *Credit Balance* column (rightmost), not the *Debit Balance* column,- -- and a credit-balance account (CapitalStock/LoansPayable) places it in- -- *Debit Balance* (leftmost) -- the opposite of the (side,mag) ->- -- (debitCell,creditCell) convention 'sideCells' uses elsewhere- -- (worksheetRows / postClosingTrialBalanceRows). See the Haddock on- -- 'compoundTrialBalanceRows' for why this was kept as explicit case- -- analysis instead of being consolidated onto 'sideCells'.- assertEqual "writeCompoundTrialBalance pinned: Cash (debit-heavy -> Credit Balance col)"- "\"\",\"100.0\",\"Cash\",\"0.0\",\"100.0\"" (lns !! 1)- assertEqual "writeCompoundTrialBalance pinned: CapitalStock (credit-heavy -> Debit Balance col)"- "\"40.0\",\"0.0\",\"CapitalStock\",\"40.0\",\"\"" (lns !! 2)- assertEqual "writeCompoundTrialBalance pinned: LoansPayable (credit-heavy -> Debit Balance col)"- "\"60.0\",\"0.0\",\"LoansPayable\",\"60.0\",\"\"" (lns !! 3)- assertEqual "writeCompoundTrialBalance pinned: totals"- "\"100.0\",\"100.0\",\"Total\",\"100.0\",\"100.0\"" (lns !! 4)--testWriteAccountOfJournalPinned :: IO ()-testWriteAccountOfJournalPinned = do- let path = "/tmp/exchangealgebra_write_aoj_pinned_test.csv"- jrn = ((100 .@ Not :< Cash) .| "sale")- .+ ((40 .@ Hat :< Cash) .| "pay")- :: Journal String Double (HatBase AccountTitles)- EW.writeAccountOfJournal [Cash] path jrn- raw <- readFileStrict path- removeFile path- let lns = lines raw- assertEqual "writeAccountOfJournal pinned: line count" 4 (length lns)- assertEqual "writeAccountOfJournal pinned: title header"- "\"Cash\",\"\",\"\"" (lns !! 0)- assertEqual "writeAccountOfJournal pinned: sub header"- "\"Note\",\"Debit\",\"Credit\"" (lns !! 1)- assertEqual "writeAccountOfJournal pinned: note order (\"pay\" < \"sale\")"- "\"\"\"pay\"\"\",\"\",\"40.0\"" (lns !! 2)- assertEqual "writeAccountOfJournal pinned: sale posting"- "\"\"\"sale\"\"\",\"100.0\",\"\"" (lns !! 3)---- | Regression tests for scale-aware numeric tolerance (WI-11/12/14).--- These exercise large magnitudes that the previous fixed @1e-13@ absolute--- tolerance handled incorrectly (retaining pure rounding noise as a residual);--- small-scale behavior is unchanged. See plans LAZY_EVAL_AUDIT.md s4.6.-testNumericToleranceScaleAware :: IO ()-testNumericToleranceScaleAware = do- assertEqual "nearlyEqScaled: large-scale rounding treated as equal"- True (EA.nearlyEqScaled (1e10 + 0.1 + 0.2) (1e10 + 0.3 :: Double))- assertEqual "isNearlyNum 1e-13: large-scale rounding rejected (documents old flaw)"- False (EA.isNearlyNum (1e10 + 0.1 + 0.2) (1e10 + 0.3) (1e-13 :: Double))- assertEqual "nearlyEqScaled: small-scale noise treated as equal"- True (EA.nearlyEqScaled (0.1 + 0.2) (0.3 :: Double))- assertEqual "nearlyEqScaled: genuine residual kept (not swallowed)"- False (EA.nearlyEqScaled (1e10 + 5.0) (1e10 :: Double))- assertEqual "nearlyEqScaled: NaN guarded (no crash, not equal)"- False (EA.nearlyEqScaled (0/0) (1.0 :: Double))- let big = (1e10 :@ (Hat :< Yen)) .+ (0.1 :@ (Hat :< Yen)) .+ (0.2 :@ (Hat :< Yen))- .+ (1e10 :@ (Not :< Yen)) .+ (0.3 :@ (Not :< Yen)) :: TestAlg- assertEqual "bar cancels balanced large-scale element to Zero"- True (EA.isZero ((.-) big))---- | Smoke test for the exact non-negative decimal value type 'MoneyDecimal' (Stage B).--- The point of an exact value type is that summation is associative, so @norm@ is--- *independent of construction order* — the property that makes the fromList O(N)--- optimization safe (Stage D). Note the raw @Seq@ order (and hence @toMap@/@Eq@)--- still depends on construction; only the numeric results are order-independent.-testMoneyDecimalExactOrderIndependent :: IO ()-testMoneyDecimalExactOrderIndependent = do- assertEqual "MoneyDecimal: 0.1 + 0.2 == 0.3 exactly"- True (0.1 + 0.2 == (0.3 :: MoneyDecimal))- let mk i = ((fromIntegral (i `mod` 7 + 1) :: MoneyDecimal)- :@ ((if even i then Hat else Not) :< ([Yen, Amount] !! (i `mod` 2))))- .| show (i `mod` 150)- xs :: [Journal String MoneyDecimal (HatBase CountUnit)]- xs = [ mk i | i <- [1 .. 400 :: Int] ]- viaFoldr = foldr (.+) mempty xs- viaFoldl = L.foldl' (.+) mempty xs- -- exact ⇒ norm is identical for the two construction orders- assertEqual "MoneyDecimal Journal: norm is construction-order-independent"- (norm viaFoldr) (norm viaFoldl)- -- banker's rounding (round half to even)- assertEqual "bankersRound 0 2.5 = 2 (half to even)" (2 :: MoneyDecimal) (bankersRound 0 2.5)- assertEqual "bankersRound 0 3.5 = 4 (half to even)" (4 :: MoneyDecimal) (bankersRound 0 3.5)- assertEqual "bankersRound 2 0.125 = 0.12 (half to even)" (0.12 :: MoneyDecimal) (bankersRound 2 0.125)---- | Strict file read helper for tests-readFileStrict :: FilePath -> IO String-readFileStrict p = do- bs <- TIO.readFile p- return (T.unpack bs)---- ================================================================--- Main--- ================================================================---- ================================================================--- Redundant-algebra axiom property tests (QuickCheck)------ Encodes the Definition 6 axioms (paper Appendix A) + derived lemmas as--- QuickCheck properties, plus regression generalizations for the union--- zero-base bug and construction-order independence. Property suite, additive.--- ================================================================--type NNAlg = EA.Alg MoneyDecimal (HatBase CountUnit)---- run a QuickCheck property in the existing IO-style harness-quickProp :: Testable p => String -> p -> IO ()-quickProp label p = do- r <- quickCheckWithResult stdArgs { maxSuccess = 200, chatty = False } p- if isSuccess r- then putStrLn ("[PASS] " ++ label)- else do putStrLn ("[FAIL] " ++ label); putStr (output r); exitFailure---- generators: concrete (non-wildcard) bases, intentional collisions-genUnit :: Gen CountUnit-genUnit = elements [Yen, Dollar, Amount]--genSide :: Gen Hat-genSide = elements [Hat, Not]--genBase :: Gen (HatBase CountUnit)-genBase = (:<) <$> genSide <*> genUnit--genNNDouble :: Gen Double -- non-negative, finite-genNNDouble = do- NonNegative x <- arbitrary- if isNaN x || isInfinite x then genNNDouble else pure x--genAlgD :: Gen TestAlg-genAlgD = sized $ \n -> do- k <- choose (0, min 40 n)- ps <- vectorOf k ((,) <$> genNNDouble <*> genBase)- pure (EA.fromList [ v .@ b | (v, b) <- ps ])--genAlgN :: Gen NNAlg-genAlgN = sized $ \n -> do- k <- choose (0, min 40 n)- ps <- vectorOf k ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)- pure (EA.fromList [ v .@ b | (v, b) <- ps ])---- exact per-base signed net (Not +, Hat -) via Rational; the observable--- accounting content. Robust to seq order; catches base misassociation.-netByBase :: (HatVal v, Real v) => EA.Alg v (HatBase CountUnit) -> M.Map CountUnit Rational-netByBase = EA.foldEntries step M.empty- where- step m v b = M.insertWith (+) (part b) (signed v b) m- part (_ :< u) = u- signed v b = if isHat b then negate (toRational v) else toRational v---- ℘ observation: retain each full Hat/Not base and its value multiset, while--- forgetting the representation-level order of the per-side Seq.-observe :: (Ord b, Ord v, HatVal v, HatBaseClass b)- => EA.Alg v b -> M.Map b [v]-observe = fmap L.sort . EA.foldEntries step M.empty- where- step m v b = M.insertWith (++) b [v] m--epsEq :: Double -> Double -> Bool-epsEq a b = abs (a - b) <= 1e-9 * (1 + max (abs a) (abs b))---- ================================================================--- ExchangeAlgebra.Convert.Csv: generic journal CSV reader.--- Read-only round-trip property: a generated list of postings rendered to a--- fixed-schema CSV string parses back to exactly the term built directly by--- journalFromSides (MoneyDecimal = exact, so strict equality, no tolerance).--- ================================================================---- concrete account titles only (no wildcard); use canonical Show names so the--- CSV round-trip does not exercise the ambiguous-alias path.-genAccountTitle :: Gen AccountTitles-genAccountTitle = elements EC.concreteAccountTitles--genSideCsv :: Gen Side-genSideCsv = elements [Debit, Credit]---- non-negative MoneyDecimal with up to 2 decimal places, written exactly as a--- decimal literal (terminating) so scientificAmount parses it back exactly.-genAmountMD :: Gen (MoneyDecimal, T.Text)-genAmountMD = do- whole <- choose (0, 99999) :: Gen Integer- cents <- choose (0, 99) :: Gen Integer- let txt = T.pack (show whole) <> T.pack "." <>- T.pack (let s = show cents in if length s == 1 then '0':s else s)- val = fromRational (toRational whole + toRational cents / 100) :: MoneyDecimal- pure (val, txt)--genPostingCsv :: Gen (Side, AccountTitles, MoneyDecimal, T.Text)-genPostingCsv = do- s <- genSideCsv- a <- genAccountTitle- (v, vtx) <- genAmountMD- pure (s, a, v, vtx)--renderCsv :: [(Side, AccountTitles, MoneyDecimal, T.Text)] -> T.Text-renderCsv rows =- T.unlines (header : L.map line rows)- where- header = T.pack "side,account,amount"- line (s, a, _, vtx) =- T.intercalate (T.pack ",")- [ sideText s, T.pack (show a), vtx ]- sideText Debit = T.pack "debit"- sideText Credit = T.pack "credit"- sideText Side = T.pack "debit" -- unused (generator never yields wildcard)--testConvertCsvRoundTrip :: IO ()-testConvertCsvRoundTrip = do- quickProp "convert-csv: render -> parse is exact (MoneyDecimal)" $- forAll (resize 30 (listOf genPostingCsv)) $ \rows ->- let csv = renderCsv rows- expected = EC.journalFromSides- [ (s, a, v) | (s, a, v, _) <- rows ]- :: EA.Alg MoneyDecimal (HatBase AccountTitles)- parsed = ECsv.parseJournalCsv csv- :: Either EC.ConvError- (EA.Alg MoneyDecimal (HatBase AccountTitles))- in parsed == Right expected-- -- structural guards: bad header, unknown account, negative amount, bad arity.- let badHeader = T.pack "s,a,amt\ndebit,Cash,1\n"- badAcct = T.pack "side,account,amount\ndebit,Goodwill_X,1\n"- badAmt = T.pack "side,account,amount\ndebit,Cash,-1\n"- badArity = T.pack "side,account,amount\ndebit,Cash\n"- run t = ECsv.parseJournalCsv t- :: Either EC.ConvError- (EA.Alg MoneyDecimal (HatBase AccountTitles))- expectLeft label pat t = case run t of- Left e | pat e -> putStrLn ("[PASS] " ++ label)- | otherwise -> do putStrLn ("[FAIL] " ++ label ++ ": wrong error " ++ show e); exitFailure- Right _ -> do putStrLn ("[FAIL] " ++ label ++ ": accepted bad input"); exitFailure- expectLeft "convert-csv: rejects bad header"- (\e -> case e of EC.MalformedCsv _ -> True; _ -> False) badHeader- expectLeft "convert-csv: rejects unknown account"- (\e -> case e of EC.UnknownAccount _ -> True; _ -> False) badAcct- expectLeft "convert-csv: rejects negative amount"- (\e -> case e of EC.BadAmount _ -> True; _ -> False) badAmt- expectLeft "convert-csv: rejects wrong field count"- (\e -> case e of EC.MalformedCsv _ -> True; _ -> False) badArity---- ================================================================--- ExchangeAlgebra.Assist: account descriptions and LLM feedback helpers.--- ================================================================--testAssistDescriptionsDrift :: IO ()-testAssistDescriptionsDrift =- assertEqual "Assist descriptions are the registry projection"- registryProjection AssistDesc.accountDescriptions- where- registryProjection =- [ (title, Registry.asNameEn spec, Registry.asNameJa spec, Registry.asDescription spec)- | title <- Registry.concreteAccountTitles- , Just spec <- [Registry.accountSpec title]- ]--testAssistDescribeAccount :: IO ()-testAssistDescribeAccount = do- let missing =- [ title- | title <- EC.concreteAccountTitles- , Assist.describeAccount title == Nothing- ]- assertEqual "Assist.describeAccount covers every concrete account"- ([] :: [AccountTitles]) missing- assertEqual "Assist.describeAccount rejects wildcard AccountTitle"- Nothing (Assist.describeAccount AccountTitle)--testAssistAllAccountInfos :: IO ()-testAssistAllAccountInfos = do- assertEqual "Assist.allAccountInfos length" 240 (length Assist.allAccountInfos)- assertEqual "Assist.allAccountInfos follows concreteAccountTitles order"- EC.concreteAccountTitles (L.map Assist.aiTitle Assist.allAccountInfos)- forM_ Assist.allAccountInfos $ \info -> do- let title = Assist.aiTitle info- case Registry.accountSemantics title of- Nothing -> do- putStrLn ("[FAIL] missing account semantics: " ++ show title)- exitFailure- Just semantics -> do- assertEqual ("Assist.aiRoles " ++ show title)- (Registry.asemRoles semantics) (Assist.aiRoles info)- assertEqual ("Assist.aiPostingCapability " ++ show title)- (Registry.asemPostingCapability semantics)- (Assist.aiPostingCapability info)- assertEqual ("Assist.aiDivisionSemantics " ++ show title)- (Registry.asemDivisionSemantics semantics)- (Assist.aiDivisionSemantics info)- assertEqual ("Assist.aiHomeSideSemantics " ++ show title)- (Registry.asemHomeSideSemantics semantics)- (Assist.aiHomeSideSemantics info)- assertEqual ("Assist.aiReportingEligibility " ++ show title)- (Registry.asemReportingEligibility semantics)- (Assist.aiReportingEligibility info)--testAccountMetadataLand1 :: IO ()-testAccountMetadataLand1 = do- let semantics =- [ (title, value)- | title <- Registry.concreteAccountTitles- , Just value <- [Registry.accountSemantics title]- ]- exceptional =- [ NetIncome, NetLoss, GrossProfit, OrdinaryProfit, IncomeSummary- , SuspensePayments, SuspenseReceipts, CashOverShort, SuspenseAccount- , BranchCurrentAccount, HeadOfficeCurrentAccount- , NetIncomeAttributableToNCI, NetLossAttributableToNCI- ]- lookupSem title = Registry.accountSemantics title- lookupInfo title = Assist.describeAccount title- nonStatementTitles =- [ title- | (title, value) <- semantics- , case Registry.asemDivisionSemantics value of- StatementDivision _ -> False- _ -> True- ]- assertEqual "Land 1 metadata covers all 240 concrete titles"- 240 (L.length semantics)- assertEqual "Land 1 metadata rejects wildcard AccountTitle"- Nothing (Registry.accountSemantics AccountTitle)- assertEqual "Land 1 non-statement metadata is exactly the reviewed exception set"- (L.sort exceptional) (L.sort nonStatementTitles)- forM_ semantics $ \(title, value) -> do- assertEqual ("Land 1 roles are non-empty: " ++ show title)- True (not (L.null (Registry.asemRoles value)))- -- Land 4a: rolesFor no longer consults asIsContra (explicit- -- enumeration), so pin the contra role to the registry flag.- assertEqual ("Land 4a contra role matches registry isContra: " ++ show title)- (Registry.classifyAccountContra title)- (ContraAccount `elem` Registry.asemRoles value)- case Registry.asemDivisionSemantics value of- StatementDivision division -> do- assertEqual ("Land 1 statement division preserves legacy value: " ++ show title)- (classifyAccountDivision title) division- _ -> assertEqual ("Land 1 exceptional title is closed-listed: " ++ show title)- True (title `L.elem` exceptional)- case Registry.asemHomeSideSemantics value of- FixedHomeSide side ->- assertEqual ("Land 1 fixed home side preserves legacy value: " ++ show title)- (whichSide (Not :< title)) side- _ -> pure ()- assertEqual "Land 1 Cash semantics"- (Just ( [OrdinaryAccount], OrdinaryPosting- , StatementDivision Assets, FixedHomeSide Debit, StatementEligible ))- (fmap semanticsTuple (lookupSem Cash))- assertEqual "Land 1 IncomeSummary semantics"- (Just ( [ClosingDevice], ClosingOnly- , DirectionEncoding Assets, ContextDependentHomeSide, NotPresented ))- (fmap semanticsTuple (lookupSem IncomeSummary))- assertEqual "Land 1 NetIncome semantics"- (Just ( [PeriodResult], EngineGeneratedOnly- , DirectionEncoding Cost, FixedHomeSide Debit, DerivedPresentation ))- (fmap semanticsTuple (lookupSem NetIncome))- assertEqual "Land 1 GrossProfit is an engine-generated coordinate"- (Just ( [ReportingSubtotal], EngineGeneratedOnly- , DirectionEncoding Revenue, FixedHomeSide Credit- , DerivedPresentation ))- (fmap semanticsTuple (lookupSem GrossProfit))- assertEqual "Land 1 NCI profit is distinct from bare net income"- (Just ( [AttributionAccount, PeriodResult], ConsolidationOnly- , DirectionEncoding Cost, FixedHomeSide Debit- , ContextualPresentation ))- (fmap semanticsTuple (lookupSem NetIncomeAttributableToNCI))- assertEqual "Land 1a NCI equity is consolidation-only"- (Just ( [AttributionAccount], ConsolidationOnly- , StatementDivision Equity, FixedHomeSide Credit- , ContextualPresentation ))- (fmap semanticsTuple (lookupSem NonControllingInterests))- assertEqual "Land 1 branch account semantics"- (Just ( [ReciprocalAccount], OrdinaryPosting- , BookkeepingControlClass Assets, FixedHomeSide Debit- , ContextualPresentation ))- (fmap semanticsTuple (lookupSem BranchCurrentAccount))- assertEqual "Land 1 IncomeSummary LLM description does not classify it as an asset"- True (case lookupInfo IncomeSummary of- Just info -> not (T.isPrefixOf (T.pack "Asset") (Assist.aiDesc info))- && T.isInfixOf (T.pack "not a balance-sheet classification")- (Assist.aiDesc info)- Nothing -> False)- assertEqual "Land 1 NetIncome LLM name drops legacy Expense wording"- (Just (T.pack "当期純利益")) (fmap Assist.aiNameJa (lookupInfo NetIncome))- where- semanticsTuple value =- ( Registry.asemRoles value- , Registry.asemPostingCapability value- , Registry.asemDivisionSemantics value- , Registry.asemHomeSideSemantics value- , Registry.asemReportingEligibility value- )--accountMetadataLand1Header :: T.Text -> T.Text-accountMetadataLand1Header what =- T.pack "# account-semantics-050 Land 1 " <> what- <> T.pack "; schema 1; base 09c8a60c0bfb1a7fedb01689ceee789b8b4e6084\n"--accountMetadataLand1Row :: AccountTitles -> T.Text-accountMetadataLand1Row title = case Registry.accountSemantics title of- Nothing -> error ("missing AccountSemantics for " ++ show title)- Just semantics -> T.intercalate (T.pack "\t")- [ goldenShow title- , goldenShow (Registry.asemRoles semantics)- , goldenShow (Registry.asemPostingCapability semantics)- , goldenShow (Registry.asemDivisionSemantics semantics)- , goldenShow (Registry.asemHomeSideSemantics semantics)- , goldenShow (Registry.asemReportingEligibility semantics)- ]--accountMetadataLand1InfoRow :: Assist.AccountInfo -> T.Text-accountMetadataLand1InfoRow info = T.intercalate (T.pack "\t")- [ goldenShow (Assist.aiTitle info)- , goldenShow (Assist.aiRoles info)- , goldenShow (Assist.aiPostingCapability info)- , goldenShow (Assist.aiDivisionSemantics info)- , goldenShow (Assist.aiHomeSideSemantics info)- , goldenShow (Assist.aiReportingEligibility info)- , goldenEsc (Assist.aiNameEn info)- , goldenEsc (Assist.aiNameJa info)- , goldenEsc (Assist.aiDesc info)- ]--accountMetadataLand1Suggestions :: T.Text-accountMetadataLand1Suggestions =- accountMetadataLand1Header- (T.pack "LLM suggestAccounts (query, total matches, top-10 titles)")- <> T.unlines (L.map row corpus)- where- infos = Assist.allAccountInfos- fields = L.concat- [ [goldenShow (Assist.aiTitle info), Assist.aiNameEn info, Assist.aiNameJa info]- | info <- infos- ]- descTokens = L.concatMap (T.words . Assist.aiDesc) infos- corpus = goldenDedupSort- (L.concatMap (\value -> [value, T.toLower value]) fields <> descTokens)- row query =- let matches = L.map Assist.aiTitle (Assist.suggestAccounts query)- in goldenEsc query <> T.pack "\t" <> goldenShow (L.length matches)- <> T.pack "\t"- <> T.intercalate (T.pack ",") (L.map goldenShow (L.take 10 matches))--testAccountMetadataLand1Golden :: IO ()-testAccountMetadataLand1Golden = do- metadata <- TIO.readFile "test/fixtures/account-semantics-050/metadata.tsv"- info <- TIO.readFile "test/fixtures/account-semantics-050/account-info.tsv"- suggest <- TIO.readFile "test/fixtures/account-semantics-050/suggest.tsv"- let expectedMetadata =- accountMetadataLand1Header- (T.pack "registry (title, roles, posting, divisionSemantics, homeSideSemantics, reportingEligibility)")- <> T.unlines (L.map accountMetadataLand1Row Registry.concreteAccountTitles)- expectedInfo =- accountMetadataLand1Header- (T.pack "LLM AccountInfo (title, roles, posting, divisionSemantics, homeSideSemantics, reportingEligibility, nameEn, nameJa, description)")- <> T.unlines (L.map accountMetadataLand1InfoRow Assist.allAccountInfos)- assertEqual "Land 1 metadata fixture has 240 rows"- 240 (L.length (L.drop 1 (T.lines metadata)))- assertEqual "Land 1 metadata fixture" metadata expectedMetadata- assertEqual "Land 1 LLM AccountInfo fixture" info expectedInfo- assertEqual "Land 1 LLM suggestion fixture"- suggest accountMetadataLand1Suggestions--testAccountInfoLand1Migration :: IO ()-testAccountInfoLand1Migration = do- legacy <- TIO.readFile "test/fixtures/pre-account-semantics-050/account-info.tsv"- let titleMap = M.fromList- [ (goldenShow title, title) | title <- Registry.concreteAccountTitles ]- rows = L.filter (not . T.null) (L.drop 1 (T.lines legacy))- assertEqual "Land 1 AccountInfo migration covers 232 legacy rows"- 232 (L.length rows)- forM_ rows $ \line -> case T.splitOn (T.pack "\t") line of- [titleText, oldDivision, oldSide, oldNameEn, oldNameJa, oldDesc] ->- case M.lookup titleText titleMap of- Nothing -> assertEqual "Land 1 migration unknown legacy title"- (T.pack "") titleText- Just title -> case (Registry.accountSemantics title, Assist.describeAccount title) of- (Just semantics, Just info) -> do- assertEqual ("Land 1 legacy division is recoverable: " ++ show title)- oldDivision- (legacyDivisionText (Registry.asemDivisionSemantics semantics))- assertEqual ("Land 1 legacy home side is recoverable: " ++ show title)- oldSide (goldenShow (whichSide (Not :< title)))- case Registry.asemDivisionSemantics semantics of- StatementDivision _ -> do- assertEqual ("Land 1 ordinary nameEn unchanged: " ++ show title)- oldNameEn (goldenEsc (Assist.aiNameEn info))- -- Land 4a: the Assist projection (aiNameJa) now- -- returns the annotation-free asLabelJa and is- -- pinned by account-semantics-050/account-info.tsv;- -- the pre-golden pins the registry fields.- assertEqual ("Land 1 registry nameJa unchanged: " ++ show title)- oldNameJa (maybe T.empty- (goldenEsc . Registry.asNameJa)- (Registry.accountSpec title))- assertEqual ("Land 1 registry description unchanged: " ++ show title)- oldDesc (legacyDescriptionSpelling title (maybe T.empty- (goldenEsc . Registry.asDescription)- (Registry.accountSpec title)))- _ -> pure ()- _ -> do- putStrLn ("[FAIL] missing Land 1 migration metadata: " ++ show title)- exitFailure- _ -> assertEqual "Land 1 migration malformed legacy row" (T.pack "") line- where- legacyDivisionText semantics = goldenShow $ case semantics of- StatementDivision division -> division- BookkeepingControlClass division -> division- DirectionEncoding division -> division- NoStatementDivision -> error "legacy division is unavailable"--testAssistSuggestAccounts :: IO ()-testAssistSuggestAccounts = do- assertEqual "Assist.suggestAccounts cash contains Cash"- True (Cash `elem` L.map Assist.aiTitle (Assist.suggestAccounts (T.pack "cash")))- assertEqual "Assist.suggestAccounts 現金 contains Cash"- True (Cash `elem` L.map Assist.aiTitle (Assist.suggestAccounts (T.pack "現金")))- assertEqual "Assist.suggestAccounts empty query"- [] (Assist.suggestAccounts T.empty)- assertEqual "Assist.suggestAccounts no match"- [] (Assist.suggestAccounts (T.pack "zzzznomatch"))---- ================================================================--- Land 1 registry: frozen pre-registry behavior.--- ================================================================--goldenCommit :: T.Text-goldenCommit = T.pack "2d9164642f2862725c653e496976770f6e2c7d6f"--goldenHeader :: T.Text -> T.Text-goldenHeader what =- T.pack "# pre-land1 " <> what <> T.pack "; commit " <> goldenCommit <> T.pack "\n"--goldenShow :: Show a => a -> T.Text-goldenShow = T.pack . show--goldenEsc :: T.Text -> T.Text-goldenEsc = T.replace (T.pack "\t") (T.pack "\\t")- . T.replace (T.pack "\n") (T.pack "\\n")--goldenDedupSort :: [T.Text] -> [T.Text]-goldenDedupSort = L.map L.head . L.group . L.sort--legacySuggestAccounts :: [Assist.AccountInfo] -> T.Text -> [Assist.AccountInfo]-legacySuggestAccounts infos query- | L.null tokens = []- | otherwise = L.map snd- . L.sortOn (\(rank, info) -> (negate rank, fromEnum (Assist.aiTitle info)))- . L.filter ((> 0) . fst)- $ [ (matchRank info, info) | info <- infos ]- where- tokens = L.map T.toCaseFold (T.words query)- matchRank info = L.length- [ token- | token <- tokens- , L.any (T.isInfixOf token) (legacySearchFields info)- ]- legacySearchFields info = L.map T.toCaseFold $ case Registry.accountSpec (Assist.aiTitle info) of- Just spec ->- [ goldenShow (Assist.aiTitle info)- , Registry.asNameEn spec- , Registry.asNameJa spec- , Registry.asDescription spec- ]- Nothing -> []--goldenInfoRow :: Assist.AccountInfo -> T.Text--- Historical schema reconstruction. The live Land 1 AccountInfo projection is--- pinned separately by accountMetadataLand1InfoRow.-goldenInfoRow info = case Registry.accountSpec (Assist.aiTitle info) of- Nothing -> error "goldenInfoRow: wildcard AccountTitle"- Just spec -> T.intercalate (T.pack "\t")- [ goldenShow (Assist.aiTitle info)- , goldenShow (Registry.asDivision spec)- , goldenShow (whichSide (Not :< Assist.aiTitle info))- , goldenEsc (Registry.asNameEn spec)- , goldenEsc (Registry.asNameJa spec)- , goldenEsc (Registry.asDescription spec)- ]--goldenAliasResolution :: T.Text -> T.Text-goldenAliasResolution fixture =- goldenHeader (T.pack "parseAccountTitle over corpus (query, show(Either ConvError AccountTitles))")- <> T.unlines (L.map row queries)- where- queries = L.map (T.takeWhile (/= '\t')) (L.drop 1 (T.lines fixture))- row query = goldenEsc query <> T.pack "\t"- <> goldenEsc (goldenShow (EC.parseAccountTitle query))--goldenSuggestions :: T.Text-goldenSuggestions =- goldenHeader (T.pack "suggestAccounts over corpus (query, total matches, top-10 titles)")- <> T.unlines (L.map row corpus)- where- -- The post-Land2 fixture is a closed diff over the pre-vocabulary 116- -- titles. V-Land 2 appends new titles, which are tested separately and- -- must not retroactively change this historical fuzzy-suggestion oracle.- infos = L.take 116 Assist.allAccountInfos- historicalTitles = L.map Assist.aiTitle infos- nameFields = L.concatMap legacyNameFields infos- descTokens = L.concatMap legacyDescTokens infos- corpus = goldenDedupSort- (L.concatMap (\q -> [q, T.toLower q]) nameFields <> descTokens)- row query =- let matches = L.filter (`L.elem` historicalTitles)- (L.map Assist.aiTitle (legacySuggestAccounts infos query))- in goldenEsc query <> T.pack "\t"- <> goldenShow (L.length matches) <> T.pack "\t"- <> T.intercalate (T.pack ",") (L.map goldenShow (L.take 10 matches))- legacyNameFields info = case Registry.accountSpec (Assist.aiTitle info) of- Just spec ->- [ goldenShow (Assist.aiTitle info)- , Registry.asNameEn spec- , Registry.asNameJa spec- ]- Nothing -> []- legacyDescTokens info = case Registry.accountSpec (Assist.aiTitle info) of- Just spec -> T.words (Registry.asDescription spec)- Nothing -> []--postVocabHeader :: T.Text -> T.Text-postVocabHeader what = T.pack "# post-vocab " <> what <> T.pack "; schema 1\n"--postVocabInfoGolden :: T.Text-postVocabInfoGolden =- postVocabHeader (T.pack "AccountInfo (title, division, homeSide, nameEn, nameJa, description)")- <> T.unlines (L.map goldenInfoRow Assist.allAccountInfos)--postVocabSuggestionsGolden :: T.Text-postVocabSuggestionsGolden =- postVocabHeader (T.pack "suggestAccounts (query, total matches, top-10 titles)")- <> T.unlines (L.map row corpus)- where- infos = Assist.allAccountInfos- nameFields = L.concatMap legacyNameFields infos- descTokens = L.concatMap legacyDescTokens infos- corpus = goldenDedupSort- (L.concatMap (\q -> [q, T.toLower q]) nameFields <> descTokens)- row query =- let matches = L.map Assist.aiTitle (legacySuggestAccounts infos query)- in goldenEsc query <> T.pack "\t"- <> goldenShow (L.length matches) <> T.pack "\t"- <> T.intercalate (T.pack ",") (L.map goldenShow (L.take 10 matches))- legacyNameFields info = case Registry.accountSpec (Assist.aiTitle info) of- Just spec ->- [ goldenShow (Assist.aiTitle info)- , Registry.asNameEn spec- , Registry.asNameJa spec- ]- Nothing -> []- legacyDescTokens info = case Registry.accountSpec (Assist.aiTitle info) of- Just spec -> T.words (Registry.asDescription spec)- Nothing -> []--postVocabOrdinalsGolden :: T.Text-postVocabOrdinalsGolden =- postVocabHeader (T.pack "Enum ordinals (constructor, fromEnum)")- <> T.unlines- [ goldenShow title <> T.pack "\t" <> goldenShow (fromEnum title)- | title <- [minBound .. maxBound] :: [AccountTitles]- ]--postVocabSemanticsGolden :: T.Text-postVocabSemanticsGolden =- postVocabHeader (T.pack "semantics (title, whatDiv, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe)")- <> T.unlines (L.map row Registry.concreteAccountTitles)- where- row title =- let nb = Not :< title :: HatBase AccountTitles- hb = Hat :< title :: HatBase AccountTitles- in T.intercalate (T.pack "\t")- [ goldenShow title- , goldenShow (whatDiv nb)- , goldenShow (Registry.classifyAccountContra title)- , goldenShow (whichSide nb)- , goldenShow (whichSide hb)- , goldenShow (whatPIMO nb)- , goldenShow (fixedCurrent nb)- , T.pack (finalStockProbeRule title)- ]--testPostVocabGolden :: IO ()-testPostVocabGolden = do- ordinals <- TIO.readFile "test/fixtures/post-vocab/ordinals.tsv"- semantics <- TIO.readFile "test/fixtures/post-vocab/semantics.tsv"- info <- TIO.readFile "test/fixtures/post-vocab/account-info.tsv"- suggestions <- TIO.readFile "test/fixtures/post-vocab/suggest.tsv"- assertEqual "post-vocab ordinal fixture" ordinals postVocabOrdinalsGolden- assertEqual "post-vocab semantics fixture" semantics postVocabSemanticsGolden- assertEqual "post-vocab account-info fixture" info postVocabInfoGolden- assertEqual "post-vocab suggest fixture" suggestions postVocabSuggestionsGolden---- ================================================================--- 0.5.0.0 account-semantics pipeline: pre-change compatibility baseline.--- ================================================================--accountSemanticsBaselineCommit :: T.Text-accountSemanticsBaselineCommit = T.pack "0d8e2791429145f2a48c79adbe62563328ee5c0b"--accountSemanticsHeader :: T.Text -> T.Text-accountSemanticsHeader what =- T.pack "# pre-account-semantics-050 " <> what- <> T.pack "; schema 1; commit " <> accountSemanticsBaselineCommit <> T.pack "\n"--accountSemanticsBinaryHex :: AccountTitles -> T.Text-accountSemanticsBinaryHex = T.pack . concatMap hexByte . BL.unpack . Binary.encode- where- hexByte byte = case showHex byte "" of- [digit] -> ['0', digit]- digits -> digits---- The pre-account-semantics fixture is immutable and predates the three--- Land 4a constructors appended after ordinal 231.-accountSemanticsBaselineTitles :: [AccountTitles]-accountSemanticsBaselineTitles = L.take 232 Registry.concreteAccountTitles--accountSemanticsSemanticsGolden :: T.Text-accountSemanticsSemanticsGolden =- accountSemanticsHeader (T.pack "semantics (title, enum, binaryHex, division, closing, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe)")- <> T.unlines (L.map row accountSemanticsBaselineTitles)- where- row title =- let nb = Not :< title :: HatBase AccountTitles- hb = Hat :< title :: HatBase AccountTitles- spec = case Registry.accountSpec title of- Just value -> value- Nothing -> error ("missing AccountSpec for " ++ show title)- in T.intercalate (T.pack "\t")- [ goldenShow title- , goldenShow (fromEnum title)- , accountSemanticsBinaryHex title- , goldenShow (Registry.asDivision spec)- , goldenShow (Registry.asClosing spec)- , goldenShow (Registry.asIsContra spec)- , goldenShow (whichSide nb)- , goldenShow (whichSide hb)- , goldenShow (whatPIMO nb)- , goldenShow (fixedCurrent nb)- , T.pack (finalStockProbeRule title)- ]--accountSemanticsInfoGolden :: T.Text-accountSemanticsInfoGolden =- accountSemanticsHeader (T.pack "AccountInfo (title, division, homeSide, nameEn, nameJa, description)")- <> T.unlines- [ legacyDescriptionSpelling (Assist.aiTitle info) (goldenInfoRow info)- | info <- L.take 232 Assist.allAccountInfos ]---- The old baselines retain the pre-2026-09-23 en-US spelling for one description.-legacyDescriptionSpelling :: AccountTitles -> T.Text -> T.Text-legacyDescriptionSpelling EquityInEarningsOfInvestee =- T.replace (T.pack "Recognized") (T.pack "Recognised")-legacyDescriptionSpelling _ = id--accountSemanticsProjectionGolden :: T.Text-accountSemanticsProjectionGolden =- accountSemanticsHeader (T.pack "projection flags for Not then Hat (currentAssets, fixedAssets, deferredAssets, currentLiability, fixedLiability, capitalStock, contraAssets, contra)")- <> T.unlines (L.map row accountSemanticsBaselineTitles)- where- kept :: (EA.Alg Double (HatBase AccountTitles)- -> EA.Alg Double (HatBase AccountTitles))- -> EA.Alg Double (HatBase AccountTitles)- -> T.Text- kept projection value = if norm (projection value) == (1 :: Double)- then T.pack "1" else T.pack "0"- row title = T.intercalate (T.pack "\t")- (goldenShow title : L.concatMap (probe title) [Not, Hat])- probe title hat =- let value = 1 .@ hat :< title :: EA.Alg Double (HatBase AccountTitles)- in [ kept EA.projCurrentAssets value- , kept EA.projFixedAssets value- , kept EA.projDeferredAssets value- , kept EA.projCurrentLiability value- , kept EA.projFixedLiability value- , kept EA.projCapitalStock value- , kept EA.projContraAssets value- , kept EA.projContra value- ]--accountSemanticsPresentationGolden :: T.Text-accountSemanticsPresentationGolden =- accountSemanticsHeader (T.pack "legacy presentation probe (title, bsRows of 1@Not, plRows of 1@Not)")- <> T.unlines (L.map row accountSemanticsBaselineTitles)- where- row title =- let value = 1 .@ Not :< title :: EA.Alg Double (HatBase AccountTitles)- in T.intercalate (T.pack "\t")- [ goldenShow title- , goldenEsc (goldenShow (EW.bsRows value))- , goldenEsc (goldenShow (EW.plRows value))- ]--testAccountSemanticsPrechangeGolden :: IO ()-testAccountSemanticsPrechangeGolden = do- semantics <- TIO.readFile "test/fixtures/pre-account-semantics-050/semantics.tsv"- info <- TIO.readFile "test/fixtures/pre-account-semantics-050/account-info.tsv"- projections <- TIO.readFile "test/fixtures/pre-account-semantics-050/projection-membership.tsv"- presentation <- TIO.readFile "test/fixtures/pre-account-semantics-050/presentation.tsv"- assertEqual "pre-account-semantics semantics fixture has 232 rows"- 232 (L.length (L.drop 1 (T.lines semantics)))- assertEqual "pre-account-semantics semantics fixture"- semantics accountSemanticsSemanticsGolden- assertEqual "pre-account-semantics legacy AccountInfo fixture"- info accountSemanticsInfoGolden- assertEqual "pre-account-semantics projection fixture"- projections accountSemanticsProjectionGolden- let oldPresentation = L.drop 1 (T.lines presentation)- newPresentation = L.drop 1 (T.lines accountSemanticsPresentationGolden)- changedTitles =- [ T.takeWhile (/= '\t') new- | (old, new) <- L.zip oldPresentation newPresentation- , old /= new- ]- assertEqual "pre-account-semantics presentation row count"- (L.length oldPresentation) (L.length newPresentation)- assertEqual "Land 3 presentation closed diff = contra rows plus two formerly phantom totals"- [ T.pack "NetLoss"- , T.pack "AllowanceForDoubtfulAccounts"- , T.pack "AccumulatedDepreciation"- , T.pack "SalesRebates"- , T.pack "RefundOfIncomeTaxes"- , T.pack "PurchaseRebates"- , T.pack "NetLossAttributableToNCI"- ]- changedTitles--testWriteRowsGolden :: IO ()-testWriteRowsGolden = do- assertEqual "write-rows-0510 fixture count"- 10 (L.length writeRowsFixtures)- forM_ writeRowsFixtures $ \(name, expected) -> do- actualFile <- TIO.readFile (writeRowsFixtureDir ++ "/" ++ name)- assertEqual ("write-rows-0510 fixture " ++ name) actualFile expected---- Land 2 (Definition 7 contra amendment) 以降: alias 解決だけが byte 一致--- (parseAccountTitle は division 非依存)。semantics / info / suggest は--- 意図的差分を持つため, closed-diff test (testLand2*ClosedDiff) が引き継ぐ。-testRegistryGolden :: IO ()-testRegistryGolden = do- aliases <- TIO.readFile "test/fixtures/pre-land1/alias-resolution.tsv"- jcci <- TIO.readFile "test/fixtures/jcci-2022/queries.tsv"- let oldRows = L.filter (not . T.null) (L.drop 1 (T.lines aliases))- currentRows = L.filter (not . T.null)- (L.drop 1 (T.lines (goldenAliasResolution aliases)))- changedQueries =- [ T.takeWhile (/= '\t') old- | (old, current) <- L.zip oldRows currentRows- , old /= current- ]- expectedChangedQueries = L.sort (L.map T.pack- [ "未払金", "借入金", "仮払金", "仮受金"- , "有価証券", "投資有価証券"- , " 未払金 ", " 借入金 ", " 仮払金 ", " 仮受金 "- , " 有価証券 ", " 投資有価証券 "- ])- officialQueries =- [ EC.normalizeTitle (fields L.!! 3)- | line <- L.filter (not . T.null) (L.drop 1 (T.lines jcci))- , let fields = T.splitOn (T.pack "\t") line- , L.length fields == 7- ]- assertEqual "registry golden: historical row count"- (L.length oldRows) (L.length currentRows)- assertEqual "registry golden: every changed historical alias is JCCI-scoped"- ([] :: [T.Text])- [q | q <- changedQueries, EC.normalizeTitle q `L.notElem` officialQueries]- -- The pre-land fixture freezes each old result; testJcciAccountNameCoverage- -- freezes each new result. Freezing this exact query set closes the diff.- assertEqual "registry golden: exact adjudicated historical alias diff"- expectedChangedQueries (L.sort changedQueries)---- | JCCI 2022 A欄/B欄の全 distinct query は, 一意のRightかfixtureで候補を--- 閉じたAmbiguousのどちらかでなければならない. Unknown/first-matchは不可.-testJcciAccountNameCoverage :: IO ()-testJcciAccountNameCoverage = do- source <- TIO.readFile "test/fixtures/jcci-2022/source.tsv"- fixture <- TIO.readFile "test/fixtures/jcci-2022/queries.tsv"- let sourceRows = L.filter (not . T.null) (L.drop 1 (T.lines source))- rows = L.filter (not . T.null) (L.drop 1 (T.lines fixture))- outcomes = [field L.!! 4 | row <- rows, let field = T.splitOn (T.pack "\t") row]- sourceFields =- [ fields- | row <- sourceRows- , let fields = T.splitOn (T.pack "\t") row- , L.length fields == 6- ]- sourceEntries =- [ (EC.normalizeTitle label, standardName)- | fields <- sourceFields- , let standardName = fields L.!! 2- , label <- standardName : T.splitOn (T.pack "|") (fields L.!! 3)- , not (T.null (T.strip label))- ]- sourceLabels = goldenDedupSort (L.map fst sourceEntries)- fixtureLabels = goldenDedupSort- [ EC.normalizeTitle (fields L.!! 3)- | row <- rows- , let fields = T.splitOn (T.pack "\t") row- , L.length fields == 7- ]- assertEqual "JCCI source A-row count" 215 (L.length sourceRows)- assertEqual "JCCI source rows have six columns"- (L.length sourceRows) (L.length sourceFields)- assertEqual "JCCI distinct normalized A/B query count" 316 (L.length rows)- assertEqual "JCCI fixture covers exactly the source A/B labels"- sourceLabels fixtureLabels- assertEqual "JCCI unique resolutions" 295 (L.length (L.filter (== T.pack "right") outcomes))- assertEqual "JCCI policy ambiguities" 21 (L.length (L.filter (== T.pack "ambiguous") outcomes))- forM_ rows $ \row -> case T.splitOn (T.pack "\t") row of- [_, _, _, query, _, _, standardNames] ->- assertEqual ("JCCI source provenance: " ++ T.unpack query)- (goldenDedupSort- [ standardName- | (label, standardName) <- sourceEntries- , label == EC.normalizeTitle query- ])- (goldenDedupSort (T.splitOn (T.pack "|") standardNames))- fields -> assertEqual "JCCI fixture provenance row has seven columns"- (7 :: Int) (L.length fields)- mapM_ check rows- where- check row = case T.splitOn (T.pack "\t") row of- [_, _, _, query, outcome, candidateText, _] ->- let names = T.splitOn (T.pack "|") candidateText- in case traverse (`M.lookup` land2TitleMap) names of- Nothing -> assertEqual "JCCI fixture names only real constructors"- (T.pack "known constructors") candidateText- Just candidates -> case candidates of- [candidate] | outcome == T.pack "right" ->- assertEqual ("JCCI Right: " ++ T.unpack query)- (Right candidate) (EC.parseAccountTitle query)- _ : _ : _ | outcome == T.pack "ambiguous" ->- assertEqual ("JCCI Ambiguous: " ++ T.unpack query)- (Left (EC.AmbiguousAccount query candidates))- (EC.parseAccountTitle query)- _ -> assertEqual "JCCI fixture outcome/candidate arity"- (T.pack "right=1 or ambiguous>=2")- (outcome <> T.pack ":" <> candidateText)- _ -> assertEqual "JCCI fixture row has seven columns" (7 :: Int)- (L.length (T.splitOn (T.pack "\t") row))---- | Every cleaned Japanese label is a bare account name, and every level-2--- JCCI A-column name that resolves uniquely is the profile display label.-testAccountLabelsLand4a :: IO ()-testAccountLabelsLand4a = do- let labels =- [ (title, Registry.asLabelJa spec)- | title <- Registry.concreteAccountTitles- , Just spec <- [Registry.accountSpec title]- ]- forbidden = L.map T.pack ["。", "—", "\\/", "'"]- invalid =- [ (title, label)- | (title, label) <- labels- , any (`T.isInfixOf` label) forbidden- || T.any (\c -> isAscii c && isAlpha c) label- ]- assertEqual "Land 4a asLabelJa covers all 240 concrete titles"- 240 (L.length labels)- assertEqual "Land 4a asLabelJa contains only cleaned Japanese account names"- ([] :: [(AccountTitles, T.Text)]) invalid-- source <- TIO.readFile "test/fixtures/jcci-2022/source.tsv"- let rows =- [ fields- | row <- L.drop 1 (T.lines source)- , not (T.null row)- , let fields = T.splitOn (T.pack "\t") row- , L.length fields == 6- , T.pack "2" `T.isInfixOf` (fields L.!! 0)- ]- aNames = L.map (L.!! 2) rows- resolved =- [ (name, title, RP.presentationLabel RP.JcciSecondGradeReport title)- | name <- aNames- , Right title <- [EC.parseAccountTitle name]- ]- skipped =- [ name- | name <- aNames- , Left (EC.AmbiguousAccount _ _) <- [EC.parseAccountTitle name]- ]- unexpected =- [ (name, show err)- | name <- aNames- , Left err <- [EC.parseAccountTitle name]- , case err of EC.AmbiguousAccount _ _ -> False; _ -> True- ]- mismatches =- [ (name, title, label)- | (name, title, label) <- resolved- , label /= name- ]- assertEqual "Land 4a JCCI level-2 A-column rows" 117 (L.length aNames)- assertEqual "Land 4a JCCI A-column MATCH count" 113 (L.length resolved)- assertEqual "Land 4a JCCI A-column skip set"- (L.sort (L.map T.pack- ["営業収益", "営業費用", "為替差損益", "有価証券評価損益"]))- (L.sort skipped)- assertEqual "Land 4a JCCI A-column has no unexpected parse failures"- ([] :: [(T.Text, String)]) unexpected- assertEqual "Land 4a JCCI A-column labels MATCH 113/113"- ([] :: [(T.Text, AccountTitles, T.Text)]) mismatches--testRegistryWildcards :: IO ()-testRegistryWildcards = do- divisionResult <- try (evaluate (classifyAccountDivision AccountTitle))- :: IO (Either SomeException AccountDivision)- assertEqual "registry wildcard: classifyAccountDivision errors"- True (case divisionResult of Left _ -> True; Right _ -> False)- assertEqual "registry wildcard: fixedCurrent is Other"- Other (fixedCurrent (Not :< AccountTitle))- assertEqual "registry wildcard: describeAccount is Nothing"- Nothing (Assist.describeAccount AccountTitle)--testRegistryContraLand2 :: IO ()-testRegistryContraLand2 =- assertEqual "registry contra True set = valuation accounts plus V-Land 2 P/L contra accounts"- allContra- (L.filter Registry.classifyAccountContra Registry.concreteAccountTitles)---- ================================================================--- Land 2 (Definition 7 contra amendment): closed-diff vs pre-land1--- golden, contract / relation properties, presentation invariance.--- pre-land1 fixtures stay frozen as the pre-amendment reference.--- ================================================================--land2Contra :: [AccountTitles]-land2Contra = [AllowanceForDoubtfulAccounts, AccumulatedDepreciation]--allContra :: [AccountTitles]-allContra = land2Contra <> [SalesRebates, RefundOfIncomeTaxes, PurchaseRebates]--land2TitleMap :: M.Map T.Text AccountTitles-land2TitleMap = M.fromList- [ (T.pack (show t), t) | t <- Registry.concreteAccountTitles ]---- registry から生成しない literal 期待値 (循環 oracle 回避)-land2ExpectedDesc :: AccountTitles -> T.Text-land2ExpectedDesc AllowanceForDoubtfulAccounts = T.pack- "Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\\/Not structure is intact. B\\/S deduction (net) presentation is the Write side's job."-land2ExpectedDesc AccumulatedDepreciation = T.pack- "Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\\/macro-accounting name."-land2ExpectedDesc t = T.pack ("land2ExpectedDesc: not a contra account: " ++ show t)---- T1: 全域機械比較 — whichSide/whatPIMO/fixedCurrent は全一致,--- whatDiv は当該 2 件 (Liability→Assets) ちょうど。-testLand2SemanticsClosedDiff :: IO ()-testLand2SemanticsClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-land1/account-semantics.tsv"- let rows = L.filter (not . T.null) (L.drop 1 (T.lines fixture))- assertEqual "land2 semantics: fixture row count" 116 (L.length rows)- mapM_ checkRow rows- where- checkRow line = case T.splitOn (T.pack "\t") line of- [name, oldDiv, oldPimo, oldSideN, oldSideH, oldFc] ->- case M.lookup name land2TitleMap of- Nothing -> assertEqual "land2 semantics: unknown fixture title" (T.pack "") name- Just t -> do- let nb = Not :< t :: HatBase AccountTitles- hb = Hat :< t :: HatBase AccountTitles- assertEqual ("land2 whatPIMO invariant: " ++ show t)- oldPimo (goldenShow (whatPIMO nb))- assertEqual ("land2 whichSide Not invariant: " ++ show t)- oldSideN (goldenShow (whichSide nb))- assertEqual ("land2 whichSide Hat invariant: " ++ show t)- oldSideH (goldenShow (whichSide hb))- assertEqual ("land2 fixedCurrent invariant: " ++ show t)- oldFc (goldenShow (fixedCurrent nb))- if t `L.elem` land2Contra- then do- assertEqual ("land2 whatDiv old was Liability: " ++ show t)- (T.pack "Liability") oldDiv- assertEqual ("land2 whatDiv new is Assets: " ++ show t)- Assets (whatDiv nb)- else assertEqual ("land2 whatDiv invariant: " ++ show t)- oldDiv (goldenShow (whatDiv nb))- _ -> assertEqual "land2 semantics: malformed fixture row" (T.pack "") line---- T8 込み: allAccountInfos の閉じた差分 — 当該 2 行だけ aiDivision と--- aiDesc が変わり (desc は "Asset (contra):" で始まる), 他は byte 一致。-testLand2InfoClosedDiff :: IO ()-testLand2InfoClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-land1/account-info.tsv"- let oldRows = L.filter (not . T.null) (L.drop 1 (T.lines fixture))- -- V-Land 2 appends new constructors after every pre-vland2 concrete- -- title. This closed-diff compares only the pinned historical prefix.- newRows = L.take (L.length oldRows)- [ (Assist.aiTitle i, goldenInfoRow i) | i <- Assist.allAccountInfos ]- assertEqual "land2 info: row count" (L.length oldRows) (L.length newRows)- mapM_ check (L.zip oldRows newRows)- where- check (oldLine, (t, newLine))- | t `L.elem` land2Contra = do- let oldF = T.splitOn (T.pack "\t") oldLine- newF = T.splitOn (T.pack "\t") newLine- assertEqual ("land2 info title invariant: " ++ show t)- (oldF L.!! 0) (newF L.!! 0)- assertEqual ("land2 info old division was Liability: " ++ show t)- (T.pack "Liability") (oldF L.!! 1)- assertEqual ("land2 info new division is Assets: " ++ show t)- (T.pack "Assets") (newF L.!! 1)- assertEqual ("land2 info home side invariant: " ++ show t)- (oldF L.!! 2) (newF L.!! 2)- assertEqual ("land2 info nameEn invariant: " ++ show t)- (oldF L.!! 3) (newF L.!! 3)- assertEqual ("land2 info nameJa invariant: " ++ show t)- (oldF L.!! 4) (newF L.!! 4)- assertEqual ("land2 info desc updated to contra wording: " ++ show t)- True (oldF L.!! 5 /= newF L.!! 5)- assertEqual ("land2 info new desc literal: " ++ show t)- (land2ExpectedDesc t) (newF L.!! 5)- | otherwise =- assertEqual ("land2 info invariant: " ++ show t)- oldLine (legacyDescriptionSpelling t newLine)---- suggest の閉じた差分: 変化した (追加/削除/変更) query は全て, 当該 2 科目の--- 旧/新 desc に対する token match rank の変化で説明できる。-testLand2SuggestClosedDiff :: IO ()-testLand2SuggestClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-land1/suggest.tsv"- infoFixture <- TIO.readFile "test/fixtures/pre-land1/account-info.tsv"- let toMap txt = M.fromList- [ (T.takeWhile (/= '\t') line, line)- | line <- L.filter (not . T.null) (L.drop 1 (T.lines txt)) ]- oldMap = toMap fixture- newMap = toMap goldenSuggestions- oldFieldsOf t = L.concat- [ [ fs L.!! 0, fs L.!! 3, fs L.!! 4, fs L.!! 5 ]- | line <- L.filter (not . T.null) (L.drop 1 (T.lines infoFixture))- , let fs = T.splitOn (T.pack "\t") line- , fs L.!! 0 == T.pack (show t)- ]- -- The 2026-09-23 spelling change is in the registry description used- -- by goldenSuggestions, while Assist.aiDesc has a newer description.- newFieldsOf EquityInEarningsOfInvestee =- case Registry.accountSpec EquityInEarningsOfInvestee of- Just spec -> [ T.pack "EquityInEarningsOfInvestee"- , Registry.asNameEn spec, Registry.asNameJa spec- , Registry.asDescription spec ]- Nothing -> []- newFieldsOf t = case Assist.describeAccount t of- Just i -> [ T.pack (show t), Assist.aiNameEn i, Assist.aiNameJa i, Assist.aiDesc i ]- Nothing -> []- rank fields q = L.length- [ tok- | tok <- L.map T.toCaseFold (T.words q)- , L.any (T.isInfixOf tok) (L.map T.toCaseFold fields) ]- -- corpus 帰属の変化 (query が旧/新 desc の token 集合の片方にだけある)- -- も desc 変更の帰結として許容する (行の追加/削除がこれで起きる)。- descTokensOf fields = case fields of- [_, _, _, desc] -> T.words desc- _ -> []- -- Include the 2026-09-23 en-US description spelling change.- changedDescriptions = EquityInEarningsOfInvestee : land2Contra- tokenMembershipChange q = L.or- [ (q `L.elem` descTokensOf (oldFieldsOf t))- /= (q `L.elem` descTokensOf (newFieldsOf t))- | t <- changedDescriptions ]- affected q = tokenMembershipChange q || L.or- [ rank (oldFieldsOf t) q /= rank (newFieldsOf t) q- | t <- changedDescriptions ]- diffQueries = L.nub- ( [ q | (q, old) <- M.toList oldMap, maybe True (/= old) (M.lookup q newMap) ]- ++ [ q | q <- M.keys newMap, not (M.member q oldMap) ] )- postFixture <- TIO.readFile "test/fixtures/post-land2/suggest.tsv"- assertEqual "land2 suggest: post fixture byte-identical (expected output itself)"- postFixture goldenSuggestions- assertEqual "land2 suggest: some diff exists (descs changed)"- True (not (L.null diffQueries))- mapM_ (\q -> assertEqual- ("land2 suggest diff explained by contra desc change: " ++ T.unpack q)- True (affected q))- diffQueries---- T2: 契約 isContra(b) ⇔ homeSide(b) ≠ defaultSide(whatDiv b)-testLand2Contract :: IO ()-testLand2Contract = mapM_ check Registry.concreteAccountTitles- where- check t =- let nb = Not :< t :: HatBase AccountTitles- in assertEqual ("land2 contract (isContra = reversed home side): " ++ show t)- (isContra nb) (whichSide nb /= defaultSide (whatDiv nb))---- T3: pimoFlip は自己逆で, 原典の交換関係を保つ-testLand2PimoFlip :: IO ()-testLand2PimoFlip = do- let pimoAll = [PS, IN, MS, OUT]- mapM_ (\x -> assertEqual ("land2 pimoFlip involution: " ++ show x)- x (pimoFlip (pimoFlip x))) pimoAll- mapM_ (\(x, y) -> assertEqual ("land2 pimoFlip preserves (<=>): " ++ show (x, y))- (x <=> y) (pimoFlip x <=> pimoFlip y))- [ (x, y) | x <- pimoAll, y <- pimoAll ]---- T4: (<=>) — PIMO instance = 原典 Prop 5.3.8, Division instance =--- pimoFromDivision 経由の外延, 旧 instance との差分 = ordered 4 case ちょうど。-testLand2ExchangeRelation :: IO ()-testLand2ExchangeRelation = do- let pimoAll = [PS, IN, MS, OUT]- divAll = [Assets, Equity, Liability, Cost, Revenue]- assertEqual "land2 (<=>) PIMO instance = Prop 5.3.8 pairs"- [ (PS,IN), (PS,MS), (IN,PS), (IN,OUT), (MS,PS), (MS,OUT), (OUT,IN), (OUT,MS) ]- [ (x, y) | x <- pimoAll, y <- pimoAll, x <=> y ]- mapM_ (\(a, b) -> assertEqual ("land2 (<=>) division = via pimoFromDivision: " ++ show (a, b))- (pimoFromDivision a <=> pimoFromDivision b) (a <=> b))- [ (a, b) | a <- divAll, b <- divAll ]- let oldRel (Assets, Liability) = True- oldRel (Liability, Assets) = True- oldRel (Assets, Equity) = True- oldRel (Equity, Assets) = True- oldRel (Cost, Liability) = True- oldRel (Liability, Cost) = True- oldRel (Cost, Equity) = True- oldRel (Equity, Cost) = True- oldRel _ = False- assertEqual "land2 (<=>) division migration = exactly 4 ordered cases"- [ (Assets, Revenue), (Cost, Revenue), (Revenue, Assets), (Revenue, Cost) ]- [ (a, b) | a <- divAll, b <- divAll, oldRel (a, b) /= (a <=> b) ]---- T9: 8 組込み instance + custom instance (SimHatBase2) の全科目 sweepで--- isContra のTrue集合が既存評価勘定2件 + V-Land 2 P/L控除3件になること。-testLand2IsContraInstances :: IO ()-testLand2IsContraInstances = do- let day0 = fromGregorian 2026 1 1- tod0 = TimeOfDay 0 0 0- nm = T.pack "spec"- sweep :: ExBaseClass b => String -> (AccountTitles -> b) -> IO ()- sweep label mk = assertEqual ("land2 isContra sweep: " ++ label)- allContra- [ t | t <- Registry.concreteAccountTitles, isContra (mk t) ]- sweep "HatBase AccountTitles" (\t -> Not :< t :: HatBase AccountTitles)- sweep "HatBase (AccountTitles, Day)" (\t -> Not :< (t, day0))- sweep "HatBase (AccountTitles, Name)" (\t -> Not :< (t, nm))- sweep "HatBase (CountUnit, AccountTitles)" (\t -> Not :< (Yen, t))- sweep "HatBase (AccountTitles, Name, CountUnit)" (\t -> Not :< (t, nm, Yen))- sweep "HatBase (AccountTitles, Name, CountUnit, Subject)"- (\t -> Not :< (t, nm, Yen, nm))- sweep "HatBase (AccountTitles, Name, CountUnit, Subject, Day)"- (\t -> Not :< (t, nm, Yen, nm, day0))- sweep "HatBase (AccountTitles, Name, CountUnit, Subject, Day, TimeOfDay)"- (\t -> Not :< (t, nm, Yen, nm, day0, tod0))- sweep "SimHatBase2 (custom instance)" (\t -> Not :< (t, 1, 2, Yen) :: SimHatBase2)---- T8: LLM-facing メタデータの literal 期待値 (registry から生成しない)-testLand2AiDivision :: IO ()-testLand2AiDivision = do- assertEqual "land2 statement metadata literal: AllowanceForDoubtfulAccounts"- (Just (StatementDivision Assets, FixedHomeSide Credit))- (fmap (\i -> (Assist.aiDivisionSemantics i, Assist.aiHomeSideSemantics i))- (Assist.describeAccount AllowanceForDoubtfulAccounts))- assertEqual "land2 statement metadata literal: AccumulatedDepreciation"- (Just (StatementDivision Assets, FixedHomeSide Credit))- (fmap (\i -> (Assist.aiDivisionSemantics i, Assist.aiHomeSideSemantics i))- (Assist.describeAccount AccumulatedDepreciation))---- T5/T6: presentation battery。bsRows/plRows の literal は, 以下で明記する--- vocabulary closing 差分を除き, Land 1 出力 (pre-land2 golden, commit--- 1c1f3f2) と byte 一致 = 表示互換 shim の証明。--- division projection は contra を含まず, contra は projContraAssets のみが選ぶ--- (意図的差分: projCurrentLiability/projFixedLiability から当該 2 件が消えた)。-land2B1, land2B2, land2B3, land2B4, land2B5 :: BAlg-land2B1 = 100 .@ Not:<Cash .+ 60 .@ Not:<LoansPayable .+ 40 .@ Not:<CapitalStock-land2B2 = 500 .@ Not:<Sales .+ 300 .@ Not:<SalesCost-land2B3 = 1000 .@ Not:<AccountsReceivable .+ 900 .@ Not:<Cash .+ 800 .@ Not:<Building- .+ 100 .@ Not:<AllowanceForDoubtfulAccounts .+ 200 .@ Not:<AccumulatedDepreciation- .+ 2000 .@ Not:<CapitalStock .+ 400 .@ Not:<LoansPayable-land2B4 = 30 .@ Not:<Cash .+ 80 .@ Hat:<Cash- .+ 100 .@ Not:<AllowanceForDoubtfulAccounts .+ 120 .@ Hat:<AllowanceForDoubtfulAccounts- .+ 500 .@ Not:<Building .+ 200 .@ Not:<LoansPayable .+ 300 .@ Hat:<LoansPayable- .+ 250 .@ Not:<AccumulatedDepreciation .+ 50 .@ Hat:<AccumulatedDepreciation-land2B5 = land2B3 .+ 300 .@ Not:<SalesCost .+ 500 .@ Not:<Sales .+ 200 .@ Not:<Cash--testLand3PresentationGroups :: IO ()-testLand3PresentationGroups = do- let defaultDefs = RG.defaultPresentationGrouping- tradeDef = maybe (error "missing TradeReceivablesGroup") id- (RG.lookupGroupDef defaultDefs RG.TradeReceivablesGroup)- amount below magnitude = RG.RelativeAmount below (magnitude :: Double)- present defs entries = RG.presentGroups defs (M.fromList entries)- contraTitles =- [ title- | title <- Registry.concreteAccountTitles- , maybe False Registry.asIsContra (Registry.accountSpec title)- ]- deductionTitles = L.concatMap RG.pgDeductions defaultDefs- allMembers def = RG.pgGross def ++ RG.pgDeductions def- assertEqual "Land 3 default groups cover each registry contra exactly once"- (L.sort contraTitles) (L.sort deductionTitles)- assertEqual "Land 3 default group memberships are disjoint"- (L.length (L.concatMap allMembers defaultDefs))- (Set.size (Set.fromList (L.concatMap allMembers defaultDefs)))- forM_ defaultDefs $ \def ->- forM_ (allMembers def) $ \title ->- assertEqual ("Land 3 default title lookup: " ++ show title)- (Just (RG.pgKey def)) (RG.presentationGroupOf title)- forM_ defaultDefs $ \def -> do- forM_ (RG.pgGross def) $ \title ->- assertEqual ("Land 3 gross member is non-contra: " ++ show title)- (Just (RG.pgDivision def, False))- ((\spec -> (Registry.asDivision spec, Registry.asIsContra spec))- <$> Registry.accountSpec title)- forM_ (RG.pgDeductions def) $ \title ->- assertEqual ("Land 3 deduction member is same-division contra: " ++ show title)- (Just (RG.pgDivision def, True))- ((\spec -> (Registry.asDivision spec, Registry.asIsContra spec))- <$> Registry.accountSpec title)-- let exceeded = present [tradeDef]- [ (AccountsReceivable, (100, 0))- , (AllowanceForDoubtfulAccounts, (0, 150))- ]- assertEqual "Land 3 edge: contra exceeding gross yields a negative net"- [(tradeDef,- [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 100)- , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 150)- , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount True 50)- ])]- (RG.gpBlocks exceeded)-- let parentAbsent = present [tradeDef]- [(AllowanceForDoubtfulAccounts, (0, 30))]- assertEqual "Land 3 edge: parent absent still renders deduction and net"- [(tradeDef,- [ RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 30)- , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount True 30)- ])]- (RG.gpBlocks parentAbsent)-- let multiContraDef = tradeDef- { RG.pgGross = [AccountsReceivable]- , RG.pgDeductions =- [AllowanceForDoubtfulAccounts, AccumulatedDepreciation]- }- multipleContra = present [multiContraDef]- [ (AccountsReceivable, (1000, 0))- , (AllowanceForDoubtfulAccounts, (0, 100))- , (AccumulatedDepreciation, (0, 200))- ]- assertEqual "Land 3 edge: multiple contra rows deduct exactly once"- [(multiContraDef,- [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 1000)- , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 100)- , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)- , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount False 700)- ])]- (RG.gpBlocks multipleContra)-- let child = tradeDef { RG.pgParent = Just RG.DepreciableAssetsGroup }- parent = maybe (error "missing DepreciableAssetsGroup") id- (RG.lookupGroupDef defaultDefs RG.DepreciableAssetsGroup)- nested = present [parent, child]- [ (AccountsReceivable, (1000, 0))- , (AllowanceForDoubtfulAccounts, (0, 100))- , (Building, (800, 0))- , (AccumulatedDepreciation, (0, 200))- ]- assertEqual "Land 3 edge: nested child precedes and rolls into parent"- [ (child,- [ RG.GroupRow (RG.GrossRow AccountsReceivable) (amount False 1000)- , RG.GroupRow (RG.DeductionRow AllowanceForDoubtfulAccounts) (amount True 100)- , RG.GroupRow (RG.NetRow RG.TradeReceivablesGroup) (amount False 900)- ])- , (parent,- [ RG.GroupRow (RG.GrossRow Building) (amount False 800)- , RG.GroupRow (RG.SubgroupRow RG.TradeReceivablesGroup) (amount False 900)- , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)- , RG.GroupRow (RG.NetRow RG.DepreciableAssetsGroup) (amount False 1500)- ])- ]- (RG.gpBlocks nested)- assertEqual "Land 3 edge: only the nested root contributes to totals"- (M.singleton Assets (1800, 300)) (RG.gpRootTotals nested)- assertEqual "Land 3 edge: all nested definition members are consumed"- (Set.fromList (L.concatMap allMembers [parent, child]))- (RG.gpConsumed nested)-- let inactiveChild = present [parent, child]- [ (AccountsReceivable, (1000, 0))- , (Building, (800, 0))- , (AccumulatedDepreciation, (0, 200))- ]- assertEqual "Land 3 edge: inactive child gross is not rolled into its parent"- [(parent,- [ RG.GroupRow (RG.GrossRow Building) (amount False 800)- , RG.GroupRow (RG.DeductionRow AccumulatedDepreciation) (amount True 200)- , RG.GroupRow (RG.NetRow RG.DepreciableAssetsGroup) (amount False 600)- ])]- (RG.gpBlocks inactiveChild)- assertEqual "Land 3 edge: inactive child gross remains unconsumed"- (Set.fromList (allMembers parent)) (RG.gpConsumed inactiveChild)- assertEqual "Land 3 edge: inactive child cannot inflate the parent total"- (M.singleton Assets (800, 200)) (RG.gpRootTotals inactiveChild)-- let salesDef = maybe (error "missing NetSalesGroup") id- (RG.lookupGroupDef defaultDefs RG.NetSalesGroup)- offsetContra = present [salesDef]- [ (Sales, (0, 500))- , (SalesRebates, (50, 50))- ]- assertEqual "Land 3 edge: fully offset contra activity still activates its group"- [(salesDef,- [ RG.GroupRow (RG.GrossRow Sales) (amount False 500)- , RG.GroupRow (RG.NetRow RG.NetSalesGroup) (amount False 500)- ])]- (RG.gpBlocks offsetContra)- assertEqual "Land 3 edge: fully offset contra title cannot leak to ordinary rows"- (Set.fromList [Sales, SalesRebates]) (RG.gpConsumed offsetContra)-- let rows f b = L.map (L.map T.unpack) (f b)- excessiveChart = (100 .@ Not:<AccountsReceivable- .+ 150 .@ Not:<AllowanceForDoubtfulAccounts- :: EA.Alg Double (HatBase AccountTitles))- purchasesAndTaxes = (500 .@ Not:<Purchases- .+ 50 .@ Not:<PurchaseRebates- .+ 300 .@ Not:<CorporateIncomeTaxes- .+ 40 .@ Not:<RefundOfIncomeTaxes- :: EA.Alg Double (HatBase AccountTitles))- excessiveRows = rows bsRows excessiveChart- purchasesAndTaxesRows = rows plRows purchasesAndTaxes- assertEqual "Land 3 rendering: contra excess signs both net and asset total"- True- (["TradeReceivablesNet","-50.0","",""] `elem` excessiveRows- && ["Total","-50.0","",""] `elem` excessiveRows)- assertEqual "Land 3 rendering: purchase rebate and net label are pinned"- True- (["PurchaseRebates","-50.0","",""] `elem` purchasesAndTaxesRows- && ["NetPurchases","450.0","",""] `elem` purchasesAndTaxesRows)- assertEqual "Land 3 rendering: tax refund and net label are pinned"- True- (["RefundOfIncomeTaxes","-40.0","",""] `elem` purchasesAndTaxesRows- && ["IncomeTaxesNet","260.0","",""] `elem` purchasesAndTaxesRows)--testLand2Presentation :: IO ()-testLand2Presentation = do- let rows f b = L.map (L.map T.unpack) (f b)- -- Non-contra statements remain byte-identical. Contra statements use- -- Land 3 gross -> deduction -> net presentation.- assertEqual "land2 bsRows b1 (= Land 1)"- [ ["Asset","","Liability",""]- , ["Cash","100.0","LoansPayable","60.0"]- , ["Total","100.0","Equity",""]- , ["","","CapitalStock","40.0"]- , ["","","Total","100.0"] ]- (rows bsRows land2B1)- assertEqual "land3 bsRows b3 contra groups"- [ ["Asset","","Liability",""]- , ["Cash","900.0","LoansPayable","400.0"]- , ["AccountsReceivable","1000.0","Equity",""]- , ["AllowanceForDoubtfulAccounts","-100.0","CapitalStock","2000.0"]- , ["TradeReceivablesNet","900.0","Total","2400.0"]- , ["Building","800.0","",""]- , ["AccumulatedDepreciation","-200.0","",""]- , ["DepreciableAssetsNet","600.0","",""]- , ["Total","2400.0","",""] ]- (rows bsRows land2B3)- assertEqual "land3 bsRows b4 abnormal balances"- [ ["Asset","","Liability",""]- , ["LoansPayable","100.0","Equity",""]- , ["AllowanceForDoubtfulAccounts","20.0","Total","0.0"]- , ["TradeReceivablesNet","20.0","",""]- , ["Building","500.0","",""]- , ["AccumulatedDepreciation","-200.0","",""]- , ["DepreciableAssetsNet","300.0","",""]- , ["Total","420.0","",""] ]- (rows bsRows land2B4)- -- Pre-vocab difference: SalesCost is now closed by the registry-derived- -- Cost rule, so Sales 500 - SalesCost 300 becomes RetainedEarnings 200.- assertEqual "land3 bsRows b5 closing and contra groups"- [ ["Asset","","Liability",""]- , ["Cash","1100.0","LoansPayable","400.0"]- , ["AccountsReceivable","1000.0","Equity",""]- , ["AllowanceForDoubtfulAccounts","-100.0","CapitalStock","2000.0"]- , ["TradeReceivablesNet","900.0","RetainedEarnings","200.0"]- , ["Building","800.0","Total","2600.0"]- , ["AccumulatedDepreciation","-200.0","",""]- , ["DepreciableAssetsNet","600.0","",""]- , ["Total","2600.0","",""] ]- (rows bsRows land2B5)- assertEqual "land2 plRows b2 (= Land 1)"- [ ["Cost","","Revenue",""]- , ["SalesCost","300.0","Sales","500.0"]- , ["Total","500.0","Total","300.0"] ]- (rows plRows land2B2)- -- projections: 資産系は Land 1 と一致, liability 系は contra が消える (意図的差分),- -- contra は projContraAssets のみが Hat/Not 双方を保持して選ぶ。- assertEqual "land2 projCurrentAssets b3 (= Land 1)"- "900.00:@Not:<Cash .+ 1000.00:@Not:<AccountsReceivable"- (show (EA.projCurrentAssets land2B3))- assertEqual "land2 projCurrentLiability b3 (intentional: contra dropped)"- "400.00:@Not:<LoansPayable"- (show (EA.projCurrentLiability land2B3))- assertEqual "land2 projFixedLiability b3 (intentional: contra dropped)"- "0"- (show (EA.projFixedLiability land2B3))- assertEqual "land2 projContraAssets b3"- "100.00:@Not:<AllowanceForDoubtfulAccounts .+ 200.00:@Not:<AccumulatedDepreciation"- (show (EA.projContraAssets land2B3))- assertEqual "land2 projCurrentAssets b4 (= Land 1; no contra Hat leakage)"- "30.00:@Not:<Cash"- (show (EA.projCurrentAssets land2B4))- assertEqual "land2 projFixedAssets b4 (= Land 1; no contra Hat leakage)"- "500.00:@Not:<Building"- (show (EA.projFixedAssets land2B4))- assertEqual "land2 projCurrentLiability b4 (intentional: contra dropped)"- "200.00:@Not:<LoansPayable"- (show (EA.projCurrentLiability land2B4))- assertEqual "land2 projContraAssets b4 keeps Hat and Not, excludes Cash"- "120.00:@Hat:<AllowanceForDoubtfulAccounts .+ 100.00:@Not:<AllowanceForDoubtfulAccounts .+ 50.00:@Hat:<AccumulatedDepreciation .+ 250.00:@Not:<AccumulatedDepreciation"- (show (EA.projContraAssets land2B4))- assertEqual "land2 projContraAssets b5"- "100.00:@Not:<AllowanceForDoubtfulAccounts .+ 200.00:@Not:<AccumulatedDepreciation"- (show (EA.projContraAssets land2B5))---- Current presentation battery. Exact Land 3 rows are pinned above; this dump--- is also compared structurally with the pre-Land 2 fixture below.-land2PresentationLines :: [T.Text]-land2PresentationLines = L.concatMap sect- [ ("b1-basic", land2B1), ("b2-pl", land2B2), ("b3-contra", land2B3)- , ("b4-abnormal", land2B4), ("b5-closing", land2B5) ]- where- sect (n, a) =- (T.pack ("## " ++ n))- : T.pack "-- bsRows"- : L.map (T.pack . show . L.map T.unpack) (bsRows a)- ++ T.pack "-- plRows"- : L.map (T.pack . show . L.map T.unpack) (plRows a)- ++ L.concat [ [T.pack ("-- " ++ pn), T.pack (show (pf a))] | (pn, pf) <- projList ]- projList =- [ ("projCurrentAssets", EA.projCurrentAssets)- , ("projFixedAssets", EA.projFixedAssets)- , ("projDeferredAssets", EA.projDeferredAssets)- , ("projCurrentLiability", EA.projCurrentLiability)- , ("projFixedLiability", EA.projFixedLiability)- , ("projCapitalStock", EA.projCapitalStock)- ]--testLand2PresentationClosedDiff :: IO ()-testLand2PresentationClosedDiff = do- fixture <- TIO.readFile "test/fixtures/pre-land2/presentation.txt"- let oldLines = L.filter (not . T.null) (L.drop 1 (T.lines fixture))- newLines = land2PresentationLines- names = L.map T.pack ["b1-basic", "b2-pl", "b3-contra", "b4-abnormal", "b5-closing"]- markers = L.map T.pack- [ "-- bsRows", "-- plRows", "-- projCurrentAssets"- , "-- projFixedAssets", "-- projDeferredAssets"- , "-- projCurrentLiability", "-- projFixedLiability"- , "-- projCapitalStock"- ]- section name xs = takeWhile (not . T.isPrefixOf (T.pack "## "))- (drop 1 (dropWhile (/= (T.pack "## " <> name)) xs))- block marker xs = marker- : takeWhile (not . T.isPrefixOf (T.pack "-- "))- (drop 1 (dropWhile (/= marker) xs))- changedBlocks =- [ (name, marker)- | name <- names- , marker <- markers- , block marker (section name oldLines) /= block marker (section name newLines)- ]- assertEqual "Land 3 closed diff: only adjudicated battery blocks changed"- [ (T.pack "b2-pl", T.pack "-- bsRows")- , (T.pack "b3-contra", T.pack "-- bsRows")- , (T.pack "b3-contra", T.pack "-- projCurrentLiability")- , (T.pack "b3-contra", T.pack "-- projFixedLiability")- , (T.pack "b4-abnormal", T.pack "-- bsRows")- , (T.pack "b4-abnormal", T.pack "-- projCurrentLiability")- , (T.pack "b4-abnormal", T.pack "-- projFixedLiability")- , (T.pack "b5-closing", T.pack "-- bsRows")- , (T.pack "b5-closing", T.pack "-- projCurrentLiability")- , (T.pack "b5-closing", T.pack "-- projFixedLiability")- ]- changedBlocks---- HatNot は whichSide で明示 error (規約の regression 固定)-testLand2HatNotPolicy :: IO ()-testLand2HatNotPolicy = do- r <- try (evaluate (whichSide (HatNot :< Cash :: HatBase AccountTitles)))- :: IO (Either SomeException Side)- assertEqual "land2 whichSide HatNot policy: explicit error"- True (case r of Left _ -> True; Right _ -> False)---- ================================================================--- ExchangeAlgebra.Convert.Checked: checked construction for generated entries.--- ================================================================--type CheckedAlgM = EA.Alg MoneyDecimal (HatBase AccountTitles)-type CheckedJournalM = EJ.Journal Int MoneyDecimal (HatBase AccountTitles)--checkedEntryM :: [(Side, AccountTitles, MoneyDecimal)]- -> Either (NE.NonEmpty (ECC.EntryError MoneyDecimal)) CheckedAlgM-checkedEntryM = ECC.checkedEntry--checkedJournalM :: [(Int, [(Side, AccountTitles, MoneyDecimal)])]- -> Either (NE.NonEmpty (ECC.JournalError Int MoneyDecimal)) CheckedJournalM-checkedJournalM = ECC.checkedJournal--genPositiveAmountMD :: Gen MoneyDecimal-genPositiveAmountMD = fromInteger <$> choose (1, 9999)--genCheckedAmountMD :: Gen MoneyDecimal-genCheckedAmountMD = fromInteger <$> choose (-5, 20)--genCheckedSide :: Gen Side-genCheckedSide = frequency- [ (8, elements [Debit, Credit])- , (1, pure Side)- ]--genCheckedAccountTitle :: Gen AccountTitles-genCheckedAccountTitle = frequency- [ (12, genAccountTitle)- , (1, pure AccountTitle)- ]--genCheckedPosting :: Gen (Side, AccountTitles, MoneyDecimal)-genCheckedPosting =- (,,) <$> genCheckedSide <*> genCheckedAccountTitle <*> genCheckedAmountMD--genAcceptedEntryRows :: Gen [(Side, AccountTitles, MoneyDecimal)]-genAcceptedEntryRows = do- amount <- genPositiveAmountMD- debitAccount <- genOrdinaryPostingTitle- creditAccount <- genOrdinaryPostingTitle- pure [ (Debit, debitAccount, amount)- , (Credit, creditAccount, amount)- ]--genOrdinaryPostingTitle :: Gen AccountTitles-genOrdinaryPostingTitle = elements- [ title- | title <- Registry.concreteAccountTitles- , Just semantics <- [Registry.accountSemantics title]- , Registry.asemPostingCapability semantics == OrdinaryPosting- ]--genCheckedEntryRows :: Gen [(Side, AccountTitles, MoneyDecimal)]-genCheckedEntryRows = frequency- [ (5, resize 8 (listOf genCheckedPosting))- , (3, genAcceptedEntryRows)- , (1, pure [])- ]--genKnownCertPosting :: Gen (Side, AccountTitles, MoneyDecimal)-genKnownCertPosting =- (,,) <$> elements [Debit, Credit] <*> genAccountTitle <*> genCheckedAmountMD--genKnownCertJournal :: Gen [(Int, [(Side, AccountTitles, MoneyDecimal)])]-genKnownCertJournal = do- rows <- resize 6 (listOf (resize 6 (listOf genKnownCertPosting)))- pure (zip [1..] rows)--sideTextForCert :: Side -> T.Text-sideTextForCert Debit = T.pack "debit"-sideTextForCert Credit = T.pack "credit"-sideTextForCert Side = T.pack "Side"--textJournalForCert- :: [(Int, [(Side, AccountTitles, MoneyDecimal)])]- -> [(Int, [(T.Text, T.Text, MoneyDecimal)])]-textJournalForCert = L.map renderEntry- where- renderEntry (txid, rows) = (txid, L.map renderPosting rows)- renderPosting (side, account, amount) =- (sideTextForCert side, T.pack (show account), amount)--checkedJournalTextReference- :: [(Int, [(T.Text, T.Text, MoneyDecimal)])]- -> Either (NE.NonEmpty (ECC.JournalError Int MoneyDecimal)) CheckedJournalM-checkedJournalTextReference entries =- case errors of- [] -> Right (L.foldl' (.+) mempty journals)- e : es -> Left (e NE.:| es)- where- checked =- [ (txid, ECC.checkedEntryText rows)- | (txid, rows) <- entries- ]- errors =- [ ECC.EntryErrors txid errs- | (txid, Left errs) <- checked- ]- journals =- [ alg .| txid- | (txid, Right alg) <- checked- ]--prop_certifyKnownAccountsMatchesCheckedJournal :: Property-prop_certifyKnownAccountsMatchesCheckedJournal =- forAll genKnownCertJournal $ \entries ->- let textEntries = textJournalForCert entries- in case ( ECC.certifyJournalText textEntries- , checkedJournalTextReference textEntries- , checkedJournalM entries- ) of- (ECC.FullyResolved actual, Right textExpected, Right expected) ->- EJ.toMap actual == EJ.toMap textExpected- && EJ.toMap actual == EJ.toMap expected- (ECC.Rejected _, Left _, Left _) -> True- _ -> False--prop_certifyUnknownAccountPreservesBalance :: Property-prop_certifyUnknownAccountPreservesBalance =- forAll genAcceptedEntryRows $ \rows ->- let replaceAccount (side, _, amount) =- (sideTextForCert side, T.pack "NoSuchAccount_XYZ", amount)- in case ECC.certifyJournalText [(1 :: Int, L.map replaceAccount rows)] of- ECC.BalancedUnresolved {} -> True- _ -> False--prop_certifyImbalancePrecedesUnknownAccount :: Property-prop_certifyImbalancePrecedesUnknownAccount =- forAll genPositiveAmountMD $ \amount ->- let input =- [ (1 :: Int,- [ (T.pack "debit", T.pack "NoSuchAccount_XYZ", amount)- , (T.pack "credit", T.pack "Cash", amount + 1)- ])- ]- in case ECC.certifyJournalText input of- ECC.Rejected errs -> any journalHasImbalance (NE.toList errs)- _ -> False- where- journalHasImbalance (ECC.EntryErrors _ errs) =- any isImbalanced (NE.toList errs)- journalHasImbalance _ = False-- isImbalanced ECC.Imbalanced {} = True- isImbalanced _ = False--prop_certifyDuplicateTxIdAlwaysRejected :: Property-prop_certifyDuplicateTxIdAlwaysRejected =- forAll genAcceptedEntryRows $ \rows ->- let input = textJournalForCert [(1, rows), (1, rows)]- in case ECC.certifyJournalText input of- ECC.Rejected errs -> ECC.DuplicateTxId 1 `elem` NE.toList errs- _ -> False--prop_certifyBalancedUnresolvedTotals :: Property-prop_certifyBalancedUnresolvedTotals =- forAll genPositiveAmountMD $ \amount ->- let input =- [ (1 :: Int,- [ (T.pack "debit", T.pack "NoSuchAccount_XYZ", amount)- , (T.pack "credit", T.pack "Sales", amount)- ])- ]- in case ECC.certifyJournalText input of- ECC.BalancedUnresolved- { ECC._certDebitTotal = debitTotal- , ECC._certCreditTotal = creditTotal- } -> debitTotal == creditTotal- && debitTotal == amount- && creditTotal == amount- _ -> False--checkedEntryAcceptsSpec :: [(Side, AccountTitles, MoneyDecimal)] -> Bool-checkedEntryAcceptsSpec rows =- not (null rows)- && all validPosting rows- && ECC.exactBalanced (EC.journalFromSides rows :: CheckedAlgM)- where- validPosting (side, account, amount) =- side /= Side- && account /= AccountTitle- && maybe False- (ECC.postingAllowedIn ECC.OrdinaryJournal- . Registry.asemPostingCapability)- (Registry.accountSemantics account)- && amount > 0- && not (EA.isErrorValue amount)--data ConsolidationFixtureRow- = FixturePosting String String [String]- AccountTitles AccountTitles MoneyDecimal- | FixtureLink String String MoneyDecimal- deriving (Show, Eq)--parseConsolidationFixtureRow :: T.Text -> Either String ConsolidationFixtureRow-parseConsolidationFixtureRow line = case T.splitOn (T.pack "\t") line of- [kind, rowId, sourceIdsText, debitText, creditText, amountText]- | kind == T.pack "source" || kind == T.pack "adjustment" -> do- debitAccount <- firstShow (EC.parseAccountTitle debitText)- creditAccount <- firstShow (EC.parseAccountTitle creditText)- amount <- parseAmount amountText- let sourceIds- | sourceIdsText == T.pack "-" = []- | otherwise = L.map T.unpack- (T.splitOn (T.pack "|") sourceIdsText)- Right (FixturePosting (T.unpack kind) (T.unpack rowId) sourceIds- debitAccount creditAccount amount)- | kind == T.pack "link" -> do- amount <- parseAmount amountText- Right (FixtureLink (T.unpack rowId) (T.unpack sourceIdsText) amount)- fields -> Left ("invalid consolidation fixture row: " ++ show fields)- where- firstShow (Left err) = Left (show err)- firstShow (Right value) = Right value- parseAmount amountText = case reads (T.unpack amountText) of- [(amount, "")] -> Right (fromInteger amount)- _ -> Left ("invalid fixture amount: " ++ T.unpack amountText)--loadMinimalConsolidationFixture- :: IO (CW.WorksheetInput String String MoneyDecimal)-loadMinimalConsolidationFixture = do- contents <- TIO.readFile- "test/fixtures/consolidation-worksheet-050/minimal.tsv"- rows <- case traverse parseConsolidationFixtureRow- (filter (not . T.null) (drop 1 (T.lines contents))) of- Left err -> fail err- Right parsed -> pure parsed- let postings =- [ (kind, rowId, sourceIds, debitAccount, creditAccount, amount)- | FixturePosting kind rowId sourceIds debitAccount creditAccount amount- <- rows- ]- sources =- [ CW.TrialBalanceSource rowId- (EC.journalFromSides- [ (Debit, debitAccount, amount)- , (Credit, creditAccount, amount)- ] :: CheckedAlgM)- | (kind, rowId, _, debitAccount, creditAccount, amount) <- postings- , kind == "source"- ]- adjustments =- [ CW.WorksheetAdjustment rowId (sourceId NE.:| sourceIdsTail)- (EC.journalFromSides- [ (Debit, debitAccount, amount)- , (Credit, creditAccount, amount)- ] :: CheckedAlgM)- | (kind, rowId, sourceId : sourceIdsTail,- debitAccount, creditAccount, amount) <- postings- , kind == "adjustment"- ]- links = M.fromList- [ (rowId, (direction, amount))- | FixtureLink rowId direction amount <- rows- ]- sourceList <- case NE.nonEmpty sources of- Nothing -> fail "minimal consolidation fixture has no sources"- Just nonEmptySources -> pure nonEmptySources- plResult <- fixtureResult links "pl-net-income"- ownersPlResult <- fixtureResult links "pl-parent-net-income"- ownersSsResult <- fixtureResult links "ss-parent-net-income"- nciResult <- fixtureResult links "nci-period-share"- openingRetained <- fixtureBalance links "opening-retained-earnings"- retainedDividends <- fixtureAmount links "retained-earnings-dividends"- ssClosingRetained <- fixtureBalance links "ss-closing-retained-earnings"- bsRetained <- fixtureBalance links "bs-retained-earnings"- openingNci <- fixtureBalance links "opening-nci"- nciDividends <- fixtureAmount links "nci-dividends"- closingNci <- fixtureBalance links "closing-nci"- bsNci <- fixtureBalance links "bs-nci"- pure (CW.WorksheetInput sourceList adjustments- (CW.WorksheetLinkage- { CW._profitOrLossNetIncome = plResult- , CW._profitOrLossNetIncomeAttributableToOwners = ownersPlResult- , CW._statementOfChangesNetIncomeAttributableToOwners =- ownersSsResult- , CW._openingRetainedEarnings = openingRetained- , CW._retainedEarningsDividends = retainedDividends- , CW._statementOfChangesClosingRetainedEarnings = ssClosingRetained- , CW._balanceSheetRetainedEarnings = bsRetained- , CW._openingNonControllingInterests = openingNci- , CW._nonControllingInterestsPeriodShare = nciResult- , CW._nonControllingInterestsDividends = nciDividends- , CW._statementOfChangesClosingNonControllingInterests = closingNci- , CW._balanceSheetNonControllingInterests = bsNci- }))- where- fixtureAmount links key = case M.lookup key links of- Just ("amount", amount) -> pure amount- Just (direction, _) -> fail- ("expected amount direction for " ++ key ++ ", got " ++ direction)- Nothing -> fail ("missing fixture link: " ++ key)- fixtureResult links key = case M.lookup key links of- Just ("profit", amount) -> pure (CW.PeriodProfit amount)- Just ("loss", amount) -> pure (CW.PeriodLoss amount)- Just ("break-even", _) -> pure CW.PeriodBreakEven- Just (direction, _) -> fail- ("invalid period-result direction for " ++ key ++ ": " ++ direction)- Nothing -> fail ("missing fixture link: " ++ key)- fixtureBalance links key = case M.lookup key links of- Just ("credit", amount) -> pure (CW.CreditBalance amount)- Just ("debit", amount) -> pure (CW.DebitBalance amount)- Just (direction, _) -> fail- ("invalid balance direction for " ++ key ++ ": " ++ direction)- Nothing -> fail ("missing fixture link: " ++ key)--leftErrors :: Either (NE.NonEmpty e) a -> Maybe (NE.NonEmpty e)-leftErrors (Left errors) = Just errors-leftErrors (Right _) = Nothing--testConsolidationWorksheet :: IO ()-testConsolidationWorksheet = do- fixture <- loadMinimalConsolidationFixture- validated <- case CW.validateConsolidationWorksheet fixture of- Left errors -> fail ("valid consolidation fixture rejected: " ++ show errors)- Right value -> pure value- assertEqual "consolidation worksheet: source provenance retained"- ["parent", "subsidiary"]- [ CW._sourceId source- | source <- NE.toList (CW.validatedSources validated)- ]- assertEqual "consolidation worksheet: adjustment provenance retained"- [("nci-attribution", ["parent", "subsidiary"])]- [ ( CW._adjustmentId adjustment- , NE.toList (CW._adjustmentSourceIds adjustment)- )- | adjustment <- CW.validatedAdjustments validated- ]- let combined = CW.combinedWorksheet validated- assertEqual "consolidation worksheet: combined fixture stays balanced"- True (ECC.exactBalanced combined)- assertEqual "consolidation worksheet: combined debit total"- (200 :: MoneyDecimal) (EA.norm (EA.decL combined))- assertEqual "consolidation worksheet: combined credit total"- (200 :: MoneyDecimal) (EA.norm (EA.decR combined))- assertEqual "consolidation worksheet: combination preserves all sequences"- 6 (L.length (EA.vals combined))-- let rawAdjustment =- (10 EA..@ (Not :< Cash)) EA..+- (5 EA..@ (Not :< Cash)) EA..+- (15 EA..@ (Not :< Sales)) :: CheckedAlgM- rawInput = CW.WorksheetInput (CW._worksheetSources fixture)- [CW.WorksheetAdjustment "raw-three-posting"- ("parent" NE.:| []) rawAdjustment]- (CW._worksheetLinkage fixture)- assertEqual "consolidation worksheet: accepts non-journal-shaped raw Alg"- True- (case CW.validateConsolidationWorksheet rawInput of- Right _ -> True- Left _ -> False)-- let sources = CW._worksheetSources fixture- links = CW._worksheetLinkage fixture- debitOnly = EC.journalFromSides- [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM- creditOnly = EC.journalFromSides- [(Credit, Sales, 10 :: MoneyDecimal)] :: CheckedAlgM- cancellingSet = debitOnly EA..+ creditOnly- cancellingInput = CW.WorksheetInput sources- [ CW.WorksheetAdjustment "bad-debit" ("parent" NE.:| []) debitOnly- , CW.WorksheetAdjustment "bad-credit" ("parent" NE.:| []) creditOnly- ] links- assertEqual "consolidation worksheet: malformed set can balance in aggregate"- True (ECC.exactBalanced cancellingSet)- assertEqual "consolidation worksheet: atomic gate rejects both malformed adjustments"- (Just- ( CW.UnbalancedAdjustment "bad-debit" 10 0 NE.:|- [CW.UnbalancedAdjustment "bad-credit" 0 10]- ))- (leftErrors (CW.validateConsolidationWorksheet cancellingInput))-- let forbidden = EC.journalFromSides- [ (Debit, NetIncome, 10 :: MoneyDecimal)- , (Credit, RetainedEarnings, 10)- ] :: CheckedAlgM- forbiddenInput = CW.WorksheetInput sources- [CW.WorksheetAdjustment "forbidden" ("parent" NE.:| []) forbidden]- links- assertEqual "consolidation worksheet: context capability applies to raw Alg"- (Just (CW.AdjustmentPostingNotAllowed "forbidden" NetIncome- EngineGeneratedOnly NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet forbiddenInput))-- let provenanceInput = CW.WorksheetInput sources- [CW.WorksheetAdjustment "unknown"- ("ghost" NE.:| ["ghost"]) (mempty :: CheckedAlgM)] links- assertEqual "consolidation worksheet: provenance rejects duplicate and unknown source"- (Just- ( CW.DuplicateAdjustmentSource "unknown" "ghost" NE.:|- [ CW.UnknownAdjustmentSource "unknown" "ghost"- , CW.EmptyAdjustment "unknown"- ]- ))- (leftErrors (CW.validateConsolidationWorksheet provenanceInput))-- let mismatchedLinks = links- { CW._statementOfChangesNetIncomeAttributableToOwners =- CW.PeriodProfit 40 }- mismatchedInput = fixture { CW._worksheetLinkage = mismatchedLinks }- assertEqual "consolidation worksheet: P/L to S/S mismatch is explicit"- True- (case CW.validateConsolidationWorksheet mismatchedInput of- Left errors -> CW.OwnersPeriodResultLinkMismatch- (CW.PeriodProfit 50) (CW.PeriodProfit 40) `elem` NE.toList errors- Right _ -> False)-- let attributionLinks = links- { CW._profitOrLossNetIncome = CW.PeriodProfit 60 }- attributionInput = fixture { CW._worksheetLinkage = attributionLinks }- assertEqual "consolidation worksheet: total attribution mismatch is explicit"- True- (case CW.validateConsolidationWorksheet attributionInput of- Left errors -> CW.NetIncomeAttributionMismatch 60 70- `elem` NE.toList errors- Right _ -> False)-- let retainedLinks = links- { CW._retainedEarningsDividends = 11 }- retainedInput = fixture { CW._worksheetLinkage = retainedLinks }- assertEqual "consolidation worksheet: retained-earnings mismatch is explicit"- True- (case CW.validateConsolidationWorksheet retainedInput of- Left errors -> CW.RetainedEarningsRollForwardMismatch 150 151- `elem` NE.toList errors- Right _ -> False)-- let balanceSheetLinks = links- { CW._balanceSheetRetainedEarnings = CW.CreditBalance 139 }- balanceSheetInput = fixture { CW._worksheetLinkage = balanceSheetLinks }- assertEqual "consolidation worksheet: S/S to B/S mismatch is explicit"- True- (case CW.validateConsolidationWorksheet balanceSheetInput of- Left errors -> CW.BalanceSheetRetainedEarningsMismatch- (CW.CreditBalance 140) (CW.CreditBalance 139)- `elem` NE.toList errors- Right _ -> False)-- let nciLinks = links { CW._nonControllingInterestsDividends = 6 }- nciInput = fixture { CW._worksheetLinkage = nciLinks }- assertEqual "consolidation worksheet: NCI roll-forward mismatch is explicit"- True- (case CW.validateConsolidationWorksheet nciInput of- Left errors -> CW.NonControllingInterestsRollForwardMismatch 50 51- `elem` NE.toList errors- Right _ -> False)-- let nciBalanceSheetLinks = links- { CW._balanceSheetNonControllingInterests = CW.CreditBalance 44 }- nciBalanceSheetInput = fixture- { CW._worksheetLinkage = nciBalanceSheetLinks }- assertEqual "consolidation worksheet: NCI S/S to B/S mismatch is explicit"- True- (case CW.validateConsolidationWorksheet nciBalanceSheetInput of- Left errors -> CW.BalanceSheetNonControllingInterestsMismatch- (CW.CreditBalance 45) (CW.CreditBalance 44)- `elem` NE.toList errors- Right _ -> False)-- let lossLinks = links- { CW._profitOrLossNetIncome = CW.PeriodLoss 25- , CW._profitOrLossNetIncomeAttributableToOwners = CW.PeriodLoss 20- , CW._statementOfChangesNetIncomeAttributableToOwners =- CW.PeriodLoss 20- , CW._openingRetainedEarnings = CW.CreditBalance 100- , CW._retainedEarningsDividends = 10- , CW._statementOfChangesClosingRetainedEarnings =- CW.CreditBalance 70- , CW._balanceSheetRetainedEarnings = CW.CreditBalance 70- , CW._openingNonControllingInterests = CW.CreditBalance 30- , CW._nonControllingInterestsPeriodShare = CW.PeriodLoss 5- , CW._nonControllingInterestsDividends = 5- , CW._statementOfChangesClosingNonControllingInterests =- CW.CreditBalance 20- , CW._balanceSheetNonControllingInterests = CW.CreditBalance 20- }- lossInput = fixture { CW._worksheetLinkage = lossLinks }- assertEqual "consolidation worksheet: loss roll-forwards preserve direction"- True- (case CW.validateConsolidationWorksheet lossInput of- Right _ -> True- Left _ -> False)-- let deficitLinks = links- { CW._profitOrLossNetIncome = CW.PeriodLoss 50- , CW._profitOrLossNetIncomeAttributableToOwners = CW.PeriodLoss 50- , CW._statementOfChangesNetIncomeAttributableToOwners =- CW.PeriodLoss 50- , CW._openingRetainedEarnings = CW.CreditBalance 10- , CW._retainedEarningsDividends = 0- , CW._statementOfChangesClosingRetainedEarnings =- CW.DebitBalance 40- , CW._balanceSheetRetainedEarnings = CW.DebitBalance 40- , CW._openingNonControllingInterests = CW.CreditBalance 0- , CW._nonControllingInterestsPeriodShare = CW.PeriodBreakEven- , CW._nonControllingInterestsDividends = 0- , CW._statementOfChangesClosingNonControllingInterests =- CW.CreditBalance 0- , CW._balanceSheetNonControllingInterests = CW.CreditBalance 0- }- deficitInput = fixture { CW._worksheetLinkage = deficitLinks }- assertEqual "consolidation worksheet: accumulated deficit is structural"- True- (case CW.validateConsolidationWorksheet deficitInput of- Right _ -> True- Left _ -> False)-- let invalidLinks = links- { CW._openingRetainedEarnings = CW.CreditBalance (-1) }- invalidInput = fixture { CW._worksheetLinkage = invalidLinks }- assertEqual "consolidation worksheet: negative linkage amount rejected"- (Just (CW.InvalidLinkAmount CW.OpeningRetainedEarnings (-1) NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet invalidInput))-- let wildcardAccount =- (10 EA..@ (Not :< AccountTitle)) EA..+- (10 EA..@ (Hat :< Cash)) :: CheckedAlgM- wildcardInput = CW.WorksheetInput sources- [CW.WorksheetAdjustment "wildcard" ("parent" NE.:| [])- wildcardAccount] links- assertEqual "consolidation worksheet: wildcard error list is total"- (Just (CW.WildcardAdjustmentAccount "wildcard" NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet wildcardInput))-- let duplicateSources = case sources of- source NE.:| rest -> source NE.:| (source : rest)- duplicateAdjustments =- [ CW.WorksheetAdjustment "same" ("parent" NE.:| [])- rawAdjustment- , CW.WorksheetAdjustment "same" ("parent" NE.:| [])- rawAdjustment- ]- duplicateInput = CW.WorksheetInput duplicateSources- duplicateAdjustments links- assertEqual "consolidation worksheet: duplicate stable IDs rejected"- True- (case CW.validateConsolidationWorksheet duplicateInput of- Left errors -> CW.DuplicateSourceId "parent" `elem` NE.toList errors- && CW.DuplicateAdjustmentId "same" `elem` NE.toList errors- Right _ -> False)-- let wildcardSource = CW.TrialBalanceSource "wild-source"- (10 :@ (HatNot :< Cash) :: CheckedAlgM)- wildcardSourceInput- :: CW.WorksheetInput String String MoneyDecimal- wildcardSourceInput = CW.WorksheetInput- (wildcardSource NE.:| []) [] links- assertEqual "consolidation worksheet: wildcard source side is total"- (Just (CW.WildcardSourceSide "wild-source" NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet wildcardSourceInput))-- let wildcardSide =- 10 :@ (HatNot :< Cash) :: CheckedAlgM- wildcardSideInput = CW.WorksheetInput sources- [CW.WorksheetAdjustment "wild-side" ("parent" NE.:| [])- wildcardSide] links- assertEqual "consolidation worksheet: wildcard adjustment side is total"- (Just (CW.WildcardAdjustmentSide "wild-side" NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet wildcardSideInput))-- let unbalancedSource = CW.TrialBalanceSource "unbalanced-source"- (EC.journalFromSides- [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM)- unbalancedSourceInput- :: CW.WorksheetInput String String MoneyDecimal- unbalancedSourceInput = CW.WorksheetInput- (unbalancedSource NE.:| []) [] links- assertEqual "consolidation worksheet: unbalanced source rejected"- (Just (CW.UnbalancedSourceTrialBalance- "unbalanced-source" 10 0 NE.:| []))- (leftErrors (CW.validateConsolidationWorksheet unbalancedSourceInput))--trialBalanceInput- :: CheckedAlgM- -> TB.TrialBalanceStage- -> TB.TrialBalanceInput MoneyDecimal-trialBalanceInput alg stage = TB.TrialBalanceInput- { TB._trialBalanceElement = alg- , TB._trialBalanceStage = stage- , TB._temporaryBalanceExplanations = M.empty- , TB._reclassificationRules = []- , TB._maturityEvidenceTitles = Set.empty- }--testSharedAccountBalancePrimitives :: IO ()-testSharedAccountBalancePrimitives = do- let balances =- [ TBB.NoBalance- , TBB.DebitBalance 7- , TBB.CreditBalance 11- ] :: [TBB.AccountBalance Int]- assertEqual "account balance: pair netting round trip"- balances (fmap (TBB.netPair . TBB.balancePair) balances)- assertEqual "account balance: structural sides"- [Side, Debit, Credit] (fmap TBB.balanceSide balances)--testTrialBalanceValidation :: IO ()-testTrialBalanceValidation = do- let reciprocalMismatch = EC.journalFromSides- [ (Debit, BranchCurrentAccount, 40 :: MoneyDecimal)- , (Credit, HeadOfficeCurrentAccount, 30)- , (Credit, CapitalStock, 10)- ] :: CheckedAlgM- reciprocalInput = trialBalanceInput reciprocalMismatch TB.BeforeClosing- expectedReciprocal = TB.ReciprocalMismatch- (TB.DebitBalance 40) (TB.CreditBalance 30)- assertEqual "trial balance: reciprocal mismatch independent of global balance"- (Just (expectedReciprocal NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.strictTrialBalancePolicy reciprocalInput))- assertEqual "trial balance: two-sided mismatch is never a standalone waiver"- (Just (expectedReciprocal NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy reciprocalInput))- let standaloneReciprocal = EC.journalFromSides- [ (Debit, BranchCurrentAccount, 40 :: MoneyDecimal)- , (Credit, CapitalStock, 40)- ] :: CheckedAlgM- standaloneInput = trialBalanceInput standaloneReciprocal TB.BeforeClosing- expectedStandalone = TB.StandaloneReciprocalBalance- BranchCurrentAccount (TB.DebitBalance 40)- standalone <- case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy standaloneInput of- Left errors -> fail ("standalone reciprocal balance rejected: " ++ show errors)- Right value -> pure value- assertEqual "trial balance: standalone policy retains permitted finding"- [expectedStandalone] (TB.validatedFindings standalone)-- let explainedSuspense = EC.journalFromSides- [ (Debit, SuspensePayments, 10 :: MoneyDecimal)- , (Credit, CapitalStock, 10)- ] :: CheckedAlgM- explanation = T.pack "invoice received after reporting date"- explainedInput = (trialBalanceInput explainedSuspense TB.BeforeClosing)- { TB._temporaryBalanceExplanations =- M.singleton SuspensePayments explanation }- expectedExplained = TB.ExplainedTemporaryBalance SuspensePayments- (TB.DebitBalance 10) explanation- explained <- case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy explainedInput of- Left errors -> fail ("explained suspense balance rejected: " ++ show errors)- Right value -> pure value- assertEqual "trial balance: explained temporary balance retained"- [expectedExplained] (TB.validatedFindings explained)- assertEqual "trial balance: policy can block explained temporary balance"- (Just (expectedExplained NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.strictTrialBalancePolicy explainedInput))- let unresolvedInput = trialBalanceInput explainedSuspense TB.BeforeClosing- assertEqual "trial balance: unexplained temporary balance blocks"- (Just (TB.UnresolvedTemporaryBalance SuspensePayments- (TB.DebitBalance 10) NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy unresolvedInput))- let blankInput = explainedInput- { TB._temporaryBalanceExplanations =- M.singleton SuspensePayments (T.pack " ") }- assertEqual "trial balance: blank explanation never opens the gate"- (Just (TB.BlankTemporaryExplanation SuspensePayments- (TB.DebitBalance 10) NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy blankInput))-- let closingResidual = EC.journalFromSides- [ (Debit, CashOverShort, 10 :: MoneyDecimal)- , (Credit, IncomeSummary, 10)- ] :: CheckedAlgM- closingInput = trialBalanceInput closingResidual TB.AfterClosing- assertEqual "trial balance: closing devices must be zero after closing"- (Just- ( TB.ClosingDeviceResidual CashOverShort (TB.DebitBalance 10)- NE.:|- [TB.ClosingDeviceResidual IncomeSummary (TB.CreditBalance 10)]- ))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy closingInput))- let unclosedNominal = EC.journalFromSides- [ (Debit, Cash, 10 :: MoneyDecimal)- , (Credit, Sales, 10)- ] :: CheckedAlgM- assertEqual "trial balance: nominal balances must close after closing"- (Just (TB.UnclosedNominalBalance Sales- (TB.CreditBalance 10) NE.:| []))- (leftErrors (TB.validateTrialBalance TB.standaloneTrialBalancePolicy- (trialBalanceInput unclosedNominal TB.AfterClosing)))-- let derivedResidual = EC.journalFromSides- [ (Debit, NetIncome, 10 :: MoneyDecimal)- , (Credit, CapitalStock, 10)- ] :: CheckedAlgM- assertEqual "trial balance: derived coordinates are residuals after closing"- (Just (TB.DerivedCoordinateResidual NetIncome- (TB.DebitBalance 10) NE.:| []))- (leftErrors (TB.validateTrialBalance TB.standaloneTrialBalancePolicy- (trialBalanceInput derivedResidual TB.AfterClosing)))-- let abnormalDeposit = EC.journalFromSides- [ (Debit, Cash, 10 :: MoneyDecimal)- , (Credit, CurrentDeposits, 10)- ] :: CheckedAlgM- abnormalInput = trialBalanceInput abnormalDeposit TB.BeforeClosing- oneRule = TB.SideReclassificationRule CurrentDeposits Credit- (ShortTermLoansPayable NE.:| [])- twoRules = TB.SideReclassificationRule CurrentDeposits Credit- (BankOverdraft NE.:| [ShortTermLoansPayable])- assertEqual "trial balance: unexplained abnormal side is explicit"- (Just (TB.UnexplainedAbnormalBalance CurrentDeposits Debit- (TB.CreditBalance 10) NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy abnormalInput))- assertEqual "trial balance: unique reclassification is an instruction"- (Just (TB.AbnormalBalanceWithReclassificationRule CurrentDeposits- (TB.CreditBalance 10) ShortTermLoansPayable NE.:| []))- (leftErrors (TB.validateTrialBalance TB.standaloneTrialBalancePolicy- abnormalInput { TB._reclassificationRules = [oneRule] }))- assertEqual "trial balance: ambiguous reclassification is never automatic"- (Just (TB.AmbiguousReclassification CurrentDeposits- (TB.CreditBalance 10)- (BankOverdraft NE.:| [ShortTermLoansPayable]) NE.:| []))- (leftErrors (TB.validateTrialBalance TB.standaloneTrialBalancePolicy- abnormalInput { TB._reclassificationRules = [twoRules] }))-- let recordedTransfer = EC.journalFromSides- [ (Debit, CurrentDeposits, 10 :: MoneyDecimal)- , (Credit, ShortTermLoansPayable, 10)- ] :: CheckedAlgM- transferred = abnormalDeposit .+ recordedTransfer- transferredInput = (trialBalanceInput transferred TB.BeforeClosing)- { TB._reclassificationRules = [oneRule] }- assertEqual "trial balance: recorded transfer clears abnormal finding"- True- (case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy transferredInput of- Right _ -> True- Left _ -> False)- assertEqual "trial balance: validation never rewrites the admitted element"- transferred- (case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy transferredInput of- Right value -> TB.validatedTrialBalance value- Left _ -> mempty)- assertEqual "trial balance: validated stage is retained"- TB.BeforeClosing- (case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy transferredInput of- Right value -> TB.validatedStage value- Left _ -> TB.AfterClosing)-- let invalidRule = TB.SideReclassificationRule CashOverShort Credit- (MiscellaneousIncome NE.:| [])- invalidRuleInput = (trialBalanceInput mempty TB.BeforeClosing)- { TB._reclassificationRules = [invalidRule] }- assertEqual "trial balance: inapplicable rules are explicit"- (Just (TB.InapplicableReclassificationRule invalidRule NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy invalidRuleInput))- let deadRule = TB.SideReclassificationRule CurrentDeposits Debit- (Cash NE.:| [])- deadRuleInput = (trialBalanceInput mempty TB.BeforeClosing)- { TB._reclassificationRules = [deadRule] }- assertEqual "trial balance: normal-side trigger is a dead rule"- (Just (TB.InapplicableReclassificationRule deadRule NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy deadRuleInput))-- let maturityBalance = EC.journalFromSides- [ (Debit, LoansReceivable, 25 :: MoneyDecimal)- , (Credit, CapitalStock, 25)- ] :: CheckedAlgM- maturityRule = TB.MaturityEvidenceRequired LoansReceivable- missingMaturity = (trialBalanceInput maturityBalance TB.BeforeClosing)- { TB._reclassificationRules = [maturityRule] }- suppliedMaturity = missingMaturity- { TB._maturityEvidenceTitles = Set.singleton LoansReceivable }- assertEqual "trial balance: maturity-sensitive title requires evidence"- (Just (TB.MissingMaturityEvidence LoansReceivable NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy missingMaturity))- assertEqual "trial balance: supplied maturity evidence clears finding"- True- (case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy suppliedMaturity of- Right _ -> True- Left _ -> False)-- let unbalanced = EC.journalFromSides- [(Debit, Cash, 10 :: MoneyDecimal)] :: CheckedAlgM- unbalancedInput = trialBalanceInput unbalanced TB.BeforeClosing- assertEqual "trial balance: exact global imbalance blocks"- (Just (TB.UnbalancedTrialBalance 10 0 NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy unbalancedInput))-- let wildcard = 10 :@ (HatNot :< Cash) :: CheckedAlgM- wildcardInput = trialBalanceInput wildcard TB.BeforeClosing- assertEqual "trial balance: wildcard side reports without whichSide crash"- (Just (TB.WildcardTrialBalanceSide NE.:| []))- (leftErrors (TB.validateTrialBalance- TB.standaloneTrialBalancePolicy wildcardInput))-- let titlesFor role =- [ title- | title <- Registry.concreteAccountTitles- , Just semantics <- [Registry.accountSemantics title]- , role `elem` Registry.asemRoles semantics- ]- assertEqual "trial balance: reciprocal registry role is pinned"- [BranchCurrentAccount, HeadOfficeCurrentAccount]- (titlesFor ReciprocalAccount)- assertEqual "trial balance: suspense registry role is pinned"- [SuspensePayments, CashOverShort, SuspenseReceipts, SuspenseAccount]- (titlesFor SuspenseOrClearingAccount)- assertEqual "trial balance: closing-device registry role is pinned"- [IncomeSummary] (titlesFor ClosingDevice)--testReportingPresentation :: IO ()-testReportingPresentation = do- let reportingBalance = EC.journalFromSides- [ (Debit, Cash, 100 :: MoneyDecimal)- , (Debit, LoansReceivable, 30)- , (Debit, Purchases, 40)- , (Debit, BranchCurrentAccount, 10)- , (Debit, IncomeTaxesRefundReceivable, 5)- , (Credit, CapitalStock, 90)- , (Credit, Sales, 70)- , (Credit, AdvancesReceived, 15)- , (Credit, HeadOfficeCurrentAccount, 10)- ] :: CheckedAlgM- maturityRule = TB.MaturityEvidenceRequired LoansReceivable- reportingInput = (trialBalanceInput reportingBalance TB.BeforeClosing)- { TB._reclassificationRules = [maturityRule]- , TB._maturityEvidenceTitles = Set.singleton LoansReceivable- }- validated = case TB.validateTrialBalance- TB.strictTrialBalancePolicy reportingInput of- Right value -> value- Left errors -> error ("reporting fixture did not validate: " ++ show errors)- rationale = T.pack "material under the documented tax review"- context scope = (RP.jcciSecondGradeContext scope)- { RP._presentationAllocations =- [RP.PresentationAllocation LoansReceivable 10 20- (T.pack "contract maturity schedule")]- , RP._presentationRelabels =- [RP.PresentationRelabel Purchases SalesCost- (T.pack "JCCI report cost-of-sales label")]- , RP._materialityDecisions =- [RP.MaterialityDecision IncomeTaxesRefundReceivable- RP.PresentSeparately rationale]- , RP._subtotalDefinitions =- [ RP.SubtotalDefinition RM.GrossProfitMetric [Sales] [SalesCost]- RP.RequireAllTitlesPresent- , RP.SubtotalDefinition RM.OrdinaryProfitMetric [Sales] [SalesCost]- RP.RequireAllTitlesPresent- ]- }- standalone = rightStatements (RP.present (context RP.Standalone) validated)- combined = rightStatements (RP.present (context RP.Combined) validated)- standaloneTitles = L.map RP._lineAccount (RP._statementLines standalone)- combinedTitles = L.map RP._lineAccount (RP._statementLines combined)- assertEqual "reporting: same validated TB changes with scope"- True (RP._statementLines standalone /= RP._statementLines combined)- assertEqual "reporting: standalone retains reciprocal lines"- True (BranchCurrentAccount `elem` standaloneTitles- && HeadOfficeCurrentAccount `elem` standaloneTitles)- assertEqual "reporting: combined eliminates reciprocal lines"- True (BranchCurrentAccount `notElem` combinedTitles- && HeadOfficeCurrentAccount `notElem` combinedTitles- && any isElimination (RP._presentationAudit combined))- assertEqual "reporting: maturity evidence splits one title"- [ (RP.CurrentAssetsSection, 10)- , (RP.NoncurrentAssetsSection, 20)- ]- [ (RP._lineSection line, RP._lineAmount line)- | line <- RP._statementLines standalone- , RP._lineAccount line == LoansReceivable- ]- assertEqual "reporting: JCCI profile uses contract-liability label"- [T.pack "契約負債"]- [ RP._lineLabel line- | line <- RP._statementLines standalone- , RP._lineAccount line == AdvancesReceived- ]- assertEqual "reporting: Purchases is relabeled to SalesCost"- True (Purchases `notElem` standaloneTitles && SalesCost `elem` standaloneTitles)- assertEqual "reporting: GrossProfit is a subtotal, not a basis line"- ( [ RP.StatementSubtotal RM.GrossProfitMetric- (T.pack "売上総利益") (TB.CreditBalance 30)- , RP.StatementSubtotal RM.OrdinaryProfitMetric- (T.pack "経常利益") (TB.CreditBalance 30)- ]- , False- )- ( RP._statementSubtotals standalone- , GrossProfit `elem` standaloneTitles- || OrdinaryProfit `elem` standaloneTitles- )- assertEqual "reporting: materiality rationale survives in audit"- True (RP.MaterialityApplied IncomeTaxesRefundReceivable- RP.PresentSeparately (TB.DebitBalance 5) rationale- `elem` RP._presentationAudit standalone)- assertEqual "reporting: relabel never mutates validated bookkeeping coordinates"- True (Purchases `elem` basesAccountTitles (TB.validatedTrialBalance validated)- && SalesCost `notElem`- basesAccountTitles (TB.validatedTrialBalance validated))-- let missingAllocation = (context RP.Standalone)- { RP._presentationAllocations = [] }- assertEqual "reporting: missing maturity evidence blocks presentation"- True (case RP.present missingAllocation validated of- Left issues -> RP.MissingPresentationAllocation LoansReceivable- `elem` NE.toList issues- Right _ -> False)-- let explainedSuspense = EC.journalFromSides- [ (Debit, SuspensePayments, 10 :: MoneyDecimal)- , (Credit, CapitalStock, 10)- ] :: CheckedAlgM- explainedInput = (trialBalanceInput explainedSuspense TB.BeforeClosing)- { TB._temporaryBalanceExplanations = M.singleton SuspensePayments- (T.pack "pending invoice") }- explainedValidated = case TB.validateTrialBalance- TB.standaloneTrialBalancePolicy explainedInput of- Right value -> value- Left errors -> error ("explained fixture rejected: " ++ show errors)- assertEqual "reporting: stricter combined context re-gates retained finding"- True (case RP.present (RP.jcciSecondGradeContext RP.Combined)- explainedValidated of- Left issues -> any isValidationBlock (NE.toList issues)- Right _ -> False)-- let contraBalance = EC.journalFromSides- [ (Debit, AccountsReceivable, 100 :: MoneyDecimal)- , (Credit, AllowanceForDoubtfulAccounts, 10)- , (Credit, CapitalStock, 90)- ] :: CheckedAlgM- contraValidated = validateFixture contraBalance- separateContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._contraPresentationRules =- [RP.PresentContraSeparately AllowanceForDoubtfulAccounts- (T.pack "show allowance as deduction")] }- netContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._contraPresentationRules =- [RP.NetContraAgainst AllowanceForDoubtfulAccounts- AccountsReceivable (T.pack "net receivables policy")] }- separateLines = RP._statementLines- (rightStatements (RP.present separateContext contraValidated))- netLines = RP._statementLines- (rightStatements (RP.present netContext contraValidated))- assertEqual "reporting: contra policy supports separate presentation"- True (any (\line -> RP._lineAccount line == AllowanceForDoubtfulAccounts- && RP._lineIsDeduction line) separateLines)- assertEqual "reporting: contra policy supports net presentation"- [(Debit, 90)]- [ (RP._lineSide line, RP._lineAmount line)- | line <- netLines, RP._lineAccount line == AccountsReceivable- ]-- let taxBalance = EC.journalFromSides- [ (Debit, Cash, 85 :: MoneyDecimal)- , (Debit, CorporateIncomeTaxes, 20)- , (Credit, CapitalStock, 100)- , (Credit, RefundOfIncomeTaxes, 5)- ] :: CheckedAlgM- taxValidated = validateFixture taxBalance- netTaxContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._materialityDecisions =- [RP.MaterialityDecision RefundOfIncomeTaxes- (RP.NetAgainst CorporateIncomeTaxes)- (T.pack "immaterial refund netted under tax policy")] }- taxLines = RP._statementLines- (rightStatements (RP.present netTaxContext taxValidated))- assertEqual "reporting: materiality policy supports tax netting"- (False, [(Debit, 15)])- ( RefundOfIncomeTaxes `elem` L.map RP._lineAccount taxLines- , [ (RP._lineSide line, RP._lineAmount line)- | line <- taxLines, RP._lineAccount line == CorporateIncomeTaxes- ]- )- let badAllocation current noncurrent evidence = (context RP.Standalone)- { RP._presentationAllocations =- [RP.PresentationAllocation LoansReceivable current noncurrent evidence] }- assertEqual "reporting: blank allocation evidence blocks"- True (hasPresentationIssue isBlankEvidence (RP.present- (badAllocation 10 20 (T.pack " ")) validated))- assertEqual "reporting: negative allocation blocks"- True (hasPresentationIssue isInvalidAllocation (RP.present- (badAllocation (-1) 31 (T.pack "schedule")) validated))- assertEqual "reporting: non-summing allocation blocks"- True (hasPresentationIssue isInvalidAllocation (RP.present- (badAllocation 10 19 (T.pack "schedule")) validated))- let duplicateAllocation = (context RP.Standalone)- { RP._presentationAllocations =- [ RP.PresentationAllocation LoansReceivable 10 20- (T.pack "schedule A")- , RP.PresentationAllocation LoansReceivable 10 20- (T.pack "schedule B")- ] }- assertEqual "reporting: duplicate allocation blocks"- True (hasPresentationIssue isDuplicateAllocation- (RP.present duplicateAllocation validated))- let unexpectedAllocation = (context RP.Standalone)- { RP._presentationAllocations =- [ RP.PresentationAllocation LoansReceivable 10 20- (T.pack "contract maturity schedule")- , RP.PresentationAllocation Cash 100 0 (T.pack "none")- ] }- assertEqual "reporting: unrequired allocation blocks"- True (hasPresentationIssue isUnexpectedAllocation- (RP.present unexpectedAllocation validated))-- let contraRequiredInput = (trialBalanceInput contraBalance TB.BeforeClosing)- { TB._reclassificationRules =- [TB.MaturityEvidenceRequired AccountsReceivable]- , TB._maturityEvidenceTitles = Set.singleton AccountsReceivable- }- contraRequired = case TB.validateTrialBalance TB.strictTrialBalancePolicy- contraRequiredInput of- Right value -> value- Left errors -> error ("required contra fixture rejected: " ++ show errors)- netAllocatedContext = netContext- { RP._presentationAllocations =- [RP.PresentationAllocation AccountsReceivable 60 30- (T.pack "receivable maturity schedule")] }- assertEqual "reporting: required net target can be allocated after netting"- [(RP.CurrentAssetsSection, 60), (RP.NoncurrentAssetsSection, 30)]- [ (RP._lineSection line, RP._lineAmount line)- | line <- RP._statementLines- (rightStatements (RP.present netAllocatedContext contraRequired))- , RP._lineAccount line == AccountsReceivable- ]- let consumeRequired = (context RP.Standalone)- { RP._materialityDecisions =- [RP.MaterialityDecision LoansReceivable (RP.NetAgainst Cash)- (T.pack "must not erase maturity obligation")] }- assertEqual "reporting: explicit maturity obligation cannot be consumed"- True (hasPresentationIssue isConflictingInstruction- (RP.present consumeRequired validated))-- let emptyValidated = validateFixture mempty- emptyStatements = rightStatements (RP.present- (RP.jcciSecondGradeContext RP.Combined) emptyValidated)- assertEqual "reporting: empty combined TB has no fabricated elimination"- ([], [])- (RP._statementLines emptyStatements, RP._presentationAudit emptyStatements)- where- rightStatements (Right statements) = statements- rightStatements (Left issues) = error ("presentation failed: " ++ show issues)- validateFixture alg = case TB.validateTrialBalance TB.strictTrialBalancePolicy- (trialBalanceInput alg TB.BeforeClosing) of- Right value -> value- Left errors -> error ("fixture did not validate: " ++ show errors)- isElimination (RP.ReciprocalAccountsEliminated _ _) = True- isElimination _ = False- isValidationBlock (RP.ValidationFindingBlocks _) = True- isValidationBlock _ = False- hasPresentationIssue predicate (Left issues) = any predicate (NE.toList issues)- hasPresentationIssue _ (Right _) = False- isBlankEvidence (RP.BlankPresentationEvidence LoansReceivable) = True- isBlankEvidence _ = False- isInvalidAllocation (RP.InvalidPresentationAllocation LoansReceivable _ _ _) = True- isInvalidAllocation _ = False- isDuplicateAllocation (RP.DuplicatePresentationAllocation LoansReceivable) = True- isDuplicateAllocation _ = False- isUnexpectedAllocation (RP.UnexpectedPresentationAllocation Cash) = True- isUnexpectedAllocation _ = False- isConflictingInstruction- (RP.ConflictingPresentationInstruction LoansReceivable) = True- isConflictingInstruction _ = False- basesAccountTitles alg =- [ title | _ :< title <- EA.bases alg ]--testDerivedMetricsLand5 :: IO ()-testDerivedMetricsLand5 = do- assertEqual "Land 5: legacy derived-coordinate ordinals and Binary bytes"- [ (NetIncome, 49, T.pack "0031")- , (GrossProfit, 54, T.pack "0036")- , (OrdinaryProfit, 55, T.pack "0037")- , (NetLoss, 64, T.pack "0040")- , (IncomeSummary, 216, T.pack "00d8")- ]- [ (title, fromEnum title, accountSemanticsBinaryHex title)- | title <- [NetIncome, GrossProfit, OrdinaryProfit, NetLoss, IncomeSummary]- ]- assertEqual "Land 5: exactly four legacy coordinates map to typed metrics"- [ (NetIncome, RM.PeriodResultMetric)- , (GrossProfit, RM.GrossProfitMetric)- , (OrdinaryProfit, RM.OrdinaryProfitMetric)- , (NetLoss, RM.PeriodResultMetric)- ]- [ (title, metric)- | title <- Registry.concreteAccountTitles- , Just metric <- [RM.metricForLegacyTitle title]- ]-- let salesOnly = 100 .@ Not :< Sales :: CheckedAlgM- afterLegacyBalancer = EAT.incomeSummaryAccount salesOnly- hatSales = 25 .@ Hat :< Sales :: CheckedAlgM- assertEqual "Land 5: period metric ignores an inserted legacy balancer"- (Right (RM.PeriodProfit 100), Right (RM.PeriodProfit 100))- ( RM.periodResultOfAlg salesOnly- , RM.periodResultOfAlg afterLegacyBalancer- )- assertEqual "Land 5: Hat is interpreted through account side, not as a scalar sign"- (Right (RM.PeriodLoss 25)) (RM.periodResultOfAlg hatSales)- assertEqual "Land 5: empty nominal basis is break-even"- (Right RM.PeriodBreakEven :: Either RM.MetricError (RM.PeriodResult MoneyDecimal))- (RM.periodResultOfAlg (mempty :: CheckedAlgM))- assertEqual "Land 5: raw metric boundary rejects wildcard sides"- (Left (RM.WildcardMetricSide Sales))- (RM.periodResultOfAlg (10 .@ HatNot :< Sales :: CheckedAlgM))-- let ordinaryLedger = EC.journalFromSides- [ (Debit, Cash, 100 :: MoneyDecimal)- , (Credit, Sales, 100)- ] :: CheckedAlgM- ordinaryValidated = validateBefore ordinaryLedger- assertEqual "Land 5: validated before-closing TB derives one period-result identity"- (Right (RM.PeriodProfit 100))- (RM.periodResultOf ordinaryValidated)-- let legacyLedger = EC.journalFromSides- [ (Debit, NetIncome, 10 :: MoneyDecimal)- , (Credit, CapitalStock, 10)- ] :: CheckedAlgM- legacyValidated = validateBefore legacyLedger- assertEqual "Land 5: typed metric rejects a residual legacy coordinate"- (Left (RM.ResidualDerivedCoordinate NetIncome))- (RM.periodResultOf legacyValidated)- assertEqual "Land 5: legacy intermediate and presentation paths are explicit alternatives"- True (case RP.present- (RP.jcciSecondGradeContext RP.Standalone) legacyValidated of- Left issues -> RP.UnpresentableBalance NetIncome- (TB.DebitBalance 10) `elem` NE.toList issues- Right _ -> False)-- let emptyValidated = validateBefore (mempty :: CheckedAlgM)- subtotal = RP.SubtotalDefinition RM.GrossProfitMetric- [Sales] [SalesCost] RP.TreatAbsentAsZero- duplicateContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._subtotalDefinitions = [subtotal, subtotal] }- assertEqual "Land 5: duplicate metric identity blocks presentation"- True (case RP.present duplicateContext emptyValidated of- Left issues -> RP.DuplicateMetricIdentity RM.GrossProfitMetric- `elem` NE.toList issues- Right _ -> False)- let absentAsZeroContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._subtotalDefinitions = [subtotal] }- assertEqual "Land 5: canonical subtotal may treat unposted titles as zero"- [RP.StatementSubtotal RM.GrossProfitMetric- (T.pack "売上総損益") TB.NoBalance]- (RP._statementSubtotals (case RP.present absentAsZeroContext emptyValidated of- Right statements -> statements- Left issues -> error ("absent-as-zero subtotal rejected: " ++ show issues)))-- let relabelledLedger = EC.journalFromSides- [ (Debit, Purchases, 30 :: MoneyDecimal)- , (Credit, Sales, 30)- ] :: CheckedAlgM- relabelledValidated = validateBefore relabelledLedger- removedTitleContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._presentationRelabels =- [RP.PresentationRelabel Purchases SalesCost (T.pack "policy")]- , RP._subtotalDefinitions =- [RP.SubtotalDefinition RM.GrossProfitMetric- [Sales] [Purchases] RP.TreatAbsentAsZero]- }- assertEqual "Land 5: absent-as-zero does not hide a relabelled non-zero title"- True (case RP.present removedTitleContext relabelledValidated of- Left issues -> RP.InvalidSubtotalDefinition RM.GrossProfitMetric- `elem` NE.toList issues- Right _ -> False)-- let Just customId = RM.mkMetricId (T.pack "ebitda-adjusted")- customDefinition = RP.SubtotalDefinition (RM.CustomMetric customId)- [Sales] [] RP.TreatAbsentAsZero- unlabelledContext = (RP.jcciSecondGradeContext RP.Standalone)- { RP._subtotalDefinitions = [customDefinition] }- assertEqual "Land 5: custom metric identity requires separate labels"- True (case RP.present unlabelledContext emptyValidated of- Left issues -> RP.UnlabelledCustomMetric customId- `elem` NE.toList issues- Right _ -> False)- let labelledContext = unlabelledContext- { RP._presentationProfile = RP.CanonicalEnglish- , RP._customMetricLabels =- [RP.CustomMetricLabel customId- (T.pack "調整後EBITDA") (T.pack "Adjusted EBITDA")]- }- assertEqual "Land 5: custom metric identity and profile label are separate"- [RP.StatementSubtotal (RM.CustomMetric customId)- (T.pack "Adjusted EBITDA") TB.NoBalance]- (RP._statementSubtotals (case RP.present labelledContext emptyValidated of- Right statements -> statements- Left issues -> error ("labeled custom metric rejected: " ++ show issues)))- let duplicateLabelContext = labelledContext- { RP._customMetricLabels = RP._customMetricLabels labelledContext- ++ RP._customMetricLabels labelledContext- }- assertEqual "Land 5: standalone label lookup rejects duplicate identities"- Nothing- (RP.metricLabel duplicateLabelContext (RM.CustomMetric customId)- TB.NoBalance)- where- validateBefore alg = case TB.validateTrialBalance- TB.strictTrialBalancePolicy (trialBalanceInput alg TB.BeforeClosing) of- Right value -> value- Left errors -> error ("Land 5 fixture did not validate: " ++ show errors)---- Pins the complete 4 context x 5 capability truth table of the accounting--- posting policy, row by row, so that a change to any single cell is visible--- as a diff here rather than only through the checked-conversion wrappers.-testPostingPolicyTruthTable :: IO ()-testPostingPolicyTruthTable = do- let contexts =- [ PP.OrdinaryJournal- , PP.ClosingProcess- , PP.ConsolidationWorksheet- , PP.EngineComputation- ]- capabilities =- [ OrdinaryPosting- , ClosingOnly- , ConsolidationOnly- , EngineGeneratedOnly- , NotPostable- ]- truthTable =- [ (PP.OrdinaryJournal, OrdinaryPosting, True)- , (PP.OrdinaryJournal, ClosingOnly, False)- , (PP.OrdinaryJournal, ConsolidationOnly, False)- , (PP.OrdinaryJournal, EngineGeneratedOnly, False)- , (PP.OrdinaryJournal, NotPostable, False)- , (PP.ClosingProcess, OrdinaryPosting, True)- , (PP.ClosingProcess, ClosingOnly, True)- , (PP.ClosingProcess, ConsolidationOnly, False)- , (PP.ClosingProcess, EngineGeneratedOnly, False)- , (PP.ClosingProcess, NotPostable, False)- , (PP.ConsolidationWorksheet, OrdinaryPosting, True)- , (PP.ConsolidationWorksheet, ClosingOnly, False)- , (PP.ConsolidationWorksheet, ConsolidationOnly, True)- , (PP.ConsolidationWorksheet, EngineGeneratedOnly, False)- , (PP.ConsolidationWorksheet, NotPostable, False)- , (PP.EngineComputation, OrdinaryPosting, True)- , (PP.EngineComputation, ClosingOnly, False)- , (PP.EngineComputation, ConsolidationOnly, False)- , (PP.EngineComputation, EngineGeneratedOnly, True)- , (PP.EngineComputation, NotPostable, False)- ]- assertEqual "posting policy: truth table enumerates every context/capability pair"- [ (context, capability)- | context <- contexts- , capability <- capabilities- ]- [ (context, capability) | (context, capability, _) <- truthTable ]- assertEqual "posting policy: 4 x 5 truth table of postingAllowedIn"- [ (context, capability, expected)- | (context, capability, expected) <- truthTable- ]- [ (context, capability, PP.postingAllowedIn context capability)- | (context, capability, _) <- truthTable- ]- assertEqual "posting policy: Convert.Checked re-exports the same gate"- [ PP.postingAllowedIn context capability- | (context, capability, _) <- truthTable- ]- [ ECC.postingAllowedIn context capability- | (context, capability, _) <- truthTable- ]- assertEqual "posting policy: wildcard title is NotPostable"- NotPostable (PP.postingCapabilityFor AccountTitle)- assertEqual "posting policy: concrete titles report registry capability"- [ Registry.asemPostingCapability <$> Registry.accountSemantics title- | title <- Registry.concreteAccountTitles- ]- [ Just (PP.postingCapabilityFor title)- | title <- Registry.concreteAccountTitles- ]--testPostingCapabilityGate :: IO ()-testPostingCapabilityGate = do- let contexts =- [ ECC.OrdinaryJournal- , ECC.ClosingProcess- , ECC.ConsolidationWorksheet- , ECC.EngineComputation- ]- capabilities =- [ OrdinaryPosting- , ClosingOnly- , ConsolidationOnly- , EngineGeneratedOnly- , NotPostable- ]- allowed context capability = (context, capability) `elem`- [ (ECC.OrdinaryJournal, OrdinaryPosting)- , (ECC.ClosingProcess, OrdinaryPosting)- , (ECC.ClosingProcess, ClosingOnly)- , (ECC.ConsolidationWorksheet, OrdinaryPosting)- , (ECC.ConsolidationWorksheet, ConsolidationOnly)- , (ECC.EngineComputation, OrdinaryPosting)- , (ECC.EngineComputation, EngineGeneratedOnly)- ]- assertEqual "posting gate: closed context/capability matrix"- [ (context, capability, allowed context capability)- | context <- contexts- , capability <- capabilities- ]- [ (context, capability, ECC.postingAllowedIn context capability)- | context <- contexts- , capability <- capabilities- ]- assertEqual "posting gate: all 240 titles follow the closed matrix"- [ (context, title, ECC.postingAllowedIn context capability)- | context <- contexts- , title <- Registry.concreteAccountTitles- , Just semantics <- [Registry.accountSemantics title]- , let capability = Registry.asemPostingCapability semantics- ]- [ (context, title, accepted context title)- | context <- contexts- , title <- Registry.concreteAccountTitles- ]- assertEqual "posting gate: derived profit coordinates stay engine-only"- (replicate 4 (Just EngineGeneratedOnly))- [ Registry.asemPostingCapability <$> Registry.accountSemantics title- | title <- [GrossProfit, OrdinaryProfit, NetIncome, NetLoss]- ]- assertEqual "posting gate: consolidation-only set is closed"- [ EquityInEarningsOfInvestee- , CumulativeTranslationAdjustment- , NonControllingInterests- , NetIncomeAttributableToNCI- , NetLossAttributableToNCI- ]- [ title- | title <- Registry.concreteAccountTitles- , Just semantics <- [Registry.accountSemantics title]- , Registry.asemPostingCapability semantics == ConsolidationOnly- ]-- assertEqual "posting gate: ordinary wrapper rejects engine-generated result"- (Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly- ECC.OrdinaryJournal NE.:| []))- (checkedEntryM- [ (Debit, NetIncome, 10)- , (Credit, RetainedEarnings, 10)- ])-- assertEqual "posting gate: closing admits IncomeSummary"- True- (case ECC.checkedEntryIn ECC.ClosingProcess- [ (Debit, Sales, 10 :: MoneyDecimal)- , (Credit, IncomeSummary, 10)- ] of- Right _ -> True- Left _ -> False)- assertEqual "posting gate: closing rejects engine-generated result"- True- (case ECC.checkedEntryIn ECC.ClosingProcess- [ (Debit, NetIncome, 10 :: MoneyDecimal)- , (Credit, RetainedEarnings, 10)- ] of- Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly- ECC.ClosingProcess NE.:| []) -> True- _ -> False)-- assertEqual "posting gate: consolidation admits NCI attribution"- True- (case ECC.checkedEntryIn ECC.ConsolidationWorksheet- [ (Debit, NetIncomeAttributableToNCI, 10 :: MoneyDecimal)- , (Credit, NonControllingInterests, 10)- ] of- Right _ -> True- Left _ -> False)- assertEqual "posting gate: ordinary journal rejects NCI equity"- True- (case ECC.checkedEntry- [ (Debit, Cash, 10 :: MoneyDecimal)- , (Credit, NonControllingInterests, 10)- ] of- Left (ECC.PostingNotAllowed 1 NonControllingInterests- ConsolidationOnly ECC.OrdinaryJournal NE.:| []) -> True- _ -> False)- assertEqual "posting gate: engine admits period result"- True- (case ECC.checkedEntryIn ECC.EngineComputation- [ (Debit, NetIncome, 10 :: MoneyDecimal)- , (Credit, RetainedEarnings, 10)- ] of- Right _ -> True- Left _ -> False)-- assertEqual "posting gate: text path uses ordinary context"- True- (case ECC.checkedEntryText- [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "RetainedEarnings", 10)- ] of- Left (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly- ECC.OrdinaryJournal NE.:| []) -> True- _ -> False)- assertEqual "posting gate: unknown account does not create false imbalance"- True- (case ECC.checkedEntryText- [ (T.pack "debit", T.pack "UnknownAccount_X", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "Cash", 10)- ] of- Left (ECC.EntryParse 0 _ NE.:| []) -> True- _ -> False)- assertEqual "posting gate: consolidation text path admits NCI loss"- True- (case ECC.checkedEntryTextIn ECC.ConsolidationWorksheet- [ (T.pack "debit", T.pack "NonControllingInterests", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "NetLossAttributableToNCI", 10)- ] of- Right _ -> True- Left _ -> False)- assertEqual "posting gate: journal error retains txid"- True- (case checkedJournalM- [ (7,- [ (Debit, IncomeSummary, 10)- , (Credit, RetainedEarnings, 10)- ])- ] of- Left (ECC.EntryErrors 7- (ECC.PostingNotAllowed 0 IncomeSummary ClosingOnly- ECC.OrdinaryJournal NE.:| []) NE.:| []) -> True- _ -> False)- assertEqual "posting gate: certification rejects known disallowed title"- True- (case ECC.certifyJournalText- [ (9 :: Int,- [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "RetainedEarnings", 10)- ])- ] of- ECC.Rejected- (ECC.EntryErrors 9- (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly- ECC.OrdinaryJournal NE.:| []) NE.:| []) -> True- _ -> False)- assertEqual "posting gate: disallowed known title outranks unresolved title"- True- (case ECC.certifyJournalText- [ (11 :: Int,- [ (T.pack "debit", T.pack "NetIncome", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "UnknownAccount_X", 10)- ])- ] of- ECC.Rejected- (ECC.EntryErrors 11- (ECC.PostingNotAllowed 0 NetIncome EngineGeneratedOnly- ECC.OrdinaryJournal NE.:| []) NE.:| []) -> True- _ -> False)- assertEqual "posting gate: certification honors closing context"- True- (case ECC.certifyJournalTextIn ECC.ClosingProcess- [ (10 :: Int,- [ (T.pack "debit", T.pack "Sales", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "IncomeSummary", 10)- ])- ] of- ECC.FullyResolved _ -> True- _ -> False)- assertEqual "posting gate: certification honors engine context"- True- (case ECC.certifyJournalTextIn ECC.EngineComputation- [ (12 :: Int,- [ (T.pack "debit", T.pack "NetLoss", 10 :: MoneyDecimal)- , (T.pack "credit", T.pack "RetainedEarnings", 10)- ])- ] of- ECC.FullyResolved _ -> True- _ -> False)- where- accepted context title =- case ECC.checkedEntryIn context- [ (Debit, title, 1 :: MoneyDecimal)- , (Credit, Cash, 1)- ] of- Right _ -> True- Left _ -> False--checkedConvertProperties :: IO ()-checkedConvertProperties = do- testPostingPolicyTruthTable- testPostingCapabilityGate- testConsolidationWorksheet- testSharedAccountBalancePrimitives- testTrialBalanceValidation- testReportingPresentation- testDerivedMetricsLand5-- quickProp "convert-checked: certify known accounts matches checkedJournal" $- prop_certifyKnownAccountsMatchesCheckedJournal-- quickProp "convert-checked: unresolved vocabulary preserves balance" $- prop_certifyUnknownAccountPreservesBalance-- quickProp "convert-checked: imbalance precedes unresolved vocabulary" $- prop_certifyImbalancePrecedesUnknownAccount-- quickProp "convert-checked: certify duplicate txid always rejected" $- prop_certifyDuplicateTxIdAlwaysRejected-- quickProp "convert-checked: balanced unresolved totals match input" $- prop_certifyBalancedUnresolvedTotals-- quickProp "convert-checked: checkedEntry accepts iff checked predicate" $- forAll genCheckedEntryRows $ \rows ->- let expected = checkedEntryAcceptsSpec rows- actual = case checkedEntryM rows of- Right _ -> True- Left _ -> False- in actual == expected-- quickProp "convert-checked: checkedEntry equals journalFromSides on accept" $- forAll genCheckedEntryRows $ \rows ->- case checkedEntryM rows of- Right alg -> alg == (EC.journalFromSides rows :: CheckedAlgM)- Left _ -> True-- quickProp "convert-checked: accepted entries form exact-balanced submonoid" $- forAll genAcceptedEntryRows $ \rows1 ->- forAll genAcceptedEntryRows $ \rows2 ->- case (checkedEntryM rows1, checkedEntryM rows2) of- (Right alg1, Right alg2) -> ECC.exactBalanced (alg1 .+ alg2)- _ -> False-- quickProp "convert-checked: checkedJournal duplicate txid only DuplicateTxId" $- forAll genAcceptedEntryRows $ \rows1 ->- forAll genAcceptedEntryRows $ \rows2 ->- case checkedJournalM [(1, rows1), (1, rows2)] of- Left errs -> NE.toList errs == [ECC.DuplicateTxId 1]- Right _ -> False-- quickProp "convert-checked: reconcileSources coverage and amount checks" $- forAll genPositiveAmountMD $ \amount ->- let entry amt = [(Debit, Cash, amt), (Credit, Sales, amt)]- shifted = amount + 1- journalResult = checkedJournalM [(1, entry amount)]- unknownResult = checkedJournalM [(1, entry amount), (2, entry 5)]- in case (journalResult, unknownResult) of- (Right journal, Right journalWithUnknown) ->- ECC.reconcileSources [(1, amount)] journal == []- && ECC.reconcileSources [(1, amount), (2, 5)] journal- == [ECC.MissingSource 2]- && ECC.reconcileSources [(1, amount)] journalWithUnknown- == [ECC.UnknownSource 2]- && ECC.reconcileSources [(1, shifted)] journal- == [ECC.AmountMismatch 1 shifted amount]- _ -> False--axiomProperties :: IO ()-axiomProperties = do- -- Definition 6 axioms (Double; semantic equality via exact per-base nets)- quickProp "axiom: Hat involution (x^^ = x)" $- forAll genAlgD $ \x -> netByBase ((.^) ((.^) x)) == netByBase x- quickProp "axiom: scalar on singleton (a*(v:@b) = (a*v):@b)" $- forAll genNNDouble $ \a -> forAll genNNDouble $ \v -> forAll genBase $ \b ->- netByBase (a .* (v .@ b)) == netByBase (((a * v) .@ b) :: TestAlg)- quickProp "axiom: scalar distributes over (.+)" $- forAll genNNDouble $ \a -> forAll genAlgD $ \x -> forAll genAlgD $ \y ->- netByBase (a .* (x .+ y)) == netByBase ((a .* x) .+ (a .* y))- quickProp "axiom: norm additivity (norm(x+y) = norm x + norm y)" $- forAll genAlgD $ \x -> forAll genAlgD $ \y ->- epsEq (norm (x .+ y)) (norm x + norm y)- quickProp "axiom: norm homogeneity (norm(a*x) = a*norm x, a>=0)" $- forAll genNNDouble $ \a -> forAll genAlgD $ \x ->- epsEq (norm (a .* x)) (a * norm x)- -- derived lemmas- quickProp "lemma: bar idempotent (bar(bar x) = bar x)" $- forAll genAlgD $ \x -> netByBase (bar (bar x)) == netByBase (bar x)- quickProp "lemma: zero identity (x .+ Zero = x)" $- forAll genAlgD $ \x -> netByBase (x .+ EA.Zero) == netByBase x- quickProp "lemma: (.+) associative" $- forAll genAlgD $ \x -> forAll genAlgD $ \y -> forAll genAlgD $ \z ->- netByBase ((x .+ y) .+ z) == netByBase (x .+ (y .+ z))- -- regression: union must not relabel a value onto a zero posting's base- -- (the 0.4.1.1 bug; raw (:@) so zero-valued singletons are exercised)- quickProp "regression: union preserves per-base net (zero-base bug)" $- forAll genNNDouble $ \v1 -> forAll genBase $ \b1 ->- forAll genNNDouble $ \v2 -> forAll genBase $ \b2 ->- let s1 = v1 :@ b1 :: TestAlg- s2 = v2 :@ b2 :: TestAlg- in netByBase (s1 .+ s2)- == M.unionWith (+) (netByBase s1) (netByBase s2)- -- construction-order independence for the exact value type (MoneyDecimal)- quickProp "MoneyDecimal: fromList per-base net is construction-order independent" $- forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->- let singles = [ v :@ b | (v, b) <- ps ] :: [NNAlg]- viaList = EA.fromList singles- viaFoldr = foldr (.+) EA.Zero singles- viaFoldl = L.foldl' (.+) EA.Zero singles- in netByBase viaList == netByBase viaFoldr- && netByBase viaFoldr == netByBase viaFoldl- -- mapBasePart (Phase 3): identity + norm preservation (no value lost on collision)- quickProp "mapBasePart id preserves per-base net (MoneyDecimal)" $- forAll genAlgN $ \x -> netByBase (EA.mapBasePart id x :: NNAlg) == netByBase x- quickProp "mapBasePart preserves norm under base collapse (MoneyDecimal)" $- forAll genAlgN $ \x -> norm (EA.mapBasePart (const Amount) x :: NNAlg) == norm x- -- S-4: functoriality of the base-relabel map pi_kappa (Prop 2.8(4)):- -- mapBasePart (kappa' . kappa) x ~=_pi mapBasePart kappa' (mapBasePart kappa x)- -- The two kappa are non-identity, non-injective relabelers on CountUnit so the- -- composite collapses bases (Yen -> Dollar -> Amount), exercising the value- -- merge on both sides. There is no dedicated ~=_pi comparator in this suite;- -- we use 'netByBase' (per-base signed net), which is the same bar/order-robust- -- observational equality the other mapBasePart / axiom properties use -- i.e.- -- "equal after bar, compared per base". The 'kappa' relabels the BasePart- -- (= CountUnit here), with mapBasePart re-merging colliding sides, so this is- -- exactly the bar-then-map equivalence the audit note specifies.- quickProp "S-4: mapBasePart is functorial (pi_{k'.k} ~=_pi pi_k' . pi_k, MoneyDecimal)" $- forAll genAlgN $ \x ->- let kappa, kappa' :: CountUnit -> CountUnit- kappa u = if u == Yen then Dollar else u -- Yen -> Dollar- kappa' u = if u == Dollar then Amount else u -- Dollar -> Amount- lhs = EA.mapBasePart (kappa' . kappa) x :: NNAlg- inner = EA.mapBasePart kappa x :: NNAlg- rhs = EA.mapBasePart kappa' inner :: NNAlg- in netByBase lhs == netByBase rhs- -- netPairMapBy (ν_κ pair read-out): three properties from the- -- easp-2026-06-11-netpairmapby handoff.- -- (a) signed-diff consistency: balanceMapBy == n - h of the pair.- -- n - h can be negative, so this is checked on the SIGNED value type- -- (Double); a non-negative-only type would break the n-h component.- quickProp "netPairMapBy: balanceMapBy x == n - h of netPairMapBy x (Double, signed)" $- forAll genAlgD $ \x ->- let bm = EA.balanceMapBy Just x :: M.Map CountUnit Double- np = EA.netPairMapBy Just x :: M.Map CountUnit (Double, Double)- diff = fmap (\(n, h) -> n - h) np- -- balanceMapBy keeps zero-net keys; netPairMapBy drops them.- -- Compare on the union: a key absent from one side reads as 0.- in all (\k -> epsEq (M.findWithDefault 0 k bm)- (M.findWithDefault 0 k diff))- (M.keys bm ++ M.keys diff)- -- (b) both pair components are non-negative (value-domain regularity).- -- Exact value type so the >= 0 check has no tolerance ambiguity.- quickProp "netPairMapBy: both components non-negative (MoneyDecimal)" $- forAll genAlgN $ \x ->- all (\(n, h) -> n >= 0 && h >= 0)- (M.elems (EA.netPairMapBy Just x :: M.Map CountUnit (MoneyDecimal, MoneyDecimal)))- -- (c) ~=_pi invariance: like the S-4 / netByBase observational equality,- -- the pair read-out is construction-order independent (bar-then-net is- -- robust to seq order and reassociation). Exact MoneyDecimal.- quickProp "netPairMapBy: ~=_pi invariant (construction-order independent, MoneyDecimal)" $- forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->- let singles = [ v :@ b | (v, b) <- ps ] :: [NNAlg]- viaList = EA.netPairMapBy Just (EA.fromList singles)- viaFoldr = EA.netPairMapBy Just (foldr (.+) EA.Zero singles)- viaFoldl = EA.netPairMapBy Just (L.foldl' (.+) EA.Zero singles)- in viaList == (viaFoldr :: M.Map CountUnit (MoneyDecimal, MoneyDecimal))- && viaFoldr == viaFoldl---- ================================================================--- Category-theory phase 1 laws and layer boundaries (P2c)--- ================================================================--categoricalPhase1Properties :: IO ()-categoricalPhase1Properties = do- quickProp "P2c mapBasePart: identity holds through per-base multisets" $- forAll genAlgN $ \x -> observe (EA.mapBasePart id x) == observe x- quickProp "P2c mapBasePart: composition holds through per-base multisets" $- forAll genAlgN $ \x ->- let f, g :: CountUnit -> CountUnit- f u = if u == Yen || u == Dollar then Yen else u- g u = if u == Yen || u == Amount then Amount else u- in observe (EA.mapBasePart (g . f) x :: NNAlg)- == observe (EA.mapBasePart g (EA.mapBasePart f x :: NNAlg) :: NNAlg)- quickProp "P2c mapBasePart: (.+) homomorphism holds through per-base multisets" $- forAll genAlgN $ \x -> forAll genAlgN $ \y ->- let f u = if u == Yen || u == Dollar then Amount else u- in observe (EA.mapBasePart f (x .+ y) :: NNAlg)- == observe ((EA.mapBasePart f x .+ EA.mapBasePart f y) :: NNAlg)- quickProp "P2c mapBasePart: norm is preserved" $- forAll genAlgN $ \x ->- norm (EA.mapBasePart (const Amount) x :: NNAlg) == norm x- quickProp "P2c mapBasePart: Hat commutes under raw Eq" $- forAll genAlgN $ \x ->- EA.mapBasePart (const Amount) ((.^) x)- == ((.^) (EA.mapBasePart (const Amount) x) :: NNAlg)- -- bar keeps the Liner constructor even when cancellation leaves one key- -- with one value; mapBasePart rebuilds that map as a singleton. Eq treats- -- the two constructors as distinct, although ℘ observes the same entry.- let identitySource = (1 .@ (Not :< Yen))- .+ (1 .@ (Hat :< Yen))- .+ (2 .@ (Not :< Dollar)) :: NNAlg- oneKeyLiner = bar identitySource- identityMapped = EA.mapBasePart id oneKeyLiner :: NNAlg- assertEqual "P2c mapBasePart: identity counterexample has equal multisets"- (observe oneKeyLiner) (observe identityMapped)- assertEqual "P2c mapBasePart: identity fails under raw Eq for one-key Liner"- False (identityMapped == oneKeyLiner)-- let sandwichLeft = bar- (EA.mapBasePart (const Amount) oneKeyLiner :: NNAlg)- sandwichRight = bar- (EA.mapBasePart (const Amount) identitySource :: NNAlg)- assertEqual "P2c mapBasePart: bar sandwich counterexample has equal multisets"- (observe sandwichLeft) (observe sandwichRight)- assertEqual "P2c mapBasePart: bar sandwich can fail under raw Eq"- False (sandwichLeft == sandwichRight)-- -- Three distinct source keys are read back in whatever order the source- -- HashMap traverses them (a representation detail, so it is observed at- -- run time rather than pinned). The first and last keys collide under f- -- while the middle key survives as a separate intermediate key; g then- -- merges everything. The two-pass route keeps the collision block- -- contiguous, whereas the direct pass interleaves the middle key, so the- -- raw Seq orders differ while the per-base multisets agree.- let rawX = (10 .@ (Hat :< Yen))- .+ (20 .@ (Hat :< Dollar))- .+ (30 .@ (Hat :< Euro)) :: NNAlg- sourceTraversal = EA.foldEntries- (\acc _ (_ :< u) -> acc ++ [u]) [] rawX- (firstU, lastU) = case sourceTraversal of- [a, _, c] -> (a, c)- other -> error ("P2c: unexpected source traversal " ++ show other)- f u = if u == lastU then firstU else u- g _ = Amount- direct = EA.mapBasePart (g . f) rawX :: NNAlg- staged = EA.mapBasePart g (EA.mapBasePart f rawX :: NNAlg) :: NNAlg- assertEqual "P2c mapBasePart: composition counterexample traverses three source keys"- 3 (length sourceTraversal)- assertEqual "P2c mapBasePart: composition raw counterexample has equal multisets"- (observe direct) (observe staged)- assertEqual "P2c mapBasePart: composition fails under raw Eq after collision"- False (direct == staged)-- let barX = (100 .@ (Not :< Yen))- .+ (100 .@ (Hat :< Dollar)) :: NNAlg- mappedAfterBar = EA.mapBasePart (const Amount) (bar barX) :: NNAlg- barAfterMapped = bar (EA.mapBasePart (const Amount) barX :: NNAlg)- assertEqual "P2c mapBasePart: does not commute with bar under collision"- False (mappedAfterBar == barAfterMapped)- assertEqual "P2c mapBasePart: map after bar retains both source residuals"- 200 (norm mappedAfterBar)- assertEqual "P2c mapBasePart: bar after map cancels collided residuals"- 0 (norm barAfterMapped)-- quickProp "P2c foldEntries: commutative sum is construction-order independent" $- forAll (listOf ((,) <$> (realToFrac <$> genNNDouble) <*> genBase)) $ \ps ->- let singles = [ v .@ b | (v, b) <- ps ] :: [NNAlg]- pairwise = EA.fromList singles- bulk = EA.sigma ps (\(v, b) -> v .@ b :: NNAlg)- sumEntries = EA.foldEntries (\acc v _ -> acc + v) 0- in sumEntries pairwise == sumEntries bulk- let entries = [1 .@ (Hat :< Yen), 2 .@ (Hat :< Yen), 3 .@ (Hat :< Yen)]- :: [NNAlg]- pairwise = EA.fromList entries- bulk = EA.sigma [1, 2, 3 :: Int]- (\i -> fromIntegral i .@ (Hat :< Yen) :: NNAlg)- collect = EA.foldEntries (\acc v _ -> acc ++ [v]) []- assertEqual "P2c foldEntries: non-commutative list append observes Seq order"- False (collect pairwise == collect bulk)-- quickProp "P2c postFromNetBy: definition equation" $- forAll genAlgN $ \x ->- let keyOf (_ :< u) = Just u- post u v = v .@ (Not :< u) :: NNAlg- collectEntry v b = (\k -> (k, v)) <$> keyOf b- rhs = EA.sigmaFromMap- (EA.foldEntriesToMap collectEntry (bar x)) post- in EA.postFromNetBy keyOf post x == rhs- quickProp "P2c postFromNetBy: factors through bar" $- forAll genAlgN $ \x ->- let keyOf (_ :< u) = Just u- post u v = v .@ (Not :< u) :: NNAlg- in EA.postFromNetBy keyOf post x- == EA.postFromNetBy keyOf post (bar x)- quickProp "P2c bar: idempotence holds under raw Eq" $- forAll genAlgN $ \x -> bar (bar x) == bar x-- -- extendBy (0.5.1.0 A1): the free extension in the redundant layer.- let dup :: MoneyDecimal -> HatBase CountUnit -> NNAlg- dup v b = (v .@ b) .+ (v .@ b)- relabel :: (CountUnit -> CountUnit) -> MoneyDecimal -> HatBase CountUnit -> NNAlg- relabel f v b = v .@ EA.merge (EA.hat b) (f (EA.base b))- quickProp "P2c extendBy: (.+) homomorphism holds through per-base multisets" $- forAll genAlgN $ \x -> forAll genAlgN $ \y ->- observe (EA.extendBy dup (x .+ y) :: NNAlg)- == observe ((EA.extendBy dup x .+ EA.extendBy dup y) :: NNAlg)- quickProp "P2c extendBy: (:@) is the unit through per-base multisets" $- forAll genAlgN $ \x -> observe (EA.extendBy (:@) x :: NNAlg) == observe x- quickProp "P2c extendBy: mapBasePart is the relabelling special case" $- forAll genAlgN $ \x ->- observe (EA.mapBasePart (const Amount) x :: NNAlg)- == observe (EA.extendBy (relabel (const Amount)) x :: NNAlg)- quickProp "P2c extendBy: norm is the sum of the substituted norms" $- forAll genAlgN $ \x -> norm (EA.extendBy dup x :: NNAlg) == 2 * norm x- quickProp "P2c extendBy: substituting Zero everywhere yields Zero" $- forAll genAlgN $ \x -> EA.isZero (EA.extendBy (\_ _ -> EA.Zero) x :: NNAlg)-- -- Raw counterexample for the unit law: a one-key Liner left by bar is- -- rebuilt as a singleton (:@), which Eq distinguishes although ℘ agrees.- let unitSource = (1 .@ (Not :< Yen))- .+ (1 .@ (Hat :< Yen))- .+ (2 .@ (Not :< Dollar)) :: NNAlg- unitOneKey = bar unitSource- unitRebuilt = EA.extendBy (:@) unitOneKey :: NNAlg- assertEqual "P2c extendBy: unit counterexample has equal multisets"- (observe unitOneKey) (observe unitRebuilt)- assertEqual "P2c extendBy: unit fails under raw Eq for one-key Liner"- False (unitRebuilt == unitOneKey)---- ================================================================--- Journal-algebra axiom properties (Phase 1.5)--- ================================================================--type NNJournal = EJ.Journal String MoneyDecimal (HatBase CountUnit)--genNote :: Gen String-genNote = elements ["a", "b", "c"]--genPosNN :: Gen MoneyDecimal -- strictly positive (avoids zero-note drop)-genPosNN = (\d -> realToFrac (1 + d)) <$> genNNDouble--genJournalN :: Gen NNJournal-genJournalN = sized $ \n -> do- k <- choose (0, min 30 n)- ps <- vectorOf k ((,,) <$> genPosNN <*> genBase <*> genNote)- pure (EJ.fromList [ (v :@ b) .| nt | (v, b, nt) <- ps ])---- per-(note, base) signed net; exact (Rational)-netJournal :: NNJournal -> M.Map (String, CountUnit) Rational-netJournal j = M.fromList- [ ((nt, u), r)- | (nt, alg) <- HM.toList (EJ.toMap j)- , (u, r) <- M.toList (netByBase alg) ]--journalProperties :: IO ()-journalProperties = do- quickProp "journal: norm additivity (norm(j1.+j2) = norm j1 + norm j2, MoneyDecimal)" $- forAll genJournalN $ \j1 -> forAll genJournalN $ \j2 ->- norm (j1 .+ j2) == norm j1 + norm j2- quickProp "journal: Hat preserves the note set" $- forAll genJournalN $ \j ->- L.sort (HM.keys (EJ.toMap ((.^) j))) == L.sort (HM.keys (EJ.toMap j))- quickProp "journal: fromList per-(note,base) net is construction-order independent (MoneyDecimal)" $- forAll (listOf ((,,) <$> genPosNN <*> genBase <*> genNote)) $ \ps ->- let js = [ (v :@ b) .| nt | (v, b, nt) <- ps ] :: [NNJournal]- in netJournal (EJ.fromList js) == netJournal (foldr (.+) mempty js)---- ================================================================--- Quotient decomposition properties (Phase 1, feat/quotient-decomposition)------ Encodes the dec_κ / π_κ axioms of the scaling formalization--- (agent-notes/drafts/scaling-formalization.md §2, §7) as QuickCheck--- properties, plus fixed sentinels for the side-sensitive non-commutation--- cases that MUST NOT silently start commuting (they encode a semantic--- choice, not a bug).--- ================================================================---- proper classifier: factors through the base part (never sees Hat/Not)-properKf :: HatBase CountUnit -> Maybe CountUnit-properKf (_ :< u) = Just u---- partial proper classifier: Yen entries fall into the residual-partialKf :: HatBase CountUnit -> Maybe CountUnit-partialKf (_ :< Yen) = Nothing-partialKf (_ :< u) = Just u---- side-sensitive classifier: sees the Hat/Not state (like decP/decM)-sideKf :: HatBase CountUnit -> Maybe Bool-sideKf b = Just (isHat b)---- residual of a partial classifier (reference implementation via filter)-residualOf :: (HatBase CountUnit -> Maybe CountUnit) -> NNAlg -> NNAlg-residualOf kf = EA.filter (\s -> s /= EA.Zero && kf (EA._hatBase s) == Nothing)---- per-base nets with exact-zero entries dropped (bar drops zero-net bases,--- so commutation properties are compared modulo zero nets)-nonZeroNet :: NNAlg -> M.Map CountUnit Rational-nonZeroNet = M.filter (/= 0) . netByBase--quotientProperties :: IO ()-quotientProperties = do- -- reconstruction: Σ_k x_k (+ residual) = x (formalization Prop 2.3)- quickProp "decBy: reconstruction, total classifier (MoneyDecimal)" $- forAll genAlgN $ \x ->- netByBase (mconcat (M.elems (EA.decBy properKf x))) == netByBase x- quickProp "decBy: reconstruction with residual, partial classifier" $- forAll genAlgN $ \x ->- netByBase (mconcat (M.elems (EA.decBy partialKf x)) .+ residualOf partialKf x)- == netByBase x- -- norm additivity over classes (formalization Prop 2.4(1))- quickProp "decBy: norm additivity over classes + residual (MoneyDecimal)" $- forAll genAlgN $ \x ->- norm x == L.foldl' (+) 0 (L.map norm (M.elems (EA.decBy partialKf x)))- + norm (residualOf partialKf x)- -- proper classifier commutes with bar componentwise (Prop 2.4(4))- quickProp "decBy: bar commutes componentwise (proper classifier)" $- forAll genAlgN $ \x ->- M.filter (not . M.null) (M.map nonZeroNet (EA.decBy properKf (bar x)))- == M.filter (not . M.null) (M.map (nonZeroNet . bar) (EA.decBy properKf x))- -- decBy equals the naive per-class filter loop (semantics check)- quickProp "decBy: equals naive per-class filter (MoneyDecimal)" $- forAll genAlgN $ \x ->- let d = EA.decBy properKf x- naive k = EA.filter- (\s -> s /= EA.Zero && properKf (EA._hatBase s) == Just k) x- in all (\(k, alg) -> netByBase alg == netByBase (naive k)) (M.toList d)- -- postFromNetBy equals an independent per-key projNetNorm pipeline- quickProp "postFromNetBy: equals per-key projNetNorm reference (MoneyDecimal)" $- forAll genAlgN $ \x ->- let kf b = if isHat b then Just (unitOf b) else Nothing- unitOf (_ :< u) = u- post u v = v .@ (Not :< u) :: NNAlg- viaApi = EA.postFromNetBy kf post x- viaRef = mconcat- [ post u s- | u <- [Yen, Dollar, Amount]- , let s = EA.projNetNorm [Hat :< u] (bar x)- , s /= 0 ]- in netByBase viaApi == netByBase viaRef- -- decTo: flatten reconstructs and norm is preserved (total classifier)- quickProp "decTo: toAlg . decTo reconstructs (total classifier, MoneyDecimal)" $- forAll genAlgN $ \x ->- let j = EJ.decTo (\(_ :< u) -> Just (show u)) x- :: EJ.Journal String MoneyDecimal (HatBase CountUnit)- in netByBase (EJ.toAlg j) == netByBase x && norm j == norm x- -- sentinel: side-sensitive classifier does NOT commute with bar- -- (decP/decM-style split; x = v:@Not:<Yen .+ v:@Hat:<Yen nets to zero- -- globally but each side survives within its own class)- let xCancel = (5 .@ (Not :< Yen)) .+ (5 .@ (Hat :< Yen)) :: NNAlg- lhs = M.filter (not . EA.isZero) (M.map bar (EA.decBy sideKf xCancel))- rhs = EA.decBy sideKf (bar xCancel)- assertEqual "sentinel: side-sensitive decBy does not commute with bar"- True (M.keys lhs /= M.keys rhs)- -- sentinel: whichSide-style classifier is also side-sensitive- -- (Cash homeSide = Debit, so Hat flips it to Credit: the two sides of one- -- base land in different classes — Deguchi Def 2.13)- let xCash = (100 .@ (Not :< Cash)) .+ (100 .@ (Hat :< Cash))- :: EA.Alg MoneyDecimal (HatBase AccountTitles)- bySide = EA.decBy (\b -> Just (whichSide b)) xCash- assertEqual "sentinel: whichSide splits one base across classes (side-sensitive)"- [Credit, Debit] (L.sort (M.keys bySide))- assertEqual "sentinel: whichSide decBy does not commute with bar"- True (M.filter (not . EA.isZero) (M.map bar bySide)- /= EA.decBy (\b -> Just (whichSide b)) (bar xCash))- -- sentinel: π_κ (mapBasePart, non-injective) does not commute with bar- -- (formalization §2.8: coarsen-then-net /= net-then-coarsen)- let xPi = (100 .@ (Not :< Yen)) .+ (100 .@ (Hat :< Dollar)) :: NNAlg- assertEqual "sentinel: bar (mapBasePart const) nets across the class"- 0 (norm (bar (EA.mapBasePart (const Amount) xPi :: NNAlg)))- assertEqual "sentinel: mapBasePart (bar x) keeps both sides (no cross-base netting)"- 200 (norm (EA.mapBasePart (const Amount) (bar xPi) :: NNAlg))---- ================================================================--- Bookkeeping closing-adjustment builders (Phase B)--- ================================================================--type BAlg = EA.Alg Double (HatBase AccountTitles)-type BAlgM = EA.Alg MoneyDecimal (HatBase AccountTitles)--mkA :: EB.MkBase (HatBase AccountTitles)-mkA = (:<)---- balanced-ness: debit-side norm equals credit-side norm (貸借一致)-isBalancedD :: BAlg -> Bool-isBalancedD x = epsEq (norm (EA.decL x)) (norm (EA.decR x))--bookkeepingProperties :: IO ()-bookkeepingProperties = do- -- (1) balanced property: every builder produces a debit=credit entry- quickProp "bookkeeping: cogsAdjustmentEntries balanced" $- forAll genNNDouble $ \beg -> forAll genNNDouble $ \end ->- isBalancedD (EB.cogsAdjustmentEntries mkA beg end)- quickProp "bookkeeping: depreciationIndirectEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.depreciationIndirectEntry mkA amt)- quickProp "bookkeeping: depreciationDirectEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.depreciationDirectEntry mkA amt Fixtures)- quickProp "bookkeeping: allowanceReplenishmentEntry balanced" $- forAll genNNDouble $ \est -> forAll genNNDouble $ \cur ->- isBalancedD (EB.allowanceReplenishmentEntry mkA est cur)- quickProp "bookkeeping: allowanceResetEntries balanced" $- forAll genNNDouble $ \est -> forAll genNNDouble $ \cur ->- isBalancedD (EB.allowanceResetEntries mkA est cur)- quickProp "bookkeeping: prepaidExpenseEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.prepaidExpenseEntry mkA amt RentExpense)- quickProp "bookkeeping: unearnedRevenueEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.unearnedRevenueEntry mkA amt RentalIncome)- quickProp "bookkeeping: accruedRevenueEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.accruedRevenueEntry mkA amt InterestEarned)- quickProp "bookkeeping: accruedExpenseEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.accruedExpenseEntry mkA amt InterestExpense)- quickProp "bookkeeping: corporateTaxInterimEntry balanced" $- forAll genNNDouble $ \amt -> isBalancedD (EB.corporateTaxInterimEntry mkA amt)- -- consumption / corporate tax settlement: amounts where received >= paid,- -- total >= interim (the in-scope branch)- quickProp "bookkeeping: consumptionTaxSettlementEntry balanced (received>=paid)" $- forAll genNNDouble $ \paid -> forAll genNNDouble $ \extra ->- isBalancedD (EB.consumptionTaxSettlementEntry mkA paid (paid + extra))- quickProp "bookkeeping: corporateTaxSettlementEntries balanced (total>=interim)" $- forAll genNNDouble $ \interim -> forAll genNNDouble $ \extra ->- isBalancedD (EB.corporateTaxSettlementEntries mkA (interim + extra) interim)- quickProp "bookkeeping: priorPeriodErrorCorrection balanced" $- forAll genNNDouble $ \curr -> forAll genNNDouble $ \prior ->- isBalancedD (EB.priorPeriodErrorCorrection mkA curr prior Depreciation Land)-- -- (2) unit tests: expected bases/amounts on representative lecture figures- -- COGS (ch.24): beg 100,000 / end 50,000. In isolation this entry's- -- Purchases net = beg - end = +50,000 (so the Purchases balance becomes COGS),- -- and MerchandiseInventory net = end - beg = -50,000 (it replaces the opening- -- balance with the closing one, i.e. 100,000 - 50,000 leaves on the ledger).- let cogs = EB.cogsAdjustmentEntries mkA 100000 50000 :: BAlg- assertNear "cogs: Purchases net = beg - end (= cost of goods sold)"- 50000 (signedNet Purchases cogs)- assertNear "cogs: MerchandiseInventory net = end - beg"- (-50000) (signedNet MerchandiseInventory cogs)- -- 差額補充法, estimate>current (ch.16): 1,400 - 1,000 -> provide 400- let repl1 = EB.allowanceReplenishmentEntry mkA 1400 1000 :: BAlg- assertNear "allowance(差額補充, shortfall): ProvisionForDoubtfulAccounts = 400"- 400 (norm (EA.projByAccountTitle ProvisionForDoubtfulAccounts repl1))- assertNear "allowance(差額補充, shortfall): AllowanceForDoubtfulAccounts = 400"- 400 (norm (EA.projByAccountTitle AllowanceForDoubtfulAccounts repl1))- -- 差額補充法, estimate<current (ch.16): 1,800 - 2,000 -> release 200- let repl2 = EB.allowanceReplenishmentEntry mkA 1800 2000 :: BAlg- assertNear "allowance(差額補充, excess): ReversalOfAllowanceForDoubtfulAccounts = 200"- 200 (norm (EA.projByAccountTitle ReversalOfAllowanceForDoubtfulAccounts repl2))- -- estimate==current -> no entry- assertEqual "allowance(差額補充, equal): Zero"- True (EA.isZero (EB.allowanceReplenishmentEntry mkA 1500 1500 :: BAlg))- -- consumption tax (ch.23): paid 1,000 / received 20,000 -> unpaid 19,000- let ctax = EB.consumptionTaxSettlementEntry mkA 1000 20000 :: BAlg- assertNear "consumptionTax: AccruedConsumptionTax = received - paid = 19000"- 19000 (norm (EA.projByAccountTitle AccruedConsumptionTax ctax))- -- corporate tax (ch.23): total 800,000 / interim 500,000 -> unpaid 300,000- let crp = EB.corporateTaxSettlementEntries mkA 800000 500000 :: BAlg- assertNear "corporateTax: AccruedCorporateIncomeTaxes = total - interim = 300000"- 300000 (norm (EA.projByAccountTitle AccruedCorporateIncomeTaxes crp))- -- prior-period error correction (#15 anchor): patent 55,000/10yr discovered 2028- -- current 5,500 / prior 2yr 11,000 -> debit=credit=16,500 (Patent credit)- let ppec = EB.priorPeriodErrorCorrection mkA 5500 11000 AmortizationExpense Patent :: BAlg- assertNear "priorPeriodErrorCorrection (#15): Patent credit = 16500"- 16500 (norm (EA.projByAccountTitle Patent ppec))- assertNear "priorPeriodErrorCorrection (#15): balanced (decL == decR)"- (norm (EA.decL ppec)) (norm (EA.decR ppec))- -- consumption-tax refund (received<paid) is rejected (out of 3-級 scope)- rRefund <- try (evaluate (norm (EB.consumptionTaxSettlementEntry mkA 5000 1000 :: BAlg)))- :: IO (Either SomeException Double)- case rRefund of- Left _ -> putStrLn "[PASS] consumptionTaxSettlementEntry rejects received<paid"- Right _ -> do putStrLn "[FAIL] consumptionTaxSettlementEntry accepted refund"; exitFailure-- -- (3) reversingEntry: involution + exact cancellation (MoneyDecimal exact)- quickProp "bookkeeping: reversingEntry is involution (MoneyDecimal)" $- forAll genBAlgM $ \x -> EB.reversingEntry (EB.reversingEntry x) == x- quickProp "bookkeeping: bar (x .+ reversingEntry x) == Zero (MoneyDecimal)" $- forAll genBAlgM $ \x -> bar (x .+ EB.reversingEntry x) == EA.Zero- where- -- exact per-account signed net (Not +, Hat -) for Double-based unit checks- signedNet :: AccountTitles -> BAlg -> Double- signedNet t = EA.foldEntries step 0- where step acc v b- | getAccountTitle b == t = if isHat b then acc - v else acc + v- | otherwise = acc---- small exact MoneyDecimal algebra over AccountTitles bases (closing-adjustment--- shaped: a few postings on bookkeeping titles), for the reversal properties.-genBAlgM :: Gen BAlgM-genBAlgM = sized $ \n -> do- k <- choose (0, min 8 n)- ps <- vectorOf k ((,) <$> genSmallMoney <*> genBookBase)- pure (EA.fromList [ v .@ b | (v, b) <- ps ])- where- genSmallMoney :: Gen MoneyDecimal- genSmallMoney = fromInteger <$> choose (1, 9999)- genBookBase :: Gen (HatBase AccountTitles)- genBookBase = (:<) <$> elements [Hat, Not]- <*> elements [ Purchases, MerchandiseInventory, Depreciation- , AccumulatedDepreciation, PrepaidExpenses- , AccruedExpenses, AccruedConsumptionTax- , Cash, InterestExpense ]---- ================================================================--- Closing-document Write functions (Phase D): worksheet,--- post-closing trial balance, account ledger--- ================================================================--closingDocsTests :: IO ()-closingDocsTests = do- -- A small balanced pre-adjustment ledger (ebex1-shaped):- -- opening capital 2,000,000; a cash sale of 500,000;- -- wages (cost) 140,000 paid in cash.- -- Pre-adjustment trial balance balances by construction.- let pre = (2000000 .@ (Not :< Cash)) -- 現金 (asset, debit)- .+ (2000000 .@ (Not :< CapitalStock)) -- 資本金 (equity, credit)- .+ (500000 .@ (Not :< Cash)) -- cash from sale (debit)- .+ (500000 .@ (Not :< Sales)) -- 売上 (revenue, credit)- .+ (140000 .@ (Hat :< Cash)) -- cash paid out (credit)- .+ (140000 .@ (Not :< WageExpenditure)) -- 給料 (cost, debit)- :: BAlg- -- one adjustment: accrue 10,000 of unpaid wages (費用の見越し)- let adj = (10000 .@ (Not :< WageExpenditure)) -- cost debit- .+ (10000 .@ (Not :< AccruedExpenses)) -- liability credit- :: BAlg- let combined = pre .+ adj-- -- (1) Worksheet self-check: the P/L column imbalance must equal the- -- B/S column imbalance, and both equal the net income.- -- Each account's *net* balance (diffRL) is routed by division:- -- P/L: Sales net 500,000 (credit) vs WageExpenditure net 150,000- -- (debit) => net income 350,000.- -- B/S: Cash net 2,360,000 (debit) vs CapitalStock 2,000,000 +- -- AccruedExpenses 10,000 (credit) = 2,010,000 => 350,000.- -- We compute the column sums the same way worksheetRows does: per- -- account title, place the *net* balance into the debit or credit- -- column according to its balance side.- let titles = L.nub (EA.foldEntries (\acc _ b -> getAccountTitle b : acc) [] combined) :: [AccountTitles]- netSide t = EA.diffRL (EA.projByAccountTitle t combined) :: (Side, Double)- colSums divs =- L.foldl' (\(d,c) t ->- if classifyAccountDivision t `elem` divs- then case netSide t of- (Debit, m) -> (d + m, c)- (Credit, m) -> (d, c + m)- _ -> (d, c)- else (d, c)) (0,0) titles- (plD, plC) = colSums [Cost, Revenue]- (bsD, bsC) = colSums [Assets, Liability, Equity]- plDiff = abs (plD - plC)- bsDiff = abs (bsD - bsC)- assertNear "worksheet self-check: P/L diff = 350000" 350000 plDiff- assertNear "worksheet self-check: B/S diff = 350000" 350000 bsDiff- assertNear "worksheet self-check: P/L diff == B/S diff (= net income)" plDiff bsDiff-- -- the rendered worksheet's net-income row must carry the same figure on- -- the P/L debit and B/S credit columns (positions 6 and 9, 1-based).- let wrows = EW.worksheetRows pre adj- netRow' = last (init wrows) -- penultimate-from-end: the Net row- assertEqual "worksheet: net row label is Net Income"- (T.pack "Net Income") (head netRow')- assertEqual "worksheet: net income on P/L debit column = 350000.0"- (T.pack "350000.0") (netRow' !! 5)- assertEqual "worksheet: net income on B/S credit column = 350000.0"- (T.pack "350000.0") (netRow' !! 8)-- -- (2) Post-closing trial balance must contain only real accounts- -- (Assets/Liability/Equity) — no Cost/Revenue titles.- let pcrows = EW.postClosingTrialBalanceRows combined- titleCells = [ row !! 1 | row <- drop 1 pcrows ] -- middle column = title- forbidden = L.map (T.pack . show) [Sales, WageExpenditure]- assertEqual "post-closing TB excludes Cost/Revenue titles"- True (not (any (`elem` forbidden) titleCells))- assertEqual "post-closing TB includes Cash"- True (T.pack (show Cash) `elem` titleCells)- assertEqual "post-closing TB includes AccruedExpenses (liability)"- True (T.pack (show AccruedExpenses) `elem` titleCells)-- -- (3) Account ledger preserves the seq: the number of posting lines for a- -- title equals the number of postings on that title (no aggregation).- -- Cash has 3 postings (2 debit, 1 credit) in `pre`.- let lrows = EW.accountLedgerRows [Cash] pre (const dummyDay)- -- drop the 2 header rows (title + sub-header); the rest are postings.- bodyLines = drop 2 lrows- cashPostings = EA.foldEntries (\acc _ b -> if getAccountTitle b == Cash then acc + 1 else acc) (0 :: Int) pre- -- each body row holds at most one debit + one credit cell; count- -- non-empty value cells (debit col=1, credit col=3).- nonEmptyVals = Prelude.length- [ () | row <- bodyLines, c <- [1,3], not (T.null (row !! c)) ]- assertEqual "account ledger: Cash posting count preserved (= 3, no aggregation)"- cashPostings nonEmptyVals- where- dummyDay :: Day- dummyDay = fromGregorian 2024 4 1---- ================================================================--- Simulate.Lite tests (Phase 2, feat/simulate-lite)--- ================================================================---- A concrete Note type for the Lite models: (event tag, term index).-type LNote = (String, Int)-type LBaseD = HatBase AccountTitles-type LedgerD = Journal LNote MoneyDouble LBaseD -- IEEE-754 path (DET-1, BSP, equiv)-type LBaseM = HatBase AccountTitles-type LedgerM = Journal LNote MoneyDecimal LBaseM -- exact path (DET-2)----------------------------------------------------------------------- Lite test 1: boilerplate acceptance example (3 fields, 2 stages).--- The body of this function (the World record, the two stages, the spec and--- the run) is the "~20 line" boilerplate the design targets.----------------------------------------------------------------------- A product-only HKD world: a ledger, a scalar price, a scalar tax rate.-data MiniW f = MiniW- { mwLedger :: HK f LedgerD- , mwPrice :: HK f MoneyDouble- , mwTax :: HK f Double- } deriving Generic---- stage A: each agent buys 1 unit at the snapshot price (a pure message).-buyStage :: Stage MiniW Int LNote MoneyDouble LBaseD-buyStage = stageFor "buy" [1 .. 5 :: Int] $ \w t _g i ->- let amt = mwPrice w * fromIntegral i- in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("buy", t)---- stage B: a single bookkeeping step paying tax on the snapshot price.-taxStage :: Stage MiniW Int LNote MoneyDouble LBaseD-taxStage = stage "tax" $ \w t ->- let amt = mwPrice w * realToFrac (mwTax w)- in ((amt .@ Not :< Sales) .+ (amt .@ Hat :< Cash)) .| ("tax", t)--miniSpec :: SimSpec MiniW Int LNote MoneyDouble LBaseD-miniSpec = mkSimSpec (1, 3) 42 mwLedger [buyStage, taxStage]--testLiteBoilerplate :: IO ()-testLiteBoilerplate = do- let w0 = MiniW { mwLedger = carry mempty- , mwPrice = carry 10- , mwTax = carry 0.1 }- n = runLite miniSpec w0 (realToFrac . norm . mwLedger)- -- 3 terms * (sum_{i=1..5} 10*i*2 + 10*0.1*2) = 3 * (300 + 2) = 906- assertNear "Lite: boilerplate mini-model runs (norm)" 906.0 n----------------------------------------------------------------------- Lite test 2 (DET-2): MoneyDecimal Sequential vs ParChunk exact match.---------------------------------------------------------------------data DecW f = DecW- { dwLedger :: HK f LedgerM- } deriving Generic--decStage :: Stage DecW Int LNote MoneyDecimal LBaseM-decStage = stageFor "post" [1 .. 50 :: Int] $ \_w t g i ->- let (k, _) = randomR (1, 9 :: Int) g- amt = fromIntegral (i + k) :: MoneyDecimal- in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)--decSpec :: Par -> SimSpec DecW Int LNote MoneyDecimal LBaseM-decSpec par = (mkSimSpec (1, 4) 7 dwLedger [decStage]) { Lite.specParallel = par }--testLiteDet2 :: IO ()-testLiteDet2 = do- let w0 = DecW { dwLedger = carry mempty }- runP par = runLite (decSpec par) w0 (toMap . dwLedger)- seqMap = runP Sequential- parMap = runP (ParChunk 8)- assertEqual "Lite DET-2: Sequential and ParChunk produce identical ledgers (exact)"- seqMap parMap----------------------------------------------------------------------- Lite test 3 (DET-1): MoneyDouble reproducibility across two runs.---------------------------------------------------------------------testLiteDet1 :: IO ()-testLiteDet1 = do- let w0 = MiniW { mwLedger = carry mempty- , mwPrice = carry 10- , mwTax = carry 0.1 }- n1 = runLite miniSpec w0 (realToFrac . norm . mwLedger)- n2 = runLite miniSpec w0 (realToFrac . norm . mwLedger)- assertNear "Lite DET-1: same spec run twice gives same norm" n1 n2----------------------------------------------------------------------- Lite test 4 (BSP intra-stage invisibility sentinel).--- Every agent in a stage reads the SAME snapshot. We encode the snapshot--- ledger's norm into each agent's message; if a later agent could see an--- earlier agent's write within the same stage, the encoded norms would differ--- from the all-zero baseline (the ledger starts empty for term 1 stage 0).---------------------------------------------------------------------data BspW f = BspW- { bwLedger :: HK f LedgerD- } deriving Generic---- each agent posts (1 + norm-of-snapshot-ledger). On term 1, stage 0, the--- snapshot ledger is empty for every agent, so each posts exactly 1.0.-bspStage :: Stage BspW Int LNote MoneyDouble LBaseD-bspStage = stageFor "bsp" [1 .. 10 :: Int] $ \w t _g _i ->- let seenNorm = norm (bwLedger w) -- must be 0 for ALL agents (BSP)- amt = 1 + realToFrac seenNorm :: MoneyDouble- in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("bsp", t)--bspSpec :: SimSpec BspW Int LNote MoneyDouble LBaseD-bspSpec = mkSimSpec (1, 1) 0 bwLedger [bspStage]--testLiteBspInvisibility :: IO ()-testLiteBspInvisibility = do- let w0 = BspW { bwLedger = carry mempty }- n = runLite bspSpec w0 (realToFrac . norm . bwLedger)- -- 10 agents each post Not:<Purchases 1.0 + Hat:<Cash 1.0 = norm 20.- -- If intra-stage writes were visible, later agents would post > 1.0 and the- -- norm would exceed 20.- assertNear "Lite BSP: intra-stage invisibility (all agents see empty ledger)"- 20.0 n----------------------------------------------------------------------- Lite test 5: gate toy-model equivalence (3 terms, agents [1..10], norm 3300).--- Rebuilds the gate-report.md prototype with the Lite API; same norm.---------------------------------------------------------------------data GateW f = GateW- { gwLedger :: HK f LedgerD- , gwPrice :: HK f MoneyDouble- } deriving Generic--gateStage :: Stage GateW Int LNote MoneyDouble LBaseD-gateStage = stageFor "buy" [1 .. 10 :: Int] $ \w t _g i ->- let amt = gwPrice w * fromIntegral i- in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("buy", t)--gateSpec :: SimSpec GateW Int LNote MoneyDouble LBaseD-gateSpec = mkSimSpec (1, 3) 1 gwLedger [gateStage]--testLiteGateEquivalence :: IO ()-testLiteGateEquivalence = do- let w0 = GateW { gwLedger = carry mempty, gwPrice = carry 10 }- n = runLite gateSpec w0 (realToFrac . norm . gwLedger)- -- norm = 3 terms * sum_{i=1..10} (10*i*2) = 3 * 2 * 10 * 55 = 3300- assertNear "Lite: gate toy-model equivalence (norm 3300)" 3300.0 n----------------------------------------------------------------------- Lite test 6: term-boundary Field rules (Carry / ResetEach / UpdateEach).--- One agent posts (current price) each term; the three runs differ only in the--- price field's boundary rule, exercising each Field constructor.---------------------------------------------------------------------data RuleW f = RuleW- { rwLedger :: HK f LedgerD- , rwPrice :: HK f MoneyDouble- } deriving Generic--ruleStage :: Stage RuleW Int LNote MoneyDouble LBaseD-ruleStage = stage "post" $ \w t ->- let amt = rwPrice w- in ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)--ruleSpec :: SimSpec RuleW Int LNote MoneyDouble LBaseD-ruleSpec = mkSimSpec (1, 3) 0 rwLedger [ruleStage]--runRule :: Field MoneyDouble -> Double-runRule priceField =- let w0 = RuleW { rwLedger = carry mempty, rwPrice = priceField }- in realToFrac (runLite ruleSpec w0 (norm . rwLedger))- -- norm counts both Not:<Purchases and Hat:<Cash, hence 2 * price each term--testLiteFieldRules :: IO ()-testLiteFieldRules = do- -- Carry 10: price stays 10 every term -> 3 * 2 * 10 = 60- assertNear "Lite Field: Carry keeps the value" 60.0 (runRule (carry 10))- -- ResetEach 5: price reset to 5 at each boundary, but stage reads BEFORE- -- the term-1 boundary commit at the same value -> 3 * 2 * 5 = 30- assertNear "Lite Field: ResetEach restores each term" 30.0 (runRule (resetEach 5))- -- UpdateEach 10 (*2): term1 price 10, boundary doubles -> term2 20, term3 40.- -- norm = 2 * (10 + 20 + 40) = 140- assertNear "Lite Field: UpdateEach applies the step each boundary"- 140.0 (runRule (updateEach 10 (* 2)))---- regression: the boundary rule must fire once per TERM, not per stage.--- (Parser pitfall: a trailing backtick operator after an inner lambda's--- do-block is swallowed into the lambda body, turning the term-boundary--- commit into a per-stage commit. Single-stage tests cannot see this.)--- 2 identical stages x UpdateEach 10 (*2): both stages must read the SAME--- price within a term -> norm = 2 entries * 2 stages * (10+20+40) = 280.--- The per-stage-commit bug yields 2 * (10+20 + 40+80 + 160+320) = 1260.-testLiteBoundaryOncePerTerm :: IO ()-testLiteBoundaryOncePerTerm =- assertNear "Lite: term boundary fires once per term (2 stages)" 280.0- (let spec2 = mkSimSpec (1, 3) 0 rwLedger [ruleStage, ruleStage]- w0 = RuleW { rwLedger = carry mempty, rwPrice = updateEach 10 (* 2) }- in realToFrac (runLite spec2 w0 (norm . rwLedger)))---- | T1: both observers visit each term once, including shifted and empty--- ranges. Reuse the exact-decimal policy fixture without changing its stages.-testLiteObserverTerms :: IO ()-testLiteObserverTerms =- forM_ [(1, 5), (3, 6), (4, 4), (4, 3)] $ \range@(lo, hi) -> do- let spec = polSpec { Lite.specTerms = range }- terms = runLiteFold (\t _ acc -> t : acc) [] spec polW0- (\acc _ -> reverse acc)- assertEqual "Lite fold observer: term order and count" [lo .. hi] terms- seen <- newIORef []- _ <- runLiteWithPolicyObs (\t _ -> modifyIORef' seen (t :))- Policy.defaultLedgerPolicy spec polW0 (toMap . pwLedger)- observed <- reverse <$> readIORef seen- assertEqual "Lite IO observer: term order and count" [lo .. hi] observed---- | T2: reuse MiniW's two stages with a changing price. Each saved snapshot--- includes both current commits, no future posting and the pre-boundary price.-testLiteObserverBoundary :: IO ()-testLiteObserverBoundary = do- let w0 = MiniW { mwLedger = carry mempty- , mwPrice = updateEach 10 (* 2)- , mwTax = carry 0.1 }- snapshots = runLiteFold (\t w acc -> (t, w) : acc) [] miniSpec w0- (\acc _ -> reverse acc)- check :: (Int, MiniW SnapT) -> IO ()- check (t, w) = do- let ledger = toMap (mwLedger w)- price = 10 * (2 ^ (t - 1)) :: MoneyDouble- keys = L.sort [(tag, u) | u <- [1 .. t], tag <- ["buy", "tax"]]- assertEqual "Lite observer: all committed notes, no future notes"- keys (L.sort (HM.keys ledger))- assertEqual "Lite observer: price before Field update" price (mwPrice w)- assertEqual "Lite observer: carried tax" 0.1 (mwTax w)- assertNear "Lite observer: current buy stage committed"- (realToFrac (30 * price))- (maybe 0 (realToFrac . norm) (HM.lookup ("buy", t) ledger))- assertNear "Lite observer: current final stage committed"- (realToFrac (2 * price * realToFrac (mwTax w)))- (maybe 0 (realToFrac . norm) (HM.lookup ("tax", t) ledger))- forM_ snapshots check- seen <- newIORef []- finalPrice <- runLiteWithPolicyObs- (\t w -> modifyIORef' seen ((t, w) :))- Policy.defaultLedgerPolicy miniSpec w0 mwPrice- ioSnapshots <- reverse <$> readIORef seen- assertEqual "Lite IO observer: all boundaries saved" [1, 2, 3] (L.map fst ioSnapshots)- forM_ ioSnapshots check- assertEqual "Lite continuation: final Field update has fired" 80 finalPrice---- | T3: final snapshots agree for all MiniW fields, and exact policy ledgers--- agree under FullAudit and retention with separate spill files for each run.-testLiteObserverEquivalence :: IO ()-testLiteObserverEquivalence = do- let w0 = MiniW { mwLedger = carry mempty- , mwPrice = updateEach 10 (* 2)- , mwTax = carry 0.1 }- project w = (toMap (mwLedger w), mwPrice w, mwTax w)- legacy = runLite miniSpec w0 id- folded = runLiteFold (\_ _ a -> a) () miniSpec w0 (\_ w -> w)- assertEqual "Lite fold: unchanged final snapshot" (project legacy) (project folded)- full <- runLiteWithPolicy Policy.defaultLedgerPolicy miniSpec w0 project- observed <- runLiteWithPolicyObs (\_ _ -> pure ())- Policy.defaultLedgerPolicy miniSpec w0 project- assertEqual "Lite IO observer: unchanged full world" full observed- forM_ [Policy.RetainAll, Policy.RetainRecent 2] $ \retention ->- withTempSpill "observer_legacy" $ \oldPath ->- withTempSpill "observer_new" $ \newPath -> do- let policy path = Policy.defaultLedgerPolicy- { Policy.retain = retention- , Policy.spillTo = Just path }- old <- runLiteWithPolicy (policy oldPath) polSpec polW0 pwLedger- new <- runLiteWithPolicyObs (\_ _ -> pure ())- (policy newPath) polSpec polW0 pwLedger- assertEqual "Lite IO observer: unchanged final policy ledger"- (toMap old) (toMap new)- oldRestored <- Policy.restoreLedger oldPath old :: IO LedgerM- newRestored <- Policy.restoreLedger newPath new :: IO LedgerM- assertEqual "Lite IO observer: unchanged spill contents"- (toMap oldRestored) (toMap newRestored)---- | T4: retained snapshots overlap, so compare their concatenated entries as--- a set. Also stream only each current term and independently restore the spill.--- Reuse PolW, polSpec and the temporary-spill helper from the policy tests.-testLiteObserverStreamingSpill :: IO ()-testLiteObserverStreamingSpill = do- full <- runLiteWithPolicy Policy.defaultLedgerPolicy polSpec polW0 pwLedger- forM_ [0, 2] $ \window -> withTempSpill "observer_stream" $ \path -> do- let policy = Policy.defaultLedgerPolicy- { Policy.retain = Policy.RetainRecent window- , Policy.spillTo = Just path }- seen <- newIORef []- resident <- runLiteWithPolicyObs- (\t w -> modifyIORef' seen ((t, pwLedger w) :))- policy polSpec polW0 pwLedger- snapshots <- reverse <$> readIORef seen- let entries = L.nub (concatMap (HM.toList . toMap . snd) snapshots)- expected = HM.toList (toMap full)- streamed = sigma snapshots $ \(t, ledger) ->- EJ.filterWithNote (\(_, u) _ -> u == t) ledger- assertEqual "Lite streaming: observer term order" [1 .. 5] (L.map fst snapshots)- assertEqual "Lite streaming: snapshot entry set equals FullAudit"- True (length entries == length expected && all (`elem` entries) expected)- assertEqual "Lite streaming: current-term output equals FullAudit exactly"- (toMap full) (toMap streamed)- assertEqual "Lite streaming: final resident window"- [6 - window .. 5] (L.sort (L.map snd (HM.keys (toMap resident))))- restored <- Policy.restoreLedger path resident :: IO LedgerM- assertEqual "Lite streaming: spill plus resident ledger is lossless"- (toMap full) (toMap restored)---- ================================================================--- Simulate.Policy tests (Phase 4, feat/ledger-policy)------ LedgerPolicy = declarative retention / spill / compaction, applied at the--- term boundary by runLiteWithPolicy. The exact MoneyDecimal value type lets us--- assert lossless round-trips and norm/compaction invariants by strict equality.--- ================================================================---- A one-field world whose stage posts a few distinct bases per term, so that a--- closed term has redundant per-base sequences (exercising CompressClosedTerms)--- and a multi-term history (exercising RetainRecent + spill).-data PolW f = PolW- { pwLedger :: HK f LedgerM- } deriving Generic---- A single-field world for the classic-bridge test: a constructor @a@ of kind--- @Type -> Type@ whose @a RealWorld@ is an @STRef RealWorld LedgerM@ (so it fits--- the @SpillOptions t a payload@ shape, where @a@ is applied to the state token).-newtype LedgerRef s = LedgerRef (STRef s LedgerM)---- Each agent posts twice to the SAME base within the term, so the term's per-base--- posting sequence has length 2 before compress and length 1 after.-polStage :: Stage PolW Int LNote MoneyDecimal LBaseM-polStage = stageFor "post" [1 .. 4 :: Int] $ \_w t _g i ->- let amt = fromIntegral i :: MoneyDecimal- one = 1 :: MoneyDecimal- m1 = ((amt .@ Not :< Purchases) .+ (amt .@ Hat :< Cash)) .| ("post", t)- m2 = ((one .@ Not :< Purchases) .+ (one .@ Hat :< Cash)) .| ("post", t)- in m1 .+ m2 :: Journal LNote MoneyDecimal LBaseM--polSpec :: SimSpec PolW Int LNote MoneyDecimal LBaseM-polSpec = mkSimSpec (1, 5) 0 pwLedger [polStage]--polW0 :: PolW InitT-polW0 = PolW { pwLedger = carry mempty }---- run a temp spill file, returning (result, path); caller removes the file.-withTempSpill :: String -> (FilePath -> IO a) -> IO a-withTempSpill tag act = do- let path = "/tmp/exchangealgebra_policy_" ++ tag ++ ".bin"- -- ensure no stale file from a previous run (append-mode would accumulate)- _ <- try (removeFile path) :: IO (Either SomeException ())- r <- act path- _ <- try (removeFile path) :: IO (Either SomeException ())- pure r---- Test 1 (flagship): defaultLedgerPolicy is observationally equal to runLite.-testPolicyEquivalence :: IO ()-testPolicyEquivalence = do- let pureLedger = runLite polSpec polW0 (toMap . pwLedger)- polLedger <- runLiteWithPolicy Policy.defaultLedgerPolicy polSpec polW0 (toMap . pwLedger)- assertEqual "Policy: runLiteWithPolicy defaultLedgerPolicy == runLite (exact)"- pureLedger polLedger---- Test 2 (flagship): RetainRecent w + spillTo gives an in-memory window AND a--- lossless restore that equals the FullAudit ledger.-testPolicyWindowRoundTrip :: IO ()-testPolicyWindowRoundTrip = withTempSpill "window" $ \path -> do- let full = runLite polSpec polW0 (toMap . pwLedger) -- FullAudit reference- pol = Policy.defaultLedgerPolicy- { Policy.retain = Policy.RetainRecent 2- , Policy.spillTo = Just path }- -- ONE policy run (append-mode spill: a second run would double the file),- -- projecting the live journal; we derive both checks from it.- residentJournal <- runLiteWithPolicy pol polSpec polW0 pwLedger- -- (a) in-memory ledger after the run contains ONLY the most recent 2 terms.- let residentMap = toMap residentJournal- residentTerms = L.sort (L.nub [ t | (_, t) <- HM.keys residentMap ])- assertEqual "Policy: RetainRecent 2 leaves only the most recent 2 terms resident"- [4, 5] residentTerms- -- (b) restoreLedger (spill file + resident remainder) == FullAudit ledger.- restored <- Policy.restoreLedger path residentJournal :: IO LedgerM- assertEqual "Policy: restoreLedger (spill + remainder) == FullAudit ledger (lossless, exact)"- full (toMap restored)---- Test 3: CompressClosedTerms — norm/balance invariant, closed-term seq length 1,--- in-progress term keeps full redundancy.-testPolicyCompressClosed :: IO ()-testPolicyCompressClosed = do- let full = runLite polSpec polW0 (toMap . pwLedger)- compactedJ <- runLiteWithPolicy- (Policy.defaultLedgerPolicy { Policy.compaction = Policy.CompressClosedTerms })- polSpec polW0 pwLedger- let fullJ = runLite polSpec polW0 pwLedger- -- (a) norm is invariant under compaction.- assertEqual "Policy: CompressClosedTerms preserves norm (exact)"- (norm fullJ) (norm compactedJ)- -- (b) balance result unchanged (still balanced overall).- assertEqual "Policy: CompressClosedTerms preserves balance"- (EA.balance fullJ) (EA.balance compactedJ)- -- (c) each CLOSED term (1..4) has at most one posting per base/side: its Alg- -- compresses to itself (idempotent), so compress . entry == entry.- let compactedMap = toMap compactedJ- closedOk = all- (\((_, t), alg) -> t == (5 :: Int) || EA.compress alg == alg)- (HM.toList compactedMap)- assertEqual "Policy: closed terms are already compressed (compress is a no-op on them)"- True closedOk- -- (d) the in-progress term (5) keeps its redundancy: in the FULL ledger term- -- 5's entry has a length-2 sequence, and the compacted ledger keeps the- -- SAME term-5 entry (untouched), i.e. it differs from its own compress.- let term5Full = HM.lookup ("post", 5) (toMap fullJ)- term5Comp = HM.lookup ("post", 5) compactedMap- assertEqual "Policy: in-progress term is untouched by CompressClosedTerms"- term5Full term5Comp- case term5Comp of- Just alg -> assertEqual "Policy: in-progress term retains its redundant sequence"- False (EA.compress alg == alg)- Nothing -> assertEqual "Policy: in-progress term present" True False---- Test 4: deletion-only (spillTo Nothing + RetainRecent) narrows the ledger to--- the window and reduces its norm by exactly the discarded terms' norm.-testPolicyDeleteOnly :: IO ()-testPolicyDeleteOnly = do- let pol = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainRecent 2 }- residentJournal <- runLiteWithPolicy pol polSpec polW0 pwLedger- let residentMap = toMap residentJournal- residentTerms = L.sort (L.nub [ t | (_, t) <- HM.keys residentMap ])- -- the FullAudit ledger restricted to the same window must match exactly- -- (deletion is just a filter; the kept terms are untouched).- full = runLite polSpec polW0 pwLedger- windowOfFull = EJ.filterWithNote (\(_, t) _ -> t >= 4) full- assertEqual "Policy: delete-only leaves only the window terms" [4, 5] residentTerms- assertEqual "Policy: delete-only window equals FullAudit restricted to the window (exact)"- (toMap windowOfFull) residentMap- -- norm strictly drops (terms 1..3 were discarded with no spill).- assertEqual "Policy: discarding older terms strictly reduces norm"- True (norm residentJournal < norm full)---- Test 5: DET — policy runs are reproducible and Sequential == ParChunk (exact).-testPolicyDeterminism :: IO ()-testPolicyDeterminism = withTempSpill "det" $ \_ -> do- let pol = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainRecent 3 }- specPar p = polSpec { Lite.specParallel = p }- r1 <- runLiteWithPolicy pol (specPar Sequential) polW0 (toMap . pwLedger)- r2 <- runLiteWithPolicy pol (specPar Sequential) polW0 (toMap . pwLedger)- rP <- runLiteWithPolicy pol (specPar (ParChunk 2)) polW0 (toMap . pwLedger)- assertEqual "Policy DET-1: same policy run twice is identical" r1 r2- assertEqual "Policy DET-2: Sequential == ParChunk under policy (exact)" r1 rP---- Test 6 (classic bridge): policySpillOptions drives the classic engine and the--- result restores losslessly, mirroring the existing binary-spill restore test.--- We exercise the derived chunk extraction + eviction directly (no full--- StateSpace needed) by checking the option fields it builds.-testPolicyClassicBridge :: IO ()-testPolicyClassicBridge = withTempSpill "bridge" $ \path -> do- -- Build a ledger spanning terms 1..3, spill terms 1..2 via the policy-derived- -- chunk extractor, keep term 3 as the remainder, then restore == whole ledger.- let pol = Policy.defaultLedgerPolicy- { Policy.retain = Policy.RetainRecent 1, Policy.spillTo = Just path }- whole :: LedgerM- whole = EJ.fromList- [ (1 .@ Not :< Purchases) .| ("post", 1)- , (2 .@ Not :< Purchases) .| ("post", 2)- , (3 .@ Not :< Purchases) .| ("post", 3) ]- -- the option built by the bridge; we use its spillExtractChunk to carve- -- terms 1..2 and write them, exactly as runSimulationWithSpill would.- opts = Policy.policySpillOptions pol 2- (\(LedgerRef r) -> readSTRef r)- (\f (LedgerRef r) -> modifySTRef' r f)- :: ES.SpillOptions Int LedgerRef LedgerM- -- emulate a single spill of the [1,2] chunk + eviction of term <= 2.- ref <- LedgerRef <$> stToIO (newSTRef whole)- chunk <- case ES.spillExtractChunk opts of- Just extract -> stToIO (extract (1, 2) ref)- Nothing -> error "policySpillOptions must set spillExtractChunk"- withFile path WriteMode $ \h -> ES.spillWriteChunk opts h (1, 2) chunk- stToIO (ES.spillDeleteRange opts (1, 2) ref)- let LedgerRef r0 = ref- remainder <- stToIO (readSTRef r0)- -- remainder is now only term 3; restore merges spill + remainder == whole.- restored <- Policy.restoreLedger path remainder :: IO LedgerM- assertEqual "Policy bridge: policySpillOptions chunk keeps spilled-range terms"- (toMap (EJ.filterWithNote (\(_, t) _ -> t >= 1 && t <= 2) whole)) (toMap chunk)- assertEqual "Policy bridge: after eviction the remainder is only the kept window"- (toMap (EJ.filterWithNote (\(_, t) _ -> t > 2) whole)) (toMap remainder)- assertEqual "Policy bridge: restore (spill + remainder) == whole ledger (lossless)"- (toMap whole) (toMap restored)---- Test 7 (HasTermAxis): termOf returns the LAST Note component for pair/triple.-testPolicyHasTermAxis :: IO ()-testPolicyHasTermAxis = do- assertEqual "Policy HasTermAxis: pair termOf = snd" (7 :: Int) (Policy.termOf ("e", 7 :: Int))- assertEqual "Policy HasTermAxis: triple termOf = 3rd" (9 :: Int)- (Policy.termOf ("e1", "e2", 9 :: Int))---- ================================================================--- Simulate.Network tests (Phase 3, feat/trade-network)------ Property + unit tests for the TradeNetwork / InputCoefficients separation,--- the deterministic generators, the smart-constructor invariants, and the--- edge-summation sigmaEdges. All read-outs are Ord-ascending (no hash order).--- ================================================================--type NetJD = Journal (Int, Int) MoneyDecimal (HatBase CountUnit)---- a tiny per-edge journal builder used by the sigmaEdges equivalence test-edgeJ :: Int -> Int -> NetJD-edgeJ i j = ((fromIntegral (i + 2 * j) :: MoneyDecimal) .@ Not :< Amount) .| (i, j)---- Test 1: completeNetwork makes sigmaEdges coincide with the all-pairs sum.--- "The notation is unchanged; only the set Σ runs over changes."-testNetCompleteEquiv :: IO ()-testNetCompleteEquiv = do- let ks = [1 .. 6 :: Int]- viaEdges = sigmaEdges (completeNetwork ks) edgeJ :: NetJD- viaPairs = EJ.sigma2When ks ks (/=) edgeJ :: NetJD- assertEqual "Network: sigmaEdges complete == all-pairs sigma2When (exact)"- (toMap viaPairs) (toMap viaEdges)---- Test 2: determinism (DET-1). Same StdGen -> identical edges for every--- generator, checked by running each twice and comparing.-testNetDeterminism :: IO ()-testNetDeterminism = do- let ks = [1 .. 30 :: Int]- g = mkStdGen 42- twice f = assertEqual ("Network DET-1: " ++ fst f) (edges (snd f g)) (edges (snd f g))- twice ("kRegular", \s -> kRegular s ks 4)- twice ("erdosRenyi", \s -> erdosRenyi s ks 0.3)- twice ("scaleFree", \s -> scaleFree s ks 3)- twice ("sectorBlock",\s -> sectorBlock s [(k, k `mod` 3) | k <- ks] (\(a,b) -> if a==b then 0.5 else 0.1))---- Test 3: smart constructors reject the four illegal cases.-testNetSmartConstructor :: IO ()-testNetSmartConstructor = do- assertEqual "Network: self-loop rejected"- (Left SelfLoop) (tradeNetwork [1,2] [(1,1)] :: Either NetworkError (TradeNetwork Int))- assertEqual "Network: duplicate edge rejected"- (Left DuplicateEdge) (tradeNetwork [1,2] [(1,2),(1,2)] :: Either NetworkError (TradeNetwork Int))- let Right g = tradeNetwork [1,2,3] [(1,3)] :: Either NetworkError (TradeNetwork Int)- assertEqual "Network: coefficient outside network rejected"- (Left CoefOutsideNetwork)- (inputCoefficients g [(2,3,0.5)] :: Either NetworkError (InputCoefficients Int Double))- assertEqual "Network: negative coefficient rejected"- (Left NegativeCoefficient)- (inputCoefficients g [(1,3,-0.5)] :: Either NetworkError (InputCoefficients Int Double))- assertEqual "Network: duplicate coefficient rejected"- (Left DuplicateCoefficient)- (inputCoefficients g [(1,3,0.2),(1,3,0.3)] :: Either NetworkError (InputCoefficients Int Double))---- Test 4: Hawkins-Simon — every buyer's column sum is strictly below 1.-testNetHawkinsSimon :: IO ()-testNetHawkinsSimon = do- let g = completeNetwork [1 .. 12 :: Int]- a = randomCoefficients (mkStdGen 11) defaultCoefOptions g :: InputCoefficients Int Double- ok = all (\j -> sum (Prelude.map snd (inputsOf a j)) < 1.0) (nodes g)- assertEqual "Network: randomCoefficients (hawkinsSimon) all column sums < 1" True ok---- Test 5: generator structure.-testNetGeneratorStructure :: IO ()-testNetGeneratorStructure = do- let ks = [1 .. 8 :: Int]- kr = kRegular (mkStdGen 3) ks 3 :: TradeNetwork Int- assertEqual "Network: kRegular in-degree = min k (N-1)"- (replicate (length ks) 3)- (Prelude.map (length . suppliersOf kr) (nodes kr))- -- erdosRenyi p=1 == complete, p=0 == empty- assertEqual "Network: erdosRenyi p=1 == completeNetwork edges"- (edges (completeNetwork ks))- (edges (erdosRenyi (mkStdGen 0) ks 1.0 :: TradeNetwork Int))- assertEqual "Network: erdosRenyi p=0 has no edges"- 0 (edgeCount (erdosRenyi (mkStdGen 0) ks 0.0 :: TradeNetwork Int))- -- scaleFree edge count is deterministic: C(m+1,2) + (N-m-1)*m- let n = length ks; m = 2- expected = (m * (m + 1) `div` 2) + (n - m - 1) * m- assertEqual "Network: scaleFree edge count matches preferential-attachment formula"- expected (edgeCount (scaleFree (mkStdGen 9) ks m :: TradeNetwork Int))---- Test 6: out/in adjacency consistency on an arbitrary generated network.--- (i,j) in edges <=> i in suppliersOf j <=> j in buyersOf i-testNetAdjacencyConsistency :: IO ()-testNetAdjacencyConsistency = do- let ks = [1 .. 25 :: Int]- g = erdosRenyi (mkStdGen 77) ks 0.25 :: TradeNetwork Int- es = edges g- fwd = all (\(i,j) -> i `elem` suppliersOf g j && j `elem` buyersOf g i) es- -- and the reverse: every (i,j) reconstructed from suppliersOf equals edges- viaSuppliers = L.sort [ (i, j) | j <- nodes g, i <- suppliersOf g j ]- viaBuyers = L.sort [ (i, j) | i <- nodes g, j <- buyersOf g i ]- assertEqual "Network: edges <=> suppliersOf (forward)" True fwd- assertEqual "Network: edges == reconstruction from suppliersOf" (L.sort es) viaSuppliers- assertEqual "Network: edges == reconstruction from buyersOf" (L.sort es) viaBuyers---- Test 7: CSV round-trip — parse . render == id (render is a test helper).-renderEdgeCsv :: [(T.Text, T.Text)] -> T.Text-renderEdgeCsv rows = T.unlines (T.pack "from,to" : [ a <> T.pack "," <> b | (a, b) <- rows ])--renderCoefCsv :: [(T.Text, T.Text, Double)] -> T.Text-renderCoefCsv rows =- T.unlines (T.pack "from,to,coef" :- [ a <> T.pack "," <> b <> T.pack "," <> T.pack (show c) | (a, b, c) <- rows ])--testNetCsvRoundTrip :: IO ()-testNetCsvRoundTrip = do- let eRows = [(T.pack "a", T.pack "b"), (T.pack "b", T.pack "c"), (T.pack "a", T.pack "c")]- assertEqual "Network: edge CSV parse . render == id"- (Right eRows) (parseEdgeCsv (renderEdgeCsv eRows))- let cRows = [(T.pack "a", T.pack "b", 0.25), (T.pack "b", T.pack "c", 0.5)]- assertEqual "Network: coef CSV parse . render == id"- (Right cRows) (parseCoefCsv (renderCoefCsv cRows))- -- ingestion helpers agree with the network/coefficient invariants- let Right (g, a) = coefficientsFromTable [(1,3,0.2),(2,3,0.5)]- :: Either NetworkError (TradeNetwork Int, InputCoefficients Int Double)- assertEqual "Network: coefficientsFromTable edges" [(1,3),(2,3)] (edges g)- assertEqual "Network: coefficientsFromTable inputsOf" [(1,0.2),(2,0.5)] (inputsOf a 3)- -- fromCoefficientMatrix drops zero cells from the support- let m i j = if i < j then fromIntegral (i + j) else 0 :: Double- (gm, am) = fromCoefficientMatrix [1,2,3 :: Int] m- assertEqual "Network: fromCoefficientMatrix support drops zeros"- [(1,2),(1,3),(2,3)] (edges gm)- assertEqual "Network: fromCoefficientMatrix coefficient" (Just 4.0) (coefficient am 1 3)- -- networkFromTable derives nodes from rows- let Right gt = networkFromTable [(1,2),(2,3)] :: Either NetworkError (TradeNetwork Int)- assertEqual "Network: networkFromTable derives node set" [1,2,3] (nodes gt)---- Test 8: the requested sparse regimes have exactly m*N edges.-testIndustrialNetworkEdgeCount :: IO ()-testIndustrialNetworkEdgeCount = do- let cases = [(200, 5, 20), (1000, 4, 10)]- forM_ cases $ \(n, k, m) -> do- let economy = industrialNetwork 2025 n k m- assertEqual ("Industrial network: exact |E|=mN for " ++ show (n,k,m))- (m * n) (edgeCount (ieNetwork economy))- let capacityLimited = industrialNetwork 1 10 1 20- assertEqual "Industrial network: capacity shortage uses every eligible pair"- 45 (edgeCount (ieNetwork capacityLimited))- let nearOne = industrialNetworkWith- defaultIndustrialOptions { ioExponent = 1.001 } 1 1000 3 5- assertEqual "Industrial network: gamma near 1 keeps every size finite"- True (all (\w -> w > 0 && not (isNaN w) && not (isInfinite w))- (M.elems (ieSize nearOne)))- assertEqual "Industrial network: gamma near 1 retains exact |E|=mN"- 5000 (edgeCount (ieNetwork nearOne))---- Test 9: every edge obeys the ordered-sector DAG invariant and is unique.-testIndustrialNetworkStructure :: IO ()-testIndustrialNetworkStructure = do- let economy = industrialNetwork 19 500 5 12- es = industrialEdges economy- sectors = ieSector economy- valid (i, j) =- let si = sectors M.! i- sj = sectors M.! j- in i /= j && (si < sj || (si == sj && i < j))- assertEqual "Industrial network: no duplicate edges"- (length es) (Set.size (Set.fromList es))- assertEqual "Industrial network: sector order and intra-sector id DAG"- True (all valid es)---- Test 10: the integer seed fixes sectors, sizes, and edges.-testIndustrialNetworkDeterminism :: IO ()-testIndustrialNetworkDeterminism = do- let a = industrialNetwork 4242 300 5 8- b = industrialNetwork 4242 300 5 8- assertEqual "Industrial network: same seed gives identical economy" a b---- Test 11: one-sector economies use the id order as a DAG and still hit m*N.-testIndustrialNetworkKOne :: IO ()-testIndustrialNetworkKOne = do- let n = 200- m = 20- economy = industrialNetwork 3 n 1 m- es = industrialEdges economy- assertEqual "Industrial network: K=1 exact |E|=mN" (m * n) (length es)- assertEqual "Industrial network: K=1 edges are increasing ids"- True (all (uncurry (<)) es)---- Test 12: market-scale construction smoke. There is deliberately no timing--- assertion; forcing the full 1.28M-edge economy catches accidental all-pairs--- construction and latent exceptions while remaining machine-independent.-testIndustrialNetworkLarge :: IO ()-testIndustrialNetworkLarge = do- economy <- evaluate (force (industrialNetwork 2025 64000 5 20))- assertEqual "Industrial network: N=64000 smoke exact |E|=mN"- (64000 * 20) (edgeCount (ieNetwork economy))---- Test 13: exact one-pass flow identities, divisibility, and tax cancellation.-testIndustrialFlowsIdentities :: IO ()-testIndustrialFlowsIdentities = do- let rate = TaxRate 1 10- den = taxDenominator rate- economy = industrialNetwork 2025 300 5 12- flows = industrialFlows rate economy- net = ieNetwork economy- js = firms economy- z i j = M.findWithDefault 0 (i,j) (flowTrade flows)- x j = flowOutput flows M.! j- input j = flowInput flows M.! j- va j = flowValueAdded flows M.! j- f j = flowFinalDemand flows M.! j- incoming j = sum [ z i j | i <- suppliersOf net j ]- outgoing j = sum [ z j m | m <- buyersOf net j ]- allAmounts = M.elems (flowTrade flows)- ++ M.elems (flowOutput flows)- ++ M.elems (flowInput flows)- ++ M.elems (flowValueAdded flows)- ++ M.elems (flowFinalDemand flows)- taxReceivedTrade = sum- [ taxOf rate (z i j) | i <- js, j <- buyersOf net i ]- taxPaidTrade = sum- [ taxOf rate (z i j) | j <- js, i <- suppliersOf net j ]- finalTax = sum [ taxOf rate (f j) | j <- js ]- netTax = taxReceivedTrade + finalTax - taxPaidTrade- expectedTax = taxNumerator rate * sum (Prelude.map f js) `div` den- assertEqual "Industrial flows: all final demand positive" True (all ((> 0) . f) js)- assertEqual "Industrial flows: all value added non-negative" True (all ((>= 0) . va) js)- assertEqual "Industrial flows: output = orders + final demand"- True (all (\j -> x j == outgoing j + f j) js)- assertEqual "Industrial flows: output = input + value added"- True (all (\j -> x j == incoming j + va j && input j == incoming j) js)- assertEqual "Industrial flows: every amount divisible by tax denominator"- True (all (\amount -> amount `mod` den == 0) allAmounts)- assertEqual "Industrial flows: sum value added = sum final demand"- (sum (Prelude.map f js)) (sum (Prelude.map va js))- assertEqual "Industrial flows: trade output tax equals trade input tax"- taxReceivedTrade taxPaidTrade- assertEqual "Industrial flows: trade tax cancels and net tax equals final-demand tax"- expectedTax netTax---- Test 14: zero allocations are retained per edge, and a hand-built economy--- outside the ordered DAG is rejected before backward substitution.-testIndustrialFlowEdgeCases :: IO ()-testIndustrialFlowEdgeCases = do- let rate = TaxRate 1 10- economy = industrialNetwork 11 50 3 5- zeroFlows = industrialFlowsWith (FlowOptions 10 0.5) rate economy- assertEqual "Industrial flows: sub-denominator inputs permit z_ij=0"- True (not (M.null (flowTrade zeroFlows)) && any (== 0) (M.elems (flowTrade zeroFlows)))- let Right badNetwork = tradeNetwork [1,2] [(2,1)]- :: Either NetworkError (TradeNetwork Int)- badEconomy = IndustrialEconomy- { ieNetwork = badNetwork- , ieSector = M.fromList [(1,0),(2,0)]- , ieSize = M.fromList [(1,1),(2,1)] }- rejected <- try (evaluate (force (industrialFlows rate badEconomy)))- :: IO (Either SomeException (IndustrialFlows Int))- assertEqual "Industrial flows: unordered hand-built economy rejected"- True (case rejected of Left _ -> True; Right _ -> False)---- ================================================================--- MarketModel equivalence tests (Phase 5, feat/market-scale-experiments)------ The examples/market/MarketModel.hs core cannot be imported here (it declares--- an orphan `instance StateTime Int` that would clash with the SICE harness's--- `instance StateTime SimTerm`), so the trade simple/tuned stages and a small--- BSP world are re-stated minimally (per the Phase 5 plan §2 commit 3 note).--- We check the two properties the plan puts in CI:--- (a) tradeStageSimple ≡ tradeStageTuned, EXACTLY, under MoneyDecimal;--- (b) Sequential ≡ ParChunk (DET-2) for the whole 3-stage model, exactly.--- (Perf ratios are out of CI; they live in run-market-experiments.sh.)--- ================================================================---- 4-axis base (AccountTitles, owner, counterparty, CountUnit), mirroring--- MarketModel.MBase. It is exactly the SICE harness's SimHatBase2, so we reuse--- that type (and its ExBaseClass / Element Int / BaseClass Int instances)--- instead of re-declaring them.-type MktFirm = SimCompany -- = Int---- ADT event tag mirroring MarketModel.MTag (typo'd tags become compile errors,--- not silently-empty projections). 'MktPlank' is the explicit blank tag.-data MktTag = MktPlank | MktTrade | MktProduction | MktReport | MktClosing | MktCarryover- deriving (Show, Eq, Ord, Enum, Bounded, Generic)-instance Hashable MktTag--- needed so the spill / runLiteWithPolicy window-transparency test can serialize--- a @Journal MktNote v b@ (derived structurally from Generic).-instance Binary.Binary MktTag-instance Note MktTag where- plank = MktPlank--type MktNote = (MktTag, Int)-type MktBase = SimHatBase2 -- = HatBase (AccountTitles, Int, Int, CountUnit)-type MktLedgM = Journal MktNote MoneyDecimal MktBase--data MktW v f = MktW- { mkLedger :: HK f (Journal MktNote v MktBase)- , mkNet :: HK f (TradeNetwork MktFirm)- , mkCoef :: HK f (InputCoefficients MktFirm v)- } deriving Generic---- own-product classifier shared by the mirror.-mktOwnerOfProduct :: BasePart MktBase -> Maybe MktFirm-mktOwnerOfProduct bp = case bp of- (Products, o, c, _) | o == c -> Just o- _ -> Nothing---- opening stock read from the (MktCarryover, t) note (indexed per-note),--- mirroring MarketModel.openingMap (carryover-based O(term) inventory).-mktOpening :: (HatVal v, Real v)- => Int -> Journal MktNote v MktBase -> M.Map MktFirm v-mktOpening t ledger =- EA.balanceMapBy mktOwnerOfProduct- (EJ.toAlg (EJ.projWithNote [(MktCarryover, t)] ledger))---- single-firm opening read (indexed per-note + per-base), mirroring--- MarketModel.openingOf: balanceBy over firm j's own-product base only.-mktOpeningOf :: (HatVal v, Real v)- => Int -> MktFirm -> Journal MktNote v MktBase -> v-mktOpeningOf t j ledger =- EA.balanceBy [Not :< (Products, j, j, Amount)]- [Hat :< (Products, j, j, Amount)]- (EJ.toAlg (EJ.projWithNote [(MktCarryover, t)] ledger))---- single-firm inventory-connected demand, mirroring MarketModel.demandOf.-mktDemandOf :: (HatVal v, Real v)- => Double -> Int -> MktFirm -> Journal MktNote v MktBase -> Double-mktDemandOf target t j ledger =- max 0 (target - realToFrac (mktOpeningOf t j ledger))--mktPurchase :: (HatVal v) => v -> MktFirm -> MktFirm -> EA.Alg v MktBase-mktPurchase amt i j =- amt .@ Not :< (Products, j, j, Amount)- .+ amt .@ Hat :< (Cash, j, j, Yen)- .+ amt .@ Not :< (Purchases, j, j, Yen)- .+ amt .@ Not :< (Cash, i, i, Yen)- .+ amt .@ Not :< (Sales, i, i, Yen)- .+ amt .@ Hat :< (Products, i, i, Amount)--mktOrderAmt :: (HatVal v, Real v)- => InputCoefficients MktFirm v -> Double -> MktFirm -> MktFirm -> Double-mktOrderAmt coef d i j =- realToFrac (maybe 0 id (coefficient coef i j)) * d---- per-firm trade stage (stageFor over the firm list), mirroring--- MarketModel.tradeStageSimple: buyer j folds its in-edges (suppliersOf).-mktTradeSimple :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase-mktTradeSimple fs target = stageOf MktTrade fs $ \w t _g j ->- -- single-note stage: emit the bare Alg; the runner attaches (MktTrade, t).- let net = mkNet w; coef = mkCoef w- d = mktDemandOf target t j (mkLedger w)- sup = suppliersOf net j- one i = let amt = realToFrac (mktOrderAmt coef d i j)- in if amt <= 0 then mempty else mktPurchase amt i j- in EA.sigma sup one--mktTradeTuned :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase-mktTradeTuned fs target = stageOf MktTrade fs $ \w t _g j ->- -- single-note stage: see 'mktTradeSimple'.- let net = mkNet w; coef = mkCoef w- d = mktDemandOf target t j (mkLedger w)- sup = suppliersOf net j- accum = L.foldl' step M.empty sup- step acc i =- let amt = realToFrac (mktOrderAmt coef d i j)- in if amt <= 0 then acc- else L.foldl' (\m (b, v) -> M.insertWith (+) b v m) acc- [ (Not :< (Products, j, j, Amount), amt)- , (Hat :< (Cash, j, j, Yen), amt)- , (Not :< (Purchases, j, j, Yen), amt)- , (Not :< (Cash, i, i, Yen), amt)- , (Not :< (Sales, i, i, Yen), amt)- , (Hat :< (Products, i, i, Amount), amt) ]- in EA.sigmaFromMap accum (\b v -> v .@ b)--mktProduction :: (HatVal v, Real v) => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase-mktProduction fs target = stageOf MktProduction fs $ \w t _g j ->- -- single-note stage: emit the bare Alg; the runner attaches (MktProduction, t).- let amt = realToFrac (mktDemandOf target t j (mkLedger w))- in if amt <= 0 then mempty- else (amt .@ Hat :< (Products, j, j, Amount))- .+ (amt .@ Not :< (SalesCost, j, j, Yen))--mktReport :: (HatVal v) => Stage (MktW v) Int MktNote v MktBase-mktReport = stageOf MktReport [()] $ \w t _g () ->- -- single-note aggregate stage: emit the bare Alg; runner attaches (MktReport, t).- let flow = EJ.toAlg (EJ.projWithNote [(MktTrade, t), (MktProduction, t)] (mkLedger w))- shortageK b = case b of- Hat :< (Products, o, c, _) | o == c -> Just o- _ -> Nothing- in EA.postFromNetBy shortageK (\j v -> v .@ Not :< (Products, j, j, Amount)) flow---- carryover stage (mirror): net this term's own-product stock and roll the--- positive surplus into (MktCarryover, t+1). Mirrors MarketModel.carryoverStage.-mktCarryover :: (HatVal v, Real v) => Stage (MktW v) Int MktNote v MktBase-mktCarryover = stage "closing" $ \w t ->- let termAlg = EJ.toAlg (EJ.filterByAxis 1 (NoteAxisKey (t :: Int)) (mkLedger w))- netMap = EA.balanceMapBy mktOwnerOfProduct termAlg- perFirm (j, v) =- if v <= 0 then mempty- else ((v .@ Hat :< (Products, j, j, Amount)) .| (MktClosing, t))- <> ((v .@ Not :< (Products, j, j, Amount)) .| (MktCarryover, t + 1))- in mconcat [ perFirm kv | kv <- M.toList netMap ]---- a fixed small (G, A) used by both equivalence tests.-mktBuild :: (HatVal v) => Int -> (TradeNetwork MktFirm, InputCoefficients MktFirm v)-mktBuild n =- let (gG, gA) = split (mkStdGen 2025)- fs = [1 .. n]- net = erdosRenyi gG fs 0.3- a = randomCoefficients gA defaultCoefOptions net- in (net, a)--mktSpec :: (HatVal v, Real v)- => Bool -> Int -> Int -> Par -> SimSpec (MktW v) Int MktNote v MktBase-mktSpec tuned n lastT par =- let fs = [1 .. n] in- (mkSimSpec (1, lastT) 2025 mkLedger- [ (if tuned then mktTradeTuned else mktTradeSimple) fs 10- , mktProduction fs 10- , mktReport- , mktCarryover ])- { Lite.specParallel = par }--mktW0 :: (HatVal v) => Int -> MktW v InitT-mktW0 n = let (net, a) = mktBuild n- in MktW { mkLedger = carry mempty, mkNet = carry net, mkCoef = carry a }---- (a) simple ≡ tuned, exactly, under MoneyDecimal (N=30, T=5).--- the redundant-algebra-correct "same result": net each note's Alg per base--- ('bar' drops the canceled part and any zero-padding), keeping the Hat/Not--- side. simple and tuned differ ONLY in seq redundancy (simple keeps the--- per-edge posting sequence; tuned pre-sums per base), so they are equal exactly--- after netting. (norm additivity already holds; this is the stronger per-base--- exact check.)-nettedMktMap :: Journal MktNote MoneyDecimal MktBase- -> HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)-nettedMktMap = toMap . EJ.map EA.bar--testMarketSimpleTunedEqual :: IO ()-testMarketSimpleTunedEqual = do- let simpleL = runLite (mktSpec False 30 5 Sequential) (mktW0 30) mkLedger- :: MktLedgM- tunedL = runLite (mktSpec True 30 5 Sequential) (mktW0 30) mkLedger- assertEqual "Market: tradeStageSimple == tradeStageTuned (MoneyDecimal, exact per-base net)"- (nettedMktMap simpleL) (nettedMktMap tunedL)- -- gross volume must also agree: bar-equality alone cannot detect an- -- accidental early Hat/Not netting in the tuned path (bar is idempotent,- -- but the pre-bar norm would shrink). norm pins the gross posting volume.- assertEqual "Market: simple/tuned gross volume (norm) agrees (no early netting)"- (norm simpleL) (norm tunedL)---- (b) DET-2: Sequential ≡ ParChunk, exactly, under MoneyDecimal (simple path).-testMarketSeqParEqual :: IO ()-testMarketSeqParEqual = do- let seqM = runLite (mktSpec False 30 5 Sequential) (mktW0 30) (toMap . mkLedger)- :: HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)- parM = runLite (mktSpec False 30 5 (ParChunk 8)) (mktW0 30) (toMap . mkLedger)- assertEqual "Market DET-2: Sequential == ParChunk (MoneyDecimal, exact)"- seqM parM---- (c) sanity: the report's net shortage is strictly positive (Hawkins-Simon),--- and the complete-network setting also runs (a participating-set sanity).-testMarketShortagePositive :: IO ()-testMarketShortagePositive = do- let finalSh = runLite (mktSpec False 24 4 Sequential) (mktW0 24)- (\final -> norm (EJ.projWithNote [(MktReport, 4)] (mkLedger final)))- :: MoneyDecimal- assertEqual "Market: final-term net shortage is strictly positive (Hawkins-Simon)"- True (finalSh > 0)- -- complete network on a tiny N just exercises the dense edge set end-to-end.- let (gG, gA) = split (mkStdGen 2025)- cnet = completeNetwork [1 .. 8 :: MktFirm]- ccoef = randomCoefficients gA defaultCoefOptions cnet :: InputCoefficients MktFirm MoneyDecimal- cw0 = MktW { mkLedger = carry mempty, mkNet = carry cnet, mkCoef = carry ccoef }- cfs = [1 .. 8 :: MktFirm]- cspec = (mkSimSpec (1, 3) 2025 mkLedger- [ mktTradeSimple cfs 10, mktProduction cfs 10, mktReport, mktCarryover ])- cNorm = runLite cspec cw0 (norm . mkLedger) :: MoneyDecimal- _ = gG- assertEqual "Market: complete-network run produces a positive ledger norm"- True (cNorm > 0)---- (d) WINDOW-TRANSPARENCY SENTINEL (Phase 5 fix, modification 3):--- the carryover bookkeeping makes the model self-contained per term, so a--- RetainRecent window must NOT change the observable result. Assert that--- RetainRecent 2 (+ spill) and RetainAll produce the EXACT SAME final-term--- report norm AND final carryover map (MoneyDecimal, so equality is exact).--- This permanently guards against the bug this round fixed (a full-ledger--- inventory sweep silently re-reading a window-truncated net: 9974.74 vs--- 9993.56). N=40, T=8 so the window (2) is strictly smaller than the history.-testMarketWindowTransparent :: IO ()-testMarketWindowTransparent = withTempSpill "market_window" $ \path -> do- let n = 40; lastT = 8- spec = mktSpec False n lastT Sequential- w0 = mktW0 n- -- project the two observables we pin: the final-term report norm and the- -- final carryover map (the next-term opening, keyed by firm).- project final =- let lj = mkLedger final :: MktLedgM- reportN = norm (EJ.projWithNote [(MktReport, lastT)] lj) :: MoneyDecimal- carryM = EA.balanceMapBy mktOwnerOfProduct- (EJ.toAlg (EJ.projWithNote [(MktCarryover, lastT + 1)] lj))- :: M.Map MktFirm MoneyDecimal- in (reportN, carryM)- polAll = Policy.defaultLedgerPolicy { Policy.retain = Policy.RetainAll }- polWin = Policy.defaultLedgerPolicy- { Policy.retain = Policy.RetainRecent 2, Policy.spillTo = Just path }- (allN, allM) <- runLiteWithPolicy polAll spec w0 project- (winN, winM) <- runLiteWithPolicy polWin spec w0 project- assertEqual "Market window-transparency: final report norm equal under RetainAll vs RetainRecent 2 + spill"- allN winN- assertEqual "Market window-transparency: final carryover map equal under RetainAll vs RetainRecent 2 + spill"- allM winM---- (e) stageOf AUTO-NOTE SENTINEL: a 'stageOf' stage and the equivalent manual--- 'stageFor' that writes @.| (tag, t)@ itself must produce the EXACT SAME ledger--- (MoneyDecimal, so equality is exact). This pins the semantics of the runner's--- single auto-attachment of @(stTag, t)@: moving the note from the stage body--- into 'runStage' changes nothing observable (incl. the zero-drop at the sigma--- commit). The manual stages below are byte-for-byte the bodies of the migrated--- mirror stages, but tagged explicitly with the OLD @if isZero then mempty@ form.-mktTradeSimpleManual :: (HatVal v, Real v)- => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase-mktTradeSimpleManual fs target = stageFor "trade" fs $ \w t _g j ->- let net = mkNet w; coef = mkCoef w- d = mktDemandOf target t j (mkLedger w)- sup = suppliersOf net j- one i = let amt = realToFrac (mktOrderAmt coef d i j)- in if amt <= 0 then mempty else mktPurchase amt i j- alg = EA.sigma sup one- in if EA.isZero alg then mempty else alg .| (MktTrade, t)--mktProductionManual :: (HatVal v, Real v)- => [MktFirm] -> Double -> Stage (MktW v) Int MktNote v MktBase-mktProductionManual fs target = stageFor "production" fs $ \w t _g j ->- let amt = realToFrac (mktDemandOf target t j (mkLedger w))- in if amt <= 0 then mempty- else ((amt .@ Hat :< (Products, j, j, Amount))- .+ (amt .@ Not :< (SalesCost, j, j, Yen)))- .| (MktProduction, t)--mktReportManual :: (HatVal v) => Stage (MktW v) Int MktNote v MktBase-mktReportManual = stage "report" $ \w t ->- let flow = EJ.toAlg (EJ.projWithNote [(MktTrade, t), (MktProduction, t)] (mkLedger w))- shortageK b = case b of- Hat :< (Products, o, c, _) | o == c -> Just o- _ -> Nothing- sh = EA.postFromNetBy shortageK (\j v -> v .@ Not :< (Products, j, j, Amount)) flow- in if EA.isZero sh then mempty else sh .| (MktReport, t)---- the same 4-stage spec as 'mktSpec' but with the three single-note stages--- expressed via manual stageFor + explicit @.| (tag, t)@ (carryover unchanged).-mktSpecManual :: (HatVal v, Real v)- => Int -> Int -> Par -> SimSpec (MktW v) Int MktNote v MktBase-mktSpecManual n lastT par =- let fs = [1 .. n] in- (mkSimSpec (1, lastT) 2025 mkLedger- [ mktTradeSimpleManual fs 10- , mktProductionManual fs 10- , mktReportManual- , mktCarryover ])- { Lite.specParallel = par }--testMarketStageOfAutoNote :: IO ()-testMarketStageOfAutoNote = do- let stageOfL = runLite (mktSpec False 30 5 Sequential) (mktW0 30) (toMap . mkLedger)- :: HM.HashMap MktNote (EA.Alg MoneyDecimal MktBase)- manualL = runLite (mktSpecManual 30 5 Sequential) (mktW0 30) (toMap . mkLedger)- assertEqual "Market stageOf auto-note: stageOf ledger == manual stageFor + .| (tag, t) (MoneyDecimal, exact)"- stageOfL manualL---- ================================================================--- ExchangeAlgebra.Optimize: pluggable solvers (Annealing / GA)--- ================================================================---- | Shared annealing configuration: minimize over 'Double' with a--- uniform-step neighbor, geometric cooling and Metropolis acceptance.-optAnnealCfg :: OA.AnnealingConfig Double-optAnnealCfg = OA.AnnealingConfig- { OA.acDirection = O.Minimize- , OA.acSteps = 2000- , OA.acSchedule = OA.geometricCooling 1.0 0.995- , OA.acNeighbor = \g x -> let (d, g') = randomR (-0.5, 0.5) g in (x + d, g')- , OA.acAccept = OA.metropolis- , OA.acSeed = 20260717- }--optSphere :: UV.Vector Double -> Double-optSphere v = UV.sum (UV.map (\x -> (x - 1) ** 2) v)---- | Annealing locates the minimum of a 1-D quadratic (seed-deterministic),--- and the reported score is the returned candidate's objective value--- (score contract: no re-evaluation, user orientation).-testOptimizeAnnealingQuadratic :: IO ()-testOptimizeAnnealingQuadratic = do- (best, score) <- O.optimize OA.Annealing optAnnealCfg- (\x -> return ((x - 3) ** 2)) 0- assertEqual "Optimize.Annealing: quadratic minimum located (|x-3| < 0.2)"- True (abs (best - 3) < 0.2)- assertNear "Optimize.Annealing: score equals returned candidate's value"- ((best - 3) ** 2) score---- | GA approaches the sphere minimum, keeps the dimension, and respects--- per-gene bounds.-testOptimizeGASphere :: IO ()-testOptimizeGASphere = do- let cfg = OG.defaultGAConfig { OG.gaSeed = 20260717, OG.gaGenerations = 80 }- (best, score) <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)- assertEqual "Optimize.GA: chromosome dimension preserved" 3 (UV.length best)- assertEqual "Optimize.GA: sphere minimum approached (score < 0.05)"- True (score < 0.05)- let bs = UV.replicate 3 (-0.5, 0.5)- bCfg = OG.defaultGAConfig { OG.gaSeed = 3- , OG.gaGenerations = 40- , OG.gaBounds = Just bs }- (bBest, _) <- O.optimize OG.GA bCfg (return . optSphere) (UV.replicate 3 0)- assertEqual "Optimize.GA: bounds respected by every gene"- True (UV.all (\x -> x >= -0.5 && x <= 0.5) bBest)---- | Maximize reports the score in the user's orientation.-testOptimizeGAMaximize :: IO ()-testOptimizeGAMaximize = do- let cfg = OG.defaultGAConfig { OG.gaDirection = O.Maximize- , OG.gaSeed = 7- , OG.gaGenerations = 80 }- (_, score) <- O.optimize OG.GA cfg (return . negate . optSphere)- (UV.replicate 3 0)- assertEqual "Optimize.GA: Maximize reports user-oriented score (> -0.05)"- True (score > -0.05)---- | Acceptance-criteria demo: the objective runs in @ST s@ (mutable--- evaluation counter), and the solver evaluates exactly once per step--- plus once for the initial candidate.-testOptimizeSTObjective :: IO ()-testOptimizeSTObjective = do- let cfg = optAnnealCfg { OA.acSteps = 100 }- (best, score, evals) = runST $ do- ref <- newSTRef (0 :: Int)- (b, s) <- O.optimize OA.Annealing cfg- (\x -> modifySTRef' ref (+ 1) >> return ((x - 3) ** 2)) 0- n <- readSTRef ref- return (b, s, n)- assertEqual "Optimize: ST objective evaluated once per step + initial"- (OA.acSteps cfg + 1) evals- assertEqual "Optimize: ST run returns finite (best, score)"- True (not (isNaN best) && not (isNaN score) && not (isInfinite score))---- | Same seed, same (deterministic) objective => identical result.-testOptimizeDeterminism :: IO ()-testOptimizeDeterminism = do- r1 <- O.optimize OA.Annealing optAnnealCfg (\x -> return ((x - 3) ** 2)) 0- r2 <- O.optimize OA.Annealing optAnnealCfg (\x -> return ((x - 3) ** 2)) 0- assertEqual "Optimize.Annealing: same seed => identical result" r1 r2- let cfg = OG.defaultGAConfig { OG.gaSeed = 11, OG.gaGenerations = 20 }- s1 <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)- s2 <- O.optimize OG.GA cfg (return . optSphere) (UV.replicate 3 0)- assertEqual "Optimize.GA: same seed => identical result" s1 s2---- | Fail-fast contract: non-finite objective scores and invalid--- configurations are rejected with 'error', never silently absorbed.-testOptimizeFailFast :: IO ()-testOptimizeFailFast = do- r <- try (O.optimize OA.Annealing optAnnealCfg (\_ -> return (0 / 0)) 0)- case (r :: Either SomeException (Double, Double)) of- Left _ -> putStrLn "[PASS] Optimize.Annealing: NaN objective rejected (fail-fast)"- Right v -> do- putStrLn ("[FAIL] Optimize.Annealing: NaN objective accepted: " ++ show v)- exitFailure- let badCfg = OG.defaultGAConfig { OG.gaEliteCount = 999 }- r2 <- try (O.optimize OG.GA badCfg (return . optSphere) (UV.replicate 3 0))- case (r2 :: Either SomeException (UV.Vector Double, Double)) of- Left _ -> putStrLn "[PASS] Optimize.GA: invalid config rejected (fail-fast)"- Right v -> do- putStrLn ("[FAIL] Optimize.GA: invalid config accepted: " ++ show v)- exitFailure--main :: IO ()-main = do- ExactSumSpec.runTests- TransferRuleSpec.runTests- ProjWildcardSpec.runTests- PostingSpec.runTests- SettleSpec.runTests- CarrySpec.runTests- testAccountTitlesBinary- testPracticalAndManufacturingAccountTitles- testAccountTitleClassification- testReplaceNotesMatchesInsert- testMapPosting- testMapMaybePosting- testProjMultiPatternOnePass- testProjNormFastPath- testMoneyDecimalHashMatchesEq- testProjDuplicateExact- testProjExactWildcardOverlap- testProjNormBarIdentity- testProjWithBaseNorm- testProjWithNoteNorm- testProjWithBaseNormBothSided- testBasesNotSideRegression- testNumericToleranceScaleAware- testMoneyDecimalExactOrderIndependent- testSigmaMergePath- testSameBaseSeqOrderPathDependence- testSigma2When- testSigmaFromMap- testJournalFromListStrict- testUnionZeroSingletonBase- testScalarRejectsNegative- testProjConcreteNoIndexForce- testLinerReservedFieldsPoisoned- testJournalSigmaMergePath- testJournalSigma2When- testJournalSigmaOn- testJournalSigmaOnFromMap- testFilterByAxisEquivalent- testFilterByAxisWithDeltaUpdates- testFinalStockTransferAlgEquivalence- testFinalStockTransferJournalEquivalence- testFinalStockTransferAggregatedAlias- testFinalStockRegistryClosedDiff- testFinalStockRuleReference- testVocabOrdinalPin- testPreVland2SemanticsClosedDiff- testIncomeSummaryBalancedNoCrash- testSpillDecisionSingleSource- testRestoreJournalFromBinarySpill- testSpillCheckedReaderWellFormed- testSpillCheckedReaderTruncated- testSpillCheckedReaderStaleAppend- testSpillCheckedReaderOverlap- testSpillCheckedReaderGap- testSpillCheckedReaderEmptyRange- testSpillCheckedReaderEmptyFile- testSimulateEx1Default- testCsvTranspose- testCsvWriteCSV- testCsvWriteCSVWithQuotes- testCsvWriteCSVEmpty- testWriteFuncResultsCsv- testWriteBSPinned- testWritePLPinned- testWriteJournalPinned- testWriteCompoundTrialBalancePinned- testWriteAccountOfJournalPinned- testLiteBoilerplate- testLiteDet2- testLiteDet1- testLiteBspInvisibility- testLiteGateEquivalence- testLiteFieldRules- testLiteBoundaryOncePerTerm- testLiteObserverTerms- testLiteObserverBoundary- testLiteObserverEquivalence- testLiteObserverStreamingSpill- testPolicyEquivalence- testPolicyWindowRoundTrip- testPolicyCompressClosed- testPolicyDeleteOnly- testPolicyDeterminism- testPolicyClassicBridge- testPolicyHasTermAxis- testNetCompleteEquiv- testNetDeterminism- testNetSmartConstructor- testNetHawkinsSimon- testNetGeneratorStructure- testNetAdjacencyConsistency- testNetCsvRoundTrip- testIndustrialNetworkEdgeCount- testIndustrialNetworkStructure- testIndustrialNetworkDeterminism- testIndustrialNetworkKOne- testIndustrialNetworkLarge- testIndustrialFlowsIdentities- testIndustrialFlowEdgeCases- testMarketSimpleTunedEqual- testMarketSeqParEqual- testMarketShortagePositive- testMarketWindowTransparent- testMarketStageOfAutoNote- testConvertCsvRoundTrip- testAssistDescriptionsDrift- testAssistDescribeAccount- testAssistAllAccountInfos- testAccountMetadataLand1- testAccountMetadataLand1Golden- testAccountInfoLand1Migration- testAssistSuggestAccounts- testPostVocabGolden- testAccountSemanticsPrechangeGolden- testWriteRowsGolden- testRegistryGolden- testJcciAccountNameCoverage- testAccountLabelsLand4a- testRegistryWildcards- testRegistryContraLand2- testLand2SemanticsClosedDiff- testLand2InfoClosedDiff- testLand2SuggestClosedDiff- testLand2Contract- testLand2PimoFlip- testLand2ExchangeRelation- testLand2IsContraInstances- testLand2AiDivision- testLand3PresentationGroups- testLand2Presentation- testLand2PresentationClosedDiff- testLand2HatNotPolicy- checkedConvertProperties- axiomProperties- categoricalPhase1Properties- journalProperties- quotientProperties- bookkeepingProperties- closingDocsTests- testOptimizeAnnealingQuadratic- testOptimizeGASphere- testOptimizeGAMaximize- testOptimizeSTObjective- testOptimizeDeterminism- testOptimizeFailFast+-- | Assemble the existing suites and the seven domain suites.+module Main (main) where++import qualified Value.MoneyParseSpec as MoneyParseSpec+import qualified Algebra.ExactReadoutSpec as ExactReadoutSpec+import qualified Algebra.AccumSpec as AccumSpec+import qualified Algebra.ExactSumSpec as ExactSumSpec+import qualified Admission.Spec as AdmissionSpec+import qualified Transfer.RuleSpec as TransferRuleSpec+import qualified Transfer.BalanceSpec as TransferBalanceSpec+import qualified Algebra.ProjWildcardSpec as ProjWildcardSpec+import qualified Posting.PostingSpec as PostingSpec+import qualified Posting.SettleSpec as SettleSpec+import qualified Journal.CarrySpec as CarrySpec+import qualified Journal.SideTotalsSpec as SideTotalsSpec+import qualified Golden.WireFormat as WireFormat+import qualified Algebra.CoreSpec as CoreSpec+import qualified Journal.JournalSpec as JournalSpec+import qualified Journal.NormalFormSpec as NormalFormSpec+import qualified Accounting.AdjustmentSpec as AdjustmentSpec+import qualified Accounting.RegistrySpec as RegistrySpec+import qualified IO.InputOutputSpec as InputOutputSpec+import qualified Simulate.SimulateSpec as SimulateSpec+import qualified Simulation.NetworkSpec as NetworkSpec+import qualified Simulation.OptimizeSpec as OptimizeSpec++main :: IO ()+main = do+ ExactReadoutSpec.runTests+ AccumSpec.runTests+ MoneyParseSpec.runTests+ ExactSumSpec.runTests+ AdmissionSpec.runTests+ TransferRuleSpec.runTests+ TransferBalanceSpec.runTests+ ProjWildcardSpec.runTests+ PostingSpec.runTests+ SettleSpec.runTests+ CarrySpec.runTests+ SideTotalsSpec.runTests+ WireFormat.checkFixture+ CoreSpec.runTests+ JournalSpec.runTests+ NormalFormSpec.runTests+ AdjustmentSpec.runTests+ RegistrySpec.runTests+ InputOutputSpec.runTests+ SimulateSpec.runTests+ NetworkSpec.runTests+ OptimizeSpec.runTests
+ test/Support.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++-- | Shared assertions, generators, and fixture bases for the domain suites.+-- The domain suites share these fixtures and their instances.+-- Read the comparison helpers before the generators and fixture bases.+module Support (eps+ , assertEqual+ , assertNear+ , TestComparison(..)+ , runTestComparison+ , TestAlg+ , TransferAlg+ , TransferJournal+ , removeSpillTestFile+ , SimTerm+ , SimCompany+ , SimHatBase2+ , readFileStrict+ , quickProp+ , genUnit+ , genSide+ , genBase+ , genNNDouble+ , netByBase+ , epsEq+ , CheckedAlgM+ , exactBalancedForTest+ , checkedRate+ , withTestTemporaryFile+ ) where++import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import ExchangeAlgebra.Journal+import qualified ExchangeAlgebra.Algebra as EA+import qualified ExchangeAlgebra.Journal as EJ+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import qualified ExchangeAlgebra.Accounting.Adjustment as Adjustment+import ExchangeAlgebra.Simulate (StateTime, initTerm, lastTerm)+import qualified Data.Map.Strict as M+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import System.Exit (exitFailure)+import System.IO (openBinaryTempFile, hClose)+import System.Directory (removeFile, getTemporaryDirectory)+import Control.Exception (try, SomeException, bracket)+import Test.QuickCheck hiding (Fixed)++eps :: Double+eps = 1e-9++assertEqual :: (Eq a, Show a) => String -> a -> a -> IO ()+assertEqual label expected actual+ | expected == actual = putStrLn ("[PASS] " ++ label)+ | otherwise = do+ putStrLn ("[FAIL] " ++ label)+ putStrLn (" expected: " ++ show expected)+ putStrLn (" actual : " ++ show actual)+ exitFailure++assertNear :: String -> Double -> Double -> IO ()+assertNear label expected actual+ | abs (expected - actual) <= eps = putStrLn ("[PASS] " ++ label)+ | otherwise = do+ putStrLn ("[FAIL] " ++ label)+ putStrLn (" expected: " ++ show expected)+ putStrLn (" actual : " ++ show actual)+ exitFailure++-- | A comparison row keeps its expected value, observed value, and equality mode.+-- Exact comparisons may use different value types without converting their expectations.+data TestComparison+ = forall a. (Eq a, Show a) => EqualComparison String a a+ -- ^ Compare exact values using their original Eq instance.+ | NearComparison String Double Double+ -- ^ Compare Double values using the existing epsilon.++-- | Execute one comparison and retain the suite's existing PASS and FAIL diagnostics.+runTestComparison :: TestComparison -> IO ()+runTestComparison (EqualComparison label expected actual) = assertEqual label expected actual+runTestComparison (NearComparison label expected actual) = assertNear label expected actual++type TestAlg = EA.Alg Double (HatBase CountUnit)+++-- ================================================================+-- Transfer regression tests+-- ================================================================++type TransferAlg = EA.Alg Double SimHatBase2+type TransferJournal = EJ.Journal String Double SimHatBase2+++removeSpillTestFile :: FilePath -> IO ()+removeSpillTestFile path = do+ _ <- try (removeFile path) :: IO (Either SomeException ())+ pure ()+++-- ================================================================+-- SimulateEx1 reproduction (default scenario only, no parallelism)+-- ================================================================++type SimTerm = Int++instance StateTime SimTerm where+ initTerm = 1+ lastTerm = 100++type SimCompany = Int++instance Element SimCompany where+ wildcard = -1++instance BaseClass SimCompany where++type SimHatBase2 = HatBase (AccountTitles, SimCompany, SimCompany, CountUnit)++instance ExBaseClass SimHatBase2 where+ getAccountTitle (h :< (a, _, _, _)) = a+ setAccountTitle (h :< (_, c, e, u)) b = h :< (b, c, e, u)+++-- | Strict file read helper for tests+readFileStrict :: FilePath -> IO String+readFileStrict p = do+ bs <- TIO.readFile p+ return (T.unpack bs)++-- run a QuickCheck property in the existing IO-style harness+quickProp :: Testable p => String -> p -> IO ()+quickProp label p = do+ r <- quickCheckWithResult stdArgs { maxSuccess = 200, chatty = False } p+ if isSuccess r+ then putStrLn ("[PASS] " ++ label)+ else do putStrLn ("[FAIL] " ++ label); putStr (output r); exitFailure++-- generators: concrete (non-wildcard) bases, intentional collisions+genUnit :: Gen CountUnit+genUnit = elements [Yen, Dollar, Amount]++genSide :: Gen Hat+genSide = elements [Hat, Not]++genBase :: Gen (HatBase CountUnit)+genBase = (:<) <$> genSide <*> genUnit++genNNDouble :: Gen Double -- non-negative, finite+genNNDouble = do+ NonNegative x <- arbitrary+ if isNaN x || isInfinite x then genNNDouble else pure x++-- exact per-base signed net (Not +, Hat -) via Rational; the observable+-- accounting content. Robust to seq order; catches base misassociation.+netByBase :: (HatVal v, Real v) => EA.Alg v (HatBase CountUnit) -> M.Map CountUnit Rational+netByBase = EA.foldEntries step M.empty+ where+ step m v b = M.insertWith (+) (part b) (signed v b) m+ part (_ :< u) = u+ signed v b = if isHat b then negate (toRational v) else toRational v++epsEq :: Double -> Double -> Bool+epsEq a b = abs (a - b) <= 1e-9 * (1 + max (abs a) (abs b))++-- ================================================================+-- ExchangeAlgebra.IO.Input: checked construction for generated entries.+-- ================================================================++type CheckedAlgM = EA.Alg MoneyDecimal (HatBase AccountTitles)++exactBalancedForTest :: (EA.HatVal v, Exchange.ExBaseClass b) => EA.Alg v b -> Bool+exactBalancedForTest x = EA.norm (Exchange.decL x) == EA.norm (Exchange.decR x)++-- | Allocate an isolated test file and remove it even if a check raises an exception.+-- Uses base and directory so the test suite needs no additional package dependency.+withTestTemporaryFile :: (FilePath -> IO a) -> IO a+withTestTemporaryFile action = bracket acquire removeSpillTestFile action+ where+ acquire = do+ directory <- getTemporaryDirectory+ (path, handle) <- openBinaryTempFile directory "exchangealgebra-test.tmp"+ hClose handle+ pure path++-- | Construct a valid rate for synthetic fixtures only.+checkedRate :: MoneyDecimal -> Adjustment.BasisPoints+checkedRate amount = case Adjustment.basisPoints amount of+ Right rate -> rate+ Left problem -> error ("valid fixture rate: " ++ show problem)
test/Surface/Accounting.hs view
@@ -175,6 +175,7 @@ , InsuranceGain , GainOnBargainPurchase , ReversalOfAllowanceForRepairs+ , ReversalOfAllowanceForBonuses , ReversalOfAllowanceForProductWarranties , GainOnDonationOfFixedAssets , GainOnNationalSubsidies@@ -242,6 +243,8 @@ , NewspaperBooksExpenses , RawMaterials , GoodsInTransit+ , SalesAllowances+ , PurchaseAllowances , AccountTitle ) , AccountDivision@@ -289,15 +292,8 @@ , projCapitalStock , projContraAssets , projContra- , TransTable- , TransTableParts- , Size- , isNullTable- , table , (.->) , (|%)- , createTransfer- , transfer ) where import ExchangeAlgebra.Accounting@@ -475,6 +471,7 @@ , InsuranceGain , GainOnBargainPurchase , ReversalOfAllowanceForRepairs+ , ReversalOfAllowanceForBonuses , ReversalOfAllowanceForProductWarranties , GainOnDonationOfFixedAssets , GainOnNationalSubsidies@@ -542,6 +539,8 @@ , NewspaperBooksExpenses , RawMaterials , GoodsInTransit+ , SalesAllowances+ , PurchaseAllowances , AccountTitle ) , AccountDivision@@ -589,13 +588,6 @@ , projCapitalStock , projContraAssets , projContra- , TransTable- , TransTableParts- , Size- , isNullTable- , table , (.->) , (|%)- , createTransfer- , transfer )
− test/Surface/Algebra/Exact.hs
@@ -1,36 +0,0 @@--- | Compile-time lock for the public names exported by--- "ExchangeAlgebra.Algebra.Exact".-module Surface.Algebra.Exact (- -- * Accumulators- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- -- * Algebra readouts- , normExact- , barExact- , projNetNormExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- -- * Accounting readouts- , diffRLExact- , balanceExact- , accountBalancesExact- ) where--import ExchangeAlgebra.Algebra.Exact (- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- , normExact- , barExact- , projNetNormExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- , diffRLExact- , balanceExact- , accountBalancesExact- )
− test/Surface/Algebra/Readout/Net.hs
@@ -1,22 +0,0 @@--- | Compile-time lock for the public names exported by--- "ExchangeAlgebra.Algebra.Readout.Net".-module Surface.Algebra.Readout.Net- ( projNetNorm- , projNorm- , balanceBy- , balanceMapBy- , netPairMapBy- , projWithBaseNetNorm- , projWithNoteBaseNetNorm- ) where--import ExchangeAlgebra.Algebra.Readout.Net- ( projNetNorm- , projNorm- , balanceBy- , balanceMapBy- , netPairMapBy- , projWithBaseNetNorm- , projWithNoteBaseNetNorm- )-
− test/Surface/Foundation.hs
@@ -1,173 +0,0 @@-{-# LANGUAGE TypeFamilies #-}---- | Compile-time lock for the public names exported by--- "ExchangeAlgebra.Foundation".-module Surface.Foundation- ( Element- ( wildcard- , haveWildcard- , isWildcard- , ignoreWildcard- , equal- , (.==)- , (./=)- , compareElement- , (.<)- , (.>)- , (.<=)- , (.>=)- , maxElement- , minElement- )- , AxisDecompose (toAxisKeys)- , (.#)- , Name- , Subject- , CountUnit- ( Yen- , Dollar- , Euro- , CNY- , Amount- , CountUnit- )- , BaseClass (compareBase)- , Hat (Hat, Not, HatNot)- , HatBaseClass- ( BasePart- , base- , hat- , merge- , toHat- , toNot- , revHat- , isHat- , isNot- , compareHatBase- )- , HatBase ((:<), _hat, _base)- , HatVal (zeroValue, isZeroValue, isErrorValue, showValue)- , Nearly (isNearly)- , isNearlyNum- , nearlyEqScaled- , Alg (Zero, (:@), _val, _hatBase)- , Redundant- ( (.^)- , (.-)- , bar- , compress- , (.+)- , (.*)- , norm- , (<+)- )- , isZero- , (.@)- , (<@)- , vals- , bases- , toList- , toASCList- , foldEntries- , foldEntriesToMap- , fromList- , sigma- , sigma2When- , sigmaFromMap- , unionsMerge- , map- , mapPosting- , mapMaybePosting- , mapBasePart- , extendBy- , filter- , proj- , decBy- , postFromNetBy- ) where--import Prelude hiding (filter, map)--import ExchangeAlgebra.Foundation- ( Element- ( wildcard- , haveWildcard- , isWildcard- , ignoreWildcard- , equal- , (.==)- , (./=)- , compareElement- , (.<)- , (.>)- , (.<=)- , (.>=)- , maxElement- , minElement- )- , AxisDecompose (toAxisKeys)- , (.#)- , Name- , Subject- , CountUnit- ( Yen- , Dollar- , Euro- , CNY- , Amount- , CountUnit- )- , BaseClass (compareBase)- , Hat (Hat, Not, HatNot)- , HatBaseClass- ( BasePart- , base- , hat- , merge- , toHat- , toNot- , revHat- , isHat- , isNot- , compareHatBase- )- , HatBase ((:<), _hat, _base)- , HatVal (zeroValue, isZeroValue, isErrorValue, showValue)- , Nearly (isNearly)- , isNearlyNum- , nearlyEqScaled- , Alg (Zero, (:@), _val, _hatBase)- , Redundant- ( (.^)- , (.-)- , bar- , compress- , (.+)- , (.*)- , norm- , (<+)- )- , isZero- , (.@)- , (<@)- , vals- , bases- , toList- , toASCList- , foldEntries- , foldEntriesToMap- , fromList- , sigma- , sigma2When- , sigmaFromMap- , unionsMerge- , map- , mapPosting- , mapMaybePosting- , mapBasePart- , extendBy- , filter- , proj- , decBy- , postFromNetBy- )
− test/Surface/Journal/Exact.hs
@@ -1,41 +0,0 @@--- | Compile-time lock for the public names exported by--- "ExchangeAlgebra.Journal.Exact".-module Surface.Journal.Exact (- -- * Accumulators- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- -- * Journal readouts- , normExact- , barExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- -- * Projections- , projNetNormExact- , projWithBaseNetNormExact- , projWithNoteBaseNetNormExact- -- * Accounting readouts- , diffRLExact- , balanceExact- , accountBalancesExact- ) where--import ExchangeAlgebra.Journal.Exact (- ExactSum(..)- , ExactSumError(..)- , netAccum- , sumExact- , normExact- , barExact- , balanceMapByExact- , netPairMapByExact- , postFromNetByExact- , projNetNormExact- , projWithBaseNetNormExact- , projWithNoteBaseNetNormExact- , diffRLExact- , balanceExact- , accountBalancesExact- )
test/Surface/Render/Bookkeeping.hs view
@@ -1,11 +1,7 @@ -- | Compile-time lock for the public names exported by -- "ExchangeAlgebra.Render.Bookkeeping". module Surface.Render.Bookkeeping- ( writeBS- , bsRows- , writePL- , plRows- , writeJournal+ ( writeJournal , journalRows , writeAccountOf , accountLedgerRows@@ -17,17 +13,10 @@ , worksheetRows , writePostClosingTrialBalance , postClosingTrialBalanceRows- , balanceOf- , tshow- , toSameLength ) where import ExchangeAlgebra.Render.Bookkeeping- ( writeBS- , bsRows- , writePL- , plRows- , writeJournal+ ( writeJournal , journalRows , writeAccountOf , accountLedgerRows@@ -39,8 +28,5 @@ , worksheetRows , writePostClosingTrialBalance , postClosingTrialBalanceRows- , balanceOf- , tshow- , toSameLength )
test/SurfaceMain.hs view
@@ -1,17 +1,18 @@ module Main (main) where import Surface.Accounting ()-import Surface.Algebra.Exact ()-import Surface.Algebra.Readout.Net ()-import Surface.Foundation ()-import Surface.Journal.Exact () import Surface.Render.Bookkeeping () import Surface.Render.Csv () import Surface.Render.Simulation () import Surface.Simulate.Analysis () import Surface.Simulate.Engine () import Surface.Simulate.Random ()+import CompatInstance (checkInstances)+import System.Process (callProcess) --- | Report successful compilation of every surface lock module.+-- | Check the public export snapshot and client-owned instance behavior. main :: IO ()-main = putStrLn "surface ok"+main = do+ checkInstances+ callProcess "python3" ["tools/check-export-surface.py", "--direct-ghc", "--suite"]+ putStrLn "surface ok"
+ test/Transfer/BalanceSpec.hs view
@@ -0,0 +1,516 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | Exact balance laws for Accounting transfers and their Journal lifting.+-- This Accounting test suite uses independent Rational observations and a+-- sequential posting rewrite oracle. Read the fixtures before 'runTests'.+module Transfer.BalanceSpec (runTests) where++import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import Control.Monad (forM_, unless)+import Data.List (sort)+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Map.Strict as Map+import Data.Maybe (isJust)+import Data.Proxy (Proxy(..))+import System.Exit (exitFailure)+import Test.QuickCheck hiding (collect, label)+import ExchangeAlgebra.Algebra hiding (base, filter, map)+import qualified ExchangeAlgebra.Algebra as Algebra+import qualified ExchangeAlgebra.Algebra.Internal as Internal+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Accounting.Closing (ClosingSide(..), closingEntries, closingSide)+import qualified ExchangeAlgebra.Accounting.Transfer as Transfer+import qualified ExchangeAlgebra.Journal.Core as Journal+import ExchangeAlgebra.Journal.Core ((.|))++-- | Two-coordinate base using the library's existing account instance.+type TestBase = HatBase (CountUnit, AccountTitles)++-- | A posting retains its value and complete base for raw pair observations.+type Posting v = (v, TestBase)++-- | A row fixture has independent source and destination account titles.+type AccountMove = (AccountTitles, AccountTitles)++-- | Stop the suite immediately on a failed law or fixture.+assertLaw :: String -> Bool -> IO ()+assertLaw label passed = unless passed $ do+ putStrLn ("[FAIL] " ++ label)+ exitFailure++-- | Require successful validation without using partial extraction.+requireRight :: Show e => String -> Either e a -> IO a+requireRight _ (Right value) = pure value+requireRight label (Left failure) = do+ putStrLn ("[FAIL] " ++ label ++ ": " ++ show failure)+ exitFailure++-- | Run properties with their law identifiers in test output.+checkLaw :: Testable property => String -> property -> IO ()+checkLaw label lawProperty = do+ putStrLn ("[LAW] " ++ label)+ result <- quickCheckWithResult stdArgs { maxSuccess = 100 } lawProperty+ assertLaw label (isSuccess result)++-- | Observe every raw posting, preserving repetitions and traversal order.+postings :: forall v. HatVal v => Alg v TestBase -> [Posting v]+postings = foldEntries append []+ where+ append :: [Posting v] -> v -> TestBase -> [Posting v]+ append entries value base = entries ++ [(value, base)]++-- | Read one finite value as an exact Rational through its accumulator.+exactValue :: HatVal v => v -> Maybe Rational+exactValue value = exactAccum (addAccum value emptyAccum)++-- | Observe exact Not-minus-Hat balances, omitting zero coordinates.+exactBalances :: forall v. HatVal v => Alg v TestBase -> Maybe (Map.Map TestBase Rational)+exactBalances ledger = fmap (Map.filter (/= 0)) $ foldl' collect (Just Map.empty)+ (postings ledger)+ where+ collect :: Maybe (Map.Map TestBase Rational)+ -> Posting v+ -> Maybe (Map.Map TestBase Rational)+ collect previous (value, side :< coordinates) = do+ balances <- previous+ amount <- exactValue value+ let signed :: Rational+ signed = case side of+ Not -> amount+ Hat -> negate amount+ HatNot -> 0+ pure (Map.insertWith (+) (Not :< coordinates) signed balances)++-- | Sum selected debit or credit values without any floating-point addition.+exactTotal :: HatVal v => Alg v TestBase -> Maybe Rational+exactTotal ledger = fmap sum $ traverse (exactValue . fst) (postings ledger)++-- | Construct a wildcard-unit row without Hat/Not in its endpoints.+moveRow :: AccountMove -> Transfer.TransferRow TestBase+moveRow (source, target) = (wildcard, source) Transfer..-> (wildcard, target)+ Transfer.|% Transfer.Relabel++-- | Build the ordered two-stage chain used by the rewrite oracle.+chainMoves :: [AccountMove]+chainMoves = [(Cash, Deposits), (Deposits, Sales)]++-- | Generate valid redundant ledgers with positive and negative net balances.+genLedger :: HatVal v => Gen (Alg v TestBase)+genLedger = do+ count <- chooseInt (0, 35)+ entries <- vectorOf count $ do+ amount <- elements [1, 2, 3, 1 / 10, 1 / 5, 9007199254740992]+ side <- elements [Hat, Not]+ unit <- elements [Yen, Dollar, wildcard]+ title <- elements [Cash, Deposits, Sales, Products]+ pure (fromRational amount .@ side :< (unit, title))+ pure (Algebra.fromList entries)++-- | Rewrite each posting row by row, independently of the transfer machinery.+rewriteLedger :: forall v. HatVal v => [AccountMove] -> Alg v TestBase -> Alg v TestBase+rewriteLedger moves ledger = Algebra.fromList+ [value .@ foldl' rewrite base moves | (value, base) <- postings ledger]+ where+ rewrite :: TestBase -> AccountMove -> TestBase+ rewrite base@(side :< (unit, title)) (source, target)+ | title /= source = base+ | whichSide (Not :< (unit, source)) == whichSide (Not :< (unit, target)) =+ side :< (unit, target)+ | otherwise = revHat (side :< (unit, target))++-- | Check the pair structure without comparing rounded totals.+validPairs :: forall v. HatVal v => [Posting v] -> Bool+validPairs entries = sideValues (whichSide (Not :< (Yen, Cash) :: TestBase))+ == sideValues (whichSide (Hat :< (Yen, Cash) :: TestBase))+ where+ sideValues :: Side -> [Maybe Rational]+ sideValues side = sort [exactValue value | (value, base) <- entries, whichSide base == side]++-- | T1-T5 use exact Rational observations, including nonrepresentable Double nets.+propRelabel :: forall v. HatVal v => Proxy v -> Property+propRelabel _ = forAll (genLedger :: Gen (Alg v TestBase)) $ \ledger ->+ case Transfer.mkTransferTable (map moveRow chainMoves) of+ Left failure -> counterexample (show failure) False+ Right table -> case Transfer.transferEntries table ledger of+ Left failure -> counterexample (show failure) False+ Right entries ->+ let combined :: Alg v TestBase+ combined = ledger .+ entries+ sourceBalances :: Maybe (Map.Map TestBase Rational)+ sourceBalances = fmap (Map.filterWithKey isSource) (exactBalances combined)+ isSource :: TestBase -> Rational -> Bool+ isSource (_ :< (_, title)) _ = title `elem` [Cash, Deposits]+ positive :: Posting v -> Bool+ positive (value, _) = value > 0 && not (isErrorValue value)+ in conjoin+ [ counterexample "T1 exact debit/credit and equal pair values" $+ exactTotal (decL entries) == exactTotal (decR entries)+ && validPairs (postings entries)+ , counterexample "T2 finite positive complete pairs" $+ all positive (postings entries)+ , counterexample "T3 exact source closure" $+ sourceBalances == Just Map.empty+ , counterexample "T4 exact empty second result" $+ Transfer.transferEntries table combined == Right Zero+ , counterexample "T5 sequential independent rewrite" $+ exactBalances combined == exactBalances (rewriteLedger chainMoves ledger)+ ]++-- | T6 covers every ordered distinct pair of registry accounts and both sides.+testAllAccountPairs :: forall v. HatVal v => Proxy v -> IO ()+testAllAccountPairs _ = forM_ concreteAccountTitles $ \source ->+ forM_ concreteAccountTitles $ \target -> unless (source == target) $+ forM_ [Hat, Not] $ \side -> do+ table <- requireRight "T6 table" $ Transfer.mkTransferTable [moveRow (source, target)]+ entries <- requireRight "T6 entries" $ Transfer.transferEntries table+ (3 .@ side :< (Yen, source) :: Alg v TestBase)+ let expectedSide :: Hat+ expectedSide+ | whichSide (Not :< (Yen, source)) == whichSide (Not :< (Yen, target)) =+ side+ | otherwise = hat (revHat (side :< (Yen, target)))+ expected :: Alg v TestBase+ expected = 3 .@ revHat (side :< (Yen, source))+ .+ 3 .@ expectedSide :< (Yen, target)+ assertLaw "T6 registry pair Hat/Not and T2 one representable pair" $+ sort (postings entries) == sort (postings expected)++-- | T7 first checks the whole closing registry before testing any transfers.+testClosingHomeSides :: IO ()+testClosingHomeSides = forM_ concreteAccountTitles $ \title -> case closingSide title of+ Nothing -> pure ()+ Just decision -> do+ let sameHome :: Bool+ sameHome = whichSide (Not :< (Yen, title) :: TestBase)+ == whichSide (Not :< (Yen, RetainedEarnings) :: TestBase)+ assertLaw ("T7 STOP: closing/home mismatch " ++ show title) $+ (decision == ClosingKeep) == sameHome++-- | T7 compares raw posting multisets over all eligible closing accounts.+testClosingMultiset :: forall v. HatVal v => Proxy v -> IO ()+testClosingMultiset _ = do+ let titles :: [AccountTitles]+ titles = filter (isJust . closingSide) concreteAccountTitles+ ledger :: Alg v TestBase+ ledger = Algebra.fromList+ [ amount .@ side :< (unit, title)+ | title <- titles+ , unit <- [Yen, Dollar, wildcard]+ , (amount, side) <- [(0.1, Not), (0.2, Not), (0.05, Hat)]+ ]+ table <- requireRight "T7 table" $ Transfer.mkTransferTable+ [moveRow (title, RetainedEarnings) | title <- titles]+ closing <- requireRight "T7 closing" (closingEntries ledger)+ entries <- requireRight "T7 transfer" (Transfer.transferEntries table ledger)+ assertLaw "T7 closing raw multiset" $ sort (postings closing) == sort (postings entries)++-- | T2 and T3 pin the Double balance that cannot fit in a single posting.+testDoubleSplit :: IO ()+testDoubleSplit = do+ table <- requireRight "T2 split table" $ Transfer.mkTransferTable [moveRow (Cash, Sales)]+ let ledger :: Alg Double TestBase+ ledger = 9007199254740992 .@ Not :< (Yen, Cash) .+ 1 .@ Not :< (Yen, Cash)+ entries <- requireRight "T2 split entries" (Transfer.transferEntries table ledger)+ assertLaw "T2 split balance has two exact pairs" $+ length (postings entries) == 4 && validPairs (postings entries)+ assertLaw "T3 split residual is exactly zero" $+ exactBalances (ledger .+ entries) == Just (Map.singleton (Not :< (Yen, Sales))+ (-9007199254740993))++-- | T8 fixes once-rounded shares, zero results, and non-idempotent portions.+testPortions :: IO ()+testPortions = do+ let row :: Transfer.TransferRow TestBase+ row = (wildcard, Cash) Transfer..-> (wildcard, Deposits) Transfer.|% Transfer.Portion 1 3+ one :: Alg MoneyDecimal TestBase+ one = 1 .@ Not :< (Yen, Cash)+ table <- requireRight "T8 thirds" (Transfer.mkTransferTable [row])+ assertLaw "T8 decimal third is unroundable" $+ Transfer.transferEntries table one+ == Left (Transfer.UnroundableAmount row (Not :< (Yen, Cash)))+ rounded <- requireRight "T8 cents" $+ Transfer.transferEntriesWith (ToMultipleOf 0.01) table one+ assertLaw "T8 cents leave exactly 0.67" $+ exactBalances (one .+ rounded) == Just (Map.fromList+ [(Not :< (Yen, Cash), 67 / 100), (Not :< (Yen, Deposits), 33 / 100)])+ thirds <- requireRight "T8 representable third" $+ Transfer.transferEntries table (3 .@ Not :< (Yen, Cash) :: Alg MoneyDecimal TestBase)+ assertLaw "T8 three divided by three moves one" $+ exactBalances thirds == Just (Map.fromList+ [(Not :< (Yen, Cash), -1), (Not :< (Yen, Deposits), 1)])+ half <- requireRight "T8 half table" $ Transfer.mkTransferTable+ [(wildcard, Cash) Transfer..-> (wildcard, Deposits) Transfer.|% Transfer.Portion 1 2]+ first <- requireRight "T8 first half" (Transfer.transferEntries half one)+ second <- requireRight "T8 second half" (Transfer.transferEntries half (one .+ first))+ assertLaw "T8 portion is not idempotent" (second /= Zero)+ zero <- requireRight "T2 rounded zero" $+ Transfer.transferEntriesWith (ToMultipleOf 1) table one+ assertLaw "T2 rounded zero emits no pair" (zero == Zero)++-- | T8 checks exact half shares on both supported value instances.+propHalfPortion :: forall v. HatVal v => Proxy v -> Property+propHalfPortion _ = forAll (chooseInteger (1, 1000000)) $ \amount ->+ let row :: Transfer.TransferRow TestBase+ row = (wildcard, Cash) Transfer..-> (wildcard, Sales) Transfer.|% Transfer.Portion 1 2+ ledger :: Alg v TestBase+ ledger = fromInteger amount .@ Hat :< (Dollar, Cash)+ halfAmount :: Rational+ halfAmount = fromInteger amount / 2+ in case Transfer.mkTransferTable [row] of+ Left failure -> counterexample (show failure) False+ Right table -> case Transfer.transferEntries table ledger of+ Left failure -> counterexample (show failure) False+ Right entries -> counterexample "T8 exact remainder and side-preserving target" $+ exactBalances (ledger .+ entries) == Just (Map.fromList+ [(Not :< (Dollar, Cash), negate halfAmount),+ (Not :< (Dollar, Sales), halfAmount)])+ && length (postings entries) == 2++-- | Wildcards in source patterns match, while concrete queries miss ledger wildcards.+testWildcardMatching :: forall v. HatVal v => Proxy v -> IO ()+testWildcardMatching _ = do+ let ledger :: Alg v TestBase+ ledger = 7 .@ Not :< (wildcard, Cash)+ concreteRow :: Transfer.TransferRow TestBase+ concreteRow = (Yen, Cash) Transfer..-> (wildcard, Deposits) Transfer.|% Transfer.Relabel+ concrete <- requireRight "T9 concrete pattern" $ Transfer.mkTransferTable [concreteRow]+ assertLaw "T9 concrete source misses wildcard ledger coordinate" $+ Transfer.transferEntries concrete ledger == Right Zero+ broad <- requireRight "T9 wildcard pattern" $+ Transfer.mkTransferTable [moveRow (Cash, Deposits)]+ entries <- requireRight "T9 wildcard ledger selection" $ Transfer.transferEntries broad ledger+ assertLaw "T9 wildcard destination keeps literal ledger wildcard" $+ exactBalances (ledger .+ entries)+ == Just (Map.singleton (Not :< (wildcard, Deposits)) 7)++-- | T9 rejects backward edges, cycles, self edges, and wildcard backflow.+testOrder :: IO ()+testOrder = do+ let forward :: [Transfer.TransferRow TestBase]+ forward = map moveRow [(Cash, Deposits), (Deposits, Securities)]+ backward :: [Transfer.TransferRow TestBase]+ backward = reverse forward+ rejectsBackward :: [Transfer.TransferRow TestBase] -> Bool+ rejectsBackward rows = case Transfer.mkTransferTable rows of+ Left (Transfer.BackwardFlow _ _) -> True+ _ -> False+ wildcardBackflow :: [Transfer.TransferRow TestBase]+ wildcardBackflow =+ [ (Yen, Deposits) Transfer..-> (wildcard, Securities) Transfer.|% Transfer.Relabel+ , (wildcard, Cash) Transfer..-> (wildcard, Deposits) Transfer.|% Transfer.Relabel+ ]+ table <- requireRight "T9 forward" (Transfer.mkTransferTable forward)+ assertLaw "T9 table preserves written order" (Transfer.tableRows table == forward)+ assertLaw "T9 backward" (rejectsBackward backward)+ assertLaw "T9 self" (rejectsBackward [moveRow (Cash, Cash)])+ assertLaw "T9 cycle" (rejectsBackward (map moveRow [(Cash, Deposits), (Deposits, Cash)]))+ assertLaw "T9 wildcard backward regression" (rejectsBackward wildcardBackflow)+ wildcardForward <- requireRight "T9 wildcard forward regression" $+ Transfer.mkTransferTable (reverse wildcardBackflow)+ let wildcardLedger :: Alg Double TestBase+ wildcardLedger = 0.1 .@ Not :< (Yen, Cash)+ .+ 0.2 .@ Not :< (Yen, Cash)+ .+ 3 .@ Hat :< (Dollar, Cash)+ .+ 2 .@ Not :< (Yen, Deposits)+ wildcardEntries <- requireRight "T9 wildcard forward entries" $+ Transfer.transferEntries wildcardForward wildcardLedger+ assertLaw "T4 wildcard forward second result is exactly empty" $+ Transfer.transferEntries wildcardForward (wildcardLedger .+ wildcardEntries) == Right Zero+ entries <- requireRight "T9 chain entries" $ Transfer.transferEntries table+ (7 .@ Not :< (Yen, Cash) :: Alg MoneyDecimal TestBase)+ assertLaw "T9 forward chain reaches final account" $+ exactBalances (7 .@ Not :< (Yen, Cash) .+ entries)+ == Just (Map.singleton (Not :< (Yen, Securities)) 7)++-- | T9 fixes validation priority and the written j-then-i candidate order.+testTableFailureOrder :: IO ()+testTableFailureOrder = do+ let invalidFirst :: Transfer.TransferRow TestBase+ invalidFirst = (Yen, Cash) Transfer..-> (Yen, Cash) Transfer.|% Transfer.Portion 0 1+ invalidLater :: Transfer.TransferRow TestBase+ invalidLater = (Yen, Cash) Transfer..-> (Yen, Cash) Transfer.|% Transfer.Portion 2 1+ overlapFirst :: Transfer.TransferRow TestBase+ overlapFirst = (Yen, Cash) Transfer..-> (Dollar, Cash) Transfer.|% Transfer.Relabel+ overlapSecond :: Transfer.TransferRow TestBase+ overlapSecond = (Dollar, Cash) Transfer..-> (Yen, Cash) Transfer.|% Transfer.Relabel+ overlapThird :: Transfer.TransferRow TestBase+ overlapThird = (wildcard, Cash) Transfer..-> (wildcard, Sales) Transfer.|% Transfer.Relabel+ overlapFourth :: Transfer.TransferRow TestBase+ overlapFourth = (wildcard, Cash) Transfer..-> (wildcard, Securities)+ Transfer.|% Transfer.Relabel+ backwardFirst :: Transfer.TransferRow TestBase+ backwardFirst = (Yen, Cash) Transfer..-> (Yen, Deposits) Transfer.|% Transfer.Relabel+ backwardSecond :: Transfer.TransferRow TestBase+ backwardSecond = (Dollar, Cash) Transfer..-> (Dollar, Deposits) Transfer.|% Transfer.Relabel+ backwardThird :: Transfer.TransferRow TestBase+ backwardThird = (wildcard, Deposits) Transfer..-> (wildcard, Cash)+ Transfer.|% Transfer.Relabel+ backwardFourth :: Transfer.TransferRow TestBase+ backwardFourth = (Yen, Securities) Transfer..-> (Yen, Cash) Transfer.|% Transfer.Relabel+ assertLaw "T9 invalid portion precedes overlaps and backflow, first invalid row wins" $+ Transfer.mkTransferTable [overlapFirst, overlapSecond, invalidFirst, invalidLater]+ == Left (Transfer.InvalidPortion invalidFirst)+ assertLaw "T9 overlap precedes backflow, first j then first i wins" $+ Transfer.mkTransferTable [overlapFirst, overlapSecond, overlapThird, overlapFourth]+ == Left (Transfer.OverlappingRows overlapFirst overlapThird)+ assertLaw "T9 backflow first j then first i wins among multiple candidates" $+ Transfer.mkTransferTable [backwardFirst, backwardSecond, backwardThird, backwardFourth]+ == Left (Transfer.BackwardFlow backwardFirst backwardThird)++-- | T10 supplies several simultaneous failures and shuffles their raw postings.+propFailureOrder :: Property+propFailureOrder = forAll (shuffle fixtures) $ \permuted ->+ case Transfer.mkTransferTable [row] of+ Left failure -> counterexample (show failure) False+ Right table -> conjoin+ [ counterexample "T10 nonfinite precedes negative on the same base" $+ apply table permuted+ == Left (Transfer.InvalidPosting invalidBase NonFiniteValue)+ , counterexample "T10 unclassified precedes unroundable" $+ apply table (filter isValidSideValue permuted)+ == Left (Transfer.UnclassifiedAccount unclassifiedBase)+ , counterexample "T10 final failure is unroundable" $+ apply table (filter isClassifiedValid permuted)+ == Left (Transfer.UnroundableAmount row validBase)+ ]+ where+ row :: Transfer.TransferRow TestBase+ row = (Yen, wildcard) Transfer..-> (Dollar, Deposits) Transfer.|% Transfer.Portion 1 3+ invalidBase :: TestBase+ invalidBase = Not :< (Yen, Cash)+ unclassifiedBase :: TestBase+ unclassifiedBase = Not :< (Yen, wildcard)+ validBase :: TestBase+ validBase = Not :< (Yen, Products)+ fixtures :: [Posting Double]+ fixtures =+ [ (-2, invalidBase)+ , (0 / 0, invalidBase)+ , (1 / 0, Not :< (Yen, Sales))+ , (1, unclassifiedBase)+ , (1, validBase)+ ]+ isValidSideValue :: Posting Double -> Bool+ isValidSideValue (value, _) = not (isErrorValue value)+ isClassifiedValid :: Posting Double -> Bool+ isClassifiedValid posting@(_, base) = isValidSideValue posting+ && getAccountTitle base /= wildcard+ apply :: Transfer.TransferTable TestBase+ -> [Posting Double]+ -> Either (Transfer.TransferError TestBase) (Alg Double TestBase)+ apply table entries = Transfer.transferEntriesWith (ToMultipleOf 0.01) table+ (Algebra.fromList [value `Internal.Single` base | (value, base) <- entries])++-- | T10 rejects raw singleton wildcard sides before checking their scalar.+testWildcardFailure :: IO ()+testWildcardFailure = do+ table <- requireRight "T10 wildcard table" $+ Transfer.mkTransferTable [moveRow (Cash, Deposits)]+ let base :: TestBase+ base = HatNot :< (Yen, Cash)+ forM_ [1, -1, 0 / 0] $ \value ->+ assertLaw "T10 wildcard precedes invalid scalar on the same posting" $+ Transfer.transferEntries table (Internal.Single value base :: Alg Double TestBase)+ == Left (Transfer.WildcardPosting (Not :< (Yen, Cash)))+ assertLaw "T10 zero wildcard contributes no failure" $+ Transfer.transferEntries table (Internal.Single 0 base :: Alg Double TestBase) == Right Zero++-- | A stable target groups notes 1 and 2, while note 4 forms a second group.+groupNote :: Int -> Int+groupNote note+ | note <= 3 = 3+ | otherwise = 5++-- | J1 combines target groups before rounding and preserves other target groups.+testJournalGroups :: forall v. HatVal v => Proxy v -> IO ()+testJournalGroups _ = do+ table <- requireRight "J1 table" $ Transfer.mkTransferTable+ [(wildcard, Cash) Transfer..-> (wildcard, Deposits) Transfer.|% Transfer.Portion 1 3]+ let ledger :: Journal.Journal Int v TestBase+ ledger = (1 .@ Not :< (Yen, Cash) .| 1)+ .+ (2 .@ Not :< (Yen, Cash) .| 2)+ .+ (6 .@ Not :< (Dollar, Cash) .| 4)+ entriesAt :: Int -> Journal.Journal Int v TestBase -> Alg v TestBase+ entriesAt note = Journal.toAlg . Journal.filterWithNote (\actual _ -> actual == note)+ result <- requireRight "J1 grouped exact thirds" $+ Transfer.transferEntriesBy groupNote table ledger+ forM_ [3, 5] $ \target -> do+ expected <- requireRight "J1 independent group" $ Transfer.transferEntries table+ (Journal.toAlg (Journal.filterWithNote (\note _ -> groupNote note == target) ledger))+ assertLaw "J1 target entries equal independently grouped transfer" $+ sort (postings (entriesAt target result)) == sort (postings expected)+ let expectedThree :: Alg v TestBase+ expectedThree = 1 .@ Hat :< (Yen, Cash) .+ 1 .@ Not :< (Yen, Deposits)+ expectedFive :: Alg v TestBase+ expectedFive = 2 .@ Hat :< (Dollar, Cash) .+ 2 .@ Not :< (Dollar, Deposits)+ assertLaw "J1 target three has the manually specified one-unit pair" $+ sort (postings (entriesAt 3 result)) == sort (postings expectedThree)+ assertLaw "J1 target five has the manually specified two-unit pair" $+ sort (postings (entriesAt 5 result)) == sort (postings expectedFive)+ assertLaw "J1 original notes absent from added entries" $+ all (Algebra.isZero . (`entriesAt` result)) [1, 2, 4]++-- | J2 rejects moving input targets before evaluating invalid posting values.+testJournalMoves :: forall v. HatVal v => Proxy v -> IO ()+testJournalMoves _ = do+ table <- requireRight "J2 table" $ Transfer.mkTransferTable [moveRow (Cash, Deposits)]+ let ledger :: Journal.Journal Int v TestBase+ ledger = (1 .@ Not :< (Yen, Cash) .| 1)+ .+ (Internal.Single (-1) (Not :< (Yen, Cash)) .| 2)+ next :: Int -> Int+ next = (+ 1)+ assertLaw "J2 note movement checked before invalid values" $+ case Transfer.transferEntriesBy next table ledger of+ Left (Transfer.TransferNoteMoves 2 3) -> True+ _ -> False+ accepted <- requireRight "J2 absent next-period target" $ Transfer.transferEntriesBy next table+ (1 .@ Not :< (Yen, Cash) .| 1 :: Journal.Journal Int v TestBase)+ assertLaw "J2 absent target accepted" $+ exactBalances (Journal.toAlg accepted) == Just (Map.fromList+ [(Not :< (Yen, Cash), -1), (Not :< (Yen, Deposits), 1)])++-- | J3 repeats a Relabel transfer after its target notes have been added.+propJournalIdempotence :: forall v. HatVal v => Proxy v -> Property+propJournalIdempotence _ = forAll (genLedger :: Gen (Alg v TestBase)) $ \ledger ->+ case Transfer.mkTransferTable (map moveRow chainMoves) of+ Left failure -> counterexample (show failure) False+ Right table ->+ let journal :: Journal.Journal Int v TestBase+ journal = ledger .| 1+ stableTarget :: Int -> Int+ stableTarget _ = 3+ in case Transfer.transferEntriesBy stableTarget table journal of+ Left failure -> counterexample (show failure) False+ Right result -> counterexample "J3 second result is exactly empty" $+ case Transfer.transferEntriesBy stableTarget table (journal .+ result) of+ Right repeated -> HashMap.null (Journal.toMap repeated)+ Left _ -> False++-- | Register the gate, both value instances, and all balance and Journal laws.+runTests :: IO ()+runTests = do+ testClosingHomeSides+ checkLaw "T1-T5 Double" (propRelabel (Proxy :: Proxy Double))+ checkLaw "T1-T5 MoneyDecimal" (propRelabel (Proxy :: Proxy MoneyDecimal))+ testAllAccountPairs (Proxy :: Proxy Double)+ testAllAccountPairs (Proxy :: Proxy MoneyDecimal)+ testClosingMultiset (Proxy :: Proxy Double)+ testClosingMultiset (Proxy :: Proxy MoneyDecimal)+ testDoubleSplit+ testPortions+ checkLaw "T8 Double half share" (propHalfPortion (Proxy :: Proxy Double))+ checkLaw "T8 MoneyDecimal half share" (propHalfPortion (Proxy :: Proxy MoneyDecimal))+ testWildcardMatching (Proxy :: Proxy Double)+ testWildcardMatching (Proxy :: Proxy MoneyDecimal)+ testOrder+ testTableFailureOrder+ checkLaw "T10 deterministic failure precedence" propFailureOrder+ testWildcardFailure+ testJournalGroups (Proxy :: Proxy Double)+ testJournalGroups (Proxy :: Proxy MoneyDecimal)+ testJournalMoves (Proxy :: Proxy Double)+ testJournalMoves (Proxy :: Proxy MoneyDecimal)+ checkLaw "J3 Double" (propJournalIdempotence (Proxy :: Proxy Double))+ checkLaw "J3 MoneyDecimal" (propJournalIdempotence (Proxy :: Proxy MoneyDecimal))+ putStrLn "[PASS] T1-T10 and J1-J3 exact transfer laws"
test/Transfer/RuleSpec.hs view
@@ -2,25 +2,44 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} --- | Laws, legacy regressions and multi-period acceptance for transfer entries.+-- | Laws, independent oracles and multi-period acceptance for transfer entries. module Transfer.RuleSpec (runTests) where +import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import ExchangeAlgebra.Accounting.Account+import ExchangeAlgebra.Accounting.Exchange+import qualified ExchangeAlgebra.Algebra.Internal as Internal import Control.Monad (unless) import qualified Data.Binary as Binary import qualified Data.HashMap.Strict as HashMap import Data.List (permutations) import qualified Data.Map.Strict as Map import Data.Proxy (Proxy(..))+import qualified Data.Set as Set+import qualified ExchangeAlgebra.Accounting.TrialBalance.Validation as Validation import System.Exit (exitFailure) import Test.QuickCheck hiding (label, sample) import ExchangeAlgebra.Algebra hiding (map, filter) import qualified ExchangeAlgebra.Algebra as Algebra-import qualified ExchangeAlgebra.Algebra.Transfer as Legacy-import ExchangeAlgebra.Algebra.Transfer.Rule+import ExchangeAlgebra.Algebra.Transfer.Rule ( TransferScale(..)+ , TransferRule(..)+ , TransferRules+ , TransferRuleError(..)+ , TransferApplyError(..)+ , mkTransferRules+ , rulesToList+ , relabel+ , scaleBy+ , divideBy+ , transferEntries+ , collapseEntries+ , collapseNetEntries+ )+import ExchangeAlgebra.Accounting.Closing (ClosingSide(..), closingSide, closingEntries) import qualified ExchangeAlgebra.Journal as Journal import ExchangeAlgebra.Journal ((.|), Note(..)) import qualified ExchangeAlgebra.Journal.Transfer.Rule as JournalRule-import ExchangeAlgebra.Value (MoneyDecimal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal) -- | Concrete account and unit coordinates for compatibility properties. type TestBase = HatBase (AccountTitles, CountUnit)@@ -31,7 +50,7 @@ getAccountTitle (_ :< (title, _)) = title setAccountTitle (postingHat :< (_, unit)) title = postingHat :< (title, unit) --- | Integer posting quantities bounded away from zero for legacy compatibility.+-- | Integer posting quantities bounded away from zero for the independent oracle. type TestQuantity = Integer -- | Generated flat postings and one permitted scale coefficient.@@ -67,14 +86,18 @@ caseAlgebra (CompatibilityCase entries _) = Algebra.fromList [fromInteger quantity .@ postingBase | (quantity, postingBase) <- entries] --- | Translate the data operations to the legacy function-valued table.-legacyTable :: (HatVal v, HatBaseClass b) => [TransferRule v b] -> Legacy.TransTable v b-legacyTable rules = Legacy.table- [ (ruleFrom rule, ruleTo rule, operation (ruleScale rule)) | rule <- rules ]+-- | Independently rewrite the generated concrete account coordinates.+-- The oracle uses the input coefficient, without interpreting transfer rules.+expectedConversion :: forall v. HatVal v => CompatibilityCase -> Alg v TestBase+expectedConversion (CompatibilityCase entries coefficient) = Algebra.fromList+ [ convert (fromInteger quantity) posting | (quantity, posting) <- entries ] where- operation Relabel = id- operation (MulBy coefficient) = (* coefficient)- operation (DivBy coefficient) = (/ coefficient)+ convert :: v -> TestBase -> Alg v TestBase+ convert value (side :< (title, unit)) = case title of+ Cash -> value .@ side :< (Deposits, unit)+ Products -> (value * fromRational coefficient) .@ side :< (RawMaterials, unit)+ Sales -> (value / fromRational coefficient) .@ side :< (RetainedEarnings, unit)+ _ -> value .@ side :< (title, unit) -- | Law observation: complete base including Hat/Not to value, after bar. obs :: (HatVal v, HatBaseClass b) => Alg v b -> Map.Map b v@@ -102,7 +125,7 @@ in case applyChecked (mkTransferRules rules) ledger of Left failure -> counterexample failure False Right entries -> counterexample (show (obs (ledger .+ entries))) $- nearObservations (obs (ledger .+ entries)) (obs (Legacy.transfer ledger (legacyTable rules)))+ nearObservations (obs (ledger .+ entries)) (obs (expectedConversion sample)) where -- Keep construction and application failures visible without a partial pattern. applyChecked constructed ledger = case constructed of@@ -111,7 +134,7 @@ Left failure -> Left (show failure) Right entries -> Right entries --- | L2 compares net closing with the legacy final-stock transformation.+-- | L2 checks fixed account directions and retains non-closing coordinates. propClosing :: forall v. HatVal v => Proxy v -> Property propClosing _ = forAll genCompatibilityCase $ \sample -> let ledger = caseAlgebra sample :: Alg v TestBase@@ -119,7 +142,13 @@ Left failure -> counterexample (show failure) False Right entries -> property $ nearObservations (obs (ledger .+ entries))- (obs (Legacy.finalStockTransfer ledger))+ (obs (Algebra.mapPosting expected ledger))+ where+ expected :: v -> TestBase -> (v, TestBase)+ expected value (side :< (title, unit)) = case title of+ Sales -> (value, side :< (RetainedEarnings, unit))+ Depreciation -> (value, revHat (side :< (RetainedEarnings, unit)))+ _ -> (value, side :< (title, unit)) -- | L3 compares debit and credit totals for side-preserving Relabel rules -- and for closing entries. Decimal totals are exact in this generator.@@ -219,9 +248,9 @@ Left (InvalidCoefficient rule) -> ruleFrom rule == ruleFrom invalid _ -> False --- | Mixed wildcard positions reproduce the frozen legacy tree's missed matches.-testLegacyMiss :: IO ()-testLegacyMiss = do+-- | Mixed wildcard positions match every posting under every rule permutation.+testMixedWildcardPermutations :: IO ()+testMixedWildcardPermutations = do let rules = [ relabel (Hat :< (wildcard, Yen)) (Hat :< (wildcard, Amount)) , relabel (Not :< (Cash, wildcard)) (Not :< (Deposits, wildcard))@@ -239,9 +268,6 @@ validated <- requireRight "mixed wildcard rules" (mkTransferRules rules) entries <- requireRight "mixed wildcard application" (transferEntries validated ledger) assertTest "new API transforms all three entries" (obs (ledger .+ entries) == obs expected)- let old = Legacy.transfer ledger (legacyTable rules)- changed = [postingBase | postingBase@(_ :< (_, Amount)) <- bases old]- assertTest "legacy transforms only one entry" (length changed == 1) -- | One-way matching, no-op, zero result, HatNot and overflow edge cases. testApplication :: IO ()@@ -264,7 +290,8 @@ wildcardRule <- requireRight "HatNot rule" $ mkTransferRules [relabel (HatNot :< (Cash, wildcard)) (HatNot :< (Deposits, wildcard))] invalidHat <- requireRight "HatNot posting is ignored" $- transferEntries wildcardRule (10 .@ HatNot :< (Cash, Yen) :: Alg Double TestBase)+ transferEntries wildcardRule+ (10 `Internal.Single` HatNot :< (Cash, Yen) :: Alg Double TestBase) assertTest "HatNot posting" (Algebra.isZero invalidHat) underflow <- requireRight "underflow rule" (mkTransferRules [scaleBy source target (1e-300 :: Double)]) zeroTarget <- requireRight "underflow result" (transferEntries underflow (1e-300 .@ source))@@ -321,7 +348,7 @@ ledger = 1e12 .@ source .+ 1e12 .@ revHat source .+ 1 .@ source :: Alg Double TestBase expected = 1 .@ revHat source .+ 1 .@ Not :< (RetainedEarnings, Yen)- assertTest "bar loses historical-small residual fixture" (Map.null (obs ledger))+ assertTest "bar retains historical-small residual fixture" (obs ledger == Map.singleton source 1) entries <- requireRight "historical-small balance closing" (closingEntries ledger) assertTest "closing preserves current one" (rawObservation entries == rawObservation expected) assertTest "closing-side public classification" $@@ -332,10 +359,10 @@ assertTest "exactly balanced source emits nothing" (Algebra.isZero offsetEntries) let accounts = 10 .@ source .+ 4 .@ Not :< (InterestEarned, Yen) :: Alg Double TestBase accountEntries <- requireRight "separate account closing" (closingEntries accounts)- let targets = Algebra.projByAccountTitle RetainedEarnings accountEntries+ let targets = Exchange.projByAccountTitle RetainedEarnings accountEntries assertTest "separate closing targets preserve account audit trail" (length (vals targets) == 2) --- | Closing rejects either overflowing side, even if both totals are Infinity.+-- | Closing rejects an unrepresentable net and accepts cancellation beyond the side range. -- Error order follows ascending normalized bases, not posting insertion order. testClosingOverflow :: IO () testClosingOverflow = do@@ -350,8 +377,9 @@ isBalanceError sales (closingEntries (overflowing sales)) assertTest "closing Hat sum overflow" $ isBalanceError sales (closingEntries (overflowing (revHat sales)))- assertTest "equal infinite sides must not be treated as zero" $- isBalanceError sales (closingEntries (overflowing sales .+ overflowing (revHat sales)))+ cancelled <- requireRight "out-of-range sides cancel exactly" $+ closingEntries (overflowing sales .+ overflowing (revHat sales))+ assertTest "equal exact sides close to zero" (Algebra.isZero cancelled) let expectedFirst = min sales purchases assertTest "first overflowing base in ascending order" $ isBalanceError expectedFirst (closingEntries (overflowing sales .+ overflowing purchases))@@ -359,7 +387,7 @@ let journal = (1e308 .@ sales .| "first") .+ (1e308 .@ sales .| "second") :: Journal.Journal String Double TestBase assertTest "Journal closing overflow across notes" $- isBalanceError sales (JournalRule.closingEntries journal)+ isBalanceError sales (closingEntries (Journal.toAlg journal)) let cash = Not :< (Cash, Yen) ignored <- requireRight "non-closing accounts are not summed" (closingEntries (overflowing cash)) assertTest "non-closing overflow ignored" (Algebra.isZero ignored)@@ -369,6 +397,110 @@ assertTest "closing target postings are not aggregated" $ length (vals retained) == 4 && all (not . isErrorValue) (vals retained) +-- | Observe exact Not-minus-Hat amounts before any rounded algebra readout.+exactNets :: HatVal v => Alg v TestBase -> Map.Map TestBase Rational+exactNets = Map.mapMaybe exactAccum . foldEntries collect Map.empty+ where+ collect :: HatVal v => Map.Map TestBase (Accum v) -> v -> TestBase+ -> Map.Map TestBase (Accum v)+ collect totals value source = Map.insertWith mergeAccum (toNot source) amount totals+ where+ amount = case hat source of+ Hat -> negateAccum (addAccum value emptyAccum)+ _ -> addAccum value emptyAccum++-- | C1-C3 and C5: exact closure, positive descending pairs, balance, and idempotence.+-- Fractions and widely separated magnitudes exercise binary splitting as well as decimals.+propExactClosing :: forall v. HatVal v => Proxy v -> Property+propExactClosing _ = forAll generated $ \ledger -> case closingEntries ledger of+ Left failure -> counterexample (show failure) False+ Right additions -> case closingEntries (ledger .+ additions) of+ Left failure -> counterexample (show failure) False+ Right repeated ->+ let remaining = Map.intersection+ (exactNets (ledger .+ additions)) (exactNets ledger)+ in conjoin+ [ property $ all (== 0) $ Map.elems remaining+ , property $ all valid $ vals additions+ , property $ all (shape additions) $ Map.toList $ exactNets ledger+ , total (decL additions) === total (decR additions)+ , property (Algebra.isZero repeated)+ ]+ where+ generated :: Gen (Alg v TestBase)+ generated = do+ count <- chooseInt (0, 24)+ postings <- vectorOf count $ do+ value <- elements [0.1, 0.2, 0.3, 1, 2 ^ (53 :: Int)]+ side <- elements [Hat, Not]+ title <- elements [Sales, Purchases, InterestEarned]+ unit <- elements [Yen, Dollar]+ pure (value .@ side :< (title, unit))+ pure (Algebra.fromList postings)++ valid :: v -> Bool+ valid value = value > zeroValue && not (isErrorValue value)++ total :: Alg v TestBase -> Maybe Rational+ total = exactAccum . foldEntries (\acc value _ -> addAccum value acc) emptyAccum++ shape :: Alg v TestBase -> (TestBase, Rational) -> Bool+ shape additions (source, amount) =+ let reversals = foldEntries collect [] additions+ collect :: [v] -> v -> TestBase -> [v]+ collect values value coordinates+ | toNot coordinates == source = values ++ [value]+ | otherwise = values+ descending = and (zipWith (>=) reversals (drop 1 reversals))+ represented :: Maybe Rational+ represented = exactAccum+ $ addAccum (fromRational (abs amount) :: v) emptyAccum+ representable = represented == Just (abs amount)+ in case compare amount 0 of+ EQ -> null reversals+ _ -> not (null reversals) && descending+ && (not representable || length reversals == 1)++-- | C6: invalid eligible scalars fail before netting, for both value types.+testClosingInvalid :: forall v. HatVal v => Proxy v -> IO ()+testClosingInvalid _ = do+ let source = Not :< (Sales, Yen)+ ledger = (-1) `Internal.Single` source :: Alg v TestBase+ assertTest "negative closing value" $ case closingEntries ledger of+ Left (NonFiniteBalance actual) -> actual == source+ _ -> False++-- | C6: overflowing sides may still have a representable exact difference.+testClosingRange :: IO ()+testClosingRange = do+ let source = Not :< (Sales, Yen)+ maximumFinite = encodeFloat (2 ^ (53 :: Int) - 1) 971 :: Double+ ledger = maximumFinite .@ source+ .+ maximumFinite .@ source+ .+ maximumFinite .@ revHat source+ additions <- requireRight "finite difference of overflowing sides" (closingEntries ledger)+ assertTest "large closing exact cancellation" $+ Map.findWithDefault 1 source (exactNets (ledger .+ additions)) == 0+ mapM_ (\value -> assertTest "nonfinite closing scalar" $+ case closingEntries (value `Internal.Single` source :: Alg Double TestBase) of+ Left (NonFiniteBalance actual) -> actual == source+ _ -> False) [0 / 0, 1 / 0, -1 / 0]++-- | C7: paying expenses 0.1 and 0.2 leaves no nominal residual after closing.+testClosingValidation :: forall v. HatVal v => Proxy v -> IO ()+testClosingValidation _ = do+ let expense :: v -> Alg v (HatBase AccountTitles)+ expense value = value .@ Not :< DeliveryExpenses .+ value .@ Hat :< Cash+ ledger = expense 0.1 .+ expense 0.2+ additions <- requireRight "fractional expense closing" (closingEntries ledger)+ let input = Validation.TrialBalanceInput (ledger .+ additions)+ Validation.AfterClosing Map.empty [] Set.empty+ unclosed :: Validation.TBFinding v -> Bool+ unclosed (Validation.UnclosedNominalBalance _ _) = True+ unclosed _ = False+ assertTest "C7 no UnclosedNominalBalance" $+ not $ any unclosed $ Validation.trialBalanceFindings input+ -- | Two goods share an owner and differ only in the axis collapsed below. data ValuationGood = GoodA | GoodB | AnyValuationGood deriving (Eq, Ord, Show, Generic)@@ -524,14 +656,14 @@ testAcceptance :: IO () testAcceptance = do let first = periodEntries 1 Alice 100 30 .+ periodEntries 1 Bob 150 40- firstClosing <- requireRight "period-one closing" (JournalRule.closingEntries first)+ firstClosing <- requireRight "period-one closing" (closingEntries (Journal.toAlg first)) let afterFirst = first .+ (firstClosing .| (Settlement, 1)) second = afterFirst .+ periodEntries 2 Alice 60 20 .+ periodEntries 2 Bob 90 30- secondClosing <- requireRight "period-two closing" (JournalRule.closingEntries second)+ secondClosing <- requireRight "period-two closing" (closingEntries (Journal.toAlg second)) let afterSecond = second .+ (secondClosing .| (Settlement, 2)) earnings owner algebra = Map.findWithDefault 0 (Not :< (RetainedEarnings, Widget, owner, Yen)) (obs algebra)- salesAt term journal = fmap (Algebra.projByAccountTitle Sales)+ salesAt term journal = fmap (Exchange.projByAccountTitle Sales) (HashMap.lookup (SalesPurchase, term) (Journal.toMap journal)) assertTest "period-one owner earnings" (earnings Alice firstClosing == 70 && earnings Bob firstClosing == 110) assertTest "period-two only current earnings" (earnings Alice secondClosing == 40 && earnings Bob secondClosing == 60)@@ -543,7 +675,7 @@ assertTest "settlement note contains its entries" $ fmap rawObservation (HashMap.lookup (Settlement, 2) (Journal.toMap afterSecond)) == Just (rawObservation secondClosing)- repeatedClosing <- requireRight "repeat closing" (JournalRule.closingEntries afterSecond)+ repeatedClosing <- requireRight "repeat closing" (closingEntries (Journal.toAlg afterSecond)) assertTest "closing twice emits no further entries" (Algebra.isZero repeatedClosing) assertTest "acceptance ledger stays balanced" (norm (decL afterSecond) == norm (decR afterSecond)) @@ -561,10 +693,17 @@ quickProperty "L5 MoneyDecimal" (propNonnegative (Proxy :: Proxy MoneyDecimal)) quickProperty "L6 collapse" propCollapse testValidation- testLegacyMiss+ testMixedWildcardPermutations testApplication testClosing testClosingOverflow+ quickProperty "C1-C3 C5 Double" (propExactClosing (Proxy :: Proxy Double))+ quickProperty "C1-C3 C5 MoneyDecimal" (propExactClosing (Proxy :: Proxy MoneyDecimal))+ testClosingInvalid (Proxy :: Proxy Double)+ testClosingInvalid (Proxy :: Proxy MoneyDecimal)+ testClosingRange+ testClosingValidation (Proxy :: Proxy Double)+ testClosingValidation (Proxy :: Proxy MoneyDecimal) testValuationCollapse testRetainedCollapse testWildcardLedger
+ test/Value/MoneyParseSpec.hs view
@@ -0,0 +1,203 @@+-- | Acceptance and property tests for exact money input conversion.+module Value.MoneyParseSpec (runTests) where++import Control.Monad (unless)+import Data.Decimal (DecimalRaw (Decimal), decimalMantissa, decimalPlaces)+import Data.Hashable (hash)+import Data.Scientific (Scientific, scientific)+import qualified Data.Text as Text+import System.Exit (exitFailure)+import Test.QuickCheck++import ExchangeAlgebra.Algebra.Value+ ( MoneyDecimal (..)+ , MoneyParseError (..)+ , moneyDecimalFromScientific+ , moneyDecimalFromText+ , toDecimal+ )++-- | Fail the suite on the first incorrect acceptance result.+check :: (Eq result, Show result) => String -> result -> result -> IO ()+check label expected actual = unless (expected == actual) $ do+ putStrLn ("[FAIL] money parse " ++ label)+ putStrLn (" expected: " ++ show expected)+ putStrLn (" actual: " ++ show actual)+ exitFailure++-- | Construct a successful result with explicit scale and mantissa.+money :: Int -> Integer -> Either MoneyParseError MoneyDecimal+money places mantissa = Right (MoneyDecimal (Decimal (fromIntegral places) mantissa))++-- | Check the exact representation, including trailing zeroes.+checkParts :: String -> String -> (Int, Integer) -> IO ()+checkParts label source expected =+ check label (Right expected) $ do+ value <- moneyDecimalFromText (Text.pack source)+ let decimal = toDecimal value+ pure (fromIntegral (decimalPlaces decimal), decimalMantissa decimal)++-- | Check Scientific scale and coefficient without normalizing the input.+checkScientificParts :: String -> Scientific -> (Int, Integer) -> IO ()+checkScientificParts label source expected =+ check label (Right expected) $ do+ value <- moneyDecimalFromScientific source+ let decimal = toDecimal value+ pure (fromIntegral (decimalPlaces decimal), decimalMantissa decimal)++-- | Check each grammar, priority, boundary, and exactness example.+testExamples :: IO ()+testExamples = do+ check "ordinary decimal" (money 2 1999) (parse "19.99")+ check "integer" (money 0 5) (parse "5")+ check "exponent and plus" (money 1 150) (parse "1.50e+1")+ check "uppercase exponent" (money 0 150) (parse "1.5E2")+ check "negative exponent" (money 2 1) (parse "1e-2")+ check "negative zero" (money 0 0) (parse "-0")+ check "negative fractional zero" (money 2 0) (parse "-0.00")+ check "negative zero exponent" (money 0 0) (parse "-0e5")+ check "negative zero exponent range" (Left MoneyExponentOutOfRange) (parse "-0e10001")+ check "negative zero scale" (Left MoneyScaleOutOfRange) (parse "-0e-256")+ mapM_ (\(label, source) -> check label (Left MalformedMoney) (parse source))+ [ ("empty", "")+ , ("bare minus", "-")+ , ("space prefix", " 1")+ , ("space suffix", "1 ")+ , ("plus prefix", "+1")+ , ("missing integer", ".5")+ , ("missing fraction", "5.")+ , ("fraction before exponent missing", "1.e5")+ , ("missing integer after minus", "-.5")+ , ("leading zero", "01")+ , ("two leading zeroes", "00")+ , ("double sign", "--1")+ , ("NaN", "NaN")+ , ("Infinity", "Infinity")+ , ("group separator", "1,000")+ , ("exponent digits missing", "1e+")+ , ("negative exponent digits missing", "1e-")+ , ("uppercase exponent digits missing", "1E")+ , ("exponent without integer", "e5")+ , ("second decimal point", "1.5.5")+ , ("fullwidth digit", "1")+ , ("trailing invalid character", "1x")+ , ("malformed beats negative and exponent", "-1e99999x")+ , ("malformed beats exponent", "01e99999")+ ]+ check "negative" (Left NegativeMoney) (parse "-1")+ check "negative beats exponent" (Left NegativeMoney) (parse "-1e99999")+ check "negative beats scale" (Left NegativeMoney) (parse "-1e-300")+ check "exponent beats scale" (Left MoneyExponentOutOfRange) (parse "1e-20000")+ check "scale" (Left MoneyScaleOutOfRange) (parse "1e-300")+ check "positive exponent over bound" (Left MoneyExponentOutOfRange) (parse "1e10001")+ check "negative exponent over bound" (Left MoneyExponentOutOfRange) (parse "1e-10001")+ check "positive exponent at bound" (Right (10 ^ (10000 :: Int))) (parse "1e10000")+ check "negative exponent at bound" (Left MoneyScaleOutOfRange) (parse "1e-10000")+ check "zero at positive exponent bound" (money 0 0) (parse "0e10000")+ check "zero at negative exponent bound" (Left MoneyScaleOutOfRange) (parse "0e-10000")+ check "scale at bound" (money 255 1) (parse "1e-255")+ check "scale above bound" (Left MoneyScaleOutOfRange) (parse "1e-256")+ checkParts "fraction scale at bound" ("0." ++ replicate 255 '0') (255, 0)+ check "fraction scale above bound" (Left MoneyScaleOutOfRange)+ (parse ("0." ++ replicate 256 '0'))+ checkParts "exponent offsets fraction scale" ("0." ++ replicate 260 '0' ++ "e5")+ (255, 0)+ check "zero-padded exponent at bound" (Right (10 ^ (10000 :: Int)))+ (parse "1e010000")+ check "zero-padded exponent above bound" (Left MoneyExponentOutOfRange)+ (parse "1e010001")+ check "long exponent" (Left MoneyExponentOutOfRange)+ (parse ("1e" ++ replicate 100000 '9'))+ check "long zero exponent" (money 0 10)+ (parse ("1e" ++ replicate 100000 '0' ++ "1"))+ checkParts "written trailing zeros" "1.50" (2, 150)+ checkParts "written zero scale" "0.00" (2, 0)+ checkParts "written negative zero scale" "-0.00" (2, 0)+ checkParts "written negative zero integer" "-0" (0, 0)+ checkParts "written negative zero exponent" "-0e5" (0, 0)+ checkParts "written exponent scale" "1.50e1" (1, 150)+ check "one tenth is exact" (Right (1 / 10))+ (toRational <$> parse "0.1")+ check "scientific raw scale" (money 2 150)+ (moneyDecimalFromScientific (scientific 150 (-2)))+ checkScientificParts "scientific raw places" (scientific 150 (-2)) (2, 150)+ checkScientificParts "scientific equivalent with scale" (scientific 10 (-1)) (1, 10)+ checkScientificParts "scientific equivalent without scale" (scientific 1 0) (0, 1)+ check "scientific negative" (Left NegativeMoney)+ (moneyDecimalFromScientific (scientific (-1) maxBound))+ check "scientific negative before minBound" (Left NegativeMoney)+ (moneyDecimalFromScientific (scientific (-1) minBound))+ check "scientific negative before scale" (Left NegativeMoney)+ (moneyDecimalFromScientific (scientific (-1) (-300)))+ check "scientific positive bound" (Right (10 ^ (10000 :: Int)))+ (moneyDecimalFromScientific (scientific 1 10000))+ check "scientific negative bound" (Left MoneyScaleOutOfRange)+ (moneyDecimalFromScientific (scientific 1 (-10000)))+ check "scientific exponent over bound" (Left MoneyExponentOutOfRange)+ (moneyDecimalFromScientific (scientific 1 10001))+ check "scientific negative exponent over bound" (Left MoneyExponentOutOfRange)+ (moneyDecimalFromScientific (scientific 1 (-10001)))+ checkScientificParts "scientific scale at bound" (scientific 1 (-255)) (255, 1)+ check "scientific scale above bound" (Left MoneyScaleOutOfRange)+ (moneyDecimalFromScientific (scientific 1 (-256)))+ check "scientific minBound" (Left MoneyExponentOutOfRange)+ (moneyDecimalFromScientific (scientific 0 minBound))+ check "scientific maxBound" (Left MoneyExponentOutOfRange)+ (moneyDecimalFromScientific (scientific 0 maxBound))+ check "scientific zero scale" (money 2 0)+ (moneyDecimalFromScientific (scientific 0 (-2)))+ checkScientificParts "scientific zero scale retained" (scientific 0 (-2)) (2, 0)+ where+ parse = moneyDecimalFromText . Text.pack++-- | Generated decimal renderings recover the same exact value.+propShowRoundTrip :: Property+propShowRoundTrip = forAll (chooseInteger (0, 10 ^ (30 :: Int))) $ \mantissa ->+ forAll (chooseInt (0, 255)) $ \places ->+ let decimal = Decimal (fromIntegral places) mantissa+ result = moneyDecimalFromText (Text.pack (show decimal))+ observe value =+ let raw = toDecimal value+ in (decimalPlaces raw, decimalMantissa raw)+ in fmap observe result === Right (decimalPlaces decimal, decimalMantissa decimal)++-- | Successful text and raw Scientific inputs have equal rational values.+propTextScientificValue :: Property+propTextScientificValue = forAll (chooseInteger (0, 10 ^ (30 :: Int))) $ \mantissa ->+ forAll (chooseInt (-30, 255)) $ \exponent ->+ let scientificValue = scientific mantissa exponent :: Scientific+ textValue = Text.pack (show mantissa ++ "e" ++ show exponent)+ fromText = moneyDecimalFromText textValue+ fromScientific = moneyDecimalFromScientific scientificValue+ in case (fromText, fromScientific) of+ (Right left, Right right) -> toRational left === toRational right+ _ -> counterexample ("unexpected rejection: " ++ show (fromText, fromScientific)) False++-- | Run a property with the same sample count as the main suite.+checkProperty :: String -> Property -> IO ()+checkProperty label propertyValue = do+ result <- quickCheckWithResult stdArgs { maxSuccess = 200, chatty = False } propertyValue+ unless (isSuccess result) $ do+ putStrLn ("[FAIL] money parse " ++ label)+ putStr (output result)+ exitFailure++-- | Execute all money parser acceptance cases and properties.+-- | Parsed amounts keep trailing zeros; values equal under 'Eq' must still+-- hash equally (0.5.3.1: the hash used the raw places and mantissa).+testHashMatchesEq :: IO ()+testHashMatchesEq = do+ let parsed = traverse (moneyDecimalFromText . Text.pack) ["1.0", "1.00", "1"]+ check "parsed 1.0, 1.00, 1 compare equal" (Right True) (fmap allSame parsed)+ check "parsed 1.0, 1.00, 1 hash equally" (Right True) (fmap (allSame . map hash) parsed)+ where+ allSame :: Eq a => [a] -> Bool+ allSame values = and (zipWith (==) values (drop 1 values))++runTests :: IO ()+runTests = do+ testExamples+ testHashMatchesEq+ checkProperty "Decimal show roundtrip" propShowRoundTrip+ checkProperty "Text and Scientific rational value" propTextScientificValue+ putStrLn "[PASS] money parser grammar, boundaries, priority, exactness, and properties"
+ test/compat-clients/CompatInstance.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}++module CompatInstance (checkInstances) where++import Control.Monad (unless)+import Data.Hashable (Hashable)+import ExchangeAlgebra.Accounting.Account ( AccountDivision(..)+ , AccountTitles(..)+ , FixedCurrent(..)+ , PIMO(..)+ , Side(..)+ )+import ExchangeAlgebra.Accounting.Exchange (ExBaseClass(..), Exchange(..))+import ExchangeAlgebra.Algebra (Alg, Redundant(..), (.@))+import ExchangeAlgebra.Algebra.Base ( BaseClass+ , Element(..)+ , Hat(..)+ , HatBase((:<))+ , HatBaseClass(..)+ )+import qualified ExchangeAlgebra.Journal as J++newtype DefaultBase = DefaultBase (HatBase AccountTitles)+ deriving (Eq, Ord, Show, Hashable)++instance Element DefaultBase where+ wildcard = DefaultBase wildcard++instance BaseClass DefaultBase++instance HatBaseClass DefaultBase where+ type BasePart DefaultBase = AccountTitles+ base (DefaultBase wrapped) = base wrapped+ hat (DefaultBase wrapped) = hat wrapped+ merge h title = DefaultBase (h :< title)+ toHat (DefaultBase wrapped) = DefaultBase (toHat wrapped)+ toNot (DefaultBase wrapped) = DefaultBase (toNot wrapped)+ revHat (DefaultBase wrapped) = DefaultBase (revHat wrapped)+ isHat (DefaultBase wrapped) = isHat wrapped+ isNot (DefaultBase wrapped) = isNot wrapped++instance ExBaseClass DefaultBase where+ getAccountTitle (DefaultBase (_ :< title)) = title+ setAccountTitle (DefaultBase (h :< _)) title = DefaultBase (h :< title)++newtype OverrideBase = OverrideBase (HatBase AccountTitles)+ deriving (Eq, Ord, Show, Hashable)++instance Element OverrideBase where+ wildcard = OverrideBase wildcard++instance BaseClass OverrideBase++instance HatBaseClass OverrideBase where+ type BasePart OverrideBase = AccountTitles+ base (OverrideBase wrapped) = base wrapped+ hat (OverrideBase wrapped) = hat wrapped+ merge h title = OverrideBase (h :< title)+ toHat (OverrideBase wrapped) = OverrideBase (toHat wrapped)+ toNot (OverrideBase wrapped) = OverrideBase (toNot wrapped)+ revHat (OverrideBase wrapped) = OverrideBase (revHat wrapped)+ isHat (OverrideBase wrapped) = isHat wrapped+ isNot (OverrideBase wrapped) = isNot wrapped++instance ExBaseClass OverrideBase where+ getAccountTitle (OverrideBase (_ :< title)) = title+ setAccountTitle (OverrideBase (h :< _)) title = OverrideBase (h :< title)+ whatDiv _ = Revenue+ isContra _ = True++check :: (Eq a, Show a) => String -> a -> a -> IO ()+check label expected actual =+ unless (expected == actual) (fail (label ++ ": " ++ show actual))++checkInstances :: IO ()+checkInstances = do+ let debit = DefaultBase (Not :< Cash)+ credit = DefaultBase (Hat :< Cash)+ alg = 4 .@ debit .+ 4 .@ credit :: Alg Double DefaultBase+ journal = alg J..| "tx" :: J.Journal String Double DefaultBase+ debitOnly = 4 .@ debit :: Alg Double DefaultBase+ debitJournal = debitOnly J..| "tx" :: J.Journal String Double DefaultBase+ override = OverrideBase (Not :< Cash)+ check "default division" Assets (whatDiv debit)+ check "default contra" False (isContra debit)+ check "default PIMO" PS (whatPIMO debit)+ check "default current" Current (fixedCurrent debit)+ check "default debit side" Debit (whichSide debit)+ check "default credit side" Credit (whichSide credit)+ check "override division" Revenue (whatDiv override)+ check "override contra" True (isContra override)+ check "override PIMO" OUT (whatPIMO override)+ check "override side" Debit (whichSide override)+ check "Alg decL" 4 (norm (decL alg))+ check "Alg decR" 4 (norm (decR alg))+ check "Alg balance" True (balance alg)+ check "Alg diffRL" (Side, 0) (diffRL alg)+ check "Alg debit-only balance" False (balance debitOnly)+ check "Alg debit-only diffRL" (Debit, 4) (diffRL debitOnly)+ check "Journal decL" 4 (norm (decL journal))+ check "Journal decR" 4 (norm (decR journal))+ check "Journal balance" True (balance journal)+ check "Journal diffRL" (Side, 0) (diffRL journal)+ check "Journal debit-only balance" False (balance debitJournal)+ check "Journal debit-only diffRL" (Debit, 4) (diffRL debitJournal)+ putStrLn "[PASS] user-defined instances and exchange results"
+ test/compat-clients/InstanceMain.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import CompatInstance (checkInstances)++main :: IO ()+main = checkInstances
+ test/compat-clients/MixedPath.hs view
@@ -0,0 +1,112 @@+module MixedPath where++import qualified OldPath as Old+import qualified ExchangeAlgebra.Simulate.Network as OldNetwork+import qualified ExchangeAlgebra.Simulation.Network as Network+import qualified ExchangeAlgebra.Simulate as OldSimulate+import qualified ExchangeAlgebra.Simulation.Analysis as Analysis+import qualified ExchangeAlgebra.Optimize as OldOptimize+import qualified ExchangeAlgebra.Simulation.Optimize as Optimize+import qualified ExchangeAlgebra.Render.Simulation as OldSimulationOutput+import qualified ExchangeAlgebra.Simulation.Output as SimulationOutput+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles(Cash))+import qualified ExchangeAlgebra.Accounting.Account as Account+import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import qualified ExchangeAlgebra.Algebra.Element as Element+import qualified ExchangeAlgebra.Algebra.Value as Value+import qualified ExchangeAlgebra.Algebra.Core as Core+import qualified ExchangeAlgebra.Algebra.Posting as Posting+import qualified ExchangeAlgebra.Algebra.Transfer.Rule as Rule+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Journal.Core as JournalCore+import qualified ExchangeAlgebra.Value as OldValue+import qualified ExchangeAlgebra.TrialBalance.Balance as OldTrialBalance+import qualified ExchangeAlgebra.Consolidation.Worksheet as OldConsolidation+import qualified ExchangeAlgebra.Accounting.TrialBalance as TrialBalance+import qualified ExchangeAlgebra.Accounting.Consolidation as Consolidation+import qualified ExchangeAlgebra.Accounting.Statements as Statements+import qualified ExchangeAlgebra.IO.Input as Input+import qualified ExchangeAlgebra.IO.Output.Statements as OutputStatements++import qualified ExchangeAlgebra.IO.Input.Admission as Admission+import qualified ExchangeAlgebra.Accounting.Transaction as Transaction++mixedPathPlaceholder :: Bool+mixedPathPlaceholder = Old.oldConstructor Old.oldExpression++mixedCore :: Double+mixedCore = Core.norm Old.oldExpression++mixedExchange :: Bool+mixedExchange = Exchange.balance Old.oldExpression++mixedJournal :: JournalCore.Journal String Double (HatBase Account.AccountTitles)+mixedJournal = Old.journalClient++mixedJournalExchange :: Bool+mixedJournalExchange = Exchange.balance mixedJournal++mixedPosting :: Either Posting.PostedError Double+mixedPosting = Posting.unPosted <$> Old.postingClient++mixedAccount :: Account.AccountDivision+mixedAccount = Account.classifyAccountDivision Cash++mixedElement :: Bool+mixedElement = Element.matchesQuery Cash Cash++mixedValue :: Value.MoneyDecimal -> Value.MoneyDecimal+mixedValue = id++mixedOldValue :: OldValue.MoneyDecimal -> Value.MoneyDecimal+mixedOldValue = id++-- | Apply the new closing entry point to an algebra built through the old path.+mixedClosing :: Either (Rule.TransferApplyError Double (HatBase Account.AccountTitles))+ (Core.Alg Double (HatBase Account.AccountTitles))+mixedClosing = Closing.closingEntries Old.oldExpression++-- | Pass a balance made through the old path to the new account readout.+mixedTrialBalance :: TrialBalance.AccountBalance Double+mixedTrialBalance = TrialBalance.combineBalances oldBalance oldBalance+ where+ oldBalance = OldTrialBalance.balanceFor Cash+ (OldTrialBalance.accountBalances Old.oldExpression)++mixedStatements :: Maybe Statements.DerivedMetric+mixedStatements = Statements.metricForLegacyTitle Cash++mixedConsolidation+ :: OldConsolidation.WorksheetInput String String Double+ -> Consolidation.WorksheetInput String String Double+mixedConsolidation = id++mixedInput :: Core.Alg Double (HatBase Account.AccountTitles)+mixedInput = Input.postingFromSide Account.Debit Cash 1++mixedOutput = OutputStatements.accountLedgerRowsJournal [Cash] Old.journalClient++mixedNetwork :: [Int]+mixedNetwork = Network.nodes (OldNetwork.completeNetwork [1, 2])++mixedAnalysis matrix = do+ inverse <- OldSimulate.leontiefInverse matrix+ Analysis.rippleEffect 1 inverse++mixedOptimize :: OldOptimize.Direction -> Optimize.Direction+mixedOptimize = id++mixedSimulationOutput :: OldSimulationOutput.Header -> SimulationOutput.Header+mixedSimulationOutput = id++-- | Feed an identifier from the retained admission entry point to its new owner.+mixedTransaction :: Bool+mixedTransaction = Transaction.isBlankKey+ (Admission.TxKey (Admission.EntityId mempty)+ (Admission.PeriodId mempty) (Admission.TxId mempty))++-- | Observe a journal through admission while old and new paths coexist.+mixedEquivalence :: Admission.Admitted String (HatBase Account.AccountTitles) -> Bool+mixedEquivalence accepted = JournalCore.toMap (Admission.admittedJournal accepted)+ == JournalCore.toMap (Admission.admittedJournal accepted)
+ test/compat-clients/NewPath.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE TypeFamilies #-}++module NewPath where++import qualified Data.List.NonEmpty+import qualified ExchangeAlgebra as Umbrella+import ExchangeAlgebra.Algebra.Core (Alg(Zero, (:@)), Redundant((.+)), ( .@ ))+import qualified ExchangeAlgebra.Algebra.Core as Core+import qualified ExchangeAlgebra.Algebra.Base as Base+import ExchangeAlgebra.Algebra.Base (Hat(Not), HatBase((:<)))+import ExchangeAlgebra.Accounting.Account (AccountTitles(Cash, Sales))+import qualified ExchangeAlgebra.Accounting.Exchange as Exchange+import qualified ExchangeAlgebra.Accounting.Account as Account+import qualified ExchangeAlgebra.Algebra.Element as Element+import qualified ExchangeAlgebra.Algebra.Transfer.Rule as Rule+import qualified ExchangeAlgebra.Accounting.Closing as Closing+import qualified ExchangeAlgebra.Journal.Core as Journal+import qualified ExchangeAlgebra.Algebra.Posting as Posting+import qualified ExchangeAlgebra.Algebra.Value as Value+import qualified ExchangeAlgebra.Accounting.Entries as Entries+import qualified ExchangeAlgebra.Accounting.TrialBalance as TrialBalance+import qualified ExchangeAlgebra.Accounting.Statements as Statements+import qualified ExchangeAlgebra.Accounting.Consolidation as Consolidation+import qualified ExchangeAlgebra.IO.Input as Input+import qualified ExchangeAlgebra.IO.Input.Csv as InputCsv+import qualified ExchangeAlgebra.IO.Input.Assist as Assist+import qualified ExchangeAlgebra.IO.Output.Csv as OutputCsv+import qualified ExchangeAlgebra.IO.Output as Output+import qualified ExchangeAlgebra.IO.Output.Statements as OutputStatements++import qualified ExchangeAlgebra.Simulation.Network as Network+import qualified ExchangeAlgebra.Simulation.Network.Flows as NetworkFlows+import qualified ExchangeAlgebra.Simulation.Network.Csv as NetworkCsv+import qualified ExchangeAlgebra.Simulation.Network.Journal as NetworkJournal+import qualified ExchangeAlgebra.Simulation.Analysis as Analysis+import qualified ExchangeAlgebra.Simulation.Optimize as Optimize+import qualified ExchangeAlgebra.Simulation.Optimize.Annealing as Annealing+import qualified ExchangeAlgebra.Simulation.Optimize.GA as GA+import qualified ExchangeAlgebra.Simulation.Output as SimulationOutput++import qualified ExchangeAlgebra.Accounting.Transaction as Transaction+import qualified ExchangeAlgebra.Accounting.Equivalence as Equivalence+import qualified ExchangeAlgebra.IO.Input.Admission as Admission++type ClientBasePart = Base.BasePart (HatBase AccountTitles)++oldExpression :: Alg Double (HatBase AccountTitles)+oldExpression = 1 .@ Not :< Cash .+ 1 .@ Not :< Sales++oldConstructor :: Alg Double (HatBase AccountTitles) -> Bool+oldConstructor Zero = True+oldConstructor (_ :@ _ :< _) = True+oldConstructor _ = False++oldClassMethod :: Bool+oldClassMethod = Umbrella.balance oldExpression++exchangeClient :: Bool+exchangeClient = Exchange.balance oldExpression++journalClient :: Journal.Journal String Double (HatBase AccountTitles)+journalClient = oldExpression Journal..| "client"++journalExchangeClient :: Bool+journalExchangeClient = Exchange.balance journalClient++postingClient :: Either Posting.PostedError (Posting.Posted Double)+postingClient = Posting.posted 1++coreClient :: Double+coreClient = Core.norm oldExpression++valueClient :: Value.MoneyDecimal -> Value.MoneyDecimal+valueClient = id++elementClient :: Bool+elementClient = Element.matchesQuery Cash Cash++-- | Use the public closing entry point with the new algebra and account paths.+closingClient :: Either (Rule.TransferApplyError Double (HatBase AccountTitles))+ (Alg Double (HatBase AccountTitles))+closingClient = Closing.closingEntries oldExpression++entriesClient :: Alg Double (HatBase AccountTitles)+entriesClient = Entries.reversingEntry oldExpression++trialBalanceClient :: TrialBalance.AccountBalance Double+trialBalanceClient = TrialBalance.balanceFor Cash+ (TrialBalance.accountBalances oldExpression)++statementsClient :: Maybe Statements.DerivedMetric+statementsClient = Statements.metricForLegacyTitle Cash++consolidationClient+ :: Consolidation.WorksheetInput String String Double+ -> Either (Data.List.NonEmpty.NonEmpty+ (Consolidation.WorksheetError String String Double))+ (Consolidation.ValidatedWorksheet String String Double)+consolidationClient = Consolidation.validateConsolidationWorksheet++inputFacadeClient :: Either Input.ConvError Account.Side+inputFacadeClient = Input.parseSide mempty++checkedClient :: Bool+checkedClient = either (const False) (const True) (Input.checkedEntry+ [(Account.Debit, Cash, 1 :: Double), (Account.Credit, Sales, 1 :: Double)])++csvClient = OutputCsv.csvTranspose++outputFacadeClient = Output.csvTranspose++inputCsvClient = InputCsv.splitTrim++assistClient :: Int+assistClient = length Assist.allAccountInfos++outputClient = OutputStatements.accountLedgerRowsJournal [Cash] journalClient++networkClient :: Network.TradeNetwork Int+networkClient = Network.completeNetwork [1, 2]++networkFlowClient :: NetworkFlows.TaxRate+networkFlowClient = NetworkFlows.TaxRate 1 10++networkCsvClient = NetworkCsv.readEdgeCsv++networkJournalClient = NetworkJournal.sigmaEdges networkClient (\_ _ -> journalClient)++analysisClient = Analysis.leontiefInverse++optimizeClient :: Optimize.Direction+optimizeClient = Optimize.Minimize++annealingClient :: Annealing.Annealing Double+annealingClient = Annealing.Annealing++gaClient :: GA.GA+gaClient = GA.GA++simulationOutputClient+ :: SimulationOutput.Header -> SimulationOutput.Header+simulationOutputClient = id++-- | Use the shared key type and its check through the new accounting path.+transactionClient :: Transaction.TxKey -> Bool+transactionClient = Transaction.isBlankKey++-- | Compare complete transaction maps through the new accounting path.+equivalenceClient :: Bool+equivalenceClient = Equivalence.isEquivalentUpTo Equivalence.PostingMultiset mempty mempty++-- | Admit a journal through the new input path.+admissionClient :: Journal.Journal String Value.MoneyDecimal (HatBase AccountTitles)+ -> Either (Admission.AdmissionFailure String (HatBase AccountTitles))+ (Admission.Admitted String (HatBase AccountTitles))+admissionClient = Admission.admit
+ test/compat-clients/OldPath.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE TypeFamilies #-}++module OldPath where++import qualified ExchangeAlgebra as Umbrella+import ExchangeAlgebra.Algebra (Alg(Zero, (:@)), Redundant((.+)), ( .@ ))+import qualified ExchangeAlgebra.Algebra.Base as Base+import ExchangeAlgebra.Algebra.Base (Hat(Not), HatBase((:<)))+import ExchangeAlgebra.Algebra.Base.Element (AccountTitles(Cash, Sales))+import qualified ExchangeAlgebra.Algebra.Base.Account.Registry as Registry+import ExchangeAlgebra.Algebra.Transfer.Rule ()+import qualified ExchangeAlgebra.Journal as Journal+import qualified ExchangeAlgebra.Posting as Posting+import qualified ExchangeAlgebra.Value as Value++type ClientBasePart = Base.BasePart (HatBase AccountTitles)++oldExpression :: Alg Double (HatBase AccountTitles)+oldExpression = 1 .@ Not :< Cash .+ 1 .@ Not :< Sales++oldConstructor :: Alg Double (HatBase AccountTitles) -> Bool+oldConstructor Zero = True+oldConstructor (_ :@ _ :< _) = True+oldConstructor _ = False++oldClassMethod :: Bool+oldClassMethod = Umbrella.balance oldExpression++journalClient :: Journal.Journal String Double (HatBase AccountTitles)+journalClient = oldExpression Journal..| "client"++postingClient :: Either Posting.PostedError (Posting.Posted Double)+postingClient = Posting.posted 1++valueClient :: Value.MoneyDecimal -> Value.MoneyDecimal+valueClient = id++registryClient :: Bool+registryClient = maybe False (const True) (Registry.accountSpec Cash)
+ test/compat-clients/README.md view
@@ -0,0 +1,21 @@+# P2 external compatibility clients++Run the clients after building the library from the repository root:++```bash+STACK_ARGS="--system-ghc --no-install-ghc" bash test/compat-clients/check.sh+```++`OldPath.hs` uses the current public imports, including the umbrella,+algebra, journal, base, transfer rule, posting, and value modules. It checks+the `.+`, `.@`, and `:<` fixities, a class method call, a constructor pattern,+and the `BasePart` associated type. `NewPath.hs` and `MixedPath.hs` are+placeholders that compile the old client now. Replace their imports and+expressions with the new and mixed paths as each P2 wave creates them.++`CompatInstance.hs` defines two client-owned base types. One uses the+`ExBaseClass` defaults, and the other overrides `whatDiv` and `isContra`.+The check fixes the observed debit and credit totals, balance, and `diffRL`+results for both `Alg` and `Journal`. The surface test suite runs these+assertions, and this script also builds and runs them as an installed-package+client with source lookup disabled.
+ test/compat-clients/check.sh view
@@ -0,0 +1,27 @@+#!/usr/bin/env bash+set -euo pipefail++compat_root=$(cd "$(dirname "$0")/../.." && pwd)+compat_tmp=$(mktemp -d "${TMPDIR:-/tmp}/ea-compat-p2.XXXXXX")+trap 'rm -rf "$compat_tmp"' EXIT+read -r -a compat_stack_args <<< "${STACK_ARGS:-}"++cd "$compat_tmp"+for compat_client in OldPath NewPath MixedPath; do+ stack ${compat_stack_args[@]+"${compat_stack_args[@]}"} \+ --stack-yaml "$compat_root/stack.yaml" exec -- ghc \+ -fno-code -fforce-recomp -v0 -fdiagnostics-color=never -i \+ -i"$compat_root/test/compat-clients" -hide-all-packages \+ -package base -package exchangealgebra \+ -outputdir "$compat_tmp" \+ "$compat_root/test/compat-clients/$compat_client.hs"+done+printf '[PASS] old, new, and mixed clients compiled\n'+stack ${compat_stack_args[@]+"${compat_stack_args[@]}"} \+ --stack-yaml "$compat_root/stack.yaml" exec -- ghc \+ -fforce-recomp -v0 -fdiagnostics-color=never -i \+ -i"$compat_root/test/compat-clients" \+ -hide-all-packages -package base -package hashable -package exchangealgebra \+ -outputdir "$compat_tmp" -o "$compat_tmp/instance-client" \+ "$compat_root/test/compat-clients/InstanceMain.hs"+"$compat_tmp/instance-client"
+ test/compile-fail/AdmissionFailureConstructor.hs view
@@ -0,0 +1,10 @@+module AdmissionFailureConstructor where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++failureItems :: AdmissionFailure n b -> ()+failureItems (AdmissionFailure _) = ()
+ test/compile-fail/AlgConstructor.hs view
@@ -0,0 +1,7 @@+module AlgConstructor where++import ExchangeAlgebra.Algebra++-- Construction through the public pattern must be rejected.+posting :: Alg Double (HatBase CountUnit)+posting = 1 :@ (Not :< Yen)
+ test/compile-fail/AlgHiddenConstructor.hs view
@@ -0,0 +1,3 @@+module AlgHiddenConstructor where++import ExchangeAlgebra.Algebra.Core (Alg(Single, Liner))
+ test/compile-fail/AlgRecordUpdate.hs view
@@ -0,0 +1,7 @@+module AlgRecordUpdate where++import ExchangeAlgebra.Algebra++-- Selectors remain public, but record updates must be rejected.+posting :: Alg Double (HatBase CountUnit)+posting = (1 .@ (Not :< Yen)) { _val = 2 }
+ test/compile-fail/Coerce.hs view
@@ -0,0 +1,12 @@+module Coerce where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import Data.Coerce (coerce)++forge :: Journal String MoneyDecimal (HatBase AccountTitles)+ -> Admitted String (HatBase AccountTitles)+forge = coerce
+ test/compile-fail/Constructor.hs view
@@ -0,0 +1,10 @@+module Constructor where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++forge :: Admitted String (HatBase AccountTitles)+forge = Admitted mempty
+ test/compile-fail/DeprecatedTransfer.hs view
@@ -0,0 +1,9 @@+-- | Compilation succeeds, but the public rule-module re-export still warns.+module DeprecatedTransfer where++import ExchangeAlgebra hiding (scaleBy)+import ExchangeAlgebra.Algebra.Transfer.Rule (scaleBy)++-- | Legacy per-posting scaling remains available during migration.+legacyRule :: TransferRule Double (HatBase (AccountTitles, CountUnit))+legacyRule = scaleBy (Not :< (RentExpense, Yen)) (Not :< (PrepaidExpenses, Yen)) 0.5
+ test/compile-fail/HiddenModule.hs view
@@ -0,0 +1,11 @@+module HiddenModule where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.IO.Input.Admission.Representation++forge :: Admitted String (HatBase AccountTitles)+forge = Admitted mempty
+ test/compile-fail/PublicClient.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings #-}++module PublicClient where++import ExchangeAlgebra.Algebra.Core (Alg((:@), _val, _hatBase), (.@))+import ExchangeAlgebra.Algebra.Base (HatBase((:<)), Hat(Not), CountUnit(Yen))+import ExchangeAlgebra.Algebra.Core (Redundant((.+)))+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Journal.Core (Journal, (.|))+import ExchangeAlgebra.Accounting.Adjustment (BasisPoints, basisPoints)+import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Accounting.Account (AccountTitles(..))++-- The same installed-package environment must accept a complete public path.+acceptedLedger :: Either String (Journal String MoneyDecimal (HatBase AccountTitles))+acceptedLedger = do+ accepted <- either (Left . show) Right+ (admit ((10 .@ Not :< Cash .+ 10 .@ Not :< Sales) .| "sale"))+ either (Left . show) Right+ (checkEvidence accepted id (10 .@ Not :< Cash .| "sale"))+ Right (admittedJournal accepted)++-- | Bundled selectors and the infix matching pattern remain public.+matchPosting :: Alg Double (HatBase CountUnit) -> Maybe (Double, HatBase CountUnit)+matchPosting entry@(_ :@ _) = Just (_val entry, _hatBase entry)+matchPosting _ = Nothing++-- | Checked construction remains available to external clients.+publicPosting :: Alg Double (HatBase CountUnit)+publicPosting = 1 .@ (Not :< Yen)++-- | Checked rates are available without their representation constructor.+publicRate :: Either String BasisPoints+publicRate = either (Left . show) Right (basisPoints 147.5)
+ test/compile-fail/PublicExplicitImportClient.hs view
@@ -0,0 +1,22 @@+module PublicExplicitImportClient where++import ExchangeAlgebra.IO.Input.Admission+ ( Admitted+ , AdmissionFailure+ , admittedJournal+ , admit+ , checkEvidence+ )+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)+import ExchangeAlgebra.Journal.Core (Journal)++-- | Explicit imports admit and observe an empty journal.+publicAdmission :: Either (AdmissionFailure String (HatBase AccountTitles))+ (Admitted String (HatBase AccountTitles))+publicAdmission = admit (mempty :: Journal String MoneyDecimal (HatBase AccountTitles))++-- | Observation and evidence checking need only the public API.+publicEvidence :: Admitted String (HatBase AccountTitles) -> Bool+publicEvidence accepted = checkEvidence accepted id (admittedJournal accepted) == Right ()
+ test/compile-fail/RawDerivation.hs view
@@ -0,0 +1,11 @@+module RawDerivation where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++deriveRaw :: Journal String MoneyDecimal (HatBase AccountTitles)+ -> Journal String MoneyDecimal (HatBase AccountTitles)+deriveRaw = admittedJournal
+ test/compile-fail/RecordUpdate.hs view
@@ -0,0 +1,11 @@+module RecordUpdate where++import ExchangeAlgebra.IO.Input.Admission+import ExchangeAlgebra.Algebra.Base (HatBase)+import ExchangeAlgebra.Accounting.Account (AccountTitles)+import ExchangeAlgebra.Journal.Core (Journal)+import ExchangeAlgebra.Algebra.Value (MoneyDecimal)++replaceJournal :: Admitted String (HatBase AccountTitles)+ -> Admitted String (HatBase AccountTitles)+replaceJournal accepted = accepted { admittedJournal = mempty }
+ test/compile-fail/check.sh view
@@ -0,0 +1,87 @@+#!/usr/bin/env bash+# From the repository root:+# stack build --test --bench --no-run-benchmarks+# bash test/compile-fail/check.sh+# Set STACK_ARGS to the global stack options used for that build (for example+# "--system-ghc --no-install-ghc" in CI) so the check reads the same package db.+# Compile as an external installed-package client with source lookup disabled.+# Logs and compiler artifacts live in a temporary directory removed on exit.+set -euo pipefail++admission_root=$(cd "$(dirname "$0")/../.." && pwd)+admission_tmp=$(mktemp -d "${TMPDIR:-/tmp}/ea-admission-boundary.XXXXXX")+trap 'rm -rf "$admission_tmp"' EXIT+read -r -a admission_stack_args <<< "${STACK_ARGS:-}"++compile() {+ local snippet=$1+ stack ${admission_stack_args[@]+"${admission_stack_args[@]}"} \+ --stack-yaml "$admission_root/stack.yaml" exec -- ghc \+ -fno-code -fforce-recomp -v0 -Wdeprecations -fdiagnostics-color=never -i \+ -hide-all-packages -package base -package text -package containers \+ -package exchangealgebra -outputdir "$admission_tmp" \+ "$admission_root/test/compile-fail/$snippet.hs" \+ > "$admission_tmp/$snippet.log" 2>&1+}++reject() {+ local snippet=$1+ local reason=$2+ if compile "$snippet"; then+ printf '[FAIL] %s compiled unexpectedly\n' "$snippet"+ exit 1+ fi+ if ! grep -Eq "$reason" "$admission_tmp/$snippet.log"; then+ printf '[FAIL] %s failed for an unexpected reason\n' "$snippet"+ cat "$admission_tmp/$snippet.log"+ exit 1+ fi+ printf '[PASS] %s rejected for the intended reason\n' "$snippet"+}++cd "$admission_tmp"+if ! compile PublicClient; then+ cat "$admission_tmp/PublicClient.log"+ exit 1+fi+printf '[PASS] public API client compiled\n'+if ! compile PublicExplicitImportClient; then+ cat "$admission_tmp/PublicExplicitImportClient.log"+ exit 1+fi+printf '[PASS] explicit-import client compiled\n'+reject Constructor 'Data constructor not in scope:.*Admitted|Not in scope:.*Admitted|Illegal term-level use of the type constructor.*Admitted'+reject AdmissionFailureConstructor 'Data constructor not in scope:.*AdmissionFailure|Not in scope:.*AdmissionFailure|Illegal term-level use of the type constructor.*AdmissionFailure'+reject RecordUpdate 'not a record selector|not a record field|Not in scope: record field.*admittedJournal'+reject Coerce "Couldn't match (representation|type)|Could not deduce.*Coercible"+reject RawDerivation "Couldn't match.*(Admitted|Journal)|Expected:.*Admitted"+reject HiddenModule 'hidden module'+if ! grep -Fq 'ExchangeAlgebra.IO.Input.Admission.Representation' "$admission_tmp/HiddenModule.log"; then+ printf '[FAIL] HiddenModule rejected a module other than the admission internal module\n'+ cat "$admission_tmp/HiddenModule.log"+ exit 1+fi++reject AlgConstructor 'non-bidirectional pattern synonym|not bidirectional'+reject AlgRecordUpdate 'non-bidirectional pattern synonym|not a record selector|not a record field'+reject AlgHiddenConstructor 'does not export any children|does not export.*(Single|Liner)'++# Deprecated APIs remain usable, and their warning survives public rule-module re-export.+warn_deprecated() {+ local snippet=$1+ local identifier=$2+ if ! compile "$snippet"; then+ printf '[FAIL] %s did not compile\n' "$snippet"+ cat "$admission_tmp/$snippet.log"+ exit 1+ fi+ if ! grep -Eq "In the use of [‘'\"]([^‘’'\"]*\\.)?${identifier}[’'\"]" \+ "$admission_tmp/$snippet.log"; then+ printf '[FAIL] %s compiled without the expected deprecation warning\n' "$snippet"+ cat "$admission_tmp/$snippet.log"+ exit 1+ fi+ printf '[PASS] %s compiled with the expected deprecation warning\n' "$snippet"+}++warn_deprecated DeprecatedTransfer scaleBy
test/doctests.hs view
@@ -5,32 +5,62 @@ main :: IO () main = doctest [ "-isrc" , "src/ExchangeAlgebra.hs"- , "src/ExchangeAlgebra/Algebra/Internal.hs"+ , "src/ExchangeAlgebra/Algebra/Core/Representation.hs" -- not reachable from the umbrella above; listed explicitly so -- its Haddock examples are checked too.- , "src/ExchangeAlgebra/Simulate/Network.hs"+ , "src/ExchangeAlgebra/Algebra/Element/Representation.hs"+ , "src/ExchangeAlgebra/Algebra/Base/Representation.hs"+ , "src/ExchangeAlgebra/Accounting/Account.hs"+ , "src/ExchangeAlgebra/Accounting/Exchange.hs"+ , "src/ExchangeAlgebra/Algebra/Transfer/Representation.hs"+ , "src/ExchangeAlgebra/Accounting/Closing.hs"+ , "src/ExchangeAlgebra/Journal/Core/Representation.hs"+ -- reporting gates have their own examples;+ -- explicit roots keep them covered independently of umbrellas.+ , "src/ExchangeAlgebra/Accounting/TrialBalance/Validation.hs"+ , "src/ExchangeAlgebra/Accounting/Statements/Presentation.hs"+ , "src/ExchangeAlgebra/Accounting/Consolidation.hs"+ -- admission execution modules are not re-exported in full;+ -- list them explicitly to check their examples too.+ , "src/ExchangeAlgebra/Accounting/Catalog.hs"+ , "src/ExchangeAlgebra/Accounting/Adjustment.hs"+ , "src/ExchangeAlgebra/IO/Input/Admission/Engine.hs"+ -- the admission walkthrough imports the trial-balance+ -- umbrella qualified; it is not reachable from the umbrella.+ , "src/ExchangeAlgebra/Accounting/TrialBalance.hs"+ -- the admission examples import its public API qualified;+ -- load that module before doctest evaluates those imports.+ , "src/ExchangeAlgebra/IO/Input/Admission.hs"+ , "src/ExchangeAlgebra/Algebra/Value.hs"+ , "src/ExchangeAlgebra/Algebra/Value/Class.hs"+ , "src/ExchangeAlgebra/Simulation/Network/Representation.hs"+ , "src/ExchangeAlgebra/Simulation/Network/Flows.hs"+ , "src/ExchangeAlgebra/Simulation/Network/Csv.hs"+ , "src/ExchangeAlgebra/Simulation/Network/Journal.hs" , "src/ExchangeAlgebra/Simulate/Policy.hs" -- closing-adjustment builders: not re-exported from the -- umbrella, so listed explicitly to check its examples too. , "src/ExchangeAlgebra/Bookkeeping.hs" -- dependency-free input-conversion core: not re-exported from -- the umbrella, so listed explicitly to check its examples too.- , "src/ExchangeAlgebra/Convert.hs"- , "src/ExchangeAlgebra/Convert/Csv.hs"- , "src/ExchangeAlgebra/Convert/Checked.hs"- , "src/ExchangeAlgebra/Assist.hs"+ , "src/ExchangeAlgebra/IO/Input.hs"+ , "src/ExchangeAlgebra/IO/Input/Conversion.hs"+ , "src/ExchangeAlgebra/IO/Input/Csv.hs"+ , "src/ExchangeAlgebra/IO/Input/Checked.hs"+ , "src/ExchangeAlgebra/IO/Input/Assist.hs" , "src/ExchangeAlgebra/Reporting/Group.hs" -- optimization layer: not re-exported from the umbrella, -- so listed explicitly to check its examples too.- , "src/ExchangeAlgebra/Optimize.hs"+ , "src/ExchangeAlgebra/Simulation/Optimize.hs" -- 0.5.1.0 umbrellas (re-export only): not reachable from the -- top-level umbrella, so listed explicitly.- , "src/ExchangeAlgebra/Foundation.hs" , "src/ExchangeAlgebra/Accounting.hs"- , "src/ExchangeAlgebra/Algebra/Readout/Net.hs" , "src/ExchangeAlgebra/Simulate/Engine.hs"- , "src/ExchangeAlgebra/Simulate/Analysis.hs"+ , "src/ExchangeAlgebra/Simulation/Analysis.hs" , "src/ExchangeAlgebra/Simulate/Random.hs"+ , "src/ExchangeAlgebra/IO/Output/Csv.hs"+ , "src/ExchangeAlgebra/IO/Output/Statements.hs"+ -- deprecated Render shims still carry the row-layout examples. , "src/ExchangeAlgebra/Render/Csv.hs" , "src/ExchangeAlgebra/Render/Bookkeeping.hs"- , "src/ExchangeAlgebra/Render/Simulation.hs"]+ , "src/ExchangeAlgebra/Simulation/Output.hs"]
+ test/fixtures/account-algebra-behavior/presentation.tsv view
@@ -0,0 +1,244 @@+# account-algebra-behavior presentation probe (title); schema 1+Cash+Deposits+CurrentDeposits+Securities+InvestmentSecurities+InvestmentInAssociate+LongTermNationalBonds+ShortTermNationalBonds+Products+Machinery+Building+Vehicle+StockInvestment+EquipmentInvestment+LongTermLoansReceivable+AccountsReceivable+ShortTermLoansReceivable+ReserveDepositReceivable+Gold+GovernmentService+CapitalStock+RetainedEarnings+LongTermLoansPayable+ShortTermLoansPayable+LoansPayable+ReserveForDepreciation+DepositPayable+LongTermNationalBondsPayable+ShortTermNationalBondsPayable+ReserveDepositPayable+CentralBankNotePayable+Depreciation+AmortizationExpense+SalesCost+BusinessTrip+Commutation+UtilitiesExpense+RentExpense+AdvertisingExpense+DeliveryExpenses+SuppliesExpenses+MiscellaneousExpenses+WageExpenditure+InterestExpense+TaxesExpense+ConsumptionExpenditure+SubsidyExpense+CentralBankPaymentExpense+Purchases+NetIncome+ValueAdded+SubsidyIncome+NationalBondInterestEarned+DepositInterestEarned+GrossProfit+OrdinaryProfit+InterestEarned+ReceiptFee+RentalIncome+WageEarned+TaxesRevenue+CentralBankPaymentIncome+Sales+EquityInEarningsOfInvestee+NetLoss+PettyCash+NotesReceivable+ElectronicallyRecordedReceivable+CreditCardReceivable+NotesLoansReceivable+MerchandiseInventory+AdvancesPaid+PrepaidExpenses+AccruedRevenue+OtherReceivables+PaymentsOnBehalf+SuspensePayments+ConsumptionTaxPaid+PrepaidCorporateIncomeTaxes+Land+Fixtures+Patent+Trademark+Software+CashOverShort+AccountsPayable+NotesPayable+ElectronicallyRecordedObligations+NotesLoansPayable+BankOverdraft+AdvancesReceived+UnearnedRevenue+AccruedExpenses+OtherPayables+DepositsReceived+SuspenseReceipts+ConsumptionTaxReceived+AccruedConsumptionTax+AccruedCorporateIncomeTaxes+UnpaidDividends+AllowanceForDoubtfulAccounts+AccumulatedDepreciation+LegalRetainedEarnings+CumulativeTranslationAdjustment+ProvisionForDoubtfulAccounts+BadDebtLoss+LossOnSalesOfFixedAssets+LossOnSalesOfNotesReceivable+PaymentFees+MiscellaneousLoss+CorporateIncomeTaxes+CommunicationExpenses+GainOnSalesOfFixedAssets+RecoveryOfBadDebts+MiscellaneousIncome+ReversalOfAllowanceForDoubtfulAccounts+TimeDeposits+LoansReceivable+GiftCertificatesReceived+SecurityDepositsPaid+SuppliesOnHand+ContractAssets+IncomeTaxesRefundReceivable+WorkInProcess+DeferredTaxAssets+LeasedAssets+ToolsAndInstruments+ConstructionInProgress+Goodwill+SoftwareInProgress+LongTermPrepaidExpenses+DishonoredNotesReceivable+PrepaidPensionCost+NetDefinedBenefitAsset+DepositsInSpecialAccounts+Structures+LeaseholdRights+NonOperatingNotesReceivable+NonOperatingElectronicallyRecordedReceivable+RefundLiabilities+NonOperatingNotesPayable+NonOperatingElectronicallyRecordedObligations+BonusesPayable+AllowanceForRepairs+AllowanceForProductWarranties+AllowanceForBonuses+DeferredTaxLiabilities+LeaseObligations+GuaranteeDepositsReceived+AllowanceForRetirementBenefits+LongTermOtherPayables+NetDefinedBenefitLiability+StockSubscriptionDeposits+LegalCapitalSurplus+OtherCapitalSurplus+DividendEqualizationReserve+RepairFundReserve+ConstructionFundReserve+GeneralReserve+ValuationDifferenceOnOtherSecurities+NonControllingInterests+CapitalSurplus+EarnedSurplus+ServiceRevenue+OperatingRevenue+GainOnSalesOfSecurities+GainOnValuationOfSecurities+DividendsReceived+InterestOnSecurities+GainOnSalesOfInvestmentSecurities+InsuranceGain+GainOnBargainPurchase+ReversalOfAllowanceForRepairs+ReversalOfAllowanceForProductWarranties+GainOnDonationOfFixedAssets+GainOnNationalSubsidies+GainOnConstructionGrants+LandRentReceived+SalesRebates+CostOfServices+OperatingExpenses+InventoryShrinkageLoss+LossOnValuationOfMerchandise+Bonuses+RetirementBenefitExpenses+ProvisionForRepairs+ProvisionForBonuses+ProvisionForProductWarranties+ResearchAndDevelopmentExpenses+AmortizationOfGoodwill+AmortizationOfSoftware+AmortizationOfPatents+LeaseExpenses+IncorporationExpenses+StockIssuanceCosts+BusinessCommencementExpenses+DevelopmentExpenses+LossOnSalesOfElectronicallyRecordedReceivables+LossOnSalesOfReceivables+LossOnSalesOfSecurities+LossOnValuationOfSecurities+LossOnSalesOfInvestmentSecurities+LossOnFire+LossOnRetirementOfFixedAssets+LossOnReductionOfFixedAssets+AdditionalIncomeTaxesForPriorPeriods+RefundOfIncomeTaxes+PurchaseRebates+WelfareExpenses+MaintenanceExpenses+StatutoryWelfareExpenses+LandRentPaid+InsuranceExpense+RepairsExpense+StorageExpenses+MembershipFees+IncomeSummary+SuspenseAccount+ForeignExchangeGains+ForeignExchangeLosses+ContraAccountForGuaranteeObligations+GuaranteeObligations+IncomeTaxesAdjustment+BranchCurrentAccount+HeadOfficeCurrentAccount+NetIncomeAttributableToNCI+NetLossAttributableToNCI+TradingSecurities+HeldToMaturityBonds+SubsidiaryStocks+AffiliateStocks+AvailableForSaleSecurities+ConsumptionTaxRefundReceivable+PropertyTaxPayable+DepositsReceivedFromOfficers+EntertainmentExpenses+MeetingExpenses+NewspaperBooksExpenses+RawMaterials+GoodsInTransit+SalesAllowances+PurchaseAllowances+ReversalOfAllowanceForBonuses
+ test/fixtures/account-algebra-behavior/projection-membership.tsv view
@@ -0,0 +1,244 @@+# account-algebra-behavior projection flags for Not then Hat (currentAssets, fixedAssets, deferredAssets, currentLiability, fixedLiability, capitalStock, contraAssets, contra); schema 1+Cash 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Deposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CurrentDeposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Securities 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InvestmentSecurities 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InvestmentInAssociate 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LongTermNationalBonds 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ShortTermNationalBonds 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Products 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Machinery 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Building 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Vehicle 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+StockInvestment 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0+EquipmentInvestment 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LongTermLoansReceivable 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AccountsReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ShortTermLoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ReserveDepositReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Gold 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GovernmentService 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CapitalStock 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+RetainedEarnings 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+LongTermLoansPayable 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+ShortTermLoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+LoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+ReserveForDepreciation 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+DepositPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+LongTermNationalBondsPayable 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+ShortTermNationalBondsPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+ReserveDepositPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+CentralBankNotePayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+Depreciation 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AmortizationExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SalesCost 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+BusinessTrip 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Commutation 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+UtilitiesExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RentExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AdvertisingExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DeliveryExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SuppliesExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MiscellaneousExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+WageExpenditure 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InterestExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+TaxesExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ConsumptionExpenditure 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SubsidyExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CentralBankPaymentExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Purchases 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NetIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ValueAdded 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SubsidyIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NationalBondInterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DepositInterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GrossProfit 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+OrdinaryProfit 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ReceiptFee 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RentalIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+WageEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+TaxesRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CentralBankPaymentIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Sales 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+EquityInEarningsOfInvestee 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NetLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PettyCash 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ElectronicallyRecordedReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CreditCardReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NotesLoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MerchandiseInventory 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AdvancesPaid 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PrepaidExpenses 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AccruedRevenue 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+OtherReceivables 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PaymentsOnBehalf 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SuspensePayments 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ConsumptionTaxPaid 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PrepaidCorporateIncomeTaxes 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Land 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Fixtures 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Patent 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Trademark 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Software 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CashOverShort 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0+AccountsPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+NotesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+ElectronicallyRecordedObligations 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+NotesLoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+BankOverdraft 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AdvancesReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+UnearnedRevenue 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AccruedExpenses 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+OtherPayables 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+DepositsReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+SuspenseReceipts 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+ConsumptionTaxReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AccruedConsumptionTax 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AccruedCorporateIncomeTaxes 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+UnpaidDividends 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AllowanceForDoubtfulAccounts 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1+AccumulatedDepreciation 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1+LegalRetainedEarnings 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+CumulativeTranslationAdjustment 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+ProvisionForDoubtfulAccounts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+BadDebtLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfNotesReceivable 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PaymentFees 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MiscellaneousLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CorporateIncomeTaxes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+CommunicationExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnSalesOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RecoveryOfBadDebts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MiscellaneousIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ReversalOfAllowanceForDoubtfulAccounts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+TimeDeposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GiftCertificatesReceived 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SecurityDepositsPaid 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SuppliesOnHand 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ContractAssets 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+IncomeTaxesRefundReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+WorkInProcess 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DeferredTaxAssets 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LeasedAssets 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ToolsAndInstruments 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ConstructionInProgress 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Goodwill 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SoftwareInProgress 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LongTermPrepaidExpenses 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DishonoredNotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PrepaidPensionCost 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NetDefinedBenefitAsset 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DepositsInSpecialAccounts 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Structures 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LeaseholdRights 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NonOperatingNotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NonOperatingElectronicallyRecordedReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RefundLiabilities 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+NonOperatingNotesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+NonOperatingElectronicallyRecordedObligations 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+BonusesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AllowanceForRepairs 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AllowanceForProductWarranties 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+AllowanceForBonuses 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+DeferredTaxLiabilities 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+LeaseObligations 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+GuaranteeDepositsReceived 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+AllowanceForRetirementBenefits 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+LongTermOtherPayables 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+NetDefinedBenefitLiability 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0+StockSubscriptionDeposits 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+LegalCapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+OtherCapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+DividendEqualizationReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+RepairFundReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+ConstructionFundReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+GeneralReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+ValuationDifferenceOnOtherSecurities 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+NonControllingInterests 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+CapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+EarnedSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0+ServiceRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+OperatingRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnSalesOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnValuationOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DividendsReceived 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InterestOnSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnSalesOfInvestmentSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InsuranceGain 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnBargainPurchase 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ReversalOfAllowanceForRepairs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ReversalOfAllowanceForProductWarranties 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnDonationOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnNationalSubsidies 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GainOnConstructionGrants 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LandRentReceived 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SalesRebates 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1+CostOfServices 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+OperatingExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InventoryShrinkageLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnValuationOfMerchandise 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+Bonuses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RetirementBenefitExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ProvisionForRepairs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ProvisionForBonuses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ProvisionForProductWarranties 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ResearchAndDevelopmentExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AmortizationOfGoodwill 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AmortizationOfSoftware 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AmortizationOfPatents 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LeaseExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+IncorporationExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+StockIssuanceCosts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+BusinessCommencementExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+DevelopmentExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfElectronicallyRecordedReceivables 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfReceivables 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnValuationOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnSalesOfInvestmentSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnFire 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnRetirementOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LossOnReductionOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AdditionalIncomeTaxesForPriorPeriods 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RefundOfIncomeTaxes 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1+PurchaseRebates 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1+WelfareExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MaintenanceExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+StatutoryWelfareExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+LandRentPaid 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+InsuranceExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RepairsExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+StorageExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MembershipFees 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+IncomeSummary 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0+SuspenseAccount 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ForeignExchangeGains 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ForeignExchangeLosses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ContraAccountForGuaranteeObligations 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0+GuaranteeObligations 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+IncomeTaxesAdjustment 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+BranchCurrentAccount 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0+HeadOfficeCurrentAccount 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NetIncomeAttributableToNCI 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NetLossAttributableToNCI 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+TradingSecurities 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+HeldToMaturityBonds 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SubsidiaryStocks 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AffiliateStocks 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+AvailableForSaleSecurities 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0+ConsumptionTaxRefundReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+PropertyTaxPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+DepositsReceivedFromOfficers 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0+EntertainmentExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+MeetingExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+NewspaperBooksExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+RawMaterials 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+GoodsInTransit 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0+SalesAllowances 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1+PurchaseAllowances 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1+ReversalOfAllowanceForBonuses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ test/fixtures/account-algebra-behavior/semantics.tsv view
@@ -0,0 +1,244 @@+# account-algebra-behavior semantics (title, enum, binaryHex, division, closing, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe); schema 1+Cash 0 8000 Assets CloseByDivision False Debit Credit PS Current Nothing+Deposits 1 8001 Assets CloseByDivision False Debit Credit PS Current Nothing+CurrentDeposits 2 8002 Assets CloseByDivision False Debit Credit PS Current Nothing+Securities 3 8003 Assets CloseByDivision False Debit Credit PS Current Nothing+InvestmentSecurities 4 8004 Assets CloseByDivision False Debit Credit PS Fixed Nothing+InvestmentInAssociate 5 8005 Assets CloseByDivision False Debit Credit PS Fixed Nothing+LongTermNationalBonds 6 8006 Assets CloseByDivision False Debit Credit PS Fixed Nothing+ShortTermNationalBonds 7 8007 Assets CloseByDivision False Debit Credit PS Current Nothing+Products 8 8008 Assets CloseByDivision False Debit Credit PS Current Nothing+Machinery 9 8009 Assets CloseByDivision False Debit Credit PS Fixed Nothing+Building 10 800a Assets CloseByDivision False Debit Credit PS Fixed Nothing+Vehicle 11 800b Assets CloseByDivision False Debit Credit PS Fixed Nothing+StockInvestment 12 800c Assets CloseByDivision False Debit Credit PS Other Nothing+EquipmentInvestment 13 800d Assets CloseByDivision False Debit Credit PS Fixed Nothing+LongTermLoansReceivable 14 800e Assets CloseByDivision False Debit Credit PS Fixed Nothing+AccountsReceivable 15 800f Assets CloseByDivision False Debit Credit PS Current Nothing+ShortTermLoansReceivable 16 8010 Assets CloseByDivision False Debit Credit PS Current Nothing+ReserveDepositReceivable 17 8011 Assets CloseByDivision False Debit Credit PS Current Nothing+Gold 18 8012 Assets CloseByDivision False Debit Credit PS Fixed Nothing+GovernmentService 19 8013 Assets CloseByDivision False Debit Credit PS Current Nothing+CapitalStock 20 8014 Equity CloseByDivision False Credit Debit MS Other Nothing+RetainedEarnings 21 8015 Equity CloseByDivision False Credit Debit MS Other SELF+LongTermLoansPayable 22 8016 Liability CloseByDivision False Credit Debit MS Fixed Nothing+ShortTermLoansPayable 23 8017 Liability CloseByDivision False Credit Debit MS Current Nothing+LoansPayable 24 8018 Liability CloseByDivision False Credit Debit MS Current Nothing+ReserveForDepreciation 25 8019 Liability CloseByDivision False Credit Debit MS Current Nothing+DepositPayable 26 801a Liability CloseByDivision False Credit Debit MS Current Nothing+LongTermNationalBondsPayable 27 801b Liability CloseByDivision False Credit Debit MS Fixed Nothing+ShortTermNationalBondsPayable 28 801c Liability CloseByDivision False Credit Debit MS Current Nothing+ReserveDepositPayable 29 801d Liability CloseByDivision False Credit Debit MS Current Nothing+CentralBankNotePayable 30 801e Liability CloseByDivision False Credit Debit MS Current Nothing+Depreciation 31 801f Cost CloseByDivision False Debit Credit OUT Other Flip+AmortizationExpense 32 8020 Cost CloseByDivision False Debit Credit OUT Other Flip+SalesCost 33 8021 Cost CloseByDivision False Debit Credit OUT Other Flip+BusinessTrip 34 8022 Cost CloseByDivision False Debit Credit OUT Other Flip+Commutation 35 8023 Cost CloseByDivision False Debit Credit OUT Other Flip+UtilitiesExpense 36 8024 Cost CloseByDivision False Debit Credit OUT Other Flip+RentExpense 37 8025 Cost CloseByDivision False Debit Credit OUT Other Flip+AdvertisingExpense 38 8026 Cost CloseByDivision False Debit Credit OUT Other Flip+DeliveryExpenses 39 8027 Cost CloseByDivision False Debit Credit OUT Other Flip+SuppliesExpenses 40 8028 Cost CloseByDivision False Debit Credit OUT Other Flip+MiscellaneousExpenses 41 8029 Cost CloseByDivision False Debit Credit OUT Other Flip+WageExpenditure 42 802a Cost CloseByDivision False Debit Credit OUT Other Flip+InterestExpense 43 802b Cost CloseByDivision False Debit Credit OUT Other Flip+TaxesExpense 44 802c Cost CloseByDivision False Debit Credit OUT Other Flip+ConsumptionExpenditure 45 802d Cost CloseByDivision False Debit Credit OUT Other Flip+SubsidyExpense 46 802e Cost CloseByDivision False Debit Credit OUT Other Flip+CentralBankPaymentExpense 47 802f Cost CloseByDivision False Debit Credit OUT Other Flip+Purchases 48 8030 Cost CloseByDivision False Debit Credit OUT Other Flip+NetIncome 49 8031 Cost NoClose False Debit Credit OUT Other Nothing+ValueAdded 50 8032 Revenue CloseByDivision False Credit Debit IN Other Keep+SubsidyIncome 51 8033 Revenue CloseByDivision False Credit Debit IN Other Keep+NationalBondInterestEarned 52 8034 Revenue CloseByDivision False Credit Debit IN Other Keep+DepositInterestEarned 53 8035 Revenue CloseByDivision False Credit Debit IN Other Keep+GrossProfit 54 8036 Revenue CloseByDivision False Credit Debit IN Other Keep+OrdinaryProfit 55 8037 Revenue CloseByDivision False Credit Debit IN Other Keep+InterestEarned 56 8038 Revenue CloseByDivision False Credit Debit IN Other Keep+ReceiptFee 57 8039 Revenue CloseByDivision False Credit Debit IN Other Keep+RentalIncome 58 803a Revenue CloseByDivision False Credit Debit IN Other Keep+WageEarned 59 803b Revenue CloseByDivision False Credit Debit IN Other Keep+TaxesRevenue 60 803c Revenue CloseByDivision False Credit Debit IN Other Keep+CentralBankPaymentIncome 61 803d Revenue CloseByDivision False Credit Debit IN Other Keep+Sales 62 803e Revenue CloseByDivision False Credit Debit IN Other Keep+EquityInEarningsOfInvestee 63 803f Revenue CloseByDivision False Credit Debit IN Other Keep+NetLoss 64 8040 Revenue NoClose False Credit Debit IN Other Nothing+PettyCash 65 8041 Assets CloseByDivision False Debit Credit PS Current Nothing+NotesReceivable 66 8042 Assets CloseByDivision False Debit Credit PS Current Nothing+ElectronicallyRecordedReceivable 67 8043 Assets CloseByDivision False Debit Credit PS Current Nothing+CreditCardReceivable 68 8044 Assets CloseByDivision False Debit Credit PS Current Nothing+NotesLoansReceivable 69 8045 Assets CloseByDivision False Debit Credit PS Current Nothing+MerchandiseInventory 70 8046 Assets CloseByDivision False Debit Credit PS Current Nothing+AdvancesPaid 71 8047 Assets CloseByDivision False Debit Credit PS Current Nothing+PrepaidExpenses 72 8048 Assets CloseByDivision False Debit Credit PS Current Nothing+AccruedRevenue 73 8049 Assets CloseByDivision False Debit Credit PS Current Nothing+OtherReceivables 74 804a Assets CloseByDivision False Debit Credit PS Current Nothing+PaymentsOnBehalf 75 804b Assets CloseByDivision False Debit Credit PS Current Nothing+SuspensePayments 76 804c Assets CloseByDivision False Debit Credit PS Current Nothing+ConsumptionTaxPaid 77 804d Assets CloseByDivision False Debit Credit PS Current Nothing+PrepaidCorporateIncomeTaxes 78 804e Assets CloseByDivision False Debit Credit PS Current Nothing+Land 79 804f Assets CloseByDivision False Debit Credit PS Fixed Nothing+Fixtures 80 8050 Assets CloseByDivision False Debit Credit PS Fixed Nothing+Patent 81 8051 Assets CloseByDivision False Debit Credit PS Fixed Nothing+Trademark 82 8052 Assets CloseByDivision False Debit Credit PS Fixed Nothing+Software 83 8053 Assets CloseByDivision False Debit Credit PS Fixed Nothing+CashOverShort 84 8054 Assets CloseByDivision False Debit Credit PS Other Nothing+AccountsPayable 85 8055 Liability CloseByDivision False Credit Debit MS Current Nothing+NotesPayable 86 8056 Liability CloseByDivision False Credit Debit MS Current Nothing+ElectronicallyRecordedObligations 87 8057 Liability CloseByDivision False Credit Debit MS Current Nothing+NotesLoansPayable 88 8058 Liability CloseByDivision False Credit Debit MS Current Nothing+BankOverdraft 89 8059 Liability CloseByDivision False Credit Debit MS Current Nothing+AdvancesReceived 90 805a Liability CloseByDivision False Credit Debit MS Current Nothing+UnearnedRevenue 91 805b Liability CloseByDivision False Credit Debit MS Current Nothing+AccruedExpenses 92 805c Liability CloseByDivision False Credit Debit MS Current Nothing+OtherPayables 93 805d Liability CloseByDivision False Credit Debit MS Current Nothing+DepositsReceived 94 805e Liability CloseByDivision False Credit Debit MS Current Nothing+SuspenseReceipts 95 805f Liability CloseByDivision False Credit Debit MS Current Nothing+ConsumptionTaxReceived 96 8060 Liability CloseByDivision False Credit Debit MS Current Nothing+AccruedConsumptionTax 97 8061 Liability CloseByDivision False Credit Debit MS Current Nothing+AccruedCorporateIncomeTaxes 98 8062 Liability CloseByDivision False Credit Debit MS Current Nothing+UnpaidDividends 99 8063 Liability CloseByDivision False Credit Debit MS Current Nothing+AllowanceForDoubtfulAccounts 100 8064 Assets CloseByDivision True Credit Debit MS Current Nothing+AccumulatedDepreciation 101 8065 Assets CloseByDivision True Credit Debit MS Fixed Nothing+LegalRetainedEarnings 102 8066 Equity CloseByDivision False Credit Debit MS Other Nothing+CumulativeTranslationAdjustment 103 8067 Equity CloseByDivision False Credit Debit MS Other Nothing+ProvisionForDoubtfulAccounts 104 8068 Cost CloseByDivision False Debit Credit OUT Other Flip+BadDebtLoss 105 8069 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfFixedAssets 106 806a Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfNotesReceivable 107 806b Cost CloseByDivision False Debit Credit OUT Other Flip+PaymentFees 108 806c Cost CloseByDivision False Debit Credit OUT Other Flip+MiscellaneousLoss 109 806d Cost CloseByDivision False Debit Credit OUT Other Flip+CorporateIncomeTaxes 110 806e Cost CloseByDivision False Debit Credit OUT Other Flip+CommunicationExpenses 111 806f Cost CloseByDivision False Debit Credit OUT Other Flip+GainOnSalesOfFixedAssets 112 8070 Revenue CloseByDivision False Credit Debit IN Other Keep+RecoveryOfBadDebts 113 8071 Revenue CloseByDivision False Credit Debit IN Other Keep+MiscellaneousIncome 114 8072 Revenue CloseByDivision False Credit Debit IN Other Keep+ReversalOfAllowanceForDoubtfulAccounts 115 8073 Revenue CloseByDivision False Credit Debit IN Other Keep+TimeDeposits 116 8074 Assets CloseByDivision False Debit Credit PS Current Nothing+LoansReceivable 117 8075 Assets CloseByDivision False Debit Credit PS Current Nothing+GiftCertificatesReceived 118 8076 Assets CloseByDivision False Debit Credit PS Current Nothing+SecurityDepositsPaid 119 8077 Assets CloseByDivision False Debit Credit PS Fixed Nothing+SuppliesOnHand 120 8078 Assets CloseByDivision False Debit Credit PS Current Nothing+ContractAssets 121 8079 Assets CloseByDivision False Debit Credit PS Current Nothing+IncomeTaxesRefundReceivable 122 807a Assets CloseByDivision False Debit Credit PS Current Nothing+WorkInProcess 123 807b Assets CloseByDivision False Debit Credit PS Current Nothing+DeferredTaxAssets 124 807c Assets CloseByDivision False Debit Credit PS Fixed Nothing+LeasedAssets 125 807d Assets CloseByDivision False Debit Credit PS Fixed Nothing+ToolsAndInstruments 126 807e Assets CloseByDivision False Debit Credit PS Fixed Nothing+ConstructionInProgress 127 807f Assets CloseByDivision False Debit Credit PS Fixed Nothing+Goodwill 128 8080 Assets CloseByDivision False Debit Credit PS Fixed Nothing+SoftwareInProgress 129 8081 Assets CloseByDivision False Debit Credit PS Fixed Nothing+LongTermPrepaidExpenses 130 8082 Assets CloseByDivision False Debit Credit PS Fixed Nothing+DishonoredNotesReceivable 131 8083 Assets CloseByDivision False Debit Credit PS Current Nothing+PrepaidPensionCost 132 8084 Assets CloseByDivision False Debit Credit PS Fixed Nothing+NetDefinedBenefitAsset 133 8085 Assets CloseByDivision False Debit Credit PS Fixed Nothing+DepositsInSpecialAccounts 134 8086 Assets CloseByDivision False Debit Credit PS Current Nothing+Structures 135 8087 Assets CloseByDivision False Debit Credit PS Fixed Nothing+LeaseholdRights 136 8088 Assets CloseByDivision False Debit Credit PS Fixed Nothing+NonOperatingNotesReceivable 137 8089 Assets CloseByDivision False Debit Credit PS Current Nothing+NonOperatingElectronicallyRecordedReceivable 138 808a Assets CloseByDivision False Debit Credit PS Current Nothing+RefundLiabilities 139 808b Liability CloseByDivision False Credit Debit MS Current Nothing+NonOperatingNotesPayable 140 808c Liability CloseByDivision False Credit Debit MS Current Nothing+NonOperatingElectronicallyRecordedObligations 141 808d Liability CloseByDivision False Credit Debit MS Current Nothing+BonusesPayable 142 808e Liability CloseByDivision False Credit Debit MS Current Nothing+AllowanceForRepairs 143 808f Liability CloseByDivision False Credit Debit MS Current Nothing+AllowanceForProductWarranties 144 8090 Liability CloseByDivision False Credit Debit MS Current Nothing+AllowanceForBonuses 145 8091 Liability CloseByDivision False Credit Debit MS Current Nothing+DeferredTaxLiabilities 146 8092 Liability CloseByDivision False Credit Debit MS Fixed Nothing+LeaseObligations 147 8093 Liability CloseByDivision False Credit Debit MS Fixed Nothing+GuaranteeDepositsReceived 148 8094 Liability CloseByDivision False Credit Debit MS Fixed Nothing+AllowanceForRetirementBenefits 149 8095 Liability CloseByDivision False Credit Debit MS Fixed Nothing+LongTermOtherPayables 150 8096 Liability CloseByDivision False Credit Debit MS Fixed Nothing+NetDefinedBenefitLiability 151 8097 Liability CloseByDivision False Credit Debit MS Fixed Nothing+StockSubscriptionDeposits 152 8098 Equity CloseByDivision False Credit Debit MS Other Nothing+LegalCapitalSurplus 153 8099 Equity CloseByDivision False Credit Debit MS Other Nothing+OtherCapitalSurplus 154 809a Equity CloseByDivision False Credit Debit MS Other Nothing+DividendEqualizationReserve 155 809b Equity CloseByDivision False Credit Debit MS Other Nothing+RepairFundReserve 156 809c Equity CloseByDivision False Credit Debit MS Other Nothing+ConstructionFundReserve 157 809d Equity CloseByDivision False Credit Debit MS Other Nothing+GeneralReserve 158 809e Equity CloseByDivision False Credit Debit MS Other Nothing+ValuationDifferenceOnOtherSecurities 159 809f Equity CloseByDivision False Credit Debit MS Other Nothing+NonControllingInterests 160 80a0 Equity CloseByDivision False Credit Debit MS Other Nothing+CapitalSurplus 161 80a1 Equity CloseByDivision False Credit Debit MS Other Nothing+EarnedSurplus 162 80a2 Equity CloseByDivision False Credit Debit MS Other Nothing+ServiceRevenue 163 80a3 Revenue CloseByDivision False Credit Debit IN Other Keep+OperatingRevenue 164 80a4 Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnSalesOfSecurities 165 80a5 Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnValuationOfSecurities 166 80a6 Revenue CloseByDivision False Credit Debit IN Other Keep+DividendsReceived 167 80a7 Revenue CloseByDivision False Credit Debit IN Other Keep+InterestOnSecurities 168 80a8 Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnSalesOfInvestmentSecurities 169 80a9 Revenue CloseByDivision False Credit Debit IN Other Keep+InsuranceGain 170 80aa Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnBargainPurchase 171 80ab Revenue CloseByDivision False Credit Debit IN Other Keep+ReversalOfAllowanceForRepairs 172 80ac Revenue CloseByDivision False Credit Debit IN Other Keep+ReversalOfAllowanceForProductWarranties 173 80ad Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnDonationOfFixedAssets 174 80ae Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnNationalSubsidies 175 80af Revenue CloseByDivision False Credit Debit IN Other Keep+GainOnConstructionGrants 176 80b0 Revenue CloseByDivision False Credit Debit IN Other Keep+LandRentReceived 177 80b1 Revenue CloseByDivision False Credit Debit IN Other Keep+SalesRebates 178 80b2 Revenue CloseByDivision True Debit Credit OUT Other Flip+CostOfServices 179 80b3 Cost CloseByDivision False Debit Credit OUT Other Flip+OperatingExpenses 180 80b4 Cost CloseByDivision False Debit Credit OUT Other Flip+InventoryShrinkageLoss 181 80b5 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnValuationOfMerchandise 182 80b6 Cost CloseByDivision False Debit Credit OUT Other Flip+Bonuses 183 80b7 Cost CloseByDivision False Debit Credit OUT Other Flip+RetirementBenefitExpenses 184 80b8 Cost CloseByDivision False Debit Credit OUT Other Flip+ProvisionForRepairs 185 80b9 Cost CloseByDivision False Debit Credit OUT Other Flip+ProvisionForBonuses 186 80ba Cost CloseByDivision False Debit Credit OUT Other Flip+ProvisionForProductWarranties 187 80bb Cost CloseByDivision False Debit Credit OUT Other Flip+ResearchAndDevelopmentExpenses 188 80bc Cost CloseByDivision False Debit Credit OUT Other Flip+AmortizationOfGoodwill 189 80bd Cost CloseByDivision False Debit Credit OUT Other Flip+AmortizationOfSoftware 190 80be Cost CloseByDivision False Debit Credit OUT Other Flip+AmortizationOfPatents 191 80bf Cost CloseByDivision False Debit Credit OUT Other Flip+LeaseExpenses 192 80c0 Cost CloseByDivision False Debit Credit OUT Other Flip+IncorporationExpenses 193 80c1 Cost CloseByDivision False Debit Credit OUT Other Flip+StockIssuanceCosts 194 80c2 Cost CloseByDivision False Debit Credit OUT Other Flip+BusinessCommencementExpenses 195 80c3 Cost CloseByDivision False Debit Credit OUT Other Flip+DevelopmentExpenses 196 80c4 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfElectronicallyRecordedReceivables 197 80c5 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfReceivables 198 80c6 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfSecurities 199 80c7 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnValuationOfSecurities 200 80c8 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnSalesOfInvestmentSecurities 201 80c9 Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnFire 202 80ca Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnRetirementOfFixedAssets 203 80cb Cost CloseByDivision False Debit Credit OUT Other Flip+LossOnReductionOfFixedAssets 204 80cc Cost CloseByDivision False Debit Credit OUT Other Flip+AdditionalIncomeTaxesForPriorPeriods 205 80cd Cost CloseByDivision False Debit Credit OUT Other Flip+RefundOfIncomeTaxes 206 80ce Cost CloseByDivision True Credit Debit IN Other Keep+PurchaseRebates 207 80cf Cost CloseByDivision True Credit Debit IN Other Keep+WelfareExpenses 208 80d0 Cost CloseByDivision False Debit Credit OUT Other Flip+MaintenanceExpenses 209 80d1 Cost CloseByDivision False Debit Credit OUT Other Flip+StatutoryWelfareExpenses 210 80d2 Cost CloseByDivision False Debit Credit OUT Other Flip+LandRentPaid 211 80d3 Cost CloseByDivision False Debit Credit OUT Other Flip+InsuranceExpense 212 80d4 Cost CloseByDivision False Debit Credit OUT Other Flip+RepairsExpense 213 80d5 Cost CloseByDivision False Debit Credit OUT Other Flip+StorageExpenses 214 80d6 Cost CloseByDivision False Debit Credit OUT Other Flip+MembershipFees 215 80d7 Cost CloseByDivision False Debit Credit OUT Other Flip+IncomeSummary 216 80d8 Assets NoClose False Debit Credit PS Other Nothing+SuspenseAccount 217 80d9 Assets CloseByDivision False Debit Credit PS Current Nothing+ForeignExchangeGains 218 80da Revenue CloseByDivision False Credit Debit IN Other Keep+ForeignExchangeLosses 219 80db Cost CloseByDivision False Debit Credit OUT Other Flip+ContraAccountForGuaranteeObligations 220 80dc Assets CloseByDivision False Debit Credit PS Other Nothing+GuaranteeObligations 221 80dd Liability CloseByDivision False Credit Debit MS Other Nothing+IncomeTaxesAdjustment 222 80de Cost CloseByDivision False Debit Credit OUT Other Flip+BranchCurrentAccount 223 80df Assets CloseByDivision False Debit Credit PS Other Nothing+HeadOfficeCurrentAccount 224 80e0 Liability CloseByDivision False Credit Debit MS Other Nothing+NetIncomeAttributableToNCI 225 80e1 Cost NoClose False Debit Credit OUT Other Nothing+NetLossAttributableToNCI 226 80e2 Revenue NoClose False Credit Debit IN Other Nothing+TradingSecurities 227 80e3 Assets CloseByDivision False Debit Credit PS Current Nothing+HeldToMaturityBonds 228 80e4 Assets CloseByDivision False Debit Credit PS Fixed Nothing+SubsidiaryStocks 229 80e5 Assets CloseByDivision False Debit Credit PS Fixed Nothing+AffiliateStocks 230 80e6 Assets CloseByDivision False Debit Credit PS Fixed Nothing+AvailableForSaleSecurities 231 80e7 Assets CloseByDivision False Debit Credit PS Fixed Nothing+ConsumptionTaxRefundReceivable 232 80e8 Assets CloseByDivision False Debit Credit PS Current Nothing+PropertyTaxPayable 233 80e9 Liability CloseByDivision False Credit Debit MS Current Nothing+DepositsReceivedFromOfficers 234 80ea Liability CloseByDivision False Credit Debit MS Current Nothing+EntertainmentExpenses 235 80eb Cost CloseByDivision False Debit Credit OUT Other Flip+MeetingExpenses 236 80ec Cost CloseByDivision False Debit Credit OUT Other Flip+NewspaperBooksExpenses 237 80ed Cost CloseByDivision False Debit Credit OUT Other Flip+RawMaterials 238 80ee Assets CloseByDivision False Debit Credit PS Current Nothing+GoodsInTransit 239 80ef Assets CloseByDivision False Debit Credit PS Current Nothing+SalesAllowances 240 80f0 Revenue CloseByDivision True Debit Credit OUT Other Flip+PurchaseAllowances 241 80f1 Cost CloseByDivision True Credit Debit IN Other Keep+ReversalOfAllowanceForBonuses 242 80f2 Revenue CloseByDivision False Credit Debit IN Other Keep
test/fixtures/account-semantics-050/README.md view
@@ -7,14 +7,12 @@ Land 1 baseline the fixture family was first cut from. - `metadata.tsv` freezes role, posting capability, division semantics,- home-side semantics, and reporting eligibility for all 235 concrete titles.+ home-side semantics, and reporting eligibility for all 240 concrete titles. - `account-info.tsv` freezes the LLM-facing projection, including descriptions that distinguish statement classifications from bookkeeping control classes and internal direction encodings. - `suggest.tsv` freezes LLM-facing search after removing legacy placeholder classifications from the search index.-- `consumer-inventory.txt` is the post-Land 1 closed consumer set. The Land 0- inventory remains frozen in `pre-account-semantics-050`. The wildcard `AccountTitle` is intentionally absent and must be rejected by `accountSemantics` and `describeAccount`.@@ -23,6 +21,5 @@ these files. First decide its role, posting capability, division semantics, home-side semantics, and reporting eligibility explicitly. -These fixtures are additive to `pre-account-semantics-050`: the earlier-fixture remains the compatibility oracle for legacy algebra, closing,-projection, and presentation behavior.+The closing, projection-membership, and one-posting statement-row behavior of+every concrete title is pinned in `account-algebra-behavior`.
test/fixtures/account-semantics-050/account-info.tsv view
@@ -99,7 +99,7 @@ AccruedConsumptionTax [OrdinaryAccount] OrdinaryPosting StatementDivision Liability FixedHomeSide Credit StatementEligible Accrued (unpaid) consumption tax 未払消費税 Liability: Accrued (unpaid) consumption tax (未払消費税) AccruedCorporateIncomeTaxes [OrdinaryAccount] OrdinaryPosting StatementDivision Liability FixedHomeSide Credit StatementEligible Accrued (unpaid) corporate income taxes 未払法人税等 Liability: Accrued (unpaid) corporate income taxes (未払法人税等) UnpaidDividends [OrdinaryAccount] OrdinaryPosting StatementDivision Liability FixedHomeSide Credit StatementEligible Unpaid dividends 未払配当金 Liability: Unpaid dividends (未払配当金)-AllowanceForDoubtfulAccounts [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Credit StatementEligible Allowance for doubtful accounts 貸倒引当金 Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\/Not structure is intact. B\/S deduction (net) presentation is the Write side's job.+AllowanceForDoubtfulAccounts [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Credit StatementEligible Allowance for doubtful accounts 貸倒引当金 Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\/Not structure is intact. B\/S deduction (net) presentation is the ExchangeAlgebra.IO.Output module's job. AccumulatedDepreciation [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Credit StatementEligible Accumulated depreciation 減価償却累計額 Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name. LegalRetainedEarnings [OrdinaryAccount] OrdinaryPosting StatementDivision Equity FixedHomeSide Credit StatementEligible Legal (appropriated) retained earnings reserve 利益準備金 Equity: Legal (appropriated) retained earnings reserve (利益準備金) CumulativeTranslationAdjustment [OrdinaryAccount] ConsolidationOnly StatementDivision Equity FixedHomeSide Credit ContextualPresentation Cumulative translation adjustment 為替換算調整勘定 Consolidation adjustment: cumulative translation adjustment (為替換算調整勘定). Available only in consolidation worksheets.@@ -239,3 +239,6 @@ NewspaperBooksExpenses [OrdinaryAccount] OrdinaryPosting StatementDivision Cost FixedHomeSide Debit StatementEligible Newspapers and books 新聞図書費 Expense: Newspapers and books (新聞図書費) RawMaterials [OrdinaryAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Debit StatementEligible Raw materials 原材料 Assets: Raw materials (原材料) GoodsInTransit [OrdinaryAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Debit StatementEligible Goods in transit 未着品 Assets: Goods in transit (未着品)+SalesAllowances [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Revenue FixedHomeSide Debit StatementEligible Sales allowances 売上値引 Revenue: Sales allowances (売上値引)+PurchaseAllowances [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Cost FixedHomeSide Credit StatementEligible Purchase allowances 仕入値引 Cost: Purchase allowances (仕入値引)+ReversalOfAllowanceForBonuses [OrdinaryAccount] OrdinaryPosting StatementDivision Revenue FixedHomeSide Credit StatementEligible Reversal of provision for bonuses 賞与引当金戻入 Revenue: Reversal of provision for bonuses (賞与引当金戻入)
− test/fixtures/account-semantics-050/consumer-inventory.txt
@@ -1,48 +0,0 @@-examples/audit-eval/gen/DeriveEA.hs-examples/audit-eval/gen/accounts.py-examples/audit-eval/harness/EmitCanonical.hs-examples/audit-eval/harness/LoadChecked.hs-examples/audit-eval/oracle/Oracle.hs-examples/audit-eval/runner/arms.py-examples/audit-eval/runner/build.py-examples/basic/elementaryBookkeepingEx2.hs-examples/basic/elementaryBookkeepingEx3.hs-examples/basic/elementaryBookkeepingEx4.hs-examples/basic/elementaryBookkeepingEx9.hs-examples/basic/simulateEx1.hs-examples/basic/simulateEx2.hs-examples/basic/simulateEx2Fast.hs-examples/benchmark/Bench.hs-examples/deterministic/ripple/RippleEffect.hs-examples/deterministic/ripple/ripple.hs-examples/deterministic/ripple/rippleWithStock.hs-examples/deterministic/ripple/rippleWithStockMultiSeeds.hs-examples/market/MarketModel.hs-examples/optimization/CGE/CGE.hs-examples/optimization/CGE/stdcge.hs-examples/optimization/cge-lite/Model.hs-src/ExchangeAlgebra/Algebra.hs-src/ExchangeAlgebra/Algebra/Base.hs-src/ExchangeAlgebra/Algebra/Base/Account/Registry.hs-src/ExchangeAlgebra/Algebra/Base/Account/Types.hs-src/ExchangeAlgebra/Algebra/Base/Element.hs-src/ExchangeAlgebra/Algebra/Transfer.hs-src/ExchangeAlgebra/Assist.hs-src/ExchangeAlgebra/Bookkeeping.hs-src/ExchangeAlgebra/Consolidation/Worksheet.hs-src/ExchangeAlgebra/Convert.hs-src/ExchangeAlgebra/Convert/Checked.hs-src/ExchangeAlgebra/Journal.hs-src/ExchangeAlgebra/Journal/Transfer.hs-src/ExchangeAlgebra/Reporting/Metric.hs-src/ExchangeAlgebra/Reporting/Presentation.hs-src/ExchangeAlgebra/Simulate/Lite.hs-src/ExchangeAlgebra/TrialBalance/Validation.hs-src/ExchangeAlgebra/Write.hs-test/Spec.hs-tools/DumpAccountMetadata.hs-tools/DumpAccountSemanticsGolden.hs-tools/DumpGolden.hs-tools/DumpPostVocabGolden.hs-tools/DumpVocabGolden.hs-tools/ProbeFinalStock.hs
test/fixtures/account-semantics-050/metadata.tsv view
@@ -239,3 +239,6 @@ NewspaperBooksExpenses [OrdinaryAccount] OrdinaryPosting StatementDivision Cost FixedHomeSide Debit StatementEligible RawMaterials [OrdinaryAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Debit StatementEligible GoodsInTransit [OrdinaryAccount] OrdinaryPosting StatementDivision Assets FixedHomeSide Debit StatementEligible+SalesAllowances [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Revenue FixedHomeSide Debit StatementEligible+PurchaseAllowances [OrdinaryAccount,ContraAccount] OrdinaryPosting StatementDivision Cost FixedHomeSide Credit StatementEligible+ReversalOfAllowanceForBonuses [OrdinaryAccount] OrdinaryPosting StatementDivision Revenue FixedHomeSide Credit StatementEligible
test/fixtures/account-semantics-050/suggest.tsv view
@@ -36,6 +36,7 @@ (交際費) 1 EntertainmentExpenses (仕入) 1 Purchases (仕入\/売上\/繰越商品) 1 Products+(仕入値引) 1 PurchaseAllowances (仕入割戻) 1 PurchaseRebates (仕掛品) 1 WorkInProcess (付加価値。SNA\/マクロ系) 1 ValueAdded@@ -104,6 +105,7 @@ (国庫補助金受贈益) 1 GainOnNationalSubsidies (土地) 1 Land (売上) 1 Sales+(売上値引) 1 SalesAllowances (売上割戻) 1 SalesRebates (売上原価) 1 SalesCost (売上総利益). 1 GrossProfit@@ -232,6 +234,7 @@ (資本金) 1 CapitalStock (賞与) 1 Bonuses (賞与引当金) 1 AllowanceForBonuses+(賞与引当金戻入) 1 ReversalOfAllowanceForBonuses (賞与引当金繰入) 1 ProvisionForBonuses (車両運搬具) 1 Vehicle (返金負債) 1 RefundLiabilities@@ -278,9 +281,9 @@ Accrued 5 AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,BonusesPayable Accrued (unpaid) consumption tax 21 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,ConsumptionTaxRefundReceivable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes Accrued (unpaid) corporate income taxes 23 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome-Accrued bonuses 8 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Accrued bonuses 9 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses Accrued expenses 29 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-Accrued revenue 42 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Accrued revenue 44 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome AccruedConsumptionTax 1 AccruedConsumptionTax AccruedCorporateIncomeTaxes 1 AccruedCorporateIncomeTaxes AccruedExpenses 1 AccruedExpenses@@ -289,7 +292,7 @@ Accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense AccumulatedDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation Additional 1 AdditionalIncomeTaxesForPriorPeriods-Additional income taxes for prior periods 49 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense+Additional income taxes for prior periods 50 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense AdditionalIncomeTaxesForPriorPeriods 1 AdditionalIncomeTaxesForPriorPeriods Advances 2 AdvancesPaid,AdvancesReceived Advances paid 13 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees,SecurityDepositsPaid@@ -299,18 +302,18 @@ Advertising 1 AdvertisingExpense AdvertisingExpense 1 AdvertisingExpense AffiliateStocks 1 AffiliateStocks-Allowance 8 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-Allowance for doubtful accounts 36 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,AccountsReceivable-AllowanceForBonuses 1 AllowanceForBonuses+Allowance 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SalesAllowances,PurchaseAllowances+Allowance for doubtful accounts 39 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+AllowanceForBonuses 2 AllowanceForBonuses,ReversalOfAllowanceForBonuses AllowanceForDoubtfulAccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts AllowanceForProductWarranties 2 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties AllowanceForRepairs 2 AllowanceForRepairs,ReversalOfAllowanceForRepairs AllowanceForRetirementBenefits 1 AllowanceForRetirementBenefits Amortization 4 AmortizationExpense,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents-Amortization expense for intangibles 72 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-Amortization of goodwill 42 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets-Amortization of patent rights 43 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software-Amortization of software 41 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee+Amortization expense for intangibles 73 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense+Amortization of goodwill 43 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets+Amortization of patent rights 44 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software+Amortization of software 42 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee AmortizationExpense 1 AmortizationExpense AmortizationOfGoodwill 1 AmortizationOfGoodwill AmortizationOfPatents 1 AmortizationOfPatents@@ -330,7 +333,7 @@ Bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft Bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome BankOverdraft 1 BankOverdraft-Bonuses 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Bonuses 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses BonusesPayable 1 BonusesPayable Branch current account 25 BranchCurrentAccount,HeadOfficeCurrentAccount,CurrentDeposits,AccountsReceivable,ReserveDepositPayable,MerchandiseInventory,PrepaidExpenses,AccruedRevenue,SuspensePayments,CashOverShort BranchCurrentAccount 1 BranchCurrentAccount@@ -374,7 +377,7 @@ ConsumptionTaxReceived 1 ConsumptionTaxReceived ConsumptionTaxRefundReceivable 1 ConsumptionTaxRefundReceivable Contra 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ContractAssets,ContraAccountForGuaranteeObligations-Contra account for guarantee obligations 50 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,SuspenseAccount,GuaranteeObligations+Contra account for guarantee obligations 51 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,SuspenseAccount,GuaranteeObligations ContraAccountForGuaranteeObligations 1 ContraAccountForGuaranteeObligations Contract 1 ContractAssets Contract assets 40 ContractAssets,SuspensePayments,AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand@@ -382,10 +385,10 @@ Corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes Corporate income taxes 19 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome,SubsidyIncome CorporateIncomeTaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Cost 51 SalesCost,NetIncome,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses-Cost of sales 76 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-Cost of services 72 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents-Cost: 47 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,CostOfServices,OperatingExpenses+Cost 52 SalesCost,NetIncome,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses+Cost of sales 79 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+Cost of services 74 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents+Cost: 48 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,CostOfServices,OperatingExpenses CostOfServices 1 CostOfServices Credit 4 CreditCardReceivable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts Credit card receivable 21 CreditCardReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables@@ -408,7 +411,7 @@ DepositPayable 2 DepositPayable,ReserveDepositPayable Deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived Deposits accepted 12 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived-Deposits for subscriptions of shares 76 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,Deposits,CurrentDeposits+Deposits for subscriptions of shares 77 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,ReversalOfAllowanceForBonuses,Deposits Deposits in special accounts 102 DepositsInSpecialAccounts,Deposits,ReserveDepositPayable,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,CurrentDeposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery Deposits received 18 DepositPayable,DepositsReceived,GuaranteeDepositsReceived,DepositsReceivedFromOfficers,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived Deposits received from officers 20 DepositsReceivedFromOfficers,DepositPayable,DepositsReceived,GuaranteeDepositsReceived,LandRentReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived@@ -441,29 +444,30 @@ Equipment 3 Machinery,EquipmentInvestment,Fixtures Equipment investment 8 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities EquipmentInvestment 1 EquipmentInvestment-Equity in earnings of investee 120 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,GrossProfit,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts+Equity in earnings of investee 121 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,GrossProfit,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts Equity: 14 CapitalStock,RetainedEarnings,LegalRetainedEarnings,StockSubscriptionDeposits,LegalCapitalSurplus,OtherCapitalSurplus,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,GeneralReserve EquityInEarningsOfInvestee 1 EquityInEarningsOfInvestee+ExchangeAlgebra.IO.Output 1 AllowanceForDoubtfulAccounts Expense: 21 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses Explicitly 1 CommunicationExpenses Fixtures 1 Fixtures Fixtures and equipment 19 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand-For 31 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts+For 32 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts Foreign 2 ForeignExchangeGains,ForeignExchangeLosses-Foreign exchange gains 2 ForeignExchangeGains,ForeignExchangeLosses-Foreign exchange losses 2 ForeignExchangeLosses,ForeignExchangeGains+Foreign exchange gains 3 ForeignExchangeGains,ForeignExchangeLosses,AllowanceForDoubtfulAccounts+Foreign exchange losses 3 ForeignExchangeLosses,ForeignExchangeGains,AllowanceForDoubtfulAccounts ForeignExchangeGains 1 ForeignExchangeGains ForeignExchangeLosses 1 ForeignExchangeLosses Gain 10 GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants,ForeignExchangeGains-Gain on bargain purchase 100 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants-Gain on contribution received for construction 110 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation-Gain on insurance claims 99 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies-Gain on receipt of donated non-current assets 130 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GrossProfit-Gain on receipt of national subsidies 112 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-Gain on sale of investment securities 120 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-Gain on sale of securities 117 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-Gain on sales of fixed assets 131 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies-Gain on valuation of securities 115 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+Gain on bargain purchase 102 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants+Gain on contribution received for construction 111 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation+Gain on insurance claims 100 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies+Gain on receipt of donated non-current assets 131 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GrossProfit+Gain on receipt of national subsidies 113 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable+Gain on sale of investment securities 122 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+Gain on sale of securities 119 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+Gain on sales of fixed assets 133 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies+Gain on valuation of securities 116 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities GainOnBargainPurchase 1 GainOnBargainPurchase GainOnConstructionGrants 1 GainOnConstructionGrants GainOnDonationOfFixedAssets 1 GainOnDonationOfFixedAssets@@ -516,7 +520,7 @@ Interest 9 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,LeaseholdRights,NonControllingInterests,InterestOnSecurities,NetIncomeAttributableToNCI,NetLossAttributableToNCI Interest earned 12 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue,LeaseholdRights,NonControllingInterests,EarnedSurplus,InterestOnSecurities Interest expense 48 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-Interest on securities 105 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+Interest on securities 106 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities InterestEarned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned InterestExpense 1 InterestExpense InterestOnSecurities 1 InterestOnSecurities@@ -555,9 +559,9 @@ Liability: 39 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable Loans 8 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable,LoansReceivable Loans payable 20 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Loans payable on notes 109 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable+Loans payable on notes 110 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable Loans receivable 23 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesReceivable-Loans receivable on notes 109 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable+Loans receivable on notes 110 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable LoansPayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable LoansReceivable 4 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable Long-term 6 LongTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,LongTermNationalBondsPayable,LongTermPrepaidExpenses,LongTermOtherPayables@@ -575,16 +579,16 @@ LongTermPrepaidExpenses 1 LongTermPrepaidExpenses Loss 17 NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss,InventoryShrinkageLoss,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities Loss attributable to non-controlling interests 38 NetLossAttributableToNCI,NetIncomeAttributableToNCI,NonControllingInterests,InventoryShrinkageLoss,LossOnRetirementOfFixedAssets,Products,StockInvestment,CapitalStock,CentralBankPaymentExpense,GrossProfit-Loss on fire 101 LossOnFire,NetLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities-Loss on retirement of non-current assets 134 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-Loss on sale of electronically recorded monetary claims 117 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets-Loss on sale of investment securities 123 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-Loss on sale of receivables 118 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise-Loss on sale of securities 120 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-Loss on sales of fixed assets 134 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities-Loss on sales of notes receivable 127 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable-Loss on tax purpose reduction entry of non-current assets 140 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-Loss on valuation of securities 118 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities+Loss on fire 102 LossOnFire,NetLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+Loss on retirement of non-current assets 135 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+Loss on sale of electronically recorded monetary claims 119 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets+Loss on sale of investment securities 125 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+Loss on sale of receivables 120 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise+Loss on sale of securities 122 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+Loss on sales of fixed assets 136 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities+Loss on sales of notes receivable 129 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable+Loss on tax purpose reduction entry of non-current assets 141 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+Loss on valuation of securities 119 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities LossOnFire 1 LossOnFire LossOnReductionOfFixedAssets 1 LossOnReductionOfFixedAssets LossOnRetirementOfFixedAssets 1 LossOnRetirementOfFixedAssets@@ -649,7 +653,7 @@ NotesReceivable 4 NotesReceivable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable Operating 6 NonOperatingNotesReceivable,NonOperatingElectronicallyRecordedReceivable,NonOperatingNotesPayable,NonOperatingElectronicallyRecordedObligations,OperatingRevenue,OperatingExpenses Operating expenses 30 OperatingExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses,LongTermPrepaidExpenses,NonOperatingNotesReceivable-Operating revenue 43 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Operating revenue 45 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome OperatingExpenses 1 OperatingExpenses OperatingRevenue 1 OperatingRevenue Ordinary profit 3 OrdinaryProfit,GrossProfit,NetIncomeAttributableToNCI@@ -668,7 +672,7 @@ Payment fees / fees paid 35 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,MembershipFees,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable PaymentFees 2 ReceiptFee,PaymentFees Payments 2 PaymentsOnBehalf,SuspensePayments-Payments made on behalf 97 PaymentsOnBehalf,SuspensePayments,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation+Payments made on behalf 98 PaymentsOnBehalf,SuspensePayments,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation PaymentsOnBehalf 1 PaymentsOnBehalf Period 4 NetIncome,NetLoss,MerchandiseInventory,AdditionalIncomeTaxesForPriorPeriods Petty 1 PettyCash@@ -686,18 +690,20 @@ Property 1 PropertyTaxPayable Property tax payable 32 PropertyTaxPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,TaxesExpense PropertyTaxPayable 1 PropertyTaxPayable-Provision 10 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-Provision for bonuses 33 AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForProductWarranties-Provision for doubtful accounts 36 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs-Provision for repairs 32 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses-Provision for retirement benefits 35 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-Provision for warranties 31 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses+Provision 11 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+Provision for bonuses 34 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+Provision for doubtful accounts 37 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+Provision for repairs 33 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses+Provision for retirement benefits 36 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+Provision for warranties 32 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses ProvisionForBonuses 1 ProvisionForBonuses ProvisionForDoubtfulAccounts 1 ProvisionForDoubtfulAccounts ProvisionForProductWarranties 1 ProvisionForProductWarranties ProvisionForRepairs 1 ProvisionForRepairs-Purchase 4 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates-Purchase rebates 5 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates+Purchase 5 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,PurchaseAllowances+Purchase allowances 6 PurchaseAllowances,Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,SalesAllowances+Purchase rebates 6 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates,PurchaseAllowances+PurchaseAllowances 1 PurchaseAllowances PurchaseRebates 1 PurchaseRebates Purchases 2 Purchases,MerchandiseInventory Purchases\/Sales\/MerchandiseInventory). 1 MerchandiseInventory@@ -711,15 +717,15 @@ ReceiptFee 2 ReceiptFee,PaymentFees Reciprocal 2 BranchCurrentAccount,HeadOfficeCurrentAccount Recovery 1 RecoveryOfBadDebts-Recovery of bad debts written off 41 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,BranchCurrentAccount,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost+Recovery of bad debts written off 42 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,BranchCurrentAccount,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost RecoveryOfBadDebts 1 RecoveryOfBadDebts Refund 4 IncomeTaxesRefundReceivable,RefundLiabilities,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable Refund liabilities 6 RefundLiabilities,IncomeTaxesRefundReceivable,DeferredTaxLiabilities,LeaseObligations,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable-Refund of income taxes 59 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense+Refund of income taxes 60 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense RefundLiabilities 1 RefundLiabilities RefundOfIncomeTaxes 1 RefundOfIncomeTaxes Rent 10 CurrentDeposits,RentExpense,RentalIncome,GainOnDonationOfFixedAssets,LandRentReceived,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LandRentPaid,BranchCurrentAccount,HeadOfficeCurrentAccount-Rent expenses on land 125 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits+Rent expenses on land 126 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits RentExpense 1 RentExpense Rental 2 RentalIncome,LandRentReceived Rental income 17 RentalIncome,LandRentReceived,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome@@ -735,10 +741,10 @@ Reserve 9 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,GeneralReserve Reserve deposits, asset side 87 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate Reserve deposits, liability side 48 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-Reserve for depreciation 36 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory-Reserve for dividend equalization 37 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,SuspensePayments-Reserve for new construction 37 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory-Reserve for repairs 36 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable+Reserve for depreciation 37 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory+Reserve for dividend equalization 38 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,SuspensePayments+Reserve for new construction 38 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory+Reserve for repairs 37 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable ReserveDepositPayable 1 ReserveDepositPayable ReserveDepositReceivable 1 ReserveDepositReceivable ReserveForDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation@@ -750,19 +756,23 @@ Retirement benefit expenses 29 RetirementBenefitExpenses,NetDefinedBenefitAsset,AllowanceForRetirementBenefits,NetDefinedBenefitLiability,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses Retirement benefit liability 43 AllowanceForRetirementBenefits,NetDefinedBenefitLiability,NetDefinedBenefitAsset,RetirementBenefitExpenses,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable RetirementBenefitExpenses 1 RetirementBenefitExpenses-Revenue 38 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Revenue: 32 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,ReceiptFee,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-Reversal 3 ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-Reversal of allowance for doubtful accounts 71 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-Reversal of provision for repairs 67 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-Reversal of provision for warranties 66 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits+Revenue 40 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned+Revenue: 34 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,ReceiptFee,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome+Reversal 4 ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+Reversal of allowance for doubtful accounts 74 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses+Reversal of provision for bonuses 69 ReversalOfAllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties+Reversal of provision for repairs 68 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses+Reversal of provision for warranties 67 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses+ReversalOfAllowanceForBonuses 1 ReversalOfAllowanceForBonuses ReversalOfAllowanceForDoubtfulAccounts 1 ReversalOfAllowanceForDoubtfulAccounts ReversalOfAllowanceForProductWarranties 1 ReversalOfAllowanceForProductWarranties ReversalOfAllowanceForRepairs 1 ReversalOfAllowanceForRepairs SNA\/macro-accounting 1 AccumulatedDepreciation SNA\/マクロ系の旧称。簿記の間接法には 1 ReserveForDepreciation-Sales 14 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables-Sales rebates 15 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+Sales 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables+Sales allowances 16 SalesAllowances,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+Sales rebates 16 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+SalesAllowances 1 SalesAllowances SalesCost 1 SalesCost SalesRebates 1 SalesRebates Savings 1 Deposits@@ -770,13 +780,13 @@ Securities 12 Securities,InvestmentSecurities,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnValuationOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities SecurityDepositsPaid 1 SecurityDepositsPaid Service 3 GovernmentService,ServiceRevenue,CostOfServices-Service revenue 40 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+Service revenue 42 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee ServiceRevenue 1 ServiceRevenue Share 4 StockSubscriptionDeposits,StockIssuanceCosts,SubsidiaryStocks,AffiliateStocks Share issuance costs 6 StockIssuanceCosts,PrepaidPensionCost,StockSubscriptionDeposits,StorageExpenses,SubsidiaryStocks,AffiliateStocks Shares 3 StockSubscriptionDeposits,SubsidiaryStocks,AffiliateStocks-Shares of associates 40 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable-Shares of subsidiaries 40 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+Shares of associates 41 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+Shares of subsidiaries 41 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable Short-term 4 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable Short-term loans payable 21 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable Short-term loans receivable 25 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,LoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable@@ -815,7 +825,7 @@ SuspensePayments 1 SuspensePayments SuspenseReceipts 1 SuspenseReceipts Tax 17 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,DeferredTaxAssets-Tax revenue 54 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+Tax revenue 56 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee Taxes 9 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment TaxesExpense 1 TaxesExpense TaxesRevenue 1 TaxesRevenue@@ -834,17 +844,17 @@ Travel 1 BusinessTrip Travel and transportation 18 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments Unearned 1 UnearnedRevenue-Unearned revenue 38 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Unearned revenue 40 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome UnearnedRevenue 1 UnearnedRevenue Unpaid 3 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends Unpaid dividends 4 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,DividendsReceived UnpaidDividends 1 UnpaidDividends-Use 9 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Use 10 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses Utilities 1 UtilitiesExpense UtilitiesExpense 1 UtilitiesExpense Valuation 6 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnValuationOfSecurities-Valuation difference on available-for-sale securities 101 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities-Valuation loss on goods 102 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,NetLoss,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables+Valuation difference on available-for-sale securities 102 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+Valuation loss on goods 103 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,NetLoss,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables ValuationDifferenceOnOtherSecurities 1 ValuationDifferenceOnOtherSecurities Value 12 NetIncome,ValueAdded,GrossProfit,OrdinaryProfit,NetLoss,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,IncomeSummary,SuspenseAccount Value added 12 ValueAdded,NetIncome,GrossProfit,OrdinaryProfit,NetLoss,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,IncomeSummary,SuspenseAccount@@ -862,8 +872,7 @@ Work 5 EquityInEarningsOfInvestee,CumulativeTranslationAdjustment,WorkInProcess,NetIncomeAttributableToNCI,NetLossAttributableToNCI Work in process 88 WorkInProcess,EquityInEarningsOfInvestee,CumulativeTranslationAdjustment,NetIncomeAttributableToNCI,NetLossAttributableToNCI,Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery WorkInProcess 1 WorkInProcess-Write 1 AllowanceForDoubtfulAccounts-a 221 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery+a 224 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery accepted 1 DepositPayable account 21 AccountsReceivable,ReserveDepositPayable,MerchandiseInventory,PrepaidExpenses,AccruedRevenue,SuspensePayments,CashOverShort,AccountsPayable,UnearnedRevenue,AccruedExpenses account: 6 SuspensePayments,CashOverShort,SuspenseReceipts,SuspenseAccount,BranchCurrentAccount,HeadOfficeCurrentAccount@@ -875,9 +884,9 @@ accrual 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses accrued (unpaid) consumption tax 21 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,ConsumptionTaxRefundReceivable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes accrued (unpaid) corporate income taxes 23 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome-accrued bonuses 8 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses+accrued bonuses 9 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses accrued expenses 29 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-accrued revenue 42 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+accrued revenue 44 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome accruedconsumptiontax 1 AccruedConsumptionTax accruedcorporateincometaxes 1 AccruedCorporateIncomeTaxes accruedexpenses 1 AccruedExpenses@@ -886,7 +895,7 @@ accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense accumulateddepreciation 2 ReserveForDepreciation,AccumulatedDepreciation added 1 ValueAdded-additional income taxes for prior periods 49 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense+additional income taxes for prior periods 50 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense additionalincometaxesforpriorperiods 1 AdditionalIncomeTaxesForPriorPeriods adjustment 2 CumulativeTranslationAdjustment,IncomeTaxesAdjustment adjustment: 1 CumulativeTranslationAdjustment@@ -897,26 +906,27 @@ advertising 1 AdvertisingExpense advertisingexpense 1 AdvertisingExpense affiliatestocks 1 AffiliateStocks-allowance 8 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-allowance for doubtful accounts 36 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,AccountsReceivable-allowanceforbonuses 1 AllowanceForBonuses+allowance 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SalesAllowances,PurchaseAllowances+allowance for doubtful accounts 39 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+allowanceforbonuses 2 AllowanceForBonuses,ReversalOfAllowanceForBonuses allowancefordoubtfulaccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts allowanceforproductwarranties 2 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties allowanceforrepairs 2 AllowanceForRepairs,ReversalOfAllowanceForRepairs allowanceforretirementbenefits 1 AllowanceForRetirementBenefits-amortization expense for intangibles 72 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-amortization of goodwill 42 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets-amortization of patent rights 43 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software-amortization of software 41 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee+allowances 2 SalesAllowances,PurchaseAllowances+amortization expense for intangibles 73 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense+amortization of goodwill 43 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets+amortization of patent rights 44 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software+amortization of software 42 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee amortizationexpense 1 AmortizationExpense amortizationofgoodwill 1 AmortizationOfGoodwill amortizationofpatents 1 AmortizationOfPatents amortizationofsoftware 1 AmortizationOfSoftware amount 1 InvestmentInAssociate-an 69 Products,Machinery,LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,CentralBankNotePayable,AmortizationExpense,BusinessTrip+an 72 Products,Machinery,LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,CentralBankNotePayable,AmortizationExpense,BusinessTrip and 18 Products,Machinery,BusinessTrip,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments are 12 Software,CashOverShort,SoftwareInProgress,StockSubscriptionDeposits,AmortizationOfSoftware,StockIssuanceCosts,WelfareExpenses,StatutoryWelfareExpenses,BranchCurrentAccount,HeadOfficeCurrentAccount-as 92 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery+as 93 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery asset 80 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery assets 40 SuspensePayments,AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand,ContractAssets assets. 1 AccumulatedDepreciation@@ -947,7 +957,7 @@ benefits 1 AllowanceForRetirementBenefits bond 6 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned,HeldToMaturityBonds bonds 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,HeldToMaturityBonds-bonuses 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+bonuses 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses bonusespayable 1 BonusesPayable bookkeeping 4 SuspensePayments,SuspenseReceipts,AccumulatedDepreciation,SuspenseAccount books 3 BranchCurrentAccount,HeadOfficeCurrentAccount,NewspaperBooksExpenses@@ -1004,7 +1014,7 @@ consumptiontaxreceived 1 ConsumptionTaxReceived consumptiontaxrefundreceivable 1 ConsumptionTaxRefundReceivable contra 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ContractAssets,ContraAccountForGuaranteeObligations-contra account for guarantee obligations 50 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,SuspenseAccount,GuaranteeObligations+contra account for guarantee obligations 51 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,SuspenseAccount,GuaranteeObligations contraaccountforguaranteeobligations 1 ContraAccountForGuaranteeObligations contract assets 40 ContractAssets,SuspensePayments,AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand contractassets 1 ContractAssets@@ -1014,8 +1024,8 @@ corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes corporate income taxes 19 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome,SubsidyIncome corporateincometaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-cost of sales 76 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-cost of services 72 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents+cost of sales 79 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+cost of services 74 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents costofservices 1 CostOfServices costs 3 PrepaidPensionCost,StockIssuanceCosts,StorageExpenses counterpart 3 PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts@@ -1047,7 +1057,7 @@ depositpayable 2 DepositPayable,ReserveDepositPayable deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived deposits accepted 12 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived-deposits for subscriptions of shares 76 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,Deposits,CurrentDeposits+deposits for subscriptions of shares 77 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,ReversalOfAllowanceForBonuses,Deposits deposits in special accounts 102 DepositsInSpecialAccounts,Deposits,ReserveDepositPayable,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,CurrentDeposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery deposits received 18 DepositPayable,DepositsReceived,GuaranteeDepositsReceived,DepositsReceivedFromOfficers,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived deposits received from officers 20 DepositsReceivedFromOfficers,DepositPayable,DepositsReceived,GuaranteeDepositsReceived,LandRentReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived@@ -1096,13 +1106,13 @@ equipment investment 8 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities equipmentinvestment 1 EquipmentInvestment equity 16 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,StockSubscriptionDeposits,LegalCapitalSurplus,OtherCapitalSurplus,DividendEqualizationReserve,RepairFundReserve-equity in earnings of investee 120 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,GrossProfit,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts+equity in earnings of investee 121 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,GrossProfit,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts equityinearningsofinvestee 1 EquityInEarningsOfInvestee estate 1 Building estimated 1 ReversalOfAllowanceForDoubtfulAccounts examples 1 ReserveDepositPayable excess 1 ReversalOfAllowanceForDoubtfulAccounts-exchange 2 ForeignExchangeGains,ForeignExchangeLosses+exchange 3 AllowanceForDoubtfulAccounts,ForeignExchangeGains,ForeignExchangeLosses existing 2 AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts expenditure 3 WageExpenditure,ConsumptionExpenditure,SubsidyExpense expense 40 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses@@ -1114,21 +1124,21 @@ fixed 5 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets fixtures 1 Fixtures fixtures and equipment 19 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand-for 31 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-foreign exchange gains 2 ForeignExchangeGains,ForeignExchangeLosses-foreign exchange losses 2 ForeignExchangeLosses,ForeignExchangeGains+for 32 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts+foreign exchange gains 3 ForeignExchangeGains,ForeignExchangeLosses,AllowanceForDoubtfulAccounts+foreign exchange losses 3 ForeignExchangeLosses,ForeignExchangeGains,AllowanceForDoubtfulAccounts foreignexchangegains 1 ForeignExchangeGains foreignexchangelosses 1 ForeignExchangeLosses from 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LandRentReceived,DepositsReceivedFromOfficers-gain on bargain purchase 100 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants-gain on contribution received for construction 110 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation-gain on insurance claims 99 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies-gain on receipt of donated non-current assets 130 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GrossProfit-gain on receipt of national subsidies 112 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-gain on sale of investment securities 120 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-gain on sale of securities 117 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-gain on sales of fixed assets 131 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies-gain on valuation of securities 115 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+gain on bargain purchase 102 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants+gain on contribution received for construction 111 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,SuspensePayments,SuspenseReceipts,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation+gain on insurance claims 100 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies+gain on receipt of donated non-current assets 131 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GrossProfit+gain on receipt of national subsidies 113 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable+gain on sale of investment securities 122 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+gain on sale of securities 119 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+gain on sales of fixed assets 133 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies+gain on valuation of securities 116 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities gainonbargainpurchase 1 GainOnBargainPurchase gainonconstructiongrants 1 GainOnConstructionGrants gainondonationoffixedassets 1 GainOnDonationOfFixedAssets@@ -1184,7 +1194,7 @@ interest 9 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,LeaseholdRights,NonControllingInterests,InterestOnSecurities,NetIncomeAttributableToNCI,NetLossAttributableToNCI interest earned 12 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue,LeaseholdRights,NonControllingInterests,EarnedSurplus,InterestOnSecurities interest expense 48 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-interest on securities 105 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+interest on securities 106 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities interestearned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned interestexpense 1 InterestExpense interestonsecurities 1 InterestOnSecurities@@ -1199,7 +1209,7 @@ investment securities 15 InvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnValuationOfSecurities investmentinassociate 1 InvestmentInAssociate investmentsecurities 3 InvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities-is 36 Products,AdvertisingExpense,MiscellaneousExpenses,NetIncome,GrossProfit,OrdinaryProfit,NetLoss,MerchandiseInventory,SuspensePayments,CashOverShort+is 37 Products,AdvertisingExpense,MiscellaneousExpenses,NetIncome,GrossProfit,OrdinaryProfit,NetLoss,MerchandiseInventory,SuspensePayments,CashOverShort issuance 1 StockIssuanceCosts it 98 Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,ReserveDepositReceivable,CapitalStock,RetainedEarnings,LongTermLoansPayable,ShortTermLoansPayable job. 1 AllowanceForDoubtfulAccounts@@ -1224,9 +1234,9 @@ liability 40 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable loans 8 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable,LoansReceivable loans payable 20 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-loans payable on notes 109 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable+loans payable on notes 110 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable loans receivable 23 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesReceivable-loans receivable on notes 109 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable+loans receivable on notes 110 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable loanspayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable loansreceivable 4 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable long-term accounts payable - other 50 LongTermOtherPayables,LongTermLoansPayable,LongTermNationalBondsPayable,LongTermNationalBonds,LongTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,AccountsPayable,OtherPayables@@ -1243,16 +1253,16 @@ longtermprepaidexpenses 1 LongTermPrepaidExpenses loss 17 NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss,InventoryShrinkageLoss,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities loss attributable to non-controlling interests 38 NetLossAttributableToNCI,NetIncomeAttributableToNCI,NonControllingInterests,InventoryShrinkageLoss,LossOnRetirementOfFixedAssets,Products,StockInvestment,CapitalStock,CentralBankPaymentExpense,GrossProfit-loss on fire 101 LossOnFire,NetLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities-loss on retirement of non-current assets 134 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-loss on sale of electronically recorded monetary claims 117 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets-loss on sale of investment securities 123 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-loss on sale of receivables 118 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise-loss on sale of securities 120 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-loss on sales of fixed assets 134 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities-loss on sales of notes receivable 127 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable-loss on tax purpose reduction entry of non-current assets 140 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-loss on valuation of securities 118 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities+loss on fire 102 LossOnFire,NetLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+loss on retirement of non-current assets 135 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+loss on sale of electronically recorded monetary claims 119 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets+loss on sale of investment securities 125 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+loss on sale of receivables 120 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise+loss on sale of securities 122 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+loss on sales of fixed assets 136 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities+loss on sales of notes receivable 129 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable+loss on tax purpose reduction entry of non-current assets 141 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+loss on valuation of securities 119 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities losses 1 ForeignExchangeLosses lossonfire 1 LossOnFire lossonreductionoffixedassets 1 LossOnReductionOfFixedAssets@@ -1285,6 +1295,7 @@ miscellaneousexpenses 1 MiscellaneousExpenses miscellaneousincome 1 MiscellaneousIncome miscellaneousloss 1 MiscellaneousLoss+module's 1 AllowanceForDoubtfulAccounts monetary 4 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedReceivable,LossOnSalesOfElectronicallyRecordedReceivables must 1 CashOverShort name. 1 AccumulatedDepreciation@@ -1325,14 +1336,14 @@ notespayable 2 NotesPayable,NonOperatingNotesPayable notesreceivable 4 NotesReceivable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable obligations 5 ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedObligations,LeaseObligations,ContraAccountForGuaranteeObligations,GuaranteeObligations-of 40 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+of 41 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets off 9 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,RecoveryOfBadDebts,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,BranchCurrentAccount,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers officers 1 DepositsReceivedFromOfficers-on 97 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip+on 98 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip only 9 NetIncome,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,NetLoss,CumulativeTranslationAdjustment,IncomeSummary,NetIncomeAttributableToNCI,NetLossAttributableToNCI only; 4 NetIncome,GrossProfit,OrdinaryProfit,NetLoss operating expenses 30 OperatingExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses,LongTermPrepaidExpenses,NonOperatingNotesReceivable-operating revenue 43 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+operating revenue 45 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome operatingexpenses 1 OperatingExpenses operatingrevenue 1 OperatingRevenue ordinary 1 OrdinaryProfit@@ -1356,7 +1367,7 @@ payment fees / fees paid 35 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,MembershipFees,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable paymentfees 2 ReceiptFee,PaymentFees payments 2 PaymentsOnBehalf,SuspensePayments-payments made on behalf 97 PaymentsOnBehalf,SuspensePayments,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation+payments made on behalf 98 PaymentsOnBehalf,SuspensePayments,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation paymentsonbehalf 1 PaymentsOnBehalf pension 1 PrepaidPensionCost periodic/3-account 1 MerchandiseInventory@@ -1382,18 +1393,20 @@ progress 2 ConstructionInProgress,SoftwareInProgress property tax payable 32 PropertyTaxPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,TaxesExpense propertytaxpayable 1 PropertyTaxPayable-provision 10 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-provision for bonuses 33 AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForProductWarranties-provision for doubtful accounts 36 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs-provision for repairs 32 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses-provision for retirement benefits 35 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-provision for warranties 31 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses+provision 11 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+provision for bonuses 34 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+provision for doubtful accounts 37 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+provision for repairs 33 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses+provision for retirement benefits 36 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+provision for warranties 32 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses provisionforbonuses 1 ProvisionForBonuses provisionfordoubtfulaccounts 1 ProvisionForDoubtfulAccounts provisionforproductwarranties 1 ProvisionForProductWarranties provisionforrepairs 1 ProvisionForRepairs-purchase 4 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates-purchase rebates 5 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates+purchase 5 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,PurchaseAllowances+purchase allowances 6 PurchaseAllowances,Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,SalesAllowances+purchase rebates 6 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates,PurchaseAllowances+purchaseallowances 1 PurchaseAllowances purchaserebates 1 PurchaseRebates purchases 2 Purchases,MerchandiseInventory purpose 1 LossOnReductionOfFixedAssets@@ -1410,19 +1423,19 @@ receivables. 1 AllowanceForDoubtfulAccounts received 10 DepositPayable,AdvancesReceived,DepositsReceived,ConsumptionTaxReceived,GiftCertificatesReceived,GuaranteeDepositsReceived,DividendsReceived,GainOnConstructionGrants,LandRentReceived,DepositsReceivedFromOfficers recorded 5 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedObligations,LossOnSalesOfElectronicallyRecordedReceivables-recovery of bad debts written off 41 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,BranchCurrentAccount,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost+recovery of bad debts written off 42 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,BranchCurrentAccount,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost recoveryofbaddebts 1 RecoveryOfBadDebts reduction 1 LossOnReductionOfFixedAssets refund 4 IncomeTaxesRefundReceivable,RefundLiabilities,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable refund liabilities 6 RefundLiabilities,IncomeTaxesRefundReceivable,DeferredTaxLiabilities,LeaseObligations,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable-refund of income taxes 59 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense+refund of income taxes 60 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense refundliabilities 1 RefundLiabilities refundofincometaxes 1 RefundOfIncomeTaxes related 1 AccumulatedDepreciation released). 1 ReversalOfAllowanceForDoubtfulAccounts remain 2 BranchCurrentAccount,HeadOfficeCurrentAccount rent 10 CurrentDeposits,RentExpense,RentalIncome,GainOnDonationOfFixedAssets,LandRentReceived,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LandRentPaid,BranchCurrentAccount,HeadOfficeCurrentAccount-rent expenses on land 125 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits+rent expenses on land 126 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits rental income 17 RentalIncome,LandRentReceived,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome rental income from land 23 LandRentReceived,RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,Land,AccruedCorporateIncomeTaxes,AllowanceForDoubtfulAccounts rentalincome 1 RentalIncome@@ -1437,10 +1450,10 @@ reserve 9 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,GeneralReserve reserve deposits, asset side 87 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate reserve deposits, liability side 48 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-reserve for depreciation 36 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory-reserve for dividend equalization 37 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,SuspensePayments-reserve for new construction 37 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory-reserve for repairs 36 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable+reserve for depreciation 37 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory+reserve for dividend equalization 38 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,SuspensePayments+reserve for new construction 38 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory+reserve for repairs 37 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable reservedepositpayable 1 ReserveDepositPayable reservedepositreceivable 1 ReserveDepositReceivable reservefordepreciation 2 ReserveForDepreciation,AccumulatedDepreciation@@ -1453,31 +1466,35 @@ retirement benefit expenses 29 RetirementBenefitExpenses,NetDefinedBenefitAsset,AllowanceForRetirementBenefits,NetDefinedBenefitLiability,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses retirement benefit liability 43 AllowanceForRetirementBenefits,NetDefinedBenefitLiability,NetDefinedBenefitAsset,RetirementBenefitExpenses,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable retirementbenefitexpenses 1 RetirementBenefitExpenses-revenue 38 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-reversal of allowance for doubtful accounts 71 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-reversal of provision for repairs 67 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-reversal of provision for warranties 66 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits+revenue 40 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned+reversal of allowance for doubtful accounts 74 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses+reversal of provision for bonuses 69 ReversalOfAllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties+reversal of provision for repairs 68 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses+reversal of provision for warranties 67 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses+reversalofallowanceforbonuses 1 ReversalOfAllowanceForBonuses reversalofallowancefordoubtfulaccounts 1 ReversalOfAllowanceForDoubtfulAccounts reversalofallowanceforproductwarranties 1 ReversalOfAllowanceForProductWarranties reversalofallowanceforrepairs 1 ReversalOfAllowanceForRepairs review 3 SuspensePayments,SuspenseReceipts,SuspenseAccount rights 2 LeaseholdRights,AmortizationOfPatents-sale 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates-sales 14 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables-sales rebates 15 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+sale 16 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+sales 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables+sales allowances 16 SalesAllowances,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+sales rebates 16 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+salesallowances 1 SalesAllowances salescost 1 SalesCost salesrebates 1 SalesRebates savings deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived securities 12 Securities,InvestmentSecurities,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnValuationOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities securitydepositspaid 1 SecurityDepositsPaid service 3 GovernmentService,ServiceRevenue,CostOfServices-service revenue 40 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+service revenue 42 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee servicerevenue 1 ServiceRevenue services 1 CostOfServices share issuance costs 6 StockIssuanceCosts,PrepaidPensionCost,StockSubscriptionDeposits,StorageExpenses,SubsidiaryStocks,AffiliateStocks shares 3 StockSubscriptionDeposits,SubsidiaryStocks,AffiliateStocks-shares of associates 40 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable-shares of subsidiaries 40 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+shares of associates 41 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+shares of subsidiaries 41 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable short 5 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,CashOverShort short-term loans payable 21 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable short-term loans receivable 25 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,LoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable@@ -1489,7 +1506,6 @@ shorttermnationalbondspayable 1 ShortTermNationalBondsPayable shrinkage 1 InventoryShrinkageLoss side 4 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation-side's 1 AllowanceForDoubtfulAccounts smaller 1 ReversalOfAllowanceForDoubtfulAccounts software 3 Software,SoftwareInProgress,AmortizationOfSoftware software in progress 90 SoftwareInProgress,ConstructionInProgress,Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery,Building,StockInvestment,EquipmentInvestment@@ -1530,7 +1546,7 @@ suspensepayments 1 SuspensePayments suspensereceipts 1 SuspenseReceipts tax 17 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,DeferredTaxAssets-tax revenue 54 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+tax revenue 56 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee taxes 9 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment taxesexpense 1 TaxesExpense taxesrevenue 1 TaxesRevenue@@ -1550,18 +1566,18 @@ travel and transportation 18 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments treasury 2 CentralBankPaymentExpense,CentralBankPaymentIncome under 3 InvestmentInAssociate,MerchandiseInventory,AccumulatedDepreciation-unearned revenue 38 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+unearned revenue 40 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome unearnedrevenue 1 UnearnedRevenue unpaid dividends 4 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,DividendsReceived unpaiddividends 1 UnpaidDividends unresolved 3 SuspensePayments,SuspenseReceipts,SuspenseAccount-use 9 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+use 10 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses used 1 ReversalOfAllowanceForDoubtfulAccounts utilities 1 UtilitiesExpense utilitiesexpense 1 UtilitiesExpense valuation 6 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnValuationOfSecurities-valuation difference on available-for-sale securities 101 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities-valuation loss on goods 102 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,NetLoss,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables+valuation difference on available-for-sale securities 102 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+valuation loss on goods 103 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,NetLoss,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables valuationdifferenceonothersecurities 1 ValuationDifferenceOnOtherSecurities value 12 NetIncome,ValueAdded,GrossProfit,OrdinaryProfit,NetLoss,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,IncomeSummary,SuspenseAccount value added 12 ValueAdded,NetIncome,GrossProfit,OrdinaryProfit,NetLoss,SuspensePayments,SuspenseReceipts,AllowanceForDoubtfulAccounts,IncomeSummary,SuspenseAccount@@ -1604,7 +1620,8 @@ 不渡手形 1 DishonoredNotesReceivable 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 1 ReserveDepositPayable 交際費 1 EntertainmentExpenses-仕入 3 Products,Purchases,PurchaseRebates+仕入 4 Products,Purchases,PurchaseRebates,PurchaseAllowances+仕入値引 1 PurchaseAllowances 仕入割戻 1 PurchaseRebates 仕掛品 1 WorkInProcess 付加価値 1 ValueAdded@@ -1673,7 +1690,8 @@ 国庫納付金支出 1 CentralBankPaymentExpense 国庫補助金受贈益 1 GainOnNationalSubsidies 土地 1 Land-売上 5 Products,SalesCost,GrossProfit,Sales,SalesRebates+売上 6 Products,SalesCost,GrossProfit,Sales,SalesRebates,SalesAllowances+売上値引 1 SalesAllowances 売上割戻 1 SalesRebates 売上原価 1 SalesCost 売上総利益 1 GrossProfit@@ -1801,8 +1819,9 @@ 資本準備金 1 LegalCapitalSurplus 資本金 1 CapitalStock 資産側 1 ReserveDepositReceivable-賞与 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses-賞与引当金 2 AllowanceForBonuses,ProvisionForBonuses+賞与 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses+賞与引当金 3 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses+賞与引当金戻入 1 ReversalOfAllowanceForBonuses 賞与引当金繰入 1 ProvisionForBonuses 車両運搬具 1 Vehicle 返金負債 1 RefundLiabilities@@ -1814,7 +1833,7 @@ 通信費 2 Commutation,CommunicationExpenses 還付法人税等 2 IncomeTaxesRefundReceivable,RefundOfIncomeTaxes 配当平均積立金 1 DividendEqualizationReserve-金 74 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings+金 75 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 1 DepositPayable 長期借入金 1 LongTermLoansPayable 長期前払費用 1 LongTermPrepaidExpenses
+ test/fixtures/admission-baseline-p1/README.md view
@@ -0,0 +1,19 @@+# Accounting equivalence baseline++`equivalence.tsv` records twelve fixed ledger comparisons with+`PostingMultiset`, `NetWithinTransaction`, and `NetAccountsInTransactions`.+The schema 1 header identifies the original baseline commit. Each row contains+its case name, `p3` classification, and Boolean result. `exact-residual` marks+comparisons of exact base residuals; `-` marks posting multiset comparisons.+The file and directory names are retained from the admission baseline.++The test suite reconstructs the inputs in `Golden.AdmissionBaseline` and+compares every row. To regenerate after an intentional change, run from the+repository root:++```bash+EA_REGEN_GOLDEN=1 stack build --test --bench --no-run-benchmarks+```++Review the changed rows, then run the command without `EA_REGEN_GOLDEN` to+check the fixture.
+ test/fixtures/admission-baseline-p1/equivalence.tsv view
@@ -0,0 +1,14 @@+# admission-baseline-p1; schema 1; commit 2a83fe178d47a1024e0e566794f39533a12b4c20+case p3 observation+cash-gross-transaction-net exact-residual True+cash-gross-postings - False+cash-gross-selected exact-residual False+retained-gross-postings - False+retained-gross-transaction-net exact-residual True+retained-gross-selected exact-residual True+near-cancellation-net exact-residual False+near-cancellation-postings - False+near-cancellation-selected exact-residual False+tiny-pair-net exact-residual False+tiny-pair-postings - False+tiny-single-net exact-residual False
+ test/fixtures/example-numbers-p1/README.md view
@@ -0,0 +1,36 @@+# Example number fixtures for P1++The `ripple-analysis.tsv` fixture records the first-term matrix calculations+from the ripple example at develop revision+`2a83fe178d47a1024e0e566794f39533a12b4c20`. The example has moved to the+comp-econ-asabm research repository; its pre-move source is preserved in tag+[archive/research-examples-pre-p4](https://github.com/yakagika/ExchangeAlgebra/tree/archive/research-examples-pre-p4/examples).+The test reproduces the+example's seed 42, in-house ratio 0.4, ten entities, and nine producing+industries. It keeps the original matrix size. The copied calculation covers+the generated input coefficients, `leontiefInverse`, and `rippleEffect` for+industry 9. It does not run the classic simulation engine. Each of the 243+numeric rows contains the matrix coordinate, the Double's hexadecimal bits,+and its `show` output.++The simulation outputs of `examples/basic/fixedPriceIO.hs` and+`examples/basic/fixedPriceIOSpill.hs` require their example-local `World`, `Event`,+and state-space instances. Reproducing those models in the library test would+exceed the 300-line limit per example. The CGE-Lite calibration has no library+public API counterpart. These three outputs are therefore not copied into+this fixture set. The calibration reference is maintained in the destination+repository, general-equilibrium; the pre-move version is preserved in tag+`archive/research-examples-pre-p4` linked in the first paragraph. The simulation+programs remain executable references until a smaller public-API model can+reproduce their output.++Regenerate after an intentional baseline change from the repository root:++```bash+EA_REGEN_GOLDEN=1 stack build --test --bench --no-run-benchmarks+```++Run the command again without `EA_REGEN_GOLDEN` to compare the generated TSV+with the committed fixture. The test checks the 243 numeric rows and compares+the complete file. The fixture is included in the package source archive by+the `test/fixtures/**/*` entry in `package.yaml`.
+ test/fixtures/example-numbers-p1/ripple-analysis.tsv view
@@ -0,0 +1,244 @@+# example-numbers-p1 ripple-analysis; schema 1; commit 2a83fe178d47a1024e0e566794f39533a12b4c20+inputCoefficient 1 1 0x3fb81bd1d98173c5 9.417449531743134e-2+inputCoefficient 1 2 0x0000000000000000 0.0+inputCoefficient 1 3 0x3fadf15449ce7268 5.848182106931149e-2+inputCoefficient 1 4 0x3fb4f2d52333bf44 8.183033093859843e-2+inputCoefficient 1 5 0x3fb5d7fe92a991b4 8.532706337570667e-2+inputCoefficient 1 6 0x3f9014bf5479c6f7 1.5704144984232973e-2+inputCoefficient 1 7 0x3f925a986153f479 1.7923718395639974e-2+inputCoefficient 1 8 0x3fb285d83c5026e0 7.235480759132029e-2+inputCoefficient 1 9 0x3f8d07b59d85ae1d 1.4174860830016784e-2+inputCoefficient 2 1 0x3f94abc76b4f89c8 2.0186534809804063e-2+inputCoefficient 2 2 0x3fa17e69491b8476 3.416756646562018e-2+inputCoefficient 2 3 0x3fa524a9958ebcc5 4.129533720640092e-2+inputCoefficient 2 4 0x3f90ce4f1fc4562b 1.6412006677532325e-2+inputCoefficient 2 5 0x3f8909b978f3a786 1.2225579265265695e-2+inputCoefficient 2 6 0x3fa91af243d5616c 4.9033709316691126e-2+inputCoefficient 2 7 0x3fb51ee593a616d6 8.250269751269043e-2+inputCoefficient 2 8 0x3f9b7fbd973bcc77 2.6854479175153458e-2+inputCoefficient 2 9 0x0000000000000000 0.0+inputCoefficient 3 1 0x0000000000000000 0.0+inputCoefficient 3 2 0x3facc62bc4be9c88 5.61994245211333e-2+inputCoefficient 3 3 0x3fa920e641b9c969 4.9079127810631125e-2+inputCoefficient 3 4 0x3fa4a4f1c43b2442 4.032092590711801e-2+inputCoefficient 3 5 0x3fb229eb68609b08 7.095214174533393e-2+inputCoefficient 3 6 0x3fa922ab7768806a 4.909263451213593e-2+inputCoefficient 3 7 0x3fb122b0067d3aa9 6.693554075648457e-2+inputCoefficient 3 8 0x0000000000000000 0.0+inputCoefficient 3 9 0x3fa620d294abc89b 4.321916642028719e-2+inputCoefficient 4 1 0x3fb4e85f3d4546c0 8.167071576942941e-2+inputCoefficient 4 2 0x3fa882616bc9b4ac 4.78697246624987e-2+inputCoefficient 4 3 0x3fae3655dd5de4b5 5.900829627257435e-2+inputCoefficient 4 4 0x3fab5ad8754cbbd0 5.342747146493532e-2+inputCoefficient 4 5 0x3fa710a9f7da0303 4.5049010758708284e-2+inputCoefficient 4 6 0x3fa7318626496b1c 4.529971330040025e-2+inputCoefficient 4 7 0x3f9120d36bfb27df 1.6726783242837825e-2+inputCoefficient 4 8 0x3fb647ff2b3811c4 8.703608327056772e-2+inputCoefficient 4 9 0x3fb6fa672fff0ad3 8.975834771903672e-2+inputCoefficient 5 1 0x3fa59bfda8ff6631 4.220574081382401e-2+inputCoefficient 5 2 0x3fb50d7008704590 8.223629194278304e-2+inputCoefficient 5 3 0x3fa586bfbb6a655d 4.204367792881201e-2+inputCoefficient 5 4 0x3fb3116da57d62d9 7.448468485143724e-2+inputCoefficient 5 5 0x3f9683078300918f 2.1984212275258094e-2+inputCoefficient 5 6 0x3fa75edf85cca44d 4.5645699580128075e-2+inputCoefficient 5 7 0x3fb0957b3216164f 6.47809026032402e-2+inputCoefficient 5 8 0x0000000000000000 0.0+inputCoefficient 5 9 0x3f94204041336437 1.965427778199943e-2+inputCoefficient 6 1 0x3f9aac843f7fb0cd 2.6048723579081213e-2+inputCoefficient 6 2 0x3f95bd305a3af34f 2.1229510791033687e-2+inputCoefficient 6 3 0x3fac57c6fd296048 5.535718765594361e-2+inputCoefficient 6 4 0x3fb255d649762834 7.162226956839463e-2+inputCoefficient 6 5 0x3fb4cdcffd236ce4 8.126544885765302e-2+inputCoefficient 6 6 0x3f90350d15d1277f 1.5827373940090702e-2+inputCoefficient 6 7 0x0000000000000000 0.0+inputCoefficient 6 8 0x3fa664b614048111 4.3737115806154254e-2+inputCoefficient 6 9 0x3fb30e33da10fd18 7.443546365628995e-2+inputCoefficient 7 1 0x3f9658e902d4d381 2.182354049473956e-2+inputCoefficient 7 2 0x3fb1ae76b9d4f344 6.90683559123047e-2+inputCoefficient 7 3 0x3fb2037cb85ed578 7.036571027022898e-2+inputCoefficient 7 4 0x3f9691dfb499d936 2.2040839574821912e-2+inputCoefficient 7 5 0x3fabdc6fc83ac587 5.4416173164450625e-2+inputCoefficient 7 6 0x3fb401f81623c228 7.815504589576305e-2+inputCoefficient 7 7 0x0000000000000000 0.0+inputCoefficient 7 8 0x3f921439c0b5b802 1.765527952868951e-2+inputCoefficient 7 9 0x3f8eb346961602b0 1.4990378813992095e-2+inputCoefficient 8 1 0x3fb3589385edc5a7 7.557031650301003e-2+inputCoefficient 8 2 0x3f9f1f8dbc17fc27 3.039380512745651e-2+inputCoefficient 8 3 0x3f914a94b1875c92 1.6886065803046586e-2+inputCoefficient 8 4 0x3f9c57f08f92c69c 2.7679213297864366e-2+inputCoefficient 8 5 0x0000000000000000 0.0+inputCoefficient 8 6 0x3fa4a81f3a88a9a7 4.034516896747969e-2+inputCoefficient 8 7 0x3fb5fbae21424d9f 8.587158500659207e-2+inputCoefficient 8 8 0x3fac100b9b5f81fa 5.480991622585303e-2+inputCoefficient 8 9 0x3fa315a8bd2eb2e4 3.727462109904936e-2+inputCoefficient 9 1 0x3fa39eab93925f3d 3.8319932712680395e-2+inputCoefficient 9 2 0x3fae1fa9c3750dbf 5.8835320577169885e-2+inputCoefficient 9 3 0x3f7ea6426216857f 7.482775983051026e-3+inputCoefficient 9 4 0x3f88f302f3f77460 1.2182257719297762e-2+inputCoefficient 9 5 0x3f9d7899f93f3cbd 2.8780370557623714e-2+inputCoefficient 9 6 0x3faf2dd3c983365c 6.0896509503078206e-2+inputCoefficient 9 7 0x3fb0b4cc8596e9e7 6.525877248251498e-2+inputCoefficient 9 8 0x3fb8f930512fcb20 9.755231840226175e-2+inputCoefficient 9 9 0x3fbb431e1ca8d19a 0.10649288367932855+leontiefInverse 1 1 0x3ff2273e9c87a7d1 1.1345811953019516+leontiefInverse 1 2 0x3fa09c30c7d116b3 3.244163932008117e-2+leontiefInverse 1 3 0x3fb7bd10d73fd8e3 9.2728664923786e-2+leontiefInverse 1 4 0x3fbed72845d78d1c 0.12047054009558561+leontiefInverse 1 5 0x3fbea926d7eca4f9 0.1197685506018792+leontiefInverse 1 6 0x3fa7f11c246d28f5 4.676139778764991e-2+leontiefInverse 1 7 0x3faa35e44975da67 5.119241139157254e-2+leontiefInverse 1 8 0x3fbb55ed219a1896 0.10677988119468504+leontiefInverse 1 9 0x3fa7c5b135697237 4.643014695168229e-2+leontiefInverse 2 1 0x3fa30326acd2a8f7 3.713341579107692e-2+leontiefInverse 2 2 0x3ff0e10f1266d30e 1.054946014291264+leontiefInverse 2 3 0x3fb0724cdcfdb613 6.424408335961855e-2+leontiefInverse 2 4 0x3fa27b07248ef9b2 3.609487839956972e-2+leontiefInverse 2 5 0x3fa1bea341e0f0e6 3.465757540877394e-2+leontiefInverse 2 6 0x3fb204b9b14f0391 7.038460329842013e-2+leontiefInverse 2 7 0x3fb984eb7e531813 9.96844466600069e-2+leontiefInverse 2 8 0x3fa60b652f056c13 4.305568884839075e-2+leontiefInverse 2 9 0x3f91d5c400c9255e 1.7417013690907417e-2+leontiefInverse 3 1 0x3f9681967e43d6b8 2.1978713471034722e-2+leontiefInverse 3 2 0x3fb67c6b6b93e20f 8.783599258811868e-2+leontiefInverse 3 3 0x3ff13e7f414250d5 1.0777580784970742+leontiefInverse 3 4 0x3fb118d07cac773c 6.678488773146979e-2+leontiefInverse 3 5 0x3fb91ed5621d35ad 9.812673230518128e-2+leontiefInverse 3 6 0x3fb4309040fb52dc 7.88660200735864e-2+leontiefInverse 3 7 0x3fb805144e16a572 9.382750421959488e-2+leontiefInverse 3 8 0x3f978d9640550956 2.3001048733257555e-2+leontiefInverse 3 9 0x3fb209190541ae6e 7.045132044157196e-2+leontiefInverse 4 1 0x3fbfe5e6537eee95 0.12460174120399821+leontiefInverse 4 2 0x3fb5c21a844d896c 8.499303560305344e-2+leontiefInverse 4 3 0x3fb86d9455d1ee51 9.542204947677503e-2+leontiefInverse 4 4 0x3ff17af18c0f734a 1.0925155135257278+leontiefInverse 4 5 0x3fb550fbeb95343d 8.326696874174018e-2+leontiefInverse 4 6 0x3fb55601b905932c 8.334360854275918e-2+leontiefInverse 4 7 0x3fae4844df4f69ba 5.914511896090198e-2+leontiefInverse 4 8 0x3fc0c7c82322edde 0.131096856266466+leontiefInverse 4 9 0x3fc0d78e96237f25 0.13157827692975235+leontiefInverse 5 1 0x3fb1cafbd4eaf068 6.950353573023593e-2+leontiefInverse 5 2 0x3fbc673defe78f08 0.11095034701389406+leontiefInverse 5 3 0x3fb2f19d3a1774e5 7.399923961880324e-2+leontiefInverse 5 4 0x3fba680982264802 0.10314998080710328+leontiefInverse 5 5 0x3ff0d7fe1f47adb3 1.052732584178426+leontiefInverse 5 6 0x3fb33ea5c293d00f 7.51746749918427e-2+leontiefInverse 5 7 0x3fb73be4abe95e85 9.075764843998442e-2+leontiefInverse 5 8 0x3f9caf22a3d4956e 2.8011838197149634e-2+leontiefInverse 5 9 0x3fa824a4c657a47a 4.715456887408549e-2+leontiefInverse 6 1 0x3fad3f738aba0bd2 5.712472027246175e-2+leontiefInverse 6 2 0x3fab53a2ad7d6bde 5.337246291920626e-2+leontiefInverse 6 3 0x3fb51aef74af5bb9 8.244225119356842e-2+leontiefInverse 6 4 0x3fb9f572976d5b65 0.10140148350670371+leontiefInverse 6 5 0x3fbbb5b04d5a130e 0.10824109924767475+leontiefInverse 6 6 0x3ff0bbee074e656d 1.0458812985801516+leontiefInverse 6 7 0x3fa0ff3a3ea90084 3.319723143477041e-2+leontiefInverse 6 8 0x3fb34ebc56c6f9a3 7.542016142449497e-2+leontiefInverse 6 9 0x3fbbb926d72ee918 0.10829394105431722+leontiefInverse 7 1 0x3fa5ed734925bc12 4.282722728645906e-2+leontiefInverse 7 2 0x3fb81d636d3624e6 9.419843117042923e-2+leontiefInverse 7 3 0x3fb8a3bffd9ba02b 9.624862615203315e-2+leontiefInverse 7 4 0x3fa91c0104a3c8d4 4.9041778389339236e-2+leontiefInverse 7 5 0x3fb4b62565a568da 8.090432864756583e-2+leontiefInverse 7 6 0x3fba0c067d8ae113 0.101745992326759+leontiefInverse 7 7 0x3ff06e3970c74afd 1.0269102483186423+leontiefInverse 7 8 0x3fa3ac02f621af39 3.8421719117506865e-2+leontiefInverse 7 9 0x3fa425825ed8b9e9 3.934867294116257e-2+leontiefInverse 8 1 0x3fbaed69a1c47559 0.10518512915992541+leontiefInverse 8 2 0x3fac328dcae88aa4 5.5073195475661835e-2+leontiefInverse 8 3 0x3fa732f5635d6153 4.531065786312873e-2+leontiefInverse 8 4 0x3fabe0c694195f32 5.444927747583818e-2+leontiefInverse 8 5 0x3f9de8f12f2cfdb7 2.9208916191462566e-2+leontiefInverse 8 6 0x3fb14bb9b15795bc 6.756172732965288e-2+leontiefInverse 8 7 0x3fbc05ab7c95a06e 0.10946151534665358+leontiefInverse 8 8 0x3ff1591ec100724b 1.0842578448420166+leontiefInverse 8 9 0x3fb00b1e75bec8d8 6.26696622337336e-2+leontiefInverse 9 1 0x3fb2e008b817ab3b 7.373098844952335e-2+leontiefInverse 9 2 0x3fb7c5619722d3d4 9.285554828495829e-2+leontiefInverse 9 3 0x3fa3b7fa5cc9fd73 3.8513015581879716e-2+leontiefInverse 9 4 0x3fa5e46b5deb7ae6 4.275832673532935e-2+leontiefInverse 9 5 0x3fae98d9e1ab199c 5.975991134396283e-2+leontiefInverse 9 6 0x3fb8d18a86c78ee4 9.694734373584485e-2+leontiefInverse 9 7 0x3fba3cd1f5961c19 0.10249054189453978+leontiefInverse 9 8 0x3fc17cd1482f5165 0.13662162804269964+leontiefInverse 9 9 0x3ff24b0d1feb51a7 1.143323063549454+rippleEffect 1 1 0x0000000000000000 0.0+rippleEffect 1 2 0x0000000000000000 0.0+rippleEffect 1 3 0x0000000000000000 0.0+rippleEffect 1 4 0x0000000000000000 0.0+rippleEffect 1 5 0x0000000000000000 0.0+rippleEffect 1 6 0x0000000000000000 0.0+rippleEffect 1 7 0x0000000000000000 0.0+rippleEffect 1 8 0x0000000000000000 0.0+rippleEffect 1 9 0x3fa7c5b135697237 4.643014695168229e-2+rippleEffect 2 1 0x0000000000000000 0.0+rippleEffect 2 2 0x0000000000000000 0.0+rippleEffect 2 3 0x0000000000000000 0.0+rippleEffect 2 4 0x0000000000000000 0.0+rippleEffect 2 5 0x0000000000000000 0.0+rippleEffect 2 6 0x0000000000000000 0.0+rippleEffect 2 7 0x0000000000000000 0.0+rippleEffect 2 8 0x0000000000000000 0.0+rippleEffect 2 9 0x3f91d5c400c9255e 1.7417013690907417e-2+rippleEffect 3 1 0x0000000000000000 0.0+rippleEffect 3 2 0x0000000000000000 0.0+rippleEffect 3 3 0x0000000000000000 0.0+rippleEffect 3 4 0x0000000000000000 0.0+rippleEffect 3 5 0x0000000000000000 0.0+rippleEffect 3 6 0x0000000000000000 0.0+rippleEffect 3 7 0x0000000000000000 0.0+rippleEffect 3 8 0x0000000000000000 0.0+rippleEffect 3 9 0x3fb209190541ae6e 7.045132044157196e-2+rippleEffect 4 1 0x0000000000000000 0.0+rippleEffect 4 2 0x0000000000000000 0.0+rippleEffect 4 3 0x0000000000000000 0.0+rippleEffect 4 4 0x0000000000000000 0.0+rippleEffect 4 5 0x0000000000000000 0.0+rippleEffect 4 6 0x0000000000000000 0.0+rippleEffect 4 7 0x0000000000000000 0.0+rippleEffect 4 8 0x0000000000000000 0.0+rippleEffect 4 9 0x3fc0d78e96237f25 0.13157827692975235+rippleEffect 5 1 0x0000000000000000 0.0+rippleEffect 5 2 0x0000000000000000 0.0+rippleEffect 5 3 0x0000000000000000 0.0+rippleEffect 5 4 0x0000000000000000 0.0+rippleEffect 5 5 0x0000000000000000 0.0+rippleEffect 5 6 0x0000000000000000 0.0+rippleEffect 5 7 0x0000000000000000 0.0+rippleEffect 5 8 0x0000000000000000 0.0+rippleEffect 5 9 0x3fa824a4c657a47a 4.715456887408549e-2+rippleEffect 6 1 0x0000000000000000 0.0+rippleEffect 6 2 0x0000000000000000 0.0+rippleEffect 6 3 0x0000000000000000 0.0+rippleEffect 6 4 0x0000000000000000 0.0+rippleEffect 6 5 0x0000000000000000 0.0+rippleEffect 6 6 0x0000000000000000 0.0+rippleEffect 6 7 0x0000000000000000 0.0+rippleEffect 6 8 0x0000000000000000 0.0+rippleEffect 6 9 0x3fbbb926d72ee918 0.10829394105431722+rippleEffect 7 1 0x0000000000000000 0.0+rippleEffect 7 2 0x0000000000000000 0.0+rippleEffect 7 3 0x0000000000000000 0.0+rippleEffect 7 4 0x0000000000000000 0.0+rippleEffect 7 5 0x0000000000000000 0.0+rippleEffect 7 6 0x0000000000000000 0.0+rippleEffect 7 7 0x0000000000000000 0.0+rippleEffect 7 8 0x0000000000000000 0.0+rippleEffect 7 9 0x3fa425825ed8b9e9 3.934867294116257e-2+rippleEffect 8 1 0x0000000000000000 0.0+rippleEffect 8 2 0x0000000000000000 0.0+rippleEffect 8 3 0x0000000000000000 0.0+rippleEffect 8 4 0x0000000000000000 0.0+rippleEffect 8 5 0x0000000000000000 0.0+rippleEffect 8 6 0x0000000000000000 0.0+rippleEffect 8 7 0x0000000000000000 0.0+rippleEffect 8 8 0x0000000000000000 0.0+rippleEffect 8 9 0x3fb00b1e75bec8d8 6.26696622337336e-2+rippleEffect 9 1 0x0000000000000000 0.0+rippleEffect 9 2 0x0000000000000000 0.0+rippleEffect 9 3 0x0000000000000000 0.0+rippleEffect 9 4 0x0000000000000000 0.0+rippleEffect 9 5 0x0000000000000000 0.0+rippleEffect 9 6 0x0000000000000000 0.0+rippleEffect 9 7 0x0000000000000000 0.0+rippleEffect 9 8 0x0000000000000000 0.0+rippleEffect 9 9 0x3ff24b0d1feb51a7 1.143323063549454
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.PostingPolicy.txt view
@@ -0,0 +1,7 @@+ClosingProcess+ConsolidationWorksheet+EngineComputation+OrdinaryJournal+ProcessingContext+postingAllowedIn+postingCapabilityFor
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Accounting.txt view
@@ -0,0 +1,359 @@+.->+.~+<=>+AccountBase+AccountDivision+AccountTitle+AccountTitles+AccountsPayable+AccountsReceivable+AccruedConsumptionTax+AccruedCorporateIncomeTaxes+AccruedExpenses+AccruedRevenue+AccumulatedDepreciation+AdditionalIncomeTaxesForPriorPeriods+AdvancesPaid+AdvancesReceived+AdvertisingExpense+AffiliateStocks+AllowanceForBonuses+AllowanceForDoubtfulAccounts+AllowanceForProductWarranties+AllowanceForRepairs+AllowanceForRetirementBenefits+AmortizationExpense+AmortizationOfGoodwill+AmortizationOfPatents+AmortizationOfSoftware+Assets+AvailableForSaleSecurities+BackwardFlow+BadDebtLoss+BankOverdraft+Bonuses+BonusesPayable+BranchCurrentAccount+Building+BusinessCommencementExpenses+BusinessTrip+CapitalStock+CapitalSurplus+Cash+CashOverShort+CentralBankNotePayable+CentralBankPaymentExpense+CentralBankPaymentIncome+ClosingFlip+ClosingKeep+ClosingSide+CommunicationExpenses+Commutation+ConstructionFundReserve+ConstructionInProgress+ConsumptionExpenditure+ConsumptionTaxPaid+ConsumptionTaxReceived+ConsumptionTaxRefundReceivable+ContraAccountForGuaranteeObligations+ContractAssets+CorporateIncomeTaxes+Cost+CostOfServices+Credit+CreditCardReceivable+CumulativeTranslationAdjustment+Current+CurrentDeposits+Debit+DeferredTaxAssets+DeferredTaxLiabilities+DeliveryExpenses+DepositInterestEarned+DepositPayable+Deposits+DepositsInSpecialAccounts+DepositsReceived+DepositsReceivedFromOfficers+Depreciation+DevelopmentExpenses+DishonoredNotesReceivable+DivBy+DividendEqualizationReserve+DividendsReceived+EarnedSurplus+ElectronicallyRecordedObligations+ElectronicallyRecordedReceivable+EntertainmentExpenses+EquipmentInvestment+Equity+EquityInEarningsOfInvestee+ExBaseClass+Exchange+Fixed+FixedCurrent+Fixtures+ForeignExchangeGains+ForeignExchangeLosses+GainOnBargainPurchase+GainOnConstructionGrants+GainOnDonationOfFixedAssets+GainOnNationalSubsidies+GainOnSalesOfFixedAssets+GainOnSalesOfInvestmentSecurities+GainOnSalesOfSecurities+GainOnValuationOfSecurities+GeneralReserve+GiftCertificatesReceived+Gold+GoodsInTransit+Goodwill+GovernmentService+GrossProfit+GuaranteeDepositsReceived+GuaranteeObligations+HeadOfficeCurrentAccount+HeldToMaturityBonds+IN+IncomeSummary+IncomeTaxesAdjustment+IncomeTaxesRefundReceivable+IncorporationExpenses+InsuranceExpense+InsuranceGain+InterestEarned+InterestExpense+InterestOnSecurities+InvalidCoefficient+InvalidPortion+InvalidPosting+InventoryShrinkageLoss+InvestmentInAssociate+InvestmentSecurities+Land+LandRentPaid+LandRentReceived+LeaseExpenses+LeaseObligations+LeasedAssets+LeaseholdRights+LegalCapitalSurplus+LegalRetainedEarnings+Liability+LoansPayable+LoansReceivable+LongTermLoansPayable+LongTermLoansReceivable+LongTermNationalBonds+LongTermNationalBondsPayable+LongTermOtherPayables+LongTermPrepaidExpenses+LossOnFire+LossOnReductionOfFixedAssets+LossOnRetirementOfFixedAssets+LossOnSalesOfElectronicallyRecordedReceivables+LossOnSalesOfFixedAssets+LossOnSalesOfInvestmentSecurities+LossOnSalesOfNotesReceivable+LossOnSalesOfReceivables+LossOnSalesOfSecurities+LossOnValuationOfMerchandise+LossOnValuationOfSecurities+MS+Machinery+MaintenanceExpenses+MeetingExpenses+MembershipFees+MerchandiseInventory+MiscellaneousExpenses+MiscellaneousIncome+MiscellaneousLoss+MulBy+NationalBondInterestEarned+NetDefinedBenefitAsset+NetDefinedBenefitLiability+NetIncome+NetIncomeAttributableToNCI+NetLoss+NetLossAttributableToNCI+NewspaperBooksExpenses+NonControllingInterests+NonFiniteBalance+NonFiniteNet+NonFiniteResult+NonOperatingElectronicallyRecordedObligations+NonOperatingElectronicallyRecordedReceivable+NonOperatingNotesPayable+NonOperatingNotesReceivable+NotesLoansPayable+NotesLoansReceivable+NotesPayable+NotesReceivable+OUT+OperatingExpenses+OperatingRevenue+OrdinaryProfit+Other+OtherCapitalSurplus+OtherPayables+OtherReceivables+OverlappingRows+OverlappingRules+PIMO+PS+Patent+PaymentFees+PaymentsOnBehalf+PettyCash+Portion+PrepaidCorporateIncomeTaxes+PrepaidExpenses+PrepaidPensionCost+Products+PropertyTaxPayable+ProvisionForBonuses+ProvisionForDoubtfulAccounts+ProvisionForProductWarranties+ProvisionForRepairs+PurchaseAllowances+PurchaseRebates+Purchases+RawMaterials+ReceiptFee+RecoveryOfBadDebts+RefundLiabilities+RefundOfIncomeTaxes+Relabel+RentExpense+RentalIncome+RepairFundReserve+RepairsExpense+ResearchAndDevelopmentExpenses+ReserveDepositPayable+ReserveDepositReceivable+ReserveForDepreciation+RetainedEarnings+RetirementBenefitExpenses+Revenue+ReversalOfAllowanceForBonuses+ReversalOfAllowanceForDoubtfulAccounts+ReversalOfAllowanceForProductWarranties+ReversalOfAllowanceForRepairs+Sales+SalesAllowances+SalesCost+SalesRebates+Securities+SecurityDepositsPaid+ServiceRevenue+SettleError+SettleRule+SettlementBatch+ShortTermLoansPayable+ShortTermLoansReceivable+ShortTermNationalBonds+ShortTermNationalBondsPayable+Side+SignedNet+Software+SoftwareInProgress+StatutoryWelfareExpenses+StockInvestment+StockIssuanceCosts+StockSubscriptionDeposits+StorageExpenses+Structures+SubsidiaryStocks+SubsidyExpense+SubsidyIncome+SuppliesExpenses+SuppliesOnHand+SuspenseAccount+SuspensePayments+SuspenseReceipts+TaxesExpense+TaxesRevenue+TimeDeposits+ToolsAndInstruments+Trademark+TradingSecurities+TransferApplyError+TransferEnds+TransferError+TransferFailed+TransferJournalError+TransferNoteMoves+TransferRow+TransferRule+TransferRuleError+TransferRules+TransferScale+TransferShare+TransferTable+TransferTableError+UnclassifiedAccount+UnearnedRevenue+UnpaidDividends+UnroundableAmount+UtilitiesExpense+ValuationDifferenceOnOtherSecurities+ValueAdded+Vehicle+WageEarned+WageExpenditure+WelfareExpenses+WildcardPosting+WorkInProcess+balance+balanceWithin+classifyAccountDivision+closingEntries+closingSide+collapseEntries+collapseNetEntries+decL+decM+decP+decR+defaultSide+diffRL+diffRLWithin+divideBy+fixedCurrent+getAccountTitle+isContra+mkTransferRules+mkTransferTable+pimoFlip+pimoFromDivision+projByAccountTitle+projCapitalStock+projContra+projContraAssets+projCredit+projCurrentAssets+projCurrentLiability+projDebit+projDeferredAssets+projFixedAssets+projFixedLiability+relabel+retainedEarningsRule+ruleFrom+ruleScale+ruleTo+rulesToList+scaleBy+setAccountTitle+settleEntries+settlementSteps+switchSide+tableRows+transferEntries+transferEntriesBy+transferEntriesByWith+transferEntriesWith+whatDiv+whatPIMO+whichSide+|%
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.JcciAliases.txt view
@@ -0,0 +1,1 @@+jcciAliases
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Registry.txt view
@@ -0,0 +1,23 @@+AccountSemantics+AccountSpec+accountAliases+accountDescriptions+accountSemantics+accountSpec+accountSpecMap+asAliases+asClosing+asDescription+asDivision+asFixedCurrent+asIsContra+asLabelJa+asNameEn+asNameJa+asemDivisionSemantics+asemHomeSideSemantics+asemPostingCapability+asemReportingEligibility+asemRoles+classifyAccountContra+concreteAccountTitles
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Account.Types.txt view
@@ -0,0 +1,46 @@+AccountDivision+AccountRole+Assets+AttributionAccount+BookkeepingControlClass+CloseByDivision+ClosingDevice+ClosingOnly+ClosingRule+ConsolidationOnly+ContextDependentHomeSide+ContextualPresentation+ContraAccount+Cost+Credit+Current+Debit+DerivedPresentation+DirectionEncoding+DivisionSemantics+EngineGeneratedOnly+Equity+Fixed+FixedCurrent+FixedHomeSide+HomeSideSemantics+Liability+NoClose+NoFixedHomeSide+NoPostingSide+NoStatementDivision+NotPostable+NotPresented+OrdinaryAccount+OrdinaryPosting+Other+PeriodResult+PostingCapability+ReciprocalAccount+ReportingEligibility+ReportingSubtotal+Revenue+Side+StatementDivision+StatementEligible+SuspenseOrClearingAccount
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.Element.txt view
@@ -0,0 +1,277 @@+.#+./=+.<+.<=+.==+.>+.>=+AccountTitle+AccountTitles+AccountsPayable+AccountsReceivable+AccruedConsumptionTax+AccruedCorporateIncomeTaxes+AccruedExpenses+AccruedRevenue+AccumulatedDepreciation+AdditionalIncomeTaxesForPriorPeriods+AdvancesPaid+AdvancesReceived+AdvertisingExpense+AffiliateStocks+AllowanceForBonuses+AllowanceForDoubtfulAccounts+AllowanceForProductWarranties+AllowanceForRepairs+AllowanceForRetirementBenefits+AmortizationExpense+AmortizationOfGoodwill+AmortizationOfPatents+AmortizationOfSoftware+Amount+AvailableForSaleSecurities+AxisDecompose+AxisKey+BadDebtLoss+BankOverdraft+Bonuses+BonusesPayable+BranchCurrentAccount+Building+BusinessCommencementExpenses+BusinessTrip+CNY+CapitalStock+CapitalSurplus+Cash+CashOverShort+CentralBankNotePayable+CentralBankPaymentExpense+CentralBankPaymentIncome+CommunicationExpenses+Commutation+ConstructionFundReserve+ConstructionInProgress+ConsumptionExpenditure+ConsumptionTaxPaid+ConsumptionTaxReceived+ConsumptionTaxRefundReceivable+ContraAccountForGuaranteeObligations+ContractAssets+CorporateIncomeTaxes+CostOfServices+CountUnit+CreditCardReceivable+CumulativeTranslationAdjustment+CurrentDeposits+DeferredTaxAssets+DeferredTaxLiabilities+DeliveryExpenses+DepositInterestEarned+DepositPayable+Deposits+DepositsInSpecialAccounts+DepositsReceived+DepositsReceivedFromOfficers+Depreciation+DevelopmentExpenses+DishonoredNotesReceivable+DividendEqualizationReserve+DividendsReceived+Dollar+EarnedSurplus+ElectronicallyRecordedObligations+ElectronicallyRecordedReceivable+Element+EntertainmentExpenses+EquipmentInvestment+EquityInEarningsOfInvestee+Euro+Fixtures+ForeignExchangeGains+ForeignExchangeLosses+GainOnBargainPurchase+GainOnConstructionGrants+GainOnDonationOfFixedAssets+GainOnNationalSubsidies+GainOnSalesOfFixedAssets+GainOnSalesOfInvestmentSecurities+GainOnSalesOfSecurities+GainOnValuationOfSecurities+GeneralReserve+Generic+GiftCertificatesReceived+Gold+GoodsInTransit+Goodwill+GovernmentService+GrossProfit+GuaranteeDepositsReceived+GuaranteeObligations+Hashable+HeadOfficeCurrentAccount+HeldToMaturityBonds+IncomeSummary+IncomeTaxesAdjustment+IncomeTaxesRefundReceivable+IncorporationExpenses+InsuranceExpense+InsuranceGain+InterestEarned+InterestExpense+InterestOnSecurities+InventoryShrinkageLoss+InvestmentInAssociate+InvestmentSecurities+Land+LandRentPaid+LandRentReceived+LeaseExpenses+LeaseObligations+LeasedAssets+LeaseholdRights+LegalCapitalSurplus+LegalRetainedEarnings+LoansPayable+LoansReceivable+LongTermLoansPayable+LongTermLoansReceivable+LongTermNationalBonds+LongTermNationalBondsPayable+LongTermOtherPayables+LongTermPrepaidExpenses+LossOnFire+LossOnReductionOfFixedAssets+LossOnRetirementOfFixedAssets+LossOnSalesOfElectronicallyRecordedReceivables+LossOnSalesOfFixedAssets+LossOnSalesOfInvestmentSecurities+LossOnSalesOfNotesReceivable+LossOnSalesOfReceivables+LossOnSalesOfSecurities+LossOnValuationOfMerchandise+LossOnValuationOfSecurities+Machinery+MaintenanceExpenses+MeetingExpenses+MembershipFees+MerchandiseInventory+MiscellaneousExpenses+MiscellaneousIncome+MiscellaneousLoss+Name+NationalBondInterestEarned+NetDefinedBenefitAsset+NetDefinedBenefitLiability+NetIncome+NetIncomeAttributableToNCI+NetLoss+NetLossAttributableToNCI+NewspaperBooksExpenses+NonControllingInterests+NonOperatingElectronicallyRecordedObligations+NonOperatingElectronicallyRecordedReceivable+NonOperatingNotesPayable+NonOperatingNotesReceivable+NotesLoansPayable+NotesLoansReceivable+NotesPayable+NotesReceivable+OperatingExpenses+OperatingRevenue+OrdinaryProfit+OtherCapitalSurplus+OtherPayables+OtherReceivables+Patent+PaymentFees+PaymentsOnBehalf+PettyCash+PrepaidCorporateIncomeTaxes+PrepaidExpenses+PrepaidPensionCost+Products+PropertyTaxPayable+ProvisionForBonuses+ProvisionForDoubtfulAccounts+ProvisionForProductWarranties+ProvisionForRepairs+PurchaseAllowances+PurchaseRebates+Purchases+RawMaterials+ReceiptFee+RecoveryOfBadDebts+RefundLiabilities+RefundOfIncomeTaxes+RentExpense+RentalIncome+RepairFundReserve+RepairsExpense+ResearchAndDevelopmentExpenses+ReserveDepositPayable+ReserveDepositReceivable+ReserveForDepreciation+RetainedEarnings+RetirementBenefitExpenses+ReversalOfAllowanceForBonuses+ReversalOfAllowanceForDoubtfulAccounts+ReversalOfAllowanceForProductWarranties+ReversalOfAllowanceForRepairs+Sales+SalesAllowances+SalesCost+SalesRebates+Securities+SecurityDepositsPaid+ServiceRevenue+ShortTermLoansPayable+ShortTermLoansReceivable+ShortTermNationalBonds+ShortTermNationalBondsPayable+Software+SoftwareInProgress+StatutoryWelfareExpenses+StockInvestment+StockIssuanceCosts+StockSubscriptionDeposits+StorageExpenses+Structures+Subject+SubsidiaryStocks+SubsidyExpense+SubsidyIncome+SuppliesExpenses+SuppliesOnHand+SuspenseAccount+SuspensePayments+SuspenseReceipts+TaxesExpense+TaxesRevenue+TimeDeposits+ToolsAndInstruments+Trademark+TradingSecurities+UnearnedRevenue+UnpaidDividends+UtilitiesExpense+ValuationDifferenceOnOtherSecurities+ValueAdded+Vehicle+WageEarned+WageExpenditure+WelfareExpenses+WorkInProcess+Yen+axisIsWildcard+compareElement+equal+hash+hashWithSalt+haveWildcard+ignoreWildcard+isWildcard+maxElement+minElement+toAxisKeys+wildcard
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Base.txt view
@@ -0,0 +1,54 @@+.#+./=+.<+.<=+.==+.>+.>=+:<+Amount+AxisDecompose+AxisKey+BaseClass+BaseForSingleHat+BasePart+CNY+CountUnit+Dollar+Element+Euro+Generic+Hashable+Hat+HatBase+HatBaseClass+HatNot+Name+Not+Subject+Yen+_base+_hat+axisIsWildcard+base+compareBase+compareElement+compareHatBase+customError+equal+hash+hashWithSalt+hat+haveWildcard+ignoreWildcard+isHat+isNot+isWildcard+maxElement+merge+minElement+revHat+toAxisKeys+toHat+toNot+wildcard
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Internal.txt view
@@ -0,0 +1,127 @@+.#+.*+.++.-+./=+.<+.<=+.==+.>+.>=+.@+.^+:<+:@+<++<@+Accum+Alg+Amount+AxisDecompose+AxisKey+BaseClass+BaseForSingleHat+BasePart+CNY+CountUnit+Dollar+Element+Euro+Generic+Hashable+Hat+HatBase+HatBaseClass+HatNot+HatVal+Liner+Name+Nearly+Not+Pair+Redundant+Single+Subject+Yen+Zero+_allBpIds+_axisPosting+_base+_bpToId+_hat+_hatBase+_hatSide+_idToBp+_nextBpId+_notSide+_realg+_val+addAccum+axisIsWildcard+balanceBy+balanceMapBy+bar+barWithin+base+bases+compareBase+compareElement+compareHatBase+compress+customError+decBy+emptyAccum+equal+exactAccum+extendBy+filter+foldEntries+foldEntriesToMap+fromList+hash+hashWithSalt+hat+haveWildcard+ignoreWildcard+isHat+isNearly+isNearlyNum+isNot+isWildcard+isZero+isZeroValue+linerFromMap+map+mapBasePart+mapMaybePosting+mapPosting+maxElement+merge+mergeAccum+minElement+negateAccum+netPairMapBy+norm+postFromNetBy+postingUpperBound+proj+projNetNorm+revHat+roundAccum+roundRational+showValue+sigma+sigma2When+sigmaFromMap+signAccum+splitAccum+toASCList+toAxisKeys+toHat+toList+toNot+unionsMerge+vals+valueError+wildcard+zeroValue
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.Rule.txt view
@@ -0,0 +1,36 @@+ClosingFlip+ClosingKeep+ClosingSide+DivBy+InvalidCoefficient+MulBy+NonFiniteBalance+NonFiniteNet+NonFiniteResult+OverlappingRules+Relabel+SettleError+SettleRule+SettlementBatch+SignedNet+TransferApplyError+TransferRule+TransferRuleError+TransferRules+TransferScale+closingEntries+closingSide+collapseEntries+collapseNetEntries+divideBy+mkTransferRules+relabel+retainedEarningsRule+ruleFrom+ruleScale+ruleTo+rulesToList+scaleBy+settleEntries+settlementSteps+transferEntries
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.Transfer.txt view
@@ -0,0 +1,18 @@+InvalidCoefficient+NonFiniteBalance+NonFiniteResult+OverlappingRules+Relabel+TransferApplyError+TransferRule+TransferRuleError+TransferRules+TransferScale+collapseEntries+collapseNetEntries+mkTransferRules+ruleFrom+ruleScale+ruleTo+rulesToList+transferEntries
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Algebra.txt view
@@ -0,0 +1,128 @@+.#+.*+.++.-+./=+.<+.<=+.==+.>+.>=+.@+.^+:<+:@+<++<@+Accum+Alg+Amount+AxisDecompose+AxisKey+BaseClass+BaseForSingleHat+BasePart+CNY+CountUnit+Dollar+Element+Euro+Generic+Hashable+Hat+HatBase+HatBaseClass+HatNot+HatVal+Name+Nearest+Nearly+NegativeValue+NonFiniteValue+Not+Redundant+Rounding+Subject+ToMultipleOf+Tolerance+ValueError+Yen+Zero+_base+_hat+_hatBase+_val+absTol+addAccum+axisIsWildcard+balanceBy+balanceMapBy+bar+barWithin+base+bases+compareBase+compareElement+compareHatBase+compress+customError+decBy+defaultTolerance+emptyAccum+equal+exactAccum+extendBy+filter+foldEntries+foldEntriesToMap+fromList+hash+hashWithSalt+hat+haveWildcard+ignoreWildcard+isErrorValue+isHat+isNearly+isNearlyNum+isNot+isWildcard+isZero+isZeroValue+map+mapBasePart+mapMaybePosting+mapPosting+maxElement+merge+mergeAccum+minElement+negateAccum+netPairMapBy+norm+postFromNetBy+postingUpperBound+proj+projNetNorm+relTol+revHat+roundAccum+roundRational+roundScaled+showValue+sigma+sigma2When+sigmaFromMap+signAccum+splitAccum+sumValues+toASCList+toAxisKeys+toHat+toList+toNot+unionsMerge+vals+valueError+wildcard+zeroValue
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.Descriptions.txt view
@@ -0,0 +1,1 @@+accountDescriptions
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Assist.txt view
@@ -0,0 +1,16 @@+AccountInfo+aiDesc+aiDivisionSemantics+aiHomeSideSemantics+aiNameEn+aiNameJa+aiPostingCapability+aiReportingEligibility+aiRoles+aiTitle+allAccountInfos+describeAccount+explainEntryError+explainJournalErrors+explainSourceErrors+suggestAccounts
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Bookkeeping.txt view
@@ -0,0 +1,19 @@+MkBase+accruedExpenseEntry+accruedRevenueEntry+allowanceReplenishmentEntry+allowanceResetEntries+cogsAdjustmentEntries+consumptionTaxSettlementEntry+corporateTaxInterimEntry+corporateTaxSettlementEntries+depreciationDirectEntry+depreciationIndirectEntry+equityMethodBalance+equityMethodDividendEntry+equityMethodEarningsEntry+equityMethodEntries+prepaidExpenseEntry+priorPeriodErrorCorrection+reversingEntry+unearnedRevenueEntry
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Consolidation.Worksheet.txt view
@@ -0,0 +1,73 @@+AccountBalance+AdjustmentPostingNotAllowed+BalanceSheetNonControllingInterests+BalanceSheetNonControllingInterestsMismatch+BalanceSheetRetainedEarnings+BalanceSheetRetainedEarningsMismatch+CreditBalance+DebitBalance+DuplicateAdjustmentId+DuplicateAdjustmentSource+DuplicateSourceId+EmptyAdjustment+InvalidAdjustmentValue+InvalidLinkAmount+InvalidSourceValue+LinkField+NetIncomeAttributionMismatch+NoBalance+NonControllingInterestsDividends+NonControllingInterestsPeriodShare+NonControllingInterestsRollForwardMismatch+OpeningNonControllingInterests+OpeningRetainedEarnings+OwnersPeriodResultLinkMismatch+PeriodBreakEven+PeriodLoss+PeriodProfit+PeriodResult+ProfitOrLossNetIncome+ProfitOrLossNetIncomeAttributableToOwners+RetainedEarningsDividends+RetainedEarningsRollForwardMismatch+StatementOfChangesClosingNonControllingInterests+StatementOfChangesClosingRetainedEarnings+StatementOfChangesNetIncomeAttributableToOwners+TrialBalanceSource+UnbalancedAdjustment+UnbalancedSourceTrialBalance+UnknownAdjustmentSource+ValidatedWorksheet+WildcardAdjustmentAccount+WildcardAdjustmentSide+WildcardSourceAccount+WildcardSourceSide+WorksheetAdjustment+WorksheetError+WorksheetInput+WorksheetLinkage+_adjustmentElement+_adjustmentId+_adjustmentSourceIds+_balanceSheetNonControllingInterests+_balanceSheetRetainedEarnings+_nonControllingInterestsDividends+_nonControllingInterestsPeriodShare+_openingNonControllingInterests+_openingRetainedEarnings+_profitOrLossNetIncome+_profitOrLossNetIncomeAttributableToOwners+_retainedEarningsDividends+_sourceId+_sourceTrialBalance+_statementOfChangesClosingNonControllingInterests+_statementOfChangesClosingRetainedEarnings+_statementOfChangesNetIncomeAttributableToOwners+_worksheetAdjustments+_worksheetLinkage+_worksheetSources+combinedWorksheet+validateConsolidationWorksheet+validatedAdjustments+validatedLinkage+validatedSources
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Checked.txt view
@@ -0,0 +1,40 @@+AmountMismatch+BalancedUnresolved+ClosingProcess+ConsolidationWorksheet+DuplicateTxId+EmptyEntry+EngineComputation+EntryError+EntryErrors+EntryParse+FullyResolved+Imbalanced+JournalCert+JournalError+MissingSource+NonPositiveAmount+OrdinaryJournal+PostingNotAllowed+ProcessingContext+Rejected+SourceError+UnknownSource+WildcardAccount+WildcardSide+_certCreditTotal+_certDebitTotal+_certResolved+_certUnresolved+_creditTotal+_debitTotal+certifyJournalText+certifyJournalTextIn+checkedEntry+checkedEntryIn+checkedEntryText+checkedEntryTextIn+checkedJournal+checkedJournalIn+postingAllowedIn+reconcileSources
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.Csv.txt view
@@ -0,0 +1,5 @@+parseJournalCsv+parseJournalCsvWith+parseNotedJournalCsv+scientificAmount+splitTrim
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Convert.txt view
@@ -0,0 +1,13 @@+AmbiguousAccount+BadAmount+ConvError+MalformedCsv+UnknownAccount+UnknownSide+concreteAccountTitles+journalFromSides+markerForSide+normalizeTitle+parseAccountTitle+parseSide+postingFromSide
+ test/fixtures/export-surface-p2/ExchangeAlgebra.IO.Input.Admission.txt view
@@ -0,0 +1,19 @@+AdmissionError+AdmissionFailure+Admitted+EntityId+EvidenceAmbiguous+EvidenceDiffers+EvidenceMismatch+EvidenceMissing+Imbalanced+NonPositiveAmount+NotPostableAccount+PeriodId+TxId+TxKey+WildcardSide+admit+admittedJournal+checkEvidence+failureErrors
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.Rule.txt view
@@ -0,0 +1,13 @@+CarryError+NegativeValue+NonFiniteValue+ResultOutOfRange+SideTotals+WildcardSide+carryBefore+carryEntries+hatTotal+notTotal+sideTotal+sideTotalsBy+transferEntries
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.Transfer.txt view
@@ -0,0 +1,12 @@+CarryError+NegativeValue+NonFiniteValue+ResultOutOfRange+SideTotals+WildcardSide+carryBefore+carryEntries+hatTotal+notTotal+sideTotal+sideTotalsBy
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Journal.txt view
@@ -0,0 +1,113 @@+.#+.*+.++.-+./=+.<+.<=+.==+.>+.>=+.@+.^+.|+:<+:@+<++Accum+Amount+AxisDecompose+AxisKey+BaseClass+BaseForSingleHat+BasePart+CNY+CountUnit+Dollar+Element+Euro+Generic+Hashable+Hat+HatBase+HatBaseClass+HatNot+HatVal+Journal+Name+Not+Note+NoteAxisKey+NoteAxisPosting+Redundant+Subject+Yen+Zero+_base+_hat+addAccum+axisIsWildcard+bar+barWithin+base+compareBase+compareElement+compareHatBase+compress+customError+decTo+emptyAccum+equal+exactAccum+filterByAxis+filterWithNote+fromList+fromMap+gather+hash+hashWithSalt+hat+haveWildcard+ignoreWildcard+isHat+isNot+isPlank+isWildcard+isZeroValue+map+maxElement+merge+mergeAccum+minElement+mkJournal+negateAccum+norm+plank+postFromNetBy+postingUpperBound+projWithBase+projWithBaseNetNorm+projWithNote+projWithNoteBase+projWithNoteBaseNetNorm+replaceNotes+revHat+roundAccum+roundRational+showValue+sigma+sigma2When+sigmaM+sigmaOn+sigmaOnFromMap+signAccum+splitAccum+toAlg+toAxisKeys+toHat+toMap+toNot+toNoteAxisKeys+valueError+wildcard+zeroValue
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.Annealing.txt view
@@ -0,0 +1,10 @@+Annealing+AnnealingConfig+acAccept+acDirection+acNeighbor+acSchedule+acSeed+acSteps+geometricCooling+metropolis
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.GA.txt view
@@ -0,0 +1,14 @@+GA+GAConfig+defaultGAConfig+gaBounds+gaCrossoverRate+gaDirection+gaEliteCount+gaGenerations+gaInitSpread+gaMutationRate+gaMutationScale+gaPopulationSize+gaSeed+gaTournamentSize
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Optimize.txt view
@@ -0,0 +1,8 @@+Candidate+Config+Direction+Maximize+Minimize+Solver+optimize+orient
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Posting.txt view
@@ -0,0 +1,13 @@+AboveBound+HatSide+InvalidValue+NotSide+PostSide+Posted+PostedError+Posting+entry+posted+postingAlg+sideHat+unPosted
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Bookkeeping.txt view
@@ -0,0 +1,12 @@+accountLedgerRows+accountLedgerRowsJournal+compoundTrialBalanceRows+journalRows+postClosingTrialBalanceRows+worksheetRows+writeAccountOf+writeAccountOfJournal+writeCompoundTrialBalance+writeJournal+writePostClosingTrialBalance+writeWorksheet
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Csv.txt view
@@ -0,0 +1,2 @@+csvTranspose+writeCSV
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Render.Simulation.txt view
@@ -0,0 +1,5 @@+Header+writeFuncResults+writeFuncResultsWithContext+writeIOMatrix+writeTermIO
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Group.txt view
@@ -0,0 +1,36 @@+DeductionRow+DepreciableAssetsGroup+GrossRow+GroupRow+GroupRowKind+GroupedPresentation+IncomeTaxesGroup+NetPurchasesGroup+NetRow+NetSalesGroup+PresentationGroup+PresentationGroupDef+RelativeAmount+SubgroupRow+TradeReceivablesGroup+addGross+defaultPresentationGrouping+gpBlocks+gpConsumed+gpRootTotals+grAmount+grKind+groupNormalSide+groupingForDivisions+lookupGroupDef+pgDeductions+pgDivision+pgGross+pgKey+pgLabel+pgParent+presentGroups+presentationGroupOf+raBelowZero+raMagnitude+relativeTo
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Metric.txt view
@@ -0,0 +1,22 @@+CustomMetric+DerivedMetric+GrossProfitMetric+InvalidMetricValue+MetricError+MetricId+MetricNotAvailableAtStage+OrdinaryProfitMetric+PeriodBreakEven+PeriodLoss+PeriodProfit+PeriodResult+PeriodResultMetric+ResidualDerivedCoordinate+WildcardMetricAccount+WildcardMetricSide+legacyTitlesForMetric+metricForLegacyTitle+metricIdText+mkMetricId+periodResultOf+periodResultOfAlg
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Reporting.Presentation.txt view
@@ -0,0 +1,111 @@+AccountRelabeled+AccountingFramework+BalanceAllocated+BlankPresentationEvidence+BlankPresentationRationale+CanonicalEnglish+CanonicalJapanese+Combined+ConflictingPresentationInstruction+ContextualCreditSection+ContextualDebitSection+ContraPresentationApplied+ContraPresentationRule+CurrentAssetsSection+CurrentLiabilitiesSection+CustomMetricLabel+DuplicateMetricIdentity+DuplicatePresentationAllocation+EquitySection+ExpenseSection+FinancialStatements+InvalidPresentationAllocation+InvalidStatementValue+InvalidSubtotalDefinition+JGAAP+JcciSecondGradeReport+LabelOverridden+MaterialityApplied+MaterialityDecision+MaterialityTreatment+MissingPresentationAccount+MissingPresentationAllocation+NetAgainst+NetContraAgainst+NoncurrentAssetsSection+NoncurrentLiabilitiesSection+PeriodResultUnavailable+PresentContraSeparately+PresentGross+PresentSeparately+PresentationAllocation+PresentationAuditEvent+PresentationIssue+PresentationProfile+PresentationRelabel+ReciprocalAccountsEliminated+ReportingContext+ReportingScope+RequireAllTitlesPresent+RevenueSection+Standalone+StatementLine+StatementSection+StatementSubtotal+SubtotalCalculated+SubtotalCoverage+SubtotalDefinition+TreatAbsentAsZero+UnbalancedStatementInput+UnexpectedPresentationAllocation+UnlabelledCustomMetric+UnpresentableBalance+UnreconciledPresentation+WildcardStatementBase+_allocationAccount+_allocationCurrent+_allocationEvidence+_allocationNoncurrent+_contraPresentationRules+_customMetricIdentity+_customMetricLabelEnglish+_customMetricLabelJapanese+_customMetricLabels+_lineAccount+_lineAmount+_lineIsDeduction+_lineLabel+_lineSection+_lineSide+_materialityAccount+_materialityDecisions+_materialityRationale+_materialityTreatment+_maturitySensitiveTitles+_presentationAllocations+_presentationAudit+_presentationProfile+_presentationRelabels+_relabelRationale+_relabelSource+_relabelTarget+_reportingFramework+_reportingScope+_statementFramework+_statementLines+_statementProfile+_statementScope+_statementSubtotalBalance+_statementSubtotalLabel+_statementSubtotalMetric+_statementSubtotals+_statementTrialBalanceStage+_subtotalCoverage+_subtotalCreditTitles+_subtotalDebitTitles+_subtotalDefinitions+_subtotalMetric+jcciSecondGradeContext+metricLabel+present+presentationLabel
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Analysis.txt view
@@ -0,0 +1,2 @@+leontiefInverse+rippleEffect
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Engine.txt view
@@ -0,0 +1,49 @@+Copy+DoNothing+Event+InitVariables+Inner+Modify+StateSpace+StateTime+Updatable+UpdatableSTArray+UpdatableSTRef+UpdatePattern+_unwrapUArray+_unwrapURef+_wrapUArray+_wrapURef+copy+event+eventAll+fstEvent+getUBounds+initAll+initT+initTerm+initialize+lastEvent+lastT+lastTerm+modify+modifyArray+modifyUArray+modifyURef+newUArray+newURef+nextTerm+prevTerm+randomSeeds+readUArray+readURef+runScenarios+runScenariosWithSpill+runSimulation+runSimulationWithSpill+unwrap+update+updateAll+updatePattern+writeUArray+writeURef
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Lite.txt view
@@ -0,0 +1,37 @@+Carry+Field+GLiteCommit+GLiteFreeze+GLiteInit+HK+InitT+LiteWorld+Par+ParChunk+RefT+ResetEach+Sequential+SimSpec+SnapT+Stage+UpdateEach+carry+gCommit+gFreeze+gInit+mkSimSpec+resetEach+runLite+runLiteFold+runLiteWithPolicy+runLiteWithPolicyObs+specLedger+specParallel+specSeed+specStages+specTerms+stage+stageFor+stageName+stageOf+updateEach
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Network.txt view
@@ -0,0 +1,64 @@+CoefOptions+CoefOutsideNetwork+DuplicateCoefficient+DuplicateEdge+FlowOptions+IndustrialEconomy+IndustrialFlows+IndustrialOptions+InputCoefficients+NegativeCoefficient+NetworkError+SelfLoop+TaxRate+TradeNetwork+buyersOf+circulant+coefRange+coefficient+coefficientsFromTable+completeNetwork+defaultCoefOptions+defaultFlowOptions+defaultIndustrialOptions+edgeCount+edges+erdosRenyi+firms+flowFinalDemand+flowInput+flowOutput+flowTrade+flowValueAdded+foInputShare+foMeanFinalDemand+fromCoefficientMatrix+hawkinsSimon+ieNetwork+ieSector+ieSize+industrialEdges+industrialFlows+industrialFlowsWith+industrialNetwork+industrialNetworkWith+inputCoefficients+inputsOf+ioExponent+ioFlow+kRegular+networkFromTable+nodes+parseCoefCsv+parseEdgeCsv+randomCoefficients+readCoefCsv+readEdgeCsv+scaleFree+sectorBlock+sigmaEdges+suppliersOf+taxDenominator+taxNumerator+taxOf+tradeNetwork
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Policy.txt view
@@ -0,0 +1,16 @@+Compaction+CompressClosedTerms+FullAudit+HasTermAxis+LedgerPolicy+RetainAll+RetainRecent+Retention+TermOf+compaction+defaultLedgerPolicy+policySpillOptions+restoreLedger+retain+spillTo+termOf
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Random.txt view
@@ -0,0 +1,3 @@+normal+normal'+updateGen
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Spill.txt view
@@ -0,0 +1,37 @@+ChunkGap+ChunkOutOfOrder+ChunkOverlap+DeleteSpilledChunk+KeepRecentTerms+NoDelete+SpillDecodeFailure+SpillDeletePolicy+SpillEmptyRange+SpillOptions+SpillRangeError+SpillRangeIssue+SpillReadError+defaultBinarySpillWriter+defaultSpillWriter+mkBinarySpillOptions+mkSpillOptions+readBinarySpillFile+readBinarySpillFileChecked+renderSpillReadError+restoreJournalFromBinarySpill+restoreJournalFromBinarySpillChecked+spillDeleteDecision+spillDeletePolicy+spillDeleteRange+spillErrorChunks+spillErrorMessage+spillErrorOffset+spillEveryTerms+spillExtract+spillExtractChunk+spillFilePath+spillRangeCurrent+spillRangeIssue+spillRangePrevious+spillWriteChunk+stepBackWith
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.Visualize.txt view
@@ -0,0 +1,15 @@+FileName+GridColumns+GridMatrix+Header+Label+TimeSeries+TimeSerieses+Title+gridLine+plotLine+plotLineVector+plotMultiLines+plotWldsDiffLine+writeFuncResults+writeFuncResultsWithContext
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Simulate.txt view
@@ -0,0 +1,89 @@+ChunkGap+ChunkOutOfOrder+ChunkOverlap+Copy+DeleteSpilledChunk+DoNothing+Event+InitVariables+Inner+KeepRecentTerms+Modify+NoDelete+SpillDecodeFailure+SpillDeletePolicy+SpillEmptyRange+SpillOptions+SpillRangeError+SpillRangeIssue+SpillReadError+StateSpace+StateTime+Updatable+UpdatableSTArray+UpdatableSTRef+UpdatePattern+_unwrapUArray+_unwrapURef+_wrapUArray+_wrapURef+copy+defaultBinarySpillWriter+defaultSpillWriter+event+eventAll+fstEvent+getUBounds+initAll+initT+initTerm+initialize+lastEvent+lastT+lastTerm+leontiefInverse+mkBinarySpillOptions+mkSpillOptions+modify+modifyArray+modifyUArray+modifyURef+newUArray+newURef+nextTerm+normal+normal'+prevTerm+randomSeeds+readBinarySpillFile+readBinarySpillFileChecked+readUArray+readURef+renderSpillReadError+rippleEffect+runScenarios+runScenariosWithSpill+runSimulation+runSimulationWithSpill+spillDeleteDecision+spillDeletePolicy+spillDeleteRange+spillErrorChunks+spillErrorMessage+spillErrorOffset+spillEveryTerms+spillExtract+spillExtractChunk+spillFilePath+spillRangeCurrent+spillRangeIssue+spillRangePrevious+spillWriteChunk+stepBackWith+unwrap+update+updateAll+updateGen+updatePattern+writeUArray+writeURef
+ test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Balance.txt view
@@ -0,0 +1,12 @@+AccountBalance+CreditBalance+DebitBalance+NoBalance+accountBalances+addPair+balanceAmount+balanceFor+balancePair+balanceSide+combineBalances+netPair
+ test/fixtures/export-surface-p2/ExchangeAlgebra.TrialBalance.Validation.txt view
@@ -0,0 +1,35 @@+AbnormalBalanceWithReclassificationRule+AccountBalance+AfterClosing+AmbiguousReclassification+BeforeClosing+BlankTemporaryExplanation+ClosingDeviceResidual+CreditBalance+DebitBalance+DerivedCoordinateResidual+ExplainedTemporaryBalance+InapplicableReclassificationRule+InvalidTrialBalanceValue+MaturityEvidenceRequired+MissingMaturityEvidence+NoBalance+ReciprocalMismatch+ReclassificationRule+SideReclassificationRule+StandaloneReciprocalBalance+TBFinding+TrialBalanceInput+TrialBalanceStage+UnbalancedTrialBalance+UnclosedNominalBalance+UnexplainedAbnormalBalance+UnresolvedTemporaryBalance+WildcardTrialBalanceAccount+WildcardTrialBalanceSide+_maturityEvidenceTitles+_reclassificationRules+_temporaryBalanceExplanations+_trialBalanceElement+_trialBalanceStage+trialBalanceFindings
+ test/fixtures/export-surface-p2/ExchangeAlgebra.Value.txt view
@@ -0,0 +1,13 @@+MalformedMoney+MoneyDecimal+MoneyDouble+MoneyExponentOutOfRange+MoneyParseError+MoneyScaleOutOfRange+NegativeMoney+bankersRound+ceilingRound+moneyDecimalFromScientific+moneyDecimalFromText+toDecimal+toDouble
+ test/fixtures/export-surface-p2/ExchangeAlgebra.txt view
@@ -0,0 +1,675 @@+.#+.*+.++.-+.->+./=+.<+.<=+.==+.>+.>=+.@+.^+.~+:<+:@+<++<=>+<@+AbnormalBalanceWithReclassificationRule+AccountBalance+AccountBase+AccountDivision+AccountRole+AccountSemantics+AccountSpec+AccountTitle+AccountTitles+AccountsPayable+AccountsReceivable+AccruedConsumptionTax+AccruedCorporateIncomeTaxes+AccruedExpenses+AccruedRevenue+Accum+AccumulatedDepreciation+AdditionalIncomeTaxesForPriorPeriods+AdvancesPaid+AdvancesReceived+AdvertisingExpense+AffiliateStocks+AfterClosing+Alg+AllowanceForBonuses+AllowanceForDoubtfulAccounts+AllowanceForProductWarranties+AllowanceForRepairs+AllowanceForRetirementBenefits+AmbiguousReclassification+AmortizationExpense+AmortizationOfGoodwill+AmortizationOfPatents+AmortizationOfSoftware+Amount+Assets+AttributionAccount+AvailableForSaleSecurities+AxisDecompose+AxisKey+BackwardFlow+BadDebtLoss+BankOverdraft+BaseClass+BaseForSingleHat+BasePart+BeforeClosing+BlankPresentationEvidence+BlankPresentationRationale+BlankTemporaryExplanation+Bonuses+BonusesPayable+BookkeepingControlClass+BranchCurrentAccount+Building+BusinessCommencementExpenses+BusinessTrip+CNY+CapitalStock+CapitalSurplus+Cash+CashOverShort+CentralBankNotePayable+CentralBankPaymentExpense+CentralBankPaymentIncome+CloseByDivision+ClosingDevice+ClosingDeviceResidual+ClosingFlip+ClosingKeep+ClosingOnly+ClosingRule+ClosingSide+Combined+CommunicationExpenses+Commutation+ConflictingPresentationInstruction+ConsolidationOnly+ConstructionFundReserve+ConstructionInProgress+ConsumptionExpenditure+ConsumptionTaxPaid+ConsumptionTaxReceived+ConsumptionTaxRefundReceivable+ContextDependentHomeSide+ContextualCreditSection+ContextualDebitSection+ContextualPresentation+ContraAccount+ContraAccountForGuaranteeObligations+ContractAssets+CorporateIncomeTaxes+Cost+CostOfServices+CountUnit+Credit+CreditBalance+CreditCardReceivable+CumulativeTranslationAdjustment+Current+CurrentAssetsSection+CurrentDeposits+CurrentLiabilitiesSection+Debit+DebitBalance+DeferredTaxAssets+DeferredTaxLiabilities+DeliveryExpenses+DepositInterestEarned+DepositPayable+Deposits+DepositsInSpecialAccounts+DepositsReceived+DepositsReceivedFromOfficers+Depreciation+DerivedCoordinateResidual+DerivedPresentation+DevelopmentExpenses+DirectionEncoding+DishonoredNotesReceivable+DivBy+DividendEqualizationReserve+DividendsReceived+DivisionSemantics+Dollar+DuplicateMetricIdentity+DuplicatePresentationAllocation+EarnedSurplus+ElectronicallyRecordedObligations+ElectronicallyRecordedReceivable+Element+EngineGeneratedOnly+EntertainmentExpenses+EquipmentInvestment+Equity+EquityInEarningsOfInvestee+EquitySection+Euro+ExBaseClass+Exchange+ExpenseSection+ExplainedTemporaryBalance+FinancialStatements+Fixed+FixedCurrent+FixedHomeSide+Fixtures+ForeignExchangeGains+ForeignExchangeLosses+GainOnBargainPurchase+GainOnConstructionGrants+GainOnDonationOfFixedAssets+GainOnNationalSubsidies+GainOnSalesOfFixedAssets+GainOnSalesOfInvestmentSecurities+GainOnSalesOfSecurities+GainOnValuationOfSecurities+GeneralReserve+Generic+GiftCertificatesReceived+Gold+GoodsInTransit+Goodwill+GovernmentService+GrossProfit+GuaranteeDepositsReceived+GuaranteeObligations+Hashable+Hat+HatBase+HatBaseClass+HatNot+HatVal+HeadOfficeCurrentAccount+HeldToMaturityBonds+HomeSideSemantics+IN+InapplicableReclassificationRule+IncomeSummary+IncomeTaxesAdjustment+IncomeTaxesRefundReceivable+IncorporationExpenses+InsuranceExpense+InsuranceGain+InterestEarned+InterestExpense+InterestOnSecurities+InvalidCoefficient+InvalidPortion+InvalidPosting+InvalidPresentationAllocation+InvalidStatementValue+InvalidSubtotalDefinition+InvalidTrialBalanceValue+InventoryShrinkageLoss+InvestmentInAssociate+InvestmentSecurities+Land+LandRentPaid+LandRentReceived+LeaseExpenses+LeaseObligations+LeasedAssets+LeaseholdRights+LegalCapitalSurplus+LegalRetainedEarnings+Liability+LoansPayable+LoansReceivable+LongTermLoansPayable+LongTermLoansReceivable+LongTermNationalBonds+LongTermNationalBondsPayable+LongTermOtherPayables+LongTermPrepaidExpenses+LossOnFire+LossOnReductionOfFixedAssets+LossOnRetirementOfFixedAssets+LossOnSalesOfElectronicallyRecordedReceivables+LossOnSalesOfFixedAssets+LossOnSalesOfInvestmentSecurities+LossOnSalesOfNotesReceivable+LossOnSalesOfReceivables+LossOnSalesOfSecurities+LossOnValuationOfMerchandise+LossOnValuationOfSecurities+MS+Machinery+MaintenanceExpenses+MalformedMoney+MaturityEvidenceRequired+MeetingExpenses+MembershipFees+MerchandiseInventory+MiscellaneousExpenses+MiscellaneousIncome+MiscellaneousLoss+MissingMaturityEvidence+MissingPresentationAccount+MissingPresentationAllocation+MoneyDecimal+MoneyDouble+MoneyExponentOutOfRange+MoneyParseError+MoneyScaleOutOfRange+MulBy+Name+NationalBondInterestEarned+Nearest+Nearly+NegativeMoney+NegativeValue+NetDefinedBenefitAsset+NetDefinedBenefitLiability+NetIncome+NetIncomeAttributableToNCI+NetLoss+NetLossAttributableToNCI+NewspaperBooksExpenses+NoBalance+NoClose+NoFixedHomeSide+NoPostingSide+NoStatementDivision+NonControllingInterests+NonFiniteBalance+NonFiniteNet+NonFiniteResult+NonFiniteValue+NonOperatingElectronicallyRecordedObligations+NonOperatingElectronicallyRecordedReceivable+NonOperatingNotesPayable+NonOperatingNotesReceivable+NoncurrentAssetsSection+NoncurrentLiabilitiesSection+Not+NotPostable+NotPresented+NotesLoansPayable+NotesLoansReceivable+NotesPayable+NotesReceivable+OUT+OperatingExpenses+OperatingRevenue+OrdinaryAccount+OrdinaryPosting+OrdinaryProfit+Other+OtherCapitalSurplus+OtherPayables+OtherReceivables+OverlappingRows+OverlappingRules+PIMO+PS+Patent+PaymentFees+PaymentsOnBehalf+PeriodResult+PeriodResultMissing+PeriodResultUnavailable+PettyCash+Portion+PostingCapability+PrepaidCorporateIncomeTaxes+PrepaidExpenses+PrepaidPensionCost+PresentationIssue+Products+PropertyTaxPayable+ProvisionForBonuses+ProvisionForDoubtfulAccounts+ProvisionForProductWarranties+ProvisionForRepairs+PurchaseAllowances+PurchaseRebates+Purchases+RationalAccum+RawMaterials+ReceiptFee+ReciprocalAccount+ReciprocalMismatch+ReclassificationRule+RecoveryOfBadDebts+Redundant+RefundLiabilities+RefundOfIncomeTaxes+Relabel+RentExpense+RentalIncome+RepairFundReserve+RepairsExpense+ReportingContext+ReportingEligibility+ReportingScope+ReportingSubtotal+ResearchAndDevelopmentExpenses+ReserveDepositPayable+ReserveDepositReceivable+ReserveForDepreciation+RetainedEarnings+RetirementBenefitExpenses+Revenue+RevenueSection+ReversalOfAllowanceForBonuses+ReversalOfAllowanceForDoubtfulAccounts+ReversalOfAllowanceForProductWarranties+ReversalOfAllowanceForRepairs+Rounding+Sales+SalesAllowances+SalesCost+SalesRebates+Securities+SecurityDepositsPaid+ServiceRevenue+SettleError+SettleRule+SettlementBatch+ShortTermLoansPayable+ShortTermLoansReceivable+ShortTermNationalBonds+ShortTermNationalBondsPayable+Side+SideReclassificationRule+SignedNet+Software+SoftwareInProgress+Standalone+StandaloneReciprocalBalance+StatementDivision+StatementEligible+StatementLine+StatementRowsError+StatementSection+StatutoryWelfareExpenses+StockInvestment+StockIssuanceCosts+StockSubscriptionDeposits+StorageExpenses+Structures+Subject+SubsidiaryStocks+SubsidyExpense+SubsidyIncome+SuppliesExpenses+SuppliesOnHand+SuspenseAccount+SuspenseOrClearingAccount+SuspensePayments+SuspenseReceipts+TBFinding+TaxesExpense+TaxesRevenue+TimeDeposits+ToMultipleOf+Tolerance+ToolsAndInstruments+Trademark+TradingSecurities+TransferApplyError+TransferEnds+TransferError+TransferFailed+TransferJournalError+TransferNoteMoves+TransferRow+TransferRule+TransferRuleError+TransferRules+TransferScale+TransferShare+TransferTable+TransferTableError+TrialBalanceInput+TrialBalanceStage+UnbalancedStatementInput+UnbalancedTrialBalance+UnclassifiedAccount+UnclosedNominalBalance+UnearnedRevenue+UnexpectedPresentationAllocation+UnexplainedAbnormalBalance+UnlabelledCustomMetric+UnpaidDividends+UnpresentableBalance+UnreconciledPresentation+UnresolvedTemporaryBalance+UnroundableAmount+UtilitiesExpense+ValuationDifferenceOnOtherSecurities+ValueAdded+ValueError+Vehicle+WageEarned+WageExpenditure+WelfareExpenses+WildcardPosting+WildcardStatementBase+WildcardTrialBalanceAccount+WildcardTrialBalanceSide+WorkInProcess+Yen+Zero+_base+_contraPresentationRules+_customMetricLabels+_hat+_hatBase+_lineAccount+_lineAmount+_lineIsDeduction+_lineLabel+_lineSection+_lineSide+_materialityDecisions+_maturityEvidenceTitles+_maturitySensitiveTitles+_presentationAllocations+_presentationAudit+_presentationProfile+_presentationRelabels+_reclassificationRules+_reportingFramework+_reportingScope+_statementFramework+_statementLines+_statementProfile+_statementScope+_statementSubtotals+_statementTrialBalanceStage+_subtotalDefinitions+_temporaryBalanceExplanations+_trialBalanceElement+_trialBalanceStage+_val+absTol+accountAliases+accountBalances+accountDescriptions+accountLedgerRows+accountLedgerRowsJournal+accountSemantics+accountSpec+accountSpecMap+addAccum+asAliases+asClosing+asDescription+asDivision+asFixedCurrent+asIsContra+asLabelJa+asNameEn+asNameJa+asemDivisionSemantics+asemHomeSideSemantics+asemPostingCapability+asemReportingEligibility+asemRoles+axisIsWildcard+balance+balanceAmount+balanceBy+balanceFor+balanceMapBy+balanceSide+balanceWithin+bankersRound+bar+barWithin+base+bases+ceilingRound+classifyAccountContra+classifyAccountDivision+closingEntries+closingSide+collapseEntries+collapseNetEntries+compareBase+compareElement+compareHatBase+compoundTrialBalanceRows+compress+concreteAccountTitles+csvTranspose+customError+decBy+decL+decM+decP+decR+defaultSide+defaultTolerance+diffRL+diffRLWithin+divideBy+emptyAccum+equal+exactAccum+extendBy+filter+financialStatementRows+fixedCurrent+foldEntries+foldEntriesToMap+fromList+getAccountTitle+hash+hashWithSalt+hat+haveWildcard+ignoreWildcard+isContra+isErrorValue+isHat+isNearly+isNearlyNum+isNot+isWildcard+isZero+isZeroValue+jcciSecondGradeContext+journalRows+map+mapBasePart+mapMaybePosting+mapPosting+maxElement+merge+mergeAccum+minElement+mkTransferRules+mkTransferTable+moneyDecimalFromScientific+moneyDecimalFromText+negateAccum+netPairMapBy+norm+pimoFlip+pimoFromDivision+postClosingTrialBalanceRows+postFromNetBy+postingUpperBound+present+proj+projByAccountTitle+projCapitalStock+projContra+projContraAssets+projCredit+projCurrentAssets+projCurrentLiability+projDebit+projDeferredAssets+projFixedAssets+projFixedLiability+projNetNorm+rationalAddAccum+rationalRoundAccum+relTol+relabel+retainedEarningsRule+revHat+roundAccum+roundRational+roundScaled+ruleFrom+ruleScale+ruleTo+rulesToList+scaleBy+setAccountTitle+settleEntries+settlementSteps+showValue+sigma+sigma2When+sigmaFromMap+signAccum+splitAccum+sumValues+switchSide+tableRows+toASCList+toAxisKeys+toDecimal+toDouble+toHat+toList+toNot+transferEntries+transferEntriesBy+transferEntriesByWith+transferEntriesWith+trialBalanceFindings+unionsMerge+vals+valueError+whatDiv+whatPIMO+whichSide+wildcard+worksheetRows+writeAccountOf+writeAccountOfJournal+writeCSV+writeCompoundTrialBalance+writeFinancialStatements+writeJournal+writePostClosingTrialBalance+writeWorksheet+zeroValue+|%
+ test/fixtures/export-surface-p2/README.md view
@@ -0,0 +1,51 @@+# P2 public export snapshots++Each `<Module>.txt` records the names in the `exports:` section of that+module's GHC interface. The generator expands grouped exports so the files+include values, types, constructors, record fields, class methods, associated+types, and names re-exported from another module. The generator removes GHC's+defining-module qualification: relocation can change the origin while the+importable name stays the same. The comparison checks the sorted set in+both directions: an added or removed name fails.+`counts.tsv` lists the pinned modules and the number of names in each+snapshot. New exposed modules can be added without changing these snapshots;+the check still requires every pinned module to remain exposed.++From the repository root, build the library and refresh snapshots when an+intentional public API change has been reviewed:++```bash+stack build exchangealgebra+STACK_ARGS="--system-ghc --no-install-ghc" python3 tools/check-export-surface.py --update+```++`--update` rewrites only the modules pinned in `counts.tsv`. To add an exposed+module to the pinned set, use `--update --add <module>`, replacing `<module>`+with its full module name. Repeat `--add` to add multiple modules. To remove+a module from the pinned set, manually delete its fixture and its row in+`counts.tsv`.++Run the check after the build:++```bash+STACK_ARGS="--system-ghc --no-install-ghc" python3 tools/check-export-surface.py+```++`package.yaml` lets hpack infer the ordinary exposed modules. The generator+reads the resulting `exchangealgebra.cabal` list and adds the conditional+`ExchangeAlgebra.Simulate.Visualize` module when visualization is enabled.+After a build with `--flag exchangealgebra:-visualize`, set `EA_VISUALIZE=0`+for the check. A missing or unexpected fixture fails, except that the+visualization fixture is retained when that flag is off.++`orphans.txt` records the source path and instance declaration in every+`-Worphans` warning from the library, tests, examples, and benchmarks in the+default build. The check permits removal or relocation of an existing orphan+and rejects increases in the count of each instance declaration. With+`EA_VISUALIZE=0`, it builds only+the library and its tests, matching the CI job with visualization disabled.+Refresh it only after reviewing an intentional instance change:++```bash+STACK_ARGS="--system-ghc --no-install-ghc" python3 tools/check-orphans.py --update+```
+ test/fixtures/export-surface-p2/counts.tsv view
@@ -0,0 +1,46 @@+module names+ExchangeAlgebra.Accounting.PostingPolicy 7+ExchangeAlgebra.Accounting 359+ExchangeAlgebra.Algebra.Base.Account.JcciAliases 1+ExchangeAlgebra.Algebra.Base.Account.Registry 23+ExchangeAlgebra.Algebra.Base.Account.Types 46+ExchangeAlgebra.Algebra.Base.Element 277+ExchangeAlgebra.Algebra.Base 54+ExchangeAlgebra.Algebra.Internal 127+ExchangeAlgebra.Algebra.Transfer.Rule 36+ExchangeAlgebra.Algebra.Transfer 18+ExchangeAlgebra.Algebra 128+ExchangeAlgebra.Assist.Descriptions 1+ExchangeAlgebra.Assist 16+ExchangeAlgebra.Bookkeeping 19+ExchangeAlgebra.Consolidation.Worksheet 73+ExchangeAlgebra.Convert.Checked 40+ExchangeAlgebra.Convert.Csv 5+ExchangeAlgebra.Convert 13+ExchangeAlgebra.IO.Input.Admission 19+ExchangeAlgebra.Journal.Transfer.Rule 13+ExchangeAlgebra.Journal.Transfer 12+ExchangeAlgebra.Journal 113+ExchangeAlgebra.Optimize.Annealing 10+ExchangeAlgebra.Optimize.GA 14+ExchangeAlgebra.Optimize 8+ExchangeAlgebra.Posting 13+ExchangeAlgebra.Render.Bookkeeping 12+ExchangeAlgebra.Render.Csv 2+ExchangeAlgebra.Render.Simulation 5+ExchangeAlgebra.Reporting.Group 36+ExchangeAlgebra.Reporting.Metric 22+ExchangeAlgebra.Reporting.Presentation 111+ExchangeAlgebra.Simulate.Analysis 2+ExchangeAlgebra.Simulate.Engine 49+ExchangeAlgebra.Simulate.Lite 37+ExchangeAlgebra.Simulate.Network 64+ExchangeAlgebra.Simulate.Policy 16+ExchangeAlgebra.Simulate.Random 3+ExchangeAlgebra.Simulate.Spill 37+ExchangeAlgebra.Simulate.Visualize 15+ExchangeAlgebra.Simulate 89+ExchangeAlgebra.TrialBalance.Balance 12+ExchangeAlgebra.TrialBalance.Validation 35+ExchangeAlgebra.Value 13+ExchangeAlgebra 675
+ test/fixtures/export-surface-p2/orphans.txt view
@@ -0,0 +1,19 @@+examples/basic/elementaryBookkeepingEx4.hs: instance Note Day+examples/basic/elementaryBookkeepingEx5.hs: instance Element Company+examples/basic/elementaryBookkeepingEx5.hs: instance ExBaseClass HatBaseC+examples/basic/fixedPriceIO.hs: instance BaseClass Company+examples/basic/fixedPriceIO.hs: instance Element Company+examples/basic/fixedPriceIO.hs: instance ExBaseClass HatBase2+examples/basic/fixedPriceIO.hs: instance StateTime Term+examples/basic/fixedPriceIOSpill.hs: instance BaseClass Company+examples/basic/fixedPriceIOSpill.hs: instance Element Company+examples/basic/fixedPriceIOSpill.hs: instance ExBaseClass HatBase2+examples/basic/fixedPriceIOSpill.hs: instance StateTime Term+examples/optimization/CGE/CGE.hs: instance StateTime Term+src/ExchangeAlgebra/Algebra/Base/Element.hs: instance Hashable Day+src/ExchangeAlgebra/Algebra/Base/Element.hs: instance Hashable TimeOfDay+test/Spec.hs: instance BaseClass SimCompany+test/Spec.hs: instance Element SimCompany+test/Spec.hs: instance ExBaseClass SimHatBase2+test/Spec.hs: instance StateTime SimTerm+test/Transfer/RuleSpec.hs: instance ExBaseClass TestBase
− test/fixtures/post-land2/README.md
@@ -1,9 +0,0 @@-# post-land2 fixtures--`suggest.tsv` は **Land 2 (Definition 7 contra 改訂) 後の期待出力そのもの**。-Spec の `testLand2SuggestClosedDiff` が byte 一致を検査し, pre-land1 fixture との-差分が contra 2 科目の description 変更で説明できることを併せて検査する。--注意: header 行は Land 1 の golden 形式 (`# pre-land1 ...; commit 2d91646...`) を-共有している (Spec の `goldenHeader`/`goldenCommit` と byte 一致させるため)。-「pre-land1」は **format の名前**であり, 内容は Land 2 時点 (ab6043f 以降) の出力。
− test/fixtures/post-land2/suggest.tsv
@@ -1,969 +0,0 @@-# pre-land1 suggestAccounts over corpus (query, total matches, top-10 titles); commit 2d9164642f2862725c653e496976770f6e2c7d6f-"Communication"); 1 CommunicationExpenses-'AccountsPayable' 1 ReserveDepositPayable-'AccumulatedDepreciation' 1 ReserveForDepreciation-'AllowanceForDoubtfulAccounts' 1 ReversalOfAllowanceForDoubtfulAccounts-'CommunicationExpenses' 1 Commutation-'Commutation' 1 CommunicationExpenses-'DepositsReceived') 1 DepositPayable-'Depreciation' 1 AmortizationExpense-'MerchandiseInventory' 1 Products-'PaymentFees') 1 ReceiptFee-'Products' 1 MerchandiseInventory-'ReceiptFee' 1 PaymentFees-'ReserveForDepreciation' 1 AccumulatedDepreciation-(3 1 MerchandiseInventory-(also 1 CommunicationExpenses-(appropriated) 1 LegalRetainedEarnings-(contra): 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-(isContra). 1 AccumulatedDepreciation-(isContra); 1 AllowanceForDoubtfulAccounts-(net) 1 AllowanceForDoubtfulAccounts-(the 1 ReversalOfAllowanceForDoubtfulAccounts-(unpaid) 2 AccruedConsumptionTax,AccruedCorporateIncomeTaxes-(クレジット売掛金) 1 CreditCardReceivable-(ソフトウェア) 1 Software-(仕入) 1 Purchases-(仕入\/売上\/繰越商品) 1 Products-(付加価値。SNA\/マクロ系) 1 ValueAdded-(仮受消費税) 1 ConsumptionTaxReceived-(仮受金) 1 SuspenseReceipts-(仮払法人税等) 1 PrepaidCorporateIncomeTaxes-(仮払消費税) 1 ConsumptionTaxPaid-(仮払金) 1 SuspensePayments-(借入金) 1 LoansPayable-(備品) 1 Fixtures-(償却債権取立益) 1 RecoveryOfBadDebts-(分記法) 1 MerchandiseInventory-(利益準備金) 1 LegalRetainedEarnings-(前受収益), 1 UnearnedRevenue-(前受金) 1 AdvancesReceived-(前払費用), 1 PrepaidExpenses-(前払金) 1 AdvancesPaid-(受入預金 1 DepositPayable-(受取利息) 1 InterestEarned-(受取家賃) 1 RentalIncome-(受取手形) 1 NotesReceivable-(受取手数料。支払側は 1 ReceiptFee-(商品)。分記法用。3 1 Products-(商標権) 1 Trademark-(固定資産売却損) 1 LossOnSalesOfFixedAssets-(固定資産売却益) 1 GainOnSalesOfFixedAssets-(国債利息収入) 1 NationalBondInterestEarned-(国庫納付金収入。SNA\/マクロ系) 1 CentralBankPaymentIncome-(国庫納付金支出。SNA\/マクロ系) 1 CentralBankPaymentExpense-(土地) 1 Land-(売上) 1 Sales-(売上原価) 1 SalesCost-(売上総利益 1 GrossProfit-(売掛金) 1 AccountsReceivable-(小口現金) 1 PettyCash-(広告宣伝費) 1 AdvertisingExpense-(建物) 1 Building-(当座借越) 1 BankOverdraft-(当座預金) 1 CurrentDeposits-(当期純利益 1 NetIncome-(当期純損失 1 NetLoss-(手形借入金) 1 NotesLoansPayable-(手形売却損) 1 LossOnSalesOfNotesReceivable-(手形貸付金) 1 NotesLoansReceivable-(投資有価証券) 1 InvestmentSecurities-(持分法による投資利益). 1 EquityInEarningsOfInvestee-(持分法適用投資勘定). 1 InvestmentInAssociate-(支払利息) 1 InterestExpense-(支払家賃) 1 RentExpense-(支払手形) 1 NotesPayable-(支払手数料), 1 PaymentFees-(政府サービス。SNA\/マクロ系) 1 GovernmentService-(旅費交通費) 1 BusinessTrip-(旧 1 ReserveDepositPayable-(普通預金) 1 Deposits-(有価証券) 1 Securities-(未収入金) 1 OtherReceivables-(未収収益), 1 AccruedRevenue-(未払法人税等) 1 AccruedCorporateIncomeTaxes-(未払消費税) 1 AccruedConsumptionTax-(未払費用), 1 AccruedExpenses-(未払配当金) 1 UnpaidDividends-(未払金) 1 OtherPayables-(株式投資) 1 StockInvestment-(機械装置) 1 Machinery-(水道光熱費) 1 UtilitiesExpense-(法人税等) 1 CorporateIncomeTaxes-(消耗品費) 1 SuppliesExpenses-(消費支出。SNA\/マクロ系) 1 ConsumptionExpenditure-(減価償却引当金 1 ReserveForDepreciation-(減価償却累計額) 2 ReserveForDepreciation,AccumulatedDepreciation-(減価償却累計額), 1 AccumulatedDepreciation-(減価償却費) 2 Depreciation,AmortizationExpense-(準備預金, 2 ReserveDepositReceivable,ReserveDepositPayable-(為替換算調整勘定; 1 CumulativeTranslationAdjustment-(無形固定資産償却費 1 AmortizationExpense-(特許権) 1 Patent-(現金) 1 Cash-(現金過不足), 1 CashOverShort-(発行銀行券。SNA\/マクロ系) 1 CentralBankNotePayable-(発送費) 1 DeliveryExpenses-(短期借入金) 1 ShortTermLoansPayable-(短期国債) 1 ShortTermNationalBonds-(短期国債, 1 ShortTermNationalBondsPayable-(短期貸付金) 1 ShortTermLoansReceivable-(租税公課) 1 TaxesExpense-(租税収入。SNA\/マクロ系) 1 TaxesRevenue-(立替金) 1 PaymentsOnBehalf-(経常利益 1 OrdinaryProfit-(経過勘定) 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-(給料) 1 WageExpenditure-(繰越利益剰余金) 1 RetainedEarnings-(繰越商品). 1 MerchandiseInventory-(補助金収入。SNA\/マクロ系) 1 SubsidyIncome-(補助金支出。SNA\/マクロ系) 1 SubsidyExpense-(設備投資) 1 EquipmentInvestment-(評価勘定) 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-(買掛金) 1 AccountsPayable-(貸倒引当金), 1 AllowanceForDoubtfulAccounts-(貸倒引当金戻入). 1 ReversalOfAllowanceForDoubtfulAccounts-(貸倒引当金繰入) 1 ProvisionForDoubtfulAccounts-(貸倒損失) 1 BadDebtLoss-(賃金収入。SNA\/マクロ系) 1 WageEarned-(資本金) 1 CapitalStock-(車両運搬具) 1 Vehicle-(通信費 2 Commutation,CommunicationExpenses-(通信費). 1 CommunicationExpenses-(金) 1 Gold-(長期借入金) 1 LongTermLoansPayable-(長期国債) 1 LongTermNationalBonds-(長期国債, 1 LongTermNationalBondsPayable-(長期貸付金) 1 LongTermLoansReceivable-(間接法), 1 AccumulatedDepreciation-(関係会社株式). 1 InvestmentInAssociate-(雑損) 1 MiscellaneousLoss-(雑益) 1 MiscellaneousIncome-(雑費) 1 MiscellaneousExpenses-(電子記録債務) 1 ElectronicallyRecordedObligations-(電子記録債権) 1 ElectronicallyRecordedReceivable-(預り金) 1 DepositsReceived-(預金利息収入) 1 DepositInterestEarned-/ 22 Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,CentralBankNotePayable,ConsumptionExpenditure,SubsidyExpense,CentralBankPaymentExpense-Accounts 6 AccountsReceivable,ReserveDepositPayable,AccountsPayable,AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-Accounts payable 16 ReserveDepositPayable,AccountsPayable,AccountsReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable-Accounts receivable 15 AccountsReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,ReserveDepositPayable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable-AccountsPayable 2 ReserveDepositPayable,AccountsPayable-AccountsReceivable 1 AccountsReceivable-Accrued 4 AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-Accrued (unpaid) consumption tax 11 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes,AccruedExpenses-Accrued (unpaid) corporate income taxes 15 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue-Accrued expenses 10 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-Accrued revenue 24 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-AccruedConsumptionTax 1 AccruedConsumptionTax-AccruedCorporateIncomeTaxes 1 AccruedCorporateIncomeTaxes-AccruedExpenses 1 AccruedExpenses-AccruedRevenue 1 AccruedRevenue-Accumulated 2 ReserveForDepreciation,AccumulatedDepreciation-Accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense-AccumulatedDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-Advances 2 AdvancesPaid,AdvancesReceived-Advances paid 9 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-Advances received 5 AdvancesReceived,DepositPayable,AdvancesPaid,DepositsReceived,ConsumptionTaxReceived-AdvancesPaid 1 AdvancesPaid-AdvancesReceived 1 AdvancesReceived-Advertising 1 AdvertisingExpense-AdvertisingExpense 1 AdvertisingExpense-Allowance 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-Allowance for doubtful accounts 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable,AccumulatedDepreciation-AllowanceForDoubtfulAccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-Amortization 1 AmortizationExpense-Amortization expense for intangibles 28 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-AmortizationExpense 1 AmortizationExpense-Asset 44 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-Asset: 40 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-B\/S 1 AllowanceForDoubtfulAccounts-Bad 2 BadDebtLoss,RecoveryOfBadDebts-Bad debt loss 7 BadDebtLoss,RecoveryOfBadDebts,NetLoss,CashOverShort,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-BadDebtLoss 1 BadDebtLoss-Bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft-Bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-BankOverdraft 1 BankOverdraft-Building 1 Building-BusinessTrip 1 BusinessTrip-Capital 1 CapitalStock-Capital stock 2 CapitalStock,StockInvestment-CapitalStock 1 CapitalStock-Carrying 1 InvestmentInAssociate-Cash 3 Cash,PettyCash,CashOverShort-Cash over and short 17 CashOverShort,Cash,ShortTermNationalBonds,Products,Machinery,ShortTermLoansReceivable,GovernmentService,ShortTermLoansPayable,ShortTermNationalBondsPayable,BusinessTrip-CashOverShort 1 CashOverShort-Central 3 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-Central bank notes 9 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,BankOverdraft,LossOnSalesOfNotesReceivable-Central bank payment to treasury 13 CentralBankPaymentExpense,CentralBankPaymentIncome,CentralBankNotePayable,Products,StockInvestment,CapitalStock,ReceiptFee,MerchandiseInventory,PaymentsOnBehalf,SuspensePayments-CentralBankNotePayable 1 CentralBankNotePayable-CentralBankPaymentExpense 1 CentralBankPaymentExpense-CentralBankPaymentIncome 1 CentralBankPaymentIncome-Communication 2 Commutation,CommunicationExpenses-Communication expenses 7 Commutation,CommunicationExpenses,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses-CommunicationExpenses 2 Commutation,CommunicationExpenses-Commutation 2 Commutation,CommunicationExpenses-Consumption 4 ConsumptionExpenditure,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-Consumption expenditure 6 ConsumptionExpenditure,WageExpenditure,SubsidyExpense,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-Consumption tax paid 13 ConsumptionTaxPaid,AccruedConsumptionTax,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedCorporateIncomeTaxes,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AdvancesPaid,PrepaidExpenses-Consumption tax received 12 ConsumptionTaxReceived,ConsumptionTaxPaid,AccruedConsumptionTax,DepositPayable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,PrepaidCorporateIncomeTaxes,AdvancesReceived,DepositsReceived-ConsumptionExpenditure 1 ConsumptionExpenditure-ConsumptionTaxPaid 1 ConsumptionTaxPaid-ConsumptionTaxReceived 1 ConsumptionTaxReceived-Corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Corporate income taxes 12 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-CorporateIncomeTaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Cost 9 SalesCost,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses-Cost of sales 18 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets,GrossProfit,OrdinaryProfit,Sales,EquityInEarningsOfInvestee-Cost: 8 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses-Credit 4 CreditCardReceivable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-Credit card receivable 13 CreditCardReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables-CreditCardReceivable 1 CreditCardReceivable-Cumulative 1 CumulativeTranslationAdjustment-Cumulative translation adjustment 1 CumulativeTranslationAdjustment-CumulativeTranslationAdjustment 1 CumulativeTranslationAdjustment-Current 1 CurrentDeposits-Current deposits 6 CurrentDeposits,Deposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-CurrentDeposits 1 CurrentDeposits-Delivery 1 DeliveryExpenses-DeliveryExpenses 1 DeliveryExpenses-Deposit 7 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositInterestEarned,DepositsReceived-Deposit interest earned 12 DepositInterestEarned,NationalBondInterestEarned,InterestEarned,Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,InterestExpense,WageEarned-DepositInterestEarned 1 DepositInterestEarned-DepositPayable 2 DepositPayable,ReserveDepositPayable-Deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-Deposits accepted 6 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived-Deposits received 8 DepositPayable,DepositsReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived-DepositsReceived 2 DepositPayable,DepositsReceived-Depreciation 4 ReserveForDepreciation,Depreciation,AmortizationExpense,AccumulatedDepreciation-Electronically 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-Electronically recorded monetary claims 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-Electronically recorded monetary obligations 2 ElectronicallyRecordedObligations,ElectronicallyRecordedReceivable-ElectronicallyRecordedObligations 1 ElectronicallyRecordedObligations-ElectronicallyRecordedReceivable 1 ElectronicallyRecordedReceivable-Equipment 3 Machinery,EquipmentInvestment,Fixtures-Equipment investment 6 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures-EquipmentInvestment 1 EquipmentInvestment-Equity 6 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment-Equity in earnings of investee 44 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,Deposits,InvestmentSecurities-Equity: 4 CapitalStock,RetainedEarnings,LegalRetainedEarnings,CumulativeTranslationAdjustment-EquityInEarningsOfInvestee 1 EquityInEarningsOfInvestee-Expense 22 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-Expense: 19 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-Explicitly 1 CommunicationExpenses-Fixtures 1 Fixtures-Fixtures and equipment 9 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-For 8 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-Gain 1 GainOnSalesOfFixedAssets-Gain on sales of fixed assets 39 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,AccumulatedDepreciation,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable-GainOnSalesOfFixedAssets 1 GainOnSalesOfFixedAssets-Gold 1 Gold-Government 1 GovernmentService-Government service 1 GovernmentService-GovernmentService 1 GovernmentService-Gross 1 GrossProfit-Gross profit 2 GrossProfit,OrdinaryProfit-GrossProfit 1 GrossProfit-Hat\/Not 1 AllowanceForDoubtfulAccounts-Home 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-Interest 4 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned-Interest earned 6 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-Interest expense 25 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-InterestEarned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned-InterestExpense 1 InterestExpense-Investment 4 InvestmentSecurities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-Investment in associate 35 InvestmentInAssociate,InvestmentSecurities,StockInvestment,EquipmentInvestment,Deposits,Products,Machinery,Building,RetainedEarnings,AmortizationExpense-Investment securities 5 InvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-InvestmentInAssociate 1 InvestmentInAssociate-InvestmentSecurities 1 InvestmentSecurities-Land 1 Land-Legal 1 LegalRetainedEarnings-Legal (appropriated) retained earnings reserve 8 LegalRetainedEarnings,RetainedEarnings,AccumulatedDepreciation,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,EquityInEarningsOfInvestee,CommunicationExpenses-LegalRetainedEarnings 1 LegalRetainedEarnings-Liability: 24 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable-Loans 7 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable-Loans payable 15 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Loans payable on notes 40 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable,NotesPayable-Loans receivable on notes 40 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable,AllowanceForDoubtfulAccounts,LongTermNationalBonds-LoansPayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-Long-term 4 LongTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,LongTermNationalBondsPayable-Long-term loans payable 16 LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,NotesLoansPayable,LongTermNationalBonds,ShortTermLoansReceivable,DepositPayable,ShortTermNationalBondsPayable-Long-term loans receivable 17 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesLoansReceivable,LongTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable-Long-term national bonds 7 LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermLoansReceivable,LongTermLoansPayable,NationalBondInterestEarned-Long-term national bonds payable 16 LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-LongTermLoansPayable 1 LongTermLoansPayable-LongTermLoansReceivable 1 LongTermLoansReceivable-LongTermNationalBonds 2 LongTermNationalBonds,LongTermNationalBondsPayable-LongTermNationalBondsPayable 1 LongTermNationalBondsPayable-Loss 6 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-Loss on sales of fixed assets 43 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,AccumulatedDepreciation,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable-Loss on sales of notes receivable 51 LossOnSalesOfNotesReceivable,LossOnSalesOfFixedAssets,NotesLoansReceivable,GainOnSalesOfFixedAssets,LongTermLoansReceivable,SalesCost,NotesReceivable,ElectronicallyRecordedReceivable,MerchandiseInventory,NotesLoansPayable-LossOnSalesOfFixedAssets 1 LossOnSalesOfFixedAssets-LossOnSalesOfNotesReceivable 1 LossOnSalesOfNotesReceivable-Machinery 1 Machinery-Machinery and equipment 9 Machinery,Fixtures,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-Merchandise 2 Products,MerchandiseInventory-Merchandise inventory 2 Products,MerchandiseInventory-MerchandiseInventory 2 Products,MerchandiseInventory-Miscellaneous 4 MiscellaneousExpenses,CashOverShort,MiscellaneousLoss,MiscellaneousIncome-Miscellaneous income 12 CashOverShort,MiscellaneousIncome,MiscellaneousExpenses,NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes-Miscellaneous loss 8 CashOverShort,MiscellaneousLoss,MiscellaneousExpenses,NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousIncome-MiscellaneousExpenses 1 MiscellaneousExpenses-MiscellaneousIncome 2 CashOverShort,MiscellaneousIncome-MiscellaneousIncome\/MiscellaneousLoss 1 CashOverShort-MiscellaneousLoss 2 CashOverShort,MiscellaneousLoss-National 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-National bond interest earned 10 NationalBondInterestEarned,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-NationalBondInterestEarned 1 NationalBondInterestEarned-Net 5 NetIncome,NetLoss,ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,AllowanceForDoubtfulAccounts-Net income 14 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,NetLoss,ElectronicallyRecordedReceivable,PrepaidCorporateIncomeTaxes,CashOverShort,ElectronicallyRecordedObligations-Net loss 10 NetLoss,NetIncome,ElectronicallyRecordedReceivable,CashOverShort,ElectronicallyRecordedObligations,AllowanceForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-NetIncome 1 NetIncome-NetLoss 1 NetLoss-Notes 6 CentralBankNotePayable,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,LossOnSalesOfNotesReceivable-Notes payable 15 CentralBankNotePayable,NotesPayable,NotesLoansPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Notes receivable 14 NotesReceivable,NotesLoansReceivable,LossOnSalesOfNotesReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,CentralBankNotePayable,ElectronicallyRecordedReceivable,CreditCardReceivable-NotesLoansPayable 1 NotesLoansPayable-NotesLoansReceivable 1 NotesLoansReceivable-NotesPayable 1 NotesPayable-NotesReceivable 2 NotesReceivable,LossOnSalesOfNotesReceivable-OCI/資本の部の項目) 1 CumulativeTranslationAdjustment-Ordinary 1 OrdinaryProfit-Ordinary profit 2 OrdinaryProfit,GrossProfit-OrdinaryProfit 1 OrdinaryProfit-Other 2 OtherReceivables,OtherPayables-Other payables 2 OtherPayables,OtherReceivables-Other receivables 3 OtherReceivables,OtherPayables,AllowanceForDoubtfulAccounts-OtherPayables 1 OtherPayables-OtherReceivables 1 OtherReceivables-Patent 1 Patent-Payment 6 CentralBankPaymentExpense,ReceiptFee,CentralBankPaymentIncome,PaymentsOnBehalf,SuspensePayments,PaymentFees-Payment fees / fees paid 32 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable-PaymentFees 2 ReceiptFee,PaymentFees-Payments 2 PaymentsOnBehalf,SuspensePayments-Payments made on behalf 31 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense-PaymentsOnBehalf 1 PaymentsOnBehalf-Petty 1 PettyCash-Petty cash 3 PettyCash,Cash,CashOverShort-PettyCash 1 PettyCash-Prepaid 2 PrepaidExpenses,PrepaidCorporateIncomeTaxes-Prepaid corporate income taxes 13 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-Prepaid expenses 8 PrepaidExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidCorporateIncomeTaxes,AccruedExpenses,CommunicationExpenses-PrepaidCorporateIncomeTaxes 1 PrepaidCorporateIncomeTaxes-PrepaidExpenses 1 PrepaidExpenses-Products 2 Products,MerchandiseInventory-Provision 1 ProvisionForDoubtfulAccounts-Provision for doubtful accounts 11 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable,AccumulatedDepreciation-ProvisionForDoubtfulAccounts 1 ProvisionForDoubtfulAccounts-Purchases 2 Purchases,MerchandiseInventory-Purchases\/Sales\/MerchandiseInventory). 1 MerchandiseInventory-Real 1 Building-Real estate 1 Building-Receipt 3 ReceiptFee,SuspenseReceipts,PaymentFees-Receipt fee 3 ReceiptFee,PaymentFees,SuspenseReceipts-ReceiptFee 2 ReceiptFee,PaymentFees-Recognized 1 EquityInEarningsOfInvestee-Recovery 1 RecoveryOfBadDebts-Recovery of bad debts written off 13 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,BadDebtLoss,LossOnSalesOfNotesReceivable-RecoveryOfBadDebts 1 RecoveryOfBadDebts-Rent 3 CurrentDeposits,RentExpense,RentalIncome-RentExpense 1 RentExpense-Rental 1 RentalIncome-Rental income 10 RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-RentalIncome 1 RentalIncome-Reserve 5 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings-Reserve deposits, asset side 47 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate-Reserve deposits, liability side 28 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-Reserve for depreciation 12 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory,AllowanceForDoubtfulAccounts,LegalRetainedEarnings,ProvisionForDoubtfulAccounts-ReserveDepositPayable 1 ReserveDepositPayable-ReserveDepositReceivable 1 ReserveDepositReceivable-ReserveForDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-Retained 4 RetainedEarnings,AccumulatedDepreciation,LegalRetainedEarnings,CommunicationExpenses-Retained earnings 5 RetainedEarnings,LegalRetainedEarnings,EquityInEarningsOfInvestee,AccumulatedDepreciation,CommunicationExpenses-RetainedEarnings 2 RetainedEarnings,LegalRetainedEarnings-Revenue 21 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Revenue: 19 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Reversal 1 ReversalOfAllowanceForDoubtfulAccounts-Reversal of allowance for doubtful accounts 21 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,CommunicationExpenses,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,SalesCost,GrossProfit-ReversalOfAllowanceForDoubtfulAccounts 1 ReversalOfAllowanceForDoubtfulAccounts-SNA\/macro-accounting 1 AccumulatedDepreciation-SNA\/マクロ系の旧称。簿記の間接法には 1 ReserveForDepreciation-Sales 6 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets-SalesCost 1 SalesCost-Savings 1 Deposits-Savings deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-Securities 2 Securities,InvestmentSecurities-Short-term 4 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable-Short-term loans payable 16 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable-Short-term loans receivable 17 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable-Short-term national bonds 7 ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermLoansReceivable,ShortTermLoansPayable,NationalBondInterestEarned-Short-term national bonds payable 16 ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-ShortTermLoansPayable 1 ShortTermLoansPayable-ShortTermLoansReceivable 1 ShortTermLoansReceivable-ShortTermNationalBonds 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-ShortTermNationalBondsPayable 1 ShortTermNationalBondsPayable-Software 1 Software-Stock 2 StockInvestment,CapitalStock-Stock investment 5 StockInvestment,InvestmentSecurities,InvestmentInAssociate,EquipmentInvestment,CapitalStock-StockInvestment 1 StockInvestment-Subsidy 2 SubsidyExpense,SubsidyIncome-Subsidy expenditure 4 SubsidyExpense,WageExpenditure,ConsumptionExpenditure,SubsidyIncome-Subsidy income 11 SubsidyIncome,SubsidyExpense,NetIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-SubsidyExpense 1 SubsidyExpense-SubsidyIncome 1 SubsidyIncome-Supplies 1 SuppliesExpenses-SuppliesExpenses 1 SuppliesExpenses-Suspense 3 SuspensePayments,CashOverShort,SuspenseReceipts-Suspense payments 4 SuspensePayments,PaymentsOnBehalf,CashOverShort,SuspenseReceipts-Suspense receipts 3 SuspenseReceipts,SuspensePayments,CashOverShort-SuspensePayments 1 SuspensePayments-SuspenseReceipts 1 SuspenseReceipts-Tax 8 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Tax revenue 28 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee-Taxes 5 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-TaxesExpense 1 TaxesExpense-TaxesRevenue 1 TaxesRevenue-This 1 AccumulatedDepreciation-Trademark 1 Trademark-Travel 1 BusinessTrip-Travel and transportation 8 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-Unearned 1 UnearnedRevenue-Unearned revenue 21 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-UnearnedRevenue 1 UnearnedRevenue-Unpaid 3 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends-Unpaid dividends 3 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-UnpaidDividends 1 UnpaidDividends-Use 5 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-Utilities 1 UtilitiesExpense-UtilitiesExpense 1 UtilitiesExpense-Value 2 ValueAdded,AllowanceForDoubtfulAccounts-Value added 2 ValueAdded,AllowanceForDoubtfulAccounts-ValueAdded 1 ValueAdded-Vehicle 1 Vehicle-Vehicles 1 Vehicle-Wage 2 WageExpenditure,WageEarned-Wage income 11 WageEarned,WageExpenditure,NetIncome,SubsidyIncome,RentalIncome,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-WageEarned 1 WageEarned-WageExpenditure 1 WageExpenditure-Wages 1 WageExpenditure-Write 1 AllowanceForDoubtfulAccounts-a 108 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-accepted 1 DepositPayable-account 13 AccountsReceivable,ReserveDepositPayable,MerchandiseInventory,PrepaidExpenses,AccruedRevenue,CashOverShort,AccountsPayable,UnearnedRevenue,AccruedExpenses,AllowanceForDoubtfulAccounts-accounts 6 AccountsReceivable,ReserveDepositPayable,AccountsPayable,AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-accounts payable 16 ReserveDepositPayable,AccountsPayable,AccountsReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable-accounts receivable 15 AccountsReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,ReserveDepositPayable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable-accountspayable 2 ReserveDepositPayable,AccountsPayable-accountsreceivable 1 AccountsReceivable-accrual 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-accrued (unpaid) consumption tax 11 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes,AccruedExpenses-accrued (unpaid) corporate income taxes 15 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue-accrued expenses 10 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-accrued revenue 24 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-accruedconsumptiontax 1 AccruedConsumptionTax-accruedcorporateincometaxes 1 AccruedCorporateIncomeTaxes-accruedexpenses 1 AccruedExpenses-accruedrevenue 1 AccruedRevenue-accumulated 2 ReserveForDepreciation,AccumulatedDepreciation-accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense-accumulateddepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-added 1 ValueAdded-adjustment 1 CumulativeTranslationAdjustment-advances paid 9 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-advances received 5 AdvancesReceived,DepositPayable,AdvancesPaid,DepositsReceived,ConsumptionTaxReceived-advancespaid 1 AdvancesPaid-advancesreceived 1 AdvancesReceived-advertising 1 AdvertisingExpense-advertisingexpense 1 AdvertisingExpense-allowance 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-allowance for doubtful accounts 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable,AccumulatedDepreciation-allowancefordoubtfulaccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-amortization expense for intangibles 28 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-amortizationexpense 1 AmortizationExpense-amount 1 InvestmentInAssociate-and 8 Products,Machinery,BusinessTrip,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-as 48 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-asset 44 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-assets 3 AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets-assets. 1 AccumulatedDepreciation-associate 1 InvestmentInAssociate-at 25 InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Building,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip-backward 1 CommunicationExpenses-bad 2 BadDebtLoss,RecoveryOfBadDebts-bad debt loss 7 BadDebtLoss,RecoveryOfBadDebts,NetLoss,CashOverShort,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-baddebtloss 1 BadDebtLoss-balance 3 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft-bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-bankoverdraft 1 BankOverdraft-because 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-behalf 1 PaymentsOnBehalf-bond 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-bonds 4 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-bookkeeping 1 AccumulatedDepreciation-building 1 Building-businesstrip 1 BusinessTrip-by 1 ReversalOfAllowanceForDoubtfulAccounts-canonical 1 AccumulatedDepreciation-capital stock 2 CapitalStock,StockInvestment-capitalstock 1 CapitalStock-card 1 CreditCardReceivable-cash 3 Cash,PettyCash,CashOverShort-cash over and short 17 CashOverShort,Cash,ShortTermNationalBonds,Products,Machinery,ShortTermLoansReceivable,GovernmentService,ShortTermLoansPayable,ShortTermNationalBondsPayable,BusinessTrip-cashovershort 1 CashOverShort-central bank notes 9 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,BankOverdraft,LossOnSalesOfNotesReceivable-central bank payment to treasury 13 CentralBankPaymentExpense,CentralBankPaymentIncome,CentralBankNotePayable,Products,StockInvestment,CapitalStock,ReceiptFee,MerchandiseInventory,PaymentsOnBehalf,SuspensePayments-centralbanknotepayable 1 CentralBankNotePayable-centralbankpaymentexpense 1 CentralBankPaymentExpense-centralbankpaymentincome 1 CentralBankPaymentIncome-claims 1 ElectronicallyRecordedReceivable-cleared 1 CashOverShort-closing 1 CashOverShort-communication 2 Commutation,CommunicationExpenses-communication expenses 7 Commutation,CommunicationExpenses,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses-communicationexpenses 2 Commutation,CommunicationExpenses-commutation 2 Commutation,CommunicationExpenses-compatibility. 1 CommunicationExpenses-consumption 4 ConsumptionExpenditure,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-consumption expenditure 6 ConsumptionExpenditure,WageExpenditure,SubsidyExpense,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-consumption tax paid 13 ConsumptionTaxPaid,AccruedConsumptionTax,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedCorporateIncomeTaxes,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AdvancesPaid,PrepaidExpenses-consumption tax received 12 ConsumptionTaxReceived,ConsumptionTaxPaid,AccruedConsumptionTax,DepositPayable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,PrepaidCorporateIncomeTaxes,AdvancesReceived,DepositsReceived-consumptionexpenditure 1 ConsumptionExpenditure-consumptiontaxpaid 1 ConsumptionTaxPaid-consumptiontaxreceived 1 ConsumptionTaxReceived-contra 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-corporate income taxes 12 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-corporateincometaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-cost of sales 18 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets,GrossProfit,OrdinaryProfit,Sales,EquityInEarningsOfInvestee-counterpart 3 PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-credit card receivable 13 CreditCardReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables-credit-balance 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-creditcardreceivable 1 CreditCardReceivable-cumulative translation adjustment 1 CumulativeTranslationAdjustment-cumulativetranslationadjustment 1 CumulativeTranslationAdjustment-current deposits 6 CurrentDeposits,Deposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-currentdeposits 1 CurrentDeposits-debit 1 PaymentFees-debt 2 BadDebtLoss,RecoveryOfBadDebts-debts 1 RecoveryOfBadDebts-deducted 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-deduction 1 AllowanceForDoubtfulAccounts-deferral 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-delivery 1 DeliveryExpenses-deliveryexpenses 1 DeliveryExpenses-deposit interest earned 12 DepositInterestEarned,NationalBondInterestEarned,InterestEarned,Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,InterestExpense,WageEarned-depositinterestearned 1 DepositInterestEarned-depositpayable 2 DepositPayable,ReserveDepositPayable-deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-deposits accepted 6 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived-deposits received 8 DepositPayable,DepositsReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived-deposits, 2 ReserveDepositReceivable,ReserveDepositPayable-depositsreceived 2 DepositPayable,DepositsReceived-depreciable 1 AccumulatedDepreciation-depreciation 4 ReserveForDepreciation,Depreciation,AmortizationExpense,AccumulatedDepreciation-depreciation; 1 AccumulatedDepreciation-dividends 1 UnpaidDividends-doubtful 3 AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-earned 5 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,WageEarned,UnearnedRevenue-earnings 3 RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings-electronically recorded monetary claims 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-electronically recorded monetary obligations 2 ElectronicallyRecordedObligations,ElectronicallyRecordedReceivable-electronicallyrecordedobligations 1 ElectronicallyRecordedObligations-electronicallyrecordedreceivable 1 ElectronicallyRecordedReceivable-equipment 3 Machinery,EquipmentInvestment,Fixtures-equipment investment 6 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures-equipmentinvestment 1 EquipmentInvestment-equity 6 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment-equity in earnings of investee 44 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,Deposits,InvestmentSecurities-equityinearningsofinvestee 1 EquityInEarningsOfInvestee-estate 1 Building-estimated 1 ReversalOfAllowanceForDoubtfulAccounts-examples 1 ReserveDepositPayable-excess 1 ReversalOfAllowanceForDoubtfulAccounts-existing 2 AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-expenditure 3 WageExpenditure,ConsumptionExpenditure,SubsidyExpense-expense 22 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-expenses 7 Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses-fee 2 ReceiptFee,PaymentFees-fees 2 ReceiptFee,PaymentFees-fixed 2 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets-fixtures 1 Fixtures-fixtures and equipment 9 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-for 8 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-from 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-gain on sales of fixed assets 39 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,AccumulatedDepreciation,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable-gainonsalesoffixedassets 1 GainOnSalesOfFixedAssets-gold 1 Gold-government service 1 GovernmentService-governmentservice 1 GovernmentService-gross profit 2 GrossProfit,OrdinaryProfit-grossprofit 1 GrossProfit-in 35 Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery,Building,StockInvestment,EquipmentInvestment,RetainedEarnings,AmortizationExpense-income 10 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-indirect 1 AccumulatedDepreciation-instead. 1 MerchandiseInventory-intact. 1 AllowanceForDoubtfulAccounts-intangibles 1 AmortizationExpense-interest 4 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned-interest earned 6 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-interest expense 25 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-interestearned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned-interestexpense 1 InterestExpense-inventory 2 Products,MerchandiseInventory-investee 1 EquityInEarningsOfInvestee-investment 4 InvestmentSecurities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-investment in associate 35 InvestmentInAssociate,InvestmentSecurities,StockInvestment,EquipmentInvestment,Deposits,Products,Machinery,Building,RetainedEarnings,AmortizationExpense-investment securities 5 InvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-investmentinassociate 1 InvestmentInAssociate-investmentsecurities 1 InvestmentSecurities-is 12 Products,AdvertisingExpense,MiscellaneousExpenses,MerchandiseInventory,CashOverShort,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,MiscellaneousLoss,CommunicationExpenses-it 49 Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,ReserveDepositReceivable,CapitalStock,RetainedEarnings,LongTermLoansPayable,ShortTermLoansPayable-job. 1 AllowanceForDoubtfulAccounts-land 1 Land-legacy 2 AccumulatedDepreciation,CommunicationExpenses-legal (appropriated) retained earnings reserve 8 LegalRetainedEarnings,RetainedEarnings,AccumulatedDepreciation,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,EquityInEarningsOfInvestee,CommunicationExpenses-legalretainedearnings 1 LegalRetainedEarnings-liability 24 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable-loans 7 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable-loans payable 15 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-loans payable on notes 40 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable,NotesPayable-loans receivable on notes 40 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable,AllowanceForDoubtfulAccounts,LongTermNationalBonds-loanspayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-long-term loans payable 16 LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,NotesLoansPayable,LongTermNationalBonds,ShortTermLoansReceivable,DepositPayable,ShortTermNationalBondsPayable-long-term loans receivable 17 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesLoansReceivable,LongTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable-long-term national bonds 7 LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermLoansReceivable,LongTermLoansPayable,NationalBondInterestEarned-long-term national bonds payable 16 LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-longtermloanspayable 1 LongTermLoansPayable-longtermloansreceivable 1 LongTermLoansReceivable-longtermnationalbonds 2 LongTermNationalBonds,LongTermNationalBondsPayable-longtermnationalbondspayable 1 LongTermNationalBondsPayable-loss 6 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-loss on sales of fixed assets 43 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,AccumulatedDepreciation,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable-loss on sales of notes receivable 51 LossOnSalesOfNotesReceivable,LossOnSalesOfFixedAssets,NotesLoansReceivable,GainOnSalesOfFixedAssets,LongTermLoansReceivable,SalesCost,NotesReceivable,ElectronicallyRecordedReceivable,MerchandiseInventory,NotesLoansPayable-lossonsalesoffixedassets 1 LossOnSalesOfFixedAssets-lossonsalesofnotesreceivable 1 LossOnSalesOfNotesReceivable-machinery 1 Machinery-machinery and equipment 9 Machinery,Fixtures,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-made 1 PaymentsOnBehalf-merchandise inventory 2 Products,MerchandiseInventory-merchandiseinventory 2 Products,MerchandiseInventory-method 4 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,AccumulatedDepreciation-method. 1 EquityInEarningsOfInvestee-miscellaneous 4 MiscellaneousExpenses,CashOverShort,MiscellaneousLoss,MiscellaneousIncome-miscellaneous income 12 CashOverShort,MiscellaneousIncome,MiscellaneousExpenses,NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes-miscellaneous loss 8 CashOverShort,MiscellaneousLoss,MiscellaneousExpenses,NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousIncome-miscellaneousexpenses 1 MiscellaneousExpenses-miscellaneousincome 2 CashOverShort,MiscellaneousIncome-miscellaneousloss 2 CashOverShort,MiscellaneousLoss-monetary 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-name. 1 AccumulatedDepreciation-named 1 CommunicationExpenses-national 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-national bond interest earned 10 NationalBondInterestEarned,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-nationalbondinterestearned 1 NationalBondInterestEarned-net income 14 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,NetLoss,ElectronicallyRecordedReceivable,PrepaidCorporateIncomeTaxes,CashOverShort,ElectronicallyRecordedObligations-net loss 10 NetLoss,NetIncome,ElectronicallyRecordedReceivable,CashOverShort,ElectronicallyRecordedObligations,AllowanceForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-netincome 1 NetIncome-netloss 1 NetLoss-non-negative 1 AllowanceForDoubtfulAccounts-notes 6 CentralBankNotePayable,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,LossOnSalesOfNotesReceivable-notes payable 15 CentralBankNotePayable,NotesPayable,NotesLoansPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-notes receivable 14 NotesReceivable,NotesLoansReceivable,LossOnSalesOfNotesReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,CentralBankNotePayable,ElectronicallyRecordedReceivable,CreditCardReceivable-notesloanspayable 1 NotesLoansPayable-notesloansreceivable 1 NotesLoansReceivable-notespayable 1 NotesPayable-notesreceivable 2 NotesReceivable,LossOnSalesOfNotesReceivable-obligations 1 ElectronicallyRecordedObligations-of 12 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-off 3 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,RecoveryOfBadDebts-on 30 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip-ordinary profit 2 OrdinaryProfit,GrossProfit-ordinaryprofit 1 OrdinaryProfit-other payables 2 OtherPayables,OtherReceivables-other receivables 3 OtherReceivables,OtherPayables,AllowanceForDoubtfulAccounts-otherpayables 1 OtherPayables-otherreceivables 1 OtherReceivables-over 4 GovernmentService,CashOverShort,BankOverdraft,RecoveryOfBadDebts-overdraft 1 BankOverdraft-paid 8 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-patent 1 Patent-payable 12 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable,NotesPayable-payables 1 OtherPayables-payment 6 CentralBankPaymentExpense,ReceiptFee,CentralBankPaymentIncome,PaymentsOnBehalf,SuspensePayments,PaymentFees-payment fees / fees paid 32 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable-paymentfees 2 ReceiptFee,PaymentFees-payments 2 PaymentsOnBehalf,SuspensePayments-payments made on behalf 31 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense-paymentsonbehalf 1 PaymentsOnBehalf-periodic/3-account 1 MerchandiseInventory-perpetual\/specific-identification 1 MerchandiseInventory-petty cash 3 PettyCash,Cash,CashOverShort-pettycash 1 PettyCash-prepaid corporate income taxes 13 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-prepaid expenses 8 PrepaidExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidCorporateIncomeTaxes,AccruedExpenses,CommunicationExpenses-prepaidcorporateincometaxes 1 PrepaidCorporateIncomeTaxes-prepaidexpenses 1 PrepaidExpenses-presentation 1 AllowanceForDoubtfulAccounts-products 2 Products,MerchandiseInventory-profit 2 GrossProfit,OrdinaryProfit-provision for doubtful accounts 11 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable,AccumulatedDepreciation-provisionfordoubtfulaccounts 1 ProvisionForDoubtfulAccounts-purchases 2 Purchases,MerchandiseInventory-real estate 1 Building-receipt fee 3 ReceiptFee,PaymentFees,SuspenseReceipts-receiptfee 2 ReceiptFee,PaymentFees-receipts 1 SuspenseReceipts-receivable 11 LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable,OtherReceivables,AllowanceForDoubtfulAccounts-receivables 2 OtherReceivables,AllowanceForDoubtfulAccounts-receivables. 1 AllowanceForDoubtfulAccounts-received 4 DepositPayable,AdvancesReceived,DepositsReceived,ConsumptionTaxReceived-recorded 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-recovery of bad debts written off 13 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,BadDebtLoss,LossOnSalesOfNotesReceivable-recoveryofbaddebts 1 RecoveryOfBadDebts-related 1 AccumulatedDepreciation-released). 1 ReversalOfAllowanceForDoubtfulAccounts-rent 3 CurrentDeposits,RentExpense,RentalIncome-rental income 10 RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-rentalincome 1 RentalIncome-rentexpense 1 RentExpense-reserve 5 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings-reserve deposits, asset side 47 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate-reserve deposits, liability side 28 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-reserve for depreciation 12 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory,AllowanceForDoubtfulAccounts,LegalRetainedEarnings,ProvisionForDoubtfulAccounts-reservedepositpayable 1 ReserveDepositPayable-reservedepositreceivable 1 ReserveDepositReceivable-reservefordepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-retained 4 RetainedEarnings,AccumulatedDepreciation,LegalRetainedEarnings,CommunicationExpenses-retained earnings 5 RetainedEarnings,LegalRetainedEarnings,EquityInEarningsOfInvestee,AccumulatedDepreciation,CommunicationExpenses-retainedearnings 2 RetainedEarnings,LegalRetainedEarnings-revenue 21 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-reversal of allowance for doubtful accounts 21 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,CommunicationExpenses,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,SalesCost,GrossProfit-reversalofallowancefordoubtfulaccounts 1 ReversalOfAllowanceForDoubtfulAccounts-sales 6 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets-salescost 1 SalesCost-savings deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-securities 2 Securities,InvestmentSecurities-service 1 GovernmentService-short 5 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,CashOverShort-short-term loans payable 16 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable-short-term loans receivable 17 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable-short-term national bonds 7 ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermLoansReceivable,ShortTermLoansPayable,NationalBondInterestEarned-short-term national bonds payable 16 ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-shorttermloanspayable 1 ShortTermLoansPayable-shorttermloansreceivable 1 ShortTermLoansReceivable-shorttermnationalbonds 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-shorttermnationalbondspayable 1 ShortTermNationalBondsPayable-side 4 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation-side's 1 AllowanceForDoubtfulAccounts-smaller 1 ReversalOfAllowanceForDoubtfulAccounts-software 1 Software-stay 1 AllowanceForDoubtfulAccounts-stock 2 StockInvestment,CapitalStock-stock investment 5 StockInvestment,InvestmentSecurities,InvestmentInAssociate,EquipmentInvestment,CapitalStock-stockinvestment 1 StockInvestment-structure 1 AllowanceForDoubtfulAccounts-subsidy expenditure 4 SubsidyExpense,WageExpenditure,ConsumptionExpenditure,SubsidyIncome-subsidy income 11 SubsidyIncome,SubsidyExpense,NetIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-subsidyexpense 1 SubsidyExpense-subsidyincome 1 SubsidyIncome-supplies 1 SuppliesExpenses-suppliesexpenses 1 SuppliesExpenses-suspense payments 4 SuspensePayments,PaymentsOnBehalf,CashOverShort,SuspenseReceipts-suspense receipts 3 SuspenseReceipts,SuspensePayments,CashOverShort-suspensepayments 1 SuspensePayments-suspensereceipts 1 SuspenseReceipts-tax 8 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-tax revenue 28 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee-taxes 5 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-taxesexpense 1 TaxesExpense-taxesrevenue 1 TaxesRevenue-temporary/suspense 1 CashOverShort-than 1 ReversalOfAllowanceForDoubtfulAccounts-the 10 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,OtherReceivables,OtherPayables,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-to 7 Products,StockInvestment,CapitalStock,CentralBankPaymentExpense,CentralBankPaymentIncome,MerchandiseInventory,CashOverShort-trademark 1 Trademark-translation 1 CumulativeTranslationAdjustment-transportation 1 BusinessTrip-travel and transportation 8 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-treasury 2 CentralBankPaymentExpense,CentralBankPaymentIncome-under 4 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,AccumulatedDepreciation-unearned revenue 21 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-unearnedrevenue 1 UnearnedRevenue-unpaid dividends 3 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-unpaiddividends 1 UnpaidDividends-use 5 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-used 1 ReversalOfAllowanceForDoubtfulAccounts-utilities 1 UtilitiesExpense-utilitiesexpense 1 UtilitiesExpense-valuation 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-value added 2 ValueAdded,AllowanceForDoubtfulAccounts-valueadded 1 ValueAdded-values 1 AllowanceForDoubtfulAccounts-vehicle 1 Vehicle-vehicles 1 Vehicle-wage income 11 WageEarned,WageExpenditure,NetIncome,SubsidyIncome,RentalIncome,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-wageearned 1 WageEarned-wageexpenditure 1 WageExpenditure-wages 1 WageExpenditure-when 1 ReversalOfAllowanceForDoubtfulAccounts-written 1 RecoveryOfBadDebts-— 10 ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-が本科目を買掛金の代用にしていた経緯あり) 1 ReserveDepositPayable-では 2 Products,Commutation-と区別される) 1 AmortizationExpense-を使う 4 Products,ReserveForDepreciation,ReserveDepositPayable,Commutation-を使う) 2 ReserveForDepreciation,Commutation-を使うこと 1 ReserveDepositPayable-クレジット売掛金 1 CreditCardReceivable-ソフトウェア 2 AmortizationExpense,Software-中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 1 ReserveDepositPayable-仕入 2 Products,Purchases-付加価値。SNA\/マクロ系 1 ValueAdded-付加価値。sna\/マクロ系 1 ValueAdded-仮受消費税 1 ConsumptionTaxReceived-仮受金 1 SuspenseReceipts-仮払法人税等 1 PrepaidCorporateIncomeTaxes-仮払消費税 1 ConsumptionTaxPaid-仮払金 1 SuspensePayments-借入金 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-備品 1 Fixtures-償却債権取立益 1 RecoveryOfBadDebts-分法 4 InvestmentInAssociate,Products,EquityInEarningsOfInvestee,MerchandiseInventory-分法: 1 MerchandiseInventory-利益準備金 1 LegalRetainedEarnings-前受収益 1 UnearnedRevenue-前受金 1 AdvancesReceived-前払費用 1 PrepaidExpenses-前払金 1 AdvancesPaid-区分) 2 NetIncome,NetLoss-受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived' 10 DepositPayable,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-受入預金 — 銀行側の負債としての預金。sna\/マクロ系。従業員等からの預り金は 'depositsreceived' 10 DepositPayable,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-受取利息 1 InterestEarned-受取家賃 1 RentalIncome-受取手形 1 NotesReceivable-受取手数料。支払側は 'PaymentFees' 1 ReceiptFee-受取手数料。支払側は 'paymentfees' 1 ReceiptFee-商品 2 Products,MerchandiseInventory-商標権 2 AmortizationExpense,Trademark-固定資産売却損 1 LossOnSalesOfFixedAssets-固定資産売却益 1 GainOnSalesOfFixedAssets-国債利息収入 1 NationalBondInterestEarned-国庫納付金収入。SNA\/マクロ系 1 CentralBankPaymentIncome-国庫納付金収入。sna\/マクロ系 1 CentralBankPaymentIncome-国庫納付金支出。SNA\/マクロ系 1 CentralBankPaymentExpense-国庫納付金支出。sna\/マクロ系 1 CentralBankPaymentExpense-土地 1 Land-在外子会社等の外貨建財務諸表の換算差額を計上する 1 CumulativeTranslationAdjustment-売上 4 Products,SalesCost,GrossProfit,Sales-売上原価 1 SalesCost-売上総利益 — 決算振替用 10 GrossProfit,NetIncome,OrdinaryProfit,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-売掛金 2 AccountsReceivable,CreditCardReceivable-小口現金 1 PettyCash-差額補充法/洗替法 1 ReversalOfAllowanceForDoubtfulAccounts-市中銀行が中央銀行に置く準備預金。SNA\/マクロ系) 1 ReserveDepositReceivable-広告宣伝費 1 AdvertisingExpense-建物 1 Building-当座借越 1 BankOverdraft-当座預金 1 CurrentDeposits-当期純利益 — 決算振替用。借方側に立つため Expense 区分 29 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost-当期純利益 — 決算振替用。借方側に立つため expense 区分 29 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost-当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 28 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-当期純損失 — 決算振替用。貸方側に立つため revenue 区分 28 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-手形借入金 1 NotesLoansPayable-手形売却損 1 LossOnSalesOfNotesReceivable-手形貸付金 1 NotesLoansReceivable-投資有価証券 1 InvestmentSecurities-持分法による投資利益 1 EquityInEarningsOfInvestee-支払利息 1 InterestExpense-支払家賃 1 RentExpense-支払手形 1 NotesPayable-支払手数料 1 PaymentFees-政府サービス。SNA\/マクロ系 1 GovernmentService-政府サービス。sna\/マクロ系 1 GovernmentService-旅費交通費 1 BusinessTrip-旧称。新規コードでは 1 Commutation-普通預金 1 Deposits-有価証券 2 Securities,InvestmentSecurities-未収入金 1 OtherReceivables-未収収益 1 AccruedRevenue-未払法人税等 1 AccruedCorporateIncomeTaxes-未払消費税 1 AccruedConsumptionTax-未払費用 1 AccruedExpenses-未払配当金 1 UnpaidDividends-未払金 1 OtherPayables-株式投資 1 StockInvestment-機械装置 1 Machinery-水道光熱費 1 UtilitiesExpense-決算振替用) 2 GrossProfit,OrdinaryProfit-決算振替用。借方側に立つため 1 NetIncome-決算振替用。貸方側に立つため 1 NetLoss-法人税等 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-消耗品費 1 SuppliesExpenses-消費支出。SNA\/マクロ系 1 ConsumptionExpenditure-消費支出。sna\/マクロ系 1 ConsumptionExpenditure-減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額 11 ReserveForDepreciation,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-減価償却引当金 — sna\/マクロ系の旧称。簿記の間接法には 'accumulateddepreciation' (減価償却累計額 11 ReserveForDepreciation,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-減価償却累計額 2 ReserveForDepreciation,AccumulatedDepreciation-減価償却費 2 Depreciation,AmortizationExpense-準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系 10 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 負債側 — 中央銀行が受け入れる準備預金。sna\/マクロ系 10 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系 10 ReserveDepositReceivable,ReserveDepositPayable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。sna\/マクロ系 10 ReserveDepositReceivable,ReserveDepositPayable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目 1 CumulativeTranslationAdjustment-為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する oci/資本の部の項目 1 CumulativeTranslationAdjustment-無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費 11 AmortizationExpense,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,Commutation,NetIncome,GrossProfit,OrdinaryProfit-無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'depreciation' (減価償却費 11 AmortizationExpense,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,Commutation,NetIncome,GrossProfit,OrdinaryProfit-特許権 2 AmortizationExpense,Patent-特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 1 AmortizationExpense-現金 3 Cash,PettyCash,CashOverShort-現金過不足 1 CashOverShort-発行側) 2 LongTermNationalBondsPayable,ShortTermNationalBondsPayable-発行銀行券。SNA\/マクロ系 1 CentralBankNotePayable-発行銀行券。sna\/マクロ系 1 CentralBankNotePayable-発送費 1 DeliveryExpenses-短期借入金 1 ShortTermLoansPayable-短期国債 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-短期国債, 発行側 2 ShortTermNationalBondsPayable,LongTermNationalBondsPayable-短期貸付金 1 ShortTermLoansReceivable-租税公課 1 TaxesExpense-租税収入。SNA\/マクロ系 1 TaxesRevenue-租税収入。sna\/マクロ系 1 TaxesRevenue-立替金 1 PaymentsOnBehalf-経常利益 — 決算振替用 10 OrdinaryProfit,NetIncome,GrossProfit,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-給料 1 WageExpenditure-繰越利益剰余金 1 RetainedEarnings-繰越商品 2 Products,MerchandiseInventory-補助金収入。SNA\/マクロ系 1 SubsidyIncome-補助金収入。sna\/マクロ系 1 SubsidyIncome-補助金支出。SNA\/マクロ系 1 SubsidyExpense-補助金支出。sna\/マクロ系 1 SubsidyExpense-設備投資 1 EquipmentInvestment-負債側 1 ReserveDepositPayable-買掛金 2 ReserveDepositPayable,AccountsPayable-貸倒引当金 3 AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-貸倒引当金戻入 1 ReversalOfAllowanceForDoubtfulAccounts-貸倒引当金繰入 1 ProvisionForDoubtfulAccounts-貸倒損失 1 BadDebtLoss-賃金収入。SNA\/マクロ系 1 WageEarned-賃金収入。sna\/マクロ系 1 WageEarned-資本金 1 CapitalStock-資産側 1 ReserveDepositReceivable-車両運搬具 1 Vehicle-通信費 2 Commutation,CommunicationExpenses-通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う 12 Commutation,ReserveForDepreciation,ReserveDepositPayable,Products,ReserveDepositReceivable,DepositPayable,AmortizationExpense,NetIncome,GrossProfit,OrdinaryProfit-通信費 — 旧称。新規コードでは 'communicationexpenses' を使う 12 Commutation,ReserveForDepreciation,ReserveDepositPayable,Products,ReserveDepositReceivable,DepositPayable,AmortizationExpense,NetIncome,GrossProfit,OrdinaryProfit-金 41 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings-銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 1 DepositPayable-長期借入金 1 LongTermLoansPayable-長期国債 2 LongTermNationalBonds,LongTermNationalBondsPayable-長期国債, 発行側 2 LongTermNationalBondsPayable,ShortTermNationalBondsPayable-長期貸付金 1 LongTermLoansReceivable-関係会社株式 1 InvestmentInAssociate-雑損 1 MiscellaneousLoss-雑益 1 MiscellaneousIncome-雑費 1 MiscellaneousExpenses-電子記録債務 1 ElectronicallyRecordedObligations-電子記録債権 1 ElectronicallyRecordedReceivable-預り金 2 DepositPayable,DepositsReceived-預金利息収入 1 DepositInterestEarned
test/fixtures/post-vocab/account-info.tsv view
@@ -99,7 +99,7 @@ AccruedConsumptionTax Liability Credit Accrued (unpaid) consumption tax 未払消費税 Liability: Accrued (unpaid) consumption tax (未払消費税) AccruedCorporateIncomeTaxes Liability Credit Accrued (unpaid) corporate income taxes 未払法人税等 Liability: Accrued (unpaid) corporate income taxes (未払法人税等) UnpaidDividends Liability Credit Unpaid dividends 未払配当金 Liability: Unpaid dividends (未払配当金)-AllowanceForDoubtfulAccounts Assets Credit Allowance for doubtful accounts 貸倒引当金 Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\/Not structure is intact. B\/S deduction (net) presentation is the Write side's job.+AllowanceForDoubtfulAccounts Assets Credit Allowance for doubtful accounts 貸倒引当金 Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\/Not structure is intact. B\/S deduction (net) presentation is the ExchangeAlgebra.IO.Output module's job. AccumulatedDepreciation Assets Credit Accumulated depreciation 減価償却累計額 Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name. LegalRetainedEarnings Equity Credit Legal (appropriated) retained earnings reserve 利益準備金 Equity: Legal (appropriated) retained earnings reserve (利益準備金) CumulativeTranslationAdjustment Equity Credit Cumulative translation adjustment 為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目 Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)@@ -239,3 +239,6 @@ NewspaperBooksExpenses Cost Debit Newspapers and books 新聞図書費 Expense: Newspapers and books (新聞図書費) RawMaterials Assets Debit Raw materials 原材料 Assets: Raw materials (原材料) GoodsInTransit Assets Debit Goods in transit 未着品 Assets: Goods in transit (未着品)+SalesAllowances Revenue Debit Sales allowances 売上値引 Revenue: Sales allowances (売上値引)+PurchaseAllowances Cost Credit Purchase allowances 仕入値引 Cost: Purchase allowances (仕入値引)+ReversalOfAllowanceForBonuses Revenue Credit Reversal of provision for bonuses 賞与引当金戻入 Revenue: Reversal of provision for bonuses (賞与引当金戻入)
test/fixtures/post-vocab/ordinals.tsv view
@@ -239,4 +239,7 @@ NewspaperBooksExpenses 237 RawMaterials 238 GoodsInTransit 239-AccountTitle 240+SalesAllowances 240+PurchaseAllowances 241+ReversalOfAllowanceForBonuses 242+AccountTitle 243
test/fixtures/post-vocab/semantics.tsv view
@@ -239,3 +239,6 @@ NewspaperBooksExpenses Cost False Debit Credit OUT Other Flip RawMaterials Assets False Debit Credit PS Current Nothing GoodsInTransit Assets False Debit Credit PS Current Nothing+SalesAllowances Revenue True Debit Credit OUT Other Flip+PurchaseAllowances Cost True Credit Debit IN Other Keep+ReversalOfAllowanceForBonuses Revenue False Credit Debit IN Other Keep
test/fixtures/post-vocab/suggest.tsv view
@@ -36,6 +36,7 @@ (交際費) 1 EntertainmentExpenses (仕入) 1 Purchases (仕入\/売上\/繰越商品) 1 Products+(仕入値引) 1 PurchaseAllowances (仕入割戻) 1 PurchaseRebates (仕掛品) 1 WorkInProcess (付加価値。SNA\/マクロ系) 1 ValueAdded@@ -104,6 +105,7 @@ (国庫補助金受贈益) 1 GainOnNationalSubsidies (土地) 1 Land (売上) 1 Sales+(売上値引) 1 SalesAllowances (売上割戻) 1 SalesRebates (売上原価) 1 SalesCost (売上総利益 1 GrossProfit@@ -232,6 +234,7 @@ (資本金) 1 CapitalStock (賞与) 1 Bonuses (賞与引当金) 1 AllowanceForBonuses+(賞与引当金戻入) 1 ReversalOfAllowanceForBonuses (賞与引当金繰入) 1 ProvisionForBonuses (車両運搬具) 1 Vehicle (返金負債) 1 RefundLiabilities@@ -278,9 +281,9 @@ Accrued 5 AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,BonusesPayable Accrued (unpaid) consumption tax 21 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,ConsumptionTaxRefundReceivable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes Accrued (unpaid) corporate income taxes 24 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome-Accrued bonuses 8 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Accrued bonuses 9 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses Accrued expenses 29 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-Accrued revenue 43 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Accrued revenue 45 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome AccruedConsumptionTax 1 AccruedConsumptionTax AccruedCorporateIncomeTaxes 1 AccruedCorporateIncomeTaxes AccruedExpenses 1 AccruedExpenses@@ -289,7 +292,7 @@ Accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense AccumulatedDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation Additional 1 AdditionalIncomeTaxesForPriorPeriods-Additional income taxes for prior periods 47 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense+Additional income taxes for prior periods 48 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense AdditionalIncomeTaxesForPriorPeriods 1 AdditionalIncomeTaxesForPriorPeriods Advances 2 AdvancesPaid,AdvancesReceived Advances paid 13 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees,SecurityDepositsPaid@@ -299,18 +302,18 @@ Advertising 1 AdvertisingExpense AdvertisingExpense 1 AdvertisingExpense AffiliateStocks 1 AffiliateStocks-Allowance 8 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-Allowance for doubtful accounts 33 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,AccountsReceivable-AllowanceForBonuses 1 AllowanceForBonuses+Allowance 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SalesAllowances,PurchaseAllowances+Allowance for doubtful accounts 36 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+AllowanceForBonuses 2 AllowanceForBonuses,ReversalOfAllowanceForBonuses AllowanceForDoubtfulAccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts AllowanceForProductWarranties 2 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties AllowanceForRepairs 2 AllowanceForRepairs,ReversalOfAllowanceForRepairs AllowanceForRetirementBenefits 1 AllowanceForRetirementBenefits Amortization 4 AmortizationExpense,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents-Amortization expense for intangibles 69 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-Amortization of goodwill 41 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets-Amortization of patent rights 42 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software-Amortization of software 40 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee+Amortization expense for intangibles 70 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense+Amortization of goodwill 42 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets+Amortization of patent rights 43 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software+Amortization of software 41 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee AmortizationExpense 1 AmortizationExpense AmortizationOfGoodwill 1 AmortizationOfGoodwill AmortizationOfPatents 1 AmortizationOfPatents@@ -329,7 +332,7 @@ Bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft Bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome BankOverdraft 1 BankOverdraft-Bonuses 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Bonuses 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses BonusesPayable 1 BonusesPayable Bookkeeping 2 AccumulatedDepreciation,IncomeSummary Branch 2 BranchCurrentAccount,HeadOfficeCurrentAccount@@ -373,7 +376,7 @@ ConsumptionTaxReceived 1 ConsumptionTaxReceived ConsumptionTaxRefundReceivable 1 ConsumptionTaxRefundReceivable Contra 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ContractAssets,ContraAccountForGuaranteeObligations-Contra account for guarantee obligations 48 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,GuaranteeObligations,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable+Contra account for guarantee obligations 49 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,GuaranteeObligations,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable ContraAccountForGuaranteeObligations 1 ContraAccountForGuaranteeObligations Contract 1 ContractAssets Contract assets 40 ContractAssets,AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand,IncomeTaxesRefundReceivable@@ -381,10 +384,10 @@ Corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes Corporate income taxes 20 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome,SubsidyIncome CorporateIncomeTaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Cost 50 SalesCost,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,PrepaidPensionCost-Cost of sales 74 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-Cost of services 70 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents-Cost: 48 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,CostOfServices,OperatingExpenses+Cost 51 SalesCost,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,PrepaidPensionCost+Cost of sales 77 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+Cost of services 72 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents+Cost: 49 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses,CostOfServices,OperatingExpenses CostOfServices 1 CostOfServices Credit 5 CreditCardReceivable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,HeadOfficeCurrentAccount Credit card receivable 22 CreditCardReceivable,AllowanceForDoubtfulAccounts,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables@@ -408,7 +411,7 @@ DepositPayable 2 DepositPayable,ReserveDepositPayable Deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived Deposits accepted 12 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived-Deposits for subscriptions of shares 72 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,Deposits,CurrentDeposits+Deposits for subscriptions of shares 73 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,ReversalOfAllowanceForBonuses,Deposits Deposits in special accounts 95 DepositsInSpecialAccounts,Deposits,ReserveDepositPayable,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,CurrentDeposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery Deposits received 18 DepositPayable,DepositsReceived,GuaranteeDepositsReceived,DepositsReceivedFromOfficers,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived Deposits received from officers 20 DepositsReceivedFromOfficers,DepositPayable,DepositsReceived,GuaranteeDepositsReceived,LandRentReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived@@ -441,30 +444,31 @@ Equipment investment 8 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities EquipmentInvestment 1 EquipmentInvestment Equity 17 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment,StockSubscriptionDeposits,LegalCapitalSurplus,OtherCapitalSurplus,DividendEqualizationReserve-Equity in earnings of investee 115 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,SoftwareInProgress+Equity in earnings of investee 116 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,SoftwareInProgress Equity: 15 CapitalStock,RetainedEarnings,LegalRetainedEarnings,CumulativeTranslationAdjustment,StockSubscriptionDeposits,LegalCapitalSurplus,OtherCapitalSurplus,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve EquityInEarningsOfInvestee 1 EquityInEarningsOfInvestee+ExchangeAlgebra.IO.Output 1 AllowanceForDoubtfulAccounts Expense 40 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses Expense: 22 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses Explicitly 1 CommunicationExpenses Fixtures 1 Fixtures Fixtures and equipment 18 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand-For 28 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties+For 29 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties Foreign 2 ForeignExchangeGains,ForeignExchangeLosses-Foreign exchange gains 2 ForeignExchangeGains,ForeignExchangeLosses-Foreign exchange losses 2 ForeignExchangeLosses,ForeignExchangeGains+Foreign exchange gains 3 ForeignExchangeGains,ForeignExchangeLosses,AllowanceForDoubtfulAccounts+Foreign exchange losses 3 ForeignExchangeLosses,ForeignExchangeGains,AllowanceForDoubtfulAccounts ForeignExchangeGains 1 ForeignExchangeGains ForeignExchangeLosses 1 ForeignExchangeLosses Gain 10 GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants,ForeignExchangeGains-Gain on bargain purchase 91 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants-Gain on contribution received for construction 101 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses-Gain on insurance claims 90 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies-Gain on receipt of donated non-current assets 127 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,AccumulatedDepreciation-Gain on receipt of national subsidies 106 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-Gain on sale of investment securities 113 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-Gain on sale of securities 110 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-Gain on sales of fixed assets 127 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies-Gain on valuation of securities 108 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+Gain on bargain purchase 93 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants+Gain on contribution received for construction 102 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses+Gain on insurance claims 91 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies+Gain on receipt of donated non-current assets 128 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,AccumulatedDepreciation+Gain on receipt of national subsidies 107 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable+Gain on sale of investment securities 115 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+Gain on sale of securities 112 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+Gain on sales of fixed assets 129 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies+Gain on valuation of securities 109 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities GainOnBargainPurchase 1 GainOnBargainPurchase GainOnConstructionGrants 1 GainOnConstructionGrants GainOnDonationOfFixedAssets 1 GainOnDonationOfFixedAssets@@ -519,7 +523,7 @@ Interest 9 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,LeaseholdRights,NonControllingInterests,InterestOnSecurities,NetIncomeAttributableToNCI,NetLossAttributableToNCI Interest earned 12 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue,LeaseholdRights,NonControllingInterests,EarnedSurplus,InterestOnSecurities Interest expense 48 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-Interest on securities 96 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+Interest on securities 97 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities InterestEarned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned InterestExpense 1 InterestExpense InterestOnSecurities 1 InterestOnSecurities@@ -554,9 +558,9 @@ Liability: 41 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable Loans 8 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable,LoansReceivable Loans payable 20 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Loans payable on notes 100 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable+Loans payable on notes 101 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable Loans receivable 23 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesReceivable-Loans receivable on notes 100 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable+Loans receivable on notes 101 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable LoansPayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable LoansReceivable 4 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable Long-term 6 LongTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,LongTermNationalBondsPayable,LongTermPrepaidExpenses,LongTermOtherPayables@@ -574,16 +578,16 @@ LongTermPrepaidExpenses 1 LongTermPrepaidExpenses Loss 18 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss,InventoryShrinkageLoss,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables Loss attributable to non-controlling interests 38 NetLossAttributableToNCI,NetIncomeAttributableToNCI,CashOverShort,NonControllingInterests,InventoryShrinkageLoss,LossOnRetirementOfFixedAssets,Products,StockInvestment,CapitalStock,CentralBankPaymentExpense-Loss on fire 94 LossOnFire,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets-Loss on retirement of non-current assets 132 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-Loss on sale of electronically recorded monetary claims 112 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets-Loss on sale of investment securities 118 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-Loss on sale of receivables 113 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise-Loss on sale of securities 115 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-Loss on sales of fixed assets 132 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities-Loss on sales of notes receivable 122 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable-Loss on tax purpose reduction entry of non-current assets 138 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-Loss on valuation of securities 113 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities+Loss on fire 95 LossOnFire,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets+Loss on retirement of non-current assets 133 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+Loss on sale of electronically recorded monetary claims 114 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets+Loss on sale of investment securities 120 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+Loss on sale of receivables 115 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise+Loss on sale of securities 117 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+Loss on sales of fixed assets 134 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities+Loss on sales of notes receivable 124 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable+Loss on tax purpose reduction entry of non-current assets 139 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+Loss on valuation of securities 114 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities LossOnFire 1 LossOnFire LossOnReductionOfFixedAssets 1 LossOnReductionOfFixedAssets LossOnRetirementOfFixedAssets 1 LossOnRetirementOfFixedAssets@@ -652,7 +656,7 @@ OCI/資本の部の項目) 1 CumulativeTranslationAdjustment Operating 6 NonOperatingNotesReceivable,NonOperatingElectronicallyRecordedReceivable,NonOperatingNotesPayable,NonOperatingElectronicallyRecordedObligations,OperatingRevenue,OperatingExpenses Operating expenses 30 OperatingExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses,LongTermPrepaidExpenses,NonOperatingNotesReceivable-Operating revenue 44 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Operating revenue 46 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome OperatingExpenses 1 OperatingExpenses OperatingRevenue 1 OperatingRevenue Ordinary 1 OrdinaryProfit@@ -672,7 +676,7 @@ Payment fees / fees paid 38 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,MembershipFees,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable PaymentFees 2 ReceiptFee,PaymentFees Payments 2 PaymentsOnBehalf,SuspensePayments-Payments made on behalf 89 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense+Payments made on behalf 90 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense PaymentsOnBehalf 1 PaymentsOnBehalf Petty 1 PettyCash Petty cash 3 PettyCash,Cash,CashOverShort@@ -690,18 +694,20 @@ Property 1 PropertyTaxPayable Property tax payable 32 PropertyTaxPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,TaxesExpense PropertyTaxPayable 1 PropertyTaxPayable-Provision 10 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-Provision for bonuses 30 AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForProductWarranties-Provision for doubtful accounts 33 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs-Provision for repairs 29 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses-Provision for retirement benefits 32 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-Provision for warranties 28 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses+Provision 11 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+Provision for bonuses 31 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+Provision for doubtful accounts 34 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+Provision for repairs 30 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses+Provision for retirement benefits 33 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+Provision for warranties 29 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses ProvisionForBonuses 1 ProvisionForBonuses ProvisionForDoubtfulAccounts 1 ProvisionForDoubtfulAccounts ProvisionForProductWarranties 1 ProvisionForProductWarranties ProvisionForRepairs 1 ProvisionForRepairs-Purchase 4 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates-Purchase rebates 5 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates+Purchase 5 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,PurchaseAllowances+Purchase allowances 6 PurchaseAllowances,Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,SalesAllowances+Purchase rebates 6 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates,PurchaseAllowances+PurchaseAllowances 1 PurchaseAllowances PurchaseRebates 1 PurchaseRebates Purchases 2 Purchases,MerchandiseInventory Purchases\/Sales\/MerchandiseInventory). 1 MerchandiseInventory@@ -715,15 +721,15 @@ ReceiptFee 2 ReceiptFee,PaymentFees Recognized 1 EquityInEarningsOfInvestee Recovery 1 RecoveryOfBadDebts-Recovery of bad debts written off 40 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost,GrossProfit+Recovery of bad debts written off 41 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost,GrossProfit RecoveryOfBadDebts 1 RecoveryOfBadDebts Refund 4 IncomeTaxesRefundReceivable,RefundLiabilities,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable Refund liabilities 6 RefundLiabilities,IncomeTaxesRefundReceivable,DeferredTaxLiabilities,LeaseObligations,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable-Refund of income taxes 59 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense+Refund of income taxes 60 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense RefundLiabilities 1 RefundLiabilities RefundOfIncomeTaxes 1 RefundOfIncomeTaxes Rent 10 CurrentDeposits,RentExpense,RentalIncome,GainOnDonationOfFixedAssets,LandRentReceived,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LandRentPaid,BranchCurrentAccount,HeadOfficeCurrentAccount-Rent expenses on land 116 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits+Rent expenses on land 117 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits RentExpense 1 RentExpense Rental 2 RentalIncome,LandRentReceived Rental income 18 RentalIncome,LandRentReceived,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes@@ -739,10 +745,10 @@ Reserve 9 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,GeneralReserve Reserve deposits, asset side 89 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,IncomeSummary,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities Reserve deposits, liability side 50 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-Reserve for depreciation 33 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory-Reserve for dividend equalization 34 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,UnpaidDividends-Reserve for new construction 34 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory-Reserve for repairs 33 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable+Reserve for depreciation 34 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory+Reserve for dividend equalization 35 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,UnpaidDividends+Reserve for new construction 35 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory+Reserve for repairs 34 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable ReserveDepositPayable 1 ReserveDepositPayable ReserveDepositReceivable 1 ReserveDepositReceivable ReserveForDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation@@ -754,19 +760,23 @@ Retirement benefit expenses 29 RetirementBenefitExpenses,NetDefinedBenefitAsset,AllowanceForRetirementBenefits,NetDefinedBenefitLiability,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses Retirement benefit liability 44 AllowanceForRetirementBenefits,NetDefinedBenefitLiability,NetDefinedBenefitAsset,RetirementBenefitExpenses,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable RetirementBenefitExpenses 1 RetirementBenefitExpenses-Revenue 39 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Revenue: 37 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Reversal 3 ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-Reversal of allowance for doubtful accounts 67 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-Reversal of provision for repairs 63 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-Reversal of provision for warranties 62 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits+Revenue 41 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned+Revenue: 39 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned+Reversal 4 ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+Reversal of allowance for doubtful accounts 70 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses+Reversal of provision for bonuses 65 ReversalOfAllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties+Reversal of provision for repairs 64 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses+Reversal of provision for warranties 63 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses+ReversalOfAllowanceForBonuses 1 ReversalOfAllowanceForBonuses ReversalOfAllowanceForDoubtfulAccounts 1 ReversalOfAllowanceForDoubtfulAccounts ReversalOfAllowanceForProductWarranties 1 ReversalOfAllowanceForProductWarranties ReversalOfAllowanceForRepairs 1 ReversalOfAllowanceForRepairs SNA\/macro-accounting 1 AccumulatedDepreciation SNA\/マクロ系の旧称。簿記の間接法には 1 ReserveForDepreciation-Sales 14 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables-Sales rebates 15 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+Sales 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables+Sales allowances 16 SalesAllowances,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+Sales rebates 16 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+SalesAllowances 1 SalesAllowances SalesCost 1 SalesCost SalesRebates 1 SalesRebates Savings 1 Deposits@@ -774,13 +784,13 @@ Securities 12 Securities,InvestmentSecurities,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnValuationOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities SecurityDepositsPaid 1 SecurityDepositsPaid Service 3 GovernmentService,ServiceRevenue,CostOfServices-Service revenue 41 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+Service revenue 43 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee ServiceRevenue 1 ServiceRevenue Share 4 StockSubscriptionDeposits,StockIssuanceCosts,SubsidiaryStocks,AffiliateStocks Share issuance costs 6 StockIssuanceCosts,PrepaidPensionCost,StockSubscriptionDeposits,StorageExpenses,SubsidiaryStocks,AffiliateStocks Shares 3 StockSubscriptionDeposits,SubsidiaryStocks,AffiliateStocks-Shares of associates 39 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable-Shares of subsidiaries 39 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+Shares of associates 40 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+Shares of subsidiaries 40 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable Short-term 4 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable Short-term loans payable 21 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable Short-term loans receivable 25 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,LoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable@@ -820,7 +830,7 @@ SuspensePayments 1 SuspensePayments SuspenseReceipts 1 SuspenseReceipts Tax 17 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,DeferredTaxAssets-Tax revenue 55 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+Tax revenue 57 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee Taxes 9 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment TaxesExpense 1 TaxesExpense TaxesRevenue 1 TaxesRevenue@@ -838,17 +848,17 @@ Travel 1 BusinessTrip Travel and transportation 17 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments Unearned 1 UnearnedRevenue-Unearned revenue 39 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+Unearned revenue 41 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome UnearnedRevenue 1 UnearnedRevenue Unpaid 3 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends Unpaid dividends 4 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,DividendsReceived UnpaidDividends 1 UnpaidDividends-Use 9 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+Use 10 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses Utilities 1 UtilitiesExpense UtilitiesExpense 1 UtilitiesExpense Valuation 6 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnValuationOfSecurities-Valuation difference on available-for-sale securities 92 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities-Valuation loss on goods 95 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables+Valuation difference on available-for-sale securities 93 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+Valuation loss on goods 96 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables ValuationDifferenceOnOtherSecurities 1 ValuationDifferenceOnOtherSecurities Value 2 ValueAdded,AllowanceForDoubtfulAccounts Value added 2 ValueAdded,AllowanceForDoubtfulAccounts@@ -866,8 +876,7 @@ Work 1 WorkInProcess Work in process 81 WorkInProcess,Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery,Building,StockInvestment,EquipmentInvestment,RetainedEarnings WorkInProcess 1 WorkInProcess-Write 1 AllowanceForDoubtfulAccounts-a 218 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery+a 221 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery accepted 1 DepositPayable account 19 AccountsReceivable,ReserveDepositPayable,MerchandiseInventory,PrepaidExpenses,AccruedRevenue,CashOverShort,AccountsPayable,UnearnedRevenue,AccruedExpenses,AllowanceForDoubtfulAccounts accounts 8 AccountsReceivable,ReserveDepositPayable,AccountsPayable,AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,DepositsInSpecialAccounts,LongTermOtherPayables@@ -878,9 +887,9 @@ accrual 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses accrued (unpaid) consumption tax 21 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,ConsumptionTaxRefundReceivable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes accrued (unpaid) corporate income taxes 24 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome-accrued bonuses 8 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses+accrued bonuses 9 BonusesPayable,AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses accrued expenses 29 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-accrued revenue 43 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+accrued revenue 45 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome accruedconsumptiontax 1 AccruedConsumptionTax accruedcorporateincometaxes 1 AccruedCorporateIncomeTaxes accruedexpenses 1 AccruedExpenses@@ -889,7 +898,7 @@ accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense accumulateddepreciation 2 ReserveForDepreciation,AccumulatedDepreciation added 1 ValueAdded-additional income taxes for prior periods 47 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense+additional income taxes for prior periods 48 AdditionalIncomeTaxesForPriorPeriods,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,RefundOfIncomeTaxes,IncomeTaxesAdjustment,ReserveForDepreciation,AmortizationExpense,TaxesExpense additionalincometaxesforpriorperiods 1 AdditionalIncomeTaxesForPriorPeriods adjustment 2 CumulativeTranslationAdjustment,IncomeTaxesAdjustment advances paid 13 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees,SecurityDepositsPaid@@ -899,24 +908,25 @@ advertising 1 AdvertisingExpense advertisingexpense 1 AdvertisingExpense affiliatestocks 1 AffiliateStocks-allowance 8 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties-allowance for doubtful accounts 33 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,AccountsReceivable-allowanceforbonuses 1 AllowanceForBonuses+allowance 11 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SalesAllowances,PurchaseAllowances+allowance for doubtful accounts 36 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses+allowanceforbonuses 2 AllowanceForBonuses,ReversalOfAllowanceForBonuses allowancefordoubtfulaccounts 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts allowanceforproductwarranties 2 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties allowanceforrepairs 2 AllowanceForRepairs,ReversalOfAllowanceForRepairs allowanceforretirementbenefits 1 AllowanceForRetirementBenefits-amortization expense for intangibles 69 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-amortization of goodwill 41 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets-amortization of patent rights 42 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software-amortization of software 40 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee+allowances 2 SalesAllowances,PurchaseAllowances+amortization expense for intangibles 70 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense+amortization of goodwill 42 AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets+amortization of patent rights 43 AmortizationOfPatents,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Patent,Software+amortization of software 41 AmortizationOfSoftware,Software,SoftwareInProgress,AmortizationOfGoodwill,AmortizationOfPatents,AmortizationExpense,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee amortizationexpense 1 AmortizationExpense amortizationofgoodwill 1 AmortizationOfGoodwill amortizationofpatents 1 AmortizationOfPatents amortizationofsoftware 1 AmortizationOfSoftware amount 1 InvestmentInAssociate and 17 Products,Machinery,BusinessTrip,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments-as 90 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery+as 91 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery asset 82 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery assets 40 AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand,ContractAssets,IncomeTaxesRefundReceivable assets. 1 AccumulatedDepreciation@@ -944,7 +954,7 @@ benefits 1 AllowanceForRetirementBenefits bond 6 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned,HeldToMaturityBonds bonds 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,HeldToMaturityBonds-bonuses 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+bonuses 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses bonusespayable 1 BonusesPayable bookkeeping 2 AccumulatedDepreciation,IncomeSummary books 2 HeadOfficeCurrentAccount,NewspaperBooksExpenses@@ -998,7 +1008,7 @@ consumptiontaxreceived 1 ConsumptionTaxReceived consumptiontaxrefundreceivable 1 ConsumptionTaxRefundReceivable contra 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ContractAssets,ContraAccountForGuaranteeObligations-contra account for guarantee obligations 48 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,GuaranteeObligations,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable+contra account for guarantee obligations 49 ContraAccountForGuaranteeObligations,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,MerchandiseInventory,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,GuaranteeObligations,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable contraaccountforguaranteeobligations 1 ContraAccountForGuaranteeObligations contract assets 40 ContractAssets,AccumulatedDepreciation,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,TimeDeposits,LoansReceivable,GiftCertificatesReceived,SecurityDepositsPaid,SuppliesOnHand,IncomeTaxesRefundReceivable contractassets 1 ContractAssets@@ -1006,8 +1016,8 @@ corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes corporate income taxes 20 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment,TaxesExpense,NetIncome,SubsidyIncome corporateincometaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-cost of sales 74 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-cost of services 70 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents+cost of sales 77 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+cost of services 72 CostOfServices,SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,LossOnValuationOfMerchandise,AmortizationOfGoodwill,AmortizationOfSoftware,AmortizationOfPatents costofservices 1 CostOfServices costs 3 PrepaidPensionCost,StockIssuanceCosts,StorageExpenses counterpart 3 PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts@@ -1038,7 +1048,7 @@ depositpayable 2 DepositPayable,ReserveDepositPayable deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived deposits accepted 12 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived-deposits for subscriptions of shares 72 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,Deposits,CurrentDeposits+deposits for subscriptions of shares 73 StockSubscriptionDeposits,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,SubsidiaryStocks,AffiliateStocks,DepositsReceivedFromOfficers,ReversalOfAllowanceForBonuses,Deposits deposits in special accounts 95 DepositsInSpecialAccounts,Deposits,ReserveDepositPayable,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,CurrentDeposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery deposits received 18 DepositPayable,DepositsReceived,GuaranteeDepositsReceived,DepositsReceivedFromOfficers,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived deposits received from officers 20 DepositsReceivedFromOfficers,DepositPayable,DepositsReceived,GuaranteeDepositsReceived,LandRentReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived@@ -1081,13 +1091,13 @@ equipment investment 8 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities equipmentinvestment 1 EquipmentInvestment equity 17 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment,StockSubscriptionDeposits,LegalCapitalSurplus,OtherCapitalSurplus,DividendEqualizationReserve-equity in earnings of investee 115 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,SoftwareInProgress+equity in earnings of investee 116 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,EarnedSurplus,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,SoftwareInProgress equityinearningsofinvestee 1 EquityInEarningsOfInvestee estate 1 Building estimated 1 ReversalOfAllowanceForDoubtfulAccounts examples 1 ReserveDepositPayable excess 1 ReversalOfAllowanceForDoubtfulAccounts-exchange 2 ForeignExchangeGains,ForeignExchangeLosses+exchange 3 AllowanceForDoubtfulAccounts,ForeignExchangeGains,ForeignExchangeLosses existing 2 AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts expenditure 3 WageExpenditure,ConsumptionExpenditure,SubsidyExpense expense 40 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses@@ -1098,21 +1108,21 @@ fixed 5 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets fixtures 1 Fixtures fixtures and equipment 18 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand-for 28 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties-foreign exchange gains 2 ForeignExchangeGains,ForeignExchangeLosses-foreign exchange losses 2 ForeignExchangeLosses,ForeignExchangeGains+for 29 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties+foreign exchange gains 3 ForeignExchangeGains,ForeignExchangeLosses,AllowanceForDoubtfulAccounts+foreign exchange losses 3 ForeignExchangeLosses,ForeignExchangeGains,AllowanceForDoubtfulAccounts foreignexchangegains 1 ForeignExchangeGains foreignexchangelosses 1 ForeignExchangeLosses from 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LandRentReceived,DepositsReceivedFromOfficers-gain on bargain purchase 91 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants-gain on contribution received for construction 101 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses-gain on insurance claims 90 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies-gain on receipt of donated non-current assets 127 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,AccumulatedDepreciation-gain on receipt of national subsidies 106 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-gain on sale of investment securities 113 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-gain on sale of securities 110 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets-gain on sales of fixed assets 127 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies-gain on valuation of securities 108 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+gain on bargain purchase 93 GainOnBargainPurchase,MerchandiseInventory,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,InsuranceGain,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,GainOnConstructionGrants+gain on contribution received for construction 102 GainOnConstructionGrants,ConstructionFundReserve,ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,ConsumptionTaxReceived,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses+gain on insurance claims 91 InsuranceGain,ElectronicallyRecordedReceivable,GainOnSalesOfFixedAssets,NonOperatingElectronicallyRecordedReceivable,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnBargainPurchase,GainOnDonationOfFixedAssets,GainOnNationalSubsidies+gain on receipt of donated non-current assets 128 GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnNationalSubsidies,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,AccumulatedDepreciation+gain on receipt of national subsidies 107 GainOnNationalSubsidies,GainOnDonationOfFixedAssets,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable+gain on sale of investment securities 115 GainOnSalesOfInvestmentSecurities,GainOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+gain on sale of securities 112 GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnValuationOfSecurities,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets+gain on sales of fixed assets 129 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnValuationOfSecurities,GainOnNationalSubsidies+gain on valuation of securities 109 GainOnValuationOfSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnDonationOfFixedAssets,GainOnNationalSubsidies,LossOnValuationOfMerchandise,LossOnSalesOfSecurities gainonbargainpurchase 1 GainOnBargainPurchase gainonconstructiongrants 1 GainOnConstructionGrants gainondonationoffixedassets 1 GainOnDonationOfFixedAssets@@ -1166,7 +1176,7 @@ interest 9 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned,LeaseholdRights,NonControllingInterests,InterestOnSecurities,NetIncomeAttributableToNCI,NetLossAttributableToNCI interest earned 12 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue,LeaseholdRights,NonControllingInterests,EarnedSurplus,InterestOnSecurities interest expense 48 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-interest on securities 96 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities+interest on securities 97 InterestOnSecurities,NationalBondInterestEarned,ValuationDifferenceOnOtherSecurities,NonControllingInterests,GainOnSalesOfSecurities,GainOnValuationOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities interestearned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned interestexpense 1 InterestExpense interestonsecurities 1 InterestOnSecurities@@ -1181,7 +1191,7 @@ investment securities 15 InvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnValuationOfSecurities investmentinassociate 1 InvestmentInAssociate investmentsecurities 3 InvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfInvestmentSecurities-is 25 Products,AdvertisingExpense,MiscellaneousExpenses,MerchandiseInventory,CashOverShort,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,MiscellaneousLoss,CommunicationExpenses+is 26 Products,AdvertisingExpense,MiscellaneousExpenses,MerchandiseInventory,CashOverShort,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,MiscellaneousLoss,CommunicationExpenses issuance 1 StockIssuanceCosts it 97 Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,ReserveDepositReceivable,CapitalStock,RetainedEarnings,LongTermLoansPayable,ShortTermLoansPayable job. 1 AllowanceForDoubtfulAccounts@@ -1206,9 +1216,9 @@ liability 41 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable loans 8 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable,LoansReceivable loans payable 20 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-loans payable on notes 100 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable+loans payable on notes 101 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,NonOperatingNotesPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable loans receivable 23 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesReceivable-loans receivable on notes 100 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable+loans receivable on notes 101 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable loanspayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable loansreceivable 4 LongTermLoansReceivable,ShortTermLoansReceivable,NotesLoansReceivable,LoansReceivable long-term accounts payable - other 45 LongTermOtherPayables,LongTermLoansPayable,LongTermNationalBondsPayable,LongTermNationalBonds,LongTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,AccountsPayable,OtherPayables@@ -1225,16 +1235,16 @@ longtermprepaidexpenses 1 LongTermPrepaidExpenses loss 18 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss,InventoryShrinkageLoss,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables loss attributable to non-controlling interests 38 NetLossAttributableToNCI,NetIncomeAttributableToNCI,CashOverShort,NonControllingInterests,InventoryShrinkageLoss,LossOnRetirementOfFixedAssets,Products,StockInvestment,CapitalStock,CentralBankPaymentExpense-loss on fire 94 LossOnFire,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets-loss on retirement of non-current assets 132 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-loss on sale of electronically recorded monetary claims 112 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets-loss on sale of investment securities 118 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-loss on sale of receivables 113 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise-loss on sale of securities 115 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets-loss on sales of fixed assets 132 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities-loss on sales of notes receivable 122 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable-loss on tax purpose reduction entry of non-current assets 138 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities-loss on valuation of securities 113 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities+loss on fire 95 LossOnFire,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnValuationOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnRetirementOfFixedAssets+loss on retirement of non-current assets 133 LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+loss on sale of electronically recorded monetary claims 114 LossOnSalesOfElectronicallyRecordedReceivables,ElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets+loss on sale of investment securities 120 LossOnSalesOfInvestmentSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+loss on sale of receivables 115 LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise+loss on sale of securities 117 LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnValuationOfSecurities,GainOnSalesOfFixedAssets+loss on sales of fixed assets 134 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnDonationOfFixedAssets,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities+loss on sales of notes receivable 124 LossOnSalesOfNotesReceivable,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfFixedAssets,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,NotesLoansReceivable,GainOnSalesOfFixedAssets,DishonoredNotesReceivable,NonOperatingNotesReceivable+loss on tax purpose reduction entry of non-current assets 139 LossOnReductionOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,LossOnValuationOfMerchandise,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables,LossOnSalesOfSecurities+loss on valuation of securities 114 LossOnValuationOfSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities,LossOnSalesOfInvestmentSecurities,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities losses 1 ForeignExchangeLosses lossonfire 1 LossOnFire lossonreductionoffixedassets 1 LossOnReductionOfFixedAssets@@ -1267,6 +1277,7 @@ miscellaneousexpenses 1 MiscellaneousExpenses miscellaneousincome 2 CashOverShort,MiscellaneousIncome miscellaneousloss 2 CashOverShort,MiscellaneousLoss+module's 1 AllowanceForDoubtfulAccounts monetary 4 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedReceivable,LossOnSalesOfElectronicallyRecordedReceivables name. 1 AccumulatedDepreciation named 1 CommunicationExpenses@@ -1305,13 +1316,13 @@ notespayable 2 NotesPayable,NonOperatingNotesPayable notesreceivable 4 NotesReceivable,LossOnSalesOfNotesReceivable,DishonoredNotesReceivable,NonOperatingNotesReceivable obligations 5 ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedObligations,LeaseObligations,ContraAccountForGuaranteeObligations,GuaranteeObligations-of 39 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets+of 40 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets off 8 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,RecoveryOfBadDebts,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers office 2 HeadOfficeCurrentAccount,DepositsReceivedFromOfficers officers 1 DepositsReceivedFromOfficers-on 88 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip+on 89 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip operating expenses 30 OperatingExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses,LongTermPrepaidExpenses,NonOperatingNotesReceivable-operating revenue 44 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+operating revenue 46 OperatingRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome operatingexpenses 1 OperatingExpenses operatingrevenue 1 OperatingRevenue ordinary profit 3 OrdinaryProfit,GrossProfit,NetIncomeAttributableToNCI@@ -1334,7 +1345,7 @@ payment fees / fees paid 38 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,MembershipFees,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable paymentfees 2 ReceiptFee,PaymentFees payments 2 PaymentsOnBehalf,SuspensePayments-payments made on behalf 89 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense+payments made on behalf 90 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense paymentsonbehalf 1 PaymentsOnBehalf pension 1 PrepaidPensionCost periodic/3-account 1 MerchandiseInventory@@ -1360,18 +1371,20 @@ progress 2 ConstructionInProgress,SoftwareInProgress property tax payable 32 PropertyTaxPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,TaxesExpense propertytaxpayable 1 PropertyTaxPayable-provision 10 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-provision for bonuses 30 AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForProductWarranties-provision for doubtful accounts 33 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs-provision for repairs 29 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses-provision for retirement benefits 32 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties-provision for warranties 28 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses+provision 11 ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+provision for bonuses 31 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+provision for doubtful accounts 34 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs+provision for repairs 30 AllowanceForRepairs,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits,RepairFundReserve,ReversalOfAllowanceForProductWarranties,ProvisionForBonuses+provision for retirement benefits 33 AllowanceForRetirementBenefits,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForRepairs,ProvisionForBonuses,ProvisionForProductWarranties+provision for warranties 29 AllowanceForProductWarranties,ReversalOfAllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ProvisionForBonuses provisionforbonuses 1 ProvisionForBonuses provisionfordoubtfulaccounts 1 ProvisionForDoubtfulAccounts provisionforproductwarranties 1 ProvisionForProductWarranties provisionforrepairs 1 ProvisionForRepairs-purchase 4 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates-purchase rebates 5 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates+purchase 5 Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,PurchaseAllowances+purchase allowances 6 PurchaseAllowances,Purchases,MerchandiseInventory,GainOnBargainPurchase,PurchaseRebates,SalesAllowances+purchase rebates 6 PurchaseRebates,Purchases,MerchandiseInventory,GainOnBargainPurchase,SalesRebates,PurchaseAllowances+purchaseallowances 1 PurchaseAllowances purchaserebates 1 PurchaseRebates purchases 2 Purchases,MerchandiseInventory purpose 1 LossOnReductionOfFixedAssets@@ -1389,18 +1402,18 @@ received 10 DepositPayable,AdvancesReceived,DepositsReceived,ConsumptionTaxReceived,GiftCertificatesReceived,GuaranteeDepositsReceived,DividendsReceived,GainOnConstructionGrants,LandRentReceived,DepositsReceivedFromOfficers reciprocal 1 HeadOfficeCurrentAccount recorded 5 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations,NonOperatingElectronicallyRecordedReceivable,NonOperatingElectronicallyRecordedObligations,LossOnSalesOfElectronicallyRecordedReceivables-recovery of bad debts written off 40 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost,GrossProfit+recovery of bad debts written off 41 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,GainOnDonationOfFixedAssets,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,HeadOfficeCurrentAccount,DepositsReceivedFromOfficers,SalesCost,GrossProfit recoveryofbaddebts 1 RecoveryOfBadDebts reduction 1 LossOnReductionOfFixedAssets refund 4 IncomeTaxesRefundReceivable,RefundLiabilities,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable refund liabilities 6 RefundLiabilities,IncomeTaxesRefundReceivable,DeferredTaxLiabilities,LeaseObligations,RefundOfIncomeTaxes,ConsumptionTaxRefundReceivable-refund of income taxes 59 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense+refund of income taxes 60 RefundOfIncomeTaxes,IncomeTaxesRefundReceivable,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,AdditionalIncomeTaxesForPriorPeriods,IncomeTaxesAdjustment,NetIncomeAttributableToNCI,SalesCost,TaxesExpense refundliabilities 1 RefundLiabilities refundofincometaxes 1 RefundOfIncomeTaxes related 1 AccumulatedDepreciation released). 1 ReversalOfAllowanceForDoubtfulAccounts rent 10 CurrentDeposits,RentExpense,RentalIncome,GainOnDonationOfFixedAssets,LandRentReceived,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,LandRentPaid,BranchCurrentAccount,HeadOfficeCurrentAccount-rent expenses on land 116 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits+rent expenses on land 117 LandRentPaid,Commutation,CommunicationExpenses,LongTermPrepaidExpenses,GainOnDonationOfFixedAssets,LandRentReceived,IncorporationExpenses,LossOnRetirementOfFixedAssets,LossOnReductionOfFixedAssets,CurrentDeposits rental income 18 RentalIncome,LandRentReceived,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes rental income from land 24 LandRentReceived,RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,Land,CashOverShort,AccruedCorporateIncomeTaxes rentalincome 1 RentalIncome@@ -1414,10 +1427,10 @@ reserve 9 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,GeneralReserve reserve deposits, asset side 89 ReserveDepositReceivable,ReserveDepositPayable,AccumulatedDepreciation,AllowanceForDoubtfulAccounts,IncomeSummary,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities reserve deposits, liability side 50 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-reserve for depreciation 33 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory-reserve for dividend equalization 34 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,UnpaidDividends-reserve for new construction 34 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory-reserve for repairs 33 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable+reserve for depreciation 34 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,DividendEqualizationReserve,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory+reserve for dividend equalization 35 DividendEqualizationReserve,ReserveForDepreciation,AccumulatedDepreciation,RepairFundReserve,ConstructionFundReserve,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,UnpaidDividends+reserve for new construction 35 ConstructionFundReserve,ReserveForDepreciation,AccumulatedDepreciation,DividendEqualizationReserve,RepairFundReserve,GainOnConstructionGrants,ReserveDepositReceivable,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory+reserve for repairs 34 RepairFundReserve,ReserveForDepreciation,AccumulatedDepreciation,AllowanceForRepairs,DividendEqualizationReserve,ConstructionFundReserve,ReversalOfAllowanceForRepairs,ProvisionForRepairs,ReserveDepositReceivable,ReserveDepositPayable reservedepositpayable 1 ReserveDepositPayable reservedepositreceivable 1 ReserveDepositReceivable reservefordepreciation 2 ReserveForDepreciation,AccumulatedDepreciation@@ -1429,17 +1442,21 @@ retirement benefit expenses 29 RetirementBenefitExpenses,NetDefinedBenefitAsset,AllowanceForRetirementBenefits,NetDefinedBenefitLiability,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses retirement benefit liability 44 AllowanceForRetirementBenefits,NetDefinedBenefitLiability,NetDefinedBenefitAsset,RetirementBenefitExpenses,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable retirementbenefitexpenses 1 RetirementBenefitExpenses-revenue 39 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-reversal of allowance for doubtful accounts 67 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-reversal of provision for repairs 63 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses,AllowanceForRetirementBenefits-reversal of provision for warranties 62 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses,AllowanceForRetirementBenefits+revenue 41 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned+reversal of allowance for doubtful accounts 70 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties,AllowanceForBonuses+reversal of provision for bonuses 65 ReversalOfAllowanceForBonuses,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForBonuses,ProvisionForBonuses,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForProductWarranties+reversal of provision for repairs 64 ReversalOfAllowanceForRepairs,ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForRepairs,ProvisionForRepairs,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForProductWarranties,AllowanceForBonuses+reversal of provision for warranties 63 ReversalOfAllowanceForProductWarranties,ReversalOfAllowanceForRepairs,ReversalOfAllowanceForBonuses,ReversalOfAllowanceForDoubtfulAccounts,AllowanceForProductWarranties,ProvisionForProductWarranties,ProvisionForDoubtfulAccounts,CommunicationExpenses,AllowanceForRepairs,AllowanceForBonuses+reversalofallowanceforbonuses 1 ReversalOfAllowanceForBonuses reversalofallowancefordoubtfulaccounts 1 ReversalOfAllowanceForDoubtfulAccounts reversalofallowanceforproductwarranties 1 ReversalOfAllowanceForProductWarranties reversalofallowanceforrepairs 1 ReversalOfAllowanceForRepairs rights 2 LeaseholdRights,AmortizationOfPatents-sale 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates-sales 14 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables-sales rebates 15 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+sale 16 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,ValuationDifferenceOnOtherSecurities,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+sales 15 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates,LossOnSalesOfElectronicallyRecordedReceivables+sales allowances 16 SalesAllowances,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,SalesRebates+sales rebates 16 SalesRebates,SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets,GainOnSalesOfSecurities,GainOnSalesOfInvestmentSecurities,LossOnSalesOfElectronicallyRecordedReceivables+salesallowances 1 SalesAllowances salescost 1 SalesCost salesrebates 1 SalesRebates savings deposits 12 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived,TimeDeposits,SecurityDepositsPaid,DepositsInSpecialAccounts,GuaranteeDepositsReceived@@ -1447,13 +1464,13 @@ securitydepositspaid 1 SecurityDepositsPaid separately 2 NetIncomeAttributableToNCI,NetLossAttributableToNCI service 3 GovernmentService,ServiceRevenue,CostOfServices-service revenue 41 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+service revenue 43 ServiceRevenue,GovernmentService,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee servicerevenue 1 ServiceRevenue services 1 CostOfServices share issuance costs 6 StockIssuanceCosts,PrepaidPensionCost,StockSubscriptionDeposits,StorageExpenses,SubsidiaryStocks,AffiliateStocks shares 3 StockSubscriptionDeposits,SubsidiaryStocks,AffiliateStocks-shares of associates 39 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable-shares of subsidiaries 39 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+shares of associates 40 AffiliateStocks,StockSubscriptionDeposits,SubsidiaryStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable+shares of subsidiaries 40 SubsidiaryStocks,StockSubscriptionDeposits,AffiliateStocks,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable short 5 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,CashOverShort short-term loans payable 21 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable short-term loans receivable 25 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,LoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable@@ -1465,7 +1482,6 @@ shorttermnationalbondspayable 1 ShortTermNationalBondsPayable shrinkage 1 InventoryShrinkageLoss side 5 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,IncomeSummary-side's 1 AllowanceForDoubtfulAccounts smaller 1 ReversalOfAllowanceForDoubtfulAccounts software 3 Software,SoftwareInProgress,AmortizationOfSoftware software in progress 83 SoftwareInProgress,ConstructionInProgress,Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery,Building,StockInvestment,EquipmentInvestment@@ -1502,7 +1518,7 @@ suspensepayments 1 SuspensePayments suspensereceipts 1 SuspenseReceipts tax 17 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,DeferredTaxAssets-tax revenue 55 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee+tax revenue 57 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee taxes 9 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,IncomeTaxesRefundReceivable,AdditionalIncomeTaxesForPriorPeriods,RefundOfIncomeTaxes,IncomeTaxesAdjustment taxesexpense 1 TaxesExpense taxesrevenue 1 TaxesRevenue@@ -1525,17 +1541,17 @@ travel and transportation 17 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts,SuppliesOnHand,ToolsAndInstruments treasury 2 CentralBankPaymentExpense,CentralBankPaymentIncome under 4 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,AccumulatedDepreciation-unearned revenue 39 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome+unearned revenue 41 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome unearnedrevenue 1 UnearnedRevenue unpaid dividends 4 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,DividendsReceived unpaiddividends 1 UnpaidDividends-use 9 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses+use 10 MiscellaneousExpenses,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses used 1 ReversalOfAllowanceForDoubtfulAccounts utilities 1 UtilitiesExpense utilitiesexpense 1 UtilitiesExpense valuation 6 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfMerchandise,LossOnValuationOfSecurities-valuation difference on available-for-sale securities 92 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities-valuation loss on goods 95 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables+valuation difference on available-for-sale securities 93 ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,GainOnSalesOfSecurities,InterestOnSecurities,GainOnSalesOfInvestmentSecurities,LossOnValuationOfMerchandise,LossOnSalesOfSecurities+valuation loss on goods 96 LossOnValuationOfMerchandise,LossOnValuationOfSecurities,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,ValuationDifferenceOnOtherSecurities,GainOnValuationOfSecurities,LossOnSalesOfElectronicallyRecordedReceivables,LossOnSalesOfReceivables valuationdifferenceonothersecurities 1 ValuationDifferenceOnOtherSecurities value added 2 ValueAdded,AllowanceForDoubtfulAccounts valueadded 1 ValueAdded@@ -1575,7 +1591,8 @@ 不渡手形 1 DishonoredNotesReceivable 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 1 ReserveDepositPayable 交際費 1 EntertainmentExpenses-仕入 3 Products,Purchases,PurchaseRebates+仕入 4 Products,Purchases,PurchaseRebates,PurchaseAllowances+仕入値引 1 PurchaseAllowances 仕入割戻 1 PurchaseRebates 仕掛品 1 WorkInProcess 付加価値。SNA\/マクロ系 1 ValueAdded@@ -1651,7 +1668,8 @@ 国庫補助金受贈益 1 GainOnNationalSubsidies 土地 1 Land 在外子会社等の外貨建財務諸表の換算差額を計上する 1 CumulativeTranslationAdjustment-売上 5 Products,SalesCost,GrossProfit,Sales,SalesRebates+売上 6 Products,SalesCost,GrossProfit,Sales,SalesRebates,SalesAllowances+売上値引 1 SalesAllowances 売上割戻 1 SalesRebates 売上原価 1 SalesCost 売上総利益 — 決算振替用 10 GrossProfit,NetIncome,OrdinaryProfit,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation@@ -1673,8 +1691,8 @@ 当座預金 1 CurrentDeposits 当期純利益 — 決算振替用。借方側に立つため Expense 区分 48 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost 当期純利益 — 決算振替用。借方側に立つため expense 区分 48 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost-当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 46 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,NetLossAttributableToNCI,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense-当期純損失 — 決算振替用。貸方側に立つため revenue 区分 46 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,NetLossAttributableToNCI,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense+当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 48 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,NetLossAttributableToNCI,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense+当期純損失 — 決算振替用。貸方側に立つため revenue 区分 48 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,NetLossAttributableToNCI,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense 役務原価 1 CostOfServices 役務収益 1 ServiceRevenue 役員預り金 1 DepositsReceivedFromOfficers@@ -1798,8 +1816,9 @@ 資本準備金 1 LegalCapitalSurplus 資本金 1 CapitalStock 資産側 1 ReserveDepositReceivable-賞与 4 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses-賞与引当金 2 AllowanceForBonuses,ProvisionForBonuses+賞与 5 BonusesPayable,AllowanceForBonuses,Bonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses+賞与引当金 3 AllowanceForBonuses,ProvisionForBonuses,ReversalOfAllowanceForBonuses+賞与引当金戻入 1 ReversalOfAllowanceForBonuses 賞与引当金繰入 1 ProvisionForBonuses 車両運搬具 1 Vehicle 返金負債 1 RefundLiabilities@@ -1813,7 +1832,7 @@ 通信費 — 旧称。新規コードでは 'communicationexpenses' を使う 12 Commutation,ReserveForDepreciation,ReserveDepositPayable,Products,ReserveDepositReceivable,DepositPayable,AmortizationExpense,NetIncome,GrossProfit,OrdinaryProfit 還付法人税等 2 IncomeTaxesRefundReceivable,RefundOfIncomeTaxes 配当平均積立金 1 DividendEqualizationReserve-金 74 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings+金 75 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 1 DepositPayable 長期借入金 1 LongTermLoansPayable 長期前払費用 1 LongTermPrepaidExpenses
− test/fixtures/pre-account-semantics-050/README.md
@@ -1,33 +0,0 @@-# Pre-account-semantics 0.5.0.0 baseline--These schema-1 fixtures freeze all 232 concrete account titles at commit-`0d8e2791429145f2a48c79adbe62563328ee5c0b`, immediately before-`audit-harness:account-semantics-reporting-pipeline` Land 1-5.--The fixtures are compatibility evidence. Placeholder divisions and legacy-presentation placement are recorded as observed behavior, not endorsed as-normatively correct accounting classifications.--- `semantics.tsv`: Enum and actual Binary bytes, registry division/closing/- contra/fixed-current metadata, side/PIMO behavior, final-stock transfer.-- `account-info.tsv`: exact LLM-facing `Assist.AccountInfo` projection.-- `projection-membership.tsv`: membership in the eight current balance-sheet- projection functions for both Not and Hat postings.-- `presentation.tsv`: exact `bsRows` and `plRows` output for one Not posting.-- `consumer-inventory.txt`: closed initial search result for functions and- metadata coupled to the current classification model.--Regenerate into a temporary directory with:--```text-stack exec runghc -- -isrc tools/DumpAccountSemanticsGolden.hs <temporary-output-directory>-```--Verify the closed consumer search with:--```text-tools/check-account-semantics-consumers.sh-```--Never overwrite these files after a semantic change. Expected Land 1-5 changes-must be expressed as explicit closed differences against this baseline.
− test/fixtures/pre-account-semantics-050/account-info.tsv
@@ -1,233 +0,0 @@-# pre-account-semantics-050 AccountInfo (title, division, homeSide, nameEn, nameJa, description); schema 1; commit 0d8e2791429145f2a48c79adbe62563328ee5c0b-Cash Assets Debit Cash 現金 Asset: Cash (現金)-Deposits Assets Debit Savings deposits 普通預金 Asset: Savings deposits (普通預金)-CurrentDeposits Assets Debit Current deposits 当座預金 Asset: Current deposits (当座預金)-Securities Assets Debit Securities 有価証券 Asset: Securities (有価証券)-InvestmentSecurities Assets Debit Investment securities 投資有価証券 Asset: Investment securities (投資有価証券)-InvestmentInAssociate Assets Debit Investment in associate 関係会社株式 Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定).-LongTermNationalBonds Assets Debit Long-term national bonds 長期国債 Asset: Long-term national bonds (長期国債)-ShortTermNationalBonds Assets Debit Short-term national bonds 短期国債 Asset: Short-term national bonds (短期国債)-Products Assets Debit Products 商品 Asset: Products (商品)。分記法用。3 分法 (仕入\/売上\/繰越商品) では 'MerchandiseInventory' を使う-Machinery Assets Debit Machinery and equipment 機械装置 Asset: Machinery and equipment (機械装置)-Building Assets Debit Real estate 建物 Asset: Real estate (建物)-Vehicle Assets Debit Vehicles 車両運搬具 Asset: Vehicles (車両運搬具)-StockInvestment Assets Debit Stock investment 株式投資 Asset: Stock investment (株式投資)-EquipmentInvestment Assets Debit Equipment investment 設備投資 Asset: Equipment investment (設備投資)-LongTermLoansReceivable Assets Debit Long-term loans receivable 長期貸付金 Asset: Long-term loans receivable (長期貸付金)-AccountsReceivable Assets Debit Accounts receivable 売掛金 Asset: Accounts receivable (売掛金)-ShortTermLoansReceivable Assets Debit Short-term loans receivable 短期貸付金 Asset: Short-term loans receivable (短期貸付金)-ReserveDepositReceivable Assets Debit Reserve deposits, asset side 準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系 Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系)-Gold Assets Debit Gold 金 Asset: Gold (金)-GovernmentService Assets Debit Government service 政府サービス。SNA\/マクロ系 Asset: Government service (政府サービス。SNA\/マクロ系)-CapitalStock Equity Credit Capital stock 資本金 Equity: Capital stock (資本金)-RetainedEarnings Equity Credit Retained earnings 繰越利益剰余金 Equity: Retained earnings (繰越利益剰余金)-LongTermLoansPayable Liability Credit Long-term loans payable 長期借入金 Liability: Long-term loans payable (長期借入金)-ShortTermLoansPayable Liability Credit Short-term loans payable 短期借入金 Liability: Short-term loans payable (短期借入金)-LoansPayable Liability Credit Loans payable 借入金 Liability: Loans payable (借入金)-ReserveForDepreciation Liability Credit Reserve for depreciation 減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額 Liability: Reserve for depreciation (減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)-DepositPayable Liability Credit Deposits accepted 受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived' Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived')-LongTermNationalBondsPayable Liability Credit Long-term national bonds payable 長期国債, 発行側 Liability: Long-term national bonds payable (長期国債, 発行側)-ShortTermNationalBondsPayable Liability Credit Short-term national bonds payable 短期国債, 発行側 Liability: Short-term national bonds payable (短期国債, 発行側)-ReserveDepositPayable Liability Credit Reserve deposits, liability side 準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系 Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)-CentralBankNotePayable Liability Credit Central bank notes 発行銀行券。SNA\/マクロ系 Liability: Central bank notes (発行銀行券。SNA\/マクロ系)-Depreciation Cost Debit Depreciation 減価償却費 Expense: Depreciation (減価償却費)-AmortizationExpense Cost Debit Amortization expense for intangibles 無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費 Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)-SalesCost Cost Debit Cost of sales 売上原価 Expense: Cost of sales (売上原価)-BusinessTrip Cost Debit Travel and transportation 旅費交通費 Expense: Travel and transportation (旅費交通費)-Commutation Cost Debit Communication 通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)-UtilitiesExpense Cost Debit Utilities 水道光熱費 Expense: Utilities (水道光熱費)-RentExpense Cost Debit Rent 支払家賃 Expense: Rent (支払家賃)-AdvertisingExpense Cost Debit Advertising 広告宣伝費 Expense: Advertising (広告宣伝費)-DeliveryExpenses Cost Debit Delivery 発送費 Expense: Delivery (発送費)-SuppliesExpenses Cost Debit Supplies 消耗品費 Expense: Supplies (消耗品費)-MiscellaneousExpenses Cost Debit Miscellaneous 雑費 Expense: Miscellaneous (雑費)-WageExpenditure Cost Debit Wages 給料 Expense: Wages (給料)-InterestExpense Cost Debit Interest expense 支払利息 Expense: Interest expense (支払利息)-TaxesExpense Cost Debit Taxes 租税公課 Expense: Taxes (租税公課)-ConsumptionExpenditure Cost Debit Consumption expenditure 消費支出。SNA\/マクロ系 Expense: Consumption expenditure (消費支出。SNA\/マクロ系)-SubsidyExpense Cost Debit Subsidy expenditure 補助金支出。SNA\/マクロ系 Expense: Subsidy expenditure (補助金支出。SNA\/マクロ系)-CentralBankPaymentExpense Cost Debit Central bank payment to treasury 国庫納付金支出。SNA\/マクロ系 Expense: Central bank payment to treasury (国庫納付金支出。SNA\/マクロ系)-Purchases Cost Debit Purchases 仕入 Expense: Purchases (仕入)-NetIncome Cost Debit Net income 当期純利益 — 決算振替用。借方側に立つため Expense 区分 Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)-ValueAdded Revenue Credit Value added 付加価値。SNA\/マクロ系 Revenue: Value added (付加価値。SNA\/マクロ系)-SubsidyIncome Revenue Credit Subsidy income 補助金収入。SNA\/マクロ系 Revenue: Subsidy income (補助金収入。SNA\/マクロ系)-NationalBondInterestEarned Revenue Credit National bond interest earned 国債利息収入 Revenue: National bond interest earned (国債利息収入)-DepositInterestEarned Revenue Credit Deposit interest earned 預金利息収入 Revenue: Deposit interest earned (預金利息収入)-GrossProfit Revenue Credit Gross profit 売上総利益 — 決算振替用 Revenue: Gross profit (売上総利益 — 決算振替用)-OrdinaryProfit Revenue Credit Ordinary profit 経常利益 — 決算振替用 Revenue: Ordinary profit (経常利益 — 決算振替用)-InterestEarned Revenue Credit Interest earned 受取利息 Revenue: Interest earned (受取利息)-ReceiptFee Revenue Credit Receipt fee 受取手数料。支払側は 'PaymentFees' Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')-RentalIncome Revenue Credit Rental income 受取家賃 Revenue: Rental income (受取家賃)-WageEarned Revenue Credit Wage income 賃金収入。SNA\/マクロ系 Revenue: Wage income (賃金収入。SNA\/マクロ系)-TaxesRevenue Revenue Credit Tax revenue 租税収入。SNA\/マクロ系 Revenue: Tax revenue (租税収入。SNA\/マクロ系)-CentralBankPaymentIncome Revenue Credit Central bank payment to treasury 国庫納付金収入。SNA\/マクロ系 Revenue: Central bank payment to treasury (国庫納付金収入。SNA\/マクロ系)-Sales Revenue Credit Sales 売上 Revenue: Sales (売上)-EquityInEarningsOfInvestee Revenue Credit Equity in earnings of investee 持分法による投資利益 Revenue: Equity in earnings of investee (持分法による投資利益). Recognised under the equity method.-NetLoss Revenue Credit Net loss 当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)-PettyCash Assets Debit Petty cash 小口現金 Asset: Petty cash (小口現金)-NotesReceivable Assets Debit Notes receivable 受取手形 Asset: Notes receivable (受取手形)-ElectronicallyRecordedReceivable Assets Debit Electronically recorded monetary claims 電子記録債権 Asset: Electronically recorded monetary claims (電子記録債権)-CreditCardReceivable Assets Debit Credit card receivable クレジット売掛金 Asset: Credit card receivable (クレジット売掛金)-NotesLoansReceivable Assets Debit Loans receivable on notes 手形貸付金 Asset: Loans receivable on notes (手形貸付金)-MerchandiseInventory Assets Debit Merchandise inventory 繰越商品 Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\/Sales\/MerchandiseInventory). For the perpetual\/specific-identification method (分記法) use 'Products' instead.-AdvancesPaid Assets Debit Advances paid 前払金 Asset: Advances paid (前払金)-PrepaidExpenses Assets Debit Prepaid expenses 前払費用 Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)-AccruedRevenue Assets Debit Accrued revenue 未収収益 Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)-OtherReceivables Assets Debit Other receivables 未収入金 Asset: Other receivables (未収入金)-PaymentsOnBehalf Assets Debit Payments made on behalf 立替金 Asset: Payments made on behalf (立替金)-SuspensePayments Assets Debit Suspense payments 仮払金 Asset: Suspense payments (仮払金)-ConsumptionTaxPaid Assets Debit Consumption tax paid 仮払消費税 Asset: Consumption tax paid (仮払消費税)-PrepaidCorporateIncomeTaxes Assets Debit Prepaid corporate income taxes 仮払法人税等 Asset: Prepaid corporate income taxes (仮払法人税等)-Land Assets Debit Land 土地 Asset: Land (土地)-Fixtures Assets Debit Fixtures and equipment 備品 Asset: Fixtures and equipment (備品)-Patent Assets Debit Patent 特許権 Asset: Patent (特許権)-Trademark Assets Debit Trademark 商標権 Asset: Trademark (商標権)-Software Assets Debit Software ソフトウェア Asset: Software (ソフトウェア)-CashOverShort Assets Debit Cash over and short 現金過不足 Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\/MiscellaneousLoss-AccountsPayable Liability Credit Accounts payable 買掛金 Liability: Accounts payable (買掛金)-NotesPayable Liability Credit Notes payable 支払手形 Liability: Notes payable (支払手形)-ElectronicallyRecordedObligations Liability Credit Electronically recorded monetary obligations 電子記録債務 Liability: Electronically recorded monetary obligations (電子記録債務)-NotesLoansPayable Liability Credit Loans payable on notes 手形借入金 Liability: Loans payable on notes (手形借入金)-BankOverdraft Liability Credit Bank overdraft 当座借越 Liability: Bank overdraft (当座借越)-AdvancesReceived Liability Credit Advances received 前受金 Liability: Advances received (前受金)-UnearnedRevenue Liability Credit Unearned revenue 前受収益 Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)-AccruedExpenses Liability Credit Accrued expenses 未払費用 Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)-OtherPayables Liability Credit Other payables 未払金 Liability: Other payables (未払金)-DepositsReceived Liability Credit Deposits received 預り金 Liability: Deposits received (預り金)-SuspenseReceipts Liability Credit Suspense receipts 仮受金 Liability: Suspense receipts (仮受金)-ConsumptionTaxReceived Liability Credit Consumption tax received 仮受消費税 Liability: Consumption tax received (仮受消費税)-AccruedConsumptionTax Liability Credit Accrued (unpaid) consumption tax 未払消費税 Liability: Accrued (unpaid) consumption tax (未払消費税)-AccruedCorporateIncomeTaxes Liability Credit Accrued (unpaid) corporate income taxes 未払法人税等 Liability: Accrued (unpaid) corporate income taxes (未払法人税等)-UnpaidDividends Liability Credit Unpaid dividends 未払配当金 Liability: Unpaid dividends (未払配当金)-AllowanceForDoubtfulAccounts Assets Credit Allowance for doubtful accounts 貸倒引当金 Asset (contra): Allowance for doubtful accounts (貸倒引当金), a credit-balance valuation account (評価勘定) deducted from receivables. Home side is Credit because it is a contra asset (isContra); values stay non-negative and the Hat\/Not structure is intact. B\/S deduction (net) presentation is the Write side's job.-AccumulatedDepreciation Assets Credit Accumulated depreciation 減価償却累計額 Asset (contra): Accumulated depreciation (減価償却累計額), a credit-balance valuation account (評価勘定) under the indirect method (間接法), deducted from the related depreciable assets. Home side is Credit because it is a contra asset (isContra). This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name.-LegalRetainedEarnings Equity Credit Legal (appropriated) retained earnings reserve 利益準備金 Equity: Legal (appropriated) retained earnings reserve (利益準備金)-CumulativeTranslationAdjustment Equity Credit Cumulative translation adjustment 為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目 Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)-ProvisionForDoubtfulAccounts Cost Debit Provision for doubtful accounts 貸倒引当金繰入 Cost: Provision for doubtful accounts (貸倒引当金繰入)-BadDebtLoss Cost Debit Bad debt loss 貸倒損失 Cost: Bad debt loss (貸倒損失)-LossOnSalesOfFixedAssets Cost Debit Loss on sales of fixed assets 固定資産売却損 Cost: Loss on sales of fixed assets (固定資産売却損)-LossOnSalesOfNotesReceivable Cost Debit Loss on sales of notes receivable 手形売却損 Cost: Loss on sales of notes receivable (手形売却損)-PaymentFees Cost Debit Payment fees / fees paid 支払手数料 Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'-MiscellaneousLoss Cost Debit Miscellaneous loss 雑損 Cost: Miscellaneous loss (雑損)-CorporateIncomeTaxes Cost Debit Corporate income taxes 法人税等 Cost: Corporate income taxes (法人税等)-CommunicationExpenses Cost Debit Communication expenses 通信費 Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also "Communication"); 'Commutation' is retained for backward compatibility.-GainOnSalesOfFixedAssets Revenue Credit Gain on sales of fixed assets 固定資産売却益 Revenue: Gain on sales of fixed assets (固定資産売却益)-RecoveryOfBadDebts Revenue Credit Recovery of bad debts written off 償却債権取立益 Revenue: Recovery of bad debts written off (償却債権取立益)-MiscellaneousIncome Revenue Credit Miscellaneous income 雑益 Revenue: Miscellaneous income (雑益)-ReversalOfAllowanceForDoubtfulAccounts Revenue Credit Reversal of allowance for doubtful accounts 貸倒引当金戻入 Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released).-TimeDeposits Assets Debit Time deposits 定期預金 Assets: Time deposits (定期預金)-LoansReceivable Assets Debit Loans receivable 貸付金 Assets: Loans receivable (貸付金)-GiftCertificatesReceived Assets Debit Gift certificates received 受取商品券 Assets: Gift certificates received (受取商品券)-SecurityDepositsPaid Assets Debit Guarantee deposits 差入保証金 Assets: Guarantee deposits (差入保証金)-SuppliesOnHand Assets Debit Supplies 貯蔵品 Assets: Supplies (貯蔵品)-ContractAssets Assets Debit Contract assets 契約資産 Assets: Contract assets (契約資産)-IncomeTaxesRefundReceivable Assets Debit Income taxes refund receivable 未収還付法人税等 Assets: Income taxes refund receivable (未収還付法人税等)-WorkInProcess Assets Debit Work in process 仕掛品 Assets: Work in process (仕掛品)-DeferredTaxAssets Assets Debit Deferred tax assets 繰延税金資産 Assets: Deferred tax assets (繰延税金資産)-LeasedAssets Assets Debit Leased assets リース資産 Assets: Leased assets (リース資産)-ToolsAndInstruments Assets Debit Tools and instruments 工具器具 Assets: Tools and instruments (工具器具)-ConstructionInProgress Assets Debit Construction in progress 建設仮勘定 Assets: Construction in progress (建設仮勘定)-Goodwill Assets Debit Goodwill のれん Assets: Goodwill (のれん)-SoftwareInProgress Assets Debit Software in progress ソフトウェア仮勘定 Assets: Software in progress (ソフトウェア仮勘定)-LongTermPrepaidExpenses Assets Debit Long-term prepaid expenses 長期前払費用 Assets: Long-term prepaid expenses (長期前払費用)-DishonoredNotesReceivable Assets Debit Dishonored notes receivable 不渡手形 Assets: Dishonored notes receivable (不渡手形)-PrepaidPensionCost Assets Debit Prepaid pension costs 前払年金費用 Assets: Prepaid pension costs (前払年金費用)-NetDefinedBenefitAsset Assets Debit Retirement benefit asset 退職給付に係る資産 Assets: Retirement benefit asset (退職給付に係る資産)-DepositsInSpecialAccounts Assets Debit Deposits in special accounts 別段預金 Assets: Deposits in special accounts (別段預金)-Structures Assets Debit Structures 構築物 Assets: Structures (構築物)-LeaseholdRights Assets Debit Leasehold interests in land 借地権 Assets: Leasehold interests in land (借地権)-NonOperatingNotesReceivable Assets Debit Non-operating notes receivable 営業外受取手形 Assets: Non-operating notes receivable (営業外受取手形)-NonOperatingElectronicallyRecordedReceivable Assets Debit Electronically recorded monetary claims - non-operating 営業外電子記録債権 Assets: Electronically recorded monetary claims - non-operating (営業外電子記録債権)-RefundLiabilities Liability Credit Refund liabilities 返金負債 Liability: Refund liabilities (返金負債)-NonOperatingNotesPayable Liability Credit Non-operating notes payable 営業外支払手形 Liability: Non-operating notes payable (営業外支払手形)-NonOperatingElectronicallyRecordedObligations Liability Credit Electronically recorded obligations - non-operating 営業外電子記録債務 Liability: Electronically recorded obligations - non-operating (営業外電子記録債務)-BonusesPayable Liability Credit Accrued bonuses 未払賞与 Liability: Accrued bonuses (未払賞与)-AllowanceForRepairs Liability Credit Provision for repairs 修繕引当金 Liability: Provision for repairs (修繕引当金)-AllowanceForProductWarranties Liability Credit Provision for warranties 商品保証引当金 Liability: Provision for warranties (商品保証引当金)-AllowanceForBonuses Liability Credit Provision for bonuses 賞与引当金 Liability: Provision for bonuses (賞与引当金)-DeferredTaxLiabilities Liability Credit Deferred tax liabilities 繰延税金負債 Liability: Deferred tax liabilities (繰延税金負債)-LeaseObligations Liability Credit Lease liabilities リース債務 Liability: Lease liabilities (リース債務)-GuaranteeDepositsReceived Liability Credit Guarantee deposits received 預り保証金 Liability: Guarantee deposits received (預り保証金)-AllowanceForRetirementBenefits Liability Credit Provision for retirement benefits 退職給付引当金 Liability: Provision for retirement benefits (退職給付引当金)-LongTermOtherPayables Liability Credit Long-term accounts payable - other 長期未払金 Liability: Long-term accounts payable - other (長期未払金)-NetDefinedBenefitLiability Liability Credit Retirement benefit liability 退職給付に係る負債 Liability: Retirement benefit liability (退職給付に係る負債)-StockSubscriptionDeposits Equity Credit Deposits for subscriptions of shares 株式申込証拠金 Equity: Deposits for subscriptions of shares (株式申込証拠金)-LegalCapitalSurplus Equity Credit Legal capital surplus 資本準備金 Equity: Legal capital surplus (資本準備金)-OtherCapitalSurplus Equity Credit Other capital surplus その他資本剰余金 Equity: Other capital surplus (その他資本剰余金)-DividendEqualizationReserve Equity Credit Reserve for dividend equalization 配当平均積立金 Equity: Reserve for dividend equalization (配当平均積立金)-RepairFundReserve Equity Credit Reserve for repairs 修繕積立金 Equity: Reserve for repairs (修繕積立金)-ConstructionFundReserve Equity Credit Reserve for new construction 新築積立金 Equity: Reserve for new construction (新築積立金)-GeneralReserve Equity Credit General reserve 別途積立金 Equity: General reserve (別途積立金)-ValuationDifferenceOnOtherSecurities Equity Credit Valuation difference on available-for-sale securities その他有価証券評価差額金 Equity: Valuation difference on available-for-sale securities (その他有価証券評価差額金)-NonControllingInterests Equity Credit Non-controlling interests 非支配株主持分 Equity: Non-controlling interests (非支配株主持分)-CapitalSurplus Equity Credit Capital surplus 資本剰余金 Equity: Capital surplus (資本剰余金)-EarnedSurplus Equity Credit Retained earnings 利益剰余金 Equity: Retained earnings (利益剰余金)-ServiceRevenue Revenue Credit Service revenue 役務収益 Revenue: Service revenue (役務収益)-OperatingRevenue Revenue Credit Operating revenue 営業収益 Revenue: Operating revenue (営業収益)-GainOnSalesOfSecurities Revenue Credit Gain on sale of securities 有価証券売却益 Revenue: Gain on sale of securities (有価証券売却益)-GainOnValuationOfSecurities Revenue Credit Gain on valuation of securities 有価証券評価益 Revenue: Gain on valuation of securities (有価証券評価益)-DividendsReceived Revenue Credit Dividend income 受取配当金 Revenue: Dividend income (受取配当金)-InterestOnSecurities Revenue Credit Interest on securities 有価証券利息 Revenue: Interest on securities (有価証券利息)-GainOnSalesOfInvestmentSecurities Revenue Credit Gain on sale of investment securities 投資有価証券売却益 Revenue: Gain on sale of investment securities (投資有価証券売却益)-InsuranceGain Revenue Credit Gain on insurance claims 保険差益 Revenue: Gain on insurance claims (保険差益)-GainOnBargainPurchase Revenue Credit Gain on bargain purchase 負ののれん発生益 Revenue: Gain on bargain purchase (負ののれん発生益)-ReversalOfAllowanceForRepairs Revenue Credit Reversal of provision for repairs 修繕引当金戻入 Revenue: Reversal of provision for repairs (修繕引当金戻入)-ReversalOfAllowanceForProductWarranties Revenue Credit Reversal of provision for warranties 商品保証引当金戻入 Revenue: Reversal of provision for warranties (商品保証引当金戻入)-GainOnDonationOfFixedAssets Revenue Credit Gain on receipt of donated non-current assets 固定資産受贈益 Revenue: Gain on receipt of donated non-current assets (固定資産受贈益)-GainOnNationalSubsidies Revenue Credit Gain on receipt of national subsidies 国庫補助金受贈益 Revenue: Gain on receipt of national subsidies (国庫補助金受贈益)-GainOnConstructionGrants Revenue Credit Gain on contribution received for construction 工事負担金受贈益 Revenue: Gain on contribution received for construction (工事負担金受贈益)-LandRentReceived Revenue Credit Rental income from land 受取地代 Revenue: Rental income from land (受取地代)-SalesRebates Revenue Debit Sales rebates 売上割戻 Revenue: Sales rebates (売上割戻)-CostOfServices Cost Debit Cost of services 役務原価 Cost: Cost of services (役務原価)-OperatingExpenses Cost Debit Operating expenses 営業費用 Cost: Operating expenses (営業費用)-InventoryShrinkageLoss Cost Debit Inventory shrinkage loss 棚卸減耗損 Cost: Inventory shrinkage loss (棚卸減耗損)-LossOnValuationOfMerchandise Cost Debit Valuation loss on goods 商品評価損 Cost: Valuation loss on goods (商品評価損)-Bonuses Cost Debit Bonuses 賞与 Cost: Bonuses (賞与)-RetirementBenefitExpenses Cost Debit Retirement benefit expenses 退職給付費用 Cost: Retirement benefit expenses (退職給付費用)-ProvisionForRepairs Cost Debit Provision for repairs 修繕引当金繰入 Cost: Provision for repairs (修繕引当金繰入)-ProvisionForBonuses Cost Debit Provision for bonuses 賞与引当金繰入 Cost: Provision for bonuses (賞与引当金繰入)-ProvisionForProductWarranties Cost Debit Provision for warranties 商品保証引当金繰入 Cost: Provision for warranties (商品保証引当金繰入)-ResearchAndDevelopmentExpenses Cost Debit Research and development expenses 研究開発費 Cost: Research and development expenses (研究開発費)-AmortizationOfGoodwill Cost Debit Amortization of goodwill のれん償却 Cost: Amortization of goodwill (のれん償却)-AmortizationOfSoftware Cost Debit Amortization of software ソフトウェア償却 Cost: Amortization of software (ソフトウェア償却)-AmortizationOfPatents Cost Debit Amortization of patent rights 特許権償却 Cost: Amortization of patent rights (特許権償却)-LeaseExpenses Cost Debit Lease expenses 支払リース料 Cost: Lease expenses (支払リース料)-IncorporationExpenses Cost Debit Organization expenses 創立費 Cost: Organization expenses (創立費)-StockIssuanceCosts Cost Debit Share issuance costs 株式交付費 Cost: Share issuance costs (株式交付費)-BusinessCommencementExpenses Cost Debit Business commencement expenses 開業費 Cost: Business commencement expenses (開業費)-DevelopmentExpenses Cost Debit Development expenses 開発費 Cost: Development expenses (開発費)-LossOnSalesOfElectronicallyRecordedReceivables Cost Debit Loss on sale of electronically recorded monetary claims 電子記録債権売却損 Cost: Loss on sale of electronically recorded monetary claims (電子記録債権売却損)-LossOnSalesOfReceivables Cost Debit Loss on sale of receivables 債権売却損 Cost: Loss on sale of receivables (債権売却損)-LossOnSalesOfSecurities Cost Debit Loss on sale of securities 有価証券売却損 Cost: Loss on sale of securities (有価証券売却損)-LossOnValuationOfSecurities Cost Debit Loss on valuation of securities 有価証券評価損 Cost: Loss on valuation of securities (有価証券評価損)-LossOnSalesOfInvestmentSecurities Cost Debit Loss on sale of investment securities 投資有価証券売却損 Cost: Loss on sale of investment securities (投資有価証券売却損)-LossOnFire Cost Debit Loss on fire 火災損失 Cost: Loss on fire (火災損失)-LossOnRetirementOfFixedAssets Cost Debit Loss on retirement of non-current assets 固定資産除却損 Cost: Loss on retirement of non-current assets (固定資産除却損)-LossOnReductionOfFixedAssets Cost Debit Loss on tax purpose reduction entry of non-current assets 固定資産圧縮損 Cost: Loss on tax purpose reduction entry of non-current assets (固定資産圧縮損)-AdditionalIncomeTaxesForPriorPeriods Cost Debit Additional income taxes for prior periods 追徴法人税等 Cost: Additional income taxes for prior periods (追徴法人税等)-RefundOfIncomeTaxes Cost Credit Refund of income taxes 還付法人税等 Cost: Refund of income taxes (還付法人税等)-PurchaseRebates Cost Credit Purchase rebates 仕入割戻 Cost: Purchase rebates (仕入割戻)-WelfareExpenses Cost Debit Welfare expenses 福利厚生費 Cost: Welfare expenses (福利厚生費)-MaintenanceExpenses Cost Debit Maintenance expenses 保守費 Cost: Maintenance expenses (保守費)-StatutoryWelfareExpenses Cost Debit Legal welfare expenses 法定福利費 Cost: Legal welfare expenses (法定福利費)-LandRentPaid Cost Debit Rent expenses on land 支払地代 Cost: Rent expenses on land (支払地代)-InsuranceExpense Cost Debit Insurance expenses 保険料 Cost: Insurance expenses (保険料)-RepairsExpense Cost Debit Repair expenses 修繕費 Cost: Repair expenses (修繕費)-StorageExpenses Cost Debit Storage costs 保管費 Cost: Storage costs (保管費)-MembershipFees Cost Debit Membership fees 諸会費 Cost: Membership fees (諸会費)-IncomeSummary Assets Debit Income summary 損益 Bookkeeping device: Income summary (損益). Assets/NoClose is a technical debit-side placeholder; it is not a balance-sheet asset and is not automatically closed.-SuspenseAccount Assets Debit Suspense account 未決算 Assets: Suspense account (未決算)-ForeignExchangeGains Revenue Credit Foreign exchange gains 為替差益 Revenue: Foreign exchange gains (為替差益)-ForeignExchangeLosses Cost Debit Foreign exchange losses 為替差損 Cost: Foreign exchange losses (為替差損)-ContraAccountForGuaranteeObligations Assets Debit Contra account for guarantee obligations 保証債務見返 Assets: Contra account for guarantee obligations (保証債務見返)-GuaranteeObligations Liability Credit Guarantee obligations 保証債務 Liability: Guarantee obligations (保証債務)-IncomeTaxesAdjustment Cost Debit Income taxes - deferred 法人税等調整額 Cost: Income taxes - deferred (法人税等調整額)-BranchCurrentAccount Assets Debit Branch current account 支店 Assets: Branch current account (支店)-HeadOfficeCurrentAccount Liability Credit Head office current account 本店 Liability: Head office current account (本店), the credit-balance reciprocal account in branch books.-NetIncomeAttributableToNCI Cost Debit Profit attributable to non-controlling interests 非支配株主に帰属する当期純利益 Cost: Profit attributable to non-controlling interests (非支配株主に帰属する当期純利益). NoClose: consolidation procedures transfer it separately to non-controlling interests.-NetLossAttributableToNCI Revenue Credit Loss attributable to non-controlling interests 非支配株主に帰属する当期純損失 Revenue: Loss attributable to non-controlling interests (非支配株主に帰属する当期純損失). NoClose: consolidation procedures transfer it separately to non-controlling interests.-TradingSecurities Assets Debit Trading securities 売買目的有価証券 Assets: Trading securities (売買目的有価証券)-HeldToMaturityBonds Assets Debit Held-to-maturity bonds 満期保有目的債券 Assets: Held-to-maturity bonds (満期保有目的債券)-SubsidiaryStocks Assets Debit Shares of subsidiaries 子会社株式 Assets: Shares of subsidiaries (子会社株式)-AffiliateStocks Assets Debit Shares of associates 関連会社株式 Assets: Shares of associates (関連会社株式)-AvailableForSaleSecurities Assets Debit Available-for-sale securities その他有価証券 Assets: Available-for-sale securities (その他有価証券)
− test/fixtures/pre-account-semantics-050/consumer-inventory.txt
@@ -1,41 +0,0 @@-examples/audit-eval/gen/DeriveEA.hs-examples/audit-eval/gen/accounts.py-examples/audit-eval/harness/EmitCanonical.hs-examples/audit-eval/harness/LoadChecked.hs-examples/audit-eval/oracle/Oracle.hs-examples/audit-eval/runner/build.py-examples/basic/elementaryBookkeepingEx2.hs-examples/basic/elementaryBookkeepingEx3.hs-examples/basic/elementaryBookkeepingEx4.hs-examples/basic/elementaryBookkeepingEx9.hs-examples/basic/simulateEx1.hs-examples/basic/simulateEx2.hs-examples/basic/simulateEx2Fast.hs-examples/benchmark/Bench.hs-examples/deterministic/ripple/RippleEffect.hs-examples/deterministic/ripple/ripple.hs-examples/deterministic/ripple/rippleWithStock.hs-examples/deterministic/ripple/rippleWithStockMultiSeeds.hs-examples/market/MarketModel.hs-examples/optimization/CGE/CGE.hs-examples/optimization/CGE/stdcge.hs-examples/optimization/cge-lite/Model.hs-src/ExchangeAlgebra/Algebra.hs-src/ExchangeAlgebra/Algebra/Base.hs-src/ExchangeAlgebra/Algebra/Base/Account/Registry.hs-src/ExchangeAlgebra/Algebra/Base/Element.hs-src/ExchangeAlgebra/Algebra/Transfer.hs-src/ExchangeAlgebra/Assist.hs-src/ExchangeAlgebra/Bookkeeping.hs-src/ExchangeAlgebra/Convert.hs-src/ExchangeAlgebra/Convert/Checked.hs-src/ExchangeAlgebra/Journal.hs-src/ExchangeAlgebra/Journal/Transfer.hs-src/ExchangeAlgebra/Simulate/Lite.hs-src/ExchangeAlgebra/Write.hs-test/Spec.hs-tools/DumpAccountSemanticsGolden.hs-tools/DumpGolden.hs-tools/DumpPostVocabGolden.hs-tools/DumpVocabGolden.hs-tools/ProbeFinalStock.hs
− test/fixtures/pre-account-semantics-050/presentation.tsv
@@ -1,233 +0,0 @@-# pre-account-semantics-050 legacy presentation probe (title, bsRows of 1@Not, plRows of 1@Not); schema 1; commit 0d8e2791429145f2a48c79adbe62563328ee5c0b-Cash [["Asset","","Liability",""],["Cash","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Deposits [["Asset","","Liability",""],["Deposits","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CurrentDeposits [["Asset","","Liability",""],["CurrentDeposits","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Securities [["Asset","","Liability",""],["Securities","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-InvestmentSecurities [["Asset","","Liability",""],["InvestmentSecurities","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-InvestmentInAssociate [["Asset","","Liability",""],["InvestmentInAssociate","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermNationalBonds [["Asset","","Liability",""],["LongTermNationalBonds","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ShortTermNationalBonds [["Asset","","Liability",""],["ShortTermNationalBonds","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Products [["Asset","","Liability",""],["Products","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Machinery [["Asset","","Liability",""],["Machinery","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Building [["Asset","","Liability",""],["Building","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Vehicle [["Asset","","Liability",""],["Vehicle","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-StockInvestment [["Asset","","Liability",""],["StockInvestment","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-EquipmentInvestment [["Asset","","Liability",""],["EquipmentInvestment","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermLoansReceivable [["Asset","","Liability",""],["LongTermLoansReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccountsReceivable [["Asset","","Liability",""],["AccountsReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ShortTermLoansReceivable [["Asset","","Liability",""],["ShortTermLoansReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ReserveDepositReceivable [["Asset","","Liability",""],["ReserveDepositReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Gold [["Asset","","Liability",""],["Gold","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-GovernmentService [["Asset","","Liability",""],["GovernmentService","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CapitalStock [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","CapitalStock","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-RetainedEarnings [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermLoansPayable [["Asset","","Liability",""],["Total","0.0","LongTermLoansPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ShortTermLoansPayable [["Asset","","Liability",""],["Total","0.0","ShortTermLoansPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LoansPayable [["Asset","","Liability",""],["Total","0.0","LoansPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ReserveForDepreciation [["Asset","","Liability",""],["Total","0.0","ReserveForDepreciation","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DepositPayable [["Asset","","Liability",""],["Total","0.0","DepositPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermNationalBondsPayable [["Asset","","Liability",""],["Total","0.0","LongTermNationalBondsPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ShortTermNationalBondsPayable [["Asset","","Liability",""],["Total","0.0","ShortTermNationalBondsPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ReserveDepositPayable [["Asset","","Liability",""],["Total","0.0","ReserveDepositPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CentralBankNotePayable [["Asset","","Liability",""],["Total","0.0","CentralBankNotePayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Depreciation [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Depreciation","1.0","",""],["Total","0.0","Total","1.0"]]-AmortizationExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AmortizationExpense","1.0","",""],["Total","0.0","Total","1.0"]]-SalesCost [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["SalesCost","1.0","",""],["Total","0.0","Total","1.0"]]-BusinessTrip [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["BusinessTrip","1.0","",""],["Total","0.0","Total","1.0"]]-Commutation [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Commutation","1.0","",""],["Total","0.0","Total","1.0"]]-UtilitiesExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["UtilitiesExpense","1.0","",""],["Total","0.0","Total","1.0"]]-RentExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["RentExpense","1.0","",""],["Total","0.0","Total","1.0"]]-AdvertisingExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AdvertisingExpense","1.0","",""],["Total","0.0","Total","1.0"]]-DeliveryExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["DeliveryExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-SuppliesExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["SuppliesExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-MiscellaneousExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["MiscellaneousExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-WageExpenditure [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["WageExpenditure","1.0","",""],["Total","0.0","Total","1.0"]]-InterestExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["InterestExpense","1.0","",""],["Total","0.0","Total","1.0"]]-TaxesExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["TaxesExpense","1.0","",""],["Total","0.0","Total","1.0"]]-ConsumptionExpenditure [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ConsumptionExpenditure","1.0","",""],["Total","0.0","Total","1.0"]]-SubsidyExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["SubsidyExpense","1.0","",""],["Total","0.0","Total","1.0"]]-CentralBankPaymentExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["CentralBankPaymentExpense","1.0","",""],["Total","0.0","Total","1.0"]]-Purchases [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Purchases","1.0","",""],["Total","0.0","Total","1.0"]]-NetIncome [["Asset","","Liability",""],["NetIncome","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["NetIncome","1.0","",""],["Total","0.0","Total","1.0"]]-ValueAdded [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ValueAdded","1.0"],["Total","1.0","Total","0.0"]]-SubsidyIncome [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","SubsidyIncome","1.0"],["Total","1.0","Total","0.0"]]-NationalBondInterestEarned [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","NationalBondInterestEarned","1.0"],["Total","1.0","Total","0.0"]]-DepositInterestEarned [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","DepositInterestEarned","1.0"],["Total","1.0","Total","0.0"]]-GrossProfit [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GrossProfit","1.0"],["Total","1.0","Total","0.0"]]-OrdinaryProfit [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","OrdinaryProfit","1.0"],["Total","1.0","Total","0.0"]]-InterestEarned [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","InterestEarned","1.0"],["Total","1.0","Total","0.0"]]-ReceiptFee [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ReceiptFee","1.0"],["Total","1.0","Total","0.0"]]-RentalIncome [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","RentalIncome","1.0"],["Total","1.0","Total","0.0"]]-WageEarned [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","WageEarned","1.0"],["Total","1.0","Total","0.0"]]-TaxesRevenue [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","TaxesRevenue","1.0"],["Total","1.0","Total","0.0"]]-CentralBankPaymentIncome [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","CentralBankPaymentIncome","1.0"],["Total","1.0","Total","0.0"]]-Sales [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","Sales","1.0"],["Total","1.0","Total","0.0"]]-EquityInEarningsOfInvestee [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","EquityInEarningsOfInvestee","1.0"],["Total","1.0","Total","0.0"]]-NetLoss [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","NetLoss","1.0"],["Total","1.0","Total","0.0"]]-PettyCash [["Asset","","Liability",""],["PettyCash","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NotesReceivable [["Asset","","Liability",""],["NotesReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ElectronicallyRecordedReceivable [["Asset","","Liability",""],["ElectronicallyRecordedReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CreditCardReceivable [["Asset","","Liability",""],["CreditCardReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NotesLoansReceivable [["Asset","","Liability",""],["NotesLoansReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-MerchandiseInventory [["Asset","","Liability",""],["MerchandiseInventory","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AdvancesPaid [["Asset","","Liability",""],["AdvancesPaid","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-PrepaidExpenses [["Asset","","Liability",""],["PrepaidExpenses","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccruedRevenue [["Asset","","Liability",""],["AccruedRevenue","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-OtherReceivables [["Asset","","Liability",""],["OtherReceivables","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-PaymentsOnBehalf [["Asset","","Liability",""],["PaymentsOnBehalf","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SuspensePayments [["Asset","","Liability",""],["SuspensePayments","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ConsumptionTaxPaid [["Asset","","Liability",""],["ConsumptionTaxPaid","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-PrepaidCorporateIncomeTaxes [["Asset","","Liability",""],["PrepaidCorporateIncomeTaxes","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Land [["Asset","","Liability",""],["Land","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Fixtures [["Asset","","Liability",""],["Fixtures","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Patent [["Asset","","Liability",""],["Patent","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Trademark [["Asset","","Liability",""],["Trademark","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Software [["Asset","","Liability",""],["Software","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CashOverShort [["Asset","","Liability",""],["CashOverShort","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccountsPayable [["Asset","","Liability",""],["Total","0.0","AccountsPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NotesPayable [["Asset","","Liability",""],["Total","0.0","NotesPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ElectronicallyRecordedObligations [["Asset","","Liability",""],["Total","0.0","ElectronicallyRecordedObligations","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NotesLoansPayable [["Asset","","Liability",""],["Total","0.0","NotesLoansPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-BankOverdraft [["Asset","","Liability",""],["Total","0.0","BankOverdraft","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AdvancesReceived [["Asset","","Liability",""],["Total","0.0","AdvancesReceived","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-UnearnedRevenue [["Asset","","Liability",""],["Total","0.0","UnearnedRevenue","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccruedExpenses [["Asset","","Liability",""],["Total","0.0","AccruedExpenses","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-OtherPayables [["Asset","","Liability",""],["Total","0.0","OtherPayables","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DepositsReceived [["Asset","","Liability",""],["Total","0.0","DepositsReceived","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SuspenseReceipts [["Asset","","Liability",""],["Total","0.0","SuspenseReceipts","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ConsumptionTaxReceived [["Asset","","Liability",""],["Total","0.0","ConsumptionTaxReceived","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccruedConsumptionTax [["Asset","","Liability",""],["Total","0.0","AccruedConsumptionTax","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccruedCorporateIncomeTaxes [["Asset","","Liability",""],["Total","0.0","AccruedCorporateIncomeTaxes","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-UnpaidDividends [["Asset","","Liability",""],["Total","0.0","UnpaidDividends","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AllowanceForDoubtfulAccounts [["Asset","","Liability",""],["Total","0.0","AllowanceForDoubtfulAccounts","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AccumulatedDepreciation [["Asset","","Liability",""],["Total","0.0","AccumulatedDepreciation","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LegalRetainedEarnings [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","LegalRetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CumulativeTranslationAdjustment [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","CumulativeTranslationAdjustment","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ProvisionForDoubtfulAccounts [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ProvisionForDoubtfulAccounts","1.0","",""],["Total","0.0","Total","1.0"]]-BadDebtLoss [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["BadDebtLoss","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfFixedAssets [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfFixedAssets","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfNotesReceivable [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfNotesReceivable","1.0","",""],["Total","0.0","Total","1.0"]]-PaymentFees [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["PaymentFees","1.0","",""],["Total","0.0","Total","1.0"]]-MiscellaneousLoss [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["MiscellaneousLoss","1.0","",""],["Total","0.0","Total","1.0"]]-CorporateIncomeTaxes [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["CorporateIncomeTaxes","1.0","",""],["Total","0.0","Total","1.0"]]-CommunicationExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["CommunicationExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-GainOnSalesOfFixedAssets [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnSalesOfFixedAssets","1.0"],["Total","1.0","Total","0.0"]]-RecoveryOfBadDebts [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","RecoveryOfBadDebts","1.0"],["Total","1.0","Total","0.0"]]-MiscellaneousIncome [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","MiscellaneousIncome","1.0"],["Total","1.0","Total","0.0"]]-ReversalOfAllowanceForDoubtfulAccounts [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ReversalOfAllowanceForDoubtfulAccounts","1.0"],["Total","1.0","Total","0.0"]]-TimeDeposits [["Asset","","Liability",""],["TimeDeposits","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LoansReceivable [["Asset","","Liability",""],["LoansReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-GiftCertificatesReceived [["Asset","","Liability",""],["GiftCertificatesReceived","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SecurityDepositsPaid [["Asset","","Liability",""],["SecurityDepositsPaid","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SuppliesOnHand [["Asset","","Liability",""],["SuppliesOnHand","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ContractAssets [["Asset","","Liability",""],["ContractAssets","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-IncomeTaxesRefundReceivable [["Asset","","Liability",""],["IncomeTaxesRefundReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-WorkInProcess [["Asset","","Liability",""],["WorkInProcess","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DeferredTaxAssets [["Asset","","Liability",""],["DeferredTaxAssets","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LeasedAssets [["Asset","","Liability",""],["LeasedAssets","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ToolsAndInstruments [["Asset","","Liability",""],["ToolsAndInstruments","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ConstructionInProgress [["Asset","","Liability",""],["ConstructionInProgress","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Goodwill [["Asset","","Liability",""],["Goodwill","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SoftwareInProgress [["Asset","","Liability",""],["SoftwareInProgress","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermPrepaidExpenses [["Asset","","Liability",""],["LongTermPrepaidExpenses","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DishonoredNotesReceivable [["Asset","","Liability",""],["DishonoredNotesReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-PrepaidPensionCost [["Asset","","Liability",""],["PrepaidPensionCost","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NetDefinedBenefitAsset [["Asset","","Liability",""],["NetDefinedBenefitAsset","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DepositsInSpecialAccounts [["Asset","","Liability",""],["DepositsInSpecialAccounts","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-Structures [["Asset","","Liability",""],["Structures","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LeaseholdRights [["Asset","","Liability",""],["LeaseholdRights","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NonOperatingNotesReceivable [["Asset","","Liability",""],["NonOperatingNotesReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NonOperatingElectronicallyRecordedReceivable [["Asset","","Liability",""],["NonOperatingElectronicallyRecordedReceivable","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-RefundLiabilities [["Asset","","Liability",""],["Total","0.0","RefundLiabilities","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NonOperatingNotesPayable [["Asset","","Liability",""],["Total","0.0","NonOperatingNotesPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NonOperatingElectronicallyRecordedObligations [["Asset","","Liability",""],["Total","0.0","NonOperatingElectronicallyRecordedObligations","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-BonusesPayable [["Asset","","Liability",""],["Total","0.0","BonusesPayable","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AllowanceForRepairs [["Asset","","Liability",""],["Total","0.0","AllowanceForRepairs","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AllowanceForProductWarranties [["Asset","","Liability",""],["Total","0.0","AllowanceForProductWarranties","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AllowanceForBonuses [["Asset","","Liability",""],["Total","0.0","AllowanceForBonuses","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DeferredTaxLiabilities [["Asset","","Liability",""],["Total","0.0","DeferredTaxLiabilities","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LeaseObligations [["Asset","","Liability",""],["Total","0.0","LeaseObligations","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-GuaranteeDepositsReceived [["Asset","","Liability",""],["Total","0.0","GuaranteeDepositsReceived","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AllowanceForRetirementBenefits [["Asset","","Liability",""],["Total","0.0","AllowanceForRetirementBenefits","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LongTermOtherPayables [["Asset","","Liability",""],["Total","0.0","LongTermOtherPayables","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NetDefinedBenefitLiability [["Asset","","Liability",""],["Total","0.0","NetDefinedBenefitLiability","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-StockSubscriptionDeposits [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","StockSubscriptionDeposits","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-LegalCapitalSurplus [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","LegalCapitalSurplus","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-OtherCapitalSurplus [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","OtherCapitalSurplus","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-DividendEqualizationReserve [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","DividendEqualizationReserve","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-RepairFundReserve [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RepairFundReserve","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ConstructionFundReserve [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","ConstructionFundReserve","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-GeneralReserve [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","GeneralReserve","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ValuationDifferenceOnOtherSecurities [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","ValuationDifferenceOnOtherSecurities","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NonControllingInterests [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","NonControllingInterests","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CapitalSurplus [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","CapitalSurplus","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-EarnedSurplus [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","EarnedSurplus","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ServiceRevenue [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ServiceRevenue","1.0"],["Total","1.0","Total","0.0"]]-OperatingRevenue [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","OperatingRevenue","1.0"],["Total","1.0","Total","0.0"]]-GainOnSalesOfSecurities [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnSalesOfSecurities","1.0"],["Total","1.0","Total","0.0"]]-GainOnValuationOfSecurities [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnValuationOfSecurities","1.0"],["Total","1.0","Total","0.0"]]-DividendsReceived [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","DividendsReceived","1.0"],["Total","1.0","Total","0.0"]]-InterestOnSecurities [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","InterestOnSecurities","1.0"],["Total","1.0","Total","0.0"]]-GainOnSalesOfInvestmentSecurities [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnSalesOfInvestmentSecurities","1.0"],["Total","1.0","Total","0.0"]]-InsuranceGain [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","InsuranceGain","1.0"],["Total","1.0","Total","0.0"]]-GainOnBargainPurchase [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnBargainPurchase","1.0"],["Total","1.0","Total","0.0"]]-ReversalOfAllowanceForRepairs [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ReversalOfAllowanceForRepairs","1.0"],["Total","1.0","Total","0.0"]]-ReversalOfAllowanceForProductWarranties [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ReversalOfAllowanceForProductWarranties","1.0"],["Total","1.0","Total","0.0"]]-GainOnDonationOfFixedAssets [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnDonationOfFixedAssets","1.0"],["Total","1.0","Total","0.0"]]-GainOnNationalSubsidies [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnNationalSubsidies","1.0"],["Total","1.0","Total","0.0"]]-GainOnConstructionGrants [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","GainOnConstructionGrants","1.0"],["Total","1.0","Total","0.0"]]-LandRentReceived [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","LandRentReceived","1.0"],["Total","1.0","Total","0.0"]]-SalesRebates [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-CostOfServices [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["CostOfServices","1.0","",""],["Total","0.0","Total","1.0"]]-OperatingExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["OperatingExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-InventoryShrinkageLoss [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["InventoryShrinkageLoss","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnValuationOfMerchandise [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnValuationOfMerchandise","1.0","",""],["Total","0.0","Total","1.0"]]-Bonuses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Bonuses","1.0","",""],["Total","0.0","Total","1.0"]]-RetirementBenefitExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["RetirementBenefitExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-ProvisionForRepairs [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ProvisionForRepairs","1.0","",""],["Total","0.0","Total","1.0"]]-ProvisionForBonuses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ProvisionForBonuses","1.0","",""],["Total","0.0","Total","1.0"]]-ProvisionForProductWarranties [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ProvisionForProductWarranties","1.0","",""],["Total","0.0","Total","1.0"]]-ResearchAndDevelopmentExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ResearchAndDevelopmentExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-AmortizationOfGoodwill [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AmortizationOfGoodwill","1.0","",""],["Total","0.0","Total","1.0"]]-AmortizationOfSoftware [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AmortizationOfSoftware","1.0","",""],["Total","0.0","Total","1.0"]]-AmortizationOfPatents [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AmortizationOfPatents","1.0","",""],["Total","0.0","Total","1.0"]]-LeaseExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LeaseExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-IncorporationExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["IncorporationExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-StockIssuanceCosts [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["StockIssuanceCosts","1.0","",""],["Total","0.0","Total","1.0"]]-BusinessCommencementExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["BusinessCommencementExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-DevelopmentExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["DevelopmentExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfElectronicallyRecordedReceivables [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfElectronicallyRecordedReceivables","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfReceivables [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfReceivables","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfSecurities [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfSecurities","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnValuationOfSecurities [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnValuationOfSecurities","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnSalesOfInvestmentSecurities [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnSalesOfInvestmentSecurities","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnFire [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnFire","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnRetirementOfFixedAssets [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnRetirementOfFixedAssets","1.0","",""],["Total","0.0","Total","1.0"]]-LossOnReductionOfFixedAssets [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LossOnReductionOfFixedAssets","1.0","",""],["Total","0.0","Total","1.0"]]-AdditionalIncomeTaxesForPriorPeriods [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["AdditionalIncomeTaxesForPriorPeriods","1.0","",""],["Total","0.0","Total","1.0"]]-RefundOfIncomeTaxes [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-PurchaseRebates [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-WelfareExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["WelfareExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-MaintenanceExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["MaintenanceExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-StatutoryWelfareExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["StatutoryWelfareExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-LandRentPaid [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["LandRentPaid","1.0","",""],["Total","0.0","Total","1.0"]]-InsuranceExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["InsuranceExpense","1.0","",""],["Total","0.0","Total","1.0"]]-RepairsExpense [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["RepairsExpense","1.0","",""],["Total","0.0","Total","1.0"]]-StorageExpenses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["StorageExpenses","1.0","",""],["Total","0.0","Total","1.0"]]-MembershipFees [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["MembershipFees","1.0","",""],["Total","0.0","Total","1.0"]]-IncomeSummary [["Asset","","Liability",""],["IncomeSummary","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SuspenseAccount [["Asset","","Liability",""],["SuspenseAccount","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-ForeignExchangeGains [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","RetainedEarnings","1.0"],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","ForeignExchangeGains","1.0"],["Total","1.0","Total","0.0"]]-ForeignExchangeLosses [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["ForeignExchangeLosses","1.0","",""],["Total","0.0","Total","1.0"]]-ContraAccountForGuaranteeObligations [["Asset","","Liability",""],["ContraAccountForGuaranteeObligations","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-GuaranteeObligations [["Asset","","Liability",""],["Total","0.0","GuaranteeObligations","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-IncomeTaxesAdjustment [["Asset","","Liability",""],["RetainedEarnings","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["IncomeTaxesAdjustment","1.0","",""],["Total","0.0","Total","1.0"]]-BranchCurrentAccount [["Asset","","Liability",""],["BranchCurrentAccount","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-HeadOfficeCurrentAccount [["Asset","","Liability",""],["Total","0.0","HeadOfficeCurrentAccount","1.0"],["","","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-NetIncomeAttributableToNCI [["Asset","","Liability",""],["NetIncomeAttributableToNCI","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["NetIncomeAttributableToNCI","1.0","",""],["Total","0.0","Total","1.0"]]-NetLossAttributableToNCI [["Asset","","Liability",""],["Total","0.0","Equity",""],["","","Total","1.0"]] [["Cost","","Revenue",""],["","","NetLossAttributableToNCI","1.0"],["Total","1.0","Total","0.0"]]-TradingSecurities [["Asset","","Liability",""],["TradingSecurities","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-HeldToMaturityBonds [["Asset","","Liability",""],["HeldToMaturityBonds","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-SubsidiaryStocks [["Asset","","Liability",""],["SubsidiaryStocks","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AffiliateStocks [["Asset","","Liability",""],["AffiliateStocks","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]-AvailableForSaleSecurities [["Asset","","Liability",""],["AvailableForSaleSecurities","1.0","Equity",""],["Total","1.0","Total","0.0"]] [["Cost","","Revenue",""],["Total","0.0","Total","0.0"]]
− test/fixtures/pre-account-semantics-050/projection-membership.tsv
@@ -1,233 +0,0 @@-# pre-account-semantics-050 projection flags for Not then Hat (currentAssets, fixedAssets, deferredAssets, currentLiability, fixedLiability, capitalStock, contraAssets, contra); schema 1; commit 0d8e2791429145f2a48c79adbe62563328ee5c0b-Cash 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Deposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CurrentDeposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Securities 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InvestmentSecurities 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InvestmentInAssociate 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LongTermNationalBonds 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ShortTermNationalBonds 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Products 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Machinery 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Building 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Vehicle 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-StockInvestment 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0-EquipmentInvestment 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LongTermLoansReceivable 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AccountsReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ShortTermLoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ReserveDepositReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Gold 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GovernmentService 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CapitalStock 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-RetainedEarnings 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-LongTermLoansPayable 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-ShortTermLoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-LoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-ReserveForDepreciation 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-DepositPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-LongTermNationalBondsPayable 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-ShortTermNationalBondsPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-ReserveDepositPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-CentralBankNotePayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-Depreciation 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AmortizationExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SalesCost 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-BusinessTrip 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Commutation 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-UtilitiesExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RentExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AdvertisingExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DeliveryExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SuppliesExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MiscellaneousExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-WageExpenditure 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InterestExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-TaxesExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ConsumptionExpenditure 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SubsidyExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CentralBankPaymentExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Purchases 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NetIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ValueAdded 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SubsidyIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NationalBondInterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DepositInterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GrossProfit 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-OrdinaryProfit 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InterestEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ReceiptFee 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RentalIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-WageEarned 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-TaxesRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CentralBankPaymentIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Sales 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-EquityInEarningsOfInvestee 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NetLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PettyCash 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ElectronicallyRecordedReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CreditCardReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NotesLoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MerchandiseInventory 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AdvancesPaid 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PrepaidExpenses 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AccruedRevenue 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-OtherReceivables 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PaymentsOnBehalf 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SuspensePayments 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ConsumptionTaxPaid 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PrepaidCorporateIncomeTaxes 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Land 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Fixtures 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Patent 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Trademark 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Software 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CashOverShort 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0-AccountsPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-NotesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-ElectronicallyRecordedObligations 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-NotesLoansPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-BankOverdraft 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AdvancesReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-UnearnedRevenue 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AccruedExpenses 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-OtherPayables 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-DepositsReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-SuspenseReceipts 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-ConsumptionTaxReceived 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AccruedConsumptionTax 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AccruedCorporateIncomeTaxes 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-UnpaidDividends 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AllowanceForDoubtfulAccounts 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1-AccumulatedDepreciation 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1-LegalRetainedEarnings 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-CumulativeTranslationAdjustment 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-ProvisionForDoubtfulAccounts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-BadDebtLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfNotesReceivable 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PaymentFees 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MiscellaneousLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CorporateIncomeTaxes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-CommunicationExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnSalesOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RecoveryOfBadDebts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MiscellaneousIncome 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ReversalOfAllowanceForDoubtfulAccounts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-TimeDeposits 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LoansReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GiftCertificatesReceived 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SecurityDepositsPaid 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SuppliesOnHand 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ContractAssets 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-IncomeTaxesRefundReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-WorkInProcess 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DeferredTaxAssets 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LeasedAssets 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ToolsAndInstruments 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ConstructionInProgress 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Goodwill 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SoftwareInProgress 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LongTermPrepaidExpenses 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DishonoredNotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-PrepaidPensionCost 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NetDefinedBenefitAsset 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DepositsInSpecialAccounts 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Structures 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LeaseholdRights 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NonOperatingNotesReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NonOperatingElectronicallyRecordedReceivable 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RefundLiabilities 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-NonOperatingNotesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-NonOperatingElectronicallyRecordedObligations 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-BonusesPayable 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AllowanceForRepairs 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AllowanceForProductWarranties 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-AllowanceForBonuses 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0-DeferredTaxLiabilities 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-LeaseObligations 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-GuaranteeDepositsReceived 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-AllowanceForRetirementBenefits 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-LongTermOtherPayables 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-NetDefinedBenefitLiability 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0-StockSubscriptionDeposits 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-LegalCapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-OtherCapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-DividendEqualizationReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-RepairFundReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-ConstructionFundReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-GeneralReserve 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-ValuationDifferenceOnOtherSecurities 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-NonControllingInterests 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-CapitalSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-EarnedSurplus 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0-ServiceRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-OperatingRevenue 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnSalesOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnValuationOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DividendsReceived 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InterestOnSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnSalesOfInvestmentSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InsuranceGain 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnBargainPurchase 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ReversalOfAllowanceForRepairs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ReversalOfAllowanceForProductWarranties 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnDonationOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnNationalSubsidies 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-GainOnConstructionGrants 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LandRentReceived 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SalesRebates 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1-CostOfServices 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-OperatingExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InventoryShrinkageLoss 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnValuationOfMerchandise 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-Bonuses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RetirementBenefitExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ProvisionForRepairs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ProvisionForBonuses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ProvisionForProductWarranties 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ResearchAndDevelopmentExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AmortizationOfGoodwill 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AmortizationOfSoftware 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AmortizationOfPatents 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LeaseExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-IncorporationExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-StockIssuanceCosts 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-BusinessCommencementExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-DevelopmentExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfElectronicallyRecordedReceivables 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfReceivables 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnValuationOfSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnSalesOfInvestmentSecurities 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnFire 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnRetirementOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LossOnReductionOfFixedAssets 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AdditionalIncomeTaxesForPriorPeriods 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RefundOfIncomeTaxes 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1-PurchaseRebates 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1-WelfareExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MaintenanceExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-StatutoryWelfareExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-LandRentPaid 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-InsuranceExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-RepairsExpense 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-StorageExpenses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-MembershipFees 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-IncomeSummary 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0-SuspenseAccount 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ForeignExchangeGains 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ForeignExchangeLosses 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-ContraAccountForGuaranteeObligations 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0-GuaranteeObligations 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-IncomeTaxesAdjustment 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-BranchCurrentAccount 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0-HeadOfficeCurrentAccount 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NetIncomeAttributableToNCI 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-NetLossAttributableToNCI 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-TradingSecurities 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-HeldToMaturityBonds 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-SubsidiaryStocks 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AffiliateStocks 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0-AvailableForSaleSecurities 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
− test/fixtures/pre-account-semantics-050/semantics.tsv
@@ -1,233 +0,0 @@-# pre-account-semantics-050 semantics (title, enum, binaryHex, division, closing, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe); schema 1; commit 0d8e2791429145f2a48c79adbe62563328ee5c0b-Cash 0 0000 Assets CloseByDivision False Debit Credit PS Current Nothing-Deposits 1 0001 Assets CloseByDivision False Debit Credit PS Current Nothing-CurrentDeposits 2 0002 Assets CloseByDivision False Debit Credit PS Current Nothing-Securities 3 0003 Assets CloseByDivision False Debit Credit PS Current Nothing-InvestmentSecurities 4 0004 Assets CloseByDivision False Debit Credit PS Fixed Nothing-InvestmentInAssociate 5 0005 Assets CloseByDivision False Debit Credit PS Fixed Nothing-LongTermNationalBonds 6 0006 Assets CloseByDivision False Debit Credit PS Fixed Nothing-ShortTermNationalBonds 7 0007 Assets CloseByDivision False Debit Credit PS Current Nothing-Products 8 0008 Assets CloseByDivision False Debit Credit PS Current Nothing-Machinery 9 0009 Assets CloseByDivision False Debit Credit PS Fixed Nothing-Building 10 000a Assets CloseByDivision False Debit Credit PS Fixed Nothing-Vehicle 11 000b Assets CloseByDivision False Debit Credit PS Fixed Nothing-StockInvestment 12 000c Assets CloseByDivision False Debit Credit PS Other Nothing-EquipmentInvestment 13 000d Assets CloseByDivision False Debit Credit PS Fixed Nothing-LongTermLoansReceivable 14 000e Assets CloseByDivision False Debit Credit PS Fixed Nothing-AccountsReceivable 15 000f Assets CloseByDivision False Debit Credit PS Current Nothing-ShortTermLoansReceivable 16 0010 Assets CloseByDivision False Debit Credit PS Current Nothing-ReserveDepositReceivable 17 0011 Assets CloseByDivision False Debit Credit PS Current Nothing-Gold 18 0012 Assets CloseByDivision False Debit Credit PS Fixed Nothing-GovernmentService 19 0013 Assets CloseByDivision False Debit Credit PS Current Nothing-CapitalStock 20 0014 Equity CloseByDivision False Credit Debit MS Other Nothing-RetainedEarnings 21 0015 Equity CloseByDivision False Credit Debit MS Other SELF-LongTermLoansPayable 22 0016 Liability CloseByDivision False Credit Debit MS Fixed Nothing-ShortTermLoansPayable 23 0017 Liability CloseByDivision False Credit Debit MS Current Nothing-LoansPayable 24 0018 Liability CloseByDivision False Credit Debit MS Current Nothing-ReserveForDepreciation 25 0019 Liability CloseByDivision False Credit Debit MS Current Nothing-DepositPayable 26 001a Liability CloseByDivision False Credit Debit MS Current Nothing-LongTermNationalBondsPayable 27 001b Liability CloseByDivision False Credit Debit MS Fixed Nothing-ShortTermNationalBondsPayable 28 001c Liability CloseByDivision False Credit Debit MS Current Nothing-ReserveDepositPayable 29 001d Liability CloseByDivision False Credit Debit MS Current Nothing-CentralBankNotePayable 30 001e Liability CloseByDivision False Credit Debit MS Current Nothing-Depreciation 31 001f Cost CloseByDivision False Debit Credit OUT Other Flip-AmortizationExpense 32 0020 Cost CloseByDivision False Debit Credit OUT Other Flip-SalesCost 33 0021 Cost CloseByDivision False Debit Credit OUT Other Flip-BusinessTrip 34 0022 Cost CloseByDivision False Debit Credit OUT Other Flip-Commutation 35 0023 Cost CloseByDivision False Debit Credit OUT Other Flip-UtilitiesExpense 36 0024 Cost CloseByDivision False Debit Credit OUT Other Flip-RentExpense 37 0025 Cost CloseByDivision False Debit Credit OUT Other Flip-AdvertisingExpense 38 0026 Cost CloseByDivision False Debit Credit OUT Other Flip-DeliveryExpenses 39 0027 Cost CloseByDivision False Debit Credit OUT Other Flip-SuppliesExpenses 40 0028 Cost CloseByDivision False Debit Credit OUT Other Flip-MiscellaneousExpenses 41 0029 Cost CloseByDivision False Debit Credit OUT Other Flip-WageExpenditure 42 002a Cost CloseByDivision False Debit Credit OUT Other Flip-InterestExpense 43 002b Cost CloseByDivision False Debit Credit OUT Other Flip-TaxesExpense 44 002c Cost CloseByDivision False Debit Credit OUT Other Flip-ConsumptionExpenditure 45 002d Cost CloseByDivision False Debit Credit OUT Other Flip-SubsidyExpense 46 002e Cost CloseByDivision False Debit Credit OUT Other Flip-CentralBankPaymentExpense 47 002f Cost CloseByDivision False Debit Credit OUT Other Flip-Purchases 48 0030 Cost CloseByDivision False Debit Credit OUT Other Flip-NetIncome 49 0031 Cost NoClose False Debit Credit OUT Other Nothing-ValueAdded 50 0032 Revenue CloseByDivision False Credit Debit IN Other Keep-SubsidyIncome 51 0033 Revenue CloseByDivision False Credit Debit IN Other Keep-NationalBondInterestEarned 52 0034 Revenue CloseByDivision False Credit Debit IN Other Keep-DepositInterestEarned 53 0035 Revenue CloseByDivision False Credit Debit IN Other Keep-GrossProfit 54 0036 Revenue CloseByDivision False Credit Debit IN Other Keep-OrdinaryProfit 55 0037 Revenue CloseByDivision False Credit Debit IN Other Keep-InterestEarned 56 0038 Revenue CloseByDivision False Credit Debit IN Other Keep-ReceiptFee 57 0039 Revenue CloseByDivision False Credit Debit IN Other Keep-RentalIncome 58 003a Revenue CloseByDivision False Credit Debit IN Other Keep-WageEarned 59 003b Revenue CloseByDivision False Credit Debit IN Other Keep-TaxesRevenue 60 003c Revenue CloseByDivision False Credit Debit IN Other Keep-CentralBankPaymentIncome 61 003d Revenue CloseByDivision False Credit Debit IN Other Keep-Sales 62 003e Revenue CloseByDivision False Credit Debit IN Other Keep-EquityInEarningsOfInvestee 63 003f Revenue CloseByDivision False Credit Debit IN Other Keep-NetLoss 64 0040 Revenue NoClose False Credit Debit IN Other Nothing-PettyCash 65 0041 Assets CloseByDivision False Debit Credit PS Current Nothing-NotesReceivable 66 0042 Assets CloseByDivision False Debit Credit PS Current Nothing-ElectronicallyRecordedReceivable 67 0043 Assets CloseByDivision False Debit Credit PS Current Nothing-CreditCardReceivable 68 0044 Assets CloseByDivision False Debit Credit PS Current Nothing-NotesLoansReceivable 69 0045 Assets CloseByDivision False Debit Credit PS Current Nothing-MerchandiseInventory 70 0046 Assets CloseByDivision False Debit Credit PS Current Nothing-AdvancesPaid 71 0047 Assets CloseByDivision False Debit Credit PS Current Nothing-PrepaidExpenses 72 0048 Assets CloseByDivision False Debit Credit PS Current Nothing-AccruedRevenue 73 0049 Assets CloseByDivision False Debit Credit PS Current Nothing-OtherReceivables 74 004a Assets CloseByDivision False Debit Credit PS Current Nothing-PaymentsOnBehalf 75 004b Assets CloseByDivision False Debit Credit PS Current Nothing-SuspensePayments 76 004c Assets CloseByDivision False Debit Credit PS Current Nothing-ConsumptionTaxPaid 77 004d Assets CloseByDivision False Debit Credit PS Current Nothing-PrepaidCorporateIncomeTaxes 78 004e Assets CloseByDivision False Debit Credit PS Current Nothing-Land 79 004f Assets CloseByDivision False Debit Credit PS Fixed Nothing-Fixtures 80 0050 Assets CloseByDivision False Debit Credit PS Fixed Nothing-Patent 81 0051 Assets CloseByDivision False Debit Credit PS Fixed Nothing-Trademark 82 0052 Assets CloseByDivision False Debit Credit PS Fixed Nothing-Software 83 0053 Assets CloseByDivision False Debit Credit PS Fixed Nothing-CashOverShort 84 0054 Assets CloseByDivision False Debit Credit PS Other Nothing-AccountsPayable 85 0055 Liability CloseByDivision False Credit Debit MS Current Nothing-NotesPayable 86 0056 Liability CloseByDivision False Credit Debit MS Current Nothing-ElectronicallyRecordedObligations 87 0057 Liability CloseByDivision False Credit Debit MS Current Nothing-NotesLoansPayable 88 0058 Liability CloseByDivision False Credit Debit MS Current Nothing-BankOverdraft 89 0059 Liability CloseByDivision False Credit Debit MS Current Nothing-AdvancesReceived 90 005a Liability CloseByDivision False Credit Debit MS Current Nothing-UnearnedRevenue 91 005b Liability CloseByDivision False Credit Debit MS Current Nothing-AccruedExpenses 92 005c Liability CloseByDivision False Credit Debit MS Current Nothing-OtherPayables 93 005d Liability CloseByDivision False Credit Debit MS Current Nothing-DepositsReceived 94 005e Liability CloseByDivision False Credit Debit MS Current Nothing-SuspenseReceipts 95 005f Liability CloseByDivision False Credit Debit MS Current Nothing-ConsumptionTaxReceived 96 0060 Liability CloseByDivision False Credit Debit MS Current Nothing-AccruedConsumptionTax 97 0061 Liability CloseByDivision False Credit Debit MS Current Nothing-AccruedCorporateIncomeTaxes 98 0062 Liability CloseByDivision False Credit Debit MS Current Nothing-UnpaidDividends 99 0063 Liability CloseByDivision False Credit Debit MS Current Nothing-AllowanceForDoubtfulAccounts 100 0064 Assets CloseByDivision True Credit Debit MS Current Nothing-AccumulatedDepreciation 101 0065 Assets CloseByDivision True Credit Debit MS Fixed Nothing-LegalRetainedEarnings 102 0066 Equity CloseByDivision False Credit Debit MS Other Nothing-CumulativeTranslationAdjustment 103 0067 Equity CloseByDivision False Credit Debit MS Other Nothing-ProvisionForDoubtfulAccounts 104 0068 Cost CloseByDivision False Debit Credit OUT Other Flip-BadDebtLoss 105 0069 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfFixedAssets 106 006a Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfNotesReceivable 107 006b Cost CloseByDivision False Debit Credit OUT Other Flip-PaymentFees 108 006c Cost CloseByDivision False Debit Credit OUT Other Flip-MiscellaneousLoss 109 006d Cost CloseByDivision False Debit Credit OUT Other Flip-CorporateIncomeTaxes 110 006e Cost CloseByDivision False Debit Credit OUT Other Flip-CommunicationExpenses 111 006f Cost CloseByDivision False Debit Credit OUT Other Flip-GainOnSalesOfFixedAssets 112 0070 Revenue CloseByDivision False Credit Debit IN Other Keep-RecoveryOfBadDebts 113 0071 Revenue CloseByDivision False Credit Debit IN Other Keep-MiscellaneousIncome 114 0072 Revenue CloseByDivision False Credit Debit IN Other Keep-ReversalOfAllowanceForDoubtfulAccounts 115 0073 Revenue CloseByDivision False Credit Debit IN Other Keep-TimeDeposits 116 0074 Assets CloseByDivision False Debit Credit PS Current Nothing-LoansReceivable 117 0075 Assets CloseByDivision False Debit Credit PS Current Nothing-GiftCertificatesReceived 118 0076 Assets CloseByDivision False Debit Credit PS Current Nothing-SecurityDepositsPaid 119 0077 Assets CloseByDivision False Debit Credit PS Fixed Nothing-SuppliesOnHand 120 0078 Assets CloseByDivision False Debit Credit PS Current Nothing-ContractAssets 121 0079 Assets CloseByDivision False Debit Credit PS Current Nothing-IncomeTaxesRefundReceivable 122 007a Assets CloseByDivision False Debit Credit PS Current Nothing-WorkInProcess 123 007b Assets CloseByDivision False Debit Credit PS Current Nothing-DeferredTaxAssets 124 007c Assets CloseByDivision False Debit Credit PS Fixed Nothing-LeasedAssets 125 007d Assets CloseByDivision False Debit Credit PS Fixed Nothing-ToolsAndInstruments 126 007e Assets CloseByDivision False Debit Credit PS Fixed Nothing-ConstructionInProgress 127 007f Assets CloseByDivision False Debit Credit PS Fixed Nothing-Goodwill 128 0080 Assets CloseByDivision False Debit Credit PS Fixed Nothing-SoftwareInProgress 129 0081 Assets CloseByDivision False Debit Credit PS Fixed Nothing-LongTermPrepaidExpenses 130 0082 Assets CloseByDivision False Debit Credit PS Fixed Nothing-DishonoredNotesReceivable 131 0083 Assets CloseByDivision False Debit Credit PS Current Nothing-PrepaidPensionCost 132 0084 Assets CloseByDivision False Debit Credit PS Fixed Nothing-NetDefinedBenefitAsset 133 0085 Assets CloseByDivision False Debit Credit PS Fixed Nothing-DepositsInSpecialAccounts 134 0086 Assets CloseByDivision False Debit Credit PS Current Nothing-Structures 135 0087 Assets CloseByDivision False Debit Credit PS Fixed Nothing-LeaseholdRights 136 0088 Assets CloseByDivision False Debit Credit PS Fixed Nothing-NonOperatingNotesReceivable 137 0089 Assets CloseByDivision False Debit Credit PS Current Nothing-NonOperatingElectronicallyRecordedReceivable 138 008a Assets CloseByDivision False Debit Credit PS Current Nothing-RefundLiabilities 139 008b Liability CloseByDivision False Credit Debit MS Current Nothing-NonOperatingNotesPayable 140 008c Liability CloseByDivision False Credit Debit MS Current Nothing-NonOperatingElectronicallyRecordedObligations 141 008d Liability CloseByDivision False Credit Debit MS Current Nothing-BonusesPayable 142 008e Liability CloseByDivision False Credit Debit MS Current Nothing-AllowanceForRepairs 143 008f Liability CloseByDivision False Credit Debit MS Current Nothing-AllowanceForProductWarranties 144 0090 Liability CloseByDivision False Credit Debit MS Current Nothing-AllowanceForBonuses 145 0091 Liability CloseByDivision False Credit Debit MS Current Nothing-DeferredTaxLiabilities 146 0092 Liability CloseByDivision False Credit Debit MS Fixed Nothing-LeaseObligations 147 0093 Liability CloseByDivision False Credit Debit MS Fixed Nothing-GuaranteeDepositsReceived 148 0094 Liability CloseByDivision False Credit Debit MS Fixed Nothing-AllowanceForRetirementBenefits 149 0095 Liability CloseByDivision False Credit Debit MS Fixed Nothing-LongTermOtherPayables 150 0096 Liability CloseByDivision False Credit Debit MS Fixed Nothing-NetDefinedBenefitLiability 151 0097 Liability CloseByDivision False Credit Debit MS Fixed Nothing-StockSubscriptionDeposits 152 0098 Equity CloseByDivision False Credit Debit MS Other Nothing-LegalCapitalSurplus 153 0099 Equity CloseByDivision False Credit Debit MS Other Nothing-OtherCapitalSurplus 154 009a Equity CloseByDivision False Credit Debit MS Other Nothing-DividendEqualizationReserve 155 009b Equity CloseByDivision False Credit Debit MS Other Nothing-RepairFundReserve 156 009c Equity CloseByDivision False Credit Debit MS Other Nothing-ConstructionFundReserve 157 009d Equity CloseByDivision False Credit Debit MS Other Nothing-GeneralReserve 158 009e Equity CloseByDivision False Credit Debit MS Other Nothing-ValuationDifferenceOnOtherSecurities 159 009f Equity CloseByDivision False Credit Debit MS Other Nothing-NonControllingInterests 160 00a0 Equity CloseByDivision False Credit Debit MS Other Nothing-CapitalSurplus 161 00a1 Equity CloseByDivision False Credit Debit MS Other Nothing-EarnedSurplus 162 00a2 Equity CloseByDivision False Credit Debit MS Other Nothing-ServiceRevenue 163 00a3 Revenue CloseByDivision False Credit Debit IN Other Keep-OperatingRevenue 164 00a4 Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnSalesOfSecurities 165 00a5 Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnValuationOfSecurities 166 00a6 Revenue CloseByDivision False Credit Debit IN Other Keep-DividendsReceived 167 00a7 Revenue CloseByDivision False Credit Debit IN Other Keep-InterestOnSecurities 168 00a8 Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnSalesOfInvestmentSecurities 169 00a9 Revenue CloseByDivision False Credit Debit IN Other Keep-InsuranceGain 170 00aa Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnBargainPurchase 171 00ab Revenue CloseByDivision False Credit Debit IN Other Keep-ReversalOfAllowanceForRepairs 172 00ac Revenue CloseByDivision False Credit Debit IN Other Keep-ReversalOfAllowanceForProductWarranties 173 00ad Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnDonationOfFixedAssets 174 00ae Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnNationalSubsidies 175 00af Revenue CloseByDivision False Credit Debit IN Other Keep-GainOnConstructionGrants 176 00b0 Revenue CloseByDivision False Credit Debit IN Other Keep-LandRentReceived 177 00b1 Revenue CloseByDivision False Credit Debit IN Other Keep-SalesRebates 178 00b2 Revenue CloseByDivision True Debit Credit OUT Other Flip-CostOfServices 179 00b3 Cost CloseByDivision False Debit Credit OUT Other Flip-OperatingExpenses 180 00b4 Cost CloseByDivision False Debit Credit OUT Other Flip-InventoryShrinkageLoss 181 00b5 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnValuationOfMerchandise 182 00b6 Cost CloseByDivision False Debit Credit OUT Other Flip-Bonuses 183 00b7 Cost CloseByDivision False Debit Credit OUT Other Flip-RetirementBenefitExpenses 184 00b8 Cost CloseByDivision False Debit Credit OUT Other Flip-ProvisionForRepairs 185 00b9 Cost CloseByDivision False Debit Credit OUT Other Flip-ProvisionForBonuses 186 00ba Cost CloseByDivision False Debit Credit OUT Other Flip-ProvisionForProductWarranties 187 00bb Cost CloseByDivision False Debit Credit OUT Other Flip-ResearchAndDevelopmentExpenses 188 00bc Cost CloseByDivision False Debit Credit OUT Other Flip-AmortizationOfGoodwill 189 00bd Cost CloseByDivision False Debit Credit OUT Other Flip-AmortizationOfSoftware 190 00be Cost CloseByDivision False Debit Credit OUT Other Flip-AmortizationOfPatents 191 00bf Cost CloseByDivision False Debit Credit OUT Other Flip-LeaseExpenses 192 00c0 Cost CloseByDivision False Debit Credit OUT Other Flip-IncorporationExpenses 193 00c1 Cost CloseByDivision False Debit Credit OUT Other Flip-StockIssuanceCosts 194 00c2 Cost CloseByDivision False Debit Credit OUT Other Flip-BusinessCommencementExpenses 195 00c3 Cost CloseByDivision False Debit Credit OUT Other Flip-DevelopmentExpenses 196 00c4 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfElectronicallyRecordedReceivables 197 00c5 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfReceivables 198 00c6 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfSecurities 199 00c7 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnValuationOfSecurities 200 00c8 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnSalesOfInvestmentSecurities 201 00c9 Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnFire 202 00ca Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnRetirementOfFixedAssets 203 00cb Cost CloseByDivision False Debit Credit OUT Other Flip-LossOnReductionOfFixedAssets 204 00cc Cost CloseByDivision False Debit Credit OUT Other Flip-AdditionalIncomeTaxesForPriorPeriods 205 00cd Cost CloseByDivision False Debit Credit OUT Other Flip-RefundOfIncomeTaxes 206 00ce Cost CloseByDivision True Credit Debit IN Other Keep-PurchaseRebates 207 00cf Cost CloseByDivision True Credit Debit IN Other Keep-WelfareExpenses 208 00d0 Cost CloseByDivision False Debit Credit OUT Other Flip-MaintenanceExpenses 209 00d1 Cost CloseByDivision False Debit Credit OUT Other Flip-StatutoryWelfareExpenses 210 00d2 Cost CloseByDivision False Debit Credit OUT Other Flip-LandRentPaid 211 00d3 Cost CloseByDivision False Debit Credit OUT Other Flip-InsuranceExpense 212 00d4 Cost CloseByDivision False Debit Credit OUT Other Flip-RepairsExpense 213 00d5 Cost CloseByDivision False Debit Credit OUT Other Flip-StorageExpenses 214 00d6 Cost CloseByDivision False Debit Credit OUT Other Flip-MembershipFees 215 00d7 Cost CloseByDivision False Debit Credit OUT Other Flip-IncomeSummary 216 00d8 Assets NoClose False Debit Credit PS Other Nothing-SuspenseAccount 217 00d9 Assets CloseByDivision False Debit Credit PS Current Nothing-ForeignExchangeGains 218 00da Revenue CloseByDivision False Credit Debit IN Other Keep-ForeignExchangeLosses 219 00db Cost CloseByDivision False Debit Credit OUT Other Flip-ContraAccountForGuaranteeObligations 220 00dc Assets CloseByDivision False Debit Credit PS Other Nothing-GuaranteeObligations 221 00dd Liability CloseByDivision False Credit Debit MS Other Nothing-IncomeTaxesAdjustment 222 00de Cost CloseByDivision False Debit Credit OUT Other Flip-BranchCurrentAccount 223 00df Assets CloseByDivision False Debit Credit PS Other Nothing-HeadOfficeCurrentAccount 224 00e0 Liability CloseByDivision False Credit Debit MS Other Nothing-NetIncomeAttributableToNCI 225 00e1 Cost NoClose False Debit Credit OUT Other Nothing-NetLossAttributableToNCI 226 00e2 Revenue NoClose False Credit Debit IN Other Nothing-TradingSecurities 227 00e3 Assets CloseByDivision False Debit Credit PS Current Nothing-HeldToMaturityBonds 228 00e4 Assets CloseByDivision False Debit Credit PS Fixed Nothing-SubsidiaryStocks 229 00e5 Assets CloseByDivision False Debit Credit PS Fixed Nothing-AffiliateStocks 230 00e6 Assets CloseByDivision False Debit Credit PS Fixed Nothing-AvailableForSaleSecurities 231 00e7 Assets CloseByDivision False Debit Credit PS Fixed Nothing
− test/fixtures/pre-land1/account-info.tsv
@@ -1,117 +0,0 @@-# pre-land1 allAccountInfos (title, aiDivision, aiHomeSide, aiNameEn, aiNameJa, aiDesc); commit 2d9164642f2862725c653e496976770f6e2c7d6f-Cash Assets Debit Cash 現金 Asset: Cash (現金)-Deposits Assets Debit Savings deposits 普通預金 Asset: Savings deposits (普通預金)-CurrentDeposits Assets Debit Current deposits 当座預金 Asset: Current deposits (当座預金)-Securities Assets Debit Securities 有価証券 Asset: Securities (有価証券)-InvestmentSecurities Assets Debit Investment securities 投資有価証券 Asset: Investment securities (投資有価証券)-InvestmentInAssociate Assets Debit Investment in associate 関係会社株式 Asset: Investment in associate (関係会社株式). Carrying amount under the equity method (持分法適用投資勘定).-LongTermNationalBonds Assets Debit Long-term national bonds 長期国債 Asset: Long-term national bonds (長期国債)-ShortTermNationalBonds Assets Debit Short-term national bonds 短期国債 Asset: Short-term national bonds (短期国債)-Products Assets Debit Products 商品 Asset: Products (商品)。分記法用。3 分法 (仕入\/売上\/繰越商品) では 'MerchandiseInventory' を使う-Machinery Assets Debit Machinery and equipment 機械装置 Asset: Machinery and equipment (機械装置)-Building Assets Debit Real estate 建物 Asset: Real estate (建物)-Vehicle Assets Debit Vehicles 車両運搬具 Asset: Vehicles (車両運搬具)-StockInvestment Assets Debit Stock investment 株式投資 Asset: Stock investment (株式投資)-EquipmentInvestment Assets Debit Equipment investment 設備投資 Asset: Equipment investment (設備投資)-LongTermLoansReceivable Assets Debit Long-term loans receivable 長期貸付金 Asset: Long-term loans receivable (長期貸付金)-AccountsReceivable Assets Debit Accounts receivable 売掛金 Asset: Accounts receivable (売掛金)-ShortTermLoansReceivable Assets Debit Short-term loans receivable 短期貸付金 Asset: Short-term loans receivable (短期貸付金)-ReserveDepositReceivable Assets Debit Reserve deposits, asset side 準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系 Asset: Reserve deposits, asset side (準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系)-Gold Assets Debit Gold 金 Asset: Gold (金)-GovernmentService Assets Debit Government service 政府サービス。SNA\/マクロ系 Asset: Government service (政府サービス。SNA\/マクロ系)-CapitalStock Equity Credit Capital stock 資本金 Equity: Capital stock (資本金)-RetainedEarnings Equity Credit Retained earnings 繰越利益剰余金 Equity: Retained earnings (繰越利益剰余金)-LongTermLoansPayable Liability Credit Long-term loans payable 長期借入金 Liability: Long-term loans payable (長期借入金)-ShortTermLoansPayable Liability Credit Short-term loans payable 短期借入金 Liability: Short-term loans payable (短期借入金)-LoansPayable Liability Credit Loans payable 借入金 Liability: Loans payable (借入金)-ReserveForDepreciation Liability Credit Reserve for depreciation 減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額 Liability: Reserve for depreciation (減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額) を使う)-DepositPayable Liability Credit Deposits accepted 受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived' Liability: Deposits accepted (受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived')-LongTermNationalBondsPayable Liability Credit Long-term national bonds payable 長期国債, 発行側 Liability: Long-term national bonds payable (長期国債, 発行側)-ShortTermNationalBondsPayable Liability Credit Short-term national bonds payable 短期国債, 発行側 Liability: Short-term national bonds payable (短期国債, 発行側)-ReserveDepositPayable Liability Credit Reserve deposits, liability side 準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系 Liability: Reserve deposits, liability side (準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 'AccountsPayable' を使うこと (旧 examples が本科目を買掛金の代用にしていた経緯あり)-CentralBankNotePayable Liability Credit Central bank notes 発行銀行券。SNA\/マクロ系 Liability: Central bank notes (発行銀行券。SNA\/マクロ系)-Depreciation Cost Debit Depreciation 減価償却費 Expense: Depreciation (減価償却費)-AmortizationExpense Cost Debit Amortization expense for intangibles 無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費 Expense: Amortization expense for intangibles (無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費) と区別される)-SalesCost Cost Debit Cost of sales 売上原価 Expense: Cost of sales (売上原価)-BusinessTrip Cost Debit Travel and transportation 旅費交通費 Expense: Travel and transportation (旅費交通費)-Commutation Cost Debit Communication 通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う Expense: Communication (通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う)-UtilitiesExpense Cost Debit Utilities 水道光熱費 Expense: Utilities (水道光熱費)-RentExpense Cost Debit Rent 支払家賃 Expense: Rent (支払家賃)-AdvertisingExpense Cost Debit Advertising 広告宣伝費 Expense: Advertising (広告宣伝費)-DeliveryExpenses Cost Debit Delivery 発送費 Expense: Delivery (発送費)-SuppliesExpenses Cost Debit Supplies 消耗品費 Expense: Supplies (消耗品費)-MiscellaneousExpenses Cost Debit Miscellaneous 雑費 Expense: Miscellaneous (雑費)-WageExpenditure Cost Debit Wages 給料 Expense: Wages (給料)-InterestExpense Cost Debit Interest expense 支払利息 Expense: Interest expense (支払利息)-TaxesExpense Cost Debit Taxes 租税公課 Expense: Taxes (租税公課)-ConsumptionExpenditure Cost Debit Consumption expenditure 消費支出。SNA\/マクロ系 Expense: Consumption expenditure (消費支出。SNA\/マクロ系)-SubsidyExpense Cost Debit Subsidy expenditure 補助金支出。SNA\/マクロ系 Expense: Subsidy expenditure (補助金支出。SNA\/マクロ系)-CentralBankPaymentExpense Cost Debit Central bank payment to treasury 国庫納付金支出。SNA\/マクロ系 Expense: Central bank payment to treasury (国庫納付金支出。SNA\/マクロ系)-Purchases Cost Debit Purchases 仕入 Expense: Purchases (仕入)-NetIncome Cost Debit Net income 当期純利益 — 決算振替用。借方側に立つため Expense 区分 Expense: Net income (当期純利益 — 決算振替用。借方側に立つため Expense 区分)-ValueAdded Revenue Credit Value added 付加価値。SNA\/マクロ系 Revenue: Value added (付加価値。SNA\/マクロ系)-SubsidyIncome Revenue Credit Subsidy income 補助金収入。SNA\/マクロ系 Revenue: Subsidy income (補助金収入。SNA\/マクロ系)-NationalBondInterestEarned Revenue Credit National bond interest earned 国債利息収入 Revenue: National bond interest earned (国債利息収入)-DepositInterestEarned Revenue Credit Deposit interest earned 預金利息収入 Revenue: Deposit interest earned (預金利息収入)-GrossProfit Revenue Credit Gross profit 売上総利益 — 決算振替用 Revenue: Gross profit (売上総利益 — 決算振替用)-OrdinaryProfit Revenue Credit Ordinary profit 経常利益 — 決算振替用 Revenue: Ordinary profit (経常利益 — 決算振替用)-InterestEarned Revenue Credit Interest earned 受取利息 Revenue: Interest earned (受取利息)-ReceiptFee Revenue Credit Receipt fee 受取手数料。支払側は 'PaymentFees' Revenue: Receipt fee (受取手数料。支払側は 'PaymentFees')-RentalIncome Revenue Credit Rental income 受取家賃 Revenue: Rental income (受取家賃)-WageEarned Revenue Credit Wage income 賃金収入。SNA\/マクロ系 Revenue: Wage income (賃金収入。SNA\/マクロ系)-TaxesRevenue Revenue Credit Tax revenue 租税収入。SNA\/マクロ系 Revenue: Tax revenue (租税収入。SNA\/マクロ系)-CentralBankPaymentIncome Revenue Credit Central bank payment to treasury 国庫納付金収入。SNA\/マクロ系 Revenue: Central bank payment to treasury (国庫納付金収入。SNA\/マクロ系)-Sales Revenue Credit Sales 売上 Revenue: Sales (売上)-EquityInEarningsOfInvestee Revenue Credit Equity in earnings of investee 持分法による投資利益 Revenue: Equity in earnings of investee (持分法による投資利益). Recognised under the equity method.-NetLoss Revenue Credit Net loss 当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 Revenue: Net loss (当期純損失 — 決算振替用。貸方側に立つため Revenue 区分)-PettyCash Assets Debit Petty cash 小口現金 Asset: Petty cash (小口現金)-NotesReceivable Assets Debit Notes receivable 受取手形 Asset: Notes receivable (受取手形)-ElectronicallyRecordedReceivable Assets Debit Electronically recorded monetary claims 電子記録債権 Asset: Electronically recorded monetary claims (電子記録債権)-CreditCardReceivable Assets Debit Credit card receivable クレジット売掛金 Asset: Credit card receivable (クレジット売掛金)-NotesLoansReceivable Assets Debit Loans receivable on notes 手形貸付金 Asset: Loans receivable on notes (手形貸付金)-MerchandiseInventory Assets Debit Merchandise inventory 繰越商品 Asset: Merchandise inventory (繰越商品). Use under the periodic/3-account method (3 分法: Purchases\/Sales\/MerchandiseInventory). For the perpetual\/specific-identification method (分記法) use 'Products' instead.-AdvancesPaid Assets Debit Advances paid 前払金 Asset: Advances paid (前払金)-PrepaidExpenses Assets Debit Prepaid expenses 前払費用 Asset: Prepaid expenses (前払費用), deferral accrual account (経過勘定)-AccruedRevenue Assets Debit Accrued revenue 未収収益 Asset: Accrued revenue (未収収益), deferral accrual account (経過勘定)-OtherReceivables Assets Debit Other receivables 未収入金 Asset: Other receivables (未収入金)-PaymentsOnBehalf Assets Debit Payments made on behalf 立替金 Asset: Payments made on behalf (立替金)-SuspensePayments Assets Debit Suspense payments 仮払金 Asset: Suspense payments (仮払金)-ConsumptionTaxPaid Assets Debit Consumption tax paid 仮払消費税 Asset: Consumption tax paid (仮払消費税)-PrepaidCorporateIncomeTaxes Assets Debit Prepaid corporate income taxes 仮払法人税等 Asset: Prepaid corporate income taxes (仮払法人税等)-Land Assets Debit Land 土地 Asset: Land (土地)-Fixtures Assets Debit Fixtures and equipment 備品 Asset: Fixtures and equipment (備品)-Patent Assets Debit Patent 特許権 Asset: Patent (特許権)-Trademark Assets Debit Trademark 商標権 Asset: Trademark (商標権)-Software Assets Debit Software ソフトウェア Asset: Software (ソフトウェア)-CashOverShort Assets Debit Cash over and short 現金過不足 Asset: Cash over and short (現金過不足), a temporary/suspense account cleared at closing to MiscellaneousIncome\/MiscellaneousLoss-AccountsPayable Liability Credit Accounts payable 買掛金 Liability: Accounts payable (買掛金)-NotesPayable Liability Credit Notes payable 支払手形 Liability: Notes payable (支払手形)-ElectronicallyRecordedObligations Liability Credit Electronically recorded monetary obligations 電子記録債務 Liability: Electronically recorded monetary obligations (電子記録債務)-NotesLoansPayable Liability Credit Loans payable on notes 手形借入金 Liability: Loans payable on notes (手形借入金)-BankOverdraft Liability Credit Bank overdraft 当座借越 Liability: Bank overdraft (当座借越)-AdvancesReceived Liability Credit Advances received 前受金 Liability: Advances received (前受金)-UnearnedRevenue Liability Credit Unearned revenue 前受収益 Liability: Unearned revenue (前受収益), deferral accrual account (経過勘定)-AccruedExpenses Liability Credit Accrued expenses 未払費用 Liability: Accrued expenses (未払費用), deferral accrual account (経過勘定)-OtherPayables Liability Credit Other payables 未払金 Liability: Other payables (未払金)-DepositsReceived Liability Credit Deposits received 預り金 Liability: Deposits received (預り金)-SuspenseReceipts Liability Credit Suspense receipts 仮受金 Liability: Suspense receipts (仮受金)-ConsumptionTaxReceived Liability Credit Consumption tax received 仮受消費税 Liability: Consumption tax received (仮受消費税)-AccruedConsumptionTax Liability Credit Accrued (unpaid) consumption tax 未払消費税 Liability: Accrued (unpaid) consumption tax (未払消費税)-AccruedCorporateIncomeTaxes Liability Credit Accrued (unpaid) corporate income taxes 未払法人税等 Liability: Accrued (unpaid) corporate income taxes (未払法人税等)-UnpaidDividends Liability Credit Unpaid dividends 未払配当金 Liability: Unpaid dividends (未払配当金)-AllowanceForDoubtfulAccounts Liability Credit Allowance for doubtful accounts 貸倒引当金 Liability: Allowance for doubtful accounts (貸倒引当金). Modeled as an independent credit-balance valuation account (評価勘定) classified under Liability: this keeps values non-negative and the Hat\/Not structure intact, deferring the B\/S contra-asset (deduction) presentation to the Write side, rather than adding a new AccountDivision (over-engineered at the 3-級 level).-AccumulatedDepreciation Liability Credit Accumulated depreciation 減価償却累計額 Liability: Accumulated depreciation (減価償却累計額), valuation account (評価勘定) under the indirect method (間接法). Classified under Liability for the same reason as 'AllowanceForDoubtfulAccounts'. This is the canonical bookkeeping account for accumulated depreciation; the existing 'ReserveForDepreciation' is retained as the legacy SNA\/macro-accounting name.-LegalRetainedEarnings Equity Credit Legal (appropriated) retained earnings reserve 利益準備金 Equity: Legal (appropriated) retained earnings reserve (利益準備金)-CumulativeTranslationAdjustment Equity Credit Cumulative translation adjustment 為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目 Equity: Cumulative translation adjustment (為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目)-ProvisionForDoubtfulAccounts Cost Debit Provision for doubtful accounts 貸倒引当金繰入 Cost: Provision for doubtful accounts (貸倒引当金繰入)-BadDebtLoss Cost Debit Bad debt loss 貸倒損失 Cost: Bad debt loss (貸倒損失)-LossOnSalesOfFixedAssets Cost Debit Loss on sales of fixed assets 固定資産売却損 Cost: Loss on sales of fixed assets (固定資産売却損)-LossOnSalesOfNotesReceivable Cost Debit Loss on sales of notes receivable 手形売却損 Cost: Loss on sales of notes receivable (手形売却損)-PaymentFees Cost Debit Payment fees / fees paid 支払手数料 Cost: Payment fees / fees paid (支払手数料), the debit counterpart of 'ReceiptFee'-MiscellaneousLoss Cost Debit Miscellaneous loss 雑損 Cost: Miscellaneous loss (雑損)-CorporateIncomeTaxes Cost Debit Corporate income taxes 法人税等 Cost: Corporate income taxes (法人税等)-CommunicationExpenses Cost Debit Communication expenses 通信費 Cost: Communication expenses (通信費). Explicitly named counterpart of the legacy 'Commutation' (also "Communication"); 'Commutation' is retained for backward compatibility.-GainOnSalesOfFixedAssets Revenue Credit Gain on sales of fixed assets 固定資産売却益 Revenue: Gain on sales of fixed assets (固定資産売却益)-RecoveryOfBadDebts Revenue Credit Recovery of bad debts written off 償却債権取立益 Revenue: Recovery of bad debts written off (償却債権取立益)-MiscellaneousIncome Revenue Credit Miscellaneous income 雑益 Revenue: Miscellaneous income (雑益)-ReversalOfAllowanceForDoubtfulAccounts Revenue Credit Reversal of allowance for doubtful accounts 貸倒引当金戻入 Revenue: Reversal of allowance for doubtful accounts (貸倒引当金戻入). Credit counterpart used by the 差額補充法/洗替法 when the estimated allowance is smaller than the existing balance (the excess of 'AllowanceForDoubtfulAccounts' is released).
− test/fixtures/pre-land1/account-semantics.tsv
@@ -1,117 +0,0 @@-# pre-land1 semantics (title, whatDiv, whatPIMO, whichSide Not, whichSide Hat, fixedCurrent); commit 2d9164642f2862725c653e496976770f6e2c7d6f-Cash Assets PS Debit Credit Current-Deposits Assets PS Debit Credit Current-CurrentDeposits Assets PS Debit Credit Current-Securities Assets PS Debit Credit Current-InvestmentSecurities Assets PS Debit Credit Fixed-InvestmentInAssociate Assets PS Debit Credit Fixed-LongTermNationalBonds Assets PS Debit Credit Fixed-ShortTermNationalBonds Assets PS Debit Credit Current-Products Assets PS Debit Credit Current-Machinery Assets PS Debit Credit Fixed-Building Assets PS Debit Credit Fixed-Vehicle Assets PS Debit Credit Fixed-StockInvestment Assets PS Debit Credit Other-EquipmentInvestment Assets PS Debit Credit Fixed-LongTermLoansReceivable Assets PS Debit Credit Fixed-AccountsReceivable Assets PS Debit Credit Current-ShortTermLoansReceivable Assets PS Debit Credit Current-ReserveDepositReceivable Assets PS Debit Credit Current-Gold Assets PS Debit Credit Fixed-GovernmentService Assets PS Debit Credit Current-CapitalStock Equity MS Credit Debit Other-RetainedEarnings Equity MS Credit Debit Other-LongTermLoansPayable Liability MS Credit Debit Fixed-ShortTermLoansPayable Liability MS Credit Debit Current-LoansPayable Liability MS Credit Debit Current-ReserveForDepreciation Liability MS Credit Debit Current-DepositPayable Liability MS Credit Debit Current-LongTermNationalBondsPayable Liability MS Credit Debit Fixed-ShortTermNationalBondsPayable Liability MS Credit Debit Current-ReserveDepositPayable Liability MS Credit Debit Current-CentralBankNotePayable Liability MS Credit Debit Current-Depreciation Cost OUT Debit Credit Other-AmortizationExpense Cost OUT Debit Credit Other-SalesCost Cost OUT Debit Credit Other-BusinessTrip Cost OUT Debit Credit Other-Commutation Cost OUT Debit Credit Other-UtilitiesExpense Cost OUT Debit Credit Other-RentExpense Cost OUT Debit Credit Other-AdvertisingExpense Cost OUT Debit Credit Other-DeliveryExpenses Cost OUT Debit Credit Other-SuppliesExpenses Cost OUT Debit Credit Other-MiscellaneousExpenses Cost OUT Debit Credit Other-WageExpenditure Cost OUT Debit Credit Other-InterestExpense Cost OUT Debit Credit Other-TaxesExpense Cost OUT Debit Credit Other-ConsumptionExpenditure Cost OUT Debit Credit Other-SubsidyExpense Cost OUT Debit Credit Other-CentralBankPaymentExpense Cost OUT Debit Credit Other-Purchases Cost OUT Debit Credit Other-NetIncome Cost OUT Debit Credit Other-ValueAdded Revenue IN Credit Debit Other-SubsidyIncome Revenue IN Credit Debit Other-NationalBondInterestEarned Revenue IN Credit Debit Other-DepositInterestEarned Revenue IN Credit Debit Other-GrossProfit Revenue IN Credit Debit Other-OrdinaryProfit Revenue IN Credit Debit Other-InterestEarned Revenue IN Credit Debit Other-ReceiptFee Revenue IN Credit Debit Other-RentalIncome Revenue IN Credit Debit Other-WageEarned Revenue IN Credit Debit Other-TaxesRevenue Revenue IN Credit Debit Other-CentralBankPaymentIncome Revenue IN Credit Debit Other-Sales Revenue IN Credit Debit Other-EquityInEarningsOfInvestee Revenue IN Credit Debit Other-NetLoss Revenue IN Credit Debit Other-PettyCash Assets PS Debit Credit Current-NotesReceivable Assets PS Debit Credit Current-ElectronicallyRecordedReceivable Assets PS Debit Credit Current-CreditCardReceivable Assets PS Debit Credit Current-NotesLoansReceivable Assets PS Debit Credit Current-MerchandiseInventory Assets PS Debit Credit Current-AdvancesPaid Assets PS Debit Credit Current-PrepaidExpenses Assets PS Debit Credit Current-AccruedRevenue Assets PS Debit Credit Current-OtherReceivables Assets PS Debit Credit Current-PaymentsOnBehalf Assets PS Debit Credit Current-SuspensePayments Assets PS Debit Credit Current-ConsumptionTaxPaid Assets PS Debit Credit Current-PrepaidCorporateIncomeTaxes Assets PS Debit Credit Current-Land Assets PS Debit Credit Fixed-Fixtures Assets PS Debit Credit Fixed-Patent Assets PS Debit Credit Fixed-Trademark Assets PS Debit Credit Fixed-Software Assets PS Debit Credit Fixed-CashOverShort Assets PS Debit Credit Other-AccountsPayable Liability MS Credit Debit Current-NotesPayable Liability MS Credit Debit Current-ElectronicallyRecordedObligations Liability MS Credit Debit Current-NotesLoansPayable Liability MS Credit Debit Current-BankOverdraft Liability MS Credit Debit Current-AdvancesReceived Liability MS Credit Debit Current-UnearnedRevenue Liability MS Credit Debit Current-AccruedExpenses Liability MS Credit Debit Current-OtherPayables Liability MS Credit Debit Current-DepositsReceived Liability MS Credit Debit Current-SuspenseReceipts Liability MS Credit Debit Current-ConsumptionTaxReceived Liability MS Credit Debit Current-AccruedConsumptionTax Liability MS Credit Debit Current-AccruedCorporateIncomeTaxes Liability MS Credit Debit Current-UnpaidDividends Liability MS Credit Debit Current-AllowanceForDoubtfulAccounts Liability MS Credit Debit Current-AccumulatedDepreciation Liability MS Credit Debit Fixed-LegalRetainedEarnings Equity MS Credit Debit Other-CumulativeTranslationAdjustment Equity MS Credit Debit Other-ProvisionForDoubtfulAccounts Cost OUT Debit Credit Other-BadDebtLoss Cost OUT Debit Credit Other-LossOnSalesOfFixedAssets Cost OUT Debit Credit Other-LossOnSalesOfNotesReceivable Cost OUT Debit Credit Other-PaymentFees Cost OUT Debit Credit Other-MiscellaneousLoss Cost OUT Debit Credit Other-CorporateIncomeTaxes Cost OUT Debit Credit Other-CommunicationExpenses Cost OUT Debit Credit Other-GainOnSalesOfFixedAssets Revenue IN Credit Debit Other-RecoveryOfBadDebts Revenue IN Credit Debit Other-MiscellaneousIncome Revenue IN Credit Debit Other-ReversalOfAllowanceForDoubtfulAccounts Revenue IN Credit Debit Other
− test/fixtures/pre-land1/alias-resolution.tsv
@@ -1,704 +0,0 @@-# pre-land1 parseAccountTitle over corpus (query, show(Either ConvError AccountTitles)); commit 2d9164642f2862725c653e496976770f6e2c7d6f- Left (UnknownAccount "")- AccountsPayable Right AccountsPayable- AccountsReceivable Right AccountsReceivable- AccruedConsumptionTax Right AccruedConsumptionTax- AccruedCorporateIncomeTaxes Right AccruedCorporateIncomeTaxes- AccruedExpenses Right AccruedExpenses- AccruedRevenue Right AccruedRevenue- AccumulatedDepreciation Right AccumulatedDepreciation- AdvancesPaid Right AdvancesPaid- AdvancesReceived Right AdvancesReceived- AdvertisingExpense Right AdvertisingExpense- AllowanceForDoubtfulAccounts Right AllowanceForDoubtfulAccounts- AmortizationExpense Right AmortizationExpense- BadDebtLoss Right BadDebtLoss- BankOverdraft Right BankOverdraft- Building Right Building- BusinessTrip Right BusinessTrip- CapitalStock Right CapitalStock- Cash Right Cash- CashOverShort Right CashOverShort- CentralBankNotePayable Right CentralBankNotePayable- CentralBankPaymentExpense Right CentralBankPaymentExpense- CentralBankPaymentIncome Right CentralBankPaymentIncome- CommunicationExpenses Right CommunicationExpenses- Commutation Right Commutation- ConsumptionExpenditure Right ConsumptionExpenditure- ConsumptionTaxPaid Right ConsumptionTaxPaid- ConsumptionTaxReceived Right ConsumptionTaxReceived- CorporateIncomeTaxes Right CorporateIncomeTaxes- CreditCardReceivable Right CreditCardReceivable- CumulativeTranslationAdjustment Right CumulativeTranslationAdjustment- CurrentDeposits Right CurrentDeposits- DeliveryExpenses Right DeliveryExpenses- DepositInterestEarned Right DepositInterestEarned- DepositPayable Right DepositPayable- Deposits Right Deposits- DepositsReceived Right DepositsReceived- Depreciation Right Depreciation- ElectronicallyRecordedObligations Right ElectronicallyRecordedObligations- ElectronicallyRecordedReceivable Right ElectronicallyRecordedReceivable- EquipmentInvestment Right EquipmentInvestment- EquityInEarningsOfInvestee Right EquityInEarningsOfInvestee- Fixtures Right Fixtures- GainOnSalesOfFixedAssets Right GainOnSalesOfFixedAssets- Gold Right Gold- GovernmentService Right GovernmentService- GrossProfit Right GrossProfit- InterestEarned Right InterestEarned- InterestExpense Right InterestExpense- InvestmentInAssociate Right InvestmentInAssociate- InvestmentSecurities Right InvestmentSecurities- Land Right Land- LegalRetainedEarnings Right LegalRetainedEarnings- LoansPayable Right LoansPayable- LongTermLoansPayable Right LongTermLoansPayable- LongTermLoansReceivable Right LongTermLoansReceivable- LongTermNationalBonds Right LongTermNationalBonds- LongTermNationalBondsPayable Right LongTermNationalBondsPayable- LossOnSalesOfFixedAssets Right LossOnSalesOfFixedAssets- LossOnSalesOfNotesReceivable Right LossOnSalesOfNotesReceivable- Machinery Right Machinery- MerchandiseInventory Right MerchandiseInventory- MiscellaneousExpenses Right MiscellaneousExpenses- MiscellaneousIncome Right MiscellaneousIncome- MiscellaneousLoss Right MiscellaneousLoss- NationalBondInterestEarned Right NationalBondInterestEarned- NetIncome Right NetIncome- NetLoss Right NetLoss- NotesLoansPayable Right NotesLoansPayable- NotesLoansReceivable Right NotesLoansReceivable- NotesPayable Right NotesPayable- NotesReceivable Right NotesReceivable- OrdinaryProfit Right OrdinaryProfit- OtherPayables Right OtherPayables- OtherReceivables Right OtherReceivables- Patent Right Patent- PaymentFees Right PaymentFees- PaymentsOnBehalf Right PaymentsOnBehalf- PettyCash Right PettyCash- PrepaidCorporateIncomeTaxes Right PrepaidCorporateIncomeTaxes- PrepaidExpenses Right PrepaidExpenses- Products Right Products- ProvisionForDoubtfulAccounts Right ProvisionForDoubtfulAccounts- Purchases Right Purchases- ReceiptFee Right ReceiptFee- RecoveryOfBadDebts Right RecoveryOfBadDebts- RentExpense Right RentExpense- RentalIncome Right RentalIncome- ReserveDepositPayable Right ReserveDepositPayable- ReserveDepositReceivable Right ReserveDepositReceivable- ReserveForDepreciation Right ReserveForDepreciation- RetainedEarnings Right RetainedEarnings- ReversalOfAllowanceForDoubtfulAccounts Right ReversalOfAllowanceForDoubtfulAccounts- Sales Right Sales- SalesCost Right SalesCost- Securities Right Securities- ShortTermLoansPayable Right ShortTermLoansPayable- ShortTermLoansReceivable Right ShortTermLoansReceivable- ShortTermNationalBonds Right ShortTermNationalBonds- ShortTermNationalBondsPayable Right ShortTermNationalBondsPayable- Software Right Software- StockInvestment Right StockInvestment- SubsidyExpense Right SubsidyExpense- SubsidyIncome Right SubsidyIncome- SuppliesExpenses Right SuppliesExpenses- SuspensePayments Right SuspensePayments- SuspenseReceipts Right SuspenseReceipts- TaxesExpense Right TaxesExpense- TaxesRevenue Right TaxesRevenue- Trademark Right Trademark- UnearnedRevenue Right UnearnedRevenue- UnpaidDividends Right UnpaidDividends- UtilitiesExpense Right UtilitiesExpense- ValueAdded Right ValueAdded- Vehicle Right Vehicle- WageEarned Right WageEarned- WageExpenditure Right WageExpenditure- a/p Right AccountsPayable- a/r Right AccountsReceivable- accounts payable Right AccountsPayable- accounts receivable Right AccountsReceivable- cogs Right SalesCost- cost of sales Right SalesCost- クレジット売掛金 Right CreditCardReceivable- ソフトウェア Right Software- 仕入 Right Purchases- 付加価値 Right ValueAdded- 仮受消費税 Right ConsumptionTaxReceived- 仮受金 Right SuspenseReceipts- 仮払法人税等 Right PrepaidCorporateIncomeTaxes- 仮払消費税 Right ConsumptionTaxPaid- 仮払金 Right SuspensePayments- 借入金 Right LoansPayable- 備品 Right Fixtures- 償却債権取立益 Right RecoveryOfBadDebts- 利益準備金 Right LegalRetainedEarnings- 前受収益 Right UnearnedRevenue- 前受金 Right AdvancesReceived- 前払費用 Right PrepaidExpenses- 前払金 Right AdvancesPaid- 受入預金 Right DepositPayable- 受取利息 Right InterestEarned- 受取家賃 Right RentalIncome- 受取手形 Right NotesReceivable- 受取手数料 Right ReceiptFee- 商品 Right Products- 商標権 Right Trademark- 固定資産売却損 Right LossOnSalesOfFixedAssets- 固定資産売却益 Right GainOnSalesOfFixedAssets- 国債利息収入 Right NationalBondInterestEarned- 国庫納付金収入 Right CentralBankPaymentIncome- 国庫納付金支出 Right CentralBankPaymentExpense- 土地 Right Land- 売上 Right Sales- 売上原価 Right SalesCost- 売上総利益 Right GrossProfit- 売掛金 Right AccountsReceivable- 小口現金 Right PettyCash- 広告宣伝費 Right AdvertisingExpense- 建物 Right Building- 当座借越 Right BankOverdraft- 当座預金 Right CurrentDeposits- 当期純利益 Right NetIncome- 当期純損失 Right NetLoss- 手形借入金 Right NotesLoansPayable- 手形売却損 Right LossOnSalesOfNotesReceivable- 手形貸付金 Right NotesLoansReceivable- 投資有価証券 Right InvestmentSecurities- 支払利息 Right InterestExpense- 支払家賃 Right RentExpense- 支払手形 Right NotesPayable- 支払手数料 Right PaymentFees- 政府サービス Right GovernmentService- 旅費交通費 Right BusinessTrip- 普通預金 Right Deposits- 有価証券 Right Securities- 未収入金 Right OtherReceivables- 未収収益 Right AccruedRevenue- 未払法人税等 Right AccruedCorporateIncomeTaxes- 未払消費税 Right AccruedConsumptionTax- 未払費用 Right AccruedExpenses- 未払配当金 Right UnpaidDividends- 未払金 Right OtherPayables- 株式投資 Right StockInvestment- 機械装置 Right Machinery- 水道光熱費 Right UtilitiesExpense- 法人税等 Right CorporateIncomeTaxes- 消耗品費 Right SuppliesExpenses- 消費支出 Right ConsumptionExpenditure- 減価償却引当金 Right ReserveForDepreciation- 減価償却累計額 Right AccumulatedDepreciation- 減価償却費 Right Depreciation- 準備預金 Left (AmbiguousAccount " \28310\20633\38928\37329 " [ReserveDepositReceivable,ReserveDepositPayable])- 特許権 Right Patent- 現金 Right Cash- 現金過不足 Right CashOverShort- 発行銀行券 Right CentralBankNotePayable- 発送費 Right DeliveryExpenses- 短期借入金 Right ShortTermLoansPayable- 短期国債 Left (AmbiguousAccount " \30701\26399\22269\20661 " [ShortTermNationalBonds,ShortTermNationalBondsPayable])- 短期貸付金 Right ShortTermLoansReceivable- 租税公課 Right TaxesExpense- 租税収入 Right TaxesRevenue- 立替金 Right PaymentsOnBehalf- 経常利益 Right OrdinaryProfit- 給料 Right WageExpenditure- 繰越利益剰余金 Right RetainedEarnings- 繰越商品 Right MerchandiseInventory- 補助金収入 Right SubsidyIncome- 補助金支出 Right SubsidyExpense- 設備投資 Right EquipmentInvestment- 買掛金 Right AccountsPayable- 貸倒引当金 Right AllowanceForDoubtfulAccounts- 貸倒引当金戻入 Right ReversalOfAllowanceForDoubtfulAccounts- 貸倒引当金繰入 Right ProvisionForDoubtfulAccounts- 貸倒損失 Right BadDebtLoss- 賃金収入 Right WageEarned- 資本金 Right CapitalStock- 車両運搬具 Right Vehicle- 通信費 Left (AmbiguousAccount " \36890\20449\36027 " [CommunicationExpenses,Commutation])- 金 Right Gold- 長期借入金 Right LongTermLoansPayable- 長期国債 Left (AmbiguousAccount " \38263\26399\22269\20661 " [LongTermNationalBonds,LongTermNationalBondsPayable])- 長期貸付金 Right LongTermLoansReceivable- 雑損 Right MiscellaneousLoss- 雑益 Right MiscellaneousIncome- 雑費 Right MiscellaneousExpenses- 電子記録債務 Right ElectronicallyRecordedObligations- 電子記録債権 Right ElectronicallyRecordedReceivable- 預り金 Right DepositsReceived- 預金利息収入 Right DepositInterestEarned-A/P Right AccountsPayable-A/R Right AccountsReceivable-ACCOUNTS PAYABLE Right AccountsPayable-ACCOUNTS RECEIVABLE Right AccountsReceivable-ACCOUNTSPAYABLE Right AccountsPayable-ACCOUNTSRECEIVABLE Right AccountsReceivable-ACCRUEDCONSUMPTIONTAX Right AccruedConsumptionTax-ACCRUEDCORPORATEINCOMETAXES Right AccruedCorporateIncomeTaxes-ACCRUEDEXPENSES Right AccruedExpenses-ACCRUEDREVENUE Right AccruedRevenue-ACCUMULATEDDEPRECIATION Right AccumulatedDepreciation-ADVANCESPAID Right AdvancesPaid-ADVANCESRECEIVED Right AdvancesReceived-ADVERTISINGEXPENSE Right AdvertisingExpense-ALLOWANCEFORDOUBTFULACCOUNTS Right AllowanceForDoubtfulAccounts-AMORTIZATIONEXPENSE Right AmortizationExpense-AccountTitle Left (UnknownAccount "AccountTitle")-AccountsPayable Right AccountsPayable-AccountsReceivable Right AccountsReceivable-AccruedConsumptionTax Right AccruedConsumptionTax-AccruedCorporateIncomeTaxes Right AccruedCorporateIncomeTaxes-AccruedExpenses Right AccruedExpenses-AccruedRevenue Right AccruedRevenue-AccumulatedDepreciation Right AccumulatedDepreciation-AdvancesPaid Right AdvancesPaid-AdvancesReceived Right AdvancesReceived-AdvertisingExpense Right AdvertisingExpense-AllowanceForDoubtfulAccounts Right AllowanceForDoubtfulAccounts-AmortizationExpense Right AmortizationExpense-BADDEBTLOSS Right BadDebtLoss-BANKOVERDRAFT Right BankOverdraft-BUILDING Right Building-BUSINESSTRIP Right BusinessTrip-BadDebtLoss Right BadDebtLoss-BankOverdraft Right BankOverdraft-Building Right Building-BusinessTrip Right BusinessTrip-CAPITALSTOCK Right CapitalStock-CASH Right Cash-CASHOVERSHORT Right CashOverShort-CENTRALBANKNOTEPAYABLE Right CentralBankNotePayable-CENTRALBANKPAYMENTEXPENSE Right CentralBankPaymentExpense-CENTRALBANKPAYMENTINCOME Right CentralBankPaymentIncome-COGS Right SalesCost-COMMUNICATIONEXPENSES Right CommunicationExpenses-COMMUTATION Right Commutation-CONSUMPTIONEXPENDITURE Right ConsumptionExpenditure-CONSUMPTIONTAXPAID Right ConsumptionTaxPaid-CONSUMPTIONTAXRECEIVED Right ConsumptionTaxReceived-CORPORATEINCOMETAXES Right CorporateIncomeTaxes-COST OF SALES Right SalesCost-CREDITCARDRECEIVABLE Right CreditCardReceivable-CUMULATIVETRANSLATIONADJUSTMENT Right CumulativeTranslationAdjustment-CURRENTDEPOSITS Right CurrentDeposits-CapitalStock Right CapitalStock-Cash Right Cash-CashOverShort Right CashOverShort-CentralBankNotePayable Right CentralBankNotePayable-CentralBankPaymentExpense Right CentralBankPaymentExpense-CentralBankPaymentIncome Right CentralBankPaymentIncome-CommunicationExpenses Right CommunicationExpenses-Commutation Right Commutation-ConsumptionExpenditure Right ConsumptionExpenditure-ConsumptionTaxPaid Right ConsumptionTaxPaid-ConsumptionTaxReceived Right ConsumptionTaxReceived-CorporateIncomeTaxes Right CorporateIncomeTaxes-CreditCardReceivable Right CreditCardReceivable-CumulativeTranslationAdjustment Right CumulativeTranslationAdjustment-CurrentDeposits Right CurrentDeposits-DELIVERYEXPENSES Right DeliveryExpenses-DEPOSITINTERESTEARNED Right DepositInterestEarned-DEPOSITPAYABLE Right DepositPayable-DEPOSITS Right Deposits-DEPOSITSRECEIVED Right DepositsReceived-DEPRECIATION Right Depreciation-DeliveryExpenses Right DeliveryExpenses-DepositInterestEarned Right DepositInterestEarned-DepositPayable Right DepositPayable-Deposits Right Deposits-DepositsReceived Right DepositsReceived-Depreciation Right Depreciation-ELECTRONICALLYRECORDEDOBLIGATIONS Right ElectronicallyRecordedObligations-ELECTRONICALLYRECORDEDRECEIVABLE Right ElectronicallyRecordedReceivable-EQUIPMENTINVESTMENT Right EquipmentInvestment-EQUITYINEARNINGSOFINVESTEE Right EquityInEarningsOfInvestee-ElectronicallyRecordedObligations Right ElectronicallyRecordedObligations-ElectronicallyRecordedReceivable Right ElectronicallyRecordedReceivable-EquipmentInvestment Right EquipmentInvestment-EquityInEarningsOfInvestee Right EquityInEarningsOfInvestee-FIXTURES Right Fixtures-Fixtures Right Fixtures-GAINONSALESOFFIXEDASSETS Right GainOnSalesOfFixedAssets-GOLD Right Gold-GOVERNMENTSERVICE Right GovernmentService-GROSSPROFIT Right GrossProfit-GainOnSalesOfFixedAssets Right GainOnSalesOfFixedAssets-Gold Right Gold-Goodwill_X Left (UnknownAccount "Goodwill_X")-GovernmentService Right GovernmentService-GrossProfit Right GrossProfit-INTERESTEARNED Right InterestEarned-INTERESTEXPENSE Right InterestExpense-INVESTMENTINASSOCIATE Right InvestmentInAssociate-INVESTMENTSECURITIES Right InvestmentSecurities-InterestEarned Right InterestEarned-InterestExpense Right InterestExpense-InvestmentInAssociate Right InvestmentInAssociate-InvestmentSecurities Right InvestmentSecurities-LAND Right Land-LEGALRETAINEDEARNINGS Right LegalRetainedEarnings-LOANSPAYABLE Right LoansPayable-LONGTERMLOANSPAYABLE Right LongTermLoansPayable-LONGTERMLOANSRECEIVABLE Right LongTermLoansReceivable-LONGTERMNATIONALBONDS Right LongTermNationalBonds-LONGTERMNATIONALBONDSPAYABLE Right LongTermNationalBondsPayable-LOSSONSALESOFFIXEDASSETS Right LossOnSalesOfFixedAssets-LOSSONSALESOFNOTESRECEIVABLE Right LossOnSalesOfNotesReceivable-Land Right Land-LegalRetainedEarnings Right LegalRetainedEarnings-LoansPayable Right LoansPayable-LongTermLoansPayable Right LongTermLoansPayable-LongTermLoansReceivable Right LongTermLoansReceivable-LongTermNationalBonds Right LongTermNationalBonds-LongTermNationalBondsPayable Right LongTermNationalBondsPayable-LossOnSalesOfFixedAssets Right LossOnSalesOfFixedAssets-LossOnSalesOfNotesReceivable Right LossOnSalesOfNotesReceivable-MACHINERY Right Machinery-MERCHANDISEINVENTORY Right MerchandiseInventory-MISCELLANEOUSEXPENSES Right MiscellaneousExpenses-MISCELLANEOUSINCOME Right MiscellaneousIncome-MISCELLANEOUSLOSS Right MiscellaneousLoss-Machinery Right Machinery-MerchandiseInventory Right MerchandiseInventory-MiscellaneousExpenses Right MiscellaneousExpenses-MiscellaneousIncome Right MiscellaneousIncome-MiscellaneousLoss Right MiscellaneousLoss-NATIONALBONDINTERESTEARNED Right NationalBondInterestEarned-NETINCOME Right NetIncome-NETLOSS Right NetLoss-NOTESLOANSPAYABLE Right NotesLoansPayable-NOTESLOANSRECEIVABLE Right NotesLoansReceivable-NOTESPAYABLE Right NotesPayable-NOTESRECEIVABLE Right NotesReceivable-NationalBondInterestEarned Right NationalBondInterestEarned-NetIncome Right NetIncome-NetLoss Right NetLoss-NotesLoansPayable Right NotesLoansPayable-NotesLoansReceivable Right NotesLoansReceivable-NotesPayable Right NotesPayable-NotesReceivable Right NotesReceivable-ORDINARYPROFIT Right OrdinaryProfit-OTHERPAYABLES Right OtherPayables-OTHERRECEIVABLES Right OtherReceivables-OrdinaryProfit Right OrdinaryProfit-OtherPayables Right OtherPayables-OtherReceivables Right OtherReceivables-PATENT Right Patent-PAYMENTFEES Right PaymentFees-PAYMENTSONBEHALF Right PaymentsOnBehalf-PETTYCASH Right PettyCash-PREPAIDCORPORATEINCOMETAXES Right PrepaidCorporateIncomeTaxes-PREPAIDEXPENSES Right PrepaidExpenses-PRODUCTS Right Products-PROVISIONFORDOUBTFULACCOUNTS Right ProvisionForDoubtfulAccounts-PURCHASES Right Purchases-Patent Right Patent-PaymentFees Right PaymentFees-PaymentsOnBehalf Right PaymentsOnBehalf-PettyCash Right PettyCash-PrepaidCorporateIncomeTaxes Right PrepaidCorporateIncomeTaxes-PrepaidExpenses Right PrepaidExpenses-Products Right Products-ProvisionForDoubtfulAccounts Right ProvisionForDoubtfulAccounts-Purchases Right Purchases-RECEIPTFEE Right ReceiptFee-RECOVERYOFBADDEBTS Right RecoveryOfBadDebts-RENTALINCOME Right RentalIncome-RENTEXPENSE Right RentExpense-RESERVEDEPOSITPAYABLE Right ReserveDepositPayable-RESERVEDEPOSITRECEIVABLE Right ReserveDepositReceivable-RESERVEFORDEPRECIATION Right ReserveForDepreciation-RETAINEDEARNINGS Right RetainedEarnings-REVERSALOFALLOWANCEFORDOUBTFULACCOUNTS Right ReversalOfAllowanceForDoubtfulAccounts-ReceiptFee Right ReceiptFee-RecoveryOfBadDebts Right RecoveryOfBadDebts-RentExpense Right RentExpense-RentalIncome Right RentalIncome-ReserveDepositPayable Right ReserveDepositPayable-ReserveDepositReceivable Right ReserveDepositReceivable-ReserveForDepreciation Right ReserveForDepreciation-RetainedEarnings Right RetainedEarnings-ReversalOfAllowanceForDoubtfulAccounts Right ReversalOfAllowanceForDoubtfulAccounts-SALES Right Sales-SALESCOST Right SalesCost-SECURITIES Right Securities-SHORTTERMLOANSPAYABLE Right ShortTermLoansPayable-SHORTTERMLOANSRECEIVABLE Right ShortTermLoansReceivable-SHORTTERMNATIONALBONDS Right ShortTermNationalBonds-SHORTTERMNATIONALBONDSPAYABLE Right ShortTermNationalBondsPayable-SOFTWARE Right Software-STOCKINVESTMENT Right StockInvestment-SUBSIDYEXPENSE Right SubsidyExpense-SUBSIDYINCOME Right SubsidyIncome-SUPPLIESEXPENSES Right SuppliesExpenses-SUSPENSEPAYMENTS Right SuspensePayments-SUSPENSERECEIPTS Right SuspenseReceipts-Sales Right Sales-SalesCost Right SalesCost-Securities Right Securities-ShortTermLoansPayable Right ShortTermLoansPayable-ShortTermLoansReceivable Right ShortTermLoansReceivable-ShortTermNationalBonds Right ShortTermNationalBonds-ShortTermNationalBondsPayable Right ShortTermNationalBondsPayable-Software Right Software-StockInvestment Right StockInvestment-SubsidyExpense Right SubsidyExpense-SubsidyIncome Right SubsidyIncome-SuppliesExpenses Right SuppliesExpenses-SuspensePayments Right SuspensePayments-SuspenseReceipts Right SuspenseReceipts-TAXESEXPENSE Right TaxesExpense-TAXESREVENUE Right TaxesRevenue-TRADEMARK Right Trademark-TaxesExpense Right TaxesExpense-TaxesRevenue Right TaxesRevenue-Trademark Right Trademark-UNEARNEDREVENUE Right UnearnedRevenue-UNPAIDDIVIDENDS Right UnpaidDividends-UTILITIESEXPENSE Right UtilitiesExpense-UnearnedRevenue Right UnearnedRevenue-UnpaidDividends Right UnpaidDividends-UtilitiesExpense Right UtilitiesExpense-VALUEADDED Right ValueAdded-VEHICLE Right Vehicle-ValueAdded Right ValueAdded-Vehicle Right Vehicle-WAGEEARNED Right WageEarned-WAGEEXPENDITURE Right WageExpenditure-WageEarned Right WageEarned-WageExpenditure Right WageExpenditure-a/p Right AccountsPayable-a/r Right AccountsReceivable-accounts payable Right AccountsPayable-accounts receivable Right AccountsReceivable-accountspayable Right AccountsPayable-accountsreceivable Right AccountsReceivable-accruedconsumptiontax Right AccruedConsumptionTax-accruedcorporateincometaxes Right AccruedCorporateIncomeTaxes-accruedexpenses Right AccruedExpenses-accruedrevenue Right AccruedRevenue-accumulateddepreciation Right AccumulatedDepreciation-advancespaid Right AdvancesPaid-advancesreceived Right AdvancesReceived-advertisingexpense Right AdvertisingExpense-allowancefordoubtfulaccounts Right AllowanceForDoubtfulAccounts-amortizationexpense Right AmortizationExpense-baddebtloss Right BadDebtLoss-bankoverdraft Right BankOverdraft-building Right Building-businesstrip Right BusinessTrip-capitalstock Right CapitalStock-cash Right Cash-cashovershort Right CashOverShort-centralbanknotepayable Right CentralBankNotePayable-centralbankpaymentexpense Right CentralBankPaymentExpense-centralbankpaymentincome Right CentralBankPaymentIncome-cogs Right SalesCost-communicationexpenses Right CommunicationExpenses-commutation Right Commutation-consumptionexpenditure Right ConsumptionExpenditure-consumptiontaxpaid Right ConsumptionTaxPaid-consumptiontaxreceived Right ConsumptionTaxReceived-corporateincometaxes Right CorporateIncomeTaxes-cost of sales Right SalesCost-creditcardreceivable Right CreditCardReceivable-cumulativetranslationadjustment Right CumulativeTranslationAdjustment-currentdeposits Right CurrentDeposits-deliveryexpenses Right DeliveryExpenses-depositinterestearned Right DepositInterestEarned-depositpayable Right DepositPayable-deposits Right Deposits-depositsreceived Right DepositsReceived-depreciation Right Depreciation-electronicallyrecordedobligations Right ElectronicallyRecordedObligations-electronicallyrecordedreceivable Right ElectronicallyRecordedReceivable-equipmentinvestment Right EquipmentInvestment-equityinearningsofinvestee Right EquityInEarningsOfInvestee-fixtures Right Fixtures-gainonsalesoffixedassets Right GainOnSalesOfFixedAssets-gold Right Gold-governmentservice Right GovernmentService-grossprofit Right GrossProfit-interestearned Right InterestEarned-interestexpense Right InterestExpense-investmentinassociate Right InvestmentInAssociate-investmentsecurities Right InvestmentSecurities-land Right Land-legalretainedearnings Right LegalRetainedEarnings-loanspayable Right LoansPayable-longtermloanspayable Right LongTermLoansPayable-longtermloansreceivable Right LongTermLoansReceivable-longtermnationalbonds Right LongTermNationalBonds-longtermnationalbondspayable Right LongTermNationalBondsPayable-lossonsalesoffixedassets Right LossOnSalesOfFixedAssets-lossonsalesofnotesreceivable Right LossOnSalesOfNotesReceivable-machinery Right Machinery-merchandiseinventory Right MerchandiseInventory-miscellaneousexpenses Right MiscellaneousExpenses-miscellaneousincome Right MiscellaneousIncome-miscellaneousloss Right MiscellaneousLoss-nationalbondinterestearned Right NationalBondInterestEarned-netincome Right NetIncome-netloss Right NetLoss-notesloanspayable Right NotesLoansPayable-notesloansreceivable Right NotesLoansReceivable-notespayable Right NotesPayable-notesreceivable Right NotesReceivable-ordinaryprofit Right OrdinaryProfit-otherpayables Right OtherPayables-otherreceivables Right OtherReceivables-patent Right Patent-paymentfees Right PaymentFees-paymentsonbehalf Right PaymentsOnBehalf-pettycash Right PettyCash-prepaidcorporateincometaxes Right PrepaidCorporateIncomeTaxes-prepaidexpenses Right PrepaidExpenses-products Right Products-provisionfordoubtfulaccounts Right ProvisionForDoubtfulAccounts-purchases Right Purchases-receiptfee Right ReceiptFee-recoveryofbaddebts Right RecoveryOfBadDebts-rentalincome Right RentalIncome-rentexpense Right RentExpense-reservedepositpayable Right ReserveDepositPayable-reservedepositreceivable Right ReserveDepositReceivable-reservefordepreciation Right ReserveForDepreciation-retainedearnings Right RetainedEarnings-reversalofallowancefordoubtfulaccounts Right ReversalOfAllowanceForDoubtfulAccounts-sales Right Sales-salescost Right SalesCost-securities Right Securities-shorttermloanspayable Right ShortTermLoansPayable-shorttermloansreceivable Right ShortTermLoansReceivable-shorttermnationalbonds Right ShortTermNationalBonds-shorttermnationalbondspayable Right ShortTermNationalBondsPayable-software Right Software-stockinvestment Right StockInvestment-subsidyexpense Right SubsidyExpense-subsidyincome Right SubsidyIncome-suppliesexpenses Right SuppliesExpenses-suspensepayments Right SuspensePayments-suspensereceipts Right SuspenseReceipts-taxesexpense Right TaxesExpense-taxesrevenue Right TaxesRevenue-trademark Right Trademark-unearnedrevenue Right UnearnedRevenue-unpaiddividends Right UnpaidDividends-utilitiesexpense Right UtilitiesExpense-valueadded Right ValueAdded-vehicle Right Vehicle-wageearned Right WageEarned-wageexpenditure Right WageExpenditure-クレジット売掛金 Right CreditCardReceivable-ソフトウェア Right Software-仕入 Right Purchases-付加価値 Right ValueAdded-仮受消費税 Right ConsumptionTaxReceived-仮受金 Right SuspenseReceipts-仮払法人税等 Right PrepaidCorporateIncomeTaxes-仮払消費税 Right ConsumptionTaxPaid-仮払金 Right SuspensePayments-借入金 Right LoansPayable-備品 Right Fixtures-償却債権取立益 Right RecoveryOfBadDebts-利益準備金 Right LegalRetainedEarnings-前受収益 Right UnearnedRevenue-前受金 Right AdvancesReceived-前払費用 Right PrepaidExpenses-前払金 Right AdvancesPaid-受入預金 Right DepositPayable-受取利息 Right InterestEarned-受取家賃 Right RentalIncome-受取手形 Right NotesReceivable-受取手数料 Right ReceiptFee-商品 Right Products-商標権 Right Trademark-固定資産売却損 Right LossOnSalesOfFixedAssets-固定資産売却益 Right GainOnSalesOfFixedAssets-国債利息収入 Right NationalBondInterestEarned-国庫納付金収入 Right CentralBankPaymentIncome-国庫納付金支出 Right CentralBankPaymentExpense-土地 Right Land-売上 Right Sales-売上原価 Right SalesCost-売上総利益 Right GrossProfit-売掛金 Right AccountsReceivable-小口現金 Right PettyCash-広告宣伝費 Right AdvertisingExpense-建物 Right Building-当座借越 Right BankOverdraft-当座預金 Right CurrentDeposits-当期純利益 Right NetIncome-当期純損失 Right NetLoss-手形借入金 Right NotesLoansPayable-手形売却損 Right LossOnSalesOfNotesReceivable-手形貸付金 Right NotesLoansReceivable-投資有価証券 Right InvestmentSecurities-支払利息 Right InterestExpense-支払家賃 Right RentExpense-支払手形 Right NotesPayable-支払手数料 Right PaymentFees-政府サービス Right GovernmentService-旅費交通費 Right BusinessTrip-普通預金 Right Deposits-有価証券 Right Securities-未収入金 Right OtherReceivables-未収収益 Right AccruedRevenue-未払法人税等 Right AccruedCorporateIncomeTaxes-未払消費税 Right AccruedConsumptionTax-未払費用 Right AccruedExpenses-未払配当金 Right UnpaidDividends-未払金 Right OtherPayables-株式投資 Right StockInvestment-機械装置 Right Machinery-水道光熱費 Right UtilitiesExpense-法人税等 Right CorporateIncomeTaxes-消耗品費 Right SuppliesExpenses-消費支出 Right ConsumptionExpenditure-減価償却引当金 Right ReserveForDepreciation-減価償却累計額 Right AccumulatedDepreciation-減価償却費 Right Depreciation-準備預金 Left (AmbiguousAccount "\28310\20633\38928\37329" [ReserveDepositReceivable,ReserveDepositPayable])-特許権 Right Patent-現金 Right Cash-現金過不足 Right CashOverShort-発行銀行券 Right CentralBankNotePayable-発送費 Right DeliveryExpenses-短期借入金 Right ShortTermLoansPayable-短期国債 Left (AmbiguousAccount "\30701\26399\22269\20661" [ShortTermNationalBonds,ShortTermNationalBondsPayable])-短期貸付金 Right ShortTermLoansReceivable-社債発行差金 Left (UnknownAccount "\31038\20661\30330\34892\24046\37329")-租税公課 Right TaxesExpense-租税収入 Right TaxesRevenue-立替金 Right PaymentsOnBehalf-経常利益 Right OrdinaryProfit-給料 Right WageExpenditure-繰越利益剰余金 Right RetainedEarnings-繰越商品 Right MerchandiseInventory-自己株式 Left (UnknownAccount "\33258\24049\26666\24335")-補助金収入 Right SubsidyIncome-補助金支出 Right SubsidyExpense-設備投資 Right EquipmentInvestment-買掛金 Right AccountsPayable-貸倒引当金 Right AllowanceForDoubtfulAccounts-貸倒引当金戻入 Right ReversalOfAllowanceForDoubtfulAccounts-貸倒引当金繰入 Right ProvisionForDoubtfulAccounts-貸倒損失 Right BadDebtLoss-賃金収入 Right WageEarned-資本金 Right CapitalStock-車両運搬具 Right Vehicle-通信費 Left (AmbiguousAccount "\36890\20449\36027" [CommunicationExpenses,Commutation])-金 Right Gold-長期借入金 Right LongTermLoansPayable-長期国債 Left (AmbiguousAccount "\38263\26399\22269\20661" [LongTermNationalBonds,LongTermNationalBondsPayable])-長期貸付金 Right LongTermLoansReceivable-雑損 Right MiscellaneousLoss-雑益 Right MiscellaneousIncome-雑費 Right MiscellaneousExpenses-電子記録債務 Right ElectronicallyRecordedObligations-電子記録債権 Right ElectronicallyRecordedReceivable-預り金 Right DepositsReceived-預金利息収入 Right DepositInterestEarned
− test/fixtures/pre-land1/suggest.tsv
@@ -1,972 +0,0 @@-# pre-land1 suggestAccounts over corpus (query, total matches, top-10 titles); commit 2d9164642f2862725c653e496976770f6e2c7d6f-"Communication"); 1 CommunicationExpenses-'AccountsPayable' 1 ReserveDepositPayable-'AccumulatedDepreciation' 1 ReserveForDepreciation-'AllowanceForDoubtfulAccounts' 2 AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-'AllowanceForDoubtfulAccounts'. 1 AccumulatedDepreciation-'CommunicationExpenses' 1 Commutation-'Commutation' 1 CommunicationExpenses-'DepositsReceived') 1 DepositPayable-'Depreciation' 1 AmortizationExpense-'MerchandiseInventory' 1 Products-'PaymentFees') 1 ReceiptFee-'Products' 1 MerchandiseInventory-'ReceiptFee' 1 PaymentFees-'ReserveForDepreciation' 1 AccumulatedDepreciation-(3 1 MerchandiseInventory-(also 1 CommunicationExpenses-(appropriated) 1 LegalRetainedEarnings-(deduction) 1 AllowanceForDoubtfulAccounts-(over-engineered 1 AllowanceForDoubtfulAccounts-(the 1 ReversalOfAllowanceForDoubtfulAccounts-(unpaid) 2 AccruedConsumptionTax,AccruedCorporateIncomeTaxes-(クレジット売掛金) 1 CreditCardReceivable-(ソフトウェア) 1 Software-(仕入) 1 Purchases-(仕入\/売上\/繰越商品) 1 Products-(付加価値。SNA\/マクロ系) 1 ValueAdded-(仮受消費税) 1 ConsumptionTaxReceived-(仮受金) 1 SuspenseReceipts-(仮払法人税等) 1 PrepaidCorporateIncomeTaxes-(仮払消費税) 1 ConsumptionTaxPaid-(仮払金) 1 SuspensePayments-(借入金) 1 LoansPayable-(備品) 1 Fixtures-(償却債権取立益) 1 RecoveryOfBadDebts-(分記法) 1 MerchandiseInventory-(利益準備金) 1 LegalRetainedEarnings-(前受収益), 1 UnearnedRevenue-(前受金) 1 AdvancesReceived-(前払費用), 1 PrepaidExpenses-(前払金) 1 AdvancesPaid-(受入預金 1 DepositPayable-(受取利息) 1 InterestEarned-(受取家賃) 1 RentalIncome-(受取手形) 1 NotesReceivable-(受取手数料。支払側は 1 ReceiptFee-(商品)。分記法用。3 1 Products-(商標権) 1 Trademark-(固定資産売却損) 1 LossOnSalesOfFixedAssets-(固定資産売却益) 1 GainOnSalesOfFixedAssets-(国債利息収入) 1 NationalBondInterestEarned-(国庫納付金収入。SNA\/マクロ系) 1 CentralBankPaymentIncome-(国庫納付金支出。SNA\/マクロ系) 1 CentralBankPaymentExpense-(土地) 1 Land-(売上) 1 Sales-(売上原価) 1 SalesCost-(売上総利益 1 GrossProfit-(売掛金) 1 AccountsReceivable-(小口現金) 1 PettyCash-(広告宣伝費) 1 AdvertisingExpense-(建物) 1 Building-(当座借越) 1 BankOverdraft-(当座預金) 1 CurrentDeposits-(当期純利益 1 NetIncome-(当期純損失 1 NetLoss-(手形借入金) 1 NotesLoansPayable-(手形売却損) 1 LossOnSalesOfNotesReceivable-(手形貸付金) 1 NotesLoansReceivable-(投資有価証券) 1 InvestmentSecurities-(持分法による投資利益). 1 EquityInEarningsOfInvestee-(持分法適用投資勘定). 1 InvestmentInAssociate-(支払利息) 1 InterestExpense-(支払家賃) 1 RentExpense-(支払手形) 1 NotesPayable-(支払手数料), 1 PaymentFees-(政府サービス。SNA\/マクロ系) 1 GovernmentService-(旅費交通費) 1 BusinessTrip-(旧 1 ReserveDepositPayable-(普通預金) 1 Deposits-(有価証券) 1 Securities-(未収入金) 1 OtherReceivables-(未収収益), 1 AccruedRevenue-(未払法人税等) 1 AccruedCorporateIncomeTaxes-(未払消費税) 1 AccruedConsumptionTax-(未払費用), 1 AccruedExpenses-(未払配当金) 1 UnpaidDividends-(未払金) 1 OtherPayables-(株式投資) 1 StockInvestment-(機械装置) 1 Machinery-(水道光熱費) 1 UtilitiesExpense-(法人税等) 1 CorporateIncomeTaxes-(消耗品費) 1 SuppliesExpenses-(消費支出。SNA\/マクロ系) 1 ConsumptionExpenditure-(減価償却引当金 1 ReserveForDepreciation-(減価償却累計額) 2 ReserveForDepreciation,AccumulatedDepreciation-(減価償却累計額), 1 AccumulatedDepreciation-(減価償却費) 2 Depreciation,AmortizationExpense-(準備預金, 2 ReserveDepositReceivable,ReserveDepositPayable-(為替換算調整勘定; 1 CumulativeTranslationAdjustment-(無形固定資産償却費 1 AmortizationExpense-(特許権) 1 Patent-(現金) 1 Cash-(現金過不足), 1 CashOverShort-(発行銀行券。SNA\/マクロ系) 1 CentralBankNotePayable-(発送費) 1 DeliveryExpenses-(短期借入金) 1 ShortTermLoansPayable-(短期国債) 1 ShortTermNationalBonds-(短期国債, 1 ShortTermNationalBondsPayable-(短期貸付金) 1 ShortTermLoansReceivable-(租税公課) 1 TaxesExpense-(租税収入。SNA\/マクロ系) 1 TaxesRevenue-(立替金) 1 PaymentsOnBehalf-(経常利益 1 OrdinaryProfit-(経過勘定) 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-(給料) 1 WageExpenditure-(繰越利益剰余金) 1 RetainedEarnings-(繰越商品). 1 MerchandiseInventory-(補助金収入。SNA\/マクロ系) 1 SubsidyIncome-(補助金支出。SNA\/マクロ系) 1 SubsidyExpense-(設備投資) 1 EquipmentInvestment-(評価勘定) 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-(買掛金) 1 AccountsPayable-(貸倒引当金). 1 AllowanceForDoubtfulAccounts-(貸倒引当金戻入). 1 ReversalOfAllowanceForDoubtfulAccounts-(貸倒引当金繰入) 1 ProvisionForDoubtfulAccounts-(貸倒損失) 1 BadDebtLoss-(賃金収入。SNA\/マクロ系) 1 WageEarned-(資本金) 1 CapitalStock-(車両運搬具) 1 Vehicle-(通信費 2 Commutation,CommunicationExpenses-(通信費). 1 CommunicationExpenses-(金) 1 Gold-(長期借入金) 1 LongTermLoansPayable-(長期国債) 1 LongTermNationalBonds-(長期国債, 1 LongTermNationalBondsPayable-(長期貸付金) 1 LongTermLoansReceivable-(間接法). 1 AccumulatedDepreciation-(関係会社株式). 1 InvestmentInAssociate-(雑損) 1 MiscellaneousLoss-(雑益) 1 MiscellaneousIncome-(雑費) 1 MiscellaneousExpenses-(電子記録債務) 1 ElectronicallyRecordedObligations-(電子記録債権) 1 ElectronicallyRecordedReceivable-(預り金) 1 DepositsReceived-(預金利息収入) 1 DepositInterestEarned-/ 22 Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,CentralBankNotePayable,ConsumptionExpenditure,SubsidyExpense,CentralBankPaymentExpense-3-級 1 AllowanceForDoubtfulAccounts-AccountDivision 1 AllowanceForDoubtfulAccounts-Accounts 7 AccountsReceivable,ReserveDepositPayable,AccountsPayable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-Accounts payable 17 ReserveDepositPayable,AccountsPayable,AccountsReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable-Accounts receivable 16 AccountsReceivable,LongTermLoansReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,ReserveDepositPayable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable,OtherReceivables-AccountsPayable 2 ReserveDepositPayable,AccountsPayable-AccountsReceivable 1 AccountsReceivable-Accrued 4 AccruedRevenue,AccruedExpenses,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-Accrued (unpaid) consumption tax 11 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes,AccruedExpenses-Accrued (unpaid) corporate income taxes 15 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue-Accrued expenses 10 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-Accrued revenue 24 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-AccruedConsumptionTax 1 AccruedConsumptionTax-AccruedCorporateIncomeTaxes 1 AccruedCorporateIncomeTaxes-AccruedExpenses 1 AccruedExpenses-AccruedRevenue 1 AccruedRevenue-Accumulated 2 ReserveForDepreciation,AccumulatedDepreciation-Accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense-AccumulatedDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-Advances 2 AdvancesPaid,AdvancesReceived-Advances paid 9 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-Advances received 5 AdvancesReceived,DepositPayable,AdvancesPaid,DepositsReceived,ConsumptionTaxReceived-AdvancesPaid 1 AdvancesPaid-AdvancesReceived 1 AdvancesReceived-Advertising 1 AdvertisingExpense-AdvertisingExpense 1 AdvertisingExpense-Allowance 3 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-Allowance for doubtful accounts 11 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable-AllowanceForDoubtfulAccounts 3 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-Amortization 1 AmortizationExpense-Amortization expense for intangibles 28 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-AmortizationExpense 1 AmortizationExpense-Asset: 40 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-B\/S 1 AllowanceForDoubtfulAccounts-Bad 2 BadDebtLoss,RecoveryOfBadDebts-Bad debt loss 7 BadDebtLoss,RecoveryOfBadDebts,NetLoss,CashOverShort,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-BadDebtLoss 1 BadDebtLoss-Bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft-Bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-BankOverdraft 1 BankOverdraft-Building 1 Building-BusinessTrip 1 BusinessTrip-Capital 1 CapitalStock-Capital stock 2 CapitalStock,StockInvestment-CapitalStock 1 CapitalStock-Carrying 1 InvestmentInAssociate-Cash 3 Cash,PettyCash,CashOverShort-Cash over and short 17 CashOverShort,AllowanceForDoubtfulAccounts,Cash,ShortTermNationalBonds,Products,Machinery,ShortTermLoansReceivable,GovernmentService,ShortTermLoansPayable,ShortTermNationalBondsPayable-CashOverShort 1 CashOverShort-Central 3 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-Central bank notes 9 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,BankOverdraft,LossOnSalesOfNotesReceivable-Central bank payment to treasury 14 CentralBankPaymentExpense,CentralBankPaymentIncome,CentralBankNotePayable,Products,StockInvestment,CapitalStock,ReceiptFee,MerchandiseInventory,PaymentsOnBehalf,SuspensePayments-CentralBankNotePayable 1 CentralBankNotePayable-CentralBankPaymentExpense 1 CentralBankPaymentExpense-CentralBankPaymentIncome 1 CentralBankPaymentIncome-Classified 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-Communication 2 Commutation,CommunicationExpenses-Communication expenses 7 Commutation,CommunicationExpenses,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses-CommunicationExpenses 2 Commutation,CommunicationExpenses-Commutation 2 Commutation,CommunicationExpenses-Consumption 4 ConsumptionExpenditure,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-Consumption expenditure 6 ConsumptionExpenditure,WageExpenditure,SubsidyExpense,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-Consumption tax paid 13 ConsumptionTaxPaid,AccruedConsumptionTax,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedCorporateIncomeTaxes,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AdvancesPaid,PrepaidExpenses-Consumption tax received 12 ConsumptionTaxReceived,ConsumptionTaxPaid,AccruedConsumptionTax,DepositPayable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,PrepaidCorporateIncomeTaxes,AdvancesReceived,DepositsReceived-ConsumptionExpenditure 1 ConsumptionExpenditure-ConsumptionTaxPaid 1 ConsumptionTaxPaid-ConsumptionTaxReceived 1 ConsumptionTaxReceived-Corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Corporate income taxes 12 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-CorporateIncomeTaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Cost 9 SalesCost,ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses-Cost of sales 18 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets,GrossProfit,OrdinaryProfit,Sales,EquityInEarningsOfInvestee-Cost: 8 ProvisionForDoubtfulAccounts,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,MiscellaneousLoss,CorporateIncomeTaxes,CommunicationExpenses-Credit 3 CreditCardReceivable,AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-Credit card receivable 12 CreditCardReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables,AllowanceForDoubtfulAccounts-CreditCardReceivable 1 CreditCardReceivable-Cumulative 1 CumulativeTranslationAdjustment-Cumulative translation adjustment 1 CumulativeTranslationAdjustment-CumulativeTranslationAdjustment 1 CumulativeTranslationAdjustment-Current 1 CurrentDeposits-Current deposits 6 CurrentDeposits,Deposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-CurrentDeposits 1 CurrentDeposits-Delivery 1 DeliveryExpenses-DeliveryExpenses 1 DeliveryExpenses-Deposit 7 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositInterestEarned,DepositsReceived-Deposit interest earned 12 DepositInterestEarned,NationalBondInterestEarned,InterestEarned,Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,InterestExpense,WageEarned-DepositInterestEarned 1 DepositInterestEarned-DepositPayable 2 DepositPayable,ReserveDepositPayable-Deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-Deposits accepted 6 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived-Deposits received 8 DepositPayable,DepositsReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived-DepositsReceived 2 DepositPayable,DepositsReceived-Depreciation 4 ReserveForDepreciation,Depreciation,AmortizationExpense,AccumulatedDepreciation-Electronically 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-Electronically recorded monetary claims 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-Electronically recorded monetary obligations 2 ElectronicallyRecordedObligations,ElectronicallyRecordedReceivable-ElectronicallyRecordedObligations 1 ElectronicallyRecordedObligations-ElectronicallyRecordedReceivable 1 ElectronicallyRecordedReceivable-Equipment 3 Machinery,EquipmentInvestment,Fixtures-Equipment investment 6 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures-EquipmentInvestment 1 EquipmentInvestment-Equity 6 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment-Equity in earnings of investee 44 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,Deposits,InvestmentSecurities-Equity: 4 CapitalStock,RetainedEarnings,LegalRetainedEarnings,CumulativeTranslationAdjustment-EquityInEarningsOfInvestee 1 EquityInEarningsOfInvestee-Expense 22 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-Expense: 19 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-Explicitly 1 CommunicationExpenses-Fixtures 1 Fixtures-Fixtures and equipment 9 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-For 8 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-Gain 1 GainOnSalesOfFixedAssets-Gain on sales of fixed assets 39 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable-GainOnSalesOfFixedAssets 1 GainOnSalesOfFixedAssets-Gold 1 Gold-Government 1 GovernmentService-Government service 1 GovernmentService-GovernmentService 1 GovernmentService-Gross 1 GrossProfit-Gross profit 2 GrossProfit,OrdinaryProfit-GrossProfit 1 GrossProfit-Hat\/Not 1 AllowanceForDoubtfulAccounts-Interest 4 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned-Interest earned 6 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-Interest expense 25 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-InterestEarned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned-InterestExpense 1 InterestExpense-Investment 4 InvestmentSecurities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-Investment in associate 35 InvestmentInAssociate,InvestmentSecurities,StockInvestment,EquipmentInvestment,Deposits,Products,Machinery,Building,RetainedEarnings,AmortizationExpense-Investment securities 5 InvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-InvestmentInAssociate 1 InvestmentInAssociate-InvestmentSecurities 1 InvestmentSecurities-Land 1 Land-Legal 1 LegalRetainedEarnings-Legal (appropriated) retained earnings reserve 8 LegalRetainedEarnings,RetainedEarnings,AccumulatedDepreciation,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,EquityInEarningsOfInvestee,CommunicationExpenses-LegalRetainedEarnings 1 LegalRetainedEarnings-Liability 26 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable-Liability: 26 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable-Loans 7 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable-Loans payable 15 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Loans payable on notes 40 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable,NotesPayable-Loans receivable on notes 40 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable,LongTermNationalBonds,ShortTermNationalBonds-LoansPayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-Long-term 4 LongTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,LongTermNationalBondsPayable-Long-term loans payable 16 LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,NotesLoansPayable,LongTermNationalBonds,ShortTermLoansReceivable,DepositPayable,ShortTermNationalBondsPayable-Long-term loans receivable 16 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesLoansReceivable,LongTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable-Long-term national bonds 7 LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermLoansReceivable,LongTermLoansPayable,NationalBondInterestEarned-Long-term national bonds payable 16 LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-LongTermLoansPayable 1 LongTermLoansPayable-LongTermLoansReceivable 1 LongTermLoansReceivable-LongTermNationalBonds 2 LongTermNationalBonds,LongTermNationalBondsPayable-LongTermNationalBondsPayable 1 LongTermNationalBondsPayable-Loss 6 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-Loss on sales of fixed assets 43 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable-Loss on sales of notes receivable 51 LossOnSalesOfNotesReceivable,LossOnSalesOfFixedAssets,NotesLoansReceivable,GainOnSalesOfFixedAssets,LongTermLoansReceivable,SalesCost,NotesReceivable,ElectronicallyRecordedReceivable,MerchandiseInventory,NotesLoansPayable-LossOnSalesOfFixedAssets 1 LossOnSalesOfFixedAssets-LossOnSalesOfNotesReceivable 1 LossOnSalesOfNotesReceivable-Machinery 1 Machinery-Machinery and equipment 9 Machinery,Fixtures,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-Merchandise 2 Products,MerchandiseInventory-Merchandise inventory 2 Products,MerchandiseInventory-MerchandiseInventory 2 Products,MerchandiseInventory-Miscellaneous 4 MiscellaneousExpenses,CashOverShort,MiscellaneousLoss,MiscellaneousIncome-Miscellaneous income 12 CashOverShort,MiscellaneousIncome,MiscellaneousExpenses,NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes-Miscellaneous loss 8 CashOverShort,MiscellaneousLoss,MiscellaneousExpenses,NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousIncome-MiscellaneousExpenses 1 MiscellaneousExpenses-MiscellaneousIncome 2 CashOverShort,MiscellaneousIncome-MiscellaneousIncome\/MiscellaneousLoss 1 CashOverShort-MiscellaneousLoss 2 CashOverShort,MiscellaneousLoss-Modeled 1 AllowanceForDoubtfulAccounts-National 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-National bond interest earned 10 NationalBondInterestEarned,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-NationalBondInterestEarned 1 NationalBondInterestEarned-Net 4 NetIncome,NetLoss,ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-Net income 13 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,NetLoss,ElectronicallyRecordedReceivable,PrepaidCorporateIncomeTaxes,CashOverShort,ElectronicallyRecordedObligations-Net loss 9 NetLoss,NetIncome,ElectronicallyRecordedReceivable,CashOverShort,ElectronicallyRecordedObligations,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-NetIncome 1 NetIncome-NetLoss 1 NetLoss-Notes 6 CentralBankNotePayable,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,LossOnSalesOfNotesReceivable-Notes payable 15 CentralBankNotePayable,NotesPayable,NotesLoansPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-Notes receivable 13 NotesReceivable,NotesLoansReceivable,LossOnSalesOfNotesReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,CentralBankNotePayable,ElectronicallyRecordedReceivable,CreditCardReceivable-NotesLoansPayable 1 NotesLoansPayable-NotesLoansReceivable 1 NotesLoansReceivable-NotesPayable 1 NotesPayable-NotesReceivable 2 NotesReceivable,LossOnSalesOfNotesReceivable-OCI/資本の部の項目) 1 CumulativeTranslationAdjustment-Ordinary 1 OrdinaryProfit-Ordinary profit 2 OrdinaryProfit,GrossProfit-OrdinaryProfit 1 OrdinaryProfit-Other 2 OtherReceivables,OtherPayables-Other payables 2 OtherPayables,OtherReceivables-Other receivables 2 OtherReceivables,OtherPayables-OtherPayables 1 OtherPayables-OtherReceivables 1 OtherReceivables-Patent 1 Patent-Payment 6 CentralBankPaymentExpense,ReceiptFee,CentralBankPaymentIncome,PaymentsOnBehalf,SuspensePayments,PaymentFees-Payment fees / fees paid 32 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable-PaymentFees 2 ReceiptFee,PaymentFees-Payments 2 PaymentsOnBehalf,SuspensePayments-Payments made on behalf 31 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense-PaymentsOnBehalf 1 PaymentsOnBehalf-Petty 1 PettyCash-Petty cash 3 PettyCash,Cash,CashOverShort-PettyCash 1 PettyCash-Prepaid 2 PrepaidExpenses,PrepaidCorporateIncomeTaxes-Prepaid corporate income taxes 13 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-Prepaid expenses 8 PrepaidExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidCorporateIncomeTaxes,AccruedExpenses,CommunicationExpenses-PrepaidCorporateIncomeTaxes 1 PrepaidCorporateIncomeTaxes-PrepaidExpenses 1 PrepaidExpenses-Products 2 Products,MerchandiseInventory-Provision 1 ProvisionForDoubtfulAccounts-Provision for doubtful accounts 11 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable-ProvisionForDoubtfulAccounts 1 ProvisionForDoubtfulAccounts-Purchases 2 Purchases,MerchandiseInventory-Purchases\/Sales\/MerchandiseInventory). 1 MerchandiseInventory-Real 1 Building-Real estate 1 Building-Receipt 3 ReceiptFee,SuspenseReceipts,PaymentFees-Receipt fee 3 ReceiptFee,PaymentFees,SuspenseReceipts-ReceiptFee 2 ReceiptFee,PaymentFees-Recognised 1 EquityInEarningsOfInvestee-Recovery 1 RecoveryOfBadDebts-Recovery of bad debts written off 13 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,BadDebtLoss,LossOnSalesOfNotesReceivable-RecoveryOfBadDebts 1 RecoveryOfBadDebts-Rent 3 CurrentDeposits,RentExpense,RentalIncome-RentExpense 1 RentExpense-Rental 1 RentalIncome-Rental income 10 RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-RentalIncome 1 RentalIncome-Reserve 5 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings-Reserve deposits, asset side 47 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds-Reserve deposits, liability side 28 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable-Reserve for depreciation 12 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory,AllowanceForDoubtfulAccounts,LegalRetainedEarnings,ProvisionForDoubtfulAccounts-ReserveDepositPayable 1 ReserveDepositPayable-ReserveDepositReceivable 1 ReserveDepositReceivable-ReserveForDepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-Retained 4 RetainedEarnings,AccumulatedDepreciation,LegalRetainedEarnings,CommunicationExpenses-Retained earnings 5 RetainedEarnings,LegalRetainedEarnings,EquityInEarningsOfInvestee,AccumulatedDepreciation,CommunicationExpenses-RetainedEarnings 2 RetainedEarnings,LegalRetainedEarnings-Revenue 21 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Revenue: 19 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-Reversal 1 ReversalOfAllowanceForDoubtfulAccounts-Reversal of allowance for doubtful accounts 21 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,SalesCost-ReversalOfAllowanceForDoubtfulAccounts 1 ReversalOfAllowanceForDoubtfulAccounts-SNA\/macro-accounting 1 AccumulatedDepreciation-SNA\/マクロ系の旧称。簿記の間接法には 1 ReserveForDepreciation-Sales 6 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets-SalesCost 1 SalesCost-Savings 1 Deposits-Savings deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-Securities 2 Securities,InvestmentSecurities-Short-term 4 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable-Short-term loans payable 16 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable-Short-term loans receivable 16 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable-Short-term national bonds 7 ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermLoansReceivable,ShortTermLoansPayable,NationalBondInterestEarned-Short-term national bonds payable 16 ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-ShortTermLoansPayable 1 ShortTermLoansPayable-ShortTermLoansReceivable 1 ShortTermLoansReceivable-ShortTermNationalBonds 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-ShortTermNationalBondsPayable 1 ShortTermNationalBondsPayable-Software 1 Software-Stock 2 StockInvestment,CapitalStock-Stock investment 5 StockInvestment,InvestmentSecurities,InvestmentInAssociate,EquipmentInvestment,CapitalStock-StockInvestment 1 StockInvestment-Subsidy 2 SubsidyExpense,SubsidyIncome-Subsidy expenditure 4 SubsidyExpense,WageExpenditure,ConsumptionExpenditure,SubsidyIncome-Subsidy income 11 SubsidyIncome,SubsidyExpense,NetIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-SubsidyExpense 1 SubsidyExpense-SubsidyIncome 1 SubsidyIncome-Supplies 1 SuppliesExpenses-SuppliesExpenses 1 SuppliesExpenses-Suspense 3 SuspensePayments,CashOverShort,SuspenseReceipts-Suspense payments 4 SuspensePayments,PaymentsOnBehalf,CashOverShort,SuspenseReceipts-Suspense receipts 3 SuspenseReceipts,SuspensePayments,CashOverShort-SuspensePayments 1 SuspensePayments-SuspenseReceipts 1 SuspenseReceipts-Tax 8 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-Tax revenue 28 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee-Taxes 5 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-TaxesExpense 1 TaxesExpense-TaxesRevenue 1 TaxesRevenue-This 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-Trademark 1 Trademark-Travel 1 BusinessTrip-Travel and transportation 8 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-Unearned 1 UnearnedRevenue-Unearned revenue 21 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-UnearnedRevenue 1 UnearnedRevenue-Unpaid 3 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends-Unpaid dividends 3 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-UnpaidDividends 1 UnpaidDividends-Use 3 MiscellaneousExpenses,MerchandiseInventory,ReversalOfAllowanceForDoubtfulAccounts-Utilities 1 UtilitiesExpense-UtilitiesExpense 1 UtilitiesExpense-Value 2 ValueAdded,AllowanceForDoubtfulAccounts-Value added 2 ValueAdded,AllowanceForDoubtfulAccounts-ValueAdded 1 ValueAdded-Vehicle 1 Vehicle-Vehicles 1 Vehicle-Wage 2 WageExpenditure,WageEarned-Wage income 11 WageEarned,WageExpenditure,NetIncome,SubsidyIncome,RentalIncome,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-WageEarned 1 WageEarned-WageExpenditure 1 WageExpenditure-Wages 1 WageExpenditure-Write 1 AllowanceForDoubtfulAccounts-a 108 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-accepted 1 DepositPayable-account 13 AccountsReceivable,ReserveDepositPayable,MerchandiseInventory,PrepaidExpenses,AccruedRevenue,CashOverShort,AccountsPayable,UnearnedRevenue,AccruedExpenses,AllowanceForDoubtfulAccounts-accounts 7 AccountsReceivable,ReserveDepositPayable,AccountsPayable,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-accounts payable 17 ReserveDepositPayable,AccountsPayable,AccountsReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable-accounts receivable 16 AccountsReceivable,LongTermLoansReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,ReserveDepositPayable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable,OtherReceivables-accountspayable 2 ReserveDepositPayable,AccountsPayable-accountsreceivable 1 AccountsReceivable-accrual 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-accrued (unpaid) consumption tax 11 AccruedConsumptionTax,AccruedCorporateIncomeTaxes,ConsumptionTaxPaid,ConsumptionTaxReceived,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AccruedRevenue,PrepaidCorporateIncomeTaxes,AccruedExpenses-accrued (unpaid) corporate income taxes 15 AccruedCorporateIncomeTaxes,PrepaidCorporateIncomeTaxes,CorporateIncomeTaxes,AccruedConsumptionTax,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue-accrued expenses 10 AccruedExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedRevenue,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CommunicationExpenses-accrued revenue 24 AccruedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-accruedconsumptiontax 1 AccruedConsumptionTax-accruedcorporateincometaxes 1 AccruedCorporateIncomeTaxes-accruedexpenses 1 AccruedExpenses-accruedrevenue 1 AccruedRevenue-accumulated 2 ReserveForDepreciation,AccumulatedDepreciation-accumulated depreciation 4 ReserveForDepreciation,AccumulatedDepreciation,Depreciation,AmortizationExpense-accumulateddepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-added 1 ValueAdded-adding 1 AllowanceForDoubtfulAccounts-adjustment 1 CumulativeTranslationAdjustment-advances paid 9 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AdvancesReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-advances received 5 AdvancesReceived,DepositPayable,AdvancesPaid,DepositsReceived,ConsumptionTaxReceived-advancespaid 1 AdvancesPaid-advancesreceived 1 AdvancesReceived-advertising 1 AdvertisingExpense-advertisingexpense 1 AdvertisingExpense-allowance 3 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-allowance for doubtful accounts 11 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable-allowancefordoubtfulaccounts 3 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-amortization expense for intangibles 28 AmortizationExpense,CommunicationExpenses,ReserveForDepreciation,Depreciation,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense-amortizationexpense 1 AmortizationExpense-amount 1 InvestmentInAssociate-an 28 Products,Machinery,LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,CentralBankNotePayable,AmortizationExpense,BusinessTrip-and 8 Products,Machinery,BusinessTrip,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-as 48 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-asset 43 Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Products,Machinery-assets 2 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets-associate 1 InvestmentInAssociate-at 25 InvestmentInAssociate,LongTermNationalBonds,ShortTermNationalBonds,Building,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip-backward 1 CommunicationExpenses-bad 2 BadDebtLoss,RecoveryOfBadDebts-bad debt loss 7 BadDebtLoss,RecoveryOfBadDebts,NetLoss,CashOverShort,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-baddebtloss 1 BadDebtLoss-balance 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-bank 4 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,BankOverdraft-bank overdraft 4 BankOverdraft,CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome-bankoverdraft 1 BankOverdraft-behalf 1 PaymentsOnBehalf-bond 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-bonds 4 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable-bookkeeping 1 AccumulatedDepreciation-building 1 Building-businesstrip 1 BusinessTrip-by 1 ReversalOfAllowanceForDoubtfulAccounts-canonical 1 AccumulatedDepreciation-capital stock 2 CapitalStock,StockInvestment-capitalstock 1 CapitalStock-card 1 CreditCardReceivable-cash 3 Cash,PettyCash,CashOverShort-cash over and short 17 CashOverShort,AllowanceForDoubtfulAccounts,Cash,ShortTermNationalBonds,Products,Machinery,ShortTermLoansReceivable,GovernmentService,ShortTermLoansPayable,ShortTermNationalBondsPayable-cashovershort 1 CashOverShort-central bank notes 9 CentralBankNotePayable,CentralBankPaymentExpense,CentralBankPaymentIncome,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,BankOverdraft,LossOnSalesOfNotesReceivable-central bank payment to treasury 14 CentralBankPaymentExpense,CentralBankPaymentIncome,CentralBankNotePayable,Products,StockInvestment,CapitalStock,ReceiptFee,MerchandiseInventory,PaymentsOnBehalf,SuspensePayments-centralbanknotepayable 1 CentralBankNotePayable-centralbankpaymentexpense 1 CentralBankPaymentExpense-centralbankpaymentincome 1 CentralBankPaymentIncome-claims 1 ElectronicallyRecordedReceivable-classified 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-cleared 1 CashOverShort-closing 1 CashOverShort-communication 2 Commutation,CommunicationExpenses-communication expenses 7 Commutation,CommunicationExpenses,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses-communicationexpenses 2 Commutation,CommunicationExpenses-commutation 2 Commutation,CommunicationExpenses-compatibility. 1 CommunicationExpenses-consumption 4 ConsumptionExpenditure,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-consumption expenditure 6 ConsumptionExpenditure,WageExpenditure,SubsidyExpense,ConsumptionTaxPaid,ConsumptionTaxReceived,AccruedConsumptionTax-consumption tax paid 13 ConsumptionTaxPaid,AccruedConsumptionTax,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedCorporateIncomeTaxes,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,AdvancesPaid,PrepaidExpenses-consumption tax received 12 ConsumptionTaxReceived,ConsumptionTaxPaid,AccruedConsumptionTax,DepositPayable,TaxesExpense,ConsumptionExpenditure,TaxesRevenue,PrepaidCorporateIncomeTaxes,AdvancesReceived,DepositsReceived-consumptionexpenditure 1 ConsumptionExpenditure-consumptiontaxpaid 1 ConsumptionTaxPaid-consumptiontaxreceived 1 ConsumptionTaxReceived-contra-asset 1 AllowanceForDoubtfulAccounts-corporate 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-corporate income taxes 12 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-corporateincometaxes 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-cost of sales 18 SalesCost,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets,GrossProfit,OrdinaryProfit,Sales,EquityInEarningsOfInvestee-counterpart 3 PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-credit card receivable 12 CreditCardReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,NotesLoansReceivable,OtherReceivables,AllowanceForDoubtfulAccounts-credit-balance 1 AllowanceForDoubtfulAccounts-creditcardreceivable 1 CreditCardReceivable-cumulative translation adjustment 1 CumulativeTranslationAdjustment-cumulativetranslationadjustment 1 CumulativeTranslationAdjustment-current deposits 6 CurrentDeposits,Deposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-currentdeposits 1 CurrentDeposits-debit 1 PaymentFees-debt 2 BadDebtLoss,RecoveryOfBadDebts-debts 1 RecoveryOfBadDebts-deferral 4 PrepaidExpenses,AccruedRevenue,UnearnedRevenue,AccruedExpenses-deferring 1 AllowanceForDoubtfulAccounts-delivery 1 DeliveryExpenses-deliveryexpenses 1 DeliveryExpenses-deposit interest earned 12 DepositInterestEarned,NationalBondInterestEarned,InterestEarned,Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,InterestExpense,WageEarned-depositinterestearned 1 DepositInterestEarned-depositpayable 2 DepositPayable,ReserveDepositPayable-deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-deposits accepted 6 DepositPayable,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,DepositsReceived-deposits received 8 DepositPayable,DepositsReceived,Deposits,CurrentDeposits,ReserveDepositReceivable,ReserveDepositPayable,AdvancesReceived,ConsumptionTaxReceived-deposits, 2 ReserveDepositReceivable,ReserveDepositPayable-depositsreceived 2 DepositPayable,DepositsReceived-depreciation 4 ReserveForDepreciation,Depreciation,AmortizationExpense,AccumulatedDepreciation-depreciation; 1 AccumulatedDepreciation-dividends 1 UnpaidDividends-doubtful 4 AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-earned 5 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,WageEarned,UnearnedRevenue-earnings 3 RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings-electronically recorded monetary claims 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-electronically recorded monetary obligations 2 ElectronicallyRecordedObligations,ElectronicallyRecordedReceivable-electronicallyrecordedobligations 1 ElectronicallyRecordedObligations-electronicallyrecordedreceivable 1 ElectronicallyRecordedReceivable-equipment 3 Machinery,EquipmentInvestment,Fixtures-equipment investment 6 EquipmentInvestment,InvestmentSecurities,InvestmentInAssociate,Machinery,StockInvestment,Fixtures-equipmentinvestment 1 EquipmentInvestment-equity 6 InvestmentInAssociate,CapitalStock,RetainedEarnings,EquityInEarningsOfInvestee,LegalRetainedEarnings,CumulativeTranslationAdjustment-equity in earnings of investee 44 EquityInEarningsOfInvestee,RetainedEarnings,LegalRetainedEarnings,InvestmentInAssociate,OrdinaryProfit,CommunicationExpenses,GainOnSalesOfFixedAssets,ReversalOfAllowanceForDoubtfulAccounts,Deposits,InvestmentSecurities-equityinearningsofinvestee 1 EquityInEarningsOfInvestee-estate 1 Building-estimated 1 ReversalOfAllowanceForDoubtfulAccounts-examples 1 ReserveDepositPayable-excess 1 ReversalOfAllowanceForDoubtfulAccounts-existing 2 AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts-expenditure 3 WageExpenditure,ConsumptionExpenditure,SubsidyExpense-expense 22 Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses,SuppliesExpenses-expenses 7 Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidExpenses,AccruedExpenses,CommunicationExpenses-fee 2 ReceiptFee,PaymentFees-fees 2 ReceiptFee,PaymentFees-fixed 2 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets-fixtures 1 Fixtures-fixtures and equipment 9 Fixtures,Machinery,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-for 8 ReserveForDepreciation,AmortizationExpense,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-gain on sales of fixed assets 39 GainOnSalesOfFixedAssets,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable-gainonsalesoffixedassets 1 GainOnSalesOfFixedAssets-gold 1 Gold-government service 1 GovernmentService-governmentservice 1 GovernmentService-gross profit 2 GrossProfit,OrdinaryProfit-grossprofit 1 GrossProfit-in 35 Deposits,InvestmentSecurities,InvestmentInAssociate,Products,Machinery,Building,StockInvestment,EquipmentInvestment,RetainedEarnings,AmortizationExpense-income 10 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-independent 1 AllowanceForDoubtfulAccounts-indirect 1 AccumulatedDepreciation-instead. 1 MerchandiseInventory-intact, 1 AllowanceForDoubtfulAccounts-intangibles 1 AmortizationExpense-interest 4 InterestExpense,NationalBondInterestEarned,DepositInterestEarned,InterestEarned-interest earned 6 NationalBondInterestEarned,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-interest expense 25 InterestExpense,Depreciation,AmortizationExpense,SalesCost,BusinessTrip,Commutation,UtilitiesExpense,RentExpense,AdvertisingExpense,DeliveryExpenses-interestearned 3 NationalBondInterestEarned,DepositInterestEarned,InterestEarned-interestexpense 1 InterestExpense-inventory 2 Products,MerchandiseInventory-investee 1 EquityInEarningsOfInvestee-investment 4 InvestmentSecurities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-investment in associate 35 InvestmentInAssociate,InvestmentSecurities,StockInvestment,EquipmentInvestment,Deposits,Products,Machinery,Building,RetainedEarnings,AmortizationExpense-investment securities 5 InvestmentSecurities,Securities,InvestmentInAssociate,StockInvestment,EquipmentInvestment-investmentinassociate 1 InvestmentInAssociate-investmentsecurities 1 InvestmentSecurities-is 13 Products,AdvertisingExpense,MiscellaneousExpenses,EquityInEarningsOfInvestee,MerchandiseInventory,CashOverShort,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,MiscellaneousLoss-keeps 1 AllowanceForDoubtfulAccounts-land 1 Land-legacy 2 AccumulatedDepreciation,CommunicationExpenses-legal (appropriated) retained earnings reserve 8 LegalRetainedEarnings,RetainedEarnings,AccumulatedDepreciation,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,EquityInEarningsOfInvestee,CommunicationExpenses-legalretainedearnings 1 LegalRetainedEarnings-level). 1 AllowanceForDoubtfulAccounts-liability 26 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,ReserveForDepreciation,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable-loans 7 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansReceivable,NotesLoansPayable-loans payable 15 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable,LongTermLoansReceivable,ShortTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-loans payable on notes 40 NotesLoansPayable,LongTermLoansPayable,NotesLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,CentralBankNotePayable,NotesPayable-loans receivable on notes 40 NotesLoansReceivable,LongTermLoansReceivable,NotesLoansPayable,LossOnSalesOfNotesReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesReceivable,ElectronicallyRecordedReceivable,LongTermNationalBonds,ShortTermNationalBonds-loanspayable 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-long-term loans payable 16 LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable,NotesLoansPayable,LongTermNationalBonds,ShortTermLoansReceivable,DepositPayable,ShortTermNationalBondsPayable-long-term loans receivable 16 LongTermLoansReceivable,ShortTermLoansReceivable,LongTermLoansPayable,NotesLoansReceivable,LongTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,ShortTermLoansPayable,LoansPayable,LongTermNationalBondsPayable-long-term national bonds 7 LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermLoansReceivable,LongTermLoansPayable,NationalBondInterestEarned-long-term national bonds payable 16 LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermLoansPayable,LongTermLoansReceivable,ShortTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-longtermloanspayable 1 LongTermLoansPayable-longtermloansreceivable 1 LongTermLoansReceivable-longtermnationalbonds 2 LongTermNationalBonds,LongTermNationalBondsPayable-longtermnationalbondspayable 1 LongTermNationalBondsPayable-loss 6 NetLoss,CashOverShort,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-loss on sales of fixed assets 43 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,SalesCost,MerchandiseInventory,CommunicationExpenses,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable-loss on sales of notes receivable 51 LossOnSalesOfNotesReceivable,LossOnSalesOfFixedAssets,NotesLoansReceivable,GainOnSalesOfFixedAssets,LongTermLoansReceivable,SalesCost,NotesReceivable,ElectronicallyRecordedReceivable,MerchandiseInventory,NotesLoansPayable-lossonsalesoffixedassets 1 LossOnSalesOfFixedAssets-lossonsalesofnotesreceivable 1 LossOnSalesOfNotesReceivable-machinery 1 Machinery-machinery and equipment 9 Machinery,Fixtures,Products,EquipmentInvestment,BusinessTrip,MerchandiseInventory,Land,CashOverShort,AllowanceForDoubtfulAccounts-made 1 PaymentsOnBehalf-merchandise inventory 2 Products,MerchandiseInventory-merchandiseinventory 2 Products,MerchandiseInventory-method 4 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,AccumulatedDepreciation-method. 1 EquityInEarningsOfInvestee-miscellaneous 4 MiscellaneousExpenses,CashOverShort,MiscellaneousLoss,MiscellaneousIncome-miscellaneous income 12 CashOverShort,MiscellaneousIncome,MiscellaneousExpenses,NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes-miscellaneous loss 8 CashOverShort,MiscellaneousLoss,MiscellaneousExpenses,NetLoss,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousIncome-miscellaneousexpenses 1 MiscellaneousExpenses-miscellaneousincome 2 CashOverShort,MiscellaneousIncome-miscellaneousloss 2 CashOverShort,MiscellaneousLoss-monetary 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-name. 1 AccumulatedDepreciation-named 1 CommunicationExpenses-national 5 LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,NationalBondInterestEarned-national bond interest earned 10 NationalBondInterestEarned,LongTermNationalBonds,ShortTermNationalBonds,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,DepositInterestEarned,InterestEarned,InterestExpense,WageEarned,UnearnedRevenue-nationalbondinterestearned 1 NationalBondInterestEarned-net income 13 NetIncome,SubsidyIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,NetLoss,ElectronicallyRecordedReceivable,PrepaidCorporateIncomeTaxes,CashOverShort,ElectronicallyRecordedObligations-net loss 9 NetLoss,NetIncome,ElectronicallyRecordedReceivable,CashOverShort,ElectronicallyRecordedObligations,BadDebtLoss,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,MiscellaneousLoss-netincome 1 NetIncome-netloss 1 NetLoss-new 1 AllowanceForDoubtfulAccounts-non-negative 1 AllowanceForDoubtfulAccounts-notes 6 CentralBankNotePayable,NotesReceivable,NotesLoansReceivable,NotesPayable,NotesLoansPayable,LossOnSalesOfNotesReceivable-notes payable 15 CentralBankNotePayable,NotesPayable,NotesLoansPayable,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable-notes receivable 13 NotesReceivable,NotesLoansReceivable,LossOnSalesOfNotesReceivable,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,CentralBankNotePayable,ElectronicallyRecordedReceivable,CreditCardReceivable-notesloanspayable 1 NotesLoansPayable-notesloansreceivable 1 NotesLoansReceivable-notespayable 1 NotesPayable-notesreceivable 2 NotesReceivable,LossOnSalesOfNotesReceivable-obligations 1 ElectronicallyRecordedObligations-of 12 SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,PaymentFees,CommunicationExpenses,GainOnSalesOfFixedAssets-off 3 LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,RecoveryOfBadDebts-on 30 LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense,BusinessTrip-ordinary profit 2 OrdinaryProfit,GrossProfit-ordinaryprofit 1 OrdinaryProfit-other payables 2 OtherPayables,OtherReceivables-other receivables 2 OtherReceivables,OtherPayables-otherpayables 1 OtherPayables-otherreceivables 1 OtherReceivables-over 5 GovernmentService,CashOverShort,BankOverdraft,AllowanceForDoubtfulAccounts,RecoveryOfBadDebts-overdraft 1 BankOverdraft-paid 8 AdvancesPaid,PrepaidExpenses,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,UnpaidDividends,PaymentFees-patent 1 Patent-payable 12 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,ReserveDepositPayable,CentralBankNotePayable,AccountsPayable,NotesPayable-payables 1 OtherPayables-payment 6 CentralBankPaymentExpense,ReceiptFee,CentralBankPaymentIncome,PaymentsOnBehalf,SuspensePayments,PaymentFees-payment fees / fees paid 32 PaymentFees,ReceiptFee,CentralBankPaymentExpense,CentralBankPaymentIncome,Products,ReserveDepositReceivable,GovernmentService,ReserveForDepreciation,DepositPayable,ReserveDepositPayable-paymentfees 2 ReceiptFee,PaymentFees-payments 2 PaymentsOnBehalf,SuspensePayments-payments made on behalf 31 PaymentsOnBehalf,LongTermNationalBonds,ShortTermNationalBonds,LongTermLoansReceivable,LongTermLoansPayable,ReserveForDepreciation,LongTermNationalBondsPayable,ShortTermNationalBondsPayable,Depreciation,AmortizationExpense-paymentsonbehalf 1 PaymentsOnBehalf-periodic/3-account 1 MerchandiseInventory-perpetual\/specific-identification 1 MerchandiseInventory-petty cash 3 PettyCash,Cash,CashOverShort-pettycash 1 PettyCash-prepaid corporate income taxes 13 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,TaxesExpense,NetIncome,SubsidyIncome,RentalIncome,WageEarned,TaxesRevenue,CentralBankPaymentIncome-prepaid expenses 8 PrepaidExpenses,Commutation,DeliveryExpenses,SuppliesExpenses,MiscellaneousExpenses,PrepaidCorporateIncomeTaxes,AccruedExpenses,CommunicationExpenses-prepaidcorporateincometaxes 1 PrepaidCorporateIncomeTaxes-prepaidexpenses 1 PrepaidExpenses-presentation 1 AllowanceForDoubtfulAccounts-products 2 Products,MerchandiseInventory-profit 2 GrossProfit,OrdinaryProfit-provision for doubtful accounts 11 ProvisionForDoubtfulAccounts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ReversalOfAllowanceForDoubtfulAccounts,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,MerchandiseInventory,AccountsPayable-provisionfordoubtfulaccounts 1 ProvisionForDoubtfulAccounts-purchases 2 Purchases,MerchandiseInventory-rather 1 AllowanceForDoubtfulAccounts-real estate 1 Building-reason 1 AccumulatedDepreciation-receipt fee 3 ReceiptFee,PaymentFees,SuspenseReceipts-receiptfee 2 ReceiptFee,PaymentFees-receipts 1 SuspenseReceipts-receivable 10 LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,NotesReceivable,ElectronicallyRecordedReceivable,CreditCardReceivable,NotesLoansReceivable,OtherReceivables,LossOnSalesOfNotesReceivable-receivables 1 OtherReceivables-received 4 DepositPayable,AdvancesReceived,DepositsReceived,ConsumptionTaxReceived-recorded 2 ElectronicallyRecordedReceivable,ElectronicallyRecordedObligations-recovery of bad debts written off 13 RecoveryOfBadDebts,LossOnSalesOfFixedAssets,GainOnSalesOfFixedAssets,SalesCost,GrossProfit,OrdinaryProfit,EquityInEarningsOfInvestee,Software,BadDebtLoss,LossOnSalesOfNotesReceivable-recoveryofbaddebts 1 RecoveryOfBadDebts-released). 1 ReversalOfAllowanceForDoubtfulAccounts-rent 3 CurrentDeposits,RentExpense,RentalIncome-rental income 10 RentalIncome,NetIncome,SubsidyIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes,MiscellaneousIncome-rentalincome 1 RentalIncome-rentexpense 1 RentExpense-reserve 5 ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AccumulatedDepreciation,LegalRetainedEarnings-reserve deposits, asset side 47 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts,Cash,Deposits,CurrentDeposits,Securities,InvestmentSecurities,InvestmentInAssociate,LongTermNationalBonds-reserve deposits, liability side 28 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,DepositPayable,LongTermNationalBondsPayable-reserve for depreciation 12 ReserveForDepreciation,AccumulatedDepreciation,AmortizationExpense,ReserveDepositReceivable,ReserveDepositPayable,Depreciation,MerchandiseInventory,AllowanceForDoubtfulAccounts,LegalRetainedEarnings,ProvisionForDoubtfulAccounts-reservedepositpayable 1 ReserveDepositPayable-reservedepositreceivable 1 ReserveDepositReceivable-reservefordepreciation 2 ReserveForDepreciation,AccumulatedDepreciation-retained 4 RetainedEarnings,AccumulatedDepreciation,LegalRetainedEarnings,CommunicationExpenses-retained earnings 5 RetainedEarnings,LegalRetainedEarnings,EquityInEarningsOfInvestee,AccumulatedDepreciation,CommunicationExpenses-retainedearnings 2 RetainedEarnings,LegalRetainedEarnings-revenue 21 ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome,WageEarned-reversal of allowance for doubtful accounts 21 ReversalOfAllowanceForDoubtfulAccounts,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,ProvisionForDoubtfulAccounts,CommunicationExpenses,AccountsReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,SalesCost-reversalofallowancefordoubtfulaccounts 1 ReversalOfAllowanceForDoubtfulAccounts-sales 6 SalesCost,Sales,MerchandiseInventory,LossOnSalesOfFixedAssets,LossOnSalesOfNotesReceivable,GainOnSalesOfFixedAssets-salescost 1 SalesCost-same 1 AccumulatedDepreciation-savings deposits 6 Deposits,CurrentDeposits,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,DepositsReceived-securities 2 Securities,InvestmentSecurities-service 1 GovernmentService-short 5 ShortTermNationalBonds,ShortTermLoansReceivable,ShortTermLoansPayable,ShortTermNationalBondsPayable,CashOverShort-short-term loans payable 16 ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable,NotesLoansPayable,ShortTermNationalBonds,LongTermLoansReceivable,DepositPayable,LongTermNationalBondsPayable-short-term loans receivable 16 ShortTermLoansReceivable,LongTermLoansReceivable,ShortTermLoansPayable,NotesLoansReceivable,ShortTermNationalBonds,AccountsReceivable,ReserveDepositReceivable,LongTermLoansPayable,LoansPayable,ShortTermNationalBondsPayable-short-term national bonds 7 ShortTermNationalBonds,ShortTermNationalBondsPayable,LongTermNationalBonds,LongTermNationalBondsPayable,ShortTermLoansReceivable,ShortTermLoansPayable,NationalBondInterestEarned-short-term national bonds payable 16 ShortTermNationalBondsPayable,ShortTermNationalBonds,LongTermNationalBondsPayable,LongTermNationalBonds,ShortTermLoansPayable,ShortTermLoansReceivable,LongTermLoansPayable,LoansPayable,DepositPayable,ReserveDepositPayable-shorttermloanspayable 1 ShortTermLoansPayable-shorttermloansreceivable 1 ShortTermLoansReceivable-shorttermnationalbonds 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-shorttermnationalbondspayable 1 ShortTermNationalBondsPayable-side 3 ReserveDepositReceivable,ReserveDepositPayable,AllowanceForDoubtfulAccounts-side, 1 AllowanceForDoubtfulAccounts-smaller 1 ReversalOfAllowanceForDoubtfulAccounts-software 1 Software-stock 2 StockInvestment,CapitalStock-stock investment 5 StockInvestment,InvestmentSecurities,InvestmentInAssociate,EquipmentInvestment,CapitalStock-stockinvestment 1 StockInvestment-structure 1 AllowanceForDoubtfulAccounts-subsidy expenditure 4 SubsidyExpense,WageExpenditure,ConsumptionExpenditure,SubsidyIncome-subsidy income 11 SubsidyIncome,SubsidyExpense,NetIncome,RentalIncome,WageEarned,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-subsidyexpense 1 SubsidyExpense-subsidyincome 1 SubsidyIncome-supplies 1 SuppliesExpenses-suppliesexpenses 1 SuppliesExpenses-suspense payments 4 SuspensePayments,PaymentsOnBehalf,CashOverShort,SuspenseReceipts-suspense receipts 3 SuspenseReceipts,SuspensePayments,CashOverShort-suspensepayments 1 SuspensePayments-suspensereceipts 1 SuspenseReceipts-tax 8 TaxesExpense,TaxesRevenue,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes,ConsumptionTaxReceived,AccruedConsumptionTax,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-tax revenue 28 TaxesRevenue,TaxesExpense,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee-taxes 5 TaxesExpense,TaxesRevenue,PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-taxesexpense 1 TaxesExpense-taxesrevenue 1 TaxesRevenue-temporary/suspense 1 CashOverShort-than 2 AllowanceForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-the 10 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,OtherReceivables,OtherPayables,AllowanceForDoubtfulAccounts,AccumulatedDepreciation,PaymentFees,CommunicationExpenses,ReversalOfAllowanceForDoubtfulAccounts-this 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-to 8 Products,StockInvestment,CapitalStock,CentralBankPaymentExpense,CentralBankPaymentIncome,MerchandiseInventory,CashOverShort,AllowanceForDoubtfulAccounts-trademark 1 Trademark-translation 1 CumulativeTranslationAdjustment-transportation 1 BusinessTrip-travel and transportation 8 BusinessTrip,Products,Machinery,MerchandiseInventory,Land,Fixtures,CashOverShort,AllowanceForDoubtfulAccounts-treasury 2 CentralBankPaymentExpense,CentralBankPaymentIncome-under 5 InvestmentInAssociate,EquityInEarningsOfInvestee,MerchandiseInventory,AllowanceForDoubtfulAccounts,AccumulatedDepreciation-unearned revenue 21 UnearnedRevenue,ValueAdded,SubsidyIncome,NationalBondInterestEarned,DepositInterestEarned,GrossProfit,OrdinaryProfit,InterestEarned,ReceiptFee,RentalIncome-unearnedrevenue 1 UnearnedRevenue-unpaid dividends 3 UnpaidDividends,AccruedConsumptionTax,AccruedCorporateIncomeTaxes-unpaiddividends 1 UnpaidDividends-use 3 MiscellaneousExpenses,MerchandiseInventory,ReversalOfAllowanceForDoubtfulAccounts-used 1 ReversalOfAllowanceForDoubtfulAccounts-utilities 1 UtilitiesExpense-utilitiesexpense 1 UtilitiesExpense-valuation 2 AllowanceForDoubtfulAccounts,AccumulatedDepreciation-value added 2 ValueAdded,AllowanceForDoubtfulAccounts-valueadded 1 ValueAdded-values 1 AllowanceForDoubtfulAccounts-vehicle 1 Vehicle-vehicles 1 Vehicle-wage income 11 WageEarned,WageExpenditure,NetIncome,SubsidyIncome,RentalIncome,CentralBankPaymentIncome,PrepaidCorporateIncomeTaxes,CashOverShort,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-wageearned 1 WageEarned-wageexpenditure 1 WageExpenditure-wages 1 WageExpenditure-when 1 ReversalOfAllowanceForDoubtfulAccounts-written 1 RecoveryOfBadDebts-— 10 ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-が本科目を買掛金の代用にしていた経緯あり) 1 ReserveDepositPayable-では 2 Products,Commutation-と区別される) 1 AmortizationExpense-を使う 4 Products,ReserveForDepreciation,ReserveDepositPayable,Commutation-を使う) 2 ReserveForDepreciation,Commutation-を使うこと 1 ReserveDepositPayable-クレジット売掛金 1 CreditCardReceivable-ソフトウェア 2 AmortizationExpense,Software-中央銀行が受け入れる準備預金。SNA\/マクロ系)。※簿記の買掛金は 1 ReserveDepositPayable-仕入 2 Products,Purchases-付加価値。SNA\/マクロ系 1 ValueAdded-付加価値。sna\/マクロ系 1 ValueAdded-仮受消費税 1 ConsumptionTaxReceived-仮受金 1 SuspenseReceipts-仮払法人税等 1 PrepaidCorporateIncomeTaxes-仮払消費税 1 ConsumptionTaxPaid-仮払金 1 SuspensePayments-借入金 4 LongTermLoansPayable,ShortTermLoansPayable,LoansPayable,NotesLoansPayable-備品 1 Fixtures-償却債権取立益 1 RecoveryOfBadDebts-分法 4 InvestmentInAssociate,Products,EquityInEarningsOfInvestee,MerchandiseInventory-分法: 1 MerchandiseInventory-利益準備金 1 LegalRetainedEarnings-前受収益 1 UnearnedRevenue-前受金 1 AdvancesReceived-前払費用 1 PrepaidExpenses-前払金 1 AdvancesPaid-区分) 2 NetIncome,NetLoss-受入預金 — 銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 'DepositsReceived' 10 DepositPayable,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-受入預金 — 銀行側の負債としての預金。sna\/マクロ系。従業員等からの預り金は 'depositsreceived' 10 DepositPayable,ReserveDepositReceivable,ReserveForDepreciation,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-受取利息 1 InterestEarned-受取家賃 1 RentalIncome-受取手形 1 NotesReceivable-受取手数料。支払側は 'PaymentFees' 1 ReceiptFee-受取手数料。支払側は 'paymentfees' 1 ReceiptFee-商品 2 Products,MerchandiseInventory-商標権 2 AmortizationExpense,Trademark-固定資産売却損 1 LossOnSalesOfFixedAssets-固定資産売却益 1 GainOnSalesOfFixedAssets-国債利息収入 1 NationalBondInterestEarned-国庫納付金収入。SNA\/マクロ系 1 CentralBankPaymentIncome-国庫納付金収入。sna\/マクロ系 1 CentralBankPaymentIncome-国庫納付金支出。SNA\/マクロ系 1 CentralBankPaymentExpense-国庫納付金支出。sna\/マクロ系 1 CentralBankPaymentExpense-土地 1 Land-在外子会社等の外貨建財務諸表の換算差額を計上する 1 CumulativeTranslationAdjustment-売上 4 Products,SalesCost,GrossProfit,Sales-売上原価 1 SalesCost-売上総利益 — 決算振替用 10 GrossProfit,NetIncome,OrdinaryProfit,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-売掛金 2 AccountsReceivable,CreditCardReceivable-小口現金 1 PettyCash-差額補充法/洗替法 1 ReversalOfAllowanceForDoubtfulAccounts-市中銀行が中央銀行に置く準備預金。SNA\/マクロ系) 1 ReserveDepositReceivable-広告宣伝費 1 AdvertisingExpense-建物 1 Building-当座借越 1 BankOverdraft-当座預金 1 CurrentDeposits-当期純利益 — 決算振替用。借方側に立つため Expense 区分 29 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost-当期純利益 — 決算振替用。借方側に立つため expense 区分 29 NetIncome,AmortizationExpense,Commutation,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,SalesCost-当期純損失 — 決算振替用。貸方側に立つため Revenue 区分 28 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-当期純損失 — 決算振替用。貸方側に立つため revenue 区分 28 NetLoss,NetIncome,GrossProfit,OrdinaryProfit,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-手形借入金 1 NotesLoansPayable-手形売却損 1 LossOnSalesOfNotesReceivable-手形貸付金 1 NotesLoansReceivable-投資有価証券 1 InvestmentSecurities-持分法による投資利益 1 EquityInEarningsOfInvestee-支払利息 1 InterestExpense-支払家賃 1 RentExpense-支払手形 1 NotesPayable-支払手数料 1 PaymentFees-政府サービス。SNA\/マクロ系 1 GovernmentService-政府サービス。sna\/マクロ系 1 GovernmentService-旅費交通費 1 BusinessTrip-旧称。新規コードでは 1 Commutation-普通預金 1 Deposits-有価証券 2 Securities,InvestmentSecurities-未収入金 1 OtherReceivables-未収収益 1 AccruedRevenue-未払法人税等 1 AccruedCorporateIncomeTaxes-未払消費税 1 AccruedConsumptionTax-未払費用 1 AccruedExpenses-未払配当金 1 UnpaidDividends-未払金 1 OtherPayables-株式投資 1 StockInvestment-機械装置 1 Machinery-水道光熱費 1 UtilitiesExpense-決算振替用) 2 GrossProfit,OrdinaryProfit-決算振替用。借方側に立つため 1 NetIncome-決算振替用。貸方側に立つため 1 NetLoss-法人税等 3 PrepaidCorporateIncomeTaxes,AccruedCorporateIncomeTaxes,CorporateIncomeTaxes-消耗品費 1 SuppliesExpenses-消費支出。SNA\/マクロ系 1 ConsumptionExpenditure-消費支出。sna\/マクロ系 1 ConsumptionExpenditure-減価償却引当金 — SNA\/マクロ系の旧称。簿記の間接法には 'AccumulatedDepreciation' (減価償却累計額 11 ReserveForDepreciation,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-減価償却引当金 — sna\/マクロ系の旧称。簿記の間接法には 'accumulateddepreciation' (減価償却累計額 11 ReserveForDepreciation,ReserveDepositReceivable,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-減価償却累計額 2 ReserveForDepreciation,AccumulatedDepreciation-減価償却費 2 Depreciation,AmortizationExpense-準備預金, 負債側 — 中央銀行が受け入れる準備預金。SNA\/マクロ系 10 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 負債側 — 中央銀行が受け入れる準備預金。sna\/マクロ系 10 ReserveDepositPayable,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。SNA\/マクロ系 10 ReserveDepositReceivable,ReserveDepositPayable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-準備預金, 資産側 — 市中銀行が中央銀行に置く準備預金。sna\/マクロ系 10 ReserveDepositReceivable,ReserveDepositPayable,ReserveForDepreciation,DepositPayable,AmortizationExpense,Commutation,NetIncome,GrossProfit,OrdinaryProfit,NetLoss-為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する OCI/資本の部の項目 1 CumulativeTranslationAdjustment-為替換算調整勘定; 在外子会社等の外貨建財務諸表の換算差額を計上する oci/資本の部の項目 1 CumulativeTranslationAdjustment-無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'Depreciation' (減価償却費 11 AmortizationExpense,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,Commutation,NetIncome,GrossProfit,OrdinaryProfit-無形固定資産償却費 — 特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 'depreciation' (減価償却費 11 AmortizationExpense,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,Depreciation,Commutation,NetIncome,GrossProfit,OrdinaryProfit-特許権 2 AmortizationExpense,Patent-特許権・商標権・ソフトウェア等の無形資産の償却費。有形の 1 AmortizationExpense-現金 3 Cash,PettyCash,CashOverShort-現金過不足 1 CashOverShort-発行側) 2 LongTermNationalBondsPayable,ShortTermNationalBondsPayable-発行銀行券。SNA\/マクロ系 1 CentralBankNotePayable-発行銀行券。sna\/マクロ系 1 CentralBankNotePayable-発送費 1 DeliveryExpenses-短期借入金 1 ShortTermLoansPayable-短期国債 2 ShortTermNationalBonds,ShortTermNationalBondsPayable-短期国債, 発行側 2 ShortTermNationalBondsPayable,LongTermNationalBondsPayable-短期貸付金 1 ShortTermLoansReceivable-租税公課 1 TaxesExpense-租税収入。SNA\/マクロ系 1 TaxesRevenue-租税収入。sna\/マクロ系 1 TaxesRevenue-立替金 1 PaymentsOnBehalf-経常利益 — 決算振替用 10 OrdinaryProfit,NetIncome,GrossProfit,NetLoss,ReserveDepositReceivable,ReserveForDepreciation,DepositPayable,ReserveDepositPayable,AmortizationExpense,Commutation-給料 1 WageExpenditure-繰越利益剰余金 1 RetainedEarnings-繰越商品 2 Products,MerchandiseInventory-補助金収入。SNA\/マクロ系 1 SubsidyIncome-補助金収入。sna\/マクロ系 1 SubsidyIncome-補助金支出。SNA\/マクロ系 1 SubsidyExpense-補助金支出。sna\/マクロ系 1 SubsidyExpense-設備投資 1 EquipmentInvestment-負債側 1 ReserveDepositPayable-買掛金 2 ReserveDepositPayable,AccountsPayable-貸倒引当金 3 AllowanceForDoubtfulAccounts,ProvisionForDoubtfulAccounts,ReversalOfAllowanceForDoubtfulAccounts-貸倒引当金戻入 1 ReversalOfAllowanceForDoubtfulAccounts-貸倒引当金繰入 1 ProvisionForDoubtfulAccounts-貸倒損失 1 BadDebtLoss-賃金収入。SNA\/マクロ系 1 WageEarned-賃金収入。sna\/マクロ系 1 WageEarned-資本金 1 CapitalStock-資産側 1 ReserveDepositReceivable-車両運搬具 1 Vehicle-通信費 2 Commutation,CommunicationExpenses-通信費 — 旧称。新規コードでは 'CommunicationExpenses' を使う 12 Commutation,ReserveForDepreciation,ReserveDepositPayable,Products,ReserveDepositReceivable,DepositPayable,AmortizationExpense,NetIncome,GrossProfit,OrdinaryProfit-通信費 — 旧称。新規コードでは 'communicationexpenses' を使う 12 Commutation,ReserveForDepreciation,ReserveDepositPayable,Products,ReserveDepositReceivable,DepositPayable,AmortizationExpense,NetIncome,GrossProfit,OrdinaryProfit-金 41 Cash,Deposits,CurrentDeposits,LongTermLoansReceivable,AccountsReceivable,ShortTermLoansReceivable,ReserveDepositReceivable,Gold,CapitalStock,RetainedEarnings-銀行側の負債としての預金。SNA\/マクロ系。従業員等からの預り金は 1 DepositPayable-長期借入金 1 LongTermLoansPayable-長期国債 2 LongTermNationalBonds,LongTermNationalBondsPayable-長期国債, 発行側 2 LongTermNationalBondsPayable,ShortTermNationalBondsPayable-長期貸付金 1 LongTermLoansReceivable-関係会社株式 1 InvestmentInAssociate-雑損 1 MiscellaneousLoss-雑益 1 MiscellaneousIncome-雑費 1 MiscellaneousExpenses-電子記録債務 1 ElectronicallyRecordedObligations-電子記録債権 1 ElectronicallyRecordedReceivable-預り金 2 DepositPayable,DepositsReceived-預金利息収入 1 DepositInterestEarned
− test/fixtures/pre-land2/presentation.txt
@@ -1,117 +0,0 @@-# pre-land2 presentation golden; commit 1c1f3f22192d646296c4f4b4d4aff3408a573f39-## b1-basic--- bsRows-["Asset","","Liability",""]-["Cash","100.0","LoansPayable","60.0"]-["Total","100.0","Equity",""]-["","","CapitalStock","40.0"]-["","","Total","100.0"]--- plRows-["Cost","","Revenue",""]-["Total","0.0","Total","0.0"]--- projCurrentAssets-100.00:@Not:<Cash--- projFixedAssets-0--- projDeferredAssets-0--- projCurrentLiability-60.00:@Not:<LoansPayable--- projFixedLiability-0--- projCapitalStock-40.00:@Not:<CapitalStock-## b2-pl--- bsRows-["Asset","","Liability",""]-["SalesCost","300.0","Equity",""]-["Total","300.0","RetainedEarnings","500.0"]-["","","Total","500.0"]--- plRows-["Cost","","Revenue",""]-["SalesCost","300.0","Sales","500.0"]-["Total","500.0","Total","300.0"]--- projCurrentAssets-0--- projFixedAssets-0--- projDeferredAssets-0--- projCurrentLiability-0--- projFixedLiability-0--- projCapitalStock-0-## b3-contra--- bsRows-["Asset","","Liability",""]-["AccountsReceivable","1000.0","LoansPayable","400.0"]-["Building","800.0","AllowanceForDoubtfulAccounts","100.0"]-["Cash","900.0","AccumulatedDepreciation","200.0"]-["Total","2700.0","Equity",""]-["","","CapitalStock","2000.0"]-["","","Total","2700.0"]--- plRows-["Cost","","Revenue",""]-["Total","0.0","Total","0.0"]--- projCurrentAssets-900.00:@Not:<Cash .+ 1000.00:@Not:<AccountsReceivable--- projFixedAssets-800.00:@Not:<Building--- projDeferredAssets-0--- projCurrentLiability-400.00:@Not:<LoansPayable .+ 100.00:@Not:<AllowanceForDoubtfulAccounts--- projFixedLiability-200.00:@Not:<AccumulatedDepreciation--- projCapitalStock-2000.00:@Not:<CapitalStock-## b4-abnormal--- bsRows-["Asset","","Liability",""]-["LoansPayable","100.0","AccumulatedDepreciation","200.0"]-["AllowanceForDoubtfulAccounts","20.0","Equity",""]-["Building","500.0","Total","250.0"]-["Total","620.0","",""]--- plRows-["Cost","","Revenue",""]-["Total","0.0","Total","0.0"]--- projCurrentAssets-30.00:@Not:<Cash--- projFixedAssets-500.00:@Not:<Building--- projDeferredAssets-0--- projCurrentLiability-200.00:@Not:<LoansPayable .+ 100.00:@Not:<AllowanceForDoubtfulAccounts--- projFixedLiability-250.00:@Not:<AccumulatedDepreciation--- projCapitalStock-0-## b5-closing--- bsRows-["Asset","","Liability",""]-["AccountsReceivable","1000.0","LoansPayable","400.0"]-["Building","800.0","AllowanceForDoubtfulAccounts","100.0"]-["SalesCost","300.0","AccumulatedDepreciation","200.0"]-["Cash","1100.0","Equity",""]-["Total","3200.0","CapitalStock","2000.0"]-["","","RetainedEarnings","500.0"]-["","","Total","3200.0"]--- plRows-["Cost","","Revenue",""]-["SalesCost","300.0","Sales","500.0"]-["Total","500.0","Total","300.0"]--- projCurrentAssets-200.00:@Not:<Cash .+ 900.00:@Not:<Cash .+ 1000.00:@Not:<AccountsReceivable--- projFixedAssets-800.00:@Not:<Building--- projDeferredAssets-0--- projCurrentLiability-400.00:@Not:<LoansPayable .+ 100.00:@Not:<AllowanceForDoubtfulAccounts--- projFixedLiability-200.00:@Not:<AccumulatedDepreciation--- projCapitalStock-2000.00:@Not:<CapitalStock
− test/fixtures/pre-vland2/semantics.tsv
@@ -1,117 +0,0 @@-# pre-vland2 semantics (title, whatDiv, isContra, whichSide Not, whichSide Hat, whatPIMO, fixedCurrent, finalStockProbe); commit 85d6a7f-Cash Assets False Debit Credit PS Current Nothing-Deposits Assets False Debit Credit PS Current Nothing-CurrentDeposits Assets False Debit Credit PS Current Nothing-Securities Assets False Debit Credit PS Current Nothing-InvestmentSecurities Assets False Debit Credit PS Fixed Nothing-InvestmentInAssociate Assets False Debit Credit PS Fixed Nothing-LongTermNationalBonds Assets False Debit Credit PS Fixed Nothing-ShortTermNationalBonds Assets False Debit Credit PS Current Nothing-Products Assets False Debit Credit PS Current Nothing-Machinery Assets False Debit Credit PS Fixed Nothing-Building Assets False Debit Credit PS Fixed Nothing-Vehicle Assets False Debit Credit PS Fixed Nothing-StockInvestment Assets False Debit Credit PS Other Nothing-EquipmentInvestment Assets False Debit Credit PS Fixed Nothing-LongTermLoansReceivable Assets False Debit Credit PS Fixed Nothing-AccountsReceivable Assets False Debit Credit PS Current Nothing-ShortTermLoansReceivable Assets False Debit Credit PS Current Nothing-ReserveDepositReceivable Assets False Debit Credit PS Current Nothing-Gold Assets False Debit Credit PS Fixed Nothing-GovernmentService Assets False Debit Credit PS Current Nothing-CapitalStock Equity False Credit Debit MS Other Nothing-RetainedEarnings Equity False Credit Debit MS Other SELF-LongTermLoansPayable Liability False Credit Debit MS Fixed Nothing-ShortTermLoansPayable Liability False Credit Debit MS Current Nothing-LoansPayable Liability False Credit Debit MS Current Nothing-ReserveForDepreciation Liability False Credit Debit MS Current Nothing-DepositPayable Liability False Credit Debit MS Current Nothing-LongTermNationalBondsPayable Liability False Credit Debit MS Fixed Nothing-ShortTermNationalBondsPayable Liability False Credit Debit MS Current Nothing-ReserveDepositPayable Liability False Credit Debit MS Current Nothing-CentralBankNotePayable Liability False Credit Debit MS Current Nothing-Depreciation Cost False Debit Credit OUT Other Flip-AmortizationExpense Cost False Debit Credit OUT Other Flip-SalesCost Cost False Debit Credit OUT Other Flip-BusinessTrip Cost False Debit Credit OUT Other Flip-Commutation Cost False Debit Credit OUT Other Flip-UtilitiesExpense Cost False Debit Credit OUT Other Flip-RentExpense Cost False Debit Credit OUT Other Flip-AdvertisingExpense Cost False Debit Credit OUT Other Flip-DeliveryExpenses Cost False Debit Credit OUT Other Flip-SuppliesExpenses Cost False Debit Credit OUT Other Flip-MiscellaneousExpenses Cost False Debit Credit OUT Other Flip-WageExpenditure Cost False Debit Credit OUT Other Flip-InterestExpense Cost False Debit Credit OUT Other Flip-TaxesExpense Cost False Debit Credit OUT Other Flip-ConsumptionExpenditure Cost False Debit Credit OUT Other Flip-SubsidyExpense Cost False Debit Credit OUT Other Flip-CentralBankPaymentExpense Cost False Debit Credit OUT Other Flip-Purchases Cost False Debit Credit OUT Other Flip-NetIncome Cost False Debit Credit OUT Other Nothing-ValueAdded Revenue False Credit Debit IN Other Keep-SubsidyIncome Revenue False Credit Debit IN Other Keep-NationalBondInterestEarned Revenue False Credit Debit IN Other Keep-DepositInterestEarned Revenue False Credit Debit IN Other Keep-GrossProfit Revenue False Credit Debit IN Other Keep-OrdinaryProfit Revenue False Credit Debit IN Other Keep-InterestEarned Revenue False Credit Debit IN Other Keep-ReceiptFee Revenue False Credit Debit IN Other Keep-RentalIncome Revenue False Credit Debit IN Other Keep-WageEarned Revenue False Credit Debit IN Other Keep-TaxesRevenue Revenue False Credit Debit IN Other Keep-CentralBankPaymentIncome Revenue False Credit Debit IN Other Keep-Sales Revenue False Credit Debit IN Other Keep-EquityInEarningsOfInvestee Revenue False Credit Debit IN Other Keep-NetLoss Revenue False Credit Debit IN Other Nothing-PettyCash Assets False Debit Credit PS Current Nothing-NotesReceivable Assets False Debit Credit PS Current Nothing-ElectronicallyRecordedReceivable Assets False Debit Credit PS Current Nothing-CreditCardReceivable Assets False Debit Credit PS Current Nothing-NotesLoansReceivable Assets False Debit Credit PS Current Nothing-MerchandiseInventory Assets False Debit Credit PS Current Nothing-AdvancesPaid Assets False Debit Credit PS Current Nothing-PrepaidExpenses Assets False Debit Credit PS Current Nothing-AccruedRevenue Assets False Debit Credit PS Current Nothing-OtherReceivables Assets False Debit Credit PS Current Nothing-PaymentsOnBehalf Assets False Debit Credit PS Current Nothing-SuspensePayments Assets False Debit Credit PS Current Nothing-ConsumptionTaxPaid Assets False Debit Credit PS Current Nothing-PrepaidCorporateIncomeTaxes Assets False Debit Credit PS Current Nothing-Land Assets False Debit Credit PS Fixed Nothing-Fixtures Assets False Debit Credit PS Fixed Nothing-Patent Assets False Debit Credit PS Fixed Nothing-Trademark Assets False Debit Credit PS Fixed Nothing-Software Assets False Debit Credit PS Fixed Nothing-CashOverShort Assets False Debit Credit PS Other Nothing-AccountsPayable Liability False Credit Debit MS Current Nothing-NotesPayable Liability False Credit Debit MS Current Nothing-ElectronicallyRecordedObligations Liability False Credit Debit MS Current Nothing-NotesLoansPayable Liability False Credit Debit MS Current Nothing-BankOverdraft Liability False Credit Debit MS Current Nothing-AdvancesReceived Liability False Credit Debit MS Current Nothing-UnearnedRevenue Liability False Credit Debit MS Current Nothing-AccruedExpenses Liability False Credit Debit MS Current Nothing-OtherPayables Liability False Credit Debit MS Current Nothing-DepositsReceived Liability False Credit Debit MS Current Nothing-SuspenseReceipts Liability False Credit Debit MS Current Nothing-ConsumptionTaxReceived Liability False Credit Debit MS Current Nothing-AccruedConsumptionTax Liability False Credit Debit MS Current Nothing-AccruedCorporateIncomeTaxes Liability False Credit Debit MS Current Nothing-UnpaidDividends Liability False Credit Debit MS Current Nothing-AllowanceForDoubtfulAccounts Assets True Credit Debit MS Current Nothing-AccumulatedDepreciation Assets True Credit Debit MS Fixed Nothing-LegalRetainedEarnings Equity False Credit Debit MS Other Nothing-CumulativeTranslationAdjustment Equity False Credit Debit MS Other Nothing-ProvisionForDoubtfulAccounts Cost False Debit Credit OUT Other Flip-BadDebtLoss Cost False Debit Credit OUT Other Flip-LossOnSalesOfFixedAssets Cost False Debit Credit OUT Other Flip-LossOnSalesOfNotesReceivable Cost False Debit Credit OUT Other Flip-PaymentFees Cost False Debit Credit OUT Other Flip-MiscellaneousLoss Cost False Debit Credit OUT Other Flip-CorporateIncomeTaxes Cost False Debit Credit OUT Other Flip-CommunicationExpenses Cost False Debit Credit OUT Other Flip-GainOnSalesOfFixedAssets Revenue False Credit Debit IN Other Keep-RecoveryOfBadDebts Revenue False Credit Debit IN Other Keep-MiscellaneousIncome Revenue False Credit Debit IN Other Keep-ReversalOfAllowanceForDoubtfulAccounts Revenue False Credit Debit IN Other Keep
− test/fixtures/pre-vocab/finalstock.tsv
@@ -1,117 +0,0 @@-# pre-vocab finalStockTransfer behavior (title, rule, division); commit b536810fac6baa8466734ebf3d8b017dbf38d1f2-Cash Nothing Assets-Deposits Nothing Assets-CurrentDeposits Nothing Assets-Securities Nothing Assets-InvestmentSecurities Nothing Assets-InvestmentInAssociate Nothing Assets-LongTermNationalBonds Nothing Assets-ShortTermNationalBonds Nothing Assets-Products Nothing Assets-Machinery Nothing Assets-Building Nothing Assets-Vehicle Nothing Assets-StockInvestment Nothing Assets-EquipmentInvestment Nothing Assets-LongTermLoansReceivable Nothing Assets-AccountsReceivable Nothing Assets-ShortTermLoansReceivable Nothing Assets-ReserveDepositReceivable Nothing Assets-Gold Nothing Assets-GovernmentService Nothing Assets-CapitalStock Nothing Equity-RetainedEarnings SELF Equity-LongTermLoansPayable Nothing Liability-ShortTermLoansPayable Nothing Liability-LoansPayable Nothing Liability-ReserveForDepreciation Nothing Liability-DepositPayable Nothing Liability-LongTermNationalBondsPayable Nothing Liability-ShortTermNationalBondsPayable Nothing Liability-ReserveDepositPayable Nothing Liability-CentralBankNotePayable Nothing Liability-Depreciation Flip Cost-AmortizationExpense Nothing Cost-SalesCost Nothing Cost-BusinessTrip Nothing Cost-Commutation Nothing Cost-UtilitiesExpense Nothing Cost-RentExpense Nothing Cost-AdvertisingExpense Nothing Cost-DeliveryExpenses Nothing Cost-SuppliesExpenses Nothing Cost-MiscellaneousExpenses Nothing Cost-WageExpenditure Flip Cost-InterestExpense Flip Cost-TaxesExpense Flip Cost-ConsumptionExpenditure Flip Cost-SubsidyExpense Flip Cost-CentralBankPaymentExpense Flip Cost-Purchases Flip Cost-NetIncome Nothing Cost-ValueAdded Keep Revenue-SubsidyIncome Keep Revenue-NationalBondInterestEarned Nothing Revenue-DepositInterestEarned Nothing Revenue-GrossProfit Keep Revenue-OrdinaryProfit Keep Revenue-InterestEarned Keep Revenue-ReceiptFee Nothing Revenue-RentalIncome Nothing Revenue-WageEarned Keep Revenue-TaxesRevenue Keep Revenue-CentralBankPaymentIncome Keep Revenue-Sales Keep Revenue-EquityInEarningsOfInvestee Nothing Revenue-NetLoss Nothing Revenue-PettyCash Nothing Assets-NotesReceivable Nothing Assets-ElectronicallyRecordedReceivable Nothing Assets-CreditCardReceivable Nothing Assets-NotesLoansReceivable Nothing Assets-MerchandiseInventory Nothing Assets-AdvancesPaid Nothing Assets-PrepaidExpenses Nothing Assets-AccruedRevenue Nothing Assets-OtherReceivables Nothing Assets-PaymentsOnBehalf Nothing Assets-SuspensePayments Nothing Assets-ConsumptionTaxPaid Nothing Assets-PrepaidCorporateIncomeTaxes Nothing Assets-Land Nothing Assets-Fixtures Nothing Assets-Patent Nothing Assets-Trademark Nothing Assets-Software Nothing Assets-CashOverShort Nothing Assets-AccountsPayable Nothing Liability-NotesPayable Nothing Liability-ElectronicallyRecordedObligations Nothing Liability-NotesLoansPayable Nothing Liability-BankOverdraft Nothing Liability-AdvancesReceived Nothing Liability-UnearnedRevenue Nothing Liability-AccruedExpenses Nothing Liability-OtherPayables Nothing Liability-DepositsReceived Nothing Liability-SuspenseReceipts Nothing Liability-ConsumptionTaxReceived Nothing Liability-AccruedConsumptionTax Nothing Liability-AccruedCorporateIncomeTaxes Nothing Liability-UnpaidDividends Nothing Liability-AllowanceForDoubtfulAccounts Nothing Assets-AccumulatedDepreciation Nothing Assets-LegalRetainedEarnings Nothing Equity-CumulativeTranslationAdjustment Nothing Equity-ProvisionForDoubtfulAccounts Nothing Cost-BadDebtLoss Nothing Cost-LossOnSalesOfFixedAssets Nothing Cost-LossOnSalesOfNotesReceivable Nothing Cost-PaymentFees Nothing Cost-MiscellaneousLoss Nothing Cost-CorporateIncomeTaxes Nothing Cost-CommunicationExpenses Nothing Cost-GainOnSalesOfFixedAssets Nothing Revenue-RecoveryOfBadDebts Nothing Revenue-MiscellaneousIncome Nothing Revenue-ReversalOfAllowanceForDoubtfulAccounts Nothing Revenue
+ test/fixtures/readout-baseline-p1/README.md view
@@ -0,0 +1,46 @@+# Readout baseline for P1++These schema 1 fixtures record the output of the current readout APIs at+commit `2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f`. Each TSV file covers+one deterministic posting series and one function group. The first row records+the schema and commit. Subsequent rows contain the function, argument, result+kind, and result fields. Double values include the hexadecimal IEEE 754 bits+and `show` output. MoneyDecimal values use `show`.++The five Double series are `f-int`, `f-dec2`, `f-cancel`, `f-exp`, and `f-sub`.+The two MoneyDecimal series are `f-int` and `f-dec2`. Each series has 11 to 14+postings across Cash, Sales, AccountsReceivable, Purchases, and+RetainedEarnings. Journals divide the same postings among notes 1, 2, and 3.+The `alg-keyed-*` files use `HatBase (AccountTitles, Int)`, with the same+integer used as the posting key. The test suite supplies its existing `Int`+basis instances when it constructs these seven fixtures.+`f-dec2` puts 0.1 and 0.2 opposite 0.3 at Cash. `f-exp` puts two 1.7e308+postings on the same side of Sales. The fixtures record observed behavior;+they do not assert that the current arithmetic is the intended 0.6 behavior.++The `alg-*` files cover `bar`, `norm`, `balance`, `diffRL`, `compress`,+`balanceMapBy`, `netPairMapBy`, `postFromNetBy`, `projNetNorm`, `balanceBy`,+`Pair.compare`, `closingEntries`, `accountBalances`, `checkedEntry`,+`trialBalanceFindings`, and `periodResultOfAlg`. The+`journal-*` files cover `bar`, `norm`, `projWithBaseNetNorm`,+`projWithNoteBaseNetNorm`, `compress`, and `closingEntries`. The Double journal+files also cover `carryEntries` and `carryBefore`; those APIs accept Double+journals only. The `f-exp` file records `SumOutOfRange` from both carry APIs+when notes 2 and 3 are selected together.++`IO.Output.Statements.accountGrossTotals` is internal and cannot be called through the public+API. The `f-exp` series omits `postFromNetBy`: its summed Sales bucket is+infinite, and the callback's posting constructor rejects that derived value.+This is a current output-domain limit, not a change to the input series.++To regenerate after an intentional baseline change, run this command from the+repository root:++```bash+EA_REGEN_GOLDEN=1 stack build --test --bench --no-run-benchmarks+```++The command writes the 21 TSV files. Then run the command without+`EA_REGEN_GOLDEN` to compare the files with the current output. Regeneration+overwrites the evidence, so compare the resulting file changes before using+them as a new baseline.
+ test/fixtures/readout-baseline-p1/alg-decimal-f-dec2.tsv view
@@ -0,0 +1,58 @@+# readout-baseline-p1 alg-decimal-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Cash 1234.55+bar all posting Hat Purchases 0.09+bar all posting Hat RetainedEarnings 0.1+bar all posting Hat Sales 20.18+bar all posting Not AccountsReceivable 1214.57+norm all scalar 2510.53+balance all result False+diffRL all Debit 0.21+compress all posting Hat AccountsReceivable 19.99+compress all posting Hat Cash 1234.86+compress all posting Hat Purchases 0.1+compress all posting Hat RetainedEarnings 0.3+compress all posting Hat Sales 20.19+compress all posting Not AccountsReceivable 1234.56+compress all posting Not Cash 0.31+compress all posting Not Purchases 0.01+compress all posting Not RetainedEarnings 0.2+compress all posting Not Sales 0.01+balanceMapBy key=account map Cash -1234.55+balanceMapBy key=account map AccountsReceivable 1214.57+balanceMapBy key=account map RetainedEarnings -0.1+balanceMapBy key=account map Purchases -0.09+balanceMapBy key=account map Sales -20.18+netPairMapBy key=account map Cash 0 1234.55+netPairMapBy key=account map AccountsReceivable 1214.57 0+netPairMapBy key=account map RetainedEarnings 0 0.1+netPairMapBy key=account map Purchases 0 0.09+netPairMapBy key=account map Sales 0 20.18+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 1214.57+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 1234.55+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 0.09+postFromNetBy key=account; callback=identity-side-Not posting Not RetainedEarnings 0.1+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 20.18+projNetNorm Cash scalar 0.31+projNetNorm Sales scalar 0.01+projNetNorm Cash+Sales scalar 0.32+balanceBy Cash Not-Hat scalar -1234.55+balanceBy Sales Not-Hat scalar -20.18+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result GT+closingEntries Alg Right+closingEntries Alg posting Hat RetainedEarnings 20.18+closingEntries Alg posting Not Purchases 0.09+closingEntries Alg posting Not RetainedEarnings 0.09+closingEntries Alg posting Not Sales 20.18+accountBalances all map Cash CreditBalance 1234.55+accountBalances all map AccountsReceivable DebitBalance 1214.57+accountBalances all map RetainedEarnings DebitBalance 0.1+accountBalances all map Purchases CreditBalance 0.09+accountBalances all map Sales DebitBalance 20.18+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 1255.37 1255.16,UnexplainedAbnormalBalance Cash Debit (CreditBalance 1234.55),UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 0.1),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 0.09),UnexplainedAbnormalBalance Sales Credit (DebitBalance 20.18)]+trialBalanceFindings.debitTotal BeforeClosing scalar 1255.37+trialBalanceFindings.creditTotal BeforeClosing scalar 1255.16+Convert.Checked.checkedEntry 0.1+0.2 vs 0.3 result Right 0.3:@Hat:<Cash .+ 0.1:@Not:<Cash .+ 0.2:@Not:<Cash+Convert.Checked.debitInput 0.1+0.2 scalar 0.3+Convert.Checked.creditInput 0.3 scalar 0.3+periodResultOfAlg all PeriodLoss 20.09
+ test/fixtures/readout-baseline-p1/alg-decimal-f-int.tsv view
@@ -0,0 +1,55 @@+# readout-baseline-p1 alg-decimal-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Cash 3+bar all posting Hat Purchases 1+bar all posting Hat RetainedEarnings 12245+bar all posting Not AccountsReceivable 12342+bar all posting Not Sales 3+norm all scalar 25008+balance all result False+diffRL all Debit 24580+compress all posting Hat AccountsReceivable 3+compress all posting Hat Cash 5+compress all posting Hat Purchases 3+compress all posting Hat RetainedEarnings 12345+compress all posting Hat Sales 100+compress all posting Not AccountsReceivable 12345+compress all posting Not Cash 2+compress all posting Not Purchases 2+compress all posting Not RetainedEarnings 100+compress all posting Not Sales 103+balanceMapBy key=account map Cash -3+balanceMapBy key=account map AccountsReceivable 12342+balanceMapBy key=account map RetainedEarnings -12245+balanceMapBy key=account map Purchases -1+balanceMapBy key=account map Sales 3+netPairMapBy key=account map Cash 0 3+netPairMapBy key=account map AccountsReceivable 12342 0+netPairMapBy key=account map RetainedEarnings 0 12245+netPairMapBy key=account map Purchases 0 1+netPairMapBy key=account map Sales 3 0+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 12342+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 3+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 1+postFromNetBy key=account; callback=identity-side-Not posting Not RetainedEarnings 12245+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 3+projNetNorm Cash scalar 2+projNetNorm Sales scalar 103+projNetNorm Cash+Sales scalar 105+balanceBy Cash Not-Hat scalar -3+balanceBy Sales Not-Hat scalar 3+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result LT+closingEntries Alg Right+closingEntries Alg posting Hat Sales 3+closingEntries Alg posting Not Purchases 1+closingEntries Alg posting Not RetainedEarnings 1+closingEntries Alg posting Not RetainedEarnings 3+accountBalances all map Cash CreditBalance 3+accountBalances all map AccountsReceivable DebitBalance 12342+accountBalances all map RetainedEarnings DebitBalance 12245+accountBalances all map Purchases CreditBalance 1+accountBalances all map Sales CreditBalance 3+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 24794 214,UnexplainedAbnormalBalance Cash Debit (CreditBalance 3),UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 12245),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 1)]+trialBalanceFindings.debitTotal BeforeClosing scalar 24794+trialBalanceFindings.creditTotal BeforeClosing scalar 214+periodResultOfAlg all PeriodProfit 4
+ test/fixtures/readout-baseline-p1/alg-double-f-cancel.tsv view
@@ -0,0 +1,52 @@+# readout-baseline-p1 alg-double-f-cancel; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat AccountsReceivable 0x3ff0000000000000 1.0+bar all posting Hat Purchases 0x433fffffffffffff 9.007199254740991e15+bar all posting Hat Sales 0x3ff0000000000000 1.0+bar all posting Not Cash 0x3ff0000000000000 1.0+norm all scalar 0x4372e1bc9bf04000 8.503599627370496e16+balance all result False+diffRL all Credit 0x433ffffffffffffe 9.00719925474099e15+compress all posting Hat AccountsReceivable 0x4340000000000000 9.007199254740992e15+compress all posting Hat Cash 0x4341c37937e08000 1.0e16+compress all posting Hat Purchases 0x4340000000000000 9.007199254740992e15+compress all posting Hat RetainedEarnings 0x4341c37937e08000 1.0e16+compress all posting Hat Sales 0x4340000000000000 9.007199254740992e15+compress all posting Not AccountsReceivable 0x433fffffffffffff 9.007199254740991e15+compress all posting Not Cash 0x4341c37937e08000 1.0e16+compress all posting Not Purchases 0x3ff0000000000000 1.0+compress all posting Not RetainedEarnings 0x4341c37937e08000 1.0e16+compress all posting Not Sales 0x4340000000000000 9.007199254740992e15+balanceMapBy key=account map Cash 0x3ff0000000000000 1.0+balanceMapBy key=account map AccountsReceivable 0xbff0000000000000 -1.0+balanceMapBy key=account map RetainedEarnings 0x0000000000000000 0.0+balanceMapBy key=account map Purchases 0xc33fffffffffffff -9.007199254740991e15+balanceMapBy key=account map Sales 0xbff0000000000000 -1.0+netPairMapBy key=account map Cash 0x3ff0000000000000 1.0 0x0000000000000000 0.0+netPairMapBy key=account map AccountsReceivable 0x0000000000000000 0.0 0x3ff0000000000000 1.0+netPairMapBy key=account map Purchases 0x0000000000000000 0.0 0x433fffffffffffff 9.007199254740991e15+netPairMapBy key=account map Sales 0x0000000000000000 0.0 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 0x433fffffffffffff 9.007199254740991e15+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 0x3ff0000000000000 1.0+projNetNorm Cash scalar 0x4341c37937e08000 1.0e16+projNetNorm Sales scalar 0x4340000000000000 9.007199254740992e15+projNetNorm Cash+Sales scalar 0x4350e1bc9bf04000 1.9007199254740992e16+balanceBy Cash Not-Hat scalar 0x3ff0000000000000 1.0+balanceBy Sales Not-Hat scalar 0xbff0000000000000 -1.0+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result LT+closingEntries Alg Right+closingEntries Alg posting Hat RetainedEarnings 0x3ff0000000000000 1.0+closingEntries Alg posting Not Purchases 0x433fffffffffffff 9.007199254740991e15+closingEntries Alg posting Not RetainedEarnings 0x433fffffffffffff 9.007199254740991e15+closingEntries Alg posting Not Sales 0x3ff0000000000000 1.0+accountBalances all map Cash DebitBalance 0x3ff0000000000000 1.0+accountBalances all map AccountsReceivable CreditBalance 0x3ff0000000000000 1.0+accountBalances all map RetainedEarnings NoBalance+accountBalances all map Purchases CreditBalance 0x433fffffffffffff 9.007199254740991e15+accountBalances all map Sales DebitBalance 0x3ff0000000000000 1.0+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 3.8014398509481984e16 4.7021597764222976e16,UnexplainedAbnormalBalance AccountsReceivable Debit (CreditBalance 1.0),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 9.007199254740991e15),UnexplainedAbnormalBalance Sales Credit (DebitBalance 1.0)]+trialBalanceFindings.debitTotal BeforeClosing scalar 0x4360e1bc9bf04000 3.8014398509481984e16+trialBalanceFindings.creditTotal BeforeClosing scalar 0x4364e1bc9bf04000 4.7021597764222976e16+periodResultOfAlg all PeriodProfit 0x433ffffffffffffe 9.00719925474099e15
+ test/fixtures/readout-baseline-p1/alg-double-f-dec2.tsv view
@@ -0,0 +1,62 @@+# readout-baseline-p1 alg-double-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Cash 0x40934a3333333333 1234.55+bar all posting Hat Purchases 0x3fb70a3d70a3d70b 9.000000000000001e-2+bar all posting Hat RetainedEarnings 0x3fb9999999999998 9.999999999999998e-2+bar all posting Hat Sales 0x40342e147ae147ae 20.18+bar all posting Not AccountsReceivable 0x4092fa47ae147ae1 1214.57+norm all scalar 0x40a39d0f5c28f5c2 2510.5299999999997+balance all result False+diffRL all Debit 0x3fcae147ae147ae2 0.21000000000000002+compress all posting Hat AccountsReceivable 0x4033fd70a3d70a3d 19.99+compress all posting Hat Cash 0x40934b70a3d70a3d 1234.86+compress all posting Hat Purchases 0x3fb999999999999a 0.1+compress all posting Hat RetainedEarnings 0x3fd3333333333333 0.3+compress all posting Hat Sales 0x403430a3d70a3d70 20.189999999999998+compress all posting Not AccountsReceivable 0x40934a3d70a3d70a 1234.56+compress all posting Not Cash 0x3fd3d70a3d70a3d7 0.31+compress all posting Not Purchases 0x3f847ae147ae147b 1.0e-2+compress all posting Not RetainedEarnings 0x3fc999999999999a 0.2+compress all posting Not Sales 0x3f847ae147ae147b 1.0e-2+balanceMapBy key=account map Cash 0xc0934a3333333333 -1234.55+balanceMapBy key=account map AccountsReceivable 0x4092fa47ae147ae1 1214.57+balanceMapBy key=account map RetainedEarnings 0xbfb9999999999998 -9.999999999999998e-2+balanceMapBy key=account map Purchases 0xbfb70a3d70a3d70b -9.000000000000001e-2+balanceMapBy key=account map Sales 0xc0342e147ae147ae -20.18+netPairMapBy key=account map Cash 0x0000000000000000 0.0 0x40934a3333333333 1234.55+netPairMapBy key=account map AccountsReceivable 0x4092fa47ae147ae1 1214.57 0x0000000000000000 0.0+netPairMapBy key=account map RetainedEarnings 0x0000000000000000 0.0 0x3fb9999999999998 9.999999999999998e-2+netPairMapBy key=account map Purchases 0x0000000000000000 0.0 0x3fb70a3d70a3d70b 9.000000000000001e-2+netPairMapBy key=account map Sales 0x0000000000000000 0.0 0x40342e147ae147ae 20.18+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 0x4092fa47ae147ae1 1214.57+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 0x40934a3333333333 1234.55+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 0x3fb70a3d70a3d70b 9.000000000000001e-2+postFromNetBy key=account; callback=identity-side-Not posting Not RetainedEarnings 0x3fb9999999999998 9.999999999999998e-2+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 0x40342e147ae147ae 20.18+projNetNorm Cash scalar 0x3fd3d70a3d70a3d7 0.31+projNetNorm Sales scalar 0x3f847ae147ae147b 1.0e-2+projNetNorm Cash+Sales scalar 0x3fd47ae147ae147b 0.32+balanceBy Cash Not-Hat scalar 0xc0934a3333333333 -1234.55+balanceBy Sales Not-Hat scalar 0xc0342e147ae147ae -20.18+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result GT+closingEntries Alg Right+closingEntries Alg posting Hat RetainedEarnings 0x3ce4940000000000 2.284630817861455e-15+closingEntries Alg posting Hat RetainedEarnings 0x40342e147ae147ad 20.179999999999996+closingEntries Alg posting Not Purchases 0x3c64000000000000 8.673617379884035e-18+closingEntries Alg posting Not Purchases 0x3fb70a3d70a3d70a 9.0e-2+closingEntries Alg posting Not RetainedEarnings 0x3c64000000000000 8.673617379884035e-18+closingEntries Alg posting Not RetainedEarnings 0x3fb70a3d70a3d70a 9.0e-2+closingEntries Alg posting Not Sales 0x3ce4940000000000 2.284630817861455e-15+closingEntries Alg posting Not Sales 0x40342e147ae147ad 20.179999999999996+accountBalances all map Cash CreditBalance 0x40934a3333333333 1234.55+accountBalances all map AccountsReceivable DebitBalance 0x4092fa47ae147ae1 1214.57+accountBalances all map RetainedEarnings DebitBalance 0x3fb9999999999998 9.999999999999998e-2+accountBalances all map Purchases CreditBalance 0x3fb70a3d70a3d70b 9.000000000000001e-2+accountBalances all map Sales DebitBalance 0x40342e147ae147ae 20.18+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 1255.37 1255.1599999999999,UnexplainedAbnormalBalance Cash Debit (CreditBalance 1234.55),UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 9.999999999999998e-2),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 9.000000000000001e-2),UnexplainedAbnormalBalance Sales Credit (DebitBalance 20.18)]+trialBalanceFindings.debitTotal BeforeClosing scalar 0x40939d7ae147ae14 1255.37+trialBalanceFindings.creditTotal BeforeClosing scalar 0x40939ca3d70a3d70 1255.1599999999999+Convert.Checked.checkedEntry 0.1+0.2 vs 0.3 result Left (Imbalanced {_debitTotal = 0.30000000000000004, _creditTotal = 0.3} :| [])+Convert.Checked.debitInput 0.1+0.2 scalar 0x3fd3333333333334 0.30000000000000004+Convert.Checked.creditInput 0.3 scalar 0x3fd3333333333333 0.3+periodResultOfAlg all PeriodLoss 0x4034170a3d70a3d7 20.09
+ test/fixtures/readout-baseline-p1/alg-double-f-exp.tsv view
@@ -0,0 +1,45 @@+# readout-baseline-p1 alg-double-f-exp; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Purchases 0x7e37e43c8800759c 1.0e300+bar all posting Hat RetainedEarnings 0x3ff0000000000000 1.0+bar all posting Not AccountsReceivable 0x3ff0000000000000 1.0+bar all posting Not Cash 0x7e37e43c8800759c 1.0e300+bar all posting Not Sales 0x7ff0000000000000 Infinity+norm all scalar 0x7ff0000000000000 Infinity+balance all result False+diffRL all Credit 0x7ff0000000000000 Infinity+compress all posting Hat AccountsReceivable 0x01a56e1fc2f8f359 1.0e-300+compress all posting Hat Cash 0x3ff0000000000000 1.0+compress all posting Hat Purchases 0x7e37e43c8800759c 1.0e300+compress all posting Hat RetainedEarnings 0x3ff0000000000000 1.0+compress all posting Not AccountsReceivable 0x3ff0000000000000 1.0+compress all posting Not Cash 0x7e37e43c8800759c 1.0e300+compress all posting Not Purchases 0x01a56e1fc2f8f359 1.0e-300+compress all posting Not RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300+compress all posting Not Sales 0x7ff0000000000000 Infinity+balanceMapBy key=account map Cash 0x7e37e43c8800759c 1.0e300+balanceMapBy key=account map AccountsReceivable 0x3ff0000000000000 1.0+balanceMapBy key=account map RetainedEarnings 0xbff0000000000000 -1.0+balanceMapBy key=account map Purchases 0xfe37e43c8800759c -1.0e300+balanceMapBy key=account map Sales 0x7ff0000000000000 Infinity+netPairMapBy key=account map Cash 0x7e37e43c8800759c 1.0e300 0x0000000000000000 0.0+netPairMapBy key=account map AccountsReceivable 0x3ff0000000000000 1.0 0x0000000000000000 0.0+netPairMapBy key=account map RetainedEarnings 0x0000000000000000 0.0 0x3ff0000000000000 1.0+netPairMapBy key=account map Purchases 0x0000000000000000 0.0 0x7e37e43c8800759c 1.0e300+netPairMapBy key=account map Sales 0x7ff0000000000000 Infinity 0x0000000000000000 0.0+projNetNorm Cash scalar 0x7e37e43c8800759c 1.0e300+projNetNorm Sales scalar 0x7ff0000000000000 Infinity+projNetNorm Cash+Sales scalar 0x7ff0000000000000 Infinity+balanceBy Cash Not-Hat scalar 0x7e37e43c8800759c 1.0e300+balanceBy Sales Not-Hat scalar 0x7ff0000000000000 Infinity+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result EQ+closingEntries Alg Left NonFiniteBalance+accountBalances all map Cash DebitBalance 0x7e37e43c8800759c 1.0e300+accountBalances all map AccountsReceivable DebitBalance 0x3ff0000000000000 1.0+accountBalances all map RetainedEarnings DebitBalance 0x3ff0000000000000 1.0+accountBalances all map Purchases CreditBalance 0x7e37e43c8800759c 1.0e300+accountBalances all map Sales CreditBalance 0x7ff0000000000000 Infinity+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 1.0e300 Infinity,UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 1.0),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 1.0e300)]+trialBalanceFindings.debitTotal BeforeClosing scalar 0x7e37e43c8800759c 1.0e300+trialBalanceFindings.creditTotal BeforeClosing scalar 0x7ff0000000000000 Infinity+periodResultOfAlg all PeriodProfit 0x7ff0000000000000 Infinity
+ test/fixtures/readout-baseline-p1/alg-double-f-int.tsv view
@@ -0,0 +1,55 @@+# readout-baseline-p1 alg-double-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Cash 0x4008000000000000 3.0+bar all posting Hat Purchases 0x3ff0000000000000 1.0+bar all posting Hat RetainedEarnings 0x40c7ea8000000000 12245.0+bar all posting Not AccountsReceivable 0x40c81b0000000000 12342.0+bar all posting Not Sales 0x4008000000000000 3.0+norm all scalar 0x40d86c0000000000 25008.0+balance all result False+diffRL all Debit 0x40d8010000000000 24580.0+compress all posting Hat AccountsReceivable 0x4008000000000000 3.0+compress all posting Hat Cash 0x4014000000000000 5.0+compress all posting Hat Purchases 0x4008000000000000 3.0+compress all posting Hat RetainedEarnings 0x40c81c8000000000 12345.0+compress all posting Hat Sales 0x4059000000000000 100.0+compress all posting Not AccountsReceivable 0x40c81c8000000000 12345.0+compress all posting Not Cash 0x4000000000000000 2.0+compress all posting Not Purchases 0x4000000000000000 2.0+compress all posting Not RetainedEarnings 0x4059000000000000 100.0+compress all posting Not Sales 0x4059c00000000000 103.0+balanceMapBy key=account map Cash 0xc008000000000000 -3.0+balanceMapBy key=account map AccountsReceivable 0x40c81b0000000000 12342.0+balanceMapBy key=account map RetainedEarnings 0xc0c7ea8000000000 -12245.0+balanceMapBy key=account map Purchases 0xbff0000000000000 -1.0+balanceMapBy key=account map Sales 0x4008000000000000 3.0+netPairMapBy key=account map Cash 0x0000000000000000 0.0 0x4008000000000000 3.0+netPairMapBy key=account map AccountsReceivable 0x40c81b0000000000 12342.0 0x0000000000000000 0.0+netPairMapBy key=account map RetainedEarnings 0x0000000000000000 0.0 0x40c7ea8000000000 12245.0+netPairMapBy key=account map Purchases 0x0000000000000000 0.0 0x3ff0000000000000 1.0+netPairMapBy key=account map Sales 0x4008000000000000 3.0 0x0000000000000000 0.0+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 0x40c81b0000000000 12342.0+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 0x4008000000000000 3.0+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not RetainedEarnings 0x40c7ea8000000000 12245.0+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 0x4008000000000000 3.0+projNetNorm Cash scalar 0x4000000000000000 2.0+projNetNorm Sales scalar 0x4059c00000000000 103.0+projNetNorm Cash+Sales scalar 0x405a400000000000 105.0+balanceBy Cash Not-Hat scalar 0xc008000000000000 -3.0+balanceBy Sales Not-Hat scalar 0x4008000000000000 3.0+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result LT+closingEntries Alg Right+closingEntries Alg posting Hat Sales 0x4008000000000000 3.0+closingEntries Alg posting Not Purchases 0x3ff0000000000000 1.0+closingEntries Alg posting Not RetainedEarnings 0x3ff0000000000000 1.0+closingEntries Alg posting Not RetainedEarnings 0x4008000000000000 3.0+accountBalances all map Cash CreditBalance 0x4008000000000000 3.0+accountBalances all map AccountsReceivable DebitBalance 0x40c81b0000000000 12342.0+accountBalances all map RetainedEarnings DebitBalance 0x40c7ea8000000000 12245.0+accountBalances all map Purchases CreditBalance 0x3ff0000000000000 1.0+accountBalances all map Sales CreditBalance 0x4008000000000000 3.0+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 24794.0 214.0,UnexplainedAbnormalBalance Cash Debit (CreditBalance 3.0),UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 12245.0),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 1.0)]+trialBalanceFindings.debitTotal BeforeClosing scalar 0x40d8368000000000 24794.0+trialBalanceFindings.creditTotal BeforeClosing scalar 0x406ac00000000000 214.0+periodResultOfAlg all PeriodProfit 0x4010000000000000 4.0
+ test/fixtures/readout-baseline-p1/alg-double-f-sub.tsv view
@@ -0,0 +1,93 @@+# readout-baseline-p1 alg-double-f-sub; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting Hat Cash 0x000ffffffffffffe 2.2250738585072004e-308+bar all posting Hat Purchases 0x3ff0000000000000 1.0+bar all posting Hat RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+bar all posting Not AccountsReceivable 0x3ff0000000000000 1.0+bar all posting Not Sales 0x000fffffffffffff 2.225073858507201e-308+norm all scalar 0x4000000000000000 2.0+balance all result False+diffRL all Debit 0x0000000000000001 5.0e-324+compress all posting Hat AccountsReceivable 0x0000000000000001 5.0e-324+compress all posting Hat Cash 0x0010000000000000 2.2250738585072014e-308+compress all posting Hat Purchases 0x3ff0000000000000 1.0+compress all posting Hat RetainedEarnings 0x0010000000000000 2.2250738585072014e-308+compress all posting Hat Sales 0x0000000000000001 5.0e-324+compress all posting Not AccountsReceivable 0x3ff0000000000000 1.0+compress all posting Not Cash 0x0000000000000002 1.0e-323+compress all posting Not Purchases 0x0010000000000000 2.2250738585072014e-308+compress all posting Not RetainedEarnings 0x0000000000000001 5.0e-324+compress all posting Not Sales 0x0010000000000000 2.2250738585072014e-308+balanceMapBy key=account map Cash 0x800ffffffffffffe -2.2250738585072004e-308+balanceMapBy key=account map AccountsReceivable 0x3ff0000000000000 1.0+balanceMapBy key=account map RetainedEarnings 0x800fffffffffffff -2.225073858507201e-308+balanceMapBy key=account map Purchases 0xbff0000000000000 -1.0+balanceMapBy key=account map Sales 0x000fffffffffffff 2.225073858507201e-308+netPairMapBy key=account map Cash 0x0000000000000000 0.0 0x000ffffffffffffe 2.2250738585072004e-308+netPairMapBy key=account map AccountsReceivable 0x3ff0000000000000 1.0 0x0000000000000000 0.0+netPairMapBy key=account map RetainedEarnings 0x0000000000000000 0.0 0x000fffffffffffff 2.225073858507201e-308+netPairMapBy key=account map Purchases 0x0000000000000000 0.0 0x3ff0000000000000 1.0+netPairMapBy key=account map Sales 0x000fffffffffffff 2.225073858507201e-308 0x0000000000000000 0.0+postFromNetBy key=account; callback=identity-side-Not posting Not AccountsReceivable 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not Cash 0x000ffffffffffffe 2.2250738585072004e-308+postFromNetBy key=account; callback=identity-side-Not posting Not Purchases 0x3ff0000000000000 1.0+postFromNetBy key=account; callback=identity-side-Not posting Not RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+postFromNetBy key=account; callback=identity-side-Not posting Not Sales 0x000fffffffffffff 2.225073858507201e-308+projNetNorm Cash scalar 0x0000000000000002 1.0e-323+projNetNorm Sales scalar 0x0010000000000000 2.2250738585072014e-308+projNetNorm Cash+Sales scalar 0x0010000000000002 2.2250738585072024e-308+balanceBy Cash Not-Hat scalar 0x800ffffffffffffe -2.2250738585072004e-308+balanceBy Sales Not-Hat scalar 0x000fffffffffffff 2.225073858507201e-308+Pair.compare Cash notes=1 vs Cash notes=2,3 result LT+Pair.compare Sales notes=1,2 vs Sales note=3 result LT+closingEntries Alg Right+closingEntries Alg posting Hat Sales 0x000fffffffffffff 2.225073858507201e-308+closingEntries Alg posting Not Purchases 0x00ffffc000000000 7.290899512170547e-304+closingEntries Alg posting Not Purchases 0x044fffffffffffff 6.567258882077401e-288+closingEntries Alg posting Not Purchases 0x079fffffffffffff 5.915260930833873e-272+closingEntries Alg posting Not Purchases 0x0aefffffffffffff 5.3279933847805367e-256+closingEntries Alg posting Not Purchases 0x0e3fffffffffffff 4.7990298044660185e-240+closingEntries Alg posting Not Purchases 0x118fffffffffffff 4.322581767826613e-224+closingEntries Alg posting Not Purchases 0x14dfffffffffffff 3.893435527772487e-208+closingEntries Alg posting Not Purchases 0x182fffffffffffff 3.5068949584134445e-192+closingEntries Alg posting Not Purchases 0x1b7fffffffffffff 3.158730165587652e-176+closingEntries Alg posting Not Purchases 0x1ecfffffffffffff 2.845131199340899e-160+closingEntries Alg posting Not Purchases 0x221fffffffffffff 2.562666361834369e-144+closingEntries Alg posting Not Purchases 0x256fffffffffffff 2.3082446544464337e-128+closingEntries Alg posting Not Purchases 0x28bfffffffffffff 2.0790819531289796e-112+closingEntries Alg posting Not Purchases 0x2c0fffffffffffff 1.8726705418768791e-96+closingEntries Alg posting Not Purchases 0x2f5fffffffffffff 1.6867516709168835e-80+closingEntries Alg posting Not Purchases 0x32afffffffffffff 1.5192908393215676e-64+closingEntries Alg posting Not Purchases 0x35ffffffffffffff 1.368455531567204e-48+closingEntries Alg posting Not Purchases 0x394fffffffffffff 1.2325951644078308e-32+closingEntries Alg posting Not Purchases 0x3c9fffffffffffff 1.1102230246251564e-16+closingEntries Alg posting Not Purchases 0x3fefffffffffffff 0.9999999999999999+closingEntries Alg posting Not RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+closingEntries Alg posting Not RetainedEarnings 0x00ffffc000000000 7.290899512170547e-304+closingEntries Alg posting Not RetainedEarnings 0x044fffffffffffff 6.567258882077401e-288+closingEntries Alg posting Not RetainedEarnings 0x079fffffffffffff 5.915260930833873e-272+closingEntries Alg posting Not RetainedEarnings 0x0aefffffffffffff 5.3279933847805367e-256+closingEntries Alg posting Not RetainedEarnings 0x0e3fffffffffffff 4.7990298044660185e-240+closingEntries Alg posting Not RetainedEarnings 0x118fffffffffffff 4.322581767826613e-224+closingEntries Alg posting Not RetainedEarnings 0x14dfffffffffffff 3.893435527772487e-208+closingEntries Alg posting Not RetainedEarnings 0x182fffffffffffff 3.5068949584134445e-192+closingEntries Alg posting Not RetainedEarnings 0x1b7fffffffffffff 3.158730165587652e-176+closingEntries Alg posting Not RetainedEarnings 0x1ecfffffffffffff 2.845131199340899e-160+closingEntries Alg posting Not RetainedEarnings 0x221fffffffffffff 2.562666361834369e-144+closingEntries Alg posting Not RetainedEarnings 0x256fffffffffffff 2.3082446544464337e-128+closingEntries Alg posting Not RetainedEarnings 0x28bfffffffffffff 2.0790819531289796e-112+closingEntries Alg posting Not RetainedEarnings 0x2c0fffffffffffff 1.8726705418768791e-96+closingEntries Alg posting Not RetainedEarnings 0x2f5fffffffffffff 1.6867516709168835e-80+closingEntries Alg posting Not RetainedEarnings 0x32afffffffffffff 1.5192908393215676e-64+closingEntries Alg posting Not RetainedEarnings 0x35ffffffffffffff 1.368455531567204e-48+closingEntries Alg posting Not RetainedEarnings 0x394fffffffffffff 1.2325951644078308e-32+closingEntries Alg posting Not RetainedEarnings 0x3c9fffffffffffff 1.1102230246251564e-16+closingEntries Alg posting Not RetainedEarnings 0x3fefffffffffffff 0.9999999999999999+accountBalances all map Cash CreditBalance 0x000ffffffffffffe 2.2250738585072004e-308+accountBalances all map AccountsReceivable DebitBalance 0x3ff0000000000000 1.0+accountBalances all map RetainedEarnings DebitBalance 0x000fffffffffffff 2.225073858507201e-308+accountBalances all map Purchases CreditBalance 0x3ff0000000000000 1.0+accountBalances all map Sales CreditBalance 0x000fffffffffffff 2.225073858507201e-308+trialBalanceFindings BeforeClosing result [UnbalancedTrialBalance 1.0 1.0,UnexplainedAbnormalBalance Cash Debit (CreditBalance 2.2250738585072004e-308),UnexplainedAbnormalBalance RetainedEarnings Credit (DebitBalance 2.225073858507201e-308),UnexplainedAbnormalBalance Purchases Debit (CreditBalance 1.0)]+trialBalanceFindings.debitTotal BeforeClosing scalar 0x3ff0000000000000 1.0+trialBalanceFindings.creditTotal BeforeClosing scalar 0x3ff0000000000000 1.0+periodResultOfAlg all PeriodProfit 0x3ff0000000000000 1.0
+ test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-dec2.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-decimal-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash -1234.55+balanceMapBy.keyed key=account map AccountsReceivable 1214.57+balanceMapBy.keyed key=account map RetainedEarnings -0.1+balanceMapBy.keyed key=account map Purchases -0.09+balanceMapBy.keyed key=account map Sales -20.18+netPairMapBy.keyed key=account map Cash 0.3 1234.85+netPairMapBy.keyed key=account map AccountsReceivable 1214.57 0+netPairMapBy.keyed key=account map RetainedEarnings 0.2 0.3+netPairMapBy.keyed key=account map Purchases 0 0.09+netPairMapBy.keyed key=account map Sales 0 20.18+projNetNorm.keyed Cash,1 scalar 0.3
+ test/fixtures/readout-baseline-p1/alg-keyed-decimal-f-int.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-decimal-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash -3+balanceMapBy.keyed key=account map AccountsReceivable 12342+balanceMapBy.keyed key=account map RetainedEarnings -12245+balanceMapBy.keyed key=account map Purchases -1+balanceMapBy.keyed key=account map Sales 3+netPairMapBy.keyed key=account map Cash 1 4+netPairMapBy.keyed key=account map AccountsReceivable 12344 2+netPairMapBy.keyed key=account map RetainedEarnings 100 12345+netPairMapBy.keyed key=account map Purchases 2 3+netPairMapBy.keyed key=account map Sales 100 97+projNetNorm.keyed Cash,1 scalar 1
+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-cancel.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-double-f-cancel; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash 0x3ff0000000000000 1.0+balanceMapBy.keyed key=account map AccountsReceivable 0xbff0000000000000 -1.0+balanceMapBy.keyed key=account map RetainedEarnings 0x0000000000000000 0.0+balanceMapBy.keyed key=account map Purchases 0xc33fffffffffffff -9.007199254740991e15+balanceMapBy.keyed key=account map Sales 0xbff0000000000000 -1.0+netPairMapBy.keyed key=account map Cash 0x4341c37937e08000 1.0e16 0x4341c37937e08000 1.0e16+netPairMapBy.keyed key=account map AccountsReceivable 0x433fffffffffffff 9.007199254740991e15 0x4340000000000000 9.007199254740992e15+netPairMapBy.keyed key=account map RetainedEarnings 0x4341c37937e08000 1.0e16 0x4341c37937e08000 1.0e16+netPairMapBy.keyed key=account map Purchases 0x3ff0000000000000 1.0 0x4340000000000000 9.007199254740992e15+netPairMapBy.keyed key=account map Sales 0x433fffffffffffff 9.007199254740991e15 0x4340000000000000 9.007199254740992e15+projNetNorm.keyed Cash,1 scalar 0x4341c37937e08000 1.0e16
+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-dec2.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-double-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash 0xc0934a3333333333 -1234.55+balanceMapBy.keyed key=account map AccountsReceivable 0x4092fa47ae147ae1 1214.57+balanceMapBy.keyed key=account map RetainedEarnings 0xbfb9999999999998 -9.999999999999998e-2+balanceMapBy.keyed key=account map Purchases 0xbfb70a3d70a3d70b -9.000000000000001e-2+balanceMapBy.keyed key=account map Sales 0xc0342e147ae147ae -20.18+netPairMapBy.keyed key=account map Cash 0x3fd3333333333334 0.30000000000000004 0x40934b6666666666 1234.85+netPairMapBy.keyed key=account map AccountsReceivable 0x4092fa47ae147ae1 1214.57 0x0000000000000000 0.0+netPairMapBy.keyed key=account map RetainedEarnings 0x3fc999999999999a 0.2 0x3fd3333333333333 0.3+netPairMapBy.keyed key=account map Purchases 0x0000000000000000 0.0 0x3fb70a3d70a3d70b 9.000000000000001e-2+netPairMapBy.keyed key=account map Sales 0x0000000000000000 0.0 0x40342e147ae147ae 20.18+projNetNorm.keyed Cash,1 scalar 0x3fd3333333333334 0.30000000000000004
+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-exp.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-double-f-exp; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash 0x7e37e43c8800759c 1.0e300+balanceMapBy.keyed key=account map AccountsReceivable 0x3ff0000000000000 1.0+balanceMapBy.keyed key=account map RetainedEarnings 0xbff0000000000000 -1.0+balanceMapBy.keyed key=account map Purchases 0xfe37e43c8800759c -1.0e300+balanceMapBy.keyed key=account map Sales 0x7ff0000000000000 Infinity+netPairMapBy.keyed key=account map Cash 0x7e37e43c8800759c 1.0e300 0x3ff0000000000000 1.0+netPairMapBy.keyed key=account map AccountsReceivable 0x3ff0000000000000 1.0 0x01a56e1fc2f8f359 1.0e-300+netPairMapBy.keyed key=account map RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300 0x3ff0000000000000 1.0+netPairMapBy.keyed key=account map Purchases 0x01a56e1fc2f8f359 1.0e-300 0x7e37e43c8800759c 1.0e300+netPairMapBy.keyed key=account map Sales 0x7ff0000000000000 Infinity 0x0000000000000000 0.0+projNetNorm.keyed Cash,1 scalar 0x7e37e43c8800759c 1.0e300
+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-int.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-double-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash 0xc008000000000000 -3.0+balanceMapBy.keyed key=account map AccountsReceivable 0x40c81b0000000000 12342.0+balanceMapBy.keyed key=account map RetainedEarnings 0xc0c7ea8000000000 -12245.0+balanceMapBy.keyed key=account map Purchases 0xbff0000000000000 -1.0+balanceMapBy.keyed key=account map Sales 0x4008000000000000 3.0+netPairMapBy.keyed key=account map Cash 0x3ff0000000000000 1.0 0x4010000000000000 4.0+netPairMapBy.keyed key=account map AccountsReceivable 0x40c81c0000000000 12344.0 0x4000000000000000 2.0+netPairMapBy.keyed key=account map RetainedEarnings 0x4059000000000000 100.0 0x40c81c8000000000 12345.0+netPairMapBy.keyed key=account map Purchases 0x4000000000000000 2.0 0x4008000000000000 3.0+netPairMapBy.keyed key=account map Sales 0x4059000000000000 100.0 0x4058400000000000 97.0+projNetNorm.keyed Cash,1 scalar 0x3ff0000000000000 1.0
+ test/fixtures/readout-baseline-p1/alg-keyed-double-f-sub.tsv view
@@ -0,0 +1,12 @@+# readout-baseline-p1 alg-keyed-double-f-sub; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+balanceMapBy.keyed key=account map Cash 0x800ffffffffffffe -2.2250738585072004e-308+balanceMapBy.keyed key=account map AccountsReceivable 0x3ff0000000000000 1.0+balanceMapBy.keyed key=account map RetainedEarnings 0x800fffffffffffff -2.225073858507201e-308+balanceMapBy.keyed key=account map Purchases 0xbff0000000000000 -1.0+balanceMapBy.keyed key=account map Sales 0x000fffffffffffff 2.225073858507201e-308+netPairMapBy.keyed key=account map Cash 0x0000000000000002 1.0e-323 0x0010000000000000 2.2250738585072014e-308+netPairMapBy.keyed key=account map AccountsReceivable 0x3ff0000000000000 1.0 0x0000000000000001 5.0e-324+netPairMapBy.keyed key=account map RetainedEarnings 0x0000000000000001 5.0e-324 0x0010000000000000 2.2250738585072014e-308+netPairMapBy.keyed key=account map Purchases 0x0010000000000000 2.2250738585072014e-308 0x3ff0000000000000 1.0+netPairMapBy.keyed key=account map Sales 0x0010000000000000 2.2250738585072014e-308 0x0000000000000001 5.0e-324+projNetNorm.keyed Cash,1 scalar 0x0000000000000001 5.0e-324
+ test/fixtures/readout-baseline-p1/journal-decimal-f-dec2.tsv view
@@ -0,0 +1,29 @@+# readout-baseline-p1 journal-decimal-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Cash 1234.55+bar all posting -1 Hat Purchases 0.09+bar all posting -1 Hat RetainedEarnings 0.1+bar all posting -1 Hat Sales 20.18+bar all posting -1 Not AccountsReceivable 1214.57+norm all scalar 2510.53+projWithBaseNetNorm Cash scalar 0.31+projWithBaseNetNorm Sales scalar 0.01+projWithNoteBaseNetNorm note=1,Cash scalar 0.3+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0.01+compress all posting 1 Hat Purchases 0.1+compress all posting 1 Hat Sales 0.2+compress all posting 1 Not Cash 0.3+compress all posting 1 Not Purchases 0.01+compress all posting 2 Hat Cash 0.3+compress all posting 2 Hat Sales 19.99+compress all posting 2 Not Cash 0.01+compress all posting 2 Not RetainedEarnings 0.2+compress all posting 2 Not Sales 0.01+compress all posting 3 Hat AccountsReceivable 19.99+compress all posting 3 Hat Cash 1234.56+compress all posting 3 Hat RetainedEarnings 0.3+compress all posting 3 Not AccountsReceivable 1234.56+closingEntries Journal Right+closingEntries Journal posting Hat RetainedEarnings 20.18+closingEntries Journal posting Not Purchases 0.09+closingEntries Journal posting Not RetainedEarnings 0.09+closingEntries Journal posting Not Sales 20.18
+ test/fixtures/readout-baseline-p1/journal-decimal-f-int.tsv view
@@ -0,0 +1,30 @@+# readout-baseline-p1 journal-decimal-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Cash 3+bar all posting -1 Hat Purchases 1+bar all posting -1 Hat RetainedEarnings 12245+bar all posting -1 Not AccountsReceivable 12342+bar all posting -1 Not Sales 3+norm all scalar 25008+projWithBaseNetNorm Cash scalar 2+projWithBaseNetNorm Sales scalar 103+projWithNoteBaseNetNorm note=1,Cash scalar 1+projWithNoteBaseNetNorm notes=1,2,Sales scalar 103+compress all posting 1 Hat Cash 2+compress all posting 1 Hat RetainedEarnings 12345+compress all posting 1 Not Cash 1+compress all posting 1 Not Purchases 2+compress all posting 1 Not Sales 100+compress all posting 2 Hat AccountsReceivable 2+compress all posting 2 Hat Purchases 3+compress all posting 2 Hat Sales 100+compress all posting 2 Not Cash 1+compress all posting 2 Not Sales 3+compress all posting 3 Hat AccountsReceivable 1+compress all posting 3 Hat Cash 3+compress all posting 3 Not AccountsReceivable 12345+compress all posting 3 Not RetainedEarnings 100+closingEntries Journal Right+closingEntries Journal posting Hat Sales 3+closingEntries Journal posting Not Purchases 1+closingEntries Journal posting Not RetainedEarnings 1+closingEntries Journal posting Not RetainedEarnings 3
+ test/fixtures/readout-baseline-p1/journal-double-f-cancel.tsv view
@@ -0,0 +1,52 @@+# readout-baseline-p1 journal-double-f-cancel; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat AccountsReceivable 0x3ff0000000000000 1.0+bar all posting -1 Hat Purchases 0x433fffffffffffff 9.007199254740991e15+bar all posting -1 Hat Sales 0x3ff0000000000000 1.0+bar all posting -1 Not Cash 0x3ff0000000000000 1.0+norm all scalar 0x4372e1bc9bf04000 8.503599627370496e16+projWithBaseNetNorm Cash scalar 0x4341c37937e08000 1.0e16+projWithBaseNetNorm Sales scalar 0x4340000000000000 9.007199254740992e15+projWithNoteBaseNetNorm note=1,Cash scalar 0x4341c37937e08000 1.0e16+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0x4340000000000000 9.007199254740992e15+compress all posting 1 Hat AccountsReceivable 0x3ff0000000000000 1.0+compress all posting 1 Not Cash 0x4341c37937e08000 1.0e16+compress all posting 1 Not Purchases 0x3ff0000000000000 1.0+compress all posting 2 Hat AccountsReceivable 0x433fffffffffffff 9.007199254740991e15+compress all posting 2 Hat Cash 0x4341c37937e08000 1.0e16+compress all posting 2 Hat Sales 0x3ff0000000000000 1.0+compress all posting 2 Not RetainedEarnings 0x4341c37937e08000 1.0e16+compress all posting 2 Not Sales 0x4340000000000000 9.007199254740992e15+compress all posting 3 Hat Purchases 0x4340000000000000 9.007199254740992e15+compress all posting 3 Hat RetainedEarnings 0x4341c37937e08000 1.0e16+compress all posting 3 Hat Sales 0x4340000000000000 9.007199254740992e15+compress all posting 3 Not AccountsReceivable 0x433fffffffffffff 9.007199254740991e15+closingEntries Journal Right+closingEntries Journal posting Hat RetainedEarnings 0x3ff0000000000000 1.0+closingEntries Journal posting Not Purchases 0x433fffffffffffff 9.007199254740991e15+closingEntries Journal posting Not RetainedEarnings 0x433fffffffffffff 9.007199254740991e15+closingEntries Journal posting Not Sales 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 Right+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x4341c37937e08000 1.0e16+carryEntries notes=1,2;carry=4 posting 1 Hat Purchases 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 1 Not AccountsReceivable 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 2 Hat RetainedEarnings 0x4341c37937e08000 1.0e16+carryEntries notes=1,2;carry=4 posting 2 Hat Sales 0x4340000000000000 9.007199254740992e15+carryEntries notes=1,2;carry=4 posting 2 Not AccountsReceivable 0x433fffffffffffff 9.007199254740991e15+carryEntries notes=1,2;carry=4 posting 2 Not Cash 0x4341c37937e08000 1.0e16+carryEntries notes=1,2;carry=4 posting 2 Not Sales 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x4340000000000000 9.007199254740992e15+carryEntries notes=1,2;carry=4 posting 4 Not Cash 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Not Purchases 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x4341c37937e08000 1.0e16+carryEntries notes=1,2;carry=4 posting 4 Not Sales 0x433fffffffffffff 9.007199254740991e15+carryBefore notes=1,2;carry=4 Right+carryBefore notes=1,2;carry=4 posting 3 Hat Purchases 0x4340000000000000 9.007199254740992e15+carryBefore notes=1,2;carry=4 posting 3 Hat RetainedEarnings 0x4341c37937e08000 1.0e16+carryBefore notes=1,2;carry=4 posting 3 Hat Sales 0x4340000000000000 9.007199254740992e15+carryBefore notes=1,2;carry=4 posting 3 Not AccountsReceivable 0x433fffffffffffff 9.007199254740991e15+carryBefore notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x4340000000000000 9.007199254740992e15+carryBefore notes=1,2;carry=4 posting 4 Not Cash 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 4 Not Purchases 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x4341c37937e08000 1.0e16+carryBefore notes=1,2;carry=4 posting 4 Not Sales 0x433fffffffffffff 9.007199254740991e15
+ test/fixtures/readout-baseline-p1/journal-double-f-dec2.tsv view
@@ -0,0 +1,57 @@+# readout-baseline-p1 journal-double-f-dec2; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Cash 0x40934a3333333333 1234.55+bar all posting -1 Hat Purchases 0x3fb70a3d70a3d70b 9.000000000000001e-2+bar all posting -1 Hat RetainedEarnings 0x3fb9999999999998 9.999999999999998e-2+bar all posting -1 Hat Sales 0x40342e147ae147ae 20.18+bar all posting -1 Not AccountsReceivable 0x4092fa47ae147ae1 1214.57+norm all scalar 0x40a39d0f5c28f5c2 2510.5299999999997+projWithBaseNetNorm Cash scalar 0x3fd3d70a3d70a3d7 0.31+projWithBaseNetNorm Sales scalar 0x3f847ae147ae147b 1.0e-2+projWithNoteBaseNetNorm note=1,Cash scalar 0x3fd3333333333334 0.30000000000000004+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0x3f847ae147ae147b 1.0e-2+compress all posting 1 Hat Purchases 0x3fb999999999999a 0.1+compress all posting 1 Hat Sales 0x3fc999999999999a 0.2+compress all posting 1 Not Cash 0x3fd3333333333334 0.30000000000000004+compress all posting 1 Not Purchases 0x3f847ae147ae147b 1.0e-2+compress all posting 2 Hat Cash 0x3fd3333333333333 0.3+compress all posting 2 Hat Sales 0x4033fd70a3d70a3d 19.99+compress all posting 2 Not Cash 0x3f847ae147ae147b 1.0e-2+compress all posting 2 Not RetainedEarnings 0x3fc999999999999a 0.2+compress all posting 2 Not Sales 0x3f847ae147ae147b 1.0e-2+compress all posting 3 Hat AccountsReceivable 0x4033fd70a3d70a3d 19.99+compress all posting 3 Hat Cash 0x40934a3d70a3d70a 1234.56+compress all posting 3 Hat RetainedEarnings 0x3fd3333333333333 0.3+compress all posting 3 Not AccountsReceivable 0x40934a3d70a3d70a 1234.56+closingEntries Journal Right+closingEntries Journal posting Hat RetainedEarnings 0x3ce4940000000000 2.284630817861455e-15+closingEntries Journal posting Hat RetainedEarnings 0x40342e147ae147ad 20.179999999999996+closingEntries Journal posting Not Purchases 0x3c64000000000000 8.673617379884035e-18+closingEntries Journal posting Not Purchases 0x3fb70a3d70a3d70a 9.0e-2+closingEntries Journal posting Not RetainedEarnings 0x3c64000000000000 8.673617379884035e-18+closingEntries Journal posting Not RetainedEarnings 0x3fb70a3d70a3d70a 9.0e-2+closingEntries Journal posting Not Sales 0x3ce4940000000000 2.284630817861455e-15+closingEntries Journal posting Not Sales 0x40342e147ae147ad 20.179999999999996+carryEntries notes=1,2;carry=4 Right+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x3fb999999999999a 0.1+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x3fc999999999999a 0.2+carryEntries notes=1,2;carry=4 posting 1 Hat Purchases 0x3f847ae147ae147b 1.0e-2+carryEntries notes=1,2;carry=4 posting 1 Not Purchases 0x3fb999999999999a 0.1+carryEntries notes=1,2;carry=4 posting 1 Not Sales 0x3fc999999999999a 0.2+carryEntries notes=1,2;carry=4 posting 2 Hat Cash 0x3f847ae147ae147b 1.0e-2+carryEntries notes=1,2;carry=4 posting 2 Hat RetainedEarnings 0x3fc999999999999a 0.2+carryEntries notes=1,2;carry=4 posting 2 Hat Sales 0x3f847ae147ae147b 1.0e-2+carryEntries notes=1,2;carry=4 posting 2 Not Cash 0x3fd3333333333333 0.3+carryEntries notes=1,2;carry=4 posting 2 Not Sales 0x4033fd70a3d70a3d 19.99+carryEntries notes=1,2;carry=4 posting 4 Hat Purchases 0x3fb70a3d70a3d70b 9.000000000000001e-2+carryEntries notes=1,2;carry=4 posting 4 Hat Sales 0x40342e147ae147ae 20.18+carryEntries notes=1,2;carry=4 posting 4 Not Cash 0x3f847ae147ae148b 1.0000000000000028e-2+carryEntries notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x3fc999999999999a 0.2+carryBefore notes=1,2;carry=4 Right+carryBefore notes=1,2;carry=4 posting 3 Hat AccountsReceivable 0x4033fd70a3d70a3d 19.99+carryBefore notes=1,2;carry=4 posting 3 Hat Cash 0x40934a3d70a3d70a 1234.56+carryBefore notes=1,2;carry=4 posting 3 Hat RetainedEarnings 0x3fd3333333333333 0.3+carryBefore notes=1,2;carry=4 posting 3 Not AccountsReceivable 0x40934a3d70a3d70a 1234.56+carryBefore notes=1,2;carry=4 posting 4 Hat Purchases 0x3fb70a3d70a3d70b 9.000000000000001e-2+carryBefore notes=1,2;carry=4 posting 4 Hat Sales 0x40342e147ae147ae 20.18+carryBefore notes=1,2;carry=4 posting 4 Not Cash 0x3f847ae147ae148b 1.0000000000000028e-2+carryBefore notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x3fc999999999999a 0.2
+ test/fixtures/readout-baseline-p1/journal-double-f-exp.tsv view
@@ -0,0 +1,47 @@+# readout-baseline-p1 journal-double-f-exp; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Purchases 0x7e37e43c8800759c 1.0e300+bar all posting -1 Hat RetainedEarnings 0x3ff0000000000000 1.0+bar all posting -1 Not AccountsReceivable 0x3ff0000000000000 1.0+bar all posting -1 Not Cash 0x7e37e43c8800759c 1.0e300+bar all posting -1 Not Sales 0x7ff0000000000000 Infinity+norm all scalar 0x7ff0000000000000 Infinity+projWithBaseNetNorm Cash scalar 0x7e37e43c8800759c 1.0e300+projWithBaseNetNorm Sales scalar 0x7ff0000000000000 Infinity+projWithNoteBaseNetNorm note=1,Cash scalar 0x7e37e43c8800759c 1.0e300+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0x7fee42d130773b76 1.7e308+compress all posting 1 Hat Purchases 0x7e37e43c8800759c 1.0e300+compress all posting 1 Not AccountsReceivable 0x3ff0000000000000 1.0+compress all posting 1 Not Cash 0x7e37e43c8800759c 1.0e300+compress all posting 1 Not RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300+compress all posting 2 Hat Cash 0x3ff0000000000000 1.0+compress all posting 2 Not Purchases 0x01a56e1fc2f8f359 1.0e-300+compress all posting 2 Not Sales 0x7fee42d130773b76 1.7e308+compress all posting 3 Hat AccountsReceivable 0x01a56e1fc2f8f359 1.0e-300+compress all posting 3 Hat RetainedEarnings 0x3ff0000000000000 1.0+compress all posting 3 Not Sales 0x7fee42d130773b76 1.7e308+closingEntries Journal Left NonFiniteBalance+carryEntries notes=1,2;carry=4 Right+carryEntries notes=1,2;carry=4 posting 1 Hat AccountsReceivable 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x01a56e1fc2f8f359 1.0e-300+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x7e37e43c8800759c 1.0e300+carryEntries notes=1,2;carry=4 posting 1 Hat RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300+carryEntries notes=1,2;carry=4 posting 1 Not Purchases 0x7e37e43c8800759c 1.0e300+carryEntries notes=1,2;carry=4 posting 2 Hat Purchases 0x01a56e1fc2f8f359 1.0e-300+carryEntries notes=1,2;carry=4 posting 2 Hat Sales 0x7fee42d130773b76 1.7e308+carryEntries notes=1,2;carry=4 posting 2 Not Cash 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Hat Purchases 0x7e37e43c8800759c 1.0e300+carryEntries notes=1,2;carry=4 posting 4 Not AccountsReceivable 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Not Cash 0x7e37e43c8800759c 1.0e300+carryEntries notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300+carryEntries notes=1,2;carry=4 posting 4 Not Sales 0x7fee42d130773b76 1.7e308+carryBefore notes=1,2;carry=4 Right+carryBefore notes=1,2;carry=4 posting 3 Hat AccountsReceivable 0x01a56e1fc2f8f359 1.0e-300+carryBefore notes=1,2;carry=4 posting 3 Hat RetainedEarnings 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 3 Not Sales 0x7fee42d130773b76 1.7e308+carryBefore notes=1,2;carry=4 posting 4 Hat Purchases 0x7e37e43c8800759c 1.0e300+carryBefore notes=1,2;carry=4 posting 4 Not AccountsReceivable 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 4 Not Cash 0x7e37e43c8800759c 1.0e300+carryBefore notes=1,2;carry=4 posting 4 Not RetainedEarnings 0x01a56e1fc2f8f359 1.0e-300+carryBefore notes=1,2;carry=4 posting 4 Not Sales 0x7fee42d130773b76 1.7e308+carryEntries notes=2,3;carry=4;overflow Left ResultOutOfRange+carryBefore notes=2,3;carry=4;overflow Left ResultOutOfRange
+ test/fixtures/readout-baseline-p1/journal-double-f-int.tsv view
@@ -0,0 +1,54 @@+# readout-baseline-p1 journal-double-f-int; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Cash 0x4008000000000000 3.0+bar all posting -1 Hat Purchases 0x3ff0000000000000 1.0+bar all posting -1 Hat RetainedEarnings 0x40c7ea8000000000 12245.0+bar all posting -1 Not AccountsReceivable 0x40c81b0000000000 12342.0+bar all posting -1 Not Sales 0x4008000000000000 3.0+norm all scalar 0x40d86c0000000000 25008.0+projWithBaseNetNorm Cash scalar 0x4000000000000000 2.0+projWithBaseNetNorm Sales scalar 0x4059c00000000000 103.0+projWithNoteBaseNetNorm note=1,Cash scalar 0x3ff0000000000000 1.0+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0x4059c00000000000 103.0+compress all posting 1 Hat Cash 0x4000000000000000 2.0+compress all posting 1 Hat RetainedEarnings 0x40c81c8000000000 12345.0+compress all posting 1 Not Cash 0x3ff0000000000000 1.0+compress all posting 1 Not Purchases 0x4000000000000000 2.0+compress all posting 1 Not Sales 0x4059000000000000 100.0+compress all posting 2 Hat AccountsReceivable 0x4000000000000000 2.0+compress all posting 2 Hat Purchases 0x4008000000000000 3.0+compress all posting 2 Hat Sales 0x4059000000000000 100.0+compress all posting 2 Not Cash 0x3ff0000000000000 1.0+compress all posting 2 Not Sales 0x4008000000000000 3.0+compress all posting 3 Hat AccountsReceivable 0x3ff0000000000000 1.0+compress all posting 3 Hat Cash 0x4008000000000000 3.0+compress all posting 3 Not AccountsReceivable 0x40c81c8000000000 12345.0+compress all posting 3 Not RetainedEarnings 0x4059000000000000 100.0+closingEntries Journal Right+closingEntries Journal posting Hat Sales 0x4008000000000000 3.0+closingEntries Journal posting Not Purchases 0x3ff0000000000000 1.0+closingEntries Journal posting Not RetainedEarnings 0x3ff0000000000000 1.0+closingEntries Journal posting Not RetainedEarnings 0x4008000000000000 3.0+carryEntries notes=1,2;carry=4 Right+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 1 Hat Purchases 0x4000000000000000 2.0+carryEntries notes=1,2;carry=4 posting 1 Hat Sales 0x4059000000000000 100.0+carryEntries notes=1,2;carry=4 posting 1 Not Cash 0x4000000000000000 2.0+carryEntries notes=1,2;carry=4 posting 1 Not RetainedEarnings 0x40c81c8000000000 12345.0+carryEntries notes=1,2;carry=4 posting 2 Hat Cash 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 2 Hat Sales 0x4008000000000000 3.0+carryEntries notes=1,2;carry=4 posting 2 Not AccountsReceivable 0x4000000000000000 2.0+carryEntries notes=1,2;carry=4 posting 2 Not Purchases 0x4008000000000000 3.0+carryEntries notes=1,2;carry=4 posting 2 Not Sales 0x4059000000000000 100.0+carryEntries notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x4000000000000000 2.0+carryEntries notes=1,2;carry=4 posting 4 Hat Purchases 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Hat RetainedEarnings 0x40c81c8000000000 12345.0+carryEntries notes=1,2;carry=4 posting 4 Not Sales 0x4008000000000000 3.0+carryBefore notes=1,2;carry=4 Right+carryBefore notes=1,2;carry=4 posting 3 Hat AccountsReceivable 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 3 Hat Cash 0x4008000000000000 3.0+carryBefore notes=1,2;carry=4 posting 3 Not AccountsReceivable 0x40c81c8000000000 12345.0+carryBefore notes=1,2;carry=4 posting 3 Not RetainedEarnings 0x4059000000000000 100.0+carryBefore notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x4000000000000000 2.0+carryBefore notes=1,2;carry=4 posting 4 Hat Purchases 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 4 Hat RetainedEarnings 0x40c81c8000000000 12345.0+carryBefore notes=1,2;carry=4 posting 4 Not Sales 0x4008000000000000 3.0
+ test/fixtures/readout-baseline-p1/journal-double-f-sub.tsv view
@@ -0,0 +1,88 @@+# readout-baseline-p1 journal-double-f-sub; schema 1; commit 2b4e5fb6c2ff19e01cf51ba48b0d4f745e61325f+bar all posting -1 Hat Cash 0x000ffffffffffffe 2.2250738585072004e-308+bar all posting -1 Hat Purchases 0x3ff0000000000000 1.0+bar all posting -1 Hat RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+bar all posting -1 Not AccountsReceivable 0x3ff0000000000000 1.0+bar all posting -1 Not Sales 0x000fffffffffffff 2.225073858507201e-308+norm all scalar 0x4000000000000000 2.0+projWithBaseNetNorm Cash scalar 0x0000000000000002 1.0e-323+projWithBaseNetNorm Sales scalar 0x0010000000000000 2.2250738585072014e-308+projWithNoteBaseNetNorm note=1,Cash scalar 0x0000000000000001 5.0e-324+projWithNoteBaseNetNorm notes=1,2,Sales scalar 0x0010000000000000 2.2250738585072014e-308+compress all posting 1 Hat Purchases 0x3ff0000000000000 1.0+compress all posting 1 Hat RetainedEarnings 0x0010000000000000 2.2250738585072014e-308+compress all posting 1 Hat Sales 0x0000000000000001 5.0e-324+compress all posting 1 Not Cash 0x0000000000000001 5.0e-324+compress all posting 2 Hat AccountsReceivable 0x0000000000000001 5.0e-324+compress all posting 2 Not Cash 0x0000000000000001 5.0e-324+compress all posting 2 Not RetainedEarnings 0x0000000000000001 5.0e-324+compress all posting 2 Not Sales 0x0010000000000000 2.2250738585072014e-308+compress all posting 3 Hat Cash 0x0010000000000000 2.2250738585072014e-308+compress all posting 3 Not AccountsReceivable 0x3ff0000000000000 1.0+compress all posting 3 Not Purchases 0x0010000000000000 2.2250738585072014e-308+closingEntries Journal Right+closingEntries Journal posting Hat Sales 0x000fffffffffffff 2.225073858507201e-308+closingEntries Journal posting Not Purchases 0x00ffffc000000000 7.290899512170547e-304+closingEntries Journal posting Not Purchases 0x044fffffffffffff 6.567258882077401e-288+closingEntries Journal posting Not Purchases 0x079fffffffffffff 5.915260930833873e-272+closingEntries Journal posting Not Purchases 0x0aefffffffffffff 5.3279933847805367e-256+closingEntries Journal posting Not Purchases 0x0e3fffffffffffff 4.7990298044660185e-240+closingEntries Journal posting Not Purchases 0x118fffffffffffff 4.322581767826613e-224+closingEntries Journal posting Not Purchases 0x14dfffffffffffff 3.893435527772487e-208+closingEntries Journal posting Not Purchases 0x182fffffffffffff 3.5068949584134445e-192+closingEntries Journal posting Not Purchases 0x1b7fffffffffffff 3.158730165587652e-176+closingEntries Journal posting Not Purchases 0x1ecfffffffffffff 2.845131199340899e-160+closingEntries Journal posting Not Purchases 0x221fffffffffffff 2.562666361834369e-144+closingEntries Journal posting Not Purchases 0x256fffffffffffff 2.3082446544464337e-128+closingEntries Journal posting Not Purchases 0x28bfffffffffffff 2.0790819531289796e-112+closingEntries Journal posting Not Purchases 0x2c0fffffffffffff 1.8726705418768791e-96+closingEntries Journal posting Not Purchases 0x2f5fffffffffffff 1.6867516709168835e-80+closingEntries Journal posting Not Purchases 0x32afffffffffffff 1.5192908393215676e-64+closingEntries Journal posting Not Purchases 0x35ffffffffffffff 1.368455531567204e-48+closingEntries Journal posting Not Purchases 0x394fffffffffffff 1.2325951644078308e-32+closingEntries Journal posting Not Purchases 0x3c9fffffffffffff 1.1102230246251564e-16+closingEntries Journal posting Not Purchases 0x3fefffffffffffff 0.9999999999999999+closingEntries Journal posting Not RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+closingEntries Journal posting Not RetainedEarnings 0x00ffffc000000000 7.290899512170547e-304+closingEntries Journal posting Not RetainedEarnings 0x044fffffffffffff 6.567258882077401e-288+closingEntries Journal posting Not RetainedEarnings 0x079fffffffffffff 5.915260930833873e-272+closingEntries Journal posting Not RetainedEarnings 0x0aefffffffffffff 5.3279933847805367e-256+closingEntries Journal posting Not RetainedEarnings 0x0e3fffffffffffff 4.7990298044660185e-240+closingEntries Journal posting Not RetainedEarnings 0x118fffffffffffff 4.322581767826613e-224+closingEntries Journal posting Not RetainedEarnings 0x14dfffffffffffff 3.893435527772487e-208+closingEntries Journal posting Not RetainedEarnings 0x182fffffffffffff 3.5068949584134445e-192+closingEntries Journal posting Not RetainedEarnings 0x1b7fffffffffffff 3.158730165587652e-176+closingEntries Journal posting Not RetainedEarnings 0x1ecfffffffffffff 2.845131199340899e-160+closingEntries Journal posting Not RetainedEarnings 0x221fffffffffffff 2.562666361834369e-144+closingEntries Journal posting Not RetainedEarnings 0x256fffffffffffff 2.3082446544464337e-128+closingEntries Journal posting Not RetainedEarnings 0x28bfffffffffffff 2.0790819531289796e-112+closingEntries Journal posting Not RetainedEarnings 0x2c0fffffffffffff 1.8726705418768791e-96+closingEntries Journal posting Not RetainedEarnings 0x2f5fffffffffffff 1.6867516709168835e-80+closingEntries Journal posting Not RetainedEarnings 0x32afffffffffffff 1.5192908393215676e-64+closingEntries Journal posting Not RetainedEarnings 0x35ffffffffffffff 1.368455531567204e-48+closingEntries Journal posting Not RetainedEarnings 0x394fffffffffffff 1.2325951644078308e-32+closingEntries Journal posting Not RetainedEarnings 0x3c9fffffffffffff 1.1102230246251564e-16+closingEntries Journal posting Not RetainedEarnings 0x3fefffffffffffff 0.9999999999999999+carryEntries notes=1,2;carry=4 Right+carryEntries notes=1,2;carry=4 posting 1 Hat Cash 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 1 Not Purchases 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 1 Not RetainedEarnings 0x0010000000000000 2.2250738585072014e-308+carryEntries notes=1,2;carry=4 posting 1 Not Sales 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 2 Hat Cash 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 2 Hat RetainedEarnings 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 2 Hat Sales 0x0010000000000000 2.2250738585072014e-308+carryEntries notes=1,2;carry=4 posting 2 Not AccountsReceivable 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x0000000000000001 5.0e-324+carryEntries notes=1,2;carry=4 posting 4 Hat Purchases 0x3ff0000000000000 1.0+carryEntries notes=1,2;carry=4 posting 4 Hat RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+carryEntries notes=1,2;carry=4 posting 4 Not Cash 0x0000000000000002 1.0e-323+carryEntries notes=1,2;carry=4 posting 4 Not Sales 0x000fffffffffffff 2.225073858507201e-308+carryBefore notes=1,2;carry=4 Right+carryBefore notes=1,2;carry=4 posting 3 Hat Cash 0x0010000000000000 2.2250738585072014e-308+carryBefore notes=1,2;carry=4 posting 3 Not AccountsReceivable 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 3 Not Purchases 0x0010000000000000 2.2250738585072014e-308+carryBefore notes=1,2;carry=4 posting 4 Hat AccountsReceivable 0x0000000000000001 5.0e-324+carryBefore notes=1,2;carry=4 posting 4 Hat Purchases 0x3ff0000000000000 1.0+carryBefore notes=1,2;carry=4 posting 4 Hat RetainedEarnings 0x000fffffffffffff 2.225073858507201e-308+carryBefore notes=1,2;carry=4 posting 4 Not Cash 0x0000000000000002 1.0e-323+carryBefore notes=1,2;carry=4 posting 4 Not Sales 0x000fffffffffffff 2.225073858507201e-308
+ test/fixtures/registry-aliases/alias-resolution.tsv view
@@ -0,0 +1,704 @@+# registry-aliases parseAccountTitle over corpus (query, show(Either ConvError AccountTitles)); schema 1+ Left (UnknownAccount "")+ AccountsPayable Right AccountsPayable+ AccountsReceivable Right AccountsReceivable+ AccruedConsumptionTax Right AccruedConsumptionTax+ AccruedCorporateIncomeTaxes Right AccruedCorporateIncomeTaxes+ AccruedExpenses Right AccruedExpenses+ AccruedRevenue Right AccruedRevenue+ AccumulatedDepreciation Right AccumulatedDepreciation+ AdvancesPaid Right AdvancesPaid+ AdvancesReceived Right AdvancesReceived+ AdvertisingExpense Right AdvertisingExpense+ AllowanceForDoubtfulAccounts Right AllowanceForDoubtfulAccounts+ AmortizationExpense Right AmortizationExpense+ BadDebtLoss Right BadDebtLoss+ BankOverdraft Right BankOverdraft+ Building Right Building+ BusinessTrip Right BusinessTrip+ CapitalStock Right CapitalStock+ Cash Right Cash+ CashOverShort Right CashOverShort+ CentralBankNotePayable Right CentralBankNotePayable+ CentralBankPaymentExpense Right CentralBankPaymentExpense+ CentralBankPaymentIncome Right CentralBankPaymentIncome+ CommunicationExpenses Right CommunicationExpenses+ Commutation Right Commutation+ ConsumptionExpenditure Right ConsumptionExpenditure+ ConsumptionTaxPaid Right ConsumptionTaxPaid+ ConsumptionTaxReceived Right ConsumptionTaxReceived+ CorporateIncomeTaxes Right CorporateIncomeTaxes+ CreditCardReceivable Right CreditCardReceivable+ CumulativeTranslationAdjustment Right CumulativeTranslationAdjustment+ CurrentDeposits Right CurrentDeposits+ DeliveryExpenses Right DeliveryExpenses+ DepositInterestEarned Right DepositInterestEarned+ DepositPayable Right DepositPayable+ Deposits Right Deposits+ DepositsReceived Right DepositsReceived+ Depreciation Right Depreciation+ ElectronicallyRecordedObligations Right ElectronicallyRecordedObligations+ ElectronicallyRecordedReceivable Right ElectronicallyRecordedReceivable+ EquipmentInvestment Right EquipmentInvestment+ EquityInEarningsOfInvestee Right EquityInEarningsOfInvestee+ Fixtures Right Fixtures+ GainOnSalesOfFixedAssets Right GainOnSalesOfFixedAssets+ Gold Right Gold+ GovernmentService Right GovernmentService+ GrossProfit Right GrossProfit+ InterestEarned Right InterestEarned+ InterestExpense Right InterestExpense+ InvestmentInAssociate Right InvestmentInAssociate+ InvestmentSecurities Right InvestmentSecurities+ Land Right Land+ LegalRetainedEarnings Right LegalRetainedEarnings+ LoansPayable Right LoansPayable+ LongTermLoansPayable Right LongTermLoansPayable+ LongTermLoansReceivable Right LongTermLoansReceivable+ LongTermNationalBonds Right LongTermNationalBonds+ LongTermNationalBondsPayable Right LongTermNationalBondsPayable+ LossOnSalesOfFixedAssets Right LossOnSalesOfFixedAssets+ LossOnSalesOfNotesReceivable Right LossOnSalesOfNotesReceivable+ Machinery Right Machinery+ MerchandiseInventory Right MerchandiseInventory+ MiscellaneousExpenses Right MiscellaneousExpenses+ MiscellaneousIncome Right MiscellaneousIncome+ MiscellaneousLoss Right MiscellaneousLoss+ NationalBondInterestEarned Right NationalBondInterestEarned+ NetIncome Right NetIncome+ NetLoss Right NetLoss+ NotesLoansPayable Right NotesLoansPayable+ NotesLoansReceivable Right NotesLoansReceivable+ NotesPayable Right NotesPayable+ NotesReceivable Right NotesReceivable+ OrdinaryProfit Right OrdinaryProfit+ OtherPayables Right OtherPayables+ OtherReceivables Right OtherReceivables+ Patent Right Patent+ PaymentFees Right PaymentFees+ PaymentsOnBehalf Right PaymentsOnBehalf+ PettyCash Right PettyCash+ PrepaidCorporateIncomeTaxes Right PrepaidCorporateIncomeTaxes+ PrepaidExpenses Right PrepaidExpenses+ Products Right Products+ ProvisionForDoubtfulAccounts Right ProvisionForDoubtfulAccounts+ Purchases Right Purchases+ ReceiptFee Right ReceiptFee+ RecoveryOfBadDebts Right RecoveryOfBadDebts+ RentExpense Right RentExpense+ RentalIncome Right RentalIncome+ ReserveDepositPayable Right ReserveDepositPayable+ ReserveDepositReceivable Right ReserveDepositReceivable+ ReserveForDepreciation Right ReserveForDepreciation+ RetainedEarnings Right RetainedEarnings+ ReversalOfAllowanceForDoubtfulAccounts Right ReversalOfAllowanceForDoubtfulAccounts+ Sales Right Sales+ SalesCost Right SalesCost+ Securities Right Securities+ ShortTermLoansPayable Right ShortTermLoansPayable+ ShortTermLoansReceivable Right ShortTermLoansReceivable+ ShortTermNationalBonds Right ShortTermNationalBonds+ ShortTermNationalBondsPayable Right ShortTermNationalBondsPayable+ Software Right Software+ StockInvestment Right StockInvestment+ SubsidyExpense Right SubsidyExpense+ SubsidyIncome Right SubsidyIncome+ SuppliesExpenses Right SuppliesExpenses+ SuspensePayments Right SuspensePayments+ SuspenseReceipts Right SuspenseReceipts+ TaxesExpense Right TaxesExpense+ TaxesRevenue Right TaxesRevenue+ Trademark Right Trademark+ UnearnedRevenue Right UnearnedRevenue+ UnpaidDividends Right UnpaidDividends+ UtilitiesExpense Right UtilitiesExpense+ ValueAdded Right ValueAdded+ Vehicle Right Vehicle+ WageEarned Right WageEarned+ WageExpenditure Right WageExpenditure+ a/p Right AccountsPayable+ a/r Right AccountsReceivable+ accounts payable Right AccountsPayable+ accounts receivable Right AccountsReceivable+ cogs Right SalesCost+ cost of sales Right SalesCost+ クレジット売掛金 Right CreditCardReceivable+ ソフトウェア Right Software+ 仕入 Right Purchases+ 付加価値 Right ValueAdded+ 仮受消費税 Right ConsumptionTaxReceived+ 仮受金 Left (AmbiguousAccount " \20206\21463\37329 " [SuspenseReceipts,ConsumptionTaxReceived])+ 仮払法人税等 Right PrepaidCorporateIncomeTaxes+ 仮払消費税 Right ConsumptionTaxPaid+ 仮払金 Left (AmbiguousAccount " \20206\25173\37329 " [SuspensePayments,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes])+ 借入金 Left (AmbiguousAccount " \20511\20837\37329 " [LoansPayable,NotesLoansPayable,BankOverdraft])+ 備品 Right Fixtures+ 償却債権取立益 Right RecoveryOfBadDebts+ 利益準備金 Right LegalRetainedEarnings+ 前受収益 Right UnearnedRevenue+ 前受金 Right AdvancesReceived+ 前払費用 Right PrepaidExpenses+ 前払金 Right AdvancesPaid+ 受入預金 Right DepositPayable+ 受取利息 Right InterestEarned+ 受取家賃 Right RentalIncome+ 受取手形 Right NotesReceivable+ 受取手数料 Right ReceiptFee+ 商品 Right Products+ 商標権 Right Trademark+ 固定資産売却損 Right LossOnSalesOfFixedAssets+ 固定資産売却益 Right GainOnSalesOfFixedAssets+ 国債利息収入 Right NationalBondInterestEarned+ 国庫納付金収入 Right CentralBankPaymentIncome+ 国庫納付金支出 Right CentralBankPaymentExpense+ 土地 Right Land+ 売上 Right Sales+ 売上原価 Right SalesCost+ 売上総利益 Right GrossProfit+ 売掛金 Right AccountsReceivable+ 小口現金 Right PettyCash+ 広告宣伝費 Right AdvertisingExpense+ 建物 Right Building+ 当座借越 Right BankOverdraft+ 当座預金 Right CurrentDeposits+ 当期純利益 Right NetIncome+ 当期純損失 Right NetLoss+ 手形借入金 Right NotesLoansPayable+ 手形売却損 Right LossOnSalesOfNotesReceivable+ 手形貸付金 Right NotesLoansReceivable+ 投資有価証券 Left (AmbiguousAccount " \25237\36039\26377\20385\35388\21048 " [InvestmentSecurities,HeldToMaturityBonds,AvailableForSaleSecurities])+ 支払利息 Right InterestExpense+ 支払家賃 Right RentExpense+ 支払手形 Right NotesPayable+ 支払手数料 Right PaymentFees+ 政府サービス Right GovernmentService+ 旅費交通費 Right BusinessTrip+ 普通預金 Right Deposits+ 有価証券 Left (AmbiguousAccount " \26377\20385\35388\21048 " [Securities,TradingSecurities])+ 未収入金 Right OtherReceivables+ 未収収益 Right AccruedRevenue+ 未払法人税等 Right AccruedCorporateIncomeTaxes+ 未払消費税 Right AccruedConsumptionTax+ 未払費用 Right AccruedExpenses+ 未払配当金 Right UnpaidDividends+ 未払金 Left (AmbiguousAccount " \26410\25173\37329 " [OtherPayables,AccruedConsumptionTax])+ 株式投資 Right StockInvestment+ 機械装置 Right Machinery+ 水道光熱費 Right UtilitiesExpense+ 法人税等 Right CorporateIncomeTaxes+ 消耗品費 Right SuppliesExpenses+ 消費支出 Right ConsumptionExpenditure+ 減価償却引当金 Right ReserveForDepreciation+ 減価償却累計額 Right AccumulatedDepreciation+ 減価償却費 Right Depreciation+ 準備預金 Left (AmbiguousAccount " \28310\20633\38928\37329 " [ReserveDepositReceivable,ReserveDepositPayable])+ 特許権 Right Patent+ 現金 Right Cash+ 現金過不足 Right CashOverShort+ 発行銀行券 Right CentralBankNotePayable+ 発送費 Right DeliveryExpenses+ 短期借入金 Right ShortTermLoansPayable+ 短期国債 Left (AmbiguousAccount " \30701\26399\22269\20661 " [ShortTermNationalBonds,ShortTermNationalBondsPayable])+ 短期貸付金 Right ShortTermLoansReceivable+ 租税公課 Right TaxesExpense+ 租税収入 Right TaxesRevenue+ 立替金 Right PaymentsOnBehalf+ 経常利益 Right OrdinaryProfit+ 給料 Right WageExpenditure+ 繰越利益剰余金 Right RetainedEarnings+ 繰越商品 Right MerchandiseInventory+ 補助金収入 Right SubsidyIncome+ 補助金支出 Right SubsidyExpense+ 設備投資 Right EquipmentInvestment+ 買掛金 Right AccountsPayable+ 貸倒引当金 Right AllowanceForDoubtfulAccounts+ 貸倒引当金戻入 Right ReversalOfAllowanceForDoubtfulAccounts+ 貸倒引当金繰入 Right ProvisionForDoubtfulAccounts+ 貸倒損失 Right BadDebtLoss+ 賃金収入 Right WageEarned+ 資本金 Right CapitalStock+ 車両運搬具 Right Vehicle+ 通信費 Left (AmbiguousAccount " \36890\20449\36027 " [CommunicationExpenses,Commutation])+ 金 Right Gold+ 長期借入金 Right LongTermLoansPayable+ 長期国債 Left (AmbiguousAccount " \38263\26399\22269\20661 " [LongTermNationalBonds,LongTermNationalBondsPayable])+ 長期貸付金 Right LongTermLoansReceivable+ 雑損 Right MiscellaneousLoss+ 雑益 Right MiscellaneousIncome+ 雑費 Right MiscellaneousExpenses+ 電子記録債務 Right ElectronicallyRecordedObligations+ 電子記録債権 Right ElectronicallyRecordedReceivable+ 預り金 Right DepositsReceived+ 預金利息収入 Right DepositInterestEarned+A/P Right AccountsPayable+A/R Right AccountsReceivable+ACCOUNTS PAYABLE Right AccountsPayable+ACCOUNTS RECEIVABLE Right AccountsReceivable+ACCOUNTSPAYABLE Right AccountsPayable+ACCOUNTSRECEIVABLE Right AccountsReceivable+ACCRUEDCONSUMPTIONTAX Right AccruedConsumptionTax+ACCRUEDCORPORATEINCOMETAXES Right AccruedCorporateIncomeTaxes+ACCRUEDEXPENSES Right AccruedExpenses+ACCRUEDREVENUE Right AccruedRevenue+ACCUMULATEDDEPRECIATION Right AccumulatedDepreciation+ADVANCESPAID Right AdvancesPaid+ADVANCESRECEIVED Right AdvancesReceived+ADVERTISINGEXPENSE Right AdvertisingExpense+ALLOWANCEFORDOUBTFULACCOUNTS Right AllowanceForDoubtfulAccounts+AMORTIZATIONEXPENSE Right AmortizationExpense+AccountTitle Left (UnknownAccount "AccountTitle")+AccountsPayable Right AccountsPayable+AccountsReceivable Right AccountsReceivable+AccruedConsumptionTax Right AccruedConsumptionTax+AccruedCorporateIncomeTaxes Right AccruedCorporateIncomeTaxes+AccruedExpenses Right AccruedExpenses+AccruedRevenue Right AccruedRevenue+AccumulatedDepreciation Right AccumulatedDepreciation+AdvancesPaid Right AdvancesPaid+AdvancesReceived Right AdvancesReceived+AdvertisingExpense Right AdvertisingExpense+AllowanceForDoubtfulAccounts Right AllowanceForDoubtfulAccounts+AmortizationExpense Right AmortizationExpense+BADDEBTLOSS Right BadDebtLoss+BANKOVERDRAFT Right BankOverdraft+BUILDING Right Building+BUSINESSTRIP Right BusinessTrip+BadDebtLoss Right BadDebtLoss+BankOverdraft Right BankOverdraft+Building Right Building+BusinessTrip Right BusinessTrip+CAPITALSTOCK Right CapitalStock+CASH Right Cash+CASHOVERSHORT Right CashOverShort+CENTRALBANKNOTEPAYABLE Right CentralBankNotePayable+CENTRALBANKPAYMENTEXPENSE Right CentralBankPaymentExpense+CENTRALBANKPAYMENTINCOME Right CentralBankPaymentIncome+COGS Right SalesCost+COMMUNICATIONEXPENSES Right CommunicationExpenses+COMMUTATION Right Commutation+CONSUMPTIONEXPENDITURE Right ConsumptionExpenditure+CONSUMPTIONTAXPAID Right ConsumptionTaxPaid+CONSUMPTIONTAXRECEIVED Right ConsumptionTaxReceived+CORPORATEINCOMETAXES Right CorporateIncomeTaxes+COST OF SALES Right SalesCost+CREDITCARDRECEIVABLE Right CreditCardReceivable+CUMULATIVETRANSLATIONADJUSTMENT Right CumulativeTranslationAdjustment+CURRENTDEPOSITS Right CurrentDeposits+CapitalStock Right CapitalStock+Cash Right Cash+CashOverShort Right CashOverShort+CentralBankNotePayable Right CentralBankNotePayable+CentralBankPaymentExpense Right CentralBankPaymentExpense+CentralBankPaymentIncome Right CentralBankPaymentIncome+CommunicationExpenses Right CommunicationExpenses+Commutation Right Commutation+ConsumptionExpenditure Right ConsumptionExpenditure+ConsumptionTaxPaid Right ConsumptionTaxPaid+ConsumptionTaxReceived Right ConsumptionTaxReceived+CorporateIncomeTaxes Right CorporateIncomeTaxes+CreditCardReceivable Right CreditCardReceivable+CumulativeTranslationAdjustment Right CumulativeTranslationAdjustment+CurrentDeposits Right CurrentDeposits+DELIVERYEXPENSES Right DeliveryExpenses+DEPOSITINTERESTEARNED Right DepositInterestEarned+DEPOSITPAYABLE Right DepositPayable+DEPOSITS Right Deposits+DEPOSITSRECEIVED Right DepositsReceived+DEPRECIATION Right Depreciation+DeliveryExpenses Right DeliveryExpenses+DepositInterestEarned Right DepositInterestEarned+DepositPayable Right DepositPayable+Deposits Right Deposits+DepositsReceived Right DepositsReceived+Depreciation Right Depreciation+ELECTRONICALLYRECORDEDOBLIGATIONS Right ElectronicallyRecordedObligations+ELECTRONICALLYRECORDEDRECEIVABLE Right ElectronicallyRecordedReceivable+EQUIPMENTINVESTMENT Right EquipmentInvestment+EQUITYINEARNINGSOFINVESTEE Right EquityInEarningsOfInvestee+ElectronicallyRecordedObligations Right ElectronicallyRecordedObligations+ElectronicallyRecordedReceivable Right ElectronicallyRecordedReceivable+EquipmentInvestment Right EquipmentInvestment+EquityInEarningsOfInvestee Right EquityInEarningsOfInvestee+FIXTURES Right Fixtures+Fixtures Right Fixtures+GAINONSALESOFFIXEDASSETS Right GainOnSalesOfFixedAssets+GOLD Right Gold+GOVERNMENTSERVICE Right GovernmentService+GROSSPROFIT Right GrossProfit+GainOnSalesOfFixedAssets Right GainOnSalesOfFixedAssets+Gold Right Gold+Goodwill_X Left (UnknownAccount "Goodwill_X")+GovernmentService Right GovernmentService+GrossProfit Right GrossProfit+INTERESTEARNED Right InterestEarned+INTERESTEXPENSE Right InterestExpense+INVESTMENTINASSOCIATE Right InvestmentInAssociate+INVESTMENTSECURITIES Right InvestmentSecurities+InterestEarned Right InterestEarned+InterestExpense Right InterestExpense+InvestmentInAssociate Right InvestmentInAssociate+InvestmentSecurities Right InvestmentSecurities+LAND Right Land+LEGALRETAINEDEARNINGS Right LegalRetainedEarnings+LOANSPAYABLE Right LoansPayable+LONGTERMLOANSPAYABLE Right LongTermLoansPayable+LONGTERMLOANSRECEIVABLE Right LongTermLoansReceivable+LONGTERMNATIONALBONDS Right LongTermNationalBonds+LONGTERMNATIONALBONDSPAYABLE Right LongTermNationalBondsPayable+LOSSONSALESOFFIXEDASSETS Right LossOnSalesOfFixedAssets+LOSSONSALESOFNOTESRECEIVABLE Right LossOnSalesOfNotesReceivable+Land Right Land+LegalRetainedEarnings Right LegalRetainedEarnings+LoansPayable Right LoansPayable+LongTermLoansPayable Right LongTermLoansPayable+LongTermLoansReceivable Right LongTermLoansReceivable+LongTermNationalBonds Right LongTermNationalBonds+LongTermNationalBondsPayable Right LongTermNationalBondsPayable+LossOnSalesOfFixedAssets Right LossOnSalesOfFixedAssets+LossOnSalesOfNotesReceivable Right LossOnSalesOfNotesReceivable+MACHINERY Right Machinery+MERCHANDISEINVENTORY Right MerchandiseInventory+MISCELLANEOUSEXPENSES Right MiscellaneousExpenses+MISCELLANEOUSINCOME Right MiscellaneousIncome+MISCELLANEOUSLOSS Right MiscellaneousLoss+Machinery Right Machinery+MerchandiseInventory Right MerchandiseInventory+MiscellaneousExpenses Right MiscellaneousExpenses+MiscellaneousIncome Right MiscellaneousIncome+MiscellaneousLoss Right MiscellaneousLoss+NATIONALBONDINTERESTEARNED Right NationalBondInterestEarned+NETINCOME Right NetIncome+NETLOSS Right NetLoss+NOTESLOANSPAYABLE Right NotesLoansPayable+NOTESLOANSRECEIVABLE Right NotesLoansReceivable+NOTESPAYABLE Right NotesPayable+NOTESRECEIVABLE Right NotesReceivable+NationalBondInterestEarned Right NationalBondInterestEarned+NetIncome Right NetIncome+NetLoss Right NetLoss+NotesLoansPayable Right NotesLoansPayable+NotesLoansReceivable Right NotesLoansReceivable+NotesPayable Right NotesPayable+NotesReceivable Right NotesReceivable+ORDINARYPROFIT Right OrdinaryProfit+OTHERPAYABLES Right OtherPayables+OTHERRECEIVABLES Right OtherReceivables+OrdinaryProfit Right OrdinaryProfit+OtherPayables Right OtherPayables+OtherReceivables Right OtherReceivables+PATENT Right Patent+PAYMENTFEES Right PaymentFees+PAYMENTSONBEHALF Right PaymentsOnBehalf+PETTYCASH Right PettyCash+PREPAIDCORPORATEINCOMETAXES Right PrepaidCorporateIncomeTaxes+PREPAIDEXPENSES Right PrepaidExpenses+PRODUCTS Right Products+PROVISIONFORDOUBTFULACCOUNTS Right ProvisionForDoubtfulAccounts+PURCHASES Right Purchases+Patent Right Patent+PaymentFees Right PaymentFees+PaymentsOnBehalf Right PaymentsOnBehalf+PettyCash Right PettyCash+PrepaidCorporateIncomeTaxes Right PrepaidCorporateIncomeTaxes+PrepaidExpenses Right PrepaidExpenses+Products Right Products+ProvisionForDoubtfulAccounts Right ProvisionForDoubtfulAccounts+Purchases Right Purchases+RECEIPTFEE Right ReceiptFee+RECOVERYOFBADDEBTS Right RecoveryOfBadDebts+RENTALINCOME Right RentalIncome+RENTEXPENSE Right RentExpense+RESERVEDEPOSITPAYABLE Right ReserveDepositPayable+RESERVEDEPOSITRECEIVABLE Right ReserveDepositReceivable+RESERVEFORDEPRECIATION Right ReserveForDepreciation+RETAINEDEARNINGS Right RetainedEarnings+REVERSALOFALLOWANCEFORDOUBTFULACCOUNTS Right ReversalOfAllowanceForDoubtfulAccounts+ReceiptFee Right ReceiptFee+RecoveryOfBadDebts Right RecoveryOfBadDebts+RentExpense Right RentExpense+RentalIncome Right RentalIncome+ReserveDepositPayable Right ReserveDepositPayable+ReserveDepositReceivable Right ReserveDepositReceivable+ReserveForDepreciation Right ReserveForDepreciation+RetainedEarnings Right RetainedEarnings+ReversalOfAllowanceForDoubtfulAccounts Right ReversalOfAllowanceForDoubtfulAccounts+SALES Right Sales+SALESCOST Right SalesCost+SECURITIES Right Securities+SHORTTERMLOANSPAYABLE Right ShortTermLoansPayable+SHORTTERMLOANSRECEIVABLE Right ShortTermLoansReceivable+SHORTTERMNATIONALBONDS Right ShortTermNationalBonds+SHORTTERMNATIONALBONDSPAYABLE Right ShortTermNationalBondsPayable+SOFTWARE Right Software+STOCKINVESTMENT Right StockInvestment+SUBSIDYEXPENSE Right SubsidyExpense+SUBSIDYINCOME Right SubsidyIncome+SUPPLIESEXPENSES Right SuppliesExpenses+SUSPENSEPAYMENTS Right SuspensePayments+SUSPENSERECEIPTS Right SuspenseReceipts+Sales Right Sales+SalesCost Right SalesCost+Securities Right Securities+ShortTermLoansPayable Right ShortTermLoansPayable+ShortTermLoansReceivable Right ShortTermLoansReceivable+ShortTermNationalBonds Right ShortTermNationalBonds+ShortTermNationalBondsPayable Right ShortTermNationalBondsPayable+Software Right Software+StockInvestment Right StockInvestment+SubsidyExpense Right SubsidyExpense+SubsidyIncome Right SubsidyIncome+SuppliesExpenses Right SuppliesExpenses+SuspensePayments Right SuspensePayments+SuspenseReceipts Right SuspenseReceipts+TAXESEXPENSE Right TaxesExpense+TAXESREVENUE Right TaxesRevenue+TRADEMARK Right Trademark+TaxesExpense Right TaxesExpense+TaxesRevenue Right TaxesRevenue+Trademark Right Trademark+UNEARNEDREVENUE Right UnearnedRevenue+UNPAIDDIVIDENDS Right UnpaidDividends+UTILITIESEXPENSE Right UtilitiesExpense+UnearnedRevenue Right UnearnedRevenue+UnpaidDividends Right UnpaidDividends+UtilitiesExpense Right UtilitiesExpense+VALUEADDED Right ValueAdded+VEHICLE Right Vehicle+ValueAdded Right ValueAdded+Vehicle Right Vehicle+WAGEEARNED Right WageEarned+WAGEEXPENDITURE Right WageExpenditure+WageEarned Right WageEarned+WageExpenditure Right WageExpenditure+a/p Right AccountsPayable+a/r Right AccountsReceivable+accounts payable Right AccountsPayable+accounts receivable Right AccountsReceivable+accountspayable Right AccountsPayable+accountsreceivable Right AccountsReceivable+accruedconsumptiontax Right AccruedConsumptionTax+accruedcorporateincometaxes Right AccruedCorporateIncomeTaxes+accruedexpenses Right AccruedExpenses+accruedrevenue Right AccruedRevenue+accumulateddepreciation Right AccumulatedDepreciation+advancespaid Right AdvancesPaid+advancesreceived Right AdvancesReceived+advertisingexpense Right AdvertisingExpense+allowancefordoubtfulaccounts Right AllowanceForDoubtfulAccounts+amortizationexpense Right AmortizationExpense+baddebtloss Right BadDebtLoss+bankoverdraft Right BankOverdraft+building Right Building+businesstrip Right BusinessTrip+capitalstock Right CapitalStock+cash Right Cash+cashovershort Right CashOverShort+centralbanknotepayable Right CentralBankNotePayable+centralbankpaymentexpense Right CentralBankPaymentExpense+centralbankpaymentincome Right CentralBankPaymentIncome+cogs Right SalesCost+communicationexpenses Right CommunicationExpenses+commutation Right Commutation+consumptionexpenditure Right ConsumptionExpenditure+consumptiontaxpaid Right ConsumptionTaxPaid+consumptiontaxreceived Right ConsumptionTaxReceived+corporateincometaxes Right CorporateIncomeTaxes+cost of sales Right SalesCost+creditcardreceivable Right CreditCardReceivable+cumulativetranslationadjustment Right CumulativeTranslationAdjustment+currentdeposits Right CurrentDeposits+deliveryexpenses Right DeliveryExpenses+depositinterestearned Right DepositInterestEarned+depositpayable Right DepositPayable+deposits Right Deposits+depositsreceived Right DepositsReceived+depreciation Right Depreciation+electronicallyrecordedobligations Right ElectronicallyRecordedObligations+electronicallyrecordedreceivable Right ElectronicallyRecordedReceivable+equipmentinvestment Right EquipmentInvestment+equityinearningsofinvestee Right EquityInEarningsOfInvestee+fixtures Right Fixtures+gainonsalesoffixedassets Right GainOnSalesOfFixedAssets+gold Right Gold+governmentservice Right GovernmentService+grossprofit Right GrossProfit+interestearned Right InterestEarned+interestexpense Right InterestExpense+investmentinassociate Right InvestmentInAssociate+investmentsecurities Right InvestmentSecurities+land Right Land+legalretainedearnings Right LegalRetainedEarnings+loanspayable Right LoansPayable+longtermloanspayable Right LongTermLoansPayable+longtermloansreceivable Right LongTermLoansReceivable+longtermnationalbonds Right LongTermNationalBonds+longtermnationalbondspayable Right LongTermNationalBondsPayable+lossonsalesoffixedassets Right LossOnSalesOfFixedAssets+lossonsalesofnotesreceivable Right LossOnSalesOfNotesReceivable+machinery Right Machinery+merchandiseinventory Right MerchandiseInventory+miscellaneousexpenses Right MiscellaneousExpenses+miscellaneousincome Right MiscellaneousIncome+miscellaneousloss Right MiscellaneousLoss+nationalbondinterestearned Right NationalBondInterestEarned+netincome Right NetIncome+netloss Right NetLoss+notesloanspayable Right NotesLoansPayable+notesloansreceivable Right NotesLoansReceivable+notespayable Right NotesPayable+notesreceivable Right NotesReceivable+ordinaryprofit Right OrdinaryProfit+otherpayables Right OtherPayables+otherreceivables Right OtherReceivables+patent Right Patent+paymentfees Right PaymentFees+paymentsonbehalf Right PaymentsOnBehalf+pettycash Right PettyCash+prepaidcorporateincometaxes Right PrepaidCorporateIncomeTaxes+prepaidexpenses Right PrepaidExpenses+products Right Products+provisionfordoubtfulaccounts Right ProvisionForDoubtfulAccounts+purchases Right Purchases+receiptfee Right ReceiptFee+recoveryofbaddebts Right RecoveryOfBadDebts+rentalincome Right RentalIncome+rentexpense Right RentExpense+reservedepositpayable Right ReserveDepositPayable+reservedepositreceivable Right ReserveDepositReceivable+reservefordepreciation Right ReserveForDepreciation+retainedearnings Right RetainedEarnings+reversalofallowancefordoubtfulaccounts Right ReversalOfAllowanceForDoubtfulAccounts+sales Right Sales+salescost Right SalesCost+securities Right Securities+shorttermloanspayable Right ShortTermLoansPayable+shorttermloansreceivable Right ShortTermLoansReceivable+shorttermnationalbonds Right ShortTermNationalBonds+shorttermnationalbondspayable Right ShortTermNationalBondsPayable+software Right Software+stockinvestment Right StockInvestment+subsidyexpense Right SubsidyExpense+subsidyincome Right SubsidyIncome+suppliesexpenses Right SuppliesExpenses+suspensepayments Right SuspensePayments+suspensereceipts Right SuspenseReceipts+taxesexpense Right TaxesExpense+taxesrevenue Right TaxesRevenue+trademark Right Trademark+unearnedrevenue Right UnearnedRevenue+unpaiddividends Right UnpaidDividends+utilitiesexpense Right UtilitiesExpense+valueadded Right ValueAdded+vehicle Right Vehicle+wageearned Right WageEarned+wageexpenditure Right WageExpenditure+クレジット売掛金 Right CreditCardReceivable+ソフトウェア Right Software+仕入 Right Purchases+付加価値 Right ValueAdded+仮受消費税 Right ConsumptionTaxReceived+仮受金 Left (AmbiguousAccount "\20206\21463\37329" [SuspenseReceipts,ConsumptionTaxReceived])+仮払法人税等 Right PrepaidCorporateIncomeTaxes+仮払消費税 Right ConsumptionTaxPaid+仮払金 Left (AmbiguousAccount "\20206\25173\37329" [SuspensePayments,ConsumptionTaxPaid,PrepaidCorporateIncomeTaxes])+借入金 Left (AmbiguousAccount "\20511\20837\37329" [LoansPayable,NotesLoansPayable,BankOverdraft])+備品 Right Fixtures+償却債権取立益 Right RecoveryOfBadDebts+利益準備金 Right LegalRetainedEarnings+前受収益 Right UnearnedRevenue+前受金 Right AdvancesReceived+前払費用 Right PrepaidExpenses+前払金 Right AdvancesPaid+受入預金 Right DepositPayable+受取利息 Right InterestEarned+受取家賃 Right RentalIncome+受取手形 Right NotesReceivable+受取手数料 Right ReceiptFee+商品 Right Products+商標権 Right Trademark+固定資産売却損 Right LossOnSalesOfFixedAssets+固定資産売却益 Right GainOnSalesOfFixedAssets+国債利息収入 Right NationalBondInterestEarned+国庫納付金収入 Right CentralBankPaymentIncome+国庫納付金支出 Right CentralBankPaymentExpense+土地 Right Land+売上 Right Sales+売上原価 Right SalesCost+売上総利益 Right GrossProfit+売掛金 Right AccountsReceivable+小口現金 Right PettyCash+広告宣伝費 Right AdvertisingExpense+建物 Right Building+当座借越 Right BankOverdraft+当座預金 Right CurrentDeposits+当期純利益 Right NetIncome+当期純損失 Right NetLoss+手形借入金 Right NotesLoansPayable+手形売却損 Right LossOnSalesOfNotesReceivable+手形貸付金 Right NotesLoansReceivable+投資有価証券 Left (AmbiguousAccount "\25237\36039\26377\20385\35388\21048" [InvestmentSecurities,HeldToMaturityBonds,AvailableForSaleSecurities])+支払利息 Right InterestExpense+支払家賃 Right RentExpense+支払手形 Right NotesPayable+支払手数料 Right PaymentFees+政府サービス Right GovernmentService+旅費交通費 Right BusinessTrip+普通預金 Right Deposits+有価証券 Left (AmbiguousAccount "\26377\20385\35388\21048" [Securities,TradingSecurities])+未収入金 Right OtherReceivables+未収収益 Right AccruedRevenue+未払法人税等 Right AccruedCorporateIncomeTaxes+未払消費税 Right AccruedConsumptionTax+未払費用 Right AccruedExpenses+未払配当金 Right UnpaidDividends+未払金 Left (AmbiguousAccount "\26410\25173\37329" [OtherPayables,AccruedConsumptionTax])+株式投資 Right StockInvestment+機械装置 Right Machinery+水道光熱費 Right UtilitiesExpense+法人税等 Right CorporateIncomeTaxes+消耗品費 Right SuppliesExpenses+消費支出 Right ConsumptionExpenditure+減価償却引当金 Right ReserveForDepreciation+減価償却累計額 Right AccumulatedDepreciation+減価償却費 Right Depreciation+準備預金 Left (AmbiguousAccount "\28310\20633\38928\37329" [ReserveDepositReceivable,ReserveDepositPayable])+特許権 Right Patent+現金 Right Cash+現金過不足 Right CashOverShort+発行銀行券 Right CentralBankNotePayable+発送費 Right DeliveryExpenses+短期借入金 Right ShortTermLoansPayable+短期国債 Left (AmbiguousAccount "\30701\26399\22269\20661" [ShortTermNationalBonds,ShortTermNationalBondsPayable])+短期貸付金 Right ShortTermLoansReceivable+社債発行差金 Left (UnknownAccount "\31038\20661\30330\34892\24046\37329")+租税公課 Right TaxesExpense+租税収入 Right TaxesRevenue+立替金 Right PaymentsOnBehalf+経常利益 Right OrdinaryProfit+給料 Right WageExpenditure+繰越利益剰余金 Right RetainedEarnings+繰越商品 Right MerchandiseInventory+自己株式 Left (UnknownAccount "\33258\24049\26666\24335")+補助金収入 Right SubsidyIncome+補助金支出 Right SubsidyExpense+設備投資 Right EquipmentInvestment+買掛金 Right AccountsPayable+貸倒引当金 Right AllowanceForDoubtfulAccounts+貸倒引当金戻入 Right ReversalOfAllowanceForDoubtfulAccounts+貸倒引当金繰入 Right ProvisionForDoubtfulAccounts+貸倒損失 Right BadDebtLoss+賃金収入 Right WageEarned+資本金 Right CapitalStock+車両運搬具 Right Vehicle+通信費 Left (AmbiguousAccount "\36890\20449\36027" [CommunicationExpenses,Commutation])+金 Right Gold+長期借入金 Right LongTermLoansPayable+長期国債 Left (AmbiguousAccount "\38263\26399\22269\20661" [LongTermNationalBonds,LongTermNationalBondsPayable])+長期貸付金 Right LongTermLoansReceivable+雑損 Right MiscellaneousLoss+雑益 Right MiscellaneousIncome+雑費 Right MiscellaneousExpenses+電子記録債務 Right ElectronicallyRecordedObligations+電子記録債権 Right ElectronicallyRecordedReceivable+預り金 Right DepositsReceived+預金利息収入 Right DepositInterestEarned
+ test/fixtures/statement-presentation/presentation.txt view
@@ -0,0 +1,66 @@+# statement-presentation battery b1-b5; schema 1+## b1-basic+-- projCurrentAssets+100.00:@Not:<Cash+-- projFixedAssets+0+-- projDeferredAssets+0+-- projCurrentLiability+60.00:@Not:<LoansPayable+-- projFixedLiability+0+-- projCapitalStock+40.00:@Not:<CapitalStock+## b2-pl+-- projCurrentAssets+0+-- projFixedAssets+0+-- projDeferredAssets+0+-- projCurrentLiability+0+-- projFixedLiability+0+-- projCapitalStock+0+## b3-contra+-- projCurrentAssets+900.00:@Not:<Cash .+ 1000.00:@Not:<AccountsReceivable+-- projFixedAssets+800.00:@Not:<Building+-- projDeferredAssets+0+-- projCurrentLiability+400.00:@Not:<LoansPayable+-- projFixedLiability+0+-- projCapitalStock+2000.00:@Not:<CapitalStock+## b4-abnormal+-- projCurrentAssets+30.00:@Not:<Cash+-- projFixedAssets+500.00:@Not:<Building+-- projDeferredAssets+0+-- projCurrentLiability+200.00:@Not:<LoansPayable+-- projFixedLiability+0+-- projCapitalStock+0+## b5-closing+-- projCurrentAssets+200.00:@Not:<Cash .+ 900.00:@Not:<Cash .+ 1000.00:@Not:<AccountsReceivable+-- projFixedAssets+800.00:@Not:<Building+-- projDeferredAssets+0+-- projCurrentLiability+400.00:@Not:<LoansPayable+-- projFixedLiability+0+-- projCapitalStock+2000.00:@Not:<CapitalStock
+ test/fixtures/wire-format-p2/README.md view
@@ -0,0 +1,35 @@+# Binary wire format baseline for P2++The schema 1 `wire-format.tsv` records the bytes produced by the public `Binary` instances+at commit `129c06772425522028eb1c91c986e530c6b00f09`. Each row contains+the type, a description of its representative value, and the lowercase hex+encoding. The first row identifies the schema and baseline commit.+The fixture includes every `AccountTitles` and `CountUnit` constructor, every+branch of the sum types, and empty, singleton, and multi-entry structures.++The fixture was generated with GHC 9.10.2 and resolver `lts-24.4`.+The SHA-256 hash of `stack.yaml.lock` is+`420e747abf1c78073daece32e6660982f6a620109bbe7a1a8a6a0c9739494277`.+`Alg` and `Journal` encode their `HashMap` entries in traversal order, so their+bytes depend on this environment and the package versions in the lock file.++The schema 1 `decode-boundaries.tsv` records bytes and the expected acceptance+or rejection for each decoder boundary. The test compares current encodings with the committed bytes, then decodes+each committed byte sequence and checks the value. `Journal` has no `Eq`+instance, so its decoded value is compared by re-encoding. It also checks+rejection by the `Posted`, `TransferRules`, and `AccountTitles` decoders, and+the invalid-tag rejection of `Alg`. `Alg` and `Journal` deliberately accept+negative values in their serialized payloads; their decoders do not validate+the posting value domain. The fixture records that behavior without changing+the library contract.++To regenerate after an intentional format change, run from the repository+root:++```bash+EA_REGEN_GOLDEN=1 stack build --test --bench --no-run-benchmarks+```++Then run the same command without `EA_REGEN_GOLDEN` to compare the committed+fixture with the current encoding. Review every changed byte sequence before+accepting a new baseline.
+ test/fixtures/wire-format-p2/decode-boundaries.tsv view
@@ -0,0 +1,25 @@+# wire-format-p2 boundaries; schema 1; commit 129c06772425522028eb1c91c986e530c6b00f09+reject Posted negative 01ff000000000000000700000000000010ffffffffffffffcc+reject Posted infinity 010100000000000000070000000000001000000000000003cc+reject Posted above bound 01010000000000000007000000000000100000000000000351+reject TransferRules zero coefficient 000000000000000100800000803e0100000000000000000000000000+reject TransferRules overlap 000000000000000200800000803e0000800000803e00+reject AccountTitles unused code 80f3+reject AccountTitles old code 0000+reject Alg invalid tag 0000000000000003+reject Alg negative value 000000000000000101ff000000000000000700000000000010ffffffffffffffcc008000+reject Journal negative value 00000000000000010000000000000001000000000000000101ff000000000000000700000000000010ffffffffffffffcc008000+reject Alg NaN 0000000000000001010100000000000000070000000000001800000000000003cc008000+reject Alg infinity 0000000000000001010100000000000000070000000000001000000000000003cc018000+reject Alg HatNot 00000000000000010101000000000000000700000000000010ffffffffffffffcc028000+reject Alg zero HatNot 000000000000000100000000000000000000000000028000+reject Alg repeated base key 00000000000000020000000000000002800000000000000000010101000000000000000700000000000010ffffffffffffffcc00000000000000008000000000000000000000000000000000010101000000000000000700000000000010ffffffffffffffcd+reject Alg repeated dropped key 0000000000000002000000000000000280000000000000000001000000000000000000000000000000000000000000800000000000000000000000000000000000+reject Journal repeated note 00000000000000020000000000000001000000000000000000000000000000010000000000000000+reject Alg Pair Hat negative 00000000000000020000000000000001800000000000000000020000000000000000000000000001ff000000000000000700000000000010ffffffffffffffcc000000000000000100000000000000000000000000+reject Alg Pair Not negative 00000000000000020000000000000001800000000000000000010000000000000000000000000000000000000000020000000000000000000000000001ff000000000000000700000000000010ffffffffffffffcc+reject Alg Pair Hat NaN 000000000000000200000000000000018000000000000000000200000000000000000000000000010100000000000000070000000000001800000000000003cc000000000000000100000000000000000000000000+reject Alg Pair Not NaN 000000000000000200000000000000018000000000000000000100000000000000000000000000000000000000000200000000000000000000000000010100000000000000070000000000001800000000000003cc+reject Alg Pair Hat infinity 000000000000000200000000000000018000000000000000000200000000000000000000000000010100000000000000070000000000001000000000000003cc000000000000000100000000000000000000000000+reject Alg Pair Not infinity 000000000000000200000000000000018000000000000000000100000000000000000000000000000000000000000200000000000000000000000000010100000000000000070000000000001000000000000003cc+accept AccountTitles wildcard ffff
+ test/fixtures/wire-format-p2/wire-format.tsv view
@@ -0,0 +1,288 @@+# wire-format-p2; schema 1; commit 129c06772425522028eb1c91c986e530c6b00f09+Hat Hat 00+Hat Not 01+Hat HatNot 02+BaseForSingleHat sole constructor +HatBase Hat Cash 008000+HatBase Not Sales 01803e+HatBase wildcard account 02ffff+AccountTitles Cash 8000+AccountTitles Deposits 8001+AccountTitles CurrentDeposits 8002+AccountTitles Securities 8003+AccountTitles InvestmentSecurities 8004+AccountTitles InvestmentInAssociate 8005+AccountTitles LongTermNationalBonds 8006+AccountTitles ShortTermNationalBonds 8007+AccountTitles Products 8008+AccountTitles Machinery 8009+AccountTitles Building 800a+AccountTitles Vehicle 800b+AccountTitles StockInvestment 800c+AccountTitles EquipmentInvestment 800d+AccountTitles LongTermLoansReceivable 800e+AccountTitles AccountsReceivable 800f+AccountTitles ShortTermLoansReceivable 8010+AccountTitles ReserveDepositReceivable 8011+AccountTitles Gold 8012+AccountTitles GovernmentService 8013+AccountTitles CapitalStock 8014+AccountTitles RetainedEarnings 8015+AccountTitles LongTermLoansPayable 8016+AccountTitles ShortTermLoansPayable 8017+AccountTitles LoansPayable 8018+AccountTitles ReserveForDepreciation 8019+AccountTitles DepositPayable 801a+AccountTitles LongTermNationalBondsPayable 801b+AccountTitles ShortTermNationalBondsPayable 801c+AccountTitles ReserveDepositPayable 801d+AccountTitles CentralBankNotePayable 801e+AccountTitles Depreciation 801f+AccountTitles AmortizationExpense 8020+AccountTitles SalesCost 8021+AccountTitles BusinessTrip 8022+AccountTitles Commutation 8023+AccountTitles UtilitiesExpense 8024+AccountTitles RentExpense 8025+AccountTitles AdvertisingExpense 8026+AccountTitles DeliveryExpenses 8027+AccountTitles SuppliesExpenses 8028+AccountTitles MiscellaneousExpenses 8029+AccountTitles WageExpenditure 802a+AccountTitles InterestExpense 802b+AccountTitles TaxesExpense 802c+AccountTitles ConsumptionExpenditure 802d+AccountTitles SubsidyExpense 802e+AccountTitles CentralBankPaymentExpense 802f+AccountTitles Purchases 8030+AccountTitles NetIncome 8031+AccountTitles ValueAdded 8032+AccountTitles SubsidyIncome 8033+AccountTitles NationalBondInterestEarned 8034+AccountTitles DepositInterestEarned 8035+AccountTitles GrossProfit 8036+AccountTitles OrdinaryProfit 8037+AccountTitles InterestEarned 8038+AccountTitles ReceiptFee 8039+AccountTitles RentalIncome 803a+AccountTitles WageEarned 803b+AccountTitles TaxesRevenue 803c+AccountTitles CentralBankPaymentIncome 803d+AccountTitles Sales 803e+AccountTitles EquityInEarningsOfInvestee 803f+AccountTitles NetLoss 8040+AccountTitles PettyCash 8041+AccountTitles NotesReceivable 8042+AccountTitles ElectronicallyRecordedReceivable 8043+AccountTitles CreditCardReceivable 8044+AccountTitles NotesLoansReceivable 8045+AccountTitles MerchandiseInventory 8046+AccountTitles AdvancesPaid 8047+AccountTitles PrepaidExpenses 8048+AccountTitles AccruedRevenue 8049+AccountTitles OtherReceivables 804a+AccountTitles PaymentsOnBehalf 804b+AccountTitles SuspensePayments 804c+AccountTitles ConsumptionTaxPaid 804d+AccountTitles PrepaidCorporateIncomeTaxes 804e+AccountTitles Land 804f+AccountTitles Fixtures 8050+AccountTitles Patent 8051+AccountTitles Trademark 8052+AccountTitles Software 8053+AccountTitles CashOverShort 8054+AccountTitles AccountsPayable 8055+AccountTitles NotesPayable 8056+AccountTitles ElectronicallyRecordedObligations 8057+AccountTitles NotesLoansPayable 8058+AccountTitles BankOverdraft 8059+AccountTitles AdvancesReceived 805a+AccountTitles UnearnedRevenue 805b+AccountTitles AccruedExpenses 805c+AccountTitles OtherPayables 805d+AccountTitles DepositsReceived 805e+AccountTitles SuspenseReceipts 805f+AccountTitles ConsumptionTaxReceived 8060+AccountTitles AccruedConsumptionTax 8061+AccountTitles AccruedCorporateIncomeTaxes 8062+AccountTitles UnpaidDividends 8063+AccountTitles AllowanceForDoubtfulAccounts 8064+AccountTitles AccumulatedDepreciation 8065+AccountTitles LegalRetainedEarnings 8066+AccountTitles CumulativeTranslationAdjustment 8067+AccountTitles ProvisionForDoubtfulAccounts 8068+AccountTitles BadDebtLoss 8069+AccountTitles LossOnSalesOfFixedAssets 806a+AccountTitles LossOnSalesOfNotesReceivable 806b+AccountTitles PaymentFees 806c+AccountTitles MiscellaneousLoss 806d+AccountTitles CorporateIncomeTaxes 806e+AccountTitles CommunicationExpenses 806f+AccountTitles GainOnSalesOfFixedAssets 8070+AccountTitles RecoveryOfBadDebts 8071+AccountTitles MiscellaneousIncome 8072+AccountTitles ReversalOfAllowanceForDoubtfulAccounts 8073+AccountTitles TimeDeposits 8074+AccountTitles LoansReceivable 8075+AccountTitles GiftCertificatesReceived 8076+AccountTitles SecurityDepositsPaid 8077+AccountTitles SuppliesOnHand 8078+AccountTitles ContractAssets 8079+AccountTitles IncomeTaxesRefundReceivable 807a+AccountTitles WorkInProcess 807b+AccountTitles DeferredTaxAssets 807c+AccountTitles LeasedAssets 807d+AccountTitles ToolsAndInstruments 807e+AccountTitles ConstructionInProgress 807f+AccountTitles Goodwill 8080+AccountTitles SoftwareInProgress 8081+AccountTitles LongTermPrepaidExpenses 8082+AccountTitles DishonoredNotesReceivable 8083+AccountTitles PrepaidPensionCost 8084+AccountTitles NetDefinedBenefitAsset 8085+AccountTitles DepositsInSpecialAccounts 8086+AccountTitles Structures 8087+AccountTitles LeaseholdRights 8088+AccountTitles NonOperatingNotesReceivable 8089+AccountTitles NonOperatingElectronicallyRecordedReceivable 808a+AccountTitles RefundLiabilities 808b+AccountTitles NonOperatingNotesPayable 808c+AccountTitles NonOperatingElectronicallyRecordedObligations 808d+AccountTitles BonusesPayable 808e+AccountTitles AllowanceForRepairs 808f+AccountTitles AllowanceForProductWarranties 8090+AccountTitles AllowanceForBonuses 8091+AccountTitles DeferredTaxLiabilities 8092+AccountTitles LeaseObligations 8093+AccountTitles GuaranteeDepositsReceived 8094+AccountTitles AllowanceForRetirementBenefits 8095+AccountTitles LongTermOtherPayables 8096+AccountTitles NetDefinedBenefitLiability 8097+AccountTitles StockSubscriptionDeposits 8098+AccountTitles LegalCapitalSurplus 8099+AccountTitles OtherCapitalSurplus 809a+AccountTitles DividendEqualizationReserve 809b+AccountTitles RepairFundReserve 809c+AccountTitles ConstructionFundReserve 809d+AccountTitles GeneralReserve 809e+AccountTitles ValuationDifferenceOnOtherSecurities 809f+AccountTitles NonControllingInterests 80a0+AccountTitles CapitalSurplus 80a1+AccountTitles EarnedSurplus 80a2+AccountTitles ServiceRevenue 80a3+AccountTitles OperatingRevenue 80a4+AccountTitles GainOnSalesOfSecurities 80a5+AccountTitles GainOnValuationOfSecurities 80a6+AccountTitles DividendsReceived 80a7+AccountTitles InterestOnSecurities 80a8+AccountTitles GainOnSalesOfInvestmentSecurities 80a9+AccountTitles InsuranceGain 80aa+AccountTitles GainOnBargainPurchase 80ab+AccountTitles ReversalOfAllowanceForRepairs 80ac+AccountTitles ReversalOfAllowanceForProductWarranties 80ad+AccountTitles GainOnDonationOfFixedAssets 80ae+AccountTitles GainOnNationalSubsidies 80af+AccountTitles GainOnConstructionGrants 80b0+AccountTitles LandRentReceived 80b1+AccountTitles SalesRebates 80b2+AccountTitles CostOfServices 80b3+AccountTitles OperatingExpenses 80b4+AccountTitles InventoryShrinkageLoss 80b5+AccountTitles LossOnValuationOfMerchandise 80b6+AccountTitles Bonuses 80b7+AccountTitles RetirementBenefitExpenses 80b8+AccountTitles ProvisionForRepairs 80b9+AccountTitles ProvisionForBonuses 80ba+AccountTitles ProvisionForProductWarranties 80bb+AccountTitles ResearchAndDevelopmentExpenses 80bc+AccountTitles AmortizationOfGoodwill 80bd+AccountTitles AmortizationOfSoftware 80be+AccountTitles AmortizationOfPatents 80bf+AccountTitles LeaseExpenses 80c0+AccountTitles IncorporationExpenses 80c1+AccountTitles StockIssuanceCosts 80c2+AccountTitles BusinessCommencementExpenses 80c3+AccountTitles DevelopmentExpenses 80c4+AccountTitles LossOnSalesOfElectronicallyRecordedReceivables 80c5+AccountTitles LossOnSalesOfReceivables 80c6+AccountTitles LossOnSalesOfSecurities 80c7+AccountTitles LossOnValuationOfSecurities 80c8+AccountTitles LossOnSalesOfInvestmentSecurities 80c9+AccountTitles LossOnFire 80ca+AccountTitles LossOnRetirementOfFixedAssets 80cb+AccountTitles LossOnReductionOfFixedAssets 80cc+AccountTitles AdditionalIncomeTaxesForPriorPeriods 80cd+AccountTitles RefundOfIncomeTaxes 80ce+AccountTitles PurchaseRebates 80cf+AccountTitles WelfareExpenses 80d0+AccountTitles MaintenanceExpenses 80d1+AccountTitles StatutoryWelfareExpenses 80d2+AccountTitles LandRentPaid 80d3+AccountTitles InsuranceExpense 80d4+AccountTitles RepairsExpense 80d5+AccountTitles StorageExpenses 80d6+AccountTitles MembershipFees 80d7+AccountTitles IncomeSummary 80d8+AccountTitles SuspenseAccount 80d9+AccountTitles ForeignExchangeGains 80da+AccountTitles ForeignExchangeLosses 80db+AccountTitles ContraAccountForGuaranteeObligations 80dc+AccountTitles GuaranteeObligations 80dd+AccountTitles IncomeTaxesAdjustment 80de+AccountTitles BranchCurrentAccount 80df+AccountTitles HeadOfficeCurrentAccount 80e0+AccountTitles NetIncomeAttributableToNCI 80e1+AccountTitles NetLossAttributableToNCI 80e2+AccountTitles TradingSecurities 80e3+AccountTitles HeldToMaturityBonds 80e4+AccountTitles SubsidiaryStocks 80e5+AccountTitles AffiliateStocks 80e6+AccountTitles AvailableForSaleSecurities 80e7+AccountTitles ConsumptionTaxRefundReceivable 80e8+AccountTitles PropertyTaxPayable 80e9+AccountTitles DepositsReceivedFromOfficers 80ea+AccountTitles EntertainmentExpenses 80eb+AccountTitles MeetingExpenses 80ec+AccountTitles NewspaperBooksExpenses 80ed+AccountTitles RawMaterials 80ee+AccountTitles GoodsInTransit 80ef+AccountTitles SalesAllowances 80f0+AccountTitles PurchaseAllowances 80f1+AccountTitles ReversalOfAllowanceForBonuses 80f2+AccountTitles AccountTitle ffff+CountUnit Yen 00+CountUnit Dollar 01+CountUnit Euro 02+CountUnit CNY 03+CountUnit Amount 04+CountUnit wildcard 05+Pair empty 00000000000000000000000000000000+Pair Hat only 00000000000000010101000000000000000700000000000010ffffffffffffffcc0000000000000000+Pair Not only 000000000000000000000000000000010101000000000000000700000000000010ffffffffffffffcd+Pair both sides and repeated values 00000000000000030101000000000000000700000000000010ffffffffffffffcc0101000000000000000700000000000010ffffffffffffffcc0101000000000000000700000000000018ffffffffffffffcd00000000000000020101000000000000000700000000000010ffffffffffffffcd0101000000000000000700000000000010ffffffffffffffce+Alg Zero 0000000000000000+Alg singleton 00000000000000010101000000000000000700000000000010ffffffffffffffcc008000+Alg repeated base on both sides 00000000000000020000000000000002800000000000000000020101000000000000000700000000000010ffffffffffffffcc0101000000000000000700000000000010ffffffffffffffcd00000000000000010101000000000000000700000000000018ffffffffffffffcd803e000000000000000000000000000000010101000000000000000700000000000010ffffffffffffffce+TransferScale Relabel 00+TransferScale MulBy 010101000000000000000700000000000010ffffffffffffffcd+TransferScale DivBy 020101000000000000000700000000000010ffffffffffffffcb+TransferRule relabel 00800001803e00+TransferRule multiply 01803e008000010101000000000000000700000000000010ffffffffffffffcd+TransferRules empty 0000000000000000+TransferRules one 000000000000000100800000803e00+TransferRules two sorted rules 000000000000000200800000803e0001800001803e00+ClosingSide keep 00+ClosingSide flip 01+Journal empty 0000000000000000+Journal two notes and repeated base 0000000000000002000000000000000100000000000000020000000000000001800000000000000000020101000000000000000700000000000010ffffffffffffffcc0101000000000000000700000000000010ffffffffffffffcd00000000000000010101000000000000000700000000000018ffffffffffffffcd000000000000000200000000000000010101000000000000000700000000000010ffffffffffffffce01803e+Posted zero 00000000000000000000000000+Posted subnormal 0101000000000000000700000000000010fffffffffffffb9a+Posted upper bound 01010000000000000007000000000000100000000000000350+PostSide HatSide 00+PostSide NotSide 01+MoneyDecimal integer 00000000000c+MoneyDecimal two places 0200000004d2+MoneyDecimal many places 080000000001+MoneyDouble zero 00000000000000000000000000+MoneyDouble subnormal 0101000000000000000700000000000010fffffffffffffb9a+MoneyDouble large finite 010100000000000000079c7500883ce41700000000000003b0
test/fixtures/write-rows-0510/README.md view
@@ -1,8 +1,8 @@ # Write rows 0.5.1.0 golden fixtures These schema-1 fixtures freeze the exact tabular output of the pure row-generators exported by `ExchangeAlgebra.Write`. They provide compatibility-evidence for the planned Write-to-Render move in 0.6.0.0.+generators exported by `ExchangeAlgebra.IO.Output.Statements`. They preserve+compatibility for the continuing bookkeeping layouts. | Fixture | Frozen call | |---|---|@@ -14,8 +14,6 @@ | `ebex8-account-ledger-journal.tsv` | `accountLedgerRowsJournal [Cash, AccountsReceivable, Sales] ex8Journal` | | `ebex9-worksheet.tsv` | `worksheetRows ex9PreAdjustment ex9Adjustments` | | `ebex9-post-closing-trial-balance.tsv` | `postClosingTrialBalanceRows ex9AfterClosing` |-| `ebex9-bs.tsv` | `bsRows ex9AfterClosing` |-| `ebex9-pl.tsv` | `plRows ex9PostAdjustment` | The inputs are direct, order-preserving transcriptions of the top-level values in `examples/basic/elementaryBookkeepingEx6.hs` through@@ -29,13 +27,10 @@ ``` The fixtures record observed behavior, not endorsed accounting output.-In particular `ebex9-bs.tsv` omits the credit-balance `CurrentDeposits`-(bank overdraft) so its two totals differ by 150000, `ebex9-pl.tsv` lists-`Purchases` twice because `plRows` does not aggregate equal titles, and `ebex8-journal.tsv` shows the same-day reversal pair as `Sales` on both sides.-Whether to correct these is a 0.6.0.0 decision recorded in the release plan.+The legacy balance-sheet and profit-and-loss fixtures were removed in 0.6.0.0. New fixtures may be added. Any difference from an existing fixture is a test failure. Regenerate an existing fixture only for an intentional behavior change recorded in a plan. This rule preserves the evidence that the 0.6.0.0-Write-to-Render move does not change behavior.+move to `IO.Output.Statements` does not change behavior.
test/fixtures/write-rows-0510/ebex6-compound-trial-balance.tsv view
@@ -1,33 +1,33 @@ # write-rows-0510 compoundTrialBalanceRows ex6AllEntries; schema 1; commit 250cbf15d9663f163f98f0ce3650f057de80170d Debit Balance Debit Total Account Title Credit Total Credit Balance-353000 30000 Cash 383000 - 356000 CurrentDeposits 83000 273000- 200000 AccountsReceivable 140000 60000- 43000 BusinessTrip 0 43000- 180000 WageExpenditure 0 180000- 250000 Purchases 20000 230000-350000 30000 Sales 380000 - 30000 PettyCash 0 30000- 80000 NotesReceivable 0 80000- 50000 ElectronicallyRecordedReceivable 0 50000+ 30000 Cash 383000 353000+273000 356000 CurrentDeposits 83000 +60000 200000 AccountsReceivable 140000 +43000 43000 BusinessTrip 0 +180000 180000 WageExpenditure 0 +230000 250000 Purchases 20000 + 30000 Sales 380000 350000+30000 30000 PettyCash 0 +80000 80000 NotesReceivable 0 +50000 50000 ElectronicallyRecordedReceivable 0 96000 CreditCardReceivable 96000 - 150000 NotesLoansReceivable 0 150000- 15000 AdvancesPaid 0 15000- 100000 OtherReceivables 0 100000- 7000 PaymentsOnBehalf 0 7000+150000 150000 NotesLoansReceivable 0 +15000 15000 AdvancesPaid 0 +100000 100000 OtherReceivables 0 +7000 7000 PaymentsOnBehalf 0 40000 SuspensePayments 40000 -10000 90000 Fixtures 100000 + 90000 Fixtures 100000 10000 3000 CashOverShort 3000 -60000 60000 AccountsPayable 120000 -60000 0 NotesPayable 60000 -40000 0 ElectronicallyRecordedObligations 40000 -200000 0 NotesLoansPayable 200000 -30000 0 BankOverdraft 30000 -25000 0 AdvancesReceived 25000 -90000 0 OtherPayables 90000 -12000 0 DepositsReceived 12000 + 60000 AccountsPayable 120000 60000+ 0 NotesPayable 60000 60000+ 0 ElectronicallyRecordedObligations 40000 40000+ 0 NotesLoansPayable 200000 200000+ 0 BankOverdraft 30000 30000+ 0 AdvancesReceived 25000 25000+ 0 OtherPayables 90000 90000+ 0 DepositsReceived 12000 12000 60000 SuspenseReceipts 60000 - 4000 PaymentFees 0 4000- 1000 MiscellaneousLoss 0 1000- 7000 CommunicationExpenses 0 7000+4000 4000 PaymentFees 0 +1000 1000 MiscellaneousLoss 0 +7000 7000 CommunicationExpenses 0 1230000 1882000 Total 1882000 1230000
test/fixtures/write-rows-0510/ebex7-compound-trial-balance.tsv view
@@ -1,17 +1,17 @@ # write-rows-0510 compoundTrialBalanceRows (foldr (.+) Zero (map snd ex7AllBalanced)); schema 1; commit 250cbf15d9663f163f98f0ce3650f057de80170d Debit Balance Debit Total Account Title Credit Total Credit Balance- 508000 Cash 0 508000-520000 0 CurrentDeposits 520000 -60000 0 Building 60000 -120000 0 AccountsReceivable 120000 - 156000 Depreciation 0 156000-680000 520000 Fixtures 1200000 - 108000 AllowanceForDoubtfulAccounts 42000 66000- 720000 AccumulatedDepreciation 96000 624000- 42000 ProvisionForDoubtfulAccounts 0 42000- 30000 BadDebtLoss 0 30000- 40000 LossOnSalesOfFixedAssets 0 40000-60000 0 GainOnSalesOfFixedAssets 60000 -8000 0 RecoveryOfBadDebts 8000 -18000 0 ReversalOfAllowanceForDoubtfulAccounts 18000 +508000 508000 Cash 0 + 0 CurrentDeposits 520000 520000+ 0 Building 60000 60000+ 0 AccountsReceivable 120000 120000+156000 156000 Depreciation 0 + 520000 Fixtures 1200000 680000+66000 108000 AllowanceForDoubtfulAccounts 42000 +624000 720000 AccumulatedDepreciation 96000 +42000 42000 ProvisionForDoubtfulAccounts 0 +30000 30000 BadDebtLoss 0 +40000 40000 LossOnSalesOfFixedAssets 0 + 0 GainOnSalesOfFixedAssets 60000 60000+ 0 RecoveryOfBadDebts 8000 8000+ 0 ReversalOfAllowanceForDoubtfulAccounts 18000 18000 1466000 2124000 Total 2124000 1466000
test/fixtures/write-rows-0510/ebex8-compound-trial-balance.tsv view
@@ -1,23 +1,23 @@ # write-rows-0510 compoundTrialBalanceRows (foldr (.+) Zero (map snd ex8SimpleBalanced)); schema 1; commit 250cbf15d9663f163f98f0ce3650f057de80170d Debit Balance Debit Total Account Title Credit Total Credit Balance-40000 0 Cash 40000 - 1000000 CurrentDeposits 300000 700000-1000000 0 CapitalStock 1000000 - 330000 RetainedEarnings 0 330000+ 0 Cash 40000 40000+700000 1000000 CurrentDeposits 300000 + 0 CapitalStock 1000000 1000000+330000 330000 RetainedEarnings 0 12000 RentExpense 12000 7500 InterestExpense 7500 6000 InterestEarned 6000 9000 RentalIncome 9000 12000 PrepaidExpenses 12000 6000 AccruedRevenue 6000 -20000 0 ConsumptionTaxPaid 20000 + 0 ConsumptionTaxPaid 20000 20000 40000 PrepaidCorporateIncomeTaxes 40000 9000 UnearnedRevenue 9000 7500 AccruedExpenses 7500 - 35000 ConsumptionTaxReceived 0 35000-15000 0 AccruedConsumptionTax 15000 -50000 0 AccruedCorporateIncomeTaxes 50000 +35000 35000 ConsumptionTaxReceived 0 + 0 AccruedConsumptionTax 15000 15000+ 0 AccruedCorporateIncomeTaxes 50000 50000 300000 UnpaidDividends 300000 -30000 0 LegalRetainedEarnings 30000 - 90000 CorporateIncomeTaxes 0 90000+ 0 LegalRetainedEarnings 30000 30000+90000 90000 CorporateIncomeTaxes 0 1155000 1864000 Total 1864000 1155000
− test/fixtures/write-rows-0510/ebex9-bs.tsv
@@ -1,12 +0,0 @@-# write-rows-0510 bsRows ex9AfterClosing; schema 1; commit 250cbf15d9663f163f98f0ce3650f057de80170d-Asset Liability -MerchandiseInventory 110000 LoansPayable 200000-PrepaidExpenses 30000 AccountsPayable 200000-Cash 774000 OtherPayables 100000-AccountsReceivable 597000 Equity -AllowanceForDoubtfulAccounts -7000 CapitalStock 1000000-TradeReceivablesNet 590000 RetainedEarnings 364000-Fixtures 700000 Total 1864000-AccumulatedDepreciation -190000 -DepreciableAssetsNet 510000 -Total 2014000
− test/fixtures/write-rows-0510/ebex9-pl.tsv
@@ -1,14 +0,0 @@-# write-rows-0510 plRows ex9PostAdjustment; schema 1; commit 250cbf15d9663f163f98f0ce3650f057de80170d-Cost Revenue -InterestExpense 6000 Sales 900000-RentExpense 120000 ReceiptFee 40000-ProvisionForDoubtfulAccounts 6000 -CommunicationExpenses 15000 -UtilitiesExpense 12000 -Depreciation 70000 -Purchases 80000 -Purchases 400000 -BusinessTrip 25000 -SuppliesExpenses 8000 -WageExpenditure 180000 -Total 940000 Total 922000
+ tools/check-export-surface.py view
@@ -0,0 +1,180 @@+#!/usr/bin/env python3+"""Compare GHC interface exports with the P2 public module snapshots."""++import argparse+import difflib+import os+import pathlib+import re+import shutil+import shlex+import subprocess+import sys+++ROOT = pathlib.Path(__file__).resolve().parent.parent+FIXTURES = ROOT / "test/fixtures/export-surface-p2"+++def exposed_modules():+ cabal = (ROOT / "exchangealgebra.cabal").read_text()+ block = re.search(+ r"(?ms)^library\n exposed-modules:\n(.*?)(?=^ [a-z-]+:|^ other-modules:)",+ cabal,+ )+ if not block:+ raise RuntimeError("Cannot read the library exposed-modules from exchangealgebra.cabal")+ modules = re.findall(r"^ (ExchangeAlgebra(?:\.[A-Za-z0-9_]+)*)$", block.group(1), re.M)+ visual_exposed = re.search(+ r"(?m)^ if flag\(visualize\)\n exposed-modules:\n"+ r" ExchangeAlgebra\.Simulate\.Visualize$", cabal)+ if visual_exposed and os.environ.get("EA_VISUALIZE", "1") != "0":+ modules.append("ExchangeAlgebra.Simulate.Visualize")+ return modules+++def interface_path(module):+ matches = list((ROOT / ".stack-work/dist").glob(+ f"*/ghc-*/build/{module.replace('.', '/')}.hi"+ ))+ if not matches:+ raise RuntimeError(f"Expected an interface for {module}, found none")+ # An old checkout keeps one dist directory per platform and GHC that built+ # it. The interface of the build that just ran is the newest one.+ return max(matches, key=lambda path: path.stat().st_mtime)+++def export_names(module):+ result = subprocess.run(+ [COMPILER, "--show-iface", str(interface_path(module))],+ check=True, capture_output=True, text=True,+ )+ match = re.search(r"(?ms)^exports:\n(.*?)(?=^[^ \n][^\n]*:\s|^direct module dependencies:)",+ result.stdout)+ if not match:+ raise RuntimeError(f"Cannot find exports section for {module}")+ entries = []+ for line in match.group(1).splitlines():+ if not line.startswith(" "):+ break+ # A long GHC export entry continues at an indentation greater than two.+ if line.startswith(" ") and entries:+ entries[-1] += " " + line.strip()+ else:+ entries.append(line.strip())+ names = set()+ def public_name(name):+ # GHC prints the defining module for re-exports. Relocation changes+ # that origin without changing the name clients import here.+ return re.sub(r"^(?:[A-Z][A-Za-z0-9_]*\.)+", "", name)++ for entry in entries:+ head, brace, members = entry.partition("{")+ if head:+ names.add(public_name(head))+ if brace:+ if not members.endswith("}"):+ raise RuntimeError(f"Unclosed export entry in {module}: {entry}")+ names.update(public_name(member) for member in members[:-1].split())+ return sorted(names)+++def main():+ parser = argparse.ArgumentParser(description=__doc__)+ parser.add_argument(+ "--update", action="store_true", help="Rewrite the fixtures of the pinned modules")+ parser.add_argument(+ "--add", action="append", default=[], metavar="MODULE",+ help="Add an exposed module to the pinned fixtures (requires --update)")+ parser.add_argument(+ "--direct-ghc", action="store_true", help="Use ghc on PATH inside Stack tests")+ parser.add_argument(+ "--suite", action="store_true", help="Skip visualize when its interface is absent")+ args = parser.parse_args()+ if args.add and not args.update:+ parser.error("--add requires --update")+ if args.update and os.environ.get("EA_VISUALIZE", "1") == "0":+ raise RuntimeError("Refresh export fixtures with visualization enabled")+ global COMPILER+ if args.direct_ghc:+ COMPILER = shutil.which("ghc")+ if not COMPILER:+ iface = interface_path("ExchangeAlgebra")+ version_dir = next(part for part in iface.parts if part.startswith("ghc-"))+ candidates = list((pathlib.Path.home() / ".stack/programs").glob(+ f"*/{version_dir}/bin/ghc"))+ if len(candidates) != 1:+ raise RuntimeError("Cannot locate the GHC used for the interface files")+ COMPILER = str(candidates[0])+ else:+ COMPILER = subprocess.run(+ ["stack", *STACK_ARGS, "--stack-yaml", str(ROOT / "stack.yaml"),+ "path", "--compiler-exe"],+ check=True, capture_output=True, text=True,+ ).stdout.strip()+ exposed = set(exposed_modules())+ visual_disabled = os.environ.get("EA_VISUALIZE", "1") == "0"+ if args.suite and "ExchangeAlgebra.Simulate.Visualize" in exposed:+ visual_iface = list((ROOT / ".stack-work/dist").glob(+ "*/ghc-*/build/ExchangeAlgebra/Simulate/Visualize.hi"))+ if not visual_iface:+ exposed.remove("ExchangeAlgebra.Simulate.Visualize")+ visual_disabled = True+ baseline = FIXTURES / "counts.tsv"+ if not baseline.exists():+ raise RuntimeError(f"Missing module inventory: {baseline}")+ inventory = dict(line.split("\t", 1) for line in+ baseline.read_text().splitlines()[1:])+ pinned = set(inventory)+ fixtures = {path.stem for path in FIXTURES.glob("ExchangeAlgebra*.txt")}+ if pinned != fixtures:+ raise RuntimeError("The module inventory and export fixtures differ")+ if args.update:+ absent = pinned - exposed+ if absent:+ raise RuntimeError(+ f"Previously exposed modules are missing: {sorted(absent)}. "+ "For an intentional removal, manually delete the fixture and its counts.tsv row.")+ unknown = set(args.add) - exposed+ if unknown:+ raise RuntimeError(f"Cannot add modules that are not exposed: {sorted(unknown)}")+ pinned.update(args.add)+ modules = list(inventory) + sorted(pinned - set(inventory))+ else:+ for module, expected_count in inventory.items():+ actual_count = len((FIXTURES / f"{module}.txt").read_text().splitlines())+ if actual_count != int(expected_count):+ raise RuntimeError(f"The module inventory count differs for {module}")+ optional = {"ExchangeAlgebra.Simulate.Visualize"} if visual_disabled else set()+ absent = pinned - exposed - optional+ if absent:+ raise RuntimeError(f"Previously exposed modules are missing: {sorted(absent)}")+ if "ExchangeAlgebra.Simulate.Visualize" not in exposed:+ pinned.discard("ExchangeAlgebra.Simulate.Visualize")+ modules = sorted(pinned)+ failed = False+ counts = []+ FIXTURES.mkdir(parents=True, exist_ok=True)+ for module in modules:+ current = [name + "\n" for name in export_names(module)]+ fixture = FIXTURES / f"{module}.txt"+ if args.update:+ fixture.write_text("".join(current))+ else:+ expected = fixture.read_text().splitlines(keepends=True) if fixture.exists() else []+ if current != expected:+ failed = True+ sys.stdout.writelines(difflib.unified_diff(+ expected, current, fromfile=str(fixture), tofile=module + " current"))+ print(f"{module}: {len(current)} names")+ counts.append((module, len(current)))+ if args.update:+ (FIXTURES / "counts.tsv").write_text(+ "module\tnames\n" + "".join(+ f"{module}\t{count}\n" for module, count in counts))+ return int(failed)+++if __name__ == "__main__":+ STACK_ARGS = shlex.split(os.environ.get("STACK_ARGS", ""))+ sys.exit(main())
+ tools/check-orphans.py view
@@ -0,0 +1,84 @@+#!/usr/bin/env python3+"""Reject orphan instance warnings that are absent from the P2 baseline."""++import argparse+from collections import Counter+import os+import pathlib+import re+import shlex+import subprocess+import sys+++ROOT = pathlib.Path(__file__).resolve().parent.parent+BASELINE = ROOT / "test/fixtures/export-surface-p2/orphans.txt"+++def main():+ parser = argparse.ArgumentParser(description=__doc__)+ parser.add_argument("--update", action="store_true", help="Write the current warning set")+ args = parser.parse_args()+ command = ["stack", *shlex.split(os.environ.get("STACK_ARGS", "")),+ "--stack-yaml", str(ROOT / "stack.yaml"), "build"]+ if os.environ.get("EA_VISUALIZE", "1") == "0":+ command.extend(["exchangealgebra", "--test", "--no-run-tests"])+ command.extend(["--flag", "exchangealgebra:-visualize"])+ else:+ command.extend(["--test", "--bench", "--no-run-tests",+ "--no-run-benchmarks"])+ command.extend(["--force-dirty", "--ghc-options=-fforce-recomp -Worphans"])+ result = subprocess.run(command, cwd=ROOT, text=True,+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)+ if result.returncode:+ print(result.stdout, file=sys.stderr)+ return result.returncode+ warnings = set()+ blocks = re.split(r"(?=^.*?:\d+:\d+: warning:.*\[-Worphans\])",+ result.stdout, flags=re.M)+ for block in blocks:+ header = re.match(r"^(.+?):\d+:\d+: warning:.*\[-Worphans\]", block)+ if not header:+ continue+ lines = block.splitlines()+ marker = next((index for index, line in enumerate(lines)+ if re.search(r"Orphan .* instance:", line)), None)+ if marker is None:+ raise RuntimeError(f"Cannot parse orphan warning: {block[:250]}")+ first = lines[marker].split(" instance:", 1)[1].strip()+ declaration_parts = [first] if first else []+ for line in lines[marker + 1:]:+ detail = re.sub(r"^[^\s>]+\s*>\s*", "", line).strip()+ if detail.startswith(("Suggested fix:", "|")) or re.match(r"^\d+\s*\|", detail):+ break+ if detail:+ declaration_parts.append(detail)+ declaration = " ".join(declaration_parts)+ if not declaration.startswith(("instance ", "type instance ", "data instance ")):+ raise RuntimeError(f"Cannot parse orphan declaration: {block[:250]}")+ source = re.sub(r"^[^\s>]+\s*>\s*", "", header.group(1)).strip()+ path = pathlib.Path(source).resolve().relative_to(ROOT)+ warnings.add(f"{path}: {declaration}")+ if args.update:+ BASELINE.parent.mkdir(parents=True, exist_ok=True)+ BASELINE.write_text("".join(item + "\n" for item in sorted(warnings)))+ elif not BASELINE.exists():+ raise RuntimeError(f"Missing orphan baseline: {BASELINE}")+ else:+ baseline = set(BASELINE.read_text().splitlines())+ known_instances = Counter(line.split(": ", 1)[1] for line in baseline)+ current_instances = Counter(warning.split(": ", 1)[1] for warning in warnings)+ added = {instance: count - known_instances[instance]+ for instance, count in current_instances.items()+ if count > known_instances[instance]}+ if added:+ print("New orphan instances:", *(+ f"{instance}: +{added[instance]}" for instance in sorted(added)),+ sep="\n", file=sys.stderr)+ return 1+ print(f"[PASS] {len(warnings)} orphan instances; no additions")+ return 0+++if __name__ == "__main__":+ sys.exit(main())