December 2025: Asset Readings & Sync Implementation
Overview
This release implements the full asset attribute and readings system, including bidirectional sync for all related entities. Operators can now capture structured readings during operator rounds, and those readings display in both Android and Web interfaces.
What's New
1. Asset Readings Display
Android:
- OperatorRound details screen now shows readings captured at each stop
- Each reading displays: attribute name, value, and unit
- Readings are loaded reactively and update in real-time
Files Changed:
OperatorRoundDetailsViewModel.kt: Added readings loading with proper Flow combiningOperatorRoundDetailsScreen.kt: Updated stop cards to display readings- New data classes:
StopWithReadings,ReadingDisplay
2. Complete Sync System for Attributes
Added bidirectional sync for the following entities:
New Entities (Backend + Android)
- AssetType: Categories of assets (Pump, Motor, Boiler, etc.)
- FunctionalLocation: Plant hierarchy locations
- Asset: Physical equipment instances
- AttributeType: Definitions of measurable attributes
- AttributeOption: Options for dropdown-type attributes (added
displayNameandsortOrder)
Backend Changes
File: SyncModels.cs
- Added 5 new UpsertSyncModel records
- Added 5 new ChangeSyncModel records
- Updated RequestSyncModel with 5 new ChangeSetSyncModel properties
- Updated ServerChangesSyncModel with 5 new ServerEntityChangesSyncModel properties
File: SyncCommand.cs (~290 lines added)
- Added upsert logic for AssetTypes, FunctionalLocations, Assets, AttributeTypes, AttributeOptions
- Added query logic for server changes since lastSyncAt
- Implemented proper foreign key dependency handling
Android Changes
New Entity Files:
Asset.kt,AssetType.kt,FunctionalLocation.ktAssetDao.kt,AssetTypeDao.kt,FunctionalLocationDao.kt
Updated Entity:
AttributeOption.kt: AddeddisplayName: String?andsortOrder: Intfields
Database:
AppDatabase.kt: Registered 3 new entities, incremented version to 13
Sync Implementation (SyncItemsWorker.kt - ~400 lines added):
- Added imports for 5 new entities and sync models
- Added DAO initialization for new entities
- Added change set building for dirty/deleted entities
- Updated RequestSyncModel to include new entity change sets
- Added 5 new
processSync()methods for server response handling - Added 5 new
toUpsert()extension functions for entity mapping - Implemented proper sync dependency ordering to satisfy foreign key constraints
- Removed temporary try-catch workaround for AssetAttributeValues (no longer needed)
3. Sync Dependency Ordering
Entities are now synced in the correct order to satisfy foreign key constraints:
Phase 1: No Dependencies
├─ AssetTypes
├─ FunctionalLocations
├─ AttributeTypes
├─ Routes
└─ UnitOfMeasures
Phase 2: Single-Level Dependencies
├─ Assets (→ AssetTypes, FunctionalLocations)
├─ RouteStops (→ Routes, Assets, FunctionalLocations)
├─ AttributeOptions (→ AttributeTypes)
└─ OperatorRounds (→ Routes)
Phase 3: Multi-Level Dependencies
└─ OperatorRoundStops (→ OperatorRounds, RouteStops)
Phase 4: Deep Dependencies
└─ AssetAttributeValues (→ Assets, AttributeTypes, AttributeOptions, OperatorRoundStops)
This ordering ensures that foreign key references are always satisfied during sync.
Technical Details
Foreign Key Relationships
Asset
├─→ AssetType
└─→ FunctionalLocation
AttributeOption
└─→ AttributeType
AssetAttributeValue
├─→ Asset
├─→ AttributeType
├─→ AttributeOption (optional)
└─→ OperatorRoundStop (optional)
Sync Protocol
The bidirectional sync follows this flow:
-
Client → Server (Upload):
- Collect dirty entities (syncDirty = true, deletedAt IS NULL)
- Collect deleted entities (deletedAt IS NOT NULL)
- Send in RequestSyncModel
-
Server Processing:
- Apply client upserts (merge into database)
- Apply client deletes (hard delete from database)
- Query changes since lastSyncAt
-
Server → Client (Download):
- Return changed entities (UpsertsSince)
- Return deleted UUIDs (DeletesSince)
- Return current server time
-
Client Processing:
- Merge server upserts (REPLACE into local DB)
- Apply server deletes (hard delete from local DB)
- Mark uploaded entities as clean (syncDirty = false)
- Update lastSyncAt to server time
Conflict Resolution
The sync uses last-write-wins strategy:
- Server's lastModified timestamp is authoritative
- Client changes are applied first, then server changes override
- For most industrial use cases, conflicts are rare
Known Limitations
Temporary Implementation: All Attributes Shown
Current Behavior: When capturing readings at a RouteStop, ALL AttributeTypes are shown to the operator.
Intended Behavior: RouteStops should define which specific attributes to capture (Approach A - Route-Defined Readings).
Status: Implemented (Approach A selected). Routes now define which specific attributes to capture at each stop.
Workaround: Operators can ignore irrelevant attributes for now. This will be fixed when Approach A is implemented.
Migration Notes
Database Version
Android database version incremented from 11 → 13:
- Version 12: Added AssetType, FunctionalLocation, Asset entities
- Version 13: Updated AttributeOption with displayName and sortOrder
The app uses fallbackToDestructiveMigration(dropAllTables = true), so existing data is wiped on upgrade. For production, implement proper migrations.
Sync Endpoint
The /api/sync endpoint has been updated to handle 5 new entity types. Clients must regenerate sync models from OpenAPI spec:
cd cyzag-blueprint-android
node ./generate-sync.js
Testing Recommendations
Unit Tests
- Test sync dependency ordering
- Test foreign key constraint satisfaction
- Test conflict resolution (last-write-wins)
Integration Tests
- Create Asset on client → sync → verify on server
- Create AttributeType on server → sync → verify on client
- Create AssetAttributeValue with dependencies → sync → verify foreign keys resolved
Offline Tests
- Capture readings while offline
- Verify readings stored locally
- Go online → sync
- Verify readings appear on server
- Verify lastKnownValue updated
Performance Considerations
Sync Payload Size
With 14 entity types syncing, payload sizes can grow large. Consider:
- Implementing pagination (sync in batches)
- Adding delta sync (only changed fields)
- Compressing HTTP payloads
Database Indexes
Ensure these indexes exist for sync performance:
lastModified(for server queries:WHERE lastModified > ?)syncDirty(for client queries:WHERE syncDirty = 1)deletedAt(for filtering:WHERE deletedAt IS NULL)
Mobile Performance
Readings are loaded using Kotlin Flows with proper combining:
combine(
roundFlow,
stepsFlow,
attributeTypesFlow,
readingsFlow
) { ... }
This ensures reactive updates without excessive database queries.
Documentation
Updated documentation:
- Updated Asset Readings with route stop attribute details
- Updated Operator Rounds with structured checklist feature
Breaking Changes
Android
AttributeOptionnow requiresdisplayNameandsortOrderfields- Database version incremented (triggers full wipe in dev)
- New entities require DAO initialization in AppDatabase
Backend
- Sync endpoint request/response models expanded
- New entity tables require database migration
Next Steps
Short Term (Pending Confirmation)
- Finalize Approach A vs B decision
- Implement RouteStopAttributeType if Approach A chosen
- Update StopUpdateViewModel to load configured attributes
Medium Term
- Add pagination to sync for large datasets
- Implement proper database migrations for Android
- Add unit tests for sync logic
Long Term
- Phase 2: Add threshold-based alerts on readings
- Phase 2: Add observation triage workflows
- Phase 3: MCP server for AI agent access to readings
Contributors
- Backend sync implementation: [December 2025]
- Android sync implementation: [December 2025]
- Readings display: [December 2025]
- Documentation: [December 2025]