Service State Workflow - Developer Guide¶
Complete code examples for managing service states, quotas, and consumption tracking.
Overview¶
Service states track quota usage for each service in a plan. This guide shows you how to: - Initialize service states from templates - Update individual service consumption - Top up quotas via Odoo payments - Track energy consumption (for energy tracking plans)
Understanding Service States¶
Each ServicePlan has an array of serviceStates:
{
serviceId: string; // Reference to Service definition
used: number; // Consumption so far (swaps, kWh, etc.)
quota: number; // Limit (can be infinity: 100000000)
currentAsset: string | null; // Currently assigned asset (battery ID)
}
Key Concepts: - Quota: Maximum allowed consumption before billing or block - Used: Current consumption (increments with each service use) - CurrentAsset: Tracks which battery customer currently has
Phase 0: Initialize Service States¶
Purpose¶
Initialize quota tracking before any service begins. This creates the serviceStates array for all services in the plan.
Basic Initialization¶
Publish Topic:
emit/uxi/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/initialize_states
Payload:
{
"timestamp": "2025-01-15T08:05:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "init-states-001",
"actor": {
"type": "system",
"id": "service-plan-manager"
},
"data": {
"action": "INITIALIZE_SERVICE_STATES"
}
}
Subscribe Topic:
echo/abs/service/plan/+/initialize_states_result
Expected Response:
{
"signals": ["SERVICE_STATES_INITIALIZED"],
"metadata": {
"called": "initializeServiceStates",
"template_id": "template-premium-bss",
"initialized_services": [
"svc-battery-fleet-kenya-premium",
"svc-electricity-kenya-72v"
],
"service_states": [
{
"service_id": "svc-battery-fleet-kenya-premium",
"quota": 100000000,
"used": 0.0,
"current_asset": null
},
{
"service_id": "svc-electricity-kenya-72v",
"quota": 100000000,
"used": 0.0,
"current_asset": null
}
],
"initialization_count": 2
}
}
What Happens:
1. ✅ Fetches services from ServicePlanTemplate
2. ✅ Creates serviceStates array with initial quotas
3. ✅ Sets used: 0.0 for all services
4. ✅ Sets current_asset: null (no battery assigned yet)
⚠️ CRITICAL: This step is MANDATORY before any workflow (rider or attendant)!
Automatic Service State Updates (W5)¶
How It Works¶
Service states are automatically updated when: 1. Attendant: Equipment checkout triggers W5 via MQTT 2. Rider: Service completion updates all states atomically
Equipment Checkout → Automatic Update¶
When an attendant issues a battery (A3.2), service states update automatically:
Publish Topic:
call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkout
Payload:
{
"timestamp": "2025-01-15T10:15:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "att-checkout-001",
"actor": {
"type": "attendant",
"id": "attendant-001"
},
"data": {
"action": "EQUIPMENT_CHECKOUT",
"replacement_equipment_id": "BAT_NEW_ATT_001",
"energy_transferred": 45.5,
"service_duration": 240
}
}
Note: System automatically publishes to emit/abs/service/plan/{plan_id}/service_completion (you don't do this manually). This updates ALL service states atomically.
What Gets Updated:
- Battery Service: used += 1 (1 swap consumed)
- Energy Service: used += 45.5 (45.5 kWh consumed, if provided)
- Current Asset: Battery ID assigned to customer
Manual Service State Updates (Optional)¶
Update Individual Service¶
For fine-grained control or testing, you can update services individually:
Publish Topic:
emit/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/update_battery_service
Payload:
{
"timestamp": "2025-01-15T11:10:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "update-battery-001",
"actor": {
"type": "system",
"id": "service-coordinator"
},
"data": {
"action": "UPDATE_INDIVIDUAL_SERVICE_STATE",
"service_id": "svc-battery-fleet-latest-a",
"consumption_amount": 1.0,
"asset_allocation": "battery-72v-003"
}
}
Subscribe Topic:
echo/abs/service/plan/+/update_battery_service_result
Expected Response:
{
"signals": ["SERVICE_STATE_UPDATED"],
"metadata": {
"called": "updateIndividualServiceState",
"service_id": "svc-battery-fleet-kenya-premium",
"consumption_amount": 1.0,
"used_before": 1.0,
"used_after": 2.0,
"quota_limit": 100000000,
"is_infinity_quota": true,
"quota_exceeded": false,
"asset_allocation": "battery-72v-003",
"independent_update": true
}
}
Service Quota Top-Up (Odoo Payment Integration)¶
How Quota Top-Up Works¶
When a customer's quota is exhausted, they can purchase more via Odoo payment gateway:
Payment Amount ÷ Service Unit Price = Additional Quota
Example:
$100 payment ÷ $5 per swap = 20 additional swaps
$250 payment ÷ $0.50 per kWh = 500 additional kWh
Key Point: Unit price is automatically fetched from the Service definition (no manual input required).
Battery Service Top-Up¶
Customer exhausted battery swap quota and needs more:
Publish Topic:
emit/abs/service/plan/service-plan-basic-newest-d/quota_topup
Payload:
{
"timestamp": "2025-01-15T10:35:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "topup-basic-001",
"actor": {
"type": "odoo",
"id": "odoo-payment-gateway"
},
"data": {
"action": "SERVICE_TOPUP",
"service_id": "svc-battery-fleet-kenya-premium",
"payment_amount": 100.0,
"payment_reference": "odoo-payment-12345"
}
}
Subscribe Topic:
echo/abs/service/plan/+/quota_topup_result
Expected Response:
{
"signals": ["SERVICE_QUOTA_UPDATED", "PAYMENT_PROCESSED"],
"metadata": {
"called": "serviceTopup",
"service_id": "svc-battery-fleet-kenya-premium",
"payment_amount": 100.0,
"unit_price": 5.0,
"unit_price_source": "service_definition",
"additional_quota": 20.0,
"quota_before": 10,
"quota_after": 30,
"quota_calculation": "100 / 5 = 20",
"service_pricing": {
"service_name": "Battery Fleet Access - Kenya Premium",
"service_asset_type": "FLEET",
"usage_metric": "Count",
"usage_unit": "1",
"unit_price": 5.0
},
"payment_reference": "odoo-payment-12345"
}
}
What Happens:
1. ✅ System fetches service.usageUnitPrice (automatically)
2. ✅ Calculates additional quota: 100 / 5 = 20 swaps
3. ✅ Updates quota: 10 + 20 = 30 swaps
4. ✅ Tracks payment reference for audit
5. ✅ Customer can resume service (20 swaps remaining)
Energy Service Top-Up¶
Customer exhausted electricity quota and needs more kWh:
Publish Topic:
emit/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/quota_topup
Payload:
{
"timestamp": "2025-01-15T10:40:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "topup-energy-001",
"actor": {
"type": "odoo",
"id": "odoo-payment-gateway"
},
"data": {
"action": "SERVICE_TOPUP",
"service_id": "svc-electricity-energy-tracking-new-a",
"payment_amount": 250.0,
"payment_reference": "odoo-payment-67890"
}
}
Subscribe Topic:
echo/abs/service/plan/+/quota_topup_result
Expected Response:
{
"signals": ["SERVICE_QUOTA_UPDATED", "PAYMENT_PROCESSED"],
"metadata": {
"called": "serviceTopup",
"service_id": "svc-electricity-energy-tracking-new-a",
"payment_amount": 250.0,
"unit_price": 0.5,
"unit_price_source": "service_definition",
"additional_quota": 500.0,
"quota_before": 5000,
"quota_after": 5500,
"quota_calculation": "250 / 0.5 = 500",
"service_pricing": {
"service_name": "Electricity Consumption - Energy Tracking",
"service_asset_type": "METER",
"usage_metric": "Energy",
"usage_unit": "kWh",
"unit_price": 0.5
},
"payment_reference": "odoo-payment-67890"
}
}
Business Logic:
1. Customer exhausts electricity quota (4850 / 5000 kWh used)
2. System blocks next swap with QUOTA_EXHAUSTED signal
3. Customer pays $250 via Odoo
4. Odoo publishes top-up MQTT message
5. System fetches unit price: $0.50 per kWh
6. Calculates additional quota: 250 / 0.5 = 500 kWh
7. Updates quota: 5000 + 500 = 5500 kWh
8. Customer resumes service (650 kWh remaining)
Multi-Service Top-Up¶
Top up BOTH battery and electricity in separate transactions:
First - Battery Service:
Publish Topic:
emit/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/quota_topup
Payload:
{
"timestamp": "2025-01-15T10:45:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "topup-multi-battery-001",
"actor": {
"type": "odoo",
"id": "odoo-payment-gateway"
},
"data": {
"action": "SERVICE_TOPUP",
"service_id": "svc-battery-fleet-energy-tracking-new-a",
"payment_amount": 50.0,
"payment_reference": "odoo-payment-multi-001"
}
}
Second - Electricity Service:
Publish Topic:
emit/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/quota_topup
Payload:
{
"timestamp": "2025-01-15T10:45:05Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "topup-multi-electricity-001",
"actor": {
"type": "odoo",
"id": "odoo-payment-gateway"
},
"data": {
"action": "SERVICE_TOPUP",
"service_id": "svc-electricity-energy-tracking-new-a",
"payment_amount": 50.0,
"payment_reference": "odoo-payment-multi-002"
}
}
Results:
- Battery fleet: 50 / 5.0 = 10 additional swaps
- Electricity: 50 / 0.5 = 100 additional kWh
- Both quotas updated independently
Quota Monitoring¶
Check Current Quota Status¶
Publish Topic:
call/uxi/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/get_service_states
Payload:
{
"timestamp": "2025-01-15T11:00:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "query-states-001",
"actor": {
"type": "customer",
"id": "CUST-001"
},
"data": {
"action": "GET_SERVICE_STATES"
}
}
Subscribe Topic:
rtrn/abs/service/plan/+/get_service_states
Expected Response:
{
"service_states": [
{
"serviceId": "svc-battery-fleet-kenya-premium",
"used": 15,
"quota": 30,
"currentAsset": "BAT_002_ISSUED",
"quota_percentage": 50.0,
"remaining": 15
},
{
"serviceId": "svc-electricity-kenya-72v",
"used": 4850,
"quota": 5500,
"currentAsset": null,
"quota_percentage": 88.2,
"remaining": 650
}
]
}
Complete Example: Service State Lifecycle¶
#!/bin/bash
# Complete service state management example
PLAN_ID="bss-plan-weekly-freedom-nairobi-v2-plan1"
echo "===== SERVICE STATE LIFECYCLE ====="
echo ""
# Step 1: Initialize service states (Phase 0 - REQUIRED)
echo "Step 1: Initializing service states..."
mosquitto_pub -h localhost -t "emit/uxi/service/plan/$PLAN_ID/initialize_states" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"lifecycle-001-init\",
\"actor\": {\"type\": \"system\", \"id\": \"service-plan-manager\"},
\"data\": {\"action\": \"INITIALIZE_SERVICE_STATES\"}
}"
sleep 3
# Step 2: Simulate service consumption (via checkout)
echo "Step 2: Simulating service consumption..."
mosquitto_pub -h localhost -t "call/uxi/attendant/plan/$PLAN_ID/equipment_checkout" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"lifecycle-002-checkout\",
\"actor\": {\"type\": \"attendant\", \"id\": \"attendant-001\"},
\"data\": {
\"action\": \"EQUIPMENT_CHECKOUT\",
\"replacement_equipment_id\": \"BAT_NEW_001\",
\"energy_transferred\": 45.5,
\"service_duration\": 180
}
}"
sleep 3
# Step 3: Check quota status
echo "Step 3: Checking quota status..."
mosquitto_pub -h localhost -t "call/uxi/service/plan/$PLAN_ID/get_service_states" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"lifecycle-003-check\",
\"actor\": {\"type\": \"customer\", \"id\": \"CUST-001\"},
\"data\": {\"action\": \"GET_SERVICE_STATES\"}
}"
sleep 2
# Step 4: Top up quota (simulate exhaustion scenario)
echo "Step 4: Processing quota top-up..."
mosquitto_pub -h localhost -t "emit/abs/service/plan/$PLAN_ID/quota_topup" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"lifecycle-004-topup\",
\"actor\": {\"type\": \"odoo\", \"id\": \"odoo-payment-gateway\"},
\"data\": {
\"action\": \"SERVICE_TOPUP\",
\"service_id\": \"svc-battery-fleet-energy-tracking-new-a\",
\"payment_amount\": 100.0,
\"payment_reference\": \"odoo-payment-lifecycle-001\"
}
}"
sleep 2
# Step 5: Verify updated quota
echo "Step 5: Verifying updated quota..."
mosquitto_pub -h localhost -t "call/uxi/service/plan/$PLAN_ID/get_service_states" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"lifecycle-005-verify\",
\"actor\": {\"type\": \"customer\", \"id\": \"CUST-001\"},
\"data\": {\"action\": \"GET_SERVICE_STATES\"}
}"
echo ""
echo "✅ Service state lifecycle completed!"
echo "Quota initialized → Consumed → Topped up → Verified"
Monitoring Service States¶
Terminal 1: Service state updates
emit/abs/service/plan/+/initialize_states
echo/abs/service/plan/+/initialize_states_result
emit/abs/service/plan/+/service_completion
echo/abs/service/plan/+/service_completion_result
Terminal 2: Quota top-ups
emit/abs/service/plan/+/quota_topup
echo/abs/service/plan/+/quota_topup_result
Terminal 3: Individual service updates
emit/abs/service/plan/+/update_battery_service
emit/abs/service/plan/+/update_energy_service
Common Scenarios¶
Scenario 1: Quota Exhaustion¶
System detects quota exhaustion:
{
"signals": ["QUOTA_EXHAUSTED"],
"metadata": {
"service_id": "svc-battery-fleet-kenya-premium",
"used": 30,
"quota": 30,
"remaining": 0
}
}
Solution: Process top-up payment (See "Battery Service Top-Up" above)
Scenario 2: Multiple Services¶
Energy tracking plans track BOTH swaps and kWh:
{
"service_states": [
{
"serviceId": "svc-battery-fleet-energy-tracking-new-a",
"used": 15,
"quota": 30
},
{
"serviceId": "svc-electricity-energy-tracking-new-a",
"used": 650,
"quota": 5500
}
]
}
Note: Each service has independent quota management
Error Handling¶
| Error Signal | Cause | Solution |
|---|---|---|
QUOTA_EXHAUSTED |
Service quota reached | Process top-up payment |
SERVICE_ID_NOT_FOUND |
Invalid service ID | Verify service exists in template |
QUOTA_LIMIT_NOT_SET |
Quota not configured | Initialize service states first |
PAYMENT_AMOUNT_INVALID |
Invalid top-up amount | Use positive non-zero amount |
Next Steps¶
- Attendant Workflow Guide - Staff-operated swaps
- Rider Workflow Guide - Mobile app-driven swaps
- Odoo Sync Workflow - Payment integration