SQLite-backed CofferProduct with company vs agent books, immutable tx log, 200c Coffer→Leora build contract as first business tx, 40c signup grant, 20c seat via Coffer with 1% cut (19.80c to New Internet Co), shirt catalog, CLI (python -m coffer), and tests. Centicredits for reseller settlement. Quarry 3005d129.
467 lines
17 KiB
Python
467 lines
17 KiB
Python
"""Durable Coffer product: wallets, ledger txs, signup, subscription, catalog."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from coffer.db import CofferDB
|
|
from coffer.ledger import CreditKind, Ledger, Lot, Tx
|
|
from coffer.store import Good, Invoice, Order, new_store
|
|
from coffer.units import (
|
|
ACCOUNT_COFFER,
|
|
ACCOUNT_LEORA,
|
|
ACCOUNT_NEW_INTERNET,
|
|
COFFER_INVESTMENT_CREDITS,
|
|
CONTRACT_CREDITS,
|
|
LEORA_FUNDED_AFTER_GRANTS_CREDITS,
|
|
SCALE,
|
|
SHIRT_CREDITS,
|
|
SIGNUP_GRANT_CREDITS,
|
|
SUBSCRIPTION_CREDITS,
|
|
SUBSCRIPTION_CUT_BPS,
|
|
agent_account,
|
|
cut_subunits,
|
|
format_credits,
|
|
to_subunits,
|
|
)
|
|
|
|
PT = ZoneInfo("Australia/Perth")
|
|
|
|
|
|
def now_pt() -> str:
|
|
return datetime.now(tz=PT).isoformat(timespec="seconds")
|
|
|
|
|
|
class ProductError(Exception):
|
|
"""Product-level error."""
|
|
|
|
|
|
class CofferProduct:
|
|
"""High-level API the platform and CLI call."""
|
|
|
|
def __init__(self, db_path: str | Path) -> None:
|
|
self.db = CofferDB(db_path)
|
|
self.ledger = Ledger()
|
|
self.store = new_store(self.ledger)
|
|
self._load()
|
|
|
|
def close(self) -> None:
|
|
self.db.close()
|
|
|
|
def _load(self) -> None:
|
|
self.ledger = Ledger()
|
|
self.store = new_store(self.ledger)
|
|
self.store.suppliers.clear()
|
|
self.store.goods.clear()
|
|
|
|
for row in self.db.execute("SELECT account_id FROM accounts").fetchall():
|
|
self.ledger.ensure_wallet(row["account_id"])
|
|
for row in self.db.execute(
|
|
"SELECT account_id, kind, remaining, reason FROM lots "
|
|
"WHERE remaining > 0 ORDER BY lot_id"
|
|
).fetchall():
|
|
self.ledger.account(row["account_id"]).lots.append(
|
|
Lot(CreditKind(row["kind"]), int(row["remaining"]), row["reason"])
|
|
)
|
|
|
|
self.ledger.transactions.clear()
|
|
self.ledger._tx_seq = 0
|
|
for row in self.db.execute(
|
|
"SELECT seq, kind, amount, debit_account, credit_account, reason, meta_json "
|
|
"FROM transactions ORDER BY seq"
|
|
).fetchall():
|
|
tx = Tx(
|
|
seq=int(row["seq"]),
|
|
kind=row["kind"],
|
|
amount=int(row["amount"]),
|
|
debit_account=row["debit_account"],
|
|
credit_account=row["credit_account"],
|
|
reason=row["reason"],
|
|
meta=self.db.loads(row["meta_json"] or "{}"),
|
|
)
|
|
self.ledger.transactions.append(tx)
|
|
self.ledger._tx_seq = max(self.ledger._tx_seq, tx.seq)
|
|
|
|
for row in self.db.execute("SELECT supplier_id, name FROM suppliers").fetchall():
|
|
self.store.add_supplier(row["supplier_id"], row["name"])
|
|
for row in self.db.execute(
|
|
"SELECT sku, name, credit_price, supplier_id, supplier_unit_cost, stock FROM goods"
|
|
).fetchall():
|
|
self.store.goods[row["sku"]] = Good(
|
|
sku=row["sku"],
|
|
name=row["name"],
|
|
credit_price=int(row["credit_price"]),
|
|
supplier_id=row["supplier_id"],
|
|
supplier_unit_cost=int(row["supplier_unit_cost"]),
|
|
stock=int(row["stock"]),
|
|
)
|
|
if "coffer-shirt" not in self.store.goods:
|
|
if "printer" not in self.store.suppliers:
|
|
self.store.add_supplier("printer", "Printer")
|
|
self.store.add_good(
|
|
sku="coffer-shirt",
|
|
name="Coffer shirt",
|
|
credit_price=SHIRT_CREDITS,
|
|
supplier_id="printer",
|
|
supplier_unit_cost=8,
|
|
)
|
|
self._persist_catalog()
|
|
self.db.commit()
|
|
|
|
for row in self.db.execute("SELECT * FROM invoices").fetchall():
|
|
inv = Invoice(
|
|
invoice_id=row["invoice_id"],
|
|
supplier_id=row["supplier_id"],
|
|
sku=row["sku"],
|
|
quantity=int(row["quantity"]),
|
|
dollars=int(row["dollars"]),
|
|
status=row["status"],
|
|
)
|
|
self.store.invoices[inv.invoice_id] = inv
|
|
self._bump_seq_from_id(inv.invoice_id)
|
|
for row in self.db.execute("SELECT * FROM orders").fetchall():
|
|
order = Order(
|
|
order_id=row["order_id"],
|
|
buyer_id=row["buyer_id"],
|
|
sku=row["sku"],
|
|
quantity=int(row["quantity"]),
|
|
credits_charged=int(row["credits_charged"]),
|
|
draws=self.db.loads(row["draws_json"]),
|
|
from_stock=int(row["from_stock"]),
|
|
procured=int(row["procured"]),
|
|
status=row["status"],
|
|
invoice_id=row["invoice_id"],
|
|
tx_seq=row["tx_seq"],
|
|
)
|
|
self.store.orders[order.order_id] = order
|
|
self._bump_seq_from_id(order.order_id)
|
|
for row in self.db.execute("SELECT key, order_id FROM idempotency").fetchall():
|
|
self.store._idempotency[row["key"]] = row["order_id"]
|
|
|
|
def _bump_seq_from_id(self, oid: str) -> None:
|
|
try:
|
|
n = int(str(oid).rsplit("-", 1)[-1])
|
|
if n > self.store._seq:
|
|
self.store._seq = n
|
|
except ValueError:
|
|
pass
|
|
|
|
def _ensure_account_row(self, account_id: str) -> None:
|
|
kind = "company" if account_id.startswith("company:") else "agent"
|
|
self.db.execute(
|
|
"INSERT OR IGNORE INTO accounts(account_id, kind, created_at) VALUES(?,?,?)",
|
|
(account_id, kind, now_pt()),
|
|
)
|
|
|
|
def _rewrite_lots(self, account_id: str) -> None:
|
|
self._ensure_account_row(account_id)
|
|
self.db.execute("DELETE FROM lots WHERE account_id=?", (account_id,))
|
|
for lot in self.ledger.account(account_id).lots:
|
|
if lot.remaining <= 0:
|
|
continue
|
|
self.db.execute(
|
|
"INSERT INTO lots(account_id, kind, remaining, reason, created_at) "
|
|
"VALUES(?,?,?,?,?)",
|
|
(account_id, lot.kind.value, lot.remaining, lot.reason, now_pt()),
|
|
)
|
|
|
|
def _persist_tx(self, tx: Tx) -> None:
|
|
self.db.execute(
|
|
"INSERT INTO transactions(seq, kind, amount, debit_account, credit_account, "
|
|
"reason, meta_json, created_at) VALUES(?,?,?,?,?,?,?,?)",
|
|
(
|
|
tx.seq,
|
|
tx.kind,
|
|
tx.amount,
|
|
tx.debit_account,
|
|
tx.credit_account,
|
|
tx.reason,
|
|
self.db.dumps(tx.meta),
|
|
now_pt(),
|
|
),
|
|
)
|
|
|
|
def _persist_catalog(self) -> None:
|
|
for s in self.store.suppliers.values():
|
|
self.db.execute(
|
|
"INSERT OR REPLACE INTO suppliers(supplier_id, name) VALUES(?,?)",
|
|
(s.supplier_id, s.name),
|
|
)
|
|
for g in self.store.goods.values():
|
|
self.db.execute(
|
|
"INSERT OR REPLACE INTO goods(sku, name, credit_price, supplier_id, "
|
|
"supplier_unit_cost, stock) VALUES(?,?,?,?,?,?)",
|
|
(
|
|
g.sku,
|
|
g.name,
|
|
g.credit_price,
|
|
g.supplier_id,
|
|
g.supplier_unit_cost,
|
|
g.stock,
|
|
),
|
|
)
|
|
|
|
def _persist_order(self, order: Order) -> None:
|
|
self.db.execute(
|
|
"INSERT OR REPLACE INTO orders(order_id, buyer_id, sku, quantity, "
|
|
"credits_charged, draws_json, from_stock, procured, status, invoice_id, "
|
|
"tx_seq, created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
(
|
|
order.order_id,
|
|
order.buyer_id,
|
|
order.sku,
|
|
order.quantity,
|
|
order.credits_charged,
|
|
self.db.dumps(order.draws),
|
|
order.from_stock,
|
|
order.procured,
|
|
order.status,
|
|
order.invoice_id,
|
|
order.tx_seq,
|
|
now_pt(),
|
|
),
|
|
)
|
|
if order.invoice_id and order.invoice_id in self.store.invoices:
|
|
inv = self.store.invoices[order.invoice_id]
|
|
self.db.execute(
|
|
"INSERT OR REPLACE INTO invoices(invoice_id, supplier_id, sku, quantity, "
|
|
"dollars, status, created_at) VALUES(?,?,?,?,?,?,?)",
|
|
(
|
|
inv.invoice_id,
|
|
inv.supplier_id,
|
|
inv.sku,
|
|
inv.quantity,
|
|
inv.dollars,
|
|
inv.status,
|
|
now_pt(),
|
|
),
|
|
)
|
|
self._persist_catalog()
|
|
|
|
def _commit_accounts(self, *account_ids: str, txs: list[Tx] | None = None) -> None:
|
|
seen: set[str] = set()
|
|
for aid in account_ids:
|
|
if aid and aid not in seen:
|
|
self._rewrite_lots(aid)
|
|
seen.add(aid)
|
|
for tx in txs or []:
|
|
self._persist_tx(tx)
|
|
self.db.commit()
|
|
|
|
# ----- public API ----------------------------------------------------------
|
|
|
|
def create_wallet(self, account_id: str) -> dict[str, Any]:
|
|
if account_id.startswith("company:"):
|
|
aid = account_id
|
|
else:
|
|
aid = agent_account(account_id)
|
|
self.ledger.ensure_wallet(aid)
|
|
self._ensure_account_row(aid)
|
|
self.db.commit()
|
|
return {"account_id": aid, "balance": self.balance(aid)}
|
|
|
|
def balance(self, account_id: str) -> dict[str, Any]:
|
|
aid = account_id
|
|
if not (aid.startswith("agent:") or aid.startswith("company:")):
|
|
aid = agent_account(aid)
|
|
raw = self.ledger.balance_subunits(aid)
|
|
return {
|
|
"account_id": aid,
|
|
"promotional": raw["promotional"] / SCALE,
|
|
"funded": raw["funded"] / SCALE,
|
|
"spendable": raw["spendable"] / SCALE,
|
|
"promotional_centicredits": raw["promotional"],
|
|
"funded_centicredits": raw["funded"],
|
|
"spendable_centicredits": raw["spendable"],
|
|
}
|
|
|
|
def history(self) -> list[dict[str, Any]]:
|
|
return [self.ledger.describe_tx(tx) for tx in self.ledger.history()]
|
|
|
|
def bootstrap_opening_books(self) -> dict[str, Any]:
|
|
"""Seed opening balances without business txs (so contract can be first tx)."""
|
|
if self.db.get_meta("bootstrapped") == "1":
|
|
return {"bootstrapped": True, "already": True}
|
|
before = len(self.ledger.transactions)
|
|
self.ledger.bootstrap_endowment(
|
|
ACCOUNT_COFFER,
|
|
COFFER_INVESTMENT_CREDITS,
|
|
CreditKind.FUNDED,
|
|
"leora-investment",
|
|
)
|
|
self.ledger.bootstrap_endowment(
|
|
ACCOUNT_LEORA,
|
|
LEORA_FUNDED_AFTER_GRANTS_CREDITS,
|
|
CreditKind.FUNDED,
|
|
"funded-after-grants",
|
|
)
|
|
self.ledger.ensure_wallet(ACCOUNT_NEW_INTERNET)
|
|
self._rewrite_lots(ACCOUNT_COFFER)
|
|
self._rewrite_lots(ACCOUNT_LEORA)
|
|
self._ensure_account_row(ACCOUNT_NEW_INTERNET)
|
|
self.db.set_meta("bootstrapped", "1")
|
|
self.db.commit()
|
|
assert len(self.ledger.transactions) == before
|
|
return {
|
|
"bootstrapped": True,
|
|
"already": False,
|
|
"coffer": self.balance(ACCOUNT_COFFER),
|
|
"leora": self.balance(ACCOUNT_LEORA),
|
|
}
|
|
|
|
def record_build_contract(
|
|
self,
|
|
amount_credits: int = CONTRACT_CREDITS,
|
|
*,
|
|
payer: str = ACCOUNT_COFFER,
|
|
payee: str = ACCOUNT_LEORA,
|
|
) -> dict[str, Any]:
|
|
"""First business ledger tx: Coffer pays Leora for the product build."""
|
|
if self.db.get_meta("contract_recorded") == "1":
|
|
for tx in self.ledger.history():
|
|
if tx.kind == "contract_payment":
|
|
return {"already": True, "tx": self.ledger.describe_tx(tx)}
|
|
raise ProductError("contract_recorded meta set but tx missing")
|
|
if self.db.get_meta("bootstrapped") != "1":
|
|
self.bootstrap_opening_books()
|
|
tx = self.ledger.transfer(
|
|
payer,
|
|
payee,
|
|
to_subunits(amount_credits),
|
|
reason="build-contract:leora-coffer-product",
|
|
tx_kind="contract_payment",
|
|
credit_kind=CreditKind.FUNDED,
|
|
meta={"contract": "coffer-product-build", "credits": amount_credits},
|
|
)
|
|
self._commit_accounts(payer, payee, txs=[tx])
|
|
self.db.set_meta("contract_recorded", "1")
|
|
self.db.commit()
|
|
return {
|
|
"already": False,
|
|
"tx": self.ledger.describe_tx(tx),
|
|
"payer": self.balance(payer),
|
|
"payee": self.balance(payee),
|
|
}
|
|
|
|
def grant_signup(
|
|
self,
|
|
agent_id: str,
|
|
amount_credits: int = SIGNUP_GRANT_CREDITS,
|
|
) -> dict[str, Any]:
|
|
aid = agent_account(agent_id)
|
|
marker = f"signup_grant:{aid}"
|
|
if self.db.get_meta(marker) == "1":
|
|
return {"already": True, "account_id": aid, "balance": self.balance(aid)}
|
|
self.ledger.ensure_wallet(aid)
|
|
before = self.ledger._tx_seq
|
|
self.ledger.grant(aid, amount_credits, reason="signup-grant")
|
|
txs = [t for t in self.ledger.transactions if t.seq > before]
|
|
self._commit_accounts(aid, txs=txs)
|
|
self.db.set_meta(marker, "1")
|
|
self.db.commit()
|
|
return {
|
|
"already": False,
|
|
"account_id": aid,
|
|
"granted_credits": amount_credits,
|
|
"balance": self.balance(aid),
|
|
"tx": self.ledger.describe_tx(txs[-1]) if txs else None,
|
|
}
|
|
|
|
def charge_subscription(
|
|
self,
|
|
agent_id: str,
|
|
amount_credits: int = SUBSCRIPTION_CREDITS,
|
|
*,
|
|
cut_bps: int = SUBSCRIPTION_CUT_BPS,
|
|
) -> dict[str, Any]:
|
|
aid = agent_account(agent_id)
|
|
gross = to_subunits(amount_credits)
|
|
cut = cut_subunits(gross, cut_bps)
|
|
net = gross - cut
|
|
draws, tx_pay = self.ledger.purchase_settle(
|
|
aid,
|
|
ACCOUNT_COFFER,
|
|
gross,
|
|
reason="subscription:new-internet-seat",
|
|
tx_kind="subscription_charge",
|
|
meta={"agent_id": aid, "credits": amount_credits},
|
|
)
|
|
tx_fwd = self.ledger.transfer(
|
|
ACCOUNT_COFFER,
|
|
ACCOUNT_NEW_INTERNET,
|
|
net,
|
|
reason="subscription:forward-to-new-internet-co",
|
|
tx_kind="subscription_forward",
|
|
credit_kind=CreditKind.FUNDED,
|
|
meta={"agent_id": aid, "cut_centicredits": cut, "net_centicredits": net},
|
|
)
|
|
self._commit_accounts(
|
|
aid, ACCOUNT_COFFER, ACCOUNT_NEW_INTERNET, txs=[tx_pay, tx_fwd]
|
|
)
|
|
return {
|
|
"agent_id": aid,
|
|
"gross_credits": amount_credits,
|
|
"coffer_cut_credits": format_credits(cut),
|
|
"coffer_cut_centicredits": cut,
|
|
"leora_net_credits": format_credits(net),
|
|
"leora_net_centicredits": net,
|
|
"draws": [
|
|
{"kind": d["kind"], "amount_centicredits": d["amount"]} for d in draws
|
|
],
|
|
"txs": [self.ledger.describe_tx(tx_pay), self.ledger.describe_tx(tx_fwd)],
|
|
"balances": {
|
|
"agent": self.balance(aid),
|
|
"coffer": self.balance(ACCOUNT_COFFER),
|
|
"new_internet_co": self.balance(ACCOUNT_NEW_INTERNET),
|
|
},
|
|
}
|
|
|
|
def signup_flow(self, agent_id: str) -> dict[str, Any]:
|
|
wallet = self.create_wallet(agent_id)
|
|
grant = self.grant_signup(agent_id)
|
|
sub = self.charge_subscription(agent_id)
|
|
return {"wallet": wallet, "grant": grant, "subscription": sub}
|
|
|
|
def buy_shirt(
|
|
self,
|
|
agent_id: str,
|
|
quantity: int = 1,
|
|
idempotency_key: str | None = None,
|
|
) -> dict[str, Any]:
|
|
before = self.ledger._tx_seq
|
|
order = self.store.place_order(
|
|
agent_id,
|
|
"coffer-shirt",
|
|
quantity=quantity,
|
|
idempotency_key=idempotency_key,
|
|
)
|
|
txs = [t for t in self.ledger.transactions if t.seq > before]
|
|
self._persist_order(order)
|
|
if idempotency_key:
|
|
self.db.execute(
|
|
"INSERT OR REPLACE INTO idempotency(key, order_id) VALUES(?,?)",
|
|
(idempotency_key, order.order_id),
|
|
)
|
|
self._commit_accounts(order.buyer_id, ACCOUNT_COFFER, txs=txs)
|
|
return {
|
|
"order": asdict(order),
|
|
"balance": self.balance(order.buyer_id),
|
|
"txs": [self.ledger.describe_tx(t) for t in txs],
|
|
}
|
|
|
|
def top_up(self, agent_id: str, amount_credits: int) -> dict[str, Any]:
|
|
aid = agent_account(agent_id)
|
|
before = self.ledger._tx_seq
|
|
self.ledger.top_up(aid, amount_credits)
|
|
txs = [t for t in self.ledger.transactions if t.seq > before]
|
|
self._commit_accounts(aid, txs=txs)
|
|
return {
|
|
"account_id": aid,
|
|
"balance": self.balance(aid),
|
|
"tx": self.ledger.describe_tx(txs[-1]),
|
|
}
|