Coastal Shipping 15 — Finance & Accounting Interface (coast_15_finance)
Take the immutable financial facts of the series, map them to balanced one-currency journals, export them to a SIMULATED ledger and reconcile the ledger's acknowledgments — an accounting interface, not a general ledger and not an invoice generator.
Its central question:
Is every issued invoice, approved supplier cost and committed agency fact posted exactly once, in a balanced journal, and has the ledger really acknowledged that posting?
coast_15_finance is application SHP-15 of the Coastal Shipping series (coast_NN_<name>, Stage 3; each able to run stand-alone or as a network member). It shares no tables and no foreign keys with its siblings
(coast_05_rating, coast_07_accounts, coast_08_portcosts, coast_09_cartage) — they link by canonical_ref strings and messages only. All data is fictional, every account is a MOCK_ account and the ledger is simulated:
no real chart of accounts, tax, wire format, endpoint or credential exists in this application.
The spine
FinSource (issued billing snapshot · approved port cost · approved cartage cost · agency cash / commission / remittance fact) → FinPostingBatch → FinPostingDocument + FinJournalLine → FinExportAttempt (simulated) → FinPostingAck → FinReconciliationItem, with FinPaymentNotice (external payments, reconcile only), FinSourceSnapshot (header and statement observations), FinAudit and FinException.
The documents
| Page | What's in it |
|---|---|
| 00 — Overview | What the app is, the rules it is built around, the 14 models by area, the demo scenario |
| 01 — Quick Reference | Menu map, every model, status vocabularies, the messages, how to run, seed and regenerate |
| 02 — System Diagram | The data model as diagrams (+ interactive viewer) and how the app links to its siblings |
Status
Built (rules and handlers, tested): source ingestion and validation (unique key = producer + type + id, conflicting hashes rejected, the posting snapshot as the only billing source, approved costs only with a supplier invoice line key, agent-paid and carrier-payable costs kept apart, one payable per supplier invoice line across port and cartage, exact cost reversals, agency facts limited to cash, commission and remittance); effective-dated approved account mappings (MOCK_ accounts only, no overlap, agency cash never on a cost expense account); the batch workflow (prepare with open period, mapping and balance checks, independent approval with a frozen payload hash, export through the simulated adapter, reconcile per document, exception, resolve); per-document acknowledgment reconciliation (document key, source hash, currency and amount); retry only after a ledger status query; corrections by linked exact reversal and replacement; external payment notices (routed, never exported); header and statement observations; append-only audit; control exceptions with resolution. Messages: see the Quick Reference.
Simulated, not real: the ledger adapter (integration/ledger_sim.py: connector scripts mock_ledger, mock_ledger_reject, mock_ledger_timeout_received, mock_ledger_timeout_lost). A profile in LIVE mode cannot be activated and its mappings cannot be approved.
SIMULATED_ACCEPTED means the pretend transport took the batch, never that anything was posted; only a matching acknowledgment makes a document POSTED.
Not built / not enforced: no screen buttons for correction, retry or ledger query (correct_document is a function in rules.py; retry and ledger query are driven by the commands finance.retry_export and finance.query_ledger, no exemplar app has row action buttons); explicit FX (the pack asks for separate design: a batch is in the profile's base currency, other
currencies wait unposted); the roles of the pack's permissions.yaml (the platform's two levels are used: an authorised user approves, quarantines, resolves and approves mappings); live ledger formats, tax rules and charts of accounts; verification and allocation of an
external payment (that belongs to the source owner); the sweep finance.detect_unreconciled is not yet on the network's timetable.
Translation decisions (pack → native DSL)
- Names:
S881 AccountingProfile…S895 ExternalSnapshotbecameFinAccountingProfile,FinAccountMapping,FinSource,FinPostingBatch,FinPostingDocument,FinJournalLine,FinExportAttempt,FinPostingAck,FinReconciliationItem,FinPaymentNotice,FinPostingPeriod,FinAudit,FinSourceSnapshot(fleet-uniqueFinprefix).OutboxEvent/InboxMessageare dropped (the integration runtime provides them).FinExceptionis added (the series' control exception). - Standard translations:
canonical_refon every model,cidon the three network entities,Decimal(18,2),Date→DateTime, text*_id→*_sid(source_event_sid), upper-case[enum:]lists from the pack workflows,Indexlines and composite uniques dropped,nameon every FK target. FinSource.source_typecarries the accounting role (PORT_COST_AGENTis notPORT_COST_CARRIER), so the pack's unique key "producer + source type + immutable source ID + accounting role" issource_app|source_type|source_ref.- The pack's
S883.canonical_id,S885.document_id: a document key isprofile|source key|document type|sequence(stable, sent to the ledger);FinPostingDocument.document_nois the readable network number.corrects_document_idlinks a reversal or replacement to the document it corrects. - Added fields: source date, posting mode (
POST/RECONCILE_ONLY), quarantine reason, payer type, supplier line key; batchprepared_by(independent approval); documenttotal_amount,payload_hash,version_no,evidence_ref,status_reason; acknowledgmentsource_hashandreason; mappingapproved_by;FinPostingBatch.prepared_atis optional (a draft batch has none). - Events (one dot per message type):
shipping.billing.posting_snapshot.v1→rating.posting_snapshot;shipping.billing.document_issued.v1→rating.billing_document_issued;shipping.portcost.approved.v1→portcost.cost_approved;shipping.cartage.cost_approved.v1→cartage.cost_approved;shipping.agency.statement_issued.v1→accounts.statement_issued;shipping.agency.financial_fact_committed.v1→accounts.financial_fact_committed;shipping.finance.posting_status.v1→finance.posting_status;shipping.finance.payment_notice.v1→finance.payment_notice; the generic exception and completion events →finance.exception_raised/finance.exception_resolved. - Added beyond the pack: commands
finance.record_ledger_acknowledgment,finance.record_payment_notice(this runtime has no inbound external adapter and does not route a command's answer, so the simulated ledger's answers arrive as commands) the sweepfinance.detect_unreconciled, andfinance.retry_export/finance.query_ledger(request{batch_no}; the resend of an exception batch with the same document keys, and the status query after an unknown outcome; a resend after UNKNOWN is refused until the ledger was queried, and when it has the batch). - An agency fact names the agent's legal entity; it is posted in the books of the approved profile (the agent's own entity if it has an ACTIVE profile, else the one approved entity). A cost carries no legal entity: it takes the one approved entity, or is quarantined.
- A batch whose transport the simulated adapter refuses (or whose answer is lost) goes straight to
EXCEPTION; after a successful resend it stays inEXCEPTIONuntil the acknowledgments reconcile it (the pack has noexception → exportedstep). - The pack's
shipping_15_operator/shipping_15_supervisorroles are not mapped (decision of 2026-11-13: the platform's two levels).
Prototype system; draft. All names, accounts and amounts in the demo data are fictional; the ledger is simulated.