# Logistiki
[](https://hex.pm/packages/logistiki)
[](https://hex.pm/packages/logistiki)
[](https://hexdocs.pm/logistiki)
[](https://hex.pm/packages/logistiki)
[](https://github.com/thanos/logistiki/actions/workflows/ci.yml)
[](https://coveralls.io/github/thanos/logistiki)
[](https://github.com/thanos/logistiki/releases)
> An embedded OTP accounting engine for Elixir applications.
Logistiki is the accounting execution layer that applications embed. It is **not**
a bank, an ERP, a payment system, or a compliance platform. It is the accounting
engine that those applications embed — the way Phoenix is for web, Ecto is for
persistence, Oban is for jobs, and Ash is for domains.
The name comes from the Greek word **λογιστική** — accounting, bookkeeping, but
deeper: reasoning, reckoning, calculation, explanation. Logistiki is not merely
about storing debits and credits; it is about **explaining why** accounting
events happened.
## What Logistiki is
- Business event processing
- Datalog-backed accounting knowledge
- Double-entry accounting
- Immutable journals and postings
- Hierarchical business entities
- Hierarchical virtual accounts
- Entity-to-account relationship graphs
- Derived balances, statements, trial balances, general ledger, balance sheet,
income statement
- Audit evidence
- Deterministic replay
- Behaviour-based ledger backends
- A Beancount-backed accounting oracle
## What Logistiki is not
- a bank, ERP, payment system, or compliance platform
- a payment rail (SWIFT, ISO 20022, ACH, card processing)
- a KYC/AML/sanctions/PEP screening tool
- a regulatory reporting or tax-filing engine
- a Phoenix dashboard (that is future work)
## The central architectural principle
**Applications never create journals directly.** The primary public API is:
```elixir
Logistiki.process(event)
```
Applications publish **business events**. Logistiki decides whether and how those
events become accounting entries. Journals and postings are internal accounting
artifacts — useful for audit, ops, and admin tooling, but not the normal
application-facing API.
```elixir
%Logistiki.Event.DepositReceived{}
%Logistiki.Event.TransferSettled{}
%Logistiki.Event.FeeAssessed{}
%Logistiki.Event.InterestAccrued{}
%Logistiki.Event.RefundIssued{}
```
Logistiki turns those events into journals and postings, and the application asks
questions afterward:
```elixir
Logistiki.balance(account)
Logistiki.statement(account)
Logistiki.trial_balance()
Logistiki.general_ledger()
Logistiki.balance_sheet()
Logistiki.income_statement()
```
## Why business events are the primary API
Money never moves because random application code says so. Money moves because a
business event happened, the knowledge layer evaluated the rules, an accounting
policy was selected, a posting template was resolved, a journal was built,
invariants were validated, and a ledger backend executed it. Keeping that chain
explicit makes accounting **explainable, reproducible, testable, and auditable**.
## Why Datalog is the knowledge layer
The knowledge layer is powered by [`ex_datalog`](https://hex.pm/packages/ex_datalog).
Datalog is not merely a rule selector — it is the accounting knowledge layer. It
holds business rules, accounting policies, posting templates, account mappings,
constraints, and dimensions as facts and rules, and answers questions like
*should this event be processed?*, *which policy applies?*, *which postings
should be generated?*, *which account does this role resolve to?*.
Datalog decides facts and relationships. Elixir materializes journals, postings,
and persisted records. **Datalog never constructs Elixir structs.**
## Why journals are internal artifacts
Journals and postings are essential to accounting, but they are not the main
application-facing API. They are generated by the runtime from business events,
validated by deterministic Elixir invariants, and executed by a ledger backend.
## Why balances are projections
Balances are never authoritative stored state. They are projections over
immutable postings, computed by `Logistiki.Projections.ProjectionEngine`. Parent
account balances aggregate descendants; entity balances aggregate linked
accounts; entity subtree balances aggregate across descendant entities.
## How Beancount is used
The Beancount backend ([`beancount_ex`](https://hex.pm/packages/beancount_ex)) is
the **executable accounting specification** for Logistiki. On `execute_journal/2`
the journal is mapped to Beancount directives and verified with `Beancount.check/1`
before being persisted. The regression suite compares Simulation-backend results
against the Beancount oracle. Beancount-specific types never leak into the public
API — the `Logistiki.Ledger.BeancountMapper` converts at the boundary.
## How a future native backend fits
A future native Elixir ledger backend (and a Rust backend) must match Beancount
behaviour for shared supported features. The `Logistiki.Ledger.Behaviour`
contract and the regression tests guarantee that.
## A runnable example
```elixir
# Configure PostgreSQL (config/dev.exs is provided), then:
mix ecto.setup
# Run the demo scenario end-to-end:
mix run -e 'Logistiki.Demo.run_demo()'
# Or open the interactive Livebook walkthrough (SQLite in-memory, no PostgreSQL needed):
livebook open docs/livebooks/logistiki_demo.livemd
```
```elixir
alias Logistiki.Event.DepositReceived
event = %DepositReceived{
id: "evt_001",
entity_type: "corporate",
account_code: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING",
cash_account_code: "ASSETS:CASH:USD:NOSTRO",
amount: Decimal.new("1000.00"),
currency: "USD",
occurred_at: ~U[2026-07-07 12:00:00Z],
effective_date: ~D[2026-07-07],
source_system: "bank_core",
source_id: "wire_123"
}
{:ok, result} = Logistiki.process(event)
# => the knowledge layer selected :cash_deposit,
# a journal was built, validated, posted, and audited.
{:ok, [balance]} = Logistiki.balance("ASSETS:CASH:USD:NOSTRO")
# => %Logistiki.Projections.Balance{net: Decimal.new("1000.00"), ...}
```
## Installation
Add `logistiki` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:logistiki, "~> 0.1.0"}
]
end
```
Requires Elixir 1.18+ and PostgreSQL.
### Optional: SQLite for tests and Livebooks
Add `ecto_sqlite3` to use SQLite in-memory instead of PostgreSQL:
```elixir
def deps do
[
{:logistiki, "~> 0.1.0"},
{:ecto_sqlite3, "~> 0.18"}
]
end
```
Set `LOGISTIKI_DB_ADAPTER=sqlite` before compilation.
## Architecture overview
```
Business Event
│
▼
Event Normalization
│
▼
Datalog Fact Generation ── ex_datalog
│
▼
Business Rule Evaluation ── Datalog (blocked / requires_approval)
│
▼
Accounting Policy Selection ── Datalog (policy/2)
│
▼
Accounting Template Resolution ── Datalog (template_posting/6, account_role/3)
│
▼
Journal Builder ── Elixir
│
▼
Posting Builder ── Elixir
│
▼
Ledger Invariant Validation ── pure Elixir (never Datalog)
│
▼
Ledger Backend Execution ── Simulation or Beancount
│
▼
Projection Generation ── balance / statement / trial balance
│
▼
Audit Evidence
```
## Documentation
See the `docs/` directory for in-depth guides:
- [Vision](docs/vision.md)
- [The accounting pipeline](docs/accounting_pipeline.md)
- [Business events](docs/business_events.md)
- [The knowledge layer](docs/knowledge_layer.md)
- [Datalog accounting policies](docs/datalog_accounting_policies.md)
- [Business entities](docs/business_entities.md)
- [Virtual accounts](docs/virtual_accounts.md)
- [Entity-account relationships](docs/entity_account_relationships.md)
- [Journals and postings](docs/journals_and_postings.md)
- [Ledger invariants](docs/ledger_invariants.md)
- [The Beancount backend](docs/beancount_backend.md)
- [Projections](docs/projections.md)
- [Audit evidence](docs/audit_evidence.md)
- [Testing strategy](docs/testing_strategy.md)
- [Demo script](docs/demo_script.md)
Full API documentation is available at
[HexDocs](https://hexdocs.pm/logistiki).
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Run `mix test` to make sure everything passes
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create a new Pull Request
## License
MIT — see [LICENSE](LICENSE).