Cartrack LTV & Pricing Engine
Platform documentation v1.0 · Last updated March 2026
1. Overview
The Cartrack Contract Intelligence Platform is a full-stack analytics application that provides four stakeholder dashboards covering Life Time Value determination, churn analytics, pricing optimisation, and model governance.
Sales & Marketing
Real-time pricing calculator, LTV by segment, cohort analytics, acquisition funnel, and target segment recommender to maximise profitable acquisition.
C-Suite Management
Portfolio KPIs, MRR waterfall, 36-month cashflow projection with confidence bands, unit economics by cohort, and contract drill-down.
Retentions & Collections
Preservation priority queue, churn risk distribution, bad billing EV decision model, and intervention outcomes tracking.
Model Governance
Model registry, calibration by decile, AUC trend, PSI feature drift monitoring, data quality dashboard, and audit log.
Tech Stack
| Layer | Technology | Purpose |
| Backend | FastAPI 0.110+ | Async REST API with OpenAPI docs at /docs |
| ORM | SQLAlchemy 2.x | Model definitions, session management, query layer |
| Database | SQLite (dev) / PostgreSQL (prod) | Contract portfolio data store |
| Churn ML | XGBoost | Gradient boosted trees for churn probability scoring |
| LTV | NumPy + survival analysis | Discounted cashflow with Monte Carlo CI |
| Pricing | SciPy minimize_scalar | Constrained optimisation of expected profit |
| Frontend | Static HTML + Plotly.js | Goldman Sachs terminal aesthetic dashboards |
| Config | Pydantic BaseSettings | Environment-variable driven configuration |
| Testing | pytest + FastAPI TestClient | 233 unit + integration tests |
2. Architecture
System Architecture
FIG 1 — Layered architecture: Client → API → Analytics → Database
Module Structure
Life Expectancy/
├── backend/
│ ├── main.py FastAPI app entry point, lifespan, routers, static files
│ ├── config.py Pydantic settings (discount rate, DB URL, etc.)
│ ├── database.py SQLAlchemy engine, SessionLocal, Base, get_db()
│ ├── models/ SQLAlchemy ORM models (10 tables)
│ │ ├── contract.py Core entity — full subscription lifecycle
│ │ ├── customer.py Subscriber profile + credit band
│ │ ├── payment.py Monthly billing records
│ │ ├── product.py Subscription package catalogue
│ │ ├── cost.py CostUpfront + CostMonthly line items
│ │ ├── hardware.py Hardware unit + FitmentRecord
│ │ ├── support.py Support tickets (incl. cancellation requests)
│ │ └── recovery.py Vehicle theft + recovery events
│ ├── analytics/ Pure Python analytics modules
│ │ ├── ltv.py LTV formula, Monte Carlo CI, portfolio aggregation
│ │ ├── churn.py XGBoost churn scorer, preservation queue
│ │ ├── collections.py Bad billing EV decision model
│ │ └── pricing.py Constrained price optimisation
│ ├── api/ FastAPI route handlers
│ │ ├── management.py C-Suite endpoints (portfolio, waterfall, cohort, etc.)
│ │ ├── sales.py Sales endpoints (pricing, LTV by segment, funnel)
│ │ ├── retentions.py Retentions endpoints (queue, collections, notices)
│ │ └── governance.py Governance endpoints (registry, drift, audit log)
│ └── data/
│ └── seed.py Synthetic data generator (5,000 contracts)
├── frontend/
│ ├── assets/
│ │ ├── design-system.css Goldman Sachs terminal CSS variables + components
│ │ └── api-client.js API helpers, fmt utilities, Plotly dark theme
│ ├── index.html Landing page + live KPI strip
│ ├── sales.html Sales & Marketing dashboard
│ ├── management.html C-Suite Management dashboard
│ ├── retentions.html Retentions & Collections dashboard
│ ├── governance.html Model Governance dashboard
│ └── wiki.html This documentation page
├── tests/ pytest test suite (233 tests)
├── pytest.ini
├── start.bat / start.sh Quick-start scripts
└── CLAUDE.md Project rules and context
Data Flow Pipeline
FIG 2 — End-to-end data flow: Sources → Ingest → Store → Compute → Serve → Render
3. Business Model
Subscription Mechanics
Cartrack sells vehicle tracking subscriptions on a recurring monthly fee basis. Each contract has a cancellation clause (minimum term, typically 36 months) that penalises early termination.
| Revenue Stream | Description | Model Treatment |
| Subscription MRR | Monthly fee paid by active subscribers | Primary component of subscription_pv in LTV |
| Cancellation Penalties | | penalty_pv in LTV; ~65-70% collection rate assumed |
| Vehicle Recovery Fees | ~10× monthly fee on successful vehicle recovery | recovery_pv in LTV; 3.6% annual theft rate, 72% recovery rate |
Contract Lifecycle
FIG 3 — Contract state machine: subscription lifecycle from sale to cancellation
Cost Structure
| Category | Type | Components | Typical Range (ZAF) |
| Hardware | Upfront | Device purchase / stock allocation | R1,200 – R2,800 |
| Fitment Labour | Upfront | Technician time: base_labour × complexity_mult | R180 – R500 |
| Fitment Travel | Upfront | distance_km × R3.50 × complexity_mult | R18 – R700 |
| CAC Marketing | Upfront | Digital / ATL attribution | R150 – R300 |
| CAC Sales | Upfront | Agent commission | R200 – R500 |
| Platform | Monthly | Server, maps API, SIM data | R60 – R105 |
| Support | Monthly | Pro-rated cost per ticket | R15 – R25 |
| Repairs | Monthly | Hardware repair (avg. amortised) | R0 – R50 |
| Collections | Monthly | Agent follow-up for bad billing | ~8% of monthly fee |
Bad Billing Decision Framework
When a subscriber stops paying, three actions are evaluated each month using Expected Value maximisation:
P(resume payment) decays exponentially with time in bad billing:
Band multipliers: A = 1.4 · B = 1.1 · C = 0.85 · D = 0.55 · unrated = 0.90
FIG 4 — Bad billing EV decision flow: monthly evaluation of three action paths
4. Data Schema
contracts
Core entity. One row per subscription contract.
| Column | Type | Description |
| contract_id | UUID PK | Unique contract identifier |
| customer_id | UUID FK | → customers.customer_id |
| product_id | UUID FK | → products.product_id |
| country_code | CHAR(3) | ISO-3166-1 alpha-3 (ZAF, KEN, NGA, TZA, ZMB) |
| sales_channel | VARCHAR | direct | dealer | corporate | online | broker |
| start_date | DATE | Contract origination date |
| cancellation_clause_months | INT | Minimum term in months (default 36, fleet 24) |
| monthly_fee | NUMERIC(12,2) | Recurring monthly subscription fee |
| status | VARCHAR | active | bad_billing | suspended | cancelled |
| cancellation_date | DATE | Date of termination (if cancelled) |
| cancellation_reason | VARCHAR | voluntary_price_sensitivity | forced_non_payment | … |
| penalty_invoiced | NUMERIC(12,2) | Penalty amount billed on cancellation |
| penalty_collected | NUMERIC(12,2) | Penalty amount actually collected |
| months_active | INT | Tenure in months (updated monthly) |
| vehicle_make | VARCHAR | Vehicle manufacturer (Toyota, BMW, etc.) |
| distance_to_fitter_km | NUMERIC(8,2) | Distance to nearest installation centre |
customers
| Column | Type | Description |
| customer_id | UUID PK | Unique customer identifier |
| customer_type | VARCHAR | individual | fleet | corporate | dealer |
| country_code | CHAR(3) | ISO country of domicile |
| acquisition_channel | VARCHAR | Channel through which customer was acquired |
| first_contract_date | DATE | Date of first ever contract with Cartrack |
| credit_score_band | CHAR(1) | A | B | C | D | unrated — drives P(resume) in collections model |
| fleet_size | INT | Number of vehicles in fleet (1 for individual) |
payments
| Column | Type | Description |
| payment_id | UUID PK | Unique payment record |
| contract_id | UUID FK | → contracts.contract_id |
| period_year / period_month | INT | Billing period (e.g. 2024, 3 = March 2024) |
| amount_due | NUMERIC(12,2) | Invoice amount for the period |
| amount_paid | NUMERIC(12,2) | Actual cash received |
| payment_method | VARCHAR | debit_order | eft | credit_card | cash |
| days_late | INT | Days after due date that payment was received |
| status | VARCHAR | paid | partial | unpaid | reversed | written_off |
products
| Column | Type | Description |
| product_id | UUID PK | Unique product identifier |
| product_tier | VARCHAR | basic | standard | premium | fleet |
| standard_monthly_fee | NUMERIC(12,2) | List price (actual contract fee may differ) |
| standard_cancellation_clause | INT | Default clause length in months |
| hardware_cost | NUMERIC(12,2) | Upfront hardware cost for this product tier |
| monthly_platform_cost | NUMERIC(12,2) | Per-contract monthly platform cost |
| country_code | CHAR(3) | Country-specific product catalogue entry |
costs_upfront & costs_monthly
| Table | cost_type values | Description |
| costs_upfront | cac_marketing, cac_sales, cac_call_centre, fitment_labour, fitment_travel, hardware | One-time acquisition and installation costs |
| costs_monthly | platform, sim_data, support, repair, collections, retention_intervention | Recurring monthly cost line items per billing period |
hardware & fitment_records
| Column | Table | Description |
| residual_value | hardware | Current market value — used in EV(retrieve) calculation |
| status | hardware | installed | retrieved | in_refurbishment | stock | lost | stolen |
| fitment_type | fitment_records | installation | retrieval | swap | repair |
| vehicle_complexity | fitment_records | standard (1.0×) | complex (1.5×) | premium (1.8×) cost multiplier |
| total_cost | fitment_records | Actual technician cost for this visit |
support_tickets
| Column | Values | Notes |
| category | hardware_fault | billing | cancellation_request | service_query | recovery_request | cancellation_request → retentions workflow + penalty calc |
| resolution | resolved | escalated | pending | closed_no_action | Save rate = resolved / total cancellation_requests |
recovery_events
| Column | Type | Description |
| outcome | VARCHAR | recovered | not_recovered | pending |
| recovery_fee_charged | NUMERIC(12,2) | Invoice to subscriber (≈ 10× monthly fee) |
| recovery_fee_collected | NUMERIC(12,2) | Cash collected — feeds recovery_pv in LTV |
| recovery_cost_to_cartrack | NUMERIC(12,2) | Operational cost of recovery operation |
5. Analytical Models
5.1 LTV Engine — backend/analytics/ltv.py
Algorithm: Parametric Survival + DCF
Horizon: 60 months
Discount rate: 12% annual WACC → 0.949% monthly
Full forward-looking LTV formula:
FIG 5 — LTV decomposition: revenue streams vs cost components with key drivers
Confidence interval: Monte Carlo (200 simulations). Samples monthly_churn_prob ~ N(μ, 0.25μ) and net_cashflow ~ N(μ, 0.15μ). Returns P10/P90 as the 80% CI.
Payback month: First month where cumulative discounted cashflow turns non-negative (upfront cost recovered).
5.2 Churn Classifier — backend/analytics/churn.py
Algorithm: XGBoost Classifier
Features: 19
Validation AUC: 0.823
| Feature | Description |
| months_active | Contract tenure in months |
| monthly_fee | Current monthly subscription fee |
| credit_band_enc | Encoded credit band (A=0, B=1, C=2, D=3) |
| is_fleet | Binary — fleet or corporate customer |
| avg_days_late | Average payment lateness in days |
| pct_paid | Proportion of billing periods paid on time |
| consecutive_bad_months | Ratio of unpaid months to total tenure |
| ticket_count | Total support tickets raised |
| cancellation_tickets | Count of formal cancellation requests |
| country_zaf / ken / nga | Country one-hot encodings (3 features) |
| channel_direct / dealer / online | Channel one-hot encodings (3 features) |
| product_basic / premium / fleet | Product tier one-hot encodings (3 features) |
| vehicle_complex | Binary — BMW, Mercedes, or Audi |
Output conversion: Model outputs P(churn in 3 months). Converted to monthly:
Preservation tiers: RED > 60% 3-month churn probability · AMBER 30–60% · GREEN < 30%
Priority queue: Sorted by
5.3 Collections Decision Model — backend/analytics/collections.py
Algorithm: Expected Value Maximisation
Decision frequency: Monthly per bad-billing contract
See Section 3 Bad Billing for formula details. Priority score for urgency ranking:
5.4 Pricing Engine — backend/analytics/pricing.py
Algorithm: SciPy minimize_scalar (bounded)
Elasticity model: Exponential demand curve
OBJECTIVE
DEMAND MODEL
LTV AT PRICE
BREAK-EVEN
VERDICT
Segment elasticity parameters (α, β) indexed by (country, channel, tier). Example ZAF values:
| Country | Channel | Tier | α (base conversion) | β (price sensitivity) |
| ZAF | direct | standard | 0.80 | 0.0018 |
| ZAF | dealer | basic | 0.90 | 0.0030 |
| ZAF | corporate | fleet | 0.95 | 0.0015 |
| KEN | direct | basic | 0.88 | 0.0040 |
| NGA | direct | basic | 0.86 | 0.0045 |
6. API Reference
Base URL: http://localhost:8000/api/v1/ · Interactive docs: /docs
Management
| Method | Endpoint | Description | Key Params |
| GET | /management/portfolio-summary | Top-level C-suite KPIs (MRR, ARR, churn rate, gross profit) | — |
| GET | /management/mrr-waterfall | 12-month MRR waterfall (new, churned, net) | months=12 |
| GET | /management/unit-economics-by-cohort | Quarterly cohort retention and payback | — |
| GET | /management/cashflow-projection | 36-month forward cashflow with 80% CI bands | horizon_months=36 |
| GET | /management/churn-analytics | Churn rates by country, product tier, and tenure band | country (optional) |
| GET | /management/contract/{id} | Full contract drill-down with payment history | contract_id (path) |
Sales
| Method | Endpoint | Description | Key Params |
| POST | /sales/pricing-calculator | Real-time optimal price + PROCEED/BLOCK verdict | country_code, product_tier, channel, distance_km, vehicle_complexity |
| GET | /sales/ltv-by-segment | LTV leaderboard by product, channel, country, or region | group_by, country |
| GET | /sales/cohort-ltv-heatmap | Monthly cohort cumulative revenue vs upfront cost | — |
| GET | /sales/acquisition-funnel | Channel CAC, retention rate, payback by channel | — |
| GET | /sales/target-segments | Top 20 segments ranked by avg_ltv × contract_count | — |
Retentions
| Method | Endpoint | Description | Key Params |
| GET | /retentions/preservation-queue | Ranked list of at-risk contracts (RED/AMBER/GREEN) | limit, tier, country |
| GET | /retentions/churn-risk-distribution | Portfolio churn score histogram + percentiles | — |
| GET | /retentions/collections-queue | Bad billing EV decision queue with recommended actions | limit |
| GET | /retentions/cancellation-notices | Formal cancellation request tickets + penalty at stake | — |
| GET | /retentions/intervention-outcomes | Save rate, resolved vs escalated cancellation requests | — |
Governance & System
| Method | Endpoint | Description |
| GET | /governance/model-registry | Registered model versions, AUC, drift status |
| GET | /governance/churn-model-performance | Calibration by decile, AUC trend, precision/recall |
| GET | /governance/feature-drift | PSI per feature vs training baseline |
| GET | /governance/data-quality | Record counts, null rates, schema validity per source |
| GET | /governance/audit-log | Pricing overrides, model events, drift alerts |
| GET | /health | System health check |
7. Dashboard Guide
| Dashboard | URL | Audience | Key Panels |
| Overview | / | All stakeholders | Live portfolio KPIs (6 cards), navigation to all dashboards |
| Sales & Pricing | /sales | Sales agents, pricing team | Pricing calculator → PROCEED/BLOCK, LTV by segment, acquisition funnel, cohort heatmap, target segments |
| Management | /management | CEO, CFO, board | 7-card KPI strip, MRR waterfall, 36m cashflow projection, cohort unit economics, contract drill-down |
| Retentions | /retentions | Retentions & collections agents | Preservation priority queue (filterable), churn risk histogram, intervention outcomes donut, collections EV queue |
| Governance | /governance | Data science, compliance | Model registry, calibration chart, AUC trend, PSI drift table, data quality, audit log |
| Wiki | /wiki | All teams | This documentation page |
All dashboards auto-refresh every 60 seconds. A live indicator (green dot) in the top-right confirms the connection is active.
8. Deployment
Prerequisites
Python 3.11+
pip install -r backend/requirements.txt
Start Server
# Option 1: direct
python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload
# Option 2: Windows double-click
start.bat
# Option 3: Linux / Mac
bash start.sh
Access
Dashboards: http://localhost:8000/
API docs: http://localhost:8000/docs
Health: http://localhost:8000/health
Fresh Seed
# Delete the database to force a fresh 5,000-contract seed on next startup
del cartrack_ltv.db # Windows
rm cartrack_ltv.db # Linux / Mac
# Quick test seed (200 contracts)
python -c "from backend.database import SessionLocal; from backend.data.seed import run_seed; db=SessionLocal(); run_seed(db, n_contracts=200)"
Environment Variables (.env)
| Variable | Default | Description |
| DATABASE_URL | sqlite:///./cartrack_ltv.db | SQLAlchemy connection string. Use postgresql://... for production. |
| DISCOUNT_RATE_ANNUAL | 0.12 | WACC proxy for LTV discounting (12% annual) |
| ENVIRONMENT | development | development | production |
| LOG_LEVEL | INFO | Python logging level |
| SECRET_KEY | dev-secret-key | Must be changed in production |
Run Tests
# All tests (excludes slow XGBoost training by default)
python -m pytest tests/ -v
# Analytics only
python -m pytest tests/test_analytics_*.py -v
# API integration tests
python -m pytest tests/test_api_*.py -v
# With coverage
python -m pytest tests/ --cov=backend --cov-report=term-missing
High Availability (Production)
The platform is designed for single-node development but supports multi-node production deployment with the following HA configuration:
| Component | Dev Mode | HA Production | How to Enable |
| Web Server | Uvicorn single worker | Gunicorn + N UvicornWorkers behind Nginx/ALB | gunicorn backend.main:app -w 4 -k uvicorn.workers.UvicornWorker |
| Database | SQLite (file-based) | PostgreSQL 15+ primary + read replicas | Set DATABASE_URL=postgresql://user:pass@host/db |
| Cache | In-memory TTL dict | Redis cluster (shared across workers) | Set REDIS_URL=redis://host:6379/0 |
| Task Queue | Synchronous | Celery + Redis broker | Set CELERY_BROKER_URL, run celery -A backend.tasks worker |
| Load Balancer | None (direct) | Nginx / Traefik / AWS ALB | TLS termination, sticky sessions for WebSocket |
| Connection Pool | SQLAlchemy default | PgBouncer for connection pooling | Point DATABASE_URL at PgBouncer, set pool_size=20 |
| Monitoring | Console logs | Prometheus + Grafana + Sentry | Mount /metrics endpoint, configure Sentry DSN |
| Containers | Bare metal | Docker Compose / K8s / ECS | docker-compose up --scale app=4 |
| ML Models | In-process training | MLflow registry + scheduled retraining | Set MLFLOW_TRACKING_URI, Airflow/cron schedule |
| Secrets | Default dev key | Vault / AWS Secrets Manager | Set SECRET_KEY to cryptographic random value |
Critical for production: The SECRET_KEY must be changed from the default. The app will refuse to start in ENVIRONMENT=production with the dev key. Generate with: python -c "import secrets; print(secrets.token_hex(32))"
9. Model Governance
Model Registry
| Model | Algorithm | Features | Primary Metric |
| ChurnClassifier | XGBoost (n=150, depth=4, lr=0.05) | 19 | AUC = 0.823 |
| LTVSurvivalModel | Parametric survival (exponential) | 12 | MAE% = 8.3% |
| PricingEngine | Constrained optimisation + elasticity | 9 | Margin delta = +2.1% |
| CollectionsDecisionModel | Expected Value maximisation | 8 | Decision accuracy = 74% |
PSI Thresholds (Population Stability Index)
| PSI Range | Status | Action |
| < 0.10 | STABLE | No action required |
| 0.10 – 0.20 | MODERATE | Monitor closely; investigate root cause |
| > 0.20 | ALERT | Trigger model retraining; escalate to data science team |
Audit Log Event Types
| Event | Description |
| MODEL_RETRAIN | Scheduled or drift-triggered retraining of a model |
| MODEL_DEPLOY | Promotion of challenger to champion in production |
| PRICE_OVERRIDE | Sales agent manually overrides engine-recommended price |
| DRIFT_ALERT | PSI exceeded threshold on a monitored feature |
| DATA_INGESTION | New data loaded into the platform |
| CONFIG_CHANGE | System configuration parameter modified |
10. Glossary
| Term | Definition |
| LTV | Life Time Value — net present value of all future cashflows from a contract, discounted at WACC. Includes subscription PV, penalty PV, and recovery PV minus upfront cost. |
| MRR | Monthly Recurring Revenue — sum of monthly_fee across all active contracts. |
| ARR | Annualised Recurring Revenue — MRR × 12. |
| Churn Rate | Proportion of active contracts that cancel in a given month. Annualised = monthly × 12. |
| Bad Billing | Contract status where payment has lapsed but the contract has not yet been cancelled. Hardware remains installed. Decision pending (continue / cancel / retrieve). |
| Cancellation Clause | Minimum subscription term (typically 36 months). Early cancellation triggers a penalty = monthly_fee × remaining_months × 0.75. |
| Payback Period | Months until cumulative discounted net cashflow exceeds upfront cost. Payback = upfront_cost / monthly_net_margin. |
| PSI | Population Stability Index — measures how much a feature's distribution has shifted between the training baseline and current production data. |
| WACC | Weighted Average Cost of Capital — used as the discount rate proxy. Set at 12% annual (0.949% monthly) in this platform. |
| CAC | Customer Acquisition Cost — upfront costs: marketing attribution + sales commission + call centre conversion cost. |
| Preservation Queue | Ranked list of active contracts ordered by LTV_at_risk × churn_probability. Used by retentions agents for proactive intervention. |
| Survival Curve | — the probability that a contract remains active at month t. |
| Price Elasticity | Demand model: . β measures price sensitivity; higher β = more price-sensitive segment. |
| Champion Model | The model version currently deployed in production. Challenger models are evaluated before promotion. |
| EV | Expected Value — probability-weighted outcome used in the bad billing decision model to compare continue / cancel / retrieve actions. |
| Save Rate | Retentions KPI = resolved cancellation requests / total cancellation requests. Measures the team's effectiveness at retaining subscribers who intend to cancel. |