State Machine Architecture for Touring Deals
Executive Summary
This document defines a state machine architecture for modeling deals and handling what is currently referred to as Settling. The core innovation is treating deals as calculable state documents where settlements are derived from state rather than stored as separate entities.
Key Paradigm Shifts:
- Settlements are calculated from state, not stored independently
- Multiple "what-if" versions can exist simultaneously (Forecast, Over Performance, etc.)
- Versions are Git-like: checkout → modify → submit
- Templates define structure, instances define values
- Cross-clause references enable tour-level aggregation without special constructs
Part 1: Core Concepts
1.1 Conceptual Hierarchy
DEAL (Top-level container)
│
├── deal_id, client_id, buyer_id, department_id, deal_type, metadata
│
├── Version Pointers
│ ├── primary_version_id: "v_abc123"
│ ├── forecast_primary_id: "v_def456"
│ └── [custom_pointer_name]: "v_..."
│
└── VERSIONS (Multiple independent snapshots)
│
├── Version 1 (Submitted - Immutable)
│ ├── version_id, status: "submitted"
│ ├── submitted_by, submitted_at
│ └── clause_blocks: [...]
│
├── Version 2 (Submitted - Immutable)
│ └── ...
│
├── Working Version A (Mutable - User editing)
│ ├── version_id, status: "working"
│ ├── created_from: "version_1_id"
│ ├── locked_by: "user_123"
│ └── clause_blocks: [...]
│
└── Working Version B (Mutable - Different user)
└── ...
CLAUSE_BLOCK (Execution unit within a version)
│
├── clause_block_id, name, reference (optional)
├── static_variables: { v_guarantee: 2500, v_artist_pct: 85 }
│
├── STATES (Calculated values with status)
│ ├── SHOW_STATE: { value, status, calculation }
│ └── SHOW_SETTLED: { value, status, calculation }
│
├── INPUTS (User-provided or sourced data)
│ ├── TICKET_DETAIL_INPUT: { value, status }
│ └── FACILITY_FEE_INPUT: { value, status }
│
└── FINANCIAL_CLAUSES (Financial obligations)
└── { name, amount, trigger, calculation, dates, payment_terms }1.2 Key Definitions
| Concept | Definition |
|---|---|
| Deal | Top-level container holding all versions of a contract |
| Version | Complete snapshot of deal state; can be "working" (mutable) or "submitted" (immutable) |
| Clause Block | Logical grouping of states, inputs, and financial clauses for one aspect of a deal (e.g., one show, merch split) |
| State | Calculated value with status; can be overridden by user |
| Input | User-provided or externally-sourced data point |
| Financial Clause | Financial obligation with trigger condition and calculation |
| Template | Reusable structure definition (without values) for creating deals |
1.3 Status Codes
State Status Codes:
| Code | Name | Meaning | Calculation Behavior |
|---|---|---|---|
| D | Draft | Initial entry, incomplete | Calculation runs, sets value |
| F | Forecast | Projected/estimated values | Calculation runs, sets value |
| C | Confirmed | Actual verified values | Calculation runs, sets value |
| O | Override | User manually set value | Calculation runs but does NOT set value |
Input Status Codes:
| Code | Name | Meaning |
|---|---|---|
| P | Pending | Input expected but not yet received; user can still enter estimated values |
| C | Confirmed | Input received and verified |
| X | Not Required | Input not needed for this deal (clause won't trigger, OR condition, etc.) |
Version Status:
| Status | Meaning | Mutability |
|---|---|---|
| working | User actively editing | Mutable (auto-saves) |
| submitted | Finalized snapshot | Immutable |
Part 2: Version Lifecycle
2.1 Git-Like Workflow
┌─────────────────────────────────────────────────────────────┐
│ DEAL: tour_spring_2025 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Submitted Versions (Immutable): │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ v_001 │ │ v_002 │ │ v_003 │ │
│ │ Nov 15 │───▶│ Nov 20 │───▶│ Dec 1 │ │
│ │ Initial │ │ Updated │ │ Post- │ │
│ │ Forecast│ │ Tickets │ │ Show │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ │ │ │ │
│ Working Versions (Mutable): │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌─────────┐ │ ┌─────────┐ │
│ │ w_abc │ │ │ w_xyz │ │
│ │ User A │ │ │ User B │ │
│ │ "What │ │ │ "Final │ │
│ │ if?" │ │ │ Settle" │ │
│ └─────────┘ │ └─────────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ w_def │ │
│ │ User C │ │
│ │ "Over │ │
│ │ Perf" │ │
│ └─────────┘ │
│ │
│ Pointers: │
│ ├── primary: v_003 │
│ ├── forecast_optimistic: w_abc │
│ └── forecast_pessimistic: (none) │
│ │
└─────────────────────────────────────────────────────────────┘2.2 Version Operations
Create Working Version:
User action: "Create new working version from v_002"
1. System creates new version document
2. Deep copies all clause_blocks, states, inputs, financial_clauses from v_002
3. Sets status = "working"
4. Sets created_from = "v_002"
5. Sets locked_by = current_user
6. User can now modifyModify Working Version:
User action: Changes TICKET_DETAIL_INPUT.quantity from 900 to 950
1. Input value updates immediately (auto-save)
2. Calculation engine runs on all dependent states
3. States with status != O get new calculated values
4. States with status == O keep user-provided values
5. Clause amounts recalculate based on triggers
6. UI reflects changes in real-timeSubmit Working Version:
User action: "Submit this version"
1. System validates all references resolve
2. System validates no circular dependencies
3. System validates required inputs (non-X) have values
4. If valid:
- Changes status from "working" to "submitted"
- Sets submitted_by, submitted_at
- Version becomes immutable
- Removes locked_by
5. If invalid:
- Returns validation errors
- Version remains "working"2.3 Version Independence
Critical Rule: Versions are 100% independent.
- Modifying one version NEVER affects another
- Each version is a complete, self-contained snapshot
- No automatic propagation between versions
- Cross-deal references must specify exact version_id
- Pointers (primary, forecast, etc.) are just convenience labels
Part 3: Clause Block Structure
3.1 Anatomy of a Clause Block
{
// Identification
clause_block_id: "cb_fonda_guarantee_001",
clause_block_ref: "CB_FONDA_GUARANTEE", // Object reference for cross-block references
name: "The Fonda - Guarantee vs NBOR",
// Optional show reference (non-relational, can duplicate data)
reference: {
show_id: "show_fonda_dec15",
venue: "The Fonda Theatre",
city: "Los Angeles",
date: "2025-12-15",
capacity: 1200
},
// Static variables (set at initialization, can vary per clause_block)
static_variables: {
v_guarantee: 2500,
v_artist_percentage: 85,
v_tax_rate: 0.0825
},
// States (calculated values)
states: [
{
state_key: "SHOW_STATE",
// ... (see State structure)
},
{
state_key: "SHOW_SETTLED",
// ... (see State structure)
}
],
// Inputs (user-provided or sourced data)
inputs: [
{
input_key: "TICKET_DETAIL_INPUT",
// ... (see Input structure)
}
],
// Financial clauses (financial obligations)
financial_clauses: [
{
clause_id: "clause_guarantee_vs_nbor",
// ... (see Financial Clause structure)
}
]
}3.2 State Structure
{
state_key: "SHOW_SETTLED", // Unique key within clause_block
state_type: "object", // "boolean" | "number" | "string" | "object"
name: "Show Settlement Data", // Human-readable name
// Current value (calculated or overridden)
value: {
status: "Ready",
gross_box_office: 22500,
sales_tax_collected: 1856.25,
facility_fees: 2250,
net_box_office_receipts: 18393.75
},
// Status determines if calculation overwrites value
status: "F", // D, F, C, O
// Calculation formula (expression language)
calculation: {
type: "aggregate",
source: "SHOW_SETTLEMENT_INPUT",
// OR more complex:
type: "expression",
expression: "TICKET_DETAIL_INPUT.total_amount - (FACILITY_FEE_INPUT.amount_per_ticket * TICKET_DETAIL_INPUT.quantity)"
},
// Metadata
last_calculated_at: "2025-12-04T15:00:00Z",
calculation_error: null // If calculation failed, error message here
}3.3 Input Structure
{
input_key: "TICKET_DETAIL_INPUT", // Unique key within clause_block
input_type: "object", // "boolean" | "number" | "string" | "object"
name: "Ticket Sales Data", // Human-readable name
// Current value (user-provided or sourced)
value: {
tier: "general_admission",
quantity: 900,
price_per_ticket: 25.00,
total_amount: 22500.00,
sales_source: "box_office",
notes: "Final box office count"
},
// Status
status: "C", // P (Pending), C (Confirmed), X (Not Required)
// Optional: source reference (if input comes from external system)
source: {
type: "show", // "show" | "external_api" | "user" | "other_clause_block"
reference: "show_fonda_dec15",
field_path: "ticket_sales"
}
}3.4 Clause Structure
{
clause_id: "clause_guarantee_vs_nbor",
name: "Guarantee vs NBOR for Show",
// Calculated amount (result of calculation when trigger is true)
amount: 15634.69, // 85% of 18393.75 = 15634.69 > 2500
// Trigger condition (when does this clause apply?)
trigger: {
type: "expression",
expression: "SHOW_SETTLED.value.status == 'Ready'"
},
// Calculation formula (how to compute amount when triggered)
calculation: {
type: "expression",
expression: "MAX((SHOW_SETTLED.value.net_box_office_receipts * v_artist_percentage / 100), v_guarantee)"
},
// Date range (when is this clause effective)
start_date: "2025-11-01",
end_date: "2025-12-25",
// Pre-condition (optional: must be true before trigger is even evaluated)
pre_condition: null,
// Metadata for calculation transparency
calculation_metadata: {
guarantee_component: 2500,
percentage_component: 15634.69,
winning_path: "percentage", // "guarantee" or "percentage"
calculation_summary: "MAX($2,500, 18,393.75 (NBOR) * 85% = 15,634.69)",
last_calculated_at: "2025-12-04T15:00:00Z"
}
}Part 4: Calculation Engine Behavior
4.1 Calculation Flow
When a working version is modified:
User changes input value
│
▼
┌──────────────────────────────────────┐
│ 1. Update input value (auto-save) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 2. Build dependency graph │
│ - Which states reference this │
│ input? │
│ - Which states reference those │
│ states? │
│ - Which clauses reference those │
│ states? │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 3. Topological sort (detect cycles) │
│ - If cycle detected: report error │
│ - Continue with valid nodes │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 4. For each state in dependency │
│ order: │
│ │
│ IF state.status == 'O': │
│ → Skip (user override) │
│ → Keep existing value │
│ │
│ ELSE: │
│ → Run calculation │
│ → If calculation succeeds: │
│ state.value = result │
│ → If calculation fails: │
│ state.calculation_error = │
│ error_message │
│ Keep existing value │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 5. For each clause: │
│ - Evaluate trigger condition │
│ - If trigger == true: │
│ → Run calculation │
│ → Set clause.amount │
│ - If trigger == false: │
│ → Set clause.amount = 0 │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 6. Auto-status update: │
│ - If input was missing (null) │
│ and now has value │
│ - AND state.status != 'O' │
│ - THEN state.status = 'C' │
└──────────────────────────────────────┘4.2 Override Behavior
When status = O (Override):
State: SHOW_SETTLED
Status: O
Value: { net_box_office_receipts: 20000 } // User manually entered
Calculation: TICKET_DETAIL_INPUT.total_amount - ...
What happens on recalc:
1. Calculation RUNS (for transparency)
2. Calculated result: 18393.75
3. BUT value remains 20000 (user override)
4. Optionally store: calculated_value: 18393.75 (for comparison)Reverting Override:
User changes status from O → C (or D, F)
Next recalc:
1. Calculation runs
2. Value IS updated to calculated result
3. User override is discarded4.3 Error Handling
Transient Errors (Non-Blocking):
// User types "$1000" instead of "1000"
{
state_key: "SHOW_SETTLED",
value: { ... }, // Keep previous value
calculation_error: {
type: "type_mismatch",
message: "Cannot perform arithmetic on string '$1000'",
field: "TICKET_DETAIL_INPUT.total_amount",
timestamp: "2025-12-04T15:00:00Z"
}
}
// UI shows error indicator on field
// User can fix and error clears
// Does NOT block saving working versionReference Errors:
// Calculation references undefined input
{
state_key: "SHOW_SETTLED",
value: null,
calculation_error: {
type: "undefined_reference",
message: "Reference 'UNDEFINED_INPUT' not found",
timestamp: "2025-12-04T15:00:00Z"
}
}4.4 "Not Required" (X) Determination
Methods to mark input as X:
User Manual: User sets status = X directly
Pre-condition Evaluation:
javascript// Clause has pre_condition that evaluates to false clause: { pre_condition: "SHOW_STATE.value == true", // If SHOW_STATE is false, all inputs for this clause → X }Post-calculation Detection:
javascript// After calculation, detect input wasn't used // Example: MAX(A, B) where B > A, so A wasn't needed // System can suggest marking A's inputs as XOR Condition:
javascript// Clause A OR Clause B triggers // If A triggers, B's inputs → X
Part 5: Cross-Clause References
5.1 Reference Syntax
Reference within same clause_block:
SHOW_SETTLED.value.net_box_office_receipts
v_artist_percentage
TICKET_DETAIL_INPUT.value.quantityReference to another clause_block (same version):
CB_FONDA_GUARANTEE.states.SHOW_SETTLED.value.net_box_office_receipts
CB_MERCH_SPLIT.financial_clauses.clause_merch_artist.amountReference to another deal/version (must specify version):
DEAL:deal_show_001:VERSION:v_abc123.clause_blocks.CB_GUARANTEE.financial_clauses.clause_main.amount5.2 Tour Aggregation Example
// Tour Summary Clause Block
{
clause_block_id: "cb_tour_summary",
clause_block_ref: "CB_TOUR_SUMMARY",
name: "Spring Tour 2025 - Total Settlement",
static_variables: {},
states: [
{
state_key: "TOTAL_ARTIST_PAYMENT",
state_type: "number",
name: "Total Artist Payment",
value: 45234.56,
status: "F",
calculation: {
type: "aggregation",
expression: "SUM(cb.financial_clauses[0].amount for cb in [CB_SHOW_1, CB_SHOW_2, CB_SHOW_3] where cb.financial_clauses[0].trigger == true)"
}
}
],
inputs: [],
financial_clauses: [
{
clause_id: "clause_tour_total",
name: "Tour Total Payment",
amount: 45234.56,
trigger: { expression: "true" },
calculation: { expression: "TOTAL_ARTIST_PAYMENT.value" },
start_date: "2025-03-01",
end_date: "2025-03-31"
}
]
}5.3 Cross-Deal References
// Deal A references Deal B's submitted version
{
input_key: "SHOW_1_PAYMENT",
source: {
type: "cross_deal",
deal_id: "deal_show_001",
version_id: "v_abc123", // MUST specify version
path: "clause_blocks.CB_GUARANTEE.financial_clauses.clause_main.amount"
},
value: 15000, // Resolved at working version creation
status: "C"
}Important: Cross-deal references are resolved once when working version is created. They do not auto-update if source deal changes. To get new values, create new working version.
Part 6: Deal-Level Structure
6.1 Complete Deal Document
{
// Identification
deal_id: "deal_spring_tour_2025",
deal_type: "guarantee_vs_percentage_nbor",
// Parties
artist_id: "artist_taylor_swift",
promoter_id: "promoter_live_nation",
// Metadata
name: "Spring Tour 2025 - West Coast Leg",
created_at: "2025-01-15T10:00:00Z",
created_by: "booking_agent_001",
// Template reference (informational only, deal is independent)
initialized_from_template: "tpl_guarantee_vs_nbor_v2",
// Version pointers (convenience labels)
version_pointers: {
primary: "v_submitted_003",
forecast_optimistic: "v_submitted_002",
forecast_pessimistic: null,
final_settlement: null
},
// All versions (both working and submitted)
versions: [
{
version_id: "v_submitted_001",
status: "submitted",
submitted_at: "2025-01-20T14:00:00Z",
submitted_by: "booking_agent_001",
created_from: null, // First version
clause_blocks: [...]
},
{
version_id: "v_submitted_002",
status: "submitted",
submitted_at: "2025-02-15T09:00:00Z",
submitted_by: "booking_agent_001",
created_from: "v_submitted_001",
clause_blocks: [...]
},
{
version_id: "v_submitted_003",
status: "submitted",
submitted_at: "2025-03-20T16:00:00Z",
submitted_by: "finance_user_002",
created_from: "v_submitted_002",
clause_blocks: [...]
},
{
version_id: "v_working_abc",
status: "working",
locked_by: "booking_agent_001",
created_from: "v_submitted_003",
last_auto_save: "2025-03-25T11:30:00Z",
clause_blocks: [...]
}
]
}6.2 Version Document (Embedded in Deal)
{
version_id: "v_submitted_003",
status: "submitted", // "working" | "submitted"
// Submission metadata (only for submitted versions)
submitted_at: "2025-03-20T16:00:00Z",
submitted_by: "finance_user_002",
// Lineage
created_from: "v_submitted_002", // Which version this was branched from
// Working version specific (only for working versions)
// locked_by: "user_id",
// last_auto_save: "timestamp",
// The actual content
clause_blocks: [
{
clause_block_id: "cb_show_1",
clause_block_ref: "CB_SHOW_1",
name: "Show 1 - The Fonda",
// ... full clause block structure
},
{
clause_block_id: "cb_show_2",
clause_block_ref: "CB_SHOW_2",
name: "Show 2 - The Greek",
// ... full clause block structure
},
{
clause_block_id: "cb_merch",
clause_block_ref: "CB_MERCH",
name: "Merchandise Split",
// ... full clause block structure
},
{
clause_block_id: "cb_tour_summary",
clause_block_ref: "CB_TOUR_SUMMARY",
name: "Tour Total",
// ... aggregation clause block
}
]
}Part 7: Key Behaviors Summary
7.1 What DOES Auto-Update
| Scenario | Behavior |
|---|---|
| User changes input in working version | All dependent states recalculate |
| Input goes from null → value (status != O) | State status → C |
| Calculation produces error | Error stored, previous value kept |
| User changes state status from O → anything else | Next recalc updates value |
7.2 What Does NOT Auto-Update
| Scenario | Behavior |
|---|---|
| Source deal for cross-deal reference changes | No change; must create new working version |
| Template is modified after deal initialization | No change; deal is independent |
| Another version of same deal is modified | No change; versions are independent |
| User modifies different working version | No change; completely isolated |
7.3 Validation on Submit
Before a working version can be submitted:
- ✓ All references resolve (no undefined inputs/states/clause_blocks)
- ✓ No circular dependencies detected
- ✓ All required inputs (status != X) have values
- ✓ No unresolved calculation errors on required paths
- ✓ Cross-deal references point to valid submitted versions
Conclusion
This state machine architecture provides:
- Flexibility: Any deal structure can be modeled with clause blocks
- Transparency: All calculations visible and traceable
- Control: Users can override any calculated value
- Planning: What-if scenarios are first-class citizens
- Auditability: Version history provides complete audit trail
- Simplicity: No separate settlement documents to manage
- Independence: Versions and deals are fully isolated
The architecture cleanly separates:
- Structure (defined by templates)
- Values (entered by users or calculated)
- State (status codes indicating confidence level)
- History (version snapshots)
This enables powerful modeling of complex touring deals while maintaining clarity and control.