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:
- Distribution — The FM engine needs proper packaging for the CODITECT install script and product suite
- Location —
analyze-new-artifacts/is a staging area, not a product home - Cloud access — Users need web-based financial modeling without CLI dependency
- Multi-tenant — Cloud version must support tenant isolation, RBAC, and collaboration
- 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:
- Local CLI — Product submodule at
submodules/products/coditect-financial-model/with SQLite backend (ADR-177 engine, unchanged) - 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:
| Model | Purpose | Tenant Isolation |
|---|---|---|
FinancialScenario | Scenario configuration | tenant FK + RLS |
FinancialPricing | Tier pricing (individual/team/enterprise) | Via scenario |
FinancialGrowthRate | Monthly growth rates by period | Via scenario |
FinancialFundingRound | Funding rounds with tranches | Via scenario |
FinancialExpenseRatio | Expense ratios by category/stage | Via scenario |
FinancialHeadcount | Headcount plans by period | Via scenario |
FinancialWorkingCapital | DSO/DPO/DIO parameters | Via scenario |
FinancialComparable | Peer company benchmarks | Via scenario |
ComputedMonth | Cached 60-month projections | Via scenario |
ScenarioHistory | Audit trail of changes | Via scenario |
API Endpoints: DRF ViewSet at /api/v1/fm/scenarios/
| Method | Endpoint | Action |
|---|---|---|
| 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 GCScompare_scenarios_task— Compare two scenarios with deltas
RBAC Permissions: 8 new permission codes in financial category:
| Permission | Granted To |
|---|---|
financial.model.create | Tenant Admin, Financial Analyst |
financial.model.read | All financial roles |
financial.model.update | Tenant Admin, Financial Analyst |
financial.model.delete | Tenant Admin |
financial.model.export | Tenant Admin, Financial Analyst, Executive |
financial.model.share | Tenant Admin, Financial Analyst |
financial.scenario.compare | Tenant Admin, Financial Analyst, Executive |
financial.scenario.history | All financial roles |
Frontend Architecture (React)
Route Structure: New /financial-models/* route tree in existing React 18 + TypeScript + Vite 5 frontend:
| Page | Path | Purpose |
|---|---|---|
| Dashboard | /financial-models | List models, create, bulk actions |
| Detail | /financial-models/:modelId | Tabbed view (assumptions/results) |
| Compare | /financial-models/:modelId/compare/:compareId | Side-by-side delta |
Key Components:
ScenarioList— DataTable with filters, sorting, clone/delete/export actionsAssumptionEditor<T>— Generic form editor with React Hook Form + Zod validationPricingTierEditor— 3-tier pricing assumption editorGrowthRateEditor— Timeline-based period editorResultsCharts— Recharts visualizations for P&L, revenue, cash flowScenarioComparison— Two-column comparison with delta highlightingExportDialog— 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.
| Resource | Current | FM Addition |
|---|---|---|
| Django Backend | 10 replicas, HPA 2-10 | New financial_models app |
| React Frontend | 2 replicas | New pages at /financial-models/* |
| Cloud SQL | PostgreSQL 16, db-custom-2-8192 | 10 new tables via migrations |
| Redis | BASIC tier, 10.159.63.195:6378 | DB 2 for FM cache (1h TTL) |
| Celery Worker | 1 replica | New tasks, HPA 1-5 for GA |
Feature Flag Rollout:
- Week 1: Internal only (
allowed_tenants=['coditect']) - Week 2: 10% pilot tenants
- Week 3: 50% rollout
- 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:
| Metric | Threshold | Alert |
|---|---|---|
| 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
| Tier | Price | Limits |
|---|---|---|
| Free (CLI) | $0 | Unlimited local scenarios, all output formats |
| Pro (Cloud) | $49/mo | 10 cloud scenarios, collaboration, export |
| Enterprise | $149/mo | Unlimited scenarios, API access, custom branding |
Consequences
Positive
- Established pattern — follows 5 existing product submodules;
coditect-runway-calculatoris a direct financial precedent - Independent release cycle — FM can version/release independently of coditect-core
- Shared calculation logic —
FinancialCalculationEngineABC 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
- 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.
- Standalone SaaS (separate GKE) — Rejected. Would duplicate infrastructure, increase costs, and require separate auth. 70% of needed infra already exists.
- Keep in analyze-new-artifacts/ — Rejected. Temporary staging location, not discoverable by install script, no version management.
- 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):
| Phase | Effort | Description |
|---|---|---|
| N.6.15.1 | 4h | Architecture analysis & decision (this ADR) |
| N.6.15.2 | 8h | Product submodule creation & local distribution |
| N.6.15.3 | 8h | Calculation engine abstraction |
| N.6.15.4 | 16h | Django cloud API |
| N.6.15.5 | 20h | React GUI |
| N.6.15.6 | 8h | Premium 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)