BSS Agent Enhancements - Technical Specification¶
Purpose: Define technical requirements for enhancing BSS Agent v2 to support the unified attendant workflow.
Status: 📝 Specification
Target Implementation: Phase 1 (2 weeks)
Overview¶
This specification outlines enhancements to the existing BSS Agent v2 to support:
- Electricity quota tracking - Track net electricity delivered during swaps
- Unified service completion - Single action to complete swap transaction
- Enhanced equipment tracking - Include SOC% data in battery identification
- Payment event publishing - Emit payment events for Odoo integration
Enhancement 1: Electricity Quota Tracking¶
Objective¶
Enable tracking of electricity (kWh) delivered during battery swaps, separate from swap-count quota.
Current State¶
The serviceStates[] array supports multiple service quotas:
serviceStates: [
{
service_id: "svc-battery-fleet-kenya-premium",
used: 6, // Swap-Count usage
quota: 10,
current_asset: "BAT-12345"
},
{
service_id: "svc-swap-network-kenya",
used: 0,
quota: 100000000, // Infinity quota
current_asset: null
}
]
Proposed Enhancement¶
Add a third service for electricity tracking:
serviceStates: [
{
service_id: "svc-battery-fleet-kenya-premium",
used: 6,
quota: 10,
current_asset: "BAT-12345"
},
{
service_id: "svc-electricity-fuel-kenya", // NEW
used: 344.5, // kWh consumed (decimal allowed)
quota: 400, // kWh limit
current_asset: null // No asset association
},
{
service_id: "svc-swap-network-kenya",
used: 0,
quota: 100000000,
current_asset: null
}
]
Implementation Requirements¶
1.1 Template Configuration¶
File: models/battery-swap/setup-data/bss-template-*.json
Add electricity service configuration:
{
"service_configurations": [
{
"service_id": "svc-battery-fleet-kenya-premium",
"quota": 10,
"tracks_asset": true
},
{
"service_id": "svc-electricity-fuel-kenya",
"quota": 400,
"tracks_asset": false,
"unit": "kWh",
"decimal_precision": 1
},
{
"service_id": "svc-swap-network-kenya",
"quota": 100000000,
"tracks_asset": false
}
]
}
1.2 Agent Function: calculateElectricityUsage()¶
Location: docs/models/bss/bss-agent-v2.ts (hypothetical - actual location TBD)
Purpose: Calculate net electricity delivered during swap.
Input:
interface ElectricityUsageInput {
incoming_battery_id: string;
incoming_kwh: number; // kWh read directly from battery BMS
outgoing_battery_id: string;
outgoing_kwh: number; // kWh read directly from battery BMS
}
Logic:
function calculateElectricityUsage(input: ElectricityUsageInput): number {
// Direct kWh values from battery scanner (no conversion needed)
const net_kwh = input.outgoing_kwh - input.incoming_kwh;
// Round to 1 decimal place
return Math.round(net_kwh * 10) / 10;
}
// Example:
// Input: { incoming_kwh: 4.8, outgoing_kwh: 30.4 }
// Output: 25.6 kWh
Output: number (kWh delivered, rounded to 1 decimal)
Note: Battery scanner reads kWh directly from BMS - no SOC% conversion required.
1.3 Integration with equipmentCheckout()¶
Enhancement: Modify existing equipmentCheckout() function to:
1. Accept incoming SOC% and outgoing SOC% as parameters
2. Call calculateElectricityUsage() to compute net kWh
3. Update svc-electricity-fuel-* quota in serviceStates[]
Updated Function Signature:
interface EquipmentCheckoutInput {
action: "EQUIPMENT_CHECKOUT";
replacement_equipment_id: string;
// NEW FIELDS
incoming_equipment_id?: string; // Optional for first visit
incoming_kwh?: number; // kWh read from battery scanner (optional for first visit)
outgoing_kwh: number; // kWh read from battery scanner (required)
}
Example Request:
{
"action": "EQUIPMENT_CHECKOUT",
"replacement_equipment_id": "BAT-67890",
"incoming_equipment_id": "BAT-12345",
"incoming_kwh": 4.8,
"outgoing_kwh": 30.4
}
Quota Check Logic (performed during checkout):
// 1. Calculate net electricity
const net_kwh = outgoing_kwh - (incoming_kwh || 0);
// 2. Get current electricity quota
const electricityService = serviceStates.find(s => s.service_id === 'svc-electricity-fuel-*');
const remaining_quota = electricityService.quota - electricityService.used;
// 3. Check if sufficient
if (remaining_quota < net_kwh) {
// Quota exhausted - require top-up
return { signals: ['QUOTA_EXHAUSTED'], metadata: { deficit_kwh: net_kwh - remaining_quota } };
} else {
// Quota sufficient - proceed
return { signals: ['QUOTA_AVAILABLE'], metadata: { net_kwh } };
}
Example Response (Quota Sufficient):
{
"signals": ["QUOTA_AVAILABLE", "EQUIPMENT_CHECKOUT_SUCCESS"],
"metadata": {
"outgoing_battery_id": "BAT-67890",
"incoming_battery_id": "BAT-12345",
"electricity_calculation": {
"incoming_kwh": 4.8,
"outgoing_kwh": 30.4,
"net_delivered_kwh": 25.6
},
"quota_check": {
"remaining_before": 55.5,
"net_required": 25.6,
"remaining_after": 29.9,
"status": "sufficient"
},
"quota_updates": [
{
"service_id": "svc-battery-fleet-kenya-premium",
"used_before": 6,
"used_after": 7,
"increment": 1
},
{
"service_id": "svc-electricity-fuel-kenya",
"used_before": 344.5,
"used_after": 370.1,
"increment": 25.6
}
]
},
"fsmInputs": [
{
"cycle": "service_cycle",
"input": "BATTERY_ISSUED"
}
]
}
Example Response (Quota Exhausted):
{
"signals": ["QUOTA_EXHAUSTED"],
"metadata": {
"outgoing_battery_id": "BAT-67890",
"incoming_battery_id": "BAT-12345",
"electricity_calculation": {
"incoming_kwh": 4.8,
"outgoing_kwh": 30.4,
"net_delivered_kwh": 25.6
},
"quota_check": {
"remaining_before": 10.0,
"net_required": 25.6,
"deficit_kwh": 15.6,
"status": "exhausted"
},
"topup_required": {
"amount_kwh": 15.6,
"estimated_cost_usd": 12.48 // Example: $0.80/kWh
}
},
"fsmInputs": [
{
"cycle": "service_cycle",
"input": "QUOTA_EXHAUSTED"
}
]
}
Enhancement 2: Unified Service Completion¶
Objective¶
Provide a single SERVICE_COMPLETE action that:
1. Publishes Service Event signal
2. Publishes Payment Event signal (if payment occurred)
3. Executes FSM transitions
4. Updates ServicePlan quotas
5. Generates transaction receipt
Current State¶
Service completion requires multiple sequential calls:
1. equipmentCheckin() - Check in returned battery
2. equipmentCheckout() - Check out new battery
3. processPayment() - Process payment (if needed)
4. reportAttendantActivity() - Report completion
Proposed Enhancement¶
Create a new completeService() action that orchestrates all completion steps.
2.1 Agent Function: completeService()¶
Purpose: Single action to complete entire swap transaction.
Input:
interface CompleteServiceInput {
action: "COMPLETE_SERVICE";
// Battery swap details
incoming_battery_id?: string; // null for first visit
incoming_kwh?: number; // kWh read from scanner (null for first visit)
outgoing_battery_id: string;
outgoing_kwh: number; // kWh read from scanner
// Payment details (if payment occurred)
payment_occurred: boolean;
payment_amount?: number;
payment_receipt_id?: string;
payment_method?: string;
// Attendant details
attendant_id: string;
attendant_station: string;
// Transaction metadata
transaction_timestamp: string;
correlation_id: string;
}
Logic Flow:
async function completeService(ctx: ExecutionContext, input: CompleteServiceInput): Promise<AgentResult> {
// Step 1: Validate all prerequisites
const validations = await validateServiceCompletion(ctx, input);
if (!validations.passed) {
return { signals: ['SERVICE_COMPLETION_FAILED'], metadata: validations };
}
// Step 2: Calculate quota updates
const quotaUpdates = calculateQuotaUpdates(input);
// Step 3: Create Service Event (for history persistence)
const serviceEvent = createServiceEvent(ctx, input, quotaUpdates);
// Step 4: Create Payment Event (if payment occurred, for history persistence)
const paymentEvent = input.payment_occurred
? createPaymentEvent(ctx, input)
: null;
// Step 5: Execute FSM transitions
const fsmInputs = [
{ cycle: 'service_cycle', input: 'BATTERY_ISSUED' }
];
if (input.payment_occurred) {
fsmInputs.push({ cycle: 'payment_cycle', input: 'PAYMENT_RECEIVED' });
}
// Step 6: Update ServicePlan
await updateServicePlan(ctx, {
quotaUpdates,
currentAsset: input.outgoing_battery_id
});
// Step 7: Persist events to ABS database (source of truth for history)
await persistServiceEvent(serviceEvent);
if (paymentEvent) {
await persistPaymentEvent(paymentEvent);
}
// Step 8: Publish events to MQTT (for Odoo integration if needed)
await publishEvent('SERVICE_COMPLETED', serviceEvent);
if (paymentEvent) {
await publishEvent('PAYMENT_RECEIVED', paymentEvent);
}
// Step 9: Generate receipt (combines service + payment events)
const receipt = generateReceipt(ctx, input, quotaUpdates);
return {
signals: ['SERVICE_COMPLETED_SUCCESS'],
metadata: {
transaction_id: generateTransactionId(),
quota_updates: quotaUpdates,
service_event: serviceEvent,
payment_event: paymentEvent,
receipt,
fsm_transitions: fsmInputs
},
fsmInputs
};
}
}
Example Request:
{
"action": "COMPLETE_SERVICE",
"incoming_battery_id": "BAT-12345",
"incoming_kwh": 4.8,
"outgoing_battery_id": "BAT-67890",
"outgoing_kwh": 30.4,
"payment_occurred": true,
"payment_amount": 15.00,
"payment_receipt_id": "PAY-78910",
"payment_method": "MOBILE_MONEY",
"attendant_id": "ATT-001",
"attendant_station": "STATION_XYZ",
"transaction_timestamp": "2025-01-15T10:30:00Z",
"correlation_id": "TXN-12345"
}
Example Response:
{
"signals": ["SERVICE_COMPLETED_SUCCESS"],
"metadata": {
"transaction_id": "TXN-12345",
"quota_updates": [
{
"service_id": "svc-battery-fleet-kenya-premium",
"used_before": 6,
"used_after": 7
},
{
"service_id": "svc-electricity-fuel-kenya",
"used_before": 344.5,
"used_after": 370.1
}
],
"receipt": {
"transaction_id": "TXN-12345",
"timestamp": "2025-01-15T10:30:00Z",
"customer_id": "CUST-001",
"batteries_swapped": {
"returned": "BAT-12345",
"issued": "BAT-67890"
},
"electricity_delivered_kwh": 25.6,
"payment": {
"amount": 15.00,
"receipt_id": "PAY-78910",
"method": "MOBILE_MONEY"
},
"quotas_remaining": {
"swap_count": "3 of 10",
"electricity_fuel": "29.9 kWh of 400 kWh"
}
},
"fsm_transitions": [
{ "cycle": "service_cycle", "input": "BATTERY_ISSUED" },
{ "cycle": "payment_cycle", "input": "PAYMENT_RECEIVED" }
]
},
"fsmInputs": [
{ "cycle": "service_cycle", "input": "BATTERY_ISSUED" },
{ "cycle": "payment_cycle", "input": "PAYMENT_RECEIVED" }
]
}
Enhancement 3: Enhanced Equipment Tracking¶
Objective¶
Include kWh data in battery identification responses.
Current State¶
identifyReturnedEquipment() and identifyOutgoingEquipment() return basic battery details but not energy data.
Proposed Enhancement¶
Integrate with battery scanner/ARM microservice to retrieve kWh data directly from BMS.
3.1 Battery Scanner Integration¶
Method: Direct kWh reading from battery BMS via scanner device.
Scanner Interface (hypothetical):
interface BatteryTelemetry {
asset_id: string;
kwh_remaining: number; // Direct kWh reading from BMS
voltage: number; // Optional
temperature_celsius: number; // Optional
timestamp: string;
}
ARM API Endpoint (for logging/verification):
GET /api/v1/assets/{asset_id}/telemetry/latest
Response:
{
"asset_id": "BAT-12345",
"telemetry": {
"kwh_remaining": 4.8,
"voltage": 51.2,
"temperature_celsius": 25,
"timestamp": "2025-01-15T10:15:00Z"
}
}
Note: Scanner reads kWh directly from battery - no conversion or estimation needed.
3.2 Agent Function Updates¶
Function: identifyReturnedEquipment()
Enhancement: After verifying battery ownership, read kWh from scanner:
async function identifyReturnedEquipment(ctx, input) {
// Existing logic: Verify ownership
const ownership = await verifyBatteryOwnership(ctx, input.equipment_id);
// NEW: Read kWh from battery scanner
const telemetry = await readBatteryTelemetry(input.equipment_id);
return {
signals: ['EQUIPMENT_BELONGS_TO_USER'],
metadata: {
equipment_id: input.equipment_id,
ownership_verified: true,
kwh_remaining: telemetry.kwh_remaining, // NEW - Direct kWh reading
read_timestamp: telemetry.timestamp, // NEW
battery_voltage: telemetry.voltage, // NEW (optional)
battery_temperature: telemetry.temperature_celsius // NEW (optional)
}
};
}
Updated Response:
{
"signals": ["EQUIPMENT_BELONGS_TO_USER"],
"metadata": {
"equipment_id": "BAT-12345",
"ownership_verified": true,
"kwh_remaining": 4.8,
"read_timestamp": "2025-01-15T10:15:00Z",
"battery_voltage": 51.2,
"battery_temperature": 25
}
}
Enhancement 4: Payment Event Publishing¶
Objective¶
Publish standardized payment events when top-up payments are received.
Current State¶
Payment confirmation is handled via Odoo callback but not explicitly published as an event.
Proposed Enhancement¶
Create a dedicated PAYMENT_RECEIVED event that is published to MQTT when payment is confirmed.
4.1 Event Structure¶
Event Type: PAYMENT_RECEIVED
Payload:
{
"event_type": "PAYMENT_RECEIVED",
"event_id": "EVT-PAY-78910",
"timestamp": "2025-01-15T10:25:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"customer_id": "CUST-001",
"payment_data": {
"amount": 15.00,
"currency": "USD",
"payment_method": "MOBILE_MONEY",
"odoo_receipt_id": "PAY-78910",
"service_description": "Battery Swap + Electricity Top-up",
"merchant_station": "STATION_XYZ",
"payment_timestamp": "2025-01-15T10:24:30Z"
},
"service_context": {
"transaction_id": "TXN-12345",
"attendant_id": "ATT-001",
"correlation_id": "att-payment-001"
},
"fsm_inputs": [
{
"cycle": "payment_cycle",
"input": "PAYMENT_RECEIVED"
}
]
}
MQTT Topic: event/abs/payment/plan/{plan_id}/payment_received
4.2 Integration Points¶
Trigger 1: Odoo payment confirmation callback
Odoo → ABS Platform → BSS Agent → Publish PAYMENT_RECEIVED event
Trigger 2: completeService() action (if payment occurred)
Attendant App → BSS Agent → completeService() → Publish PAYMENT_RECEIVED event
Testing Requirements¶
Unit Tests¶
- [ ]
calculateElectricityUsage()- Various kWh combinations (including first visit with incoming_kwh=0) - [ ]
completeService()- All workflow paths (with/without payment) - [ ]
identifyReturnedEquipment()- kWh data retrieval from scanner - [ ] Quota update logic - Electricity quota increments and exhaustion detection
Integration Tests¶
- [ ] End-to-end workflow: Customer scan → Battery swap → Service complete
- [ ] Payment flow: Quota exhaustion → Payment → Service resume
- [ ] First visit workflow: Skip battery return → Issue battery
- [ ] Error scenarios: Wrong battery, damaged battery, payment failure
Performance Tests¶
- [ ] MQTT message latency (< 500ms)
- [ ] Agent calculation time (< 1s)
- [ ] Database update time (< 2s)
- [ ] Receipt generation time (< 500ms)
Migration Plan¶
Phase 1: Backend Changes (Week 1)¶
- [ ] Add electricity service to templates
- [ ] Implement
calculateElectricityUsage() - [ ] Enhance
equipmentCheckout()with SOC tracking - [ ] Add ARM telemetry integration
Phase 2: Agent Enhancements (Week 1)¶
- [ ] Implement
completeService()action - [ ] Add payment event publishing
- [ ] Update FSM transition logic
- [ ] Implement receipt generation
Phase 3: Testing (Week 2)¶
- [ ] Unit tests for new functions
- [ ] Integration tests for workflows
- [ ] Performance testing
- [ ] UAT with attendants
Phase 4: Deployment (Week 2)¶
- [ ] Deploy to staging environment
- [ ] Smoke testing
- [ ] Deploy to production
- [ ] Monitor for issues
API Documentation Updates¶
New MQTT Topics¶
| Topic Pattern | Description | QoS | Retained |
|---|---|---|---|
call/uxi/attendant/plan/{plan_id}/complete_service |
Complete swap transaction | 0 | No |
rtrn/abs/attendant/plan/{plan_id}/complete_service_response |
Immediate acknowledgment | 0 | No |
event/abs/payment/plan/{plan_id}/payment_received |
Payment event broadcast | 0 | No |
event/abs/service/plan/{plan_id}/service_completed |
Service event broadcast | 0 | No |
Updated Agent Actions¶
| Action | Status | Changes |
|---|---|---|
IDENTIFY_CUSTOMER |
Unchanged | No changes |
IDENTIFY_RETURNED_EQUIPMENT |
Enhanced | + kWh data from scanner |
IDENTIFY_OUTGOING_EQUIPMENT |
Enhanced | + kWh data from scanner |
EQUIPMENT_CHECKOUT |
Enhanced | + kWh tracking, quota check, electricity quota update |
COMPLETE_SERVICE |
NEW | Orchestrates service completion |
Configuration Changes¶
Template Configuration¶
File: models/battery-swap/setup-data/bss-template-weekly-freedom-nairobi-v2.json
Add:
{
"service_configurations": [
{
"service_id": "svc-electricity-fuel-kenya",
"quota": 400,
"tracks_asset": false,
"unit": "kWh",
"decimal_precision": 1
}
]
}
Agent Parameters¶
File: models/battery-swap/setup-data/bss-agent-params-v2.json (hypothetical)
Add:
{
"electricity_tracking": {
"enabled": true,
"default_battery_capacity_kwh": 32,
"decimal_precision": 1,
"soc_telemetry_timeout_seconds": 10
},
"service_completion": {
"require_payment_confirmation": true,
"receipt_generation_enabled": true,
"event_publishing_enabled": true
}
}
Rollback Plan¶
If issues arise post-deployment:
- Disable Electricity Tracking:
- Set
electricity_tracking.enabled = falsein agent params -
System reverts to swap-count quota only
-
Disable Unified Completion:
- Remove
COMPLETE_SERVICEaction from available actions -
Revert to sequential completion steps
-
Revert Template Changes:
- Remove electricity service from templates
-
Service plans continue with existing quota structure
-
Database Rollback:
- Restore
serviceStates[]to pre-deployment state - Electricity usage data is lost (acceptable for pilot phase)
Appendix: Code Examples¶
Example: Electricity Quota Update¶
// Before
serviceStates: [
{ service_id: "svc-battery-fleet", used: 6, quota: 10 }
]
// After swap (25.6 kWh delivered)
serviceStates: [
{ service_id: "svc-battery-fleet", used: 7, quota: 10 },
{ service_id: "svc-electricity-fuel", used: 25.6, quota: 400 }
]
Example: First Visit (No Incoming Battery)¶
// Request
{
"action": "EQUIPMENT_CHECKOUT",
"replacement_equipment_id": "BAT-67890",
"incoming_equipment_id": null, // No incoming battery
"incoming_soc_percent": null,
"outgoing_soc_percent": 95
}
// Electricity calculation
incoming_kwh = 0 (no incoming battery)
outgoing_kwh = 30.4 (95% of 32 kWh)
net_delivered_kwh = 30.4 (full battery delivered)
Conclusion¶
These enhancements enable the unified attendant workflow while maintaining backward compatibility with existing systems. All changes are additive and can be rolled back if needed.
Next Steps: 1. Review and approve this specification 2. Begin Phase 1 implementation 3. Proceed with UXI mock-up development in parallel