packages feed

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 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())