Add engagement margin reporting
This commit is contained in:
parent
33883e0977
commit
8338c501ed
10 changed files with 397 additions and 8 deletions
|
|
@ -2,19 +2,18 @@
|
|||
# Custodian Brief — fin-hub
|
||||
|
||||
**Domain:** financials
|
||||
**Last synced:** 2026-08-10 18:32 UTC
|
||||
**Last synced:** 2026-08-10 18:42 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Client attribution and billing basis
|
||||
Progress: 2/6 done | workplan_id: `ebc1d2de-ae11-4cde-b860-047922fc74b9`
|
||||
Progress: 3/6 done | workplan_id: `ebc1d2de-ae11-4cde-b860-047922fc74b9`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Define shared-infrastructure allocation `899a5c29`
|
||||
- ! Export a per-client billing basis `b5886131`
|
||||
- ! Select the external invoicing system `d30b606f`
|
||||
- · Add engagement revenue and margin reporting `f917bb0b`
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ uv sync
|
|||
uv run pytest
|
||||
uv run finhub runway --balance 12000 --monthly-burn 2100,2200,2000
|
||||
uv run finhub ledger import cloud tests/fixtures/cloud-costs.csv
|
||||
uv run finhub ledger set-price --client acme --application portal --instance prod-01 --period 2026-07 --amount 100 --source agreement-2026-01
|
||||
uv run finhub ledger margins
|
||||
uv run finhub evaluate
|
||||
uv run finhub evidence --seed-fixtures
|
||||
uv run finhub serve
|
||||
|
|
@ -46,6 +48,12 @@ ledger is opened. The `ops-costs` report retains its per-service view and adds
|
|||
an `attributions` view separated by currency. Client and application identity
|
||||
remain authoritative outside fin-hub.
|
||||
|
||||
Engagement prices are reporting entitlements, not invoices or received
|
||||
payments. A price is identified by client/application/instance, reporting
|
||||
month, and currency. Corrections are append-only: pass the current record ID
|
||||
to `ledger set-price --revision-of`; margin reports use the newest revision
|
||||
and preserve the earlier price for auditability.
|
||||
|
||||
## Related Workplans
|
||||
|
||||
- `the-custodian/workplans/CUST-WP-0025-fos-hub-bootstrap.md` — umbrella
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
| task | FIN-WP-0001-T06 | done | — | workplans/FIN-WP-0001-runway-operations-lane.md |
|
||||
| task | FIN-WP-0002-T00 | done | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T01 | done | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T02 | todo | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T02 | done | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T03 | wait | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T04 | wait | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
| task | FIN-WP-0002-T05 | wait | — | workplans/FIN-WP-0002-client-attribution-and-billing-basis.md |
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ from fin_hub.services.alerts import evaluate_budget_alerts
|
|||
from fin_hub.services.evaluate import evaluate_runway
|
||||
from fin_hub.services.evidence import write_runway_evidence
|
||||
from fin_hub.services.ledger import (
|
||||
client_margin_report,
|
||||
default_ledger_path,
|
||||
import_csv,
|
||||
ledger_stats_json,
|
||||
record_engagement_price,
|
||||
set_opening_balance,
|
||||
)
|
||||
from fin_hub.services.runway import compute_runway
|
||||
|
|
@ -125,6 +127,28 @@ def _cmd_ledger_set_balance(args: argparse.Namespace) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_set_price(args: argparse.Namespace) -> int:
|
||||
price = record_engagement_price(
|
||||
client_id=args.client,
|
||||
application_id=args.application,
|
||||
app_instance_id=args.instance,
|
||||
period_month=args.period,
|
||||
amount=args.amount,
|
||||
currency=args.currency,
|
||||
source=args.source,
|
||||
revision_of=args.revision_of,
|
||||
ledger_path=_ledger_path(args),
|
||||
)
|
||||
print(json.dumps(price.__dict__, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_margins(args: argparse.Namespace) -> int:
|
||||
rows = client_margin_report(ledger_path=_ledger_path(args))
|
||||
print(json.dumps([row.as_dict() for row in rows], indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_evaluate(args: argparse.Namespace) -> int:
|
||||
report = evaluate_runway(
|
||||
ledger_path=_ledger_path(args),
|
||||
|
|
@ -217,6 +241,26 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
ledger_balance.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
ledger_balance.set_defaults(func=_cmd_ledger_set_balance)
|
||||
|
||||
ledger_price = ledger_sub.add_parser(
|
||||
"set-price", help="Record or revise an engagement price for a reporting period"
|
||||
)
|
||||
ledger_price.add_argument("--client", required=True)
|
||||
ledger_price.add_argument("--application", required=True)
|
||||
ledger_price.add_argument("--instance", required=True)
|
||||
ledger_price.add_argument("--period", required=True, help="Reporting month in YYYY-MM")
|
||||
ledger_price.add_argument("--amount", required=True, type=float)
|
||||
ledger_price.add_argument("--currency", default="EUR")
|
||||
ledger_price.add_argument("--source", required=True)
|
||||
ledger_price.add_argument("--revision-of")
|
||||
ledger_price.add_argument("--ledger", help="Ledger database path")
|
||||
ledger_price.set_defaults(func=_cmd_ledger_set_price)
|
||||
|
||||
ledger_margins = ledger_sub.add_parser(
|
||||
"margins", help="Report revenue, attributed cost, and margin by engagement"
|
||||
)
|
||||
ledger_margins.add_argument("--ledger", help="Ledger database path")
|
||||
ledger_margins.set_defaults(func=_cmd_ledger_margins)
|
||||
|
||||
evaluate = sub.add_parser(
|
||||
"evaluate",
|
||||
help="Evaluate runway from ledger burns (cron/systemd friendly)",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from fin_hub.models.budget import Budget, BurnRate, Commitment, RunwayProjection, TokenSpend
|
||||
from fin_hub.models.service_cost import ServiceCost
|
||||
from fin_hub.models.engagement_price import EngagementPrice
|
||||
|
||||
__all__ = [
|
||||
"Budget",
|
||||
|
|
@ -9,5 +10,6 @@ __all__ = [
|
|||
"Commitment",
|
||||
"RunwayProjection",
|
||||
"ServiceCost",
|
||||
"EngagementPrice",
|
||||
"TokenSpend",
|
||||
]
|
||||
]
|
||||
|
|
|
|||
33
src/fin_hub/models/engagement_price.py
Normal file
33
src/fin_hub/models/engagement_price.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""Period-effective engagement pricing for client margin reporting."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import Date, Float, ForeignKey, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from hub_core.models.base import Base, TimestampMixin
|
||||
|
||||
|
||||
class EngagementPrice(Base, TimestampMixin):
|
||||
"""A reporting entitlement price, not an invoice or payment record."""
|
||||
|
||||
__tablename__ = "fin_engagement_prices"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
client_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
|
||||
application_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
|
||||
app_instance_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
|
||||
cost_attribution_key: Mapped[str] = mapped_column(String(423), nullable=False, index=True)
|
||||
effective_from: Mapped[date] = mapped_column(Date, nullable=False, index=True)
|
||||
effective_to: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
amount: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
currency: Mapped[str] = mapped_column(String(3), nullable=False, default="EUR")
|
||||
source: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
revision_of: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("fin_engagement_prices.id"), nullable=True
|
||||
)
|
||||
notes: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
import sqlite3
|
||||
import uuid
|
||||
from dataclasses import asdict, dataclass
|
||||
from datetime import date, datetime, timezone
|
||||
from pathlib import Path
|
||||
|
|
@ -53,6 +54,38 @@ class ImportResult:
|
|||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EngagementPriceRecord:
|
||||
id: str
|
||||
client_id: str
|
||||
application_id: str
|
||||
app_instance_id: str
|
||||
cost_attribution_key: str
|
||||
period_month: str
|
||||
amount: float
|
||||
currency: str
|
||||
source: str
|
||||
revision_of: str | None
|
||||
recorded_at: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ClientMargin:
|
||||
cost_attribution_key: str
|
||||
client_id: str
|
||||
application_id: str
|
||||
app_instance_id: str
|
||||
period_month: str
|
||||
currency: str
|
||||
revenue: float
|
||||
attributed_cost: float
|
||||
margin: float
|
||||
price_id: str
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def default_ledger_path() -> Path:
|
||||
return DEFAULT_LEDGER_PATH
|
||||
|
||||
|
|
@ -94,6 +127,23 @@ def _ensure_schema(conn: sqlite3.Connection) -> None:
|
|||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS engagement_prices (
|
||||
id TEXT PRIMARY KEY,
|
||||
client_id TEXT NOT NULL,
|
||||
application_id TEXT NOT NULL,
|
||||
app_instance_id TEXT NOT NULL,
|
||||
cost_attribution_key TEXT NOT NULL,
|
||||
period_month TEXT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
currency TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
revision_of TEXT REFERENCES engagement_prices(id),
|
||||
recorded_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_engagement_prices_basis
|
||||
ON engagement_prices (cost_attribution_key, period_month, currency);
|
||||
"""
|
||||
)
|
||||
columns = {row["name"] for row in conn.execute("PRAGMA table_info(ledger_entries)")}
|
||||
|
|
@ -304,6 +354,134 @@ def monthly_burn_series(
|
|||
return burns
|
||||
|
||||
|
||||
def record_engagement_price(
|
||||
*,
|
||||
client_id: str,
|
||||
application_id: str,
|
||||
app_instance_id: str,
|
||||
period_month: str,
|
||||
amount: float,
|
||||
currency: str,
|
||||
source: str,
|
||||
ledger_path: Path | None = None,
|
||||
price_id: str | None = None,
|
||||
revision_of: str | None = None,
|
||||
) -> EngagementPriceRecord:
|
||||
from fin_hub.attribution import ClientAttribution
|
||||
|
||||
attribution = ClientAttribution(client_id, application_id, app_instance_id)
|
||||
if len(period_month) != 7 or period_month[4] != "-":
|
||||
raise ValueError("period_month must use YYYY-MM")
|
||||
try:
|
||||
date.fromisoformat(f"{period_month}-01")
|
||||
except ValueError as exc:
|
||||
raise ValueError("period_month must use YYYY-MM") from exc
|
||||
normalized_currency = currency.strip().upper()
|
||||
if len(normalized_currency) != 3 or not normalized_currency.isalpha():
|
||||
raise ValueError("currency must be a three-letter code")
|
||||
if amount < 0:
|
||||
raise ValueError("engagement price amount cannot be negative")
|
||||
if not source.strip():
|
||||
raise ValueError("source is required")
|
||||
|
||||
ledger = ledger_path or default_ledger_path()
|
||||
identifier = price_id or str(uuid.uuid4())
|
||||
recorded_at = _utc_now()
|
||||
with _connect(ledger) as conn:
|
||||
basis = (attribution.key, period_month, normalized_currency)
|
||||
current = conn.execute(
|
||||
"SELECT id FROM engagement_prices WHERE cost_attribution_key = ? "
|
||||
"AND period_month = ? AND currency = ? "
|
||||
"ORDER BY recorded_at DESC, rowid DESC LIMIT 1",
|
||||
basis,
|
||||
).fetchone()
|
||||
if current is not None and revision_of != current["id"]:
|
||||
raise ValueError(f"revision_of must reference current price {current['id']}")
|
||||
if current is None and revision_of is not None:
|
||||
raise ValueError("revision_of cannot be used without an existing price")
|
||||
conn.execute(
|
||||
"INSERT INTO engagement_prices ("
|
||||
"id, client_id, application_id, app_instance_id, cost_attribution_key, "
|
||||
"period_month, amount, currency, source, revision_of, recorded_at"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
identifier,
|
||||
attribution.client_id,
|
||||
attribution.application_id,
|
||||
attribution.app_instance_id,
|
||||
attribution.key,
|
||||
period_month,
|
||||
amount,
|
||||
normalized_currency,
|
||||
source.strip(),
|
||||
revision_of,
|
||||
recorded_at,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
return EngagementPriceRecord(
|
||||
id=identifier,
|
||||
client_id=attribution.client_id,
|
||||
application_id=attribution.application_id,
|
||||
app_instance_id=attribution.app_instance_id,
|
||||
cost_attribution_key=attribution.key,
|
||||
period_month=period_month,
|
||||
amount=amount,
|
||||
currency=normalized_currency,
|
||||
source=source.strip(),
|
||||
revision_of=revision_of,
|
||||
recorded_at=recorded_at,
|
||||
)
|
||||
|
||||
|
||||
def client_margin_report(*, ledger_path: Path | None = None) -> list[ClientMargin]:
|
||||
ledger = ledger_path or default_ledger_path()
|
||||
with _connect(ledger) as conn:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
WITH latest_prices AS (
|
||||
SELECT p.*
|
||||
FROM engagement_prices p
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM engagement_prices revision
|
||||
WHERE revision.revision_of = p.id
|
||||
)
|
||||
), attributed_costs AS (
|
||||
SELECT cost_attribution_key, period_month, currency,
|
||||
SUM(amount) AS attributed_cost
|
||||
FROM ledger_entries
|
||||
WHERE cost_attribution_key IS NOT NULL
|
||||
GROUP BY cost_attribution_key, period_month, currency
|
||||
)
|
||||
SELECT p.id AS price_id, p.client_id, p.application_id,
|
||||
p.app_instance_id, p.cost_attribution_key, p.period_month,
|
||||
p.currency, p.amount AS revenue,
|
||||
COALESCE(c.attributed_cost, 0) AS attributed_cost
|
||||
FROM latest_prices p
|
||||
LEFT JOIN attributed_costs c
|
||||
ON c.cost_attribution_key = p.cost_attribution_key
|
||||
AND c.period_month = p.period_month
|
||||
AND c.currency = p.currency
|
||||
ORDER BY p.period_month, p.client_id, p.application_id, p.app_instance_id
|
||||
"""
|
||||
).fetchall()
|
||||
return [
|
||||
ClientMargin(
|
||||
cost_attribution_key=row["cost_attribution_key"],
|
||||
client_id=row["client_id"],
|
||||
application_id=row["application_id"],
|
||||
app_instance_id=row["app_instance_id"],
|
||||
period_month=row["period_month"],
|
||||
currency=row["currency"],
|
||||
revenue=float(row["revenue"]),
|
||||
attributed_cost=float(row["attributed_cost"]),
|
||||
margin=float(row["revenue"]) - float(row["attributed_cost"]),
|
||||
price_id=row["price_id"],
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
def ledger_stats(*, ledger_path: Path | None = None) -> dict:
|
||||
ledger = ledger_path or default_ledger_path()
|
||||
balance, balance_currency = get_opening_balance(ledger)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@ import pytest
|
|||
|
||||
from fin_hub.services.evaluate import evaluate_runway
|
||||
from fin_hub.services.evidence import build_runway_report, seed_fixture_ledger, write_runway_evidence
|
||||
from fin_hub.services.ledger import import_csv, monthly_burn_series, monthly_summary, set_opening_balance
|
||||
from fin_hub.services.ledger import (
|
||||
client_margin_report,
|
||||
import_csv,
|
||||
monthly_burn_series,
|
||||
monthly_summary,
|
||||
record_engagement_price,
|
||||
set_opening_balance,
|
||||
)
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
|
@ -84,6 +91,107 @@ def test_existing_ledger_schema_is_migrated_additively(tmp_path: Path):
|
|||
}.issubset(columns)
|
||||
|
||||
|
||||
def test_engagement_price_and_margin_report(tmp_path: Path):
|
||||
source = tmp_path / "attributed.csv"
|
||||
source.write_text(
|
||||
"product,amount,currency,invoice_date,client_id,application_id,app_instance_id\n"
|
||||
"Managed cluster,42.00,EUR,2026-07-01,acme,portal,prod-01\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
ledger = tmp_path / "ledger.db"
|
||||
import_csv(source, "hosteurope", ledger_path=ledger)
|
||||
price = record_engagement_price(
|
||||
client_id="acme",
|
||||
application_id="portal",
|
||||
app_instance_id="prod-01",
|
||||
period_month="2026-07",
|
||||
amount=100.0,
|
||||
currency="eur",
|
||||
source="agreement-2026-01",
|
||||
ledger_path=ledger,
|
||||
)
|
||||
|
||||
margins = client_margin_report(ledger_path=ledger)
|
||||
|
||||
assert margins[0].price_id == price.id
|
||||
assert margins[0].revenue == 100.0
|
||||
assert margins[0].attributed_cost == 42.0
|
||||
assert margins[0].margin == 58.0
|
||||
assert margins[0].currency == "EUR"
|
||||
|
||||
|
||||
def test_engagement_price_revision_supersedes_prior_price(tmp_path: Path):
|
||||
ledger = tmp_path / "ledger.db"
|
||||
original = record_engagement_price(
|
||||
client_id="acme",
|
||||
application_id="portal",
|
||||
app_instance_id="prod-01",
|
||||
period_month="2026-07",
|
||||
amount=100.0,
|
||||
currency="EUR",
|
||||
source="agreement-v1",
|
||||
ledger_path=ledger,
|
||||
)
|
||||
revision = record_engagement_price(
|
||||
client_id="acme",
|
||||
application_id="portal",
|
||||
app_instance_id="prod-01",
|
||||
period_month="2026-07",
|
||||
amount=120.0,
|
||||
currency="EUR",
|
||||
source="agreement-v2",
|
||||
revision_of=original.id,
|
||||
ledger_path=ledger,
|
||||
)
|
||||
|
||||
margins = client_margin_report(ledger_path=ledger)
|
||||
|
||||
assert [(row.price_id, row.revenue) for row in margins] == [(revision.id, 120.0)]
|
||||
|
||||
|
||||
def test_engagement_price_requires_explicit_revision(tmp_path: Path):
|
||||
ledger = tmp_path / "ledger.db"
|
||||
kwargs = {
|
||||
"client_id": "acme",
|
||||
"application_id": "portal",
|
||||
"app_instance_id": "prod-01",
|
||||
"period_month": "2026-07",
|
||||
"amount": 100.0,
|
||||
"currency": "EUR",
|
||||
"source": "agreement",
|
||||
"ledger_path": ledger,
|
||||
}
|
||||
original = record_engagement_price(**kwargs)
|
||||
|
||||
with pytest.raises(ValueError, match=original.id):
|
||||
record_engagement_price(**kwargs)
|
||||
|
||||
|
||||
def test_margin_does_not_join_cost_in_another_currency(tmp_path: Path):
|
||||
source = tmp_path / "attributed.csv"
|
||||
source.write_text(
|
||||
"product,amount,currency,invoice_date,client_id,application_id,app_instance_id\n"
|
||||
"Managed cluster,42.00,USD,2026-07-01,acme,portal,prod-01\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
ledger = tmp_path / "ledger.db"
|
||||
import_csv(source, "hosteurope", ledger_path=ledger)
|
||||
record_engagement_price(
|
||||
client_id="acme",
|
||||
application_id="portal",
|
||||
app_instance_id="prod-01",
|
||||
period_month="2026-07",
|
||||
amount=100.0,
|
||||
currency="EUR",
|
||||
source="agreement",
|
||||
ledger_path=ledger,
|
||||
)
|
||||
|
||||
margin = client_margin_report(ledger_path=ledger)[0]
|
||||
assert margin.attributed_cost == 0.0
|
||||
assert margin.margin == 100.0
|
||||
|
||||
|
||||
def test_evaluate_runway_from_ledger(tmp_path: Path):
|
||||
ledger = tmp_path / "ledger.db"
|
||||
seed_fixture_ledger(ledger_path=ledger, opening_balance=12000.0)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
"""Smoke tests for fin-hub model registration."""
|
||||
|
||||
from fin_hub.models import Budget, BurnRate, Commitment, RunwayProjection, ServiceCost, TokenSpend
|
||||
from fin_hub.models import (
|
||||
Budget,
|
||||
BurnRate,
|
||||
Commitment,
|
||||
EngagementPrice,
|
||||
RunwayProjection,
|
||||
ServiceCost,
|
||||
TokenSpend,
|
||||
)
|
||||
from hub_core.models.base import Base
|
||||
|
||||
|
||||
|
|
@ -12,6 +20,7 @@ def test_fin_models_register_on_metadata():
|
|||
assert "fin_runway_projections" in tables
|
||||
assert "fin_token_spends" in tables
|
||||
assert "fin_service_costs" in tables
|
||||
assert "fin_engagement_prices" in tables
|
||||
|
||||
|
||||
def test_model_classes_importable():
|
||||
|
|
@ -21,6 +30,7 @@ def test_model_classes_importable():
|
|||
assert RunwayProjection.__tablename__ == "fin_runway_projections"
|
||||
assert TokenSpend.__tablename__ == "fin_token_spends"
|
||||
assert ServiceCost.__tablename__ == "fin_service_costs"
|
||||
assert EngagementPrice.__tablename__ == "fin_engagement_prices"
|
||||
|
||||
|
||||
def test_service_cost_has_external_attribution_seam():
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ explicitly unattributed. Covered by the full test suite and documented in
|
|||
|
||||
```task
|
||||
id: FIN-WP-0002-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f917bb0b-f44e-4642-9130-c1a004185180"
|
||||
```
|
||||
|
|
@ -105,6 +105,13 @@ and application. Preserve currency, effective periods, provenance, and price
|
|||
revisions; do not treat a price record as an issued invoice or received
|
||||
payment.
|
||||
|
||||
Completed 2026-08-10: added versioned engagement-price records with stable
|
||||
IDs, reporting month, currency, source provenance, and explicit append-only
|
||||
revision links. The SQLite ledger and CLI now report revenue, attributed cost,
|
||||
and margin by client/application/instance without representing prices as
|
||||
invoices or payments. Currency mismatches cannot silently join, superseded
|
||||
prices remain auditable, and duplicate prices require an explicit revision.
|
||||
|
||||
## Define shared-infrastructure allocation
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue