Datalog accounting policies

Copy Markdown View Source

Accounting policies answer how should this event be accounted for? They are Datalog-backed rules in Logistiki.Knowledge.Program.

Policy selection

The policy/2 relation is derived from the event's facts:

rule policy(E, :cash_deposit) do
  event_type(E, :deposit_received)
end

rule policy(E, :corporate_wire_fee) do
  event_type(E, :fee_assessed)
  event_fee_type(E, :wire_fee)
  event_entity_type(E, :corporate)
end

rule policy(E, :retail_wire_fee) do
  event_type(E, :fee_assessed)
  event_fee_type(E, :wire_fee)
  event_entity_type(E, :individual)
end

rule policy(E, :internal_transfer) do
  event_type(E, :transfer_settled)
end

rule policy(E, :interest_accrual) do
  event_type(E, :interest_accrued)
end

rule policy(E, :refund_paid) do
  event_type(E, :refund_issued)
end

Because policies are derived from multiple facts, the knowledge layer genuinely selects a policy based on the event's properties (event type, fee type, entity type, product, jurisdiction, ...) rather than hard-coding a mapping.

Posting templates

Templates answer which postings should be generated by this policy? They are static facts:

fact template(:cash_deposit)

facts :template_posting do
  row(:cash_deposit, 1, :debit,  :cash_account,             :event_amount, :event_currency)
  row(:cash_deposit, 2, :credit, :client_liability_account, :event_amount, :event_currency)
end

Each template posting spec has a sequence, a direction (:debit/:credit), a symbolic role, and the amount/currency variables (:event_amount / :event_currency in v0.1.0).

Account mappings

Account mappings answer which concrete account code should a symbolic role resolve to? They are derived by account_role/3 rules:

rule account_role(E, :client_liability_account, C) do
  event_account(E, C)
end

rule account_role(E, :cash_account, C) do
  event_cash_account(E, C)
end

# Static fallback for roles not carried by the event:
rule account_role(E, R, C) do
  account_role_static(R, C)
  event_type(E, _)
end

A static fallback is provided for roles an event does not carry directly (e.g. account_role_static(:interest_expense_account, "EXPENSES:INTEREST")).

Constraints

requires_dimension/2 facts declare which dimensions a policy requires (e.g. requires_dimension(:cash_deposit, :entity_id)). The runtime records these for audit; hard invariants are enforced in Elixir.

Business rules vs. accounting policies vs. ledger invariants

Three distinct rule categories (see Logistiki.Knowledge.Rules):

  • Business rules (Datalog): should the event be processed / blocked / approved?
  • Accounting policies (Datalog): which policy, template, roles, dimensions?
  • Ledger invariants (pure Elixir): debits equal credits, postings positive, posted journals immutable, etc. — never Datalog.

Datalog may say what should happen. Elixir enforces what must always be true.