{{ theme.skipToContentLabel || 'Skip to content' }}

Remove Flow Meter String Dependency

Date: 2026-01-15
Phase: Frontend Refactoring Phase 2
Related: 20260115000000_add_asset_id_to_flow_meters.sql

Overview

This phase removes the frontend's dependency on the legacy flow_meter string field and switches to using data_assets.display_id for all UI displays. The flow_meter column remains in the database for backward compatibility but is no longer used in the UI.

Changes Made

1. Type Definitions Enhanced

File: src/features/flow-meter/types.ts

typescript
export type FlowMeterRecord = {
  site: string;
  flowMeter: string;           // DEPRECATED: Legacy field for backward compatibility
  assetId?: string;            // Foreign key to data_assets
  assetDisplayId?: string;     // NEW: Display ID from data_assets table
  litresDispensed: number;
  dateTimeDispensed: Date;
  scrapedAt: Date;
};

export type DustLocRefill = {
  // ... other fields
  flowMeter?: string;          // DEPRECATED: Legacy field
  assetId?: string;            // Foreign key to data_assets
  assetDisplayId?: string;     // NEW: Display ID from data_assets
  // ... other fields
};

2. Database Queries with JOIN

File: src/features/flow-meter/services/databaseService.ts

fetchAllFlowMeterData()

typescript
// Before: Simple SELECT *
const { data } = await supabase
  .from("data_flow_meters")
  .select("*");

// After: JOIN with data_assets to get display_id
const { data } = await supabase
  .from("data_flow_meters")
  .select(`
    *,
    asset:data_assets!data_flow_meters_asset_id_fkey(display_id)
  `);

// Map to include assetDisplayId
return data.map((row) => ({
  ...mapFlowMeterRow(row),
  assetDisplayId: row.asset?.display_id ?? row.flow_meter,
}));

fetchAllRefills()

typescript
// Similar JOIN pattern for refills
const { data } = await supabase
  .from("ops_dustloc_refills")
  .select(`
    *,
    asset:data_assets!ops_dustloc_refills_asset_id_fkey(display_id)
  `);

Smart Fallback: If asset.display_id is not available (old records), falls back to flow_meter string.

3. UI Display Updates

All UI components updated to use assetDisplayId with fallback:

typescript
// Before
{record.flowMeter}

// After
{record.assetDisplayId || record.flowMeter}

Updated Components:

  • ✅ Flow Meter page: Event table displays
  • ✅ SiteSummaryCard: Flow meter dropdowns
  • ✅ AddRefillModal: Available flow meters list
  • ✅ EditRefillModal: Available flow meters list
  • ✅ All export/print views

4. Filtering Logic Updated

Flow Meter Selection:

typescript
// Before: Filter by flowMeter string
records.filter((r) => r.flowMeter === selectedFlowMeter)

// After: Filter by assetDisplayId (with fallback)
records.filter((r) => (r.assetDisplayId || r.flowMeter) === selectedFlowMeter)

Unique Values:

typescript
// Before: Extract flowMeter strings
Array.from(new Set(records.map((r) => r.flowMeter)))

// After: Extract assetDisplayId (with fallback)
Array.from(new Set(
  records.map((r) => r.assetDisplayId || r.flowMeter).filter(Boolean)
))

Data Flow

Display Flow (Read)

Database Query

JOIN with data_assets

Get display_id from asset

Map to assetDisplayId

UI displays assetDisplayId
   (with flow_meter fallback)

Input Flow (Write)

User selects from asset dropdown

Get asset_id (UUID)

Save to database:
  - asset_id (primary reference)
  - flow_meter (legacy fallback)

Next read will show display_id

Backward Compatibility Strategy

The system maintains full backward compatibility through a layered approach:

Layer 1: Database

  • ✅ Both flow_meter and asset_id columns exist
  • ✅ Neither is required (both nullable)
  • ✅ Legacy records with only flow_meter still work

Layer 2: Data Layer

  • ✅ JOIN attempts to get display_id from data_assets
  • ✅ If JOIN returns null, uses flow_meter as fallback
  • ✅ Mapping functions handle both old and new records

Layer 3: UI Layer

  • ✅ Display: assetDisplayId || flowMeter
  • ✅ Filtering: Works with either field
  • ✅ Selection: Prioritizes assets, falls back to strings

Data States Handled

Database StateQuery ResultUI Display
asset_id + asset.display_idassetDisplayId: "FM-01""FM-01" ✅
asset_id only (asset deleted)assetDisplayId: nullUses flowMeter ⚠️
flow_meter only (legacy)assetDisplayId: nullUses flowMeter
Both asset_id + flow_meterassetDisplayId: "FM-01""FM-01" (prioritizes asset) ✅
Neither (shouldn't happen)Both null"-" or "Unknown" ⚠️

Benefits

1. Consistent Display Names

  • All flow meters show the same name across the system
  • Names managed centrally in data_assets
  • Easy to update display names globally

2. Rich Metadata Available

  • Can access operational status
  • Can show last contact time
  • Can filter by asset type
  • Can show descriptions

3. Future-Proof

  • Easily add asset-based features
  • Maintenance scheduling integration
  • Status-based filtering
  • Asset lifecycle tracking

4. Data Integrity

  • Foreign keys prevent invalid references
  • Cascading deletes handled properly
  • Referential integrity enforced

5. Better UX

  • Consistent naming
  • Can show additional context (status, description)
  • Color-coded status indicators (future)

Testing

Automated Tests

  • ✅ TypeScript compilation passes
  • ✅ Vite build succeeds
  • ✅ No type errors

Manual Testing Checklist

Read Operations:

  • [ ] Flow meter page displays asset display IDs
  • [ ] Old records (flow_meter only) still display correctly
  • [ ] New records (asset_id) display asset display_id
  • [ ] Flow meter dropdown shows correct names
  • [ ] Filtering by flow meter works
  • [ ] Site summary cards show correct data

Write Operations:

  • [ ] Add refill modal shows asset dropdown
  • [ ] Can select asset and submit
  • [ ] New refills save with asset_id
  • [ ] Edit refill modal works
  • [ ] Calibration modal works

Edge Cases:

  • [ ] Records with deleted assets (orphaned) still display
  • [ ] Mixed data (some with assets, some without) displays correctly
  • [ ] Empty/null handling works
  • [ ] Filtering with mixed data works

Migration Impact

Database

  • ✅ No schema changes (already migrated)
  • ✅ Existing data unaffected
  • ✅ JOINs are read-only

Frontend

  • ✅ All components updated
  • ✅ Backward compatibility maintained
  • ✅ Build successful
  • ✅ No breaking changes

User Impact

  • ✨ Users see asset display names instead of arbitrary strings
  • ✨ Consistent naming across all views
  • ✨ No training required (UI looks the same)

Performance Considerations

JOIN Impact

  • Query Complexity: Increased (now includes JOIN)
  • Performance: Minimal impact (indexed foreign key)
  • Network: Same number of requests
  • Client Processing: Same (mapping happens client-side)

Optimization Opportunities

  1. Caching: Cache asset display names
  2. Batch Loading: Load all assets once, map client-side
  3. Materialized View: Pre-join common queries
  4. Index: Ensure asset_id columns are indexed (✅ done)

Next Steps

Immediate (Optional)

  1. Update other features using flow meters:

    • [ ] Dashboard calibration compliance
    • [ ] Weekly reports
    • [ ] Data freshness cards
  2. Add visual enhancements:

    • [ ] Show asset status icon next to name
    • [ ] Color-code by operational status
    • [ ] Add tooltips with asset details

Medium-term

  1. Deprecation Notice: Add UI warnings for records without assets
  2. Migration Tool: Admin UI to map legacy records to assets
  3. Validation: Require asset selection for new records

Long-term (Phase 3)

  1. Remove Legacy Field: Once all records migrated
  2. Make Asset Required: Change asset_id to NOT NULL
  3. Simplify Code: Remove fallback logic
  4. Drop Column: Remove flow_meter string column

Rollback Plan

If issues arise:

Quick Rollback (Frontend Only)

typescript
// Revert to using flowMeter instead of assetDisplayId
{record.flowMeter || record.assetDisplayId}

Full Rollback

  1. Restore previous component files from git
  2. Remove JOINs from database queries
  3. System continues to work with legacy strings

No Database Rollback Needed

  • Database schema unchanged
  • All queries still return flow_meter field
  • Backward compatible

Modified files:

  • src/features/flow-meter/types.ts - Added assetDisplayId field
  • src/features/flow-meter/services/databaseService.ts - Added JOINs
  • src/app/(admin)/(pages)/flow-meter/index.tsx - Updated displays
  • src/features/flow-meter/components/SiteSummaryCard.tsx - Updated filtering
  • src/features/flow-meter/components/AddRefillModal.tsx - Updated selector
  • src/features/flow-meter/components/EditRefillModal.tsx - Updated selector

Summary

This phase successfully migrates the UI to use data_assets.display_id while maintaining full backward compatibility with legacy data. The system now:

✅ Displays asset display IDs from centralized source
✅ Maintains backward compatibility with old records
✅ Provides consistent naming across all views
✅ Enables future asset-based features
✅ Preserves all existing functionality
✅ Passes all compilation checks

Status: ✅ Complete and Ready for Testing