19 KiB
TEI Tool
The TEI (Total Economic Impact) Tool provides a configurable financial calculator for building client-specific business cases. It attaches to an existing Proposal or Engagement, inheriting client context (company name, industry, etc.) without redundant data entry, and exposes a REST API consumed by Streamlit calculators and LLM report-generation pipelines.
Overview
Two parent objects define a TEI calculation:
- TEIReport (admin-configured template) — defines the field schema, analysis period, and discount rate. Created once per study type (e.g., "Amazon Connect 2026"). Multiple tools share one report.
- TEITool (one per client opportunity) — holds the actual values, financial summary, and version history. Inherits
name,description,owner,subscriber,proposal,engagement, andis_activefrom {py:class}core.models.BaseTool.
The tool lifecycle is: create → seed values → edit values → calculate → save version → export.
Data Model
| Model | Role |
|---|---|
TEIReport |
Admin template: field schema + financial parameters |
TEIReportField |
One field definition (benefit or cost) on a report |
TEITool |
A specific calculation attached to a Proposal/Engagement |
TEIFieldValue |
Current value for one field, one year (or null for non-annual) |
TEIFinancialSummary |
Fixed-schema rollup (1-to-1 with a tool); written by /calculate/ |
TEIVersion |
Immutable JSON snapshot of values + summary at a point in time |
Public identifiers: TEIReport, TEITool, and TEIVersion are addressed by a 12-character short UUID (public_id) in all API URLs. TEIReportField is addressed by its integer PK under its parent report's public_id.
API Base URL and Authentication
/api/v1/tei/
All endpoints require IsAuthenticated. The error envelope is always:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description",
"details": [...]
}
}
Permissions
| Operation | Admin | Consultant | Viewer |
|---|---|---|---|
| Create/edit/delete Reports and Fields | Yes | No | No |
| Create TEITool | Yes | Yes | No |
| Edit values, trigger calculation, save version | Yes | Yes | No |
| View tools, values, summary, versions, export | Yes | Yes | Yes |
| Delete TEITool | Yes | Yes (own only) | No |
Data isolation follows the parent Proposal/Engagement — if a user cannot see the Proposal, they cannot see the attached TEI tool.
Reports (admin)
Reports are read-only for consultants; admins manage them.
List / Create
GET /api/v1/tei/reports/
POST /api/v1/tei/reports/
Query params (GET): status (draft | active | archived), vendor
POST body:
{
"name": "Amazon Connect 2026",
"vendor": "AWS",
"version": "1.0",
"description": "Based on Forrester TEI February 2026",
"analysis_period_years": 3,
"discount_rate": "0.10",
"status": "draft"
}
Response shape (list item):
{
"id": "Ab3Cd5Ef7Gh9",
"name": "Amazon Connect 2026",
"vendor": "AWS",
"version": "1.0",
"description": "...",
"analysis_period_years": 3,
"discount_rate": "0.1000",
"status": "active",
"field_count": 12,
"instance_count": 3,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
Detail / Update / Delete
GET /api/v1/tei/reports/{report_id}/
PUT /api/v1/tei/reports/{report_id}/
PATCH /api/v1/tei/reports/{report_id}/
DELETE /api/v1/tei/reports/{report_id}/
Business rules:
DELETEis only allowed whenstatus = draftand no tools exist (instance_count = 0).analysis_period_yearscannot be changed if any tools reference this report — raises409 MODEL_HAS_INSTANCES.
Report Fields (admin)
Fields define what the calculator collects. They are nested under their parent report.
List / Add
GET /api/v1/tei/reports/{report_id}/fields/
POST /api/v1/tei/reports/{report_id}/fields/
Query params (GET): table (benefits | costs), category
POST body:
{
"table": "benefits",
"field_key": "ai_resolution_efficiency",
"label": "AI-Driven Contact Resolution Efficiency",
"description": "Labor savings from AI-powered self-service",
"field_type": "currency",
"category": "AI Resolution",
"default_value": null,
"is_annual": true,
"risk_adjustment": "0.20",
"sort_order": 1,
"is_required": true,
"source_notes": "Forrester TEI 2026 — $64.3M over 3 years"
}
field_type choices: currency, percentage, integer, decimal, text
Adding a field to a report that already has tools will back-fill TEIFieldValue rows (null values) for every existing tool.
Update / Delete
PUT /api/v1/tei/reports/{report_id}/fields/{field_id}/
PATCH /api/v1/tei/reports/{report_id}/fields/{field_id}/
DELETE /api/v1/tei/reports/{report_id}/fields/{field_id}/?confirm=true
Protected fields — once any tool has values for a field, these attributes are immutable (raises 409 PROTECTED_FIELD):
field_keyfield_typeis_annual
Always mutable: label, description, default_value, risk_adjustment, sort_order, category, source_notes.
DELETE requires ?confirm=true and cascades all TEIFieldValue rows for that field across every tool. Historical version snapshots are unaffected (they are stored as JSON).
Reorder
PATCH /api/v1/tei/reports/{report_id}/fields/reorder/
{
"field_order": [
{"id": 1, "sort_order": 1},
{"id": 2, "sort_order": 2}
]
}
Tools
A tool is one TEI calculation attached to one Proposal or Engagement.
List / Create
GET /api/v1/tei/tools/
POST /api/v1/tei/tools/
Query params (GET): status, report (report public_id), proposal (PK), engagement (PK)
POST body:
{
"report": "Ab3Cd5Ef7Gh9",
"proposal": 42,
"name": null,
"status": "draft"
}
Supply exactly one of proposal or engagement. name defaults to the report name when omitted.
Side effects on create:
- Creates
TEIFieldValuerows for every field in the report (populated fromdefault_value, or empty string). - For annual fields, creates one row per year (1 through
analysis_period_years). - Creates an empty
TEIFinancialSummaryrecord.
A duplicate active tool for the same report + proposal/engagement raises 409 DUPLICATE_INSTANCE.
Detail / Update / Delete
GET /api/v1/tei/tools/{tool_id}/
PUT /api/v1/tei/tools/{tool_id}/
PATCH /api/v1/tei/tools/{tool_id}/
DELETE /api/v1/tei/tools/{tool_id}/?confirm=true
Only name and status are mutable via PUT/PATCH. DELETE requires ?confirm=true and cascades all values, versions, and summary.
Tool detail response:
{
"id": "Xy1Za2Bc3De4",
"report": {
"id": "Ab3Cd5Ef7Gh9",
"name": "Amazon Connect 2026",
"vendor": "AWS",
"version": "1.0",
"analysis_period_years": 3,
"discount_rate": "0.1000"
},
"opportunity": {
"id": "...",
"name": "Acme Corp CX Transformation",
"proposal_id": "...",
"client": {
"id": "...",
"name": "Acme Corporation",
"short_name": "Acme"
}
},
"engagement": null,
"name": "Amazon Connect 2026",
"status": "in_progress",
"current_version": 2,
"summary": {
"net_present_value": "14200000.00",
"roi_percentage": "289.8000",
"total_benefits_pv": "19100000.00",
"total_costs_pv": "4900000.00"
},
"created_date": "2025-01-20T14:30:00Z",
"modified_date": "2025-02-03T09:15:00Z"
}
Values
Get all values
GET /api/v1/tei/tools/{tool_id}/values/
Query params: table (benefits | costs), category
Annual fields return a years object; non-annual fields return a flat value:
{
"tool_id": "Xy1Za2Bc3De4",
"report": "Amazon Connect 2026",
"values": [
{
"id": 1,
"field_key": "ai_resolution_efficiency",
"label": "AI-Driven Contact Resolution Efficiency",
"table": "benefits",
"category": "AI Resolution",
"field_type": "currency",
"is_annual": true,
"risk_adjustment": "0.20",
"years": {
"1": {"value": "12500000.00", "risk_adjustment": null, "notes": ""},
"2": {"value": "24800000.00", "risk_adjustment": null, "notes": ""},
"3": {"value": "27000000.00", "risk_adjustment": "0.25", "notes": "Phase 3 risk"}
}
},
{
"id": 2,
"field_key": "legacy_termination",
"label": "Legacy Solution Termination Fees",
"table": "costs",
"category": "Migration",
"field_type": "currency",
"is_annual": false,
"risk_adjustment": null,
"value": "1200000.00",
"notes": "Confirmed by procurement"
}
]
}
Bulk update
PUT /api/v1/tei/tools/{tool_id}/values/
{
"values": [
{"field_key": "ai_resolution_efficiency", "year": 1, "value": "12500000.00", "risk_adjustment": null, "notes": null},
{"field_key": "ai_resolution_efficiency", "year": 2, "value": "24800000.00", "risk_adjustment": null, "notes": "60% containment by month 18"},
{"field_key": "legacy_termination", "year": null, "value": "1200000.00", "risk_adjustment": null, "notes": null}
]
}
yearmust benullfor non-annual fields,1..Nfor annual fields.- Validation raises
400 INVALID_FIELD_KEY,400 INVALID_YEAR, or400 TYPE_MISMATCHon bad input. - Does not auto-recalculate — call
/calculate/explicitly. - Returns the same shape as
GET /values/.
Update single value
PATCH /api/v1/tei/tools/{tool_id}/values/{field_key}/?year=1
{
"value": "13000000.00",
"risk_adjustment": "0.15",
"notes": "Revised after benchmarking call"
}
?year is required for annual fields; omit (or pass year=null) for non-annual fields.
Calculation
POST /api/v1/tei/tools/{tool_id}/calculate/
No request body. Uses current stored values. Persists result in TEIFinancialSummary (upsert).
Response:
{
"total_benefits_pv": "19100000.00",
"total_costs_pv": "4900000.00",
"net_present_value": "14200000.00",
"roi_percentage": "289.8000",
"payback_period_months": 8,
"total_benefits_nominal": "22300000.00",
"total_costs_nominal": "5400000.00",
"benefits_year_1": "5200000.00",
"benefits_year_2": "9800000.00",
"benefits_year_3": "7300000.00",
"costs_year_1": "3800000.00",
"costs_year_2": "900000.00",
"costs_year_3": "700000.00",
"discount_rate": "0.1000",
"calculated_at": "2025-02-03T09:15:00Z"
}
Summary
GET /api/v1/tei/tools/{tool_id}/summary/
Returns the stored TEIFinancialSummary in the same shape as the /calculate/ response. Returns 404 SUMMARY_NOT_CALCULATED if /calculate/ has never been called.
Versions
Versions are immutable snapshots. They cannot be updated or deleted via the API.
List
GET /api/v1/tei/tools/{tool_id}/versions/
Returns headline summary only (NPV + ROI) per version item.
Save new version
POST /api/v1/tei/tools/{tool_id}/versions/
{
"date": "2025-02-03",
"note": "Updated with actuals from finance team. Containment revised to 24%."
}
Side effects:
- Auto-triggers
/calculate/to ensure the summary is current. - Snapshots all current
TEIFieldValuerows as JSON intovalues_snapshot. - Snapshots current
TEIFinancialSummaryas JSON intosummary_snapshot. - Increments
tool.current_version.
Returns the created version (without full snapshots — use the detail endpoint to retrieve those).
Version detail
GET /api/v1/tei/tools/{tool_id}/versions/{version_number}/
Returns full values_snapshot and summary_snapshot.
Export (LLM payload)
GET /api/v1/tei/tools/{tool_id}/export/
Returns everything needed for LLM report generation in one payload. Auto-recalculates before building the response.
Response shape:
{
"export_date": "2025-02-03T09:30:00Z",
"client": {
"name": "Acme Corporation",
"short_name": "Acme",
"industry": "Financial Services",
"size": "enterprise"
},
"opportunity": {
"name": "CX Transformation",
"stage": "proposal"
},
"engagement": null,
"report": {
"name": "Amazon Connect 2026",
"vendor": "AWS",
"version": "1.0",
"analysis_period_years": 3,
"discount_rate": "0.10"
},
"benefits": [
{
"field_key": "ai_resolution_efficiency",
"label": "AI-Driven Contact Resolution Efficiency",
"category": "AI Resolution",
"risk_adjustment": "0.20",
"source_notes": "Forrester TEI 2026 — $64.3M over 3 years",
"years": {
"1": {"nominal": "12500000.00", "risk_adjusted": "10000000.00"},
"2": {"nominal": "24800000.00", "risk_adjusted": "19840000.00"},
"3": {"nominal": "27000000.00", "risk_adjusted": "20250000.00"}
},
"total_nominal": "64300000.00",
"total_risk_adjusted": "50090000.00",
"present_value": "41200000.00"
}
],
"costs": [
{
"field_key": "connect_licensing",
"label": "Amazon Connect Licensing & Usage",
"category": "Platform",
"source_notes": "Per-minute pricing model",
"years": {
"1": {"value": "2000000.00"},
"2": {"value": "2200000.00"},
"3": {"value": "2400000.00"}
},
"total_nominal": "6600000.00",
"present_value": "5490000.00"
}
],
"summary": {
"total_benefits_pv": "19100000.00",
"total_costs_pv": "4900000.00",
"net_present_value": "14200000.00",
"roi_percentage": "289.80",
"payback_period_months": 8
},
"versions": [
{"version_number": 2, "date": "2025-02-03", "note": "Actuals from finance team"},
{"version_number": 1, "date": "2025-01-15", "note": "Initial Forrester defaults"}
]
}
Non-annual cost fields appear under "years": {"1": {"value": "..."}} (treated as Year 1).
Cross-Tool Rollup
GET /api/v1/tei/summary/
Query params: status, vendor, min_npv
Returns aggregate NPV across all calculated tools plus per-tool headline rows. Only includes tools where /calculate/ has been run at least once.
Jupyter Notebook Workflow
A typical notebook session using the TEI API:
import requests
BASE = "https://athena.example.com/api/v1/tei"
HEADERS = {"Authorization": "Token <your-token>"}
TOOL_ID = "Xy1Za2Bc3De4" # TEITool public_id
# 1. Load tool metadata and client context
tool = requests.get(f"{BASE}/tools/{TOOL_ID}/", headers=HEADERS).json()
# 2. Load current values (benefits + costs)
values_resp = requests.get(f"{BASE}/tools/{TOOL_ID}/values/", headers=HEADERS).json()
values = values_resp["values"]
# 3. Update values (customize for the client)
updated_rows = [
{"field_key": "ai_resolution_efficiency", "year": 1, "value": "11000000.00"},
{"field_key": "ai_resolution_efficiency", "year": 2, "value": "23000000.00"},
{"field_key": "ai_resolution_efficiency", "year": 3, "value": "25500000.00"},
{"field_key": "legacy_termination", "year": None, "value": "950000.00"},
]
requests.put(
f"{BASE}/tools/{TOOL_ID}/values/",
headers=HEADERS,
json={"values": updated_rows},
)
# 4. Recalculate
summary = requests.post(f"{BASE}/tools/{TOOL_ID}/calculate/", headers=HEADERS).json()
print(f"NPV: {summary['net_present_value']}, ROI: {summary['roi_percentage']}%")
# 5. Save a version snapshot
requests.post(
f"{BASE}/tools/{TOOL_ID}/versions/",
headers=HEADERS,
json={"date": "2025-02-03", "note": "Notebook scenario — conservative containment"},
)
# 6. Get the full LLM-ready export
export = requests.get(f"{BASE}/tools/{TOOL_ID}/export/", headers=HEADERS).json()
# export["benefits"], export["costs"], export["summary"] are all populated
Finding the tool_id
If you know the Proposal PK or Engagement PK:
tools = requests.get(
f"{BASE}/tools/",
headers=HEADERS,
params={"proposal": 42, "status": "in_progress"},
).json()
tool_id = tools["results"][0]["id"]
If the tool doesn't exist yet, create it first:
new_tool = requests.post(
f"{BASE}/tools/",
headers=HEADERS,
json={"report": "Ab3Cd5Ef7Gh9", "proposal": 42},
).json()
tool_id = new_tool["id"]
Calculation Logic Reference
Benefit risk adjustment
risk_adjusted_value = nominal_value × (1 − risk_adjustment)
risk_adjustment comes from the TEIFieldValue instance override if set, otherwise from TEIReportField.risk_adjustment, otherwise 0. Costs are never risk-adjusted.
Present value discounting
Each annual value is discounted to today:
PV = value / (1 + discount_rate) ^ year
where year is 1, 2, … N and discount_rate comes from the TEIReport. Non-annual (one-time) values are treated as Year 1.
Summary calculations
total_benefits_nominal = sum of all risk-adjusted benefit values (all years)
total_costs_nominal = sum of all cost values (all years)
total_benefits_pv = Σ PV(risk_adjusted_benefit, year) for all benefit fields
total_costs_pv = Σ PV(cost, year) for all cost fields
net_present_value = total_benefits_pv − total_costs_pv
roi_percentage = (net_present_value / total_costs_pv) × 100
roi_percentage is null when total_costs_pv is 0.
Payback period
Annual values are prorated evenly across 12 months. One-time (non-annual) values land in month 1 as a lump sum. The payback month is the first month where cumulative risk-adjusted benefits ≥ cumulative costs. Returns null if never achieved within analysis_period_years.
Edge cases
| Scenario | Behaviour |
|---|---|
| Null/blank field value | Treated as 0 |
| All benefits zero | NPV = −total_costs_pv, ROI = null if costs are also 0 |
| All costs zero | NPV = total_benefits_pv, ROI = null (division by zero) |
risk_adjustment = 1.0 |
Benefit is zeroed out (fully excluded) |
risk_adjustment = 0.0 |
Full nominal value used |
| Non-annual field | Folded into Year 1 for NPV and payback |
All arithmetic uses Python Decimal to avoid floating-point drift. Values are stored as strings and cast at calculation time.
Error Codes
| HTTP | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR |
Missing required field, wrong type |
| 400 | INVALID_FIELD_KEY |
field_key not defined in the tool's report |
| 400 | INVALID_YEAR |
Year missing for annual field, or present for non-annual, or out of range |
| 400 | TYPE_MISMATCH |
Value cannot be parsed as the field's field_type |
| 401 | AUTHENTICATION_REQUIRED |
Missing or invalid token |
| 403 | PERMISSION_DENIED |
Role does not allow this operation |
| 404 | NOT_FOUND |
Resource does not exist |
| 404 | SUMMARY_NOT_CALCULATED |
GET /summary/ before any /calculate/ run |
| 409 | DUPLICATE_INSTANCE |
Creating a second active tool for the same report + proposal/engagement |
| 409 | FIELD_KEY_EXISTS |
field_key already exists within the report |
| 409 | PROTECTED_FIELD |
Changing field_key, field_type, or is_annual when values exist |
| 409 | MODEL_HAS_INSTANCES |
Deleting a report or changing analysis_period_years when tools exist |
| 422 | CALCULATION_ERROR |
Calculation failed unexpectedly |
TEI Tool — Athena