Skip to content

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