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 ExternalSnapshot became FinAccountingProfile, FinAccountMapping, FinSource, FinPostingBatch, FinPostingDocument, FinJournalLine, FinExportAttempt, FinPostingAck, FinReconciliationItem, FinPaymentNotice, FinPostingPeriod, FinAudit, FinSourceSnapshot (fleet-unique Fin prefix). OutboxEvent / InboxMessage are dropped (the integration runtime provides them). FinException is added (the series' control exception).
  • Standard translations: canonical_ref on every model, cid on the three network entities, Decimal(18,2), Date → DateTime, text *_id → *_sid (source_event_sid), upper-case [enum:] lists from the pack workflows, Index lines and composite uniques dropped, name on every FK target.
  • FinSource.source_type carries the accounting role (PORT_COST_AGENT is not PORT_COST_CARRIER), so the pack's unique key "producer + source type + immutable source ID + accounting role" is source_app|source_type|source_ref.
  • The pack's S883.canonical_id, S885.document_id: a document key is profile|source key|document type|sequence (stable, sent to the ledger); FinPostingDocument.document_no is the readable network number. corrects_document_id links 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; batch prepared_by (independent approval); document total_amount, payload_hash, version_no, evidence_ref, status_reason; acknowledgment source_hash and reason; mapping approved_by; FinPostingBatch.prepared_at is 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 sweep finance.detect_unreconciled, and finance.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 in EXCEPTION until the acknowledgments reconcile it (the pack has no exception → exported step).
  • The pack's shipping_15_operator / shipping_15_supervisor roles 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.