"""Sizer — compute vendor obligations per order. Called by ``crypto_payment_worker`` when transitioning received → sizing. Returns the list of vendor_obligations rows to insert plus the total USD that needs to land at RelayFi (``needed_usd_cents``). Two categories of obligation: 1. filing_fee — paid via Relay debit card via Playwright flow. Includes a +10% buffer for card-decline retries + slippage. 2. commission — paid via Relay ACH 14 days post-delivery. Exact amount. The sizer reads order data from the relevant table (compliance_orders, formation_orders, canada_crtc_orders, bundle_orders) + existing config tables (state_filing_fees, sales_agents) + a small static map for vendors that don't live in a table (NECA OCN, BC Registry, etc.). """ from __future__ import annotations import json import logging import os from dataclasses import dataclass, field from datetime import datetime, timedelta from typing import Optional import psycopg2 import psycopg2.extras logger = logging.getLogger(__name__) # ── Static vendor fee map (single source of truth for non-table vendors) ── # # These are vendors whose fees are fixed + don't live in a queryable # table. Keep in sync with the COMPLIANCE_SERVICES catalog in # api/src/routes/compliance-orders.ts when their pricing changes. STATIC_VENDOR_FEES_CENTS: dict[str, int] = { "nwra": 12500, # Northwest Registered Agent $125/yr "neca_ocn": 55000, # NECA OCN registration $550 standard "neca_ocn_expedited": 67500, # NECA OCN expedited $675 "bc_registry": 27700, # BC Registry incorporation $277 CAD ≈ rounded "cores_frn": 0, # FCC CORES FRN — no filing fee "usac_499_initial": 0, # USAC 499 Initial — no filing fee "usac_499a": 0, # USAC 499-A — no filing fee (contributions separate) "fcc_cpni": 0, # FCC CPNI cert — no filing fee "fcc_rmd": 10000, # FCC RMD — $100 filing fee (effective 2025) "fcc_bdc": 0, # FCC BDC — no filing fee "fcc_stir_shaken": 0, # FCC STIR/SHAKEN — no filing fee "fcc_63_11": 0, # FCC Foreign Carrier Affiliation — no filing fee "fcc_calea_ssi": 0, # CALEA SSI — no filing fee (internal plan) "dc_agent": 0, # DC Agent — our DC agent service has no external fee } # Default filing-fee buffer in basis points (1000 bps = 10%) DEFAULT_FILING_BUFFER_BPS = 1000 # Commission payout happens 14 days post-delivery per commission_ledger # lifecycle (migration 011); reserve the cash at offramp time. COMMISSION_DUE_DAYS = 14 @dataclass class VendorObligation: """Matches the shape inserted into vendor_obligations.""" order_id: str order_type: str obligation_kind: str # 'filing_fee' | 'commission' vendor: str vendor_detail: Optional[str] amount_usd_cents: int fx_buffer_bps: int due_by: Optional[datetime] = None commission_ledger_id: Optional[int] = None @dataclass class SizingResult: obligations: list[VendorObligation] filing_total_cents: int # raw filing-fee sum, pre-buffer filing_total_with_buffer_cents: int commission_total_cents: int needed_usd_cents: int # what we need at Relay notes: list[str] = field(default_factory=list) def _db_connect(): return psycopg2.connect(os.environ.get("DATABASE_URL", "")) def compute_obligations( *, order_id: str, order_type: str, conn=None, ) -> SizingResult: """Compute vendor obligations for the given order. ``order_type`` is one of: 'compliance' | 'formation' | 'canada_crtc' | 'bundle'. """ close_after = False if conn is None: conn = _db_connect() close_after = True try: if order_type == "compliance": return _size_compliance(order_id, conn) if order_type == "formation": return _size_formation(order_id, conn) if order_type == "canada_crtc": return _size_canada_crtc(order_id, conn) if order_type == "bundle": return _size_bundle(order_id, conn) logger.warning("sizer: unknown order_type=%s for %s", order_type, order_id) return SizingResult([], 0, 0, 0, 0, ["unknown order_type — zero obligations"]) finally: if close_after: conn.close() # ── Compliance orders (FCC filings + OCN + DC agent + bundles) ────────── def _size_compliance(order_id: str, conn) -> SizingResult: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute( """ SELECT co.*, te.id AS entity_id, (co.intake_data->>'expedited')::boolean AS expedited FROM compliance_orders co LEFT JOIN telecom_entities te ON te.id = co.telecom_entity_id WHERE co.order_number = %s """, (order_id,), ) order = cur.fetchone() if not order: return SizingResult([], 0, 0, 0, 0, [f"compliance order {order_id} not found"]) slug = order.get("service_slug") or "" obligations: list[VendorObligation] = [] # OCN registration has real money to NECA; optional expedited upcharge. if slug == "ocn-registration": fee_key = "neca_ocn_expedited" if order.get("expedited") else "neca_ocn" obligations.append(_filing_vo( order_id, "compliance", "neca_ocn", None, STATIC_VENDOR_FEES_CENTS[fee_key], )) # Any service that includes NRA registered-agent coverage in its bundle. # Customer opts in via intake_data.include_nwra_ra or similar; read the flag. if (order.get("intake_data") or {}).get("include_nwra_ra"): obligations.append(_filing_vo( order_id, "compliance", "nwra", None, STATIC_VENDOR_FEES_CENTS["nwra"], )) # FCC 499-family, CPNI, RMD, BDC, STIR/SHAKEN, 63-11, CALEA SSI, DC agent, # compliance checkup, CDR analysis, CORES FRN — no vendor filing fee. # (Our service fee is pure markup; nothing to reserve for vendors.) # Commission reserve if a sales agent is attached commission = _compute_commission_obligation( order_id, "compliance", order, slug, conn, ) if commission: obligations.append(commission) filing = [o for o in obligations if o.obligation_kind == "filing_fee"] comms = [o for o in obligations if o.obligation_kind == "commission"] filing_total = sum(o.amount_usd_cents for o in filing) filing_buf = int(filing_total * (1 + DEFAULT_FILING_BUFFER_BPS / 10000)) comm_total = sum(o.amount_usd_cents for o in comms) return SizingResult( obligations=obligations, filing_total_cents=filing_total, filing_total_with_buffer_cents=filing_buf, commission_total_cents=comm_total, needed_usd_cents=filing_buf + comm_total, ) # ── Formation orders (LLC / Corp state filings) ───────────────────────── def _size_formation(order_id: str, conn) -> SizingResult: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute("SELECT * FROM formation_orders WHERE order_number = %s", (order_id,)) order = cur.fetchone() if not order: return SizingResult([], 0, 0, 0, 0, [f"formation order {order_id} not found"]) state = order.get("state_code") or order.get("state") entity_type = (order.get("entity_type") or "llc").lower() obligations: list[VendorObligation] = [] if state: # state_filing_fees UNIT CONVENTIONS per column (confirmed by # spot-checking 15+ states against real SOS fee schedules): # llc_formation_fee / corp_formation_fee — CENTS (e.g., 7000 = $70 CA LLC) # foreign_llc_fee / foreign_corp_fee — CENTS # expedited_fee — DOLLARS × 10000 # (e.g., 7500000 = $750 CA same-day) # llc_annual_fee / corp_annual_fee — mixed (some rows are franchise-tax # amounts; verify per-state before trust) # name_reservation_fee, franchise_tax_min — DOLLARS × 10000 in most rows # # This sizer only reads formation + foreign + expedited and # normalizes expedited to cents. formation_col = "corp_formation_fee" if entity_type.startswith("corp") else "llc_formation_fee" foreign_col = "foreign_corp_fee" if entity_type.startswith("corp") else "foreign_llc_fee" with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute( f""" SELECT {formation_col} AS formation_cents, {foreign_col} AS foreign_cents, expedited_fee AS expedited_raw FROM state_filing_fees WHERE state_code = %s LIMIT 1 """, (state,), ) fee = cur.fetchone() if fee: is_foreign = bool(order.get("foreign_filing") or order.get("is_foreign_entity")) base_cents = int((fee.get("foreign_cents") or 0) if is_foreign else (fee.get("formation_cents") or 0)) expedited_cents = 0 if order.get("expedited") and fee.get("expedited_raw"): # Divide by 100 to convert (dollars × 10000) → cents expedited_cents = int(fee["expedited_raw"]) // 100 total = base_cents + expedited_cents if total > 0: obligations.append(_filing_vo( order_id, "formation", "state_sos", state, total, )) if order.get("include_ra_service") or order.get("include_registered_agent"): obligations.append(_filing_vo( order_id, "formation", "nwra", state, STATIC_VENDOR_FEES_CENTS["nwra"], )) commission = _compute_commission_obligation( order_id, "formation", order, f"formation-{entity_type}", conn, ) if commission: obligations.append(commission) filing = [o for o in obligations if o.obligation_kind == "filing_fee"] comms = [o for o in obligations if o.obligation_kind == "commission"] filing_total = sum(o.amount_usd_cents for o in filing) filing_buf = int(filing_total * (1 + DEFAULT_FILING_BUFFER_BPS / 10000)) comm_total = sum(o.amount_usd_cents for o in comms) return SizingResult( obligations=obligations, filing_total_cents=filing_total, filing_total_with_buffer_cents=filing_buf, commission_total_cents=comm_total, needed_usd_cents=filing_buf + comm_total, ) # ── Canada CRTC orders ────────────────────────────────────────────────── def _size_canada_crtc(order_id: str, conn) -> SizingResult: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute("SELECT * FROM canada_crtc_orders WHERE order_number = %s", (order_id,)) order = cur.fetchone() if not order: return SizingResult([], 0, 0, 0, 0, [f"canada_crtc order {order_id} not found"]) obligations: list[VendorObligation] = [] obligations.append(_filing_vo( order_id, "canada_crtc", "bc_registry", "BC", STATIC_VENDOR_FEES_CENTS["bc_registry"], )) if order.get("amb_annual_price_cents"): obligations.append(_filing_vo( order_id, "canada_crtc", "amb_mailbox", None, int(order["amb_annual_price_cents"]), )) commission = _compute_commission_obligation( order_id, "canada_crtc", order, "canada_crtc", conn, ) if commission: obligations.append(commission) filing = [o for o in obligations if o.obligation_kind == "filing_fee"] comms = [o for o in obligations if o.obligation_kind == "commission"] filing_total = sum(o.amount_usd_cents for o in filing) filing_buf = int(filing_total * (1 + DEFAULT_FILING_BUFFER_BPS / 10000)) comm_total = sum(o.amount_usd_cents for o in comms) return SizingResult( obligations=obligations, filing_total_cents=filing_total, filing_total_with_buffer_cents=filing_buf, commission_total_cents=comm_total, needed_usd_cents=filing_buf + comm_total, ) # ── Bundle orders (sum of sub-orders) ─────────────────────────────────── def _size_bundle(order_id: str, conn) -> SizingResult: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute("SELECT * FROM bundle_orders WHERE order_number = %s", (order_id,)) order = cur.fetchone() if not order: return SizingResult([], 0, 0, 0, 0, [f"bundle order {order_id} not found"]) # bundle_orders.child_order_numbers is a TEXT[] of the sub-order numbers # (verify schema during implementation — may be JSONB with types). sub_ids = order.get("child_order_numbers") or [] sub_types = order.get("child_order_types") or [] all_obligations: list[VendorObligation] = [] for sub_id, sub_type in zip(sub_ids, sub_types): sub_res = compute_obligations(order_id=sub_id, order_type=sub_type, conn=conn) all_obligations.extend(sub_res.obligations) # Bundle-level commission (override the per-sub commissions if present) commission = _compute_commission_obligation( order_id, "bundle", order, "bundle", conn, ) if commission: # Drop sub-level commissions; keep only the bundle-level one all_obligations = [o for o in all_obligations if o.obligation_kind != "commission"] all_obligations.append(commission) filing = [o for o in all_obligations if o.obligation_kind == "filing_fee"] comms = [o for o in all_obligations if o.obligation_kind == "commission"] filing_total = sum(o.amount_usd_cents for o in filing) filing_buf = int(filing_total * (1 + DEFAULT_FILING_BUFFER_BPS / 10000)) comm_total = sum(o.amount_usd_cents for o in comms) return SizingResult( obligations=all_obligations, filing_total_cents=filing_total, filing_total_with_buffer_cents=filing_buf, commission_total_cents=comm_total, needed_usd_cents=filing_buf + comm_total, ) # ── Commission obligation (shared across all order types) ─────────────── def _compute_commission_obligation( order_id: str, order_type: str, order: dict, slug_for_override: str, conn, ) -> Optional[VendorObligation]: """If the order is attributed to a sales agent, reserve their commission.""" agent_id = order.get("sales_agent_id") or order.get("referral_agent_id") if not agent_id: return None with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: cur.execute( """ SELECT id, commission_type, commission_default_cents, commission_pct, commission_overrides, active FROM sales_agents WHERE id = %s """, (agent_id,), ) agent = cur.fetchone() if not agent or not agent.get("active"): return None overrides = agent.get("commission_overrides") or {} if isinstance(overrides, str): try: overrides = json.loads(overrides) except (TypeError, ValueError): overrides = {} override = overrides.get(slug_for_override) or {} ctype = override.get("type") or agent.get("commission_type", "flat") amount_cents = 0 if ctype == "flat": amount_cents = int( override.get("flat_cents") or agent.get("commission_default_cents") or 0 ) elif ctype == "percent": pct = float(override.get("pct") or agent.get("commission_pct") or 0) # Use subtotal (pre-surcharge) as the basis; fall back to service_fee_cents subtotal = int( order.get("subtotal_cents") or order.get("service_fee_cents") or order.get("total_cents") or 0 ) amount_cents = int(subtotal * pct / 100) if amount_cents <= 0: return None # Due 14 days post-delivery. If we don't know delivery date, estimate # from created_at + 7 days (typical service turnaround) + 14. created = order.get("created_at") or datetime.utcnow() due_by = created + timedelta(days=7 + COMMISSION_DUE_DAYS) return VendorObligation( order_id=order_id, order_type=order_type, obligation_kind="commission", vendor="agent_commission", vendor_detail=str(agent_id), amount_usd_cents=amount_cents, fx_buffer_bps=0, # commissions are exact, no buffer due_by=due_by, ) # ── Constructors ──────────────────────────────────────────────────────── def _filing_vo( order_id: str, order_type: str, vendor: str, vendor_detail: Optional[str], amount_cents: int, ) -> VendorObligation: return VendorObligation( order_id=order_id, order_type=order_type, obligation_kind="filing_fee", vendor=vendor, vendor_detail=vendor_detail, amount_usd_cents=amount_cents, fx_buffer_bps=DEFAULT_FILING_BUFFER_BPS, ) # ── Persistence helpers ───────────────────────────────────────────────── def persist_obligations(order_id: str, result: SizingResult, conn=None) -> None: """Upsert the SizingResult into vendor_obligations. Idempotent: deletes existing pending rows for this order then re-inserts. We never overwrite rows already marked reserved/paid/waived. """ close_after = False if conn is None: conn = _db_connect() close_after = True try: with conn.cursor() as cur: cur.execute( "DELETE FROM vendor_obligations " "WHERE order_id = %s AND status = 'pending'", (order_id,), ) for o in result.obligations: cur.execute( """ INSERT INTO vendor_obligations ( order_id, order_type, obligation_kind, vendor, vendor_detail, amount_usd_cents, fx_buffer_bps, due_by, status, commission_ledger_id ) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,'pending',%s) """, ( o.order_id, o.order_type, o.obligation_kind, o.vendor, o.vendor_detail, o.amount_usd_cents, o.fx_buffer_bps, o.due_by, o.commission_ledger_id, ), ) conn.commit() finally: if close_after: conn.close()