Skip to content

Add Ledger model + enums (rollout slice 0a)#5836

Draft
buchen wants to merge 1 commit into
portfolio-performance:masterfrom
buchen:wip/ledger-phase0a
Draft

Add Ledger model + enums (rollout slice 0a)#5836
buchen wants to merge 1 commit into
portfolio-performance:masterfrom
buchen:wip/ledger-phase0a

Conversation

@buchen

@buchen buchen commented Jul 4, 2026

Copy link
Copy Markdown
Member

Verbatim extraction of the pure-data Ledger model, configuration enums/definitions, and structural validator from pr/5816-ledger as a compile-only island with no callers. No persistence, no version bump, no Client wiring. Foundation for slices 0b (migrator) and 0c (projection).

  • 52 production files: model/ledger + configuration (+ rule), minus the two behavior-reaching files (LedgerMutationContext, LedgerNativeEntryDefinitionValidator), plus model/LedgerDiagnosticCode.
  • 9 model-only unit tests (75 methods).
  • 33 English NLS keys for the structural validator + diagnostic formatter.

Excluded (later slices): compatibility, legacy, nativeentry, projection packages; persistence and Client wiring.

@buchen buchen force-pushed the wip/ledger-phase0a branch from a51decf to 32e0f2d Compare July 4, 2026 19:02
@buchen

buchen commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

@Morpheus1w3 here an attempt for phase 0a. I am open to discuss the scoping of the phases. Please let me know. The change still includes a lot of "needed for possible future extensions" where I am unsure when to prune this. In my experience it is not good to predict how and if a feature will be implemented in the future.

I keep it as draft - it is not merge ready - it is a basis for discussion.

@buchen buchen marked this pull request as draft July 4, 2026 19:05
Verbatim extraction of the pure-data Ledger model, configuration
enums/definitions, and structural validator from pr/5816-ledger as a
compile-only island with no callers. No persistence, no version bump,
no Client wiring. Foundation for slices 0b (migrator) and 0c (projection).

- 52 production files: model/ledger + configuration (+ rule), minus the
  two behavior-reaching files (LedgerMutationContext,
  LedgerNativeEntryDefinitionValidator), plus model/LedgerDiagnosticCode.
- 9 model-only unit tests (75 methods).
- 33 English NLS keys for the structural validator + diagnostic formatter.

Excluded (later slices): compatibility, legacy, nativeentry, projection
packages; persistence and Client wiring.
@buchen buchen force-pushed the wip/ledger-phase0a branch from 32e0f2d to 595ef78 Compare July 4, 2026 19:13
@buchen

buchen commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

To have something more visual, I asked Claude to visualize the core entities. (I believe this reflects commit afd3c56 of the #5816 pull request)

Ledger data model (rollout slice 0a)

This document describes the Ledger data model as it exists after slice 0a — the dormant,
pure-data model island (model/ledger + model/ledger/configuration). It is the persisted-truth
model from PR #5816, extracted as inert data structures with no callers, no persistence, and no
version bump
yet. The projection layer that rebuilds legacy AccountTransaction /
PortfolioTransaction views is not part of 0a and is therefore not shown here.


1. Mental model

One real-world business event = one LedgerEntry. The entry carries a list of postings
(the money / security legs — cash out, shares in, a fee, a tax) and a list of typed parameters
(extra facts that don't fit a leg, e.g. a ratio or an ex-date). A posting may point at an existing
Security / Account / Portfolio.

Everything gets its meaning from an enum vocabulary (§3) and a static shape catalog of
definitions (§4) that declares which postings, parameters, and roles each entry type is allowed to have.


2. Runtime entity graph

The object graph that lives in memory. Ledger is the aggregate root; LedgerEntry composes its
postings and parameters; postings reference the existing model entities by object pointer.

erDiagram
    LEDGER              ||--o{ LEDGER_ENTRY     : "entries (0..*)"
    LEDGER_ENTRY        ||--|{ LEDGER_POSTING   : "postings (1..*)"
    LEDGER_ENTRY        ||--o{ LEDGER_PARAMETER : "entry parameters (0..*)"
    LEDGER_POSTING      ||--o{ LEDGER_PARAMETER : "posting parameters (0..*)"
    LEDGER_POSTING      }o--o| SECURITY         : "references"
    LEDGER_POSTING      }o--o| ACCOUNT          : "references"
    LEDGER_POSTING      }o--o| PORTFOLIO        : "references"

    LEDGER {
        List_LedgerEntry entries
    }
    LEDGER_ENTRY {
        string        uuid PK
        LedgerEntryType type
        LocalDateTime dateTime
        string        note
        string        source
        Instant       updatedAt
        string        generatedByPlanKey
        LocalDate     planExecutionDate
        Integer       planExecutionSequence
        string        preferredViewKind
    }
    LEDGER_POSTING {
        string                     uuid PK
        LedgerPostingType          type
        long                       amount
        string                     currency
        Long                       forexAmount
        string                     forexCurrency
        BigDecimal                 exchangeRate
        long                       shares
        LedgerPostingSemanticRole  semanticRole
        LedgerPostingDirection     direction
        CorporateActionLeg         corporateActionLeg
        LedgerPostingUnitRole      unitRole
        string                     groupKey
        string                     localKey
        Security                   security FK
        Account                    account FK
        Portfolio                  portfolio FK
    }
    LEDGER_PARAMETER {
        LedgerParameterType type
        ValueKind           valueKind
        T                   value
    }
    SECURITY  { string name }
    ACCOUNT   { string name }
    PORTFOLIO { string name }
Loading

Notes

  • A LedgerParameter is attached either to an entry (event-level, e.g. EX_DATE) or to a
    single posting (leg-level). It is one generic key→value pair: type is the key, valueKind says how
    the value is typed, value is the payload.
  • A posting's groupKey / localKey and the role fields (semanticRole, direction,
    corporateActionLeg, unitRole) are what a later projection slice will use to rebuild the legacy
    transaction views. In 0a they are inert data.
  • Which posting fields are meaningful (currency required? shares meaningful? account pointer relevant?)
    is declared by the posting type's characteristics — see §3/§4.

3. The enum vocabulary (the "shape" system)

Each list member above is given meaning by an enum. Every enum value carries a stable persistence
code
— existing codes must never change meaning.

erDiagram
    LEDGER_ENTRY     }o--|| LEDGER_ENTRY_TYPE      : "type"
    LEDGER_ENTRY_TYPE }o--|| SHAPE                 : "classified by"

    LEDGER_POSTING   }o--|| LEDGER_POSTING_TYPE    : "type"
    LEDGER_POSTING   }o--o| POSTING_SEMANTIC_ROLE  : "semanticRole"
    LEDGER_POSTING   }o--o| POSTING_DIRECTION      : "direction"
    LEDGER_POSTING   }o--o| POSTING_UNIT_ROLE      : "unitRole"
    LEDGER_POSTING   }o--o| CORPORATE_ACTION_LEG   : "corporateActionLeg"
    LEDGER_POSTING_TYPE }o--|| COMPONENT_CLASS     : "exactly one"
    LEDGER_POSTING_TYPE }o--o{ CHARACTERISTIC      : "zero or more flags"

    LEDGER_PARAMETER }o--|| LEDGER_PARAMETER_TYPE  : "type"
    LEDGER_PARAMETER }o--|| VALUE_KIND             : "valueKind"
    LEDGER_PARAMETER_TYPE }o--|| SCOPE             : "scoped by"
    LEDGER_PARAMETER_TYPE }o--|| VALUE_KIND        : "expects"
    LEDGER_PARAMETER_TYPE }o--o| PARAMETER_CODE_DOMAIN : "optional controlled codes"

    LEDGER_ENTRY_TYPE {
        int    id
        string code
    }
    SHAPE {
        enum LEGACY_FIXED
        enum LEDGER_NATIVE_TARGETED
    }
    LEDGER_POSTING_TYPE {
        string code
    }
    COMPONENT_CLASS  { enum CASH_SECURITY_FEE_TAX_etc }
    CHARACTERISTIC   { enum MONEY_BEARING_CURRENCY_REQUIRED_etc }
    LEDGER_PARAMETER_TYPE {
        string code
    }
    SCOPE            { enum GENERAL_CORPORATE_ACTION }
    VALUE_KIND       { enum STRING_DECIMAL_LONG_MONEY_SECURITY_etc }
    PARAMETER_CODE_DOMAIN { enum CONTROLLED_VOCABULARIES }
Loading
  • LedgerEntryType — the master switch (20 kinds). Its private Shape splits them:
    • LEGACY_FIXED (ids 1–15): DEPOSIT, REMOVAL, INTEREST, INTEREST_CHARGE, FEES, FEES_REFUND, TAXES, TAX_REFUND, DIVIDENDS, BUY, SELL, CASH_TRANSFER, SECURITY_TRANSFER, DELIVERY_INBOUND, DELIVERY_OUTBOUND — map 1:1 onto today's transaction kinds.
    • LEDGER_NATIVE_TARGETED (ids 16–20): SPIN_OFF, STOCK_DIVIDEND, BONUS_ISSUE, RIGHTS_DISTRIBUTION, BOND_CONVERSION — corporate actions with no clean legacy equivalent.
  • LedgerPostingType (CASH, SECURITY, CASH_COMPENSATION, FEE, TAX, GROSS_VALUE, FOREX, RIGHT, BOND, ACCRUED_INTEREST, PRINCIPAL_REDEMPTION) — one ComponentClass (broad category) plus a set of
    Characteristic capability flags. The flags are why one flat LedgerPosting can safely hold both
    money and security fields: they declare which fields apply.
  • LedgerParameterType (~50 values) — each declares a Scope (GENERAL vs CORPORATE_ACTION),
    an expected ValueKind, and optionally a LedgerParameterCodeDomain (controlled string
    vocabulary; only the code string is persisted, so codes must stay stable).

4. The shape catalog (static definitions)

The definition/registry layer is the self-describing rulebook: for every LedgerEntryType and
LedgerPostingType there is exactly one immutable definition declaring the allowed shape. These are
static configuration data (no callers in 0a); the migrator (0b), assembler, and validator consult them.

erDiagram
    ENTRY_DEFINITION_REGISTRY   ||--|{ LEDGER_ENTRY_DEFINITION        : "one per entry type"
    LEDGER_ENTRY_TYPE           ||--|| LEDGER_ENTRY_DEFINITION        : "defines"
    POSTINGTYPE_DEF_REGISTRY    ||--|{ LEDGER_POSTINGTYPE_DEFINITION  : "one per posting type"
    LEDGER_POSTING_TYPE         ||--|| LEDGER_POSTINGTYPE_DEFINITION  : "defines"

    LEDGER_ENTRY_DEFINITION     ||--o{ LEG_DEFINITION                 : "legDefinitions"
    LEDGER_ENTRY_DEFINITION     ||--o| NATIVE_ENTRY_SHAPE            : "nativeShape"
    LEDGER_ENTRY_DEFINITION     ||--o{ POSTING_RULE                  : "postingRules"
    LEDGER_ENTRY_DEFINITION     ||--o{ PARAMETER_RULE                : "entry/posting parameterRules"
    LEDGER_ENTRY_DEFINITION     ||--o{ PROJECTION_RULE               : "projectionRules"
    LEDGER_ENTRY_DEFINITION     ||--o{ POSTING_GROUP_RULE            : "postingGroupRules"
    LEDGER_ENTRY_DEFINITION     ||--o{ REQUIREMENT_GROUP             : "alternativeRequirementGroups"
    LEDGER_ENTRY_DEFINITION     }o--o{ LEDGER_PROJECTION_ROLE        : "projectionRoles"
    LEDGER_ENTRY_DEFINITION     }o--|| REPORTING_CLASS               : "reportingClass"
    LEDGER_ENTRY_DEFINITION     }o--|| PERFORMANCE_TREATMENT         : "performanceTreatment"
    LEG_DEFINITION              }o--|| LEDGER_LEG_ROLE               : "legRole"
    LEG_DEFINITION              }o--|| LEG_CARDINALITY               : "cardinality"

    LEDGER_ENTRY_DEFINITION {
        LedgerEntryType   entryType PK
        LedgerNativeEntryShape nativeShape
        Set_PostingType   postingTypes
        Set_ParameterType entryParameterTypes
        Set_ParameterType postingParameterTypes
        Set_ProjectionRole projectionRoles
        LedgerReportingClass reportingClass
        LedgerPerformanceTreatment performanceTreatment
    }
    LEDGER_POSTINGTYPE_DEFINITION {
        LedgerPostingType postingType PK
        ComponentClass    componentClass
        Set_Characteristic characteristics
    }
    LEG_DEFINITION {
        LedgerLegRole     legRole
        LedgerLegCardinality cardinality
    }
Loading
  • LedgerEntryDefinition (built by LedgerEntryDefinitionRegistry) is the per-entry-type rulebook:
    which posting types and parameter types are allowed, which legs exist (LedgerLegDefinition +
    LedgerLegRole + LedgerLegCardinality), the native shape for corporate actions
    (LedgerNativeEntryShape), the projection roles it will expose, and validation rules
    (LedgerPostingRule, LedgerParameterRule, LedgerProjectionRule, LedgerPostingGroupRule,
    LedgerRequirementGroup).
  • LedgerPostingTypeDefinition (built by LedgerPostingTypeDefinitionRegistry) binds each posting
    type to its ComponentClass and Characteristic flag set.
  • LedgerStructuralValidator (also in 0a) checks a concrete Ledger graph against these definitions
    — unique UUIDs, required fields, allowed parameter codes/value-kinds, and projection-role cardinality.

5. What is intentionally absent in 0a

  • No projection refs / memberships. How a ledger entry is rebuilt into legacy
    AccountTransaction / PortfolioTransaction / CrossEntry views lives in the excluded
    model/ledger/projection package (a later slice, 0c).
  • No persistence. No protobuf PLedger*, no XML <ledger>, no Client.ledger field, and
    Client.CURRENT_VERSION is unchanged. Nothing in the running application constructs a Ledger yet.
  • No mutation/edit API. LedgerMutationContext and the compatibility creators/editors/converters
    are out of scope for 0a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants