Skip to main content

ADR-179: Hybrid Financial Model Distribution Architecture

Status

Proposed — 2026-02-12

Extends: ADR-177 (Database-Driven Financial Model Engine)

Context

ADR-177 established a local SQLite-backed financial model engine (coditect_fm.py) with CLI interface, scenario management, and pluggable output formatters. The engine is production-quality (2,358 LOC, 10 SQLite tables, 4 output formats) but is currently embedded in coditect-core/analyze-new-artifacts/ — a temporary location unsuitable for distribution.

Key problems requiring this ADR:

  1. Distribution — The FM engine needs proper packaging for the CODITECT install script and product suite
  2. Locationanalyze-new-artifacts/ is a staging area, not a product home
  3. Cloud access — Users need web-based financial modeling without CLI dependency
  4. Multi-tenant — Cloud version must support tenant isolation, RBAC, and collaboration
  5. Shared logic — Local CLI and cloud GUI must share the same calculation engine to avoid divergence

The existing GKE production infrastructure (coditect-citus-prod) already runs 10 Django backend replicas, a React frontend, Cloud SQL PostgreSQL 16, Redis, and Celery workers — providing 70% of the infrastructure needed.

Decision

Adopt a hybrid distribution architecture with two tiers:

  1. Local CLI — Product submodule at submodules/products/coditect-financial-model/ with SQLite backend (ADR-177 engine, unchanged)
  2. Cloud GUI — React frontend + Django REST API + PostgreSQL backend deployed to existing GKE infrastructure

Both tiers share an abstract FinancialCalculationEngine that separates computation from storage.

Product Placement

The FM engine moves to submodules/products/coditect-financial-model/ as its own product submodule, following the established pattern:

submodules/products/
├── coditect-development-studio/ # Existing product
├── coditect-document-management/ # Existing product
├── coditect-financial-model/ # NEW — this ADR
│ ├── src/
│ │ └── coditect_fm.py # Engine (from analyze-new-artifacts/)
│ ├── data/
│ │ └── CODITECT_Model_Export.json # Seed data
│ ├── tests/
│ ├── docs/
│ ├── dist/ # Example outputs
│ ├── requirements.txt # xlsxwriter
│ └── README.md
├── coditect-research-continuum/ # Existing product
├── coditect-runway-calculator/ # Existing (related financial product)
└── coditect-step-dev-platform/ # Existing product

Rationale: Five products already follow this pattern. coditect-runway-calculator is a direct precedent for financial tools. The FM engine is too large (2,358 LOC + database + 40 output files) for a core skill, and the cloud version will add Django app code that doesn't belong in coditect-core.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│ CODITECT Financial Model │
├──────────────────────┬──────────────────────────────────────────┤
│ LOCAL CLI (Free) │ CLOUD GUI (Premium) │
│ │ │
│ /fm build base │ React SPA at /financial-models/* │
│ /fm compare A B │ Django REST API /api/v1/fm/ │
│ SQLite storage │ PostgreSQL + Redis + Celery │
│ │ Multi-tenant RBAC │
├──────────────────────┴──────────────────────────────────────────┤
│ FinancialCalculationEngine (Shared) │
│ compute_model() | compute_unit_economics() | compare_scenarios()│
├──────────────────────┬──────────────────────────────────────────┤
│ LocalFinancialModel │ CloudFinancialModelBackend │
│ Backend (SQLite) │ (PostgreSQL via Django ORM) │
└──────────────────────┴──────────────────────────────────────────┘

Calculation Engine Abstraction

The engine separates pure financial computation from data persistence:

class FinancialCalculationEngine(ABC):
"""Abstract base for financial model computation."""

@abstractmethod
def compute_model(self, assumptions: Dict[str, Any]) -> Dict[str, Any]:
"""Compute 60-month projections: revenue, expenses, cash, unit economics."""
pass

@abstractmethod
def compute_unit_economics(self, monthly_data: List[Dict]) -> Dict[str, Decimal]:
"""Calculate CAC, LTV, LTV/CAC ratio, payback months."""
pass

@abstractmethod
def compare_scenarios(self, scenario_a: Dict, scenario_b: Dict) -> Dict[str, Any]:
"""Compare two scenario projections with deltas."""
pass

# Shared computation methods (revenue, expenses, cash flow)
# implemented in base class, used by both backends


class LocalFinancialModelBackend(FinancialCalculationEngine):
"""SQLite storage backend — wraps existing coditect_fm.py."""

def __init__(self, db_path: str):
from coditect_fm import FinancialModel
self.model = FinancialModel(db_path)


class CloudFinancialModelBackend(FinancialCalculationEngine):
"""PostgreSQL storage backend — Django ORM with tenant isolation."""

def __init__(self, scenario_id: int):
self.scenario = FinancialScenario.objects.select_related(
'working_capital'
).prefetch_related(
'pricing', 'growth_rates', 'funding_rounds',
'expense_ratios', 'headcount', 'comparables'
).get(id=scenario_id)

Backend Architecture (Django)

Django App: coditect_license/financial_models/ with 10 PostgreSQL models mirroring the SQLite schema (ADR-177) but with multi-tenant isolation:

ModelPurposeTenant Isolation
FinancialScenarioScenario configurationtenant FK + RLS
FinancialPricingTier pricing (individual/team/enterprise)Via scenario
FinancialGrowthRateMonthly growth rates by periodVia scenario
FinancialFundingRoundFunding rounds with tranchesVia scenario
FinancialExpenseRatioExpense ratios by category/stageVia scenario
FinancialHeadcountHeadcount plans by periodVia scenario
FinancialWorkingCapitalDSO/DPO/DIO parametersVia scenario
FinancialComparablePeer company benchmarksVia scenario
ComputedMonthCached 60-month projectionsVia scenario
ScenarioHistoryAudit trail of changesVia scenario

API Endpoints: DRF ViewSet at /api/v1/fm/scenarios/

MethodEndpointAction
GET/scenarios/List scenarios for tenant
POST/scenarios/Create scenario
GET/scenarios/{id}/Get scenario detail
PATCH/scenarios/{id}/Update scenario
DELETE/scenarios/{id}/Delete scenario
POST/scenarios/{id}/build/Trigger async computation
GET/scenarios/{id}/results/Get computed projections
POST/scenarios/{id}/compare/{other}/Compare two scenarios
POST/scenarios/{id}/export/Export to PDF/Excel/JSON
POST/scenarios/{id}/share/Share with team members
GET/scenarios/{id}/history/Audit trail
POST/scenarios/{id}/clone/Clone scenario
GET/PATCH/scenarios/{id}/assumptions/CRUD assumptions

Async Processing: 3 Celery tasks for CPU-intensive operations:

  • compute_scenario_task — Build 60-month projections (2-5s)
  • export_scenario_task — Generate PDF/Excel, upload to GCS
  • compare_scenarios_task — Compare two scenarios with deltas

RBAC Permissions: 8 new permission codes in financial category:

PermissionGranted To
financial.model.createTenant Admin, Financial Analyst
financial.model.readAll financial roles
financial.model.updateTenant Admin, Financial Analyst
financial.model.deleteTenant Admin
financial.model.exportTenant Admin, Financial Analyst, Executive
financial.model.shareTenant Admin, Financial Analyst
financial.scenario.compareTenant Admin, Financial Analyst, Executive
financial.scenario.historyAll financial roles

Frontend Architecture (React)

Route Structure: New /financial-models/* route tree in existing React 18 + TypeScript + Vite 5 frontend:

PagePathPurpose
Dashboard/financial-modelsList models, create, bulk actions
Detail/financial-models/:modelIdTabbed view (assumptions/results)
Compare/financial-models/:modelId/compare/:compareIdSide-by-side delta

Key Components:

  • ScenarioList — DataTable with filters, sorting, clone/delete/export actions
  • AssumptionEditor<T> — Generic form editor with React Hook Form + Zod validation
  • PricingTierEditor — 3-tier pricing assumption editor
  • GrowthRateEditor — Timeline-based period editor
  • ResultsCharts — Recharts visualizations for P&L, revenue, cash flow
  • ScenarioComparison — Two-column comparison with delta highlighting
  • ExportDialog — Multi-format export (XLSX, Formula XLSX, JSON, CSV, PDF)

State Management:

  • Server state: TanStack React Query (useScenarios, useAssumptions, useResults)
  • Form state: React Hook Form + Zod schemas
  • URL state: React Router v6 search params for active tab

Bundle Impact: +180KB gzipped (Recharts ~120KB, new components ~60KB), code-split at route level.

Deployment & Infrastructure

Infrastructure Reuse (70%): No new GKE services, clusters, or load balancers required.

ResourceCurrentFM Addition
Django Backend10 replicas, HPA 2-10New financial_models app
React Frontend2 replicasNew pages at /financial-models/*
Cloud SQLPostgreSQL 16, db-custom-2-819210 new tables via migrations
RedisBASIC tier, 10.159.63.195:6378DB 2 for FM cache (1h TTL)
Celery Worker1 replicaNew tasks, HPA 1-5 for GA

Feature Flag Rollout:

  1. Week 1: Internal only (allowed_tenants=['coditect'])
  2. Week 2: 10% pilot tenants
  3. Week 3: 50% rollout
  4. Week 4: 100% GA

Database Migration: Zero-downtime — all new tables, no ALTER TABLE on existing schemas. Rollback: DROP TABLE (safe in v1).

Container Versioning: django-backend:v1.24.0-fm, coditect-frontend:v1.29.0-fm

Monitoring:

MetricThresholdAlert
API latency P95<500ms reads, <5s builds>1s / >10s
Build error rate<1%>5% over 5min
Cache hit rate>80%<60% over 15min
Celery queue depth<10>50 for 10min

Storage: <1MB per tenant (60 months x 5 scenarios x 500 bytes). Redis: ~2.5MB per tenant cached. Existing Cloud SQL and Redis capacity support 2000+ tenants.

Monetization Tiers

TierPriceLimits
Free (CLI)$0Unlimited local scenarios, all output formats
Pro (Cloud)$49/mo10 cloud scenarios, collaboration, export
Enterprise$149/moUnlimited scenarios, API access, custom branding

Consequences

Positive

  • Established pattern — follows 5 existing product submodules; coditect-runway-calculator is a direct financial precedent
  • Independent release cycle — FM can version/release independently of coditect-core
  • Shared calculation logicFinancialCalculationEngine ABC prevents CLI/cloud divergence
  • 70% infrastructure reuse — no new GKE resources needed
  • Zero-downtime deployment — feature flags + new-table-only migrations
  • Revenue opportunity — tiered monetization for cloud features
  • Install script integration — product submodule detected and linked by existing install infrastructure

Negative

  • Engine abstraction effort — refactoring coditect_fm.py to use the ABC
  • Two storage backends — SQLite and PostgreSQL must stay in sync
  • Frontend bundle growth — +180KB gzipped for Recharts + components
  • Celery scaling — may need 3-5 workers at GA (currently 1)

Mitigations

  • Phase 1 (submodule creation) requires zero engine changes — just file relocation
  • Engine abstraction (Phase 2) is additive — existing SQLite path unchanged
  • Code-splitting prevents bundle impact on non-FM pages
  • Celery HPA auto-scales workers based on CPU utilization

Alternatives Considered

  1. Move FM into coditect-core — Rejected. Engine is 2,358 LOC with its own database and 40+ output files. Cloud version adds Django app code. Too large and domain-specific for a core skill.
  2. Standalone SaaS (separate GKE) — Rejected. Would duplicate infrastructure, increase costs, and require separate auth. 70% of needed infra already exists.
  3. Keep in analyze-new-artifacts/ — Rejected. Temporary staging location, not discoverable by install script, no version management.
  4. Cloud-only (no local CLI) — Rejected. CLI is already production-quality and provides offline/free access.

Implementation Plan

See TRACK-N Phase N.6.15 for detailed 6-phase task breakdown (60+ sub-tasks):

PhaseEffortDescription
N.6.15.14hArchitecture analysis & decision (this ADR)
N.6.15.28hProduct submodule creation & local distribution
N.6.15.38hCalculation engine abstraction
N.6.15.416hDjango cloud API
N.6.15.520hReact GUI
N.6.15.68hPremium features & monetization

Total estimated effort: 64 hours across 6 phases

References

  • ADR-177 — Database-Driven Financial Model Engine (local CLI, predecessor)
  • ADR-092 — RBAC Permission System
  • ADR-118 — Database Architecture
  • ADR-145 — Multi-Tenant Isolation
  • Analysis: internal/analysis/financial-model-distribution/fm-distribution-architecture-analysis-2026-02-12.md
  • Track: N.6.15 in TRACK-N-GTM-LAUNCH.md
  • Engine: analyze-new-artifacts/coditect-financial-model-2026-02-04/coditect_fm.py

Decision Date: 2026-02-12 Decision Maker: Hal Casteel Author: Claude (Opus 4.6)