Skip to main content

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 combining
  • OperatorRoundDetailsScreen.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)

  1. AssetType: Categories of assets (Pump, Motor, Boiler, etc.)
  2. FunctionalLocation: Plant hierarchy locations
  3. Asset: Physical equipment instances
  4. AttributeType: Definitions of measurable attributes
  5. AttributeOption: Options for dropdown-type attributes (added displayName and sortOrder)

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.kt
  • AssetDao.kt, AssetTypeDao.kt, FunctionalLocationDao.kt

Updated Entity:

  • AttributeOption.kt: Added displayName: String? and sortOrder: Int fields

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:

  1. Client → Server (Upload):

    • Collect dirty entities (syncDirty = true, deletedAt IS NULL)
    • Collect deleted entities (deletedAt IS NOT NULL)
    • Send in RequestSyncModel
  2. Server Processing:

    • Apply client upserts (merge into database)
    • Apply client deletes (hard delete from database)
    • Query changes since lastSyncAt
  3. Server → Client (Download):

    • Return changed entities (UpsertsSince)
    • Return deleted UUIDs (DeletesSince)
    • Return current server time
  4. 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

  1. Create Asset on client → sync → verify on server
  2. Create AttributeType on server → sync → verify on client
  3. Create AssetAttributeValue with dependencies → sync → verify foreign keys resolved

Offline Tests

  1. Capture readings while offline
  2. Verify readings stored locally
  3. Go online → sync
  4. Verify readings appear on server
  5. 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:

  1. Updated Asset Readings with route stop attribute details
  2. Updated Operator Rounds with structured checklist feature

Breaking Changes

Android

  • AttributeOption now requires displayName and sortOrder fields
  • 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)

  1. Finalize Approach A vs B decision
  2. Implement RouteStopAttributeType if Approach A chosen
  3. Update StopUpdateViewModel to load configured attributes

Medium Term

  1. Add pagination to sync for large datasets
  2. Implement proper database migrations for Android
  3. Add unit tests for sync logic

Long Term

  1. Phase 2: Add threshold-based alerts on readings
  2. Phase 2: Add observation triage workflows
  3. 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]

See Also