Insights Domain
The Insights domain (insights_v1) is a bounded context responsible for business intelligence, analytics, and reporting. It manages insight generation, KPI (key performance indicator) calculations, dashboard state, and data export functionality. This domain synthesizes data from across the system to provide actionable intelligence about estate performance, asset utilization, and operational metrics.
Overview
Section titled “Overview”The Insights domain sits at the convergence of data analysis and reporting infrastructure. It provides:
- Insight Generation: Creation of business intelligence reports with typed data models
- KPI Calculation: Computation of performance metrics with target tracking and trend analysis
- Insight Lifecycle: Status management (active → acknowledged → resolved/dismissed) with severity-based routing
- Data Exports: Multi-format exports (CSV, GeoJSON, PDF) with cross-domain data enrichment
- Dashboard Reporting: Estate summaries, drawing exports, media/voice note archives
Key Aggregates and Metrics
Section titled “Key Aggregates and Metrics”InsightAggregate
Section titled “InsightAggregate”The root aggregate of this domain, representing a single business intelligence insight with complete lifecycle state.
Identity:
InsightId: Unique identifier for the insightAggregateId: Framework ID for event sourcing
Core Attributes:
final InsightId insightId; // Unique insight IDfinal InsightName title; // Human-readable titlefinal InsightDescription description; // Detailed descriptionfinal InsightType insightType; // Type: performance, utilization, cost, compliance, etc.final InsightSeverity severity; // Critical, warning, info, or successfinal InsightStatus status; // active, acknowledged, resolved, or dismissedTemporal Data:
final DateTime generatedAt; // When the insight was createdfinal DateTime updatedAt; // Last modification timestampfinal DateTime? expiresAt; // Optional expiration time (null = never)final UserId generatedBy; // User/system that generated the insightSource Tracking:
final String? sourceEntityId; // ID of related entity (asset, site, etc.)final String? sourceEntityType; // Type of source (asset, site, estate, etc.)final List<DomainText> tags; // Categorization tagsfinal List<KPIId> relatedKPIs; // Related KPI calculationsTyped Data:
final InsightData insightData; // Type-safe insight data (performance, utilization, etc.)final Map<KPIId, KPIValue> _kpiValues; // Calculated KPI valuesKey Methods:
isActive,isAcknowledged,isResolved,isDismissed: Status checksisExpired: Check if insight has surpassed expirationrequiresImmediateAttention: True if critical and activecanBeResolved(),canBeDismissed(): Status transition validationisStale: True if older than 30 days and still activepriorityScore: Calculate urgency (0-200+) based on severity, age, and expirytypedData<T>(): Access typed insight data via pattern matching
KPIValue (Value Object)
Section titled “KPIValue (Value Object)”Represents a calculated key performance indicator with target tracking and trend analysis.
Attributes:
final KPIId kpiId; // Unique KPI IDfinal KPIName name; // Display namefinal KPIDescription description; // Explanationfinal KPIType kpiType; // efficiency, performance, cost, carbonEmissions, etc.final double value; // Current calculated valuefinal String unit; // Unit of measurement (%, kW, kg CO2, etc.)final double? targetValue; // Target/benchmark (null if no target)final double? previousValue; // Previous period value for trendfinal Trend trend; // up, down, or stablefinal DateTime calculatedAt; // Calculation timestampfinal String? entityId; // Optional: related entityfinal String? entityType; // Optional: entity typefinal String? timeframe; // daily, weekly, monthly, quarterly, yearlyfinal UserId calculatedBy; // User/system that calculatedfinal KPIMetadata typedMetadata; // Type-safe metadataKey Methods:
hasTarget: Whether a target value existsisMeetingTarget: Compare actual vs. target (direction-aware for cost vs. efficiency)targetVariance: Percentage difference from targetchangeFromPrevious: Absolute change from previous periodpercentageChangeFromPrevious: Percentage change from previousisImproving: True if trend is favorable for the KPI type
Insight Data Types
Section titled “Insight Data Types”The domain supports multiple typed insight data models accessed via insightData:
GenericInsightData
- Fallback container with arbitrary properties map
- Used when specific type is unknown
PerformanceInsightData
efficiencyScore: 0-100utilization: 0-100 percentmetricsSnapshot: Key metrics at time of generation
UtilizationInsightData
utilizationPercentage: Current utilizationcapacityUsed: Absolute valuecapacityAvailable: Total availabletrend: Over time trend
CostInsightData
estimatedCost: Projected costcostUnit: Currency or unitcostBreakdown: By category/departmentsavingsOpportunity: Potential reduction
ComplianceInsightData
complianceScore: 0-100 percentviolations: Count of issuesrequiredActions: List of remediation tasks
ExportInsightData
format: CSV, GeoJSON, PNG, PDFcontent: Export datasourceType: drawing, site, estatesourceId: Reference to sourceassetCount: How many assetsincludesImage: Whether drawing image includedassets: Structured asset datageneratedAt: Export creation time
Domain Events
Section titled “Domain Events”The domain produces events capturing all state changes and calculations:
KPICalculatedEvent
Section titled “KPICalculatedEvent”Fired when a key performance indicator is computed for an insight or entity.
KPICalculatedEvent( kpiId: KPIId.fromString('kpi-hvac-efficiency'), name: KPIName.fromString('HVAC Efficiency'), description: KPIDescription.fromString('Overall HVAC system efficiency'), kpiType: KPIType.efficiency, value: 87.5, unit: '%', targetValue: 90.0, previousValue: 85.0, trend: Trend.up, calculatedAt: DateTime.now(), entityId: 'asset-123', entityType: 'hvac_system', timeframe: 'monthly', calculatedBy: UserId.fromString('system'), metadata: {'data_quality': 'high', 'sample_size': 1000},)When Fired:
- KPI calculation services compute new metrics
- Periodic analytics jobs produce trend data
- On-demand KPI requests are executed
InsightGeneratedEvent
Section titled “InsightGeneratedEvent”Fired when a new business insight is created.
InsightGeneratedEvent( insightId: InsightId.fromString('insight-001'), title: InsightName.fromString('HVAC Efficiency Below Target'), description: InsightDescription.fromString('Building A HVAC running at 82%, target is 90%'), insightType: InsightType.performance, severity: InsightSeverity.warning, status: InsightStatus.active, sourceEntityId: 'hvac-system-1', sourceEntityType: 'hvac_system', generatedAt: DateTime.now(), expiresAt: DateTime.now().add(Duration(days: 30)), generatedBy: UserId.fromString('analytics-engine'), tags: [ DomainText.fromString('hvac'), DomainText.fromString('performance'), ], insightData: PerformanceInsightData( efficiencyScore: 82, utilization: 95, metricsSnapshot: {...}, ), relatedKPIs: [KPIId.fromString('kpi-hvac-efficiency')],)When Fired:
- Analytics services detect conditions requiring attention
- Thresholds are breached (efficiency, cost, compliance)
- Automated reporting generates periodic summaries
InsightStatusChangedEvent
Section titled “InsightStatusChangedEvent”Fired when an insight’s status transitions.
InsightStatusChangedEvent( insightId: InsightId.fromString('insight-001'), previousStatus: InsightStatus.active, newStatus: InsightStatus.acknowledged, changedBy: UserId.fromString('user-42'), changedAt: DateTime.now(), reason: 'Maintenance scheduled for next week',)Valid Transitions:
active → acknowledged → resolved ↓ dismissed (unless critical)
active → dismissed (except critical)EstateSummaryGeneratedEvent
Section titled “EstateSummaryGeneratedEvent”Fired when a printable estate summary is generated.
EstateSummaryGeneratedEvent( summaryId: EstateSummaryId.fromString('summary-estate-001'), summaryType: 'comprehensive', outputFormat: 'pdf', estateId: EstateId.fromString('estate-nyc'), organizationId: OrganizationId.fromString('org-123'), requestedBy: UserId.fromString('user-42'), generatedAt: DateTime.now(), summaryContent: ''' Estate Summary Report ===================== Estate: NYC Main Building Generated: 2024-12-15 ... ''', summaryMetrics: { 'userCount': 12, 'assetCount': 145, 'siteCount': 3, 'generationTimeMs': 2500, },)DrawingExportGeneratedEvent
Section titled “DrawingExportGeneratedEvent”Fired when a floor plan drawing with associated assets is exported.
DrawingExportGeneratedEvent( exportId: InsightId.fromString('export-drawing-001'), format: ExportFormat.csv, content: ''' ID,Name,Type,Status,Location asset-1,HVAC Unit,hvac_ahu,active,Room 201 ... ''', estateId: EstateId.fromString('estate-nyc'), siteId: SiteId.fromString('site-main'), drawingFeatureKey: GeoFeatureKey.fromString('floor_3_plan'), assetCount: 42, includesImage: true, assets: [ExportedAsset(...), ...], generatedBy: UserId.fromString('user-42'), generatedAt: DateTime.now(),)Supported Export Formats:
csv: Detailed or summary mode with grouping optionsgeojson: Geographic feature collection for GIS toolspng,pdf: Binary drawing images (server-side generation)
VoiceNoteArchiveGeneratedEvent
Section titled “VoiceNoteArchiveGeneratedEvent”Fired when voice note recordings are collected and archived.
VoiceNoteArchiveGeneratedEvent( archiveId: InsightId.fromString('archive-voices-001'), estateId: EstateId.fromString('estate-nyc'), siteId: SiteId.fromString('site-main'), // optional filter grouping: VoiceNoteExportGrouping.byFloor, voiceNoteCount: 87, totalSizeBytes: 4523000, includesTranscriptions: true, voiceNotes: [ExportedVoiceNote(...), ...], generatedBy: UserId.fromString('user-42'), generatedAt: DateTime.now(),)MediaArchiveGeneratedEvent
Section titled “MediaArchiveGeneratedEvent”Fired when multi-media files (photos, videos, voice notes) are collected.
MediaArchiveGeneratedEvent( archiveId: InsightId.fromString('archive-media-001'), estateId: EstateId.fromString('estate-nyc'), siteId: null, // all sites includedContent: { MediaArchiveContentType.photos, MediaArchiveContentType.voiceNotes, }, grouping: MediaArchiveGrouping.byFloorRoom, fileCountsByType: { MediaArchiveContentType.photos: 234, MediaArchiveContentType.voiceNotes: 87, MediaArchiveContentType.documents: 12, }, totalFileCount: 333, totalSizeBytes: 52348000, // ~50 MB includesTranscriptions: true, generatedBy: UserId.fromString('user-42'), generatedAt: DateTime.now(),)Dashboard and Reporting Concepts
Section titled “Dashboard and Reporting Concepts”Insight Lifecycle and Status
Section titled “Insight Lifecycle and Status”Insights progress through a lifecycle managing attention and resolution:
Status Sequence:
active (newly created or unaddressed) ↓acknowledged (someone is aware and may be handling) ↓resolved (addressed and closed)Alternatively:
active ↓dismissed (not addressing; allowed only for non-critical insights)Business Rules:
- Critical insights cannot be dismissed directly; must be resolved
- Dismissed insights can be re-opened to active
- Expiration applies independently (insights auto-expire if
expiresAtis reached)
Severity and Priority
Section titled “Severity and Priority”Severity Levels:
critical: Requires immediate action (facility-impacting issues)warning: Should be addressed soon (performance degradation)info: Informational (performance improvements, general data)success: Positive outcome or achievement
Priority Scoring Algorithm:
base_score = 0
// Severity multiplierif critical: base_score += 100if warning: base_score += 50if info: base_score += 10if success: base_score += 5
// Age penalty (older = lower priority)base_score -= age_in_days
// Expiry urgency (expires soon = higher priority)if expiry_days_remaining <= 7: base_score += 20
result = max(base_score, 0)Insight Freshness
Section titled “Insight Freshness”Insights track staleness to identify those needing review:
- Fresh: Generated recently (less than 7 days)
- Aging: 7-30 days old; actionable but attention needed
- Stale: 30+ days old while still active; should review for closure
Export Concepts
Section titled “Export Concepts”CSV Exports
Section titled “CSV Exports”Detailed Mode:
- One row per asset
- All custom fields as columns
- Comments, voice note transcriptions
- Comprehensive data for analysis
Summary Mode:
- Aggregated counts by grouping (listing, category, status)
- High-level overview
- For dashboards and reporting
GeoJSON Exports
Section titled “GeoJSON Exports”- Asset positions as Point features
- Properties include ID, name, asset type, listing, category
- Compatible with GIS tools and mapping libraries
- Only includes assets with coordinates
Media Archives
Section titled “Media Archives”Collects related media into organized ZIP structures:
Content Types:
photos: Image attachments from assetsvideos: Video attachmentsvoiceNotes: Audio recordings with optional transcriptionsdocuments: Files and PDFs
Grouping Options:
byAsset: Folder per assetbyFloorRoom: Organized by floor/room hierarchybySiteFloor: Organized by site and floorflat: All files in root
Operations and Directives
Section titled “Operations and Directives”Insight Lifecycle
Section titled “Insight Lifecycle”GenerateInsightDirective Creates a new insight with full business intelligence data.
GenerateInsightPayload( insightId: InsightId.generate(), title: InsightName.fromString('Energy Efficiency Alert'), description: InsightDescription.fromString('Building exceeding energy baseline'), insightType: InsightType.performance, severity: InsightSeverity.warning, sourceEntityId: 'bldg-a', sourceEntityType: 'building', expiresAt: DateTime.now().add(Duration(days: 30)), generatedBy: UserId.fromString('analytics-service'), tags: [ DomainText.fromString('energy'), DomainText.fromString('efficiency'), ], data: { 'efficiencyScore': 75, 'baselineValue': 85, 'variance': -10, }, relatedKPIs: [KPIId.fromString('kpi-energy-efficiency')],)UpdateInsightStatusDirective Transition insight status (active → acknowledged → resolved/dismissed).
UpdateInsightStatusPayload( insightId: InsightId.fromString('insight-001'), previousStatus: InsightStatus.active, newStatus: InsightStatus.acknowledged, changedBy: UserId.fromString('user-42'), reason: 'Maintenance request submitted',)KPI Calculation
Section titled “KPI Calculation”CalculateKPIDirective Compute a performance metric and associate with insights.
CalculateKPIPayload( kpiId: KPIId.generate(), name: KPIName.fromString('HVAC Efficiency'), description: KPIDescription.fromString('System-wide efficiency metric'), kpiType: KPIType.efficiency, value: 87.5, unit: '%', targetValue: 90.0, previousValue: 85.2, trend: Trend.up, relatedEntityId: 'hvac-system-1', entityType: 'hvac_system', timeframe: 'monthly', calculatedBy: UserId.fromString('system'), metadata: { 'methodology': 'weighted_average', 'sample_count': 1000, 'confidence': 0.95, },)Drawing Exports
Section titled “Drawing Exports”GenerateDrawingExportDirective Export assets from a specific drawing with cross-domain data enrichment.
GenerateDrawingExportPayload( exportId: InsightId.generate(), estateId: EstateId.fromString('estate-nyc'), siteId: SiteId.fromString('site-main'), drawingFeatureKey: GeoFeatureKey.fromString('floor_3_plan'), format: ExportFormat.csv, csvMode: CsvExportMode.detailed, summaryGrouping: CsvSummaryGrouping.byListing, includeAssets: true, includeImage: true, generatedBy: UserId.fromString('user-42'), content: '', // populated in plan step assets: [], // populated in plan step)Cross-Domain Data Resolution (in plan step):
- Queries all assets in estate workspace
- Filters to assets on the specified drawing
- Resolves attachment metadata from catalogue workspace
- Collects custom field definitions and display names
- Resolves taxonomy information (categories, listing names)
- Collects voice note transcriptions from asset comments
- Generates export content with fully resolved field names
Estate Summaries
Section titled “Estate Summaries”GenerateEstateSummaryDirective Create a comprehensive estate report with selected metrics.
GenerateEstateSummaryPayload( summaryId: EstateSummaryId.generate(), summaryType: 'comprehensive', outputFormat: 'pdf', estateId: EstateId.fromString('estate-nyc'), requestedBy: UserId.fromString('user-42'), includeAssetCounts: true, includeUserCounts: true, includeSiteBreakdown: true, includeGrowthMetrics: true, includeCollaborationStats: true, fromDate: DateTime.now().subtract(Duration(days: 90)), toDate: DateTime.now(), filterTags: ['active', 'priority'], formatOptions: {'pageSize': 'A4', 'orientation': 'portrait'}, metadata: {'reportVersion': '1.0'},)Included Sections:
- Estate and organization identification
- User and asset counts
- Site breakdown and hierarchy
- Asset categorization by type/status
- Collaboration metrics (comments, permissions)
- Growth trends over timeframe
- Custom field aggregations
Voice Note Archives
Section titled “Voice Note Archives”GenerateVoiceNoteArchiveDirective Collect voice notes and organize into downloadable archive.
GenerateVoiceNoteArchivePayload( archiveId: InsightId.generate(), estateId: EstateId.fromString('estate-nyc'), siteId: SiteId.fromString('site-main'), // optional grouping: VoiceNoteExportGrouping.byFloor, includeTranscriptions: true, includeManifestCsv: true, generatedBy: UserId.fromString('user-42'), voiceNotes: [], // populated in plan step)Plan Step Processing:
- Queries all assets (or filtered by site)
- Collects voice note IDs from asset comments
- Resolves attachment metadata (transcription, duration, blob ref)
- Organizes by floor/room hierarchy
- Generates manifest CSV for ZIP contents
Media Archives
Section titled “Media Archives”GenerateMediaArchiveDirective Comprehensive media collection with flexible filtering and organization.
GenerateMediaArchivePayload( archiveId: InsightId.generate(), estateId: EstateId.fromString('estate-nyc'), siteId: null, // all sites includeContent: { MediaArchiveContentType.photos, MediaArchiveContentType.voiceNotes, }, grouping: MediaArchiveGrouping.byFloorRoom, includeMetadataCsv: true, includeTranscriptions: true, generatedBy: UserId.fromString('user-42'), mediaFiles: [], // populated in plan step)Plan Step Processing:
- Queries all assets with attachments
- Filters by site (if specified)
- Collects attachments by content type
- Determines content type from MIME type
- Resolves attachment metadata (size, blob ref, transcription)
- Organizes by selected grouping strategy
- Generates manifest CSV
Heat Loss Calculator Service
Section titled “Heat Loss Calculator Service”The domain includes a specialized service for building thermal analysis:
PeakHeatLossCalculator
Section titled “PeakHeatLossCalculator”Calculates building peak heat loss using fabric elements and ventilation data.
// Define building elementsfinal fabricElements = [ FabricElement( type: 'wall', area: 120.0, // m² uValue: 0.25, // W/m²K - well-insulated description: 'External walls', ), FabricElement( type: 'roof', area: 80.0, // m² uValue: 0.16, // W/m²K ), FabricElement( type: 'window', area: 30.0, // m² uValue: 1.4, // W/m²K - double glazing ),];
// Define ventilationfinal ventilation = VentilationData( airChangeRate: 1.5, // ACH (air changes per hour) heatRecoveryEfficiency: 0.75, // 75% recovery buildingVolume: 1200.0, // m³ (calculated from floor area × 3m height));
// Define temperature setpointsfinal temperatures = TemperatureSetpoints( internalTemp: 21.0, // °C externalTemp: -5.0, // °C (design temperature));
// Calculatefinal inputs = PeakHeatLossInputs( fabricElements: fabricElements, ventilation: ventilation, temperatures: temperatures,);
final results = PeakHeatLossCalculator.calculate(inputs);
// Access resultsprint('Fabric heat loss: ${results.fabricHeatLoss} W/K');print('Ventilation heat loss: ${results.ventilationHeatLoss} W/K');print('Total heat loss coefficient: ${results.totalHeatLossCoefficient} W/K');print('Peak heat loss: ${results.peakHeatLoss} kW');
// Analyze breakdownfor (final elementResult in results.fabricBreakdown) { print('${elementResult.type}: ${elementResult.percentage.toStringAsFixed(1)}%');}Calculation Method:
- Fabric Heat Loss: Σ(Area × U-value) for all elements
- Ventilation Heat Loss:
- Air flow rate = (ACH × Building Volume) / 3600
- Heat loss = Air flow × Air density × Specific heat capacity
- Apply heat recovery: Final = Heat loss × (1 - Recovery efficiency)
- Total Heat Loss Coefficient: Fabric + Ventilation (W/K)
- Peak Heat Loss: Total × Temperature difference (convert W to kW)
Validation:
final errors = PeakHeatLossCalculator.validateInputs(inputs);if (errors.isNotEmpty) { print('Validation errors:'); for (final error in errors) { print(' - $error'); }}Typical U-Values:
final typicalValues = PeakHeatLossCalculator.getTypicalUValues();// {// 'wall': 0.25, // W/m²K - well-insulated// 'roof': 0.16, // W/m²K// 'floor': 0.22, // W/m²K// 'window': 1.4, // W/m²K - double glazing// 'door': 1.8, // W/m²K// }Typical Air Change Rates:
final typicalAch = PeakHeatLossCalculator.getTypicalAirChangeRates();// {// 'office': 2.0,// 'residential': 1.0,// 'retail': 3.0,// 'warehouse': 1.5,// 'school': 4.0,// 'hospital': 6.0,// }Querying and Retrieving Insights
Section titled “Querying and Retrieving Insights”The domain provides typed selectors for insight queries:
import 'package:insights_v1/insights_v1.dart';
// Stream all active insights for an estatewatchActiveInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId, sourceEntityType: 'estate', sourceEntityId: 'estate-nyc',);
// Stream insights requiring immediate attentionwatchCriticalInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId,).listen((insights) { for (final insight in insights) { if (insight.requiresImmediateAttention) { print('Action required: ${insight.title.value}'); } }});
// Stream stale insights (30+ days, still active)watchStaleInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId,);
// Stream insights by prioritywatchInsightsByPriority( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId,).listen((insights) { // Sorted by priorityScore descending for (final insight in insights) { print('Priority ${insight.priorityScore}: ${insight.title}'); }});
// Stream insights expiring soon (7 days or less)watchExpiringInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId,);Insight Data Type Patterns
Section titled “Insight Data Type Patterns”Access typed insight data using pattern matching:
// Extract performance metricsif (insight.typedData<PerformanceInsightData>() case final perfData?) { print('Efficiency: ${perfData.efficiencyScore}%'); print('Utilization: ${perfData.utilization}%');}
// Extract cost dataif (insight.typedData<CostInsightData>() case final costData?) { print('Estimated cost: ${costData.estimatedCost} ${costData.costUnit}'); print('Savings opportunity: ${costData.savingsOpportunity}');}
// Extract export dataif (insight.typedData<ExportInsightData>() case final exportData?) { print('Export format: ${exportData.format.displayName}'); print('Asset count: ${exportData.assetCount}'); final assets = exportData.assets; // Type-safe asset list}
// Fallback for generic datafinal genericProps = insight.insightData.toJson();Business Rules and Validation
Section titled “Business Rules and Validation”Insight Aggregate Invariants
Section titled “Insight Aggregate Invariants”- Insight ID cannot be unresolved (unless aggregate is empty)
- Title cannot be empty
- Severity and status must be valid enum values
- Critical Insights Cannot Be Dismissed: Critical severity insights must be resolved before closing (not dismissed)
- Expiry date cannot be before generation date
- Related KPIs must exist in system
KPI Calculation Rules
Section titled “KPI Calculation Rules”- Value must be numeric
- Unit must be non-empty
- If target exists, unit must be compatible
- Trend determination:
up: Value increased from previousdown: Value decreased from previousstable: Value unchanged or minimal change
- Meeting target is direction-aware:
- For efficiency/performance/count: value
>=target - For cost/emissions: value
<=target
- For efficiency/performance/count: value
Integration Points
Section titled “Integration Points”Upstream Dependencies
Section titled “Upstream Dependencies”- contracts_v1: Published language for IDs (
InsightId,KPIId), severity/status enums, metadata structures - trackable_asset_v1: Assets for export directives, asset metadata
- estate_structures_v1: Site, building, room definitions for scope
- attachments_v1: Attachment aggregate for file exports and metadata
- catalogues_v1: Listing and taxonomy definitions for enrichment
- nomos_core: Core framework (events, aggregates, directives)
Downstream Consumers
Section titled “Downstream Consumers”- Applications & UIs: Display insights, KPI dashboards, trigger exports
- Policies & Workflows: Route insights by severity, orchestrate responses
- Analytics Engines: Consume events for trending and alerting
- Reporting Systems: Generate periodic summary reports
- External Systems: Integrate via exported data (GeoJSON, CSV)
Validation and Error Handling
Section titled “Validation and Error Handling”Input Validation
Section titled “Input Validation”Export directives perform comprehensive validation:
// Drawing exports validate:// - Estate/site/drawing exist and are accessible// - Cross-workspace queries are permitted// - Asset attachment metadata is resolvable// - Field names don't exceed CSV limits
// Heat loss calculator validates:// - Fabric elements have positive area// - U-values are reasonable (0-10 W/m²K)// - Air change rates are sensible (0.5-50 ACH)// - Temperatures make sense (-50 to 50°C)// - Heat recovery efficiency 0-1.0Error Recovery
Section titled “Error Recovery”// Attachment not found in export// → Silently skip, use raw ID in export
// Field name not found in taxonomy// → Fallback to humanized field ID
// Asset has no coordinates for GeoJSON// → Exclude from feature collection
// Voice note transcription missing// → Export audio with placeholder noteComplete Example: Dashboard Workflow
Section titled “Complete Example: Dashboard Workflow”import 'package:insights_v1/insights_v1.dart';import 'package:contracts_v1/contracts_v1.dart';
// 1. Generate a performance insightfinal generateInsightDirective = GenerateInsightDirective( payload: GenerateInsightPayload( insightId: InsightId.generate(), title: InsightName.fromString('Building A Efficiency Below Target'), description: InsightDescription.fromString( 'HVAC system efficiency dropped to 82%, 8% below monthly target' ), insightType: InsightType.performance, severity: InsightSeverity.warning, sourceEntityId: 'bldg-a', sourceEntityType: 'building', expiresAt: DateTime.now().add(Duration(days: 30)), generatedBy: UserId.fromString('analytics-service'), tags: [ DomainText.fromString('hvac'), DomainText.fromString('efficiency'), DomainText.fromString('building-a'), ], data: { 'efficiencyScore': 82, 'targetScore': 90, 'variance': -8, 'trend': 'declining', 'affectedSystems': ['hvac-unit-1', 'hvac-unit-2'], }, relatedKPIs: [KPIId.fromString('kpi-hvac-efficiency-monthly')], ),);
// 2. Calculate the related KPIfinal calculateKpiDirective = CalculateKPIDirective( payload: CalculateKPIPayload( kpiId: KPIId.fromString('kpi-hvac-efficiency-monthly'), name: KPIName.fromString('HVAC Efficiency - Monthly'), description: KPIDescription.fromString('Building-wide HVAC efficiency average'), kpiType: KPIType.efficiency, value: 82.0, unit: '%', targetValue: 90.0, previousValue: 85.0, trend: Trend.down, relatedEntityId: 'bldg-a', entityType: 'building', timeframe: 'monthly', calculatedBy: UserId.fromString('analytics-service'), metadata: { 'unitCount': 2, 'uptime': 99.8, 'sampleSize': 4320, 'method': 'weighted_average', }, ),);
// 3. Monitor insight statuswatchActiveInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId, sourceEntityType: 'building', sourceEntityId: 'bldg-a',).listen((insights) { for (final insight in insights) { print('Building A: ${insight.title.value}'); print('Severity: ${insight.severity.code}'); print('Priority Score: ${insight.priorityScore}'); }});
// 4. Acknowledge insight when action beginsfinal acknowledgeDirective = UpdateInsightStatusDirective( payload: UpdateInsightStatusPayload( insightId: InsightId.fromString('insight-001'), previousStatus: InsightStatus.active, newStatus: InsightStatus.acknowledged, changedBy: UserId.fromString('maintenance-team'), reason: 'HVAC filters scheduled for replacement next Tuesday', ),);
// 5. Export drawing with assets for maintenance crewfinal exportDirective = GenerateDrawingExportDirective( payload: GenerateDrawingExportPayload( exportId: InsightId.generate(), estateId: EstateId.fromString('estate-nyc'), siteId: SiteId.fromString('site-main'), drawingFeatureKey: GeoFeatureKey.fromString('bldg-a-floor-3-hvac-plan'), format: ExportFormat.csv, csvMode: CsvExportMode.detailed, includeAssets: true, includeImage: true, generatedBy: UserId.fromString('maintenance-supervisor'), content: '', // Populated in plan step assets: [], // Populated in plan step ),);
// 6. Resolve insight after action completefinal resolveDirective = UpdateInsightStatusDirective( payload: UpdateInsightStatusPayload( insightId: InsightId.fromString('insight-001'), previousStatus: InsightStatus.acknowledged, newStatus: InsightStatus.resolved, changedBy: UserId.fromString('maintenance-team'), reason: 'HVAC filters replaced, efficiency restored to 88%', ),);
// 7. Verify via querying resolved insightswatchResolvedInsights( app: nomosApp, workspaceId: workspaceId, timelineId: timelineId,).listen((resolved) { print('${resolved.length} insights resolved this period');});Architecture Notes
Section titled “Architecture Notes”- Event Sourcing: All insights are built via event sourcing, with state reconstructed by applying events
- Snapshot Optimization: Periodic snapshots prevent replaying long event chains
- Cross-Domain Queries: Export directives perform cross-workspace, cross-domain data enrichment in the plan step
- Type Safety: Insight data uses sealed unions (
GenericInsightData,PerformanceInsightData, etc.) for type-safe access - Timeline Awareness: All operations support branched and alternative timelines
- Transactional Consistency: Directives ensure all related events (insight + KPI) are generated together
Related Domains
Section titled “Related Domains”- trackable_asset_v1: Asset data sourced for exports and insight generation
- estate_structures_v1: Site/building/room structure for scope
- contracts_v1: Shared enums and IDs
- attachments_v1: File metadata for export organization
- catalogues_v1: Listing and taxonomy for field enrichment