Skip to content

Proposals Domain (proposals_v1)

The Proposals domain (proposals_v1) is a bounded context that manages the complete lifecycle of proposals—formal requests for changes, improvements, or strategic initiatives within the organization. This domain handles proposal creation, submission, review, approval workflows, impact assessment, and planning for asset replacement scenarios.

The Proposals domain provides a structured workflow for managing organizational changes and initiatives. It supports multiple proposal types (asset replacement, site improvements, operational changes, compliance updates, etc.), tracks approval and assessment workflows, and captures the impact analysis required for informed decision-making.

Key responsibilities:

  1. Proposal Lifecycle Management: Track proposals through draft, submitted, under review, approved, rejected, and implemented states
  2. Impact Assessment: Store and aggregate impact analysis across carbon reduction, cost, and risk dimensions
  3. Asset Replacement Planning: Support planning for trackable asset replacement with site and retiring asset tracking
  4. Approval Workflows: Enforce business rules for state transitions and approval requirements
  5. Impact Analysis: Maintain comprehensive impact assessments with typed analysis data, carbon impact, cost impact, and risk scoring
dart_packages/co2/domains/proposals_v1/
├── lib/
│ ├── proposals_v1.dart # Main export and registration
│ └── src/
│ ├── aggregates/
│ │ └── proposal_aggregate.dart # ProposalAggregate root entity
│ ├── events/
│ │ └── proposal_events.dart # Domain events
│ └── directives/
│ └── proposal_directives.dart # Command handlers
├── pubspec.yaml
├── test/
│ └── proposals_v1_test.dart
└── coverage/

The domain supports multiple proposal types through the ProposalType value object:

TypeCodePurpose
Asset Replacementasset_replacementRetiring old assets and deploying new ones
Site Improvementsite_improvementEnhancements to site facilities
Operational Changeoperational_changeProcess or procedure modifications
Compliance Updatecompliance_updateRegulatory or standards compliance changes
Maintenance Schedulemaintenance_schedulePlanned maintenance operations
Compliance Systemcompliance_systemCompliance technology deployment
Business Intelligencebusiness_intelligenceBI/analytics initiatives
Technology Upgradetechnology_upgradeSystem or technology improvements
Mobile Operationsmobile_operationsField operations enhancements
Sustainability Initiativesustainability_initiativeEnvironmental/sustainability projects
Office Improvementoffice_improvementWorkplace and office enhancements

A proposal transitions through six distinct states:

┌─────────┐ ┌───────────┐
│ DRAFT │──────→│ SUBMITTED │
└─────────┘ └─────┬─────┘
├──────────────┐
↓ ↓
┌────────────────┐ ┌─────────┐
│ UNDER REVIEW │ │ REJECTED│
└────────┬───────┘ └─────────┘
┌───────────┐
│ APPROVED │
└─────┬─────┘
┌───────────────┐
│ IMPLEMENTED │
└───────────────┘
  • Initial state after creation
  • Proposal can be freely edited
  • No assessment or approval required
  • Used for preparation and refinement
  • Proposal formally submitted for review
  • Transitions from draft or under review
  • Ready for approver assessment
  • Cannot be edited directly (must go back to draft)
  • Actively being evaluated by approvers
  • Impact assessments may be added
  • Business rule enforcement begins
  • Can transition to approved or rejected
  • Proposal has passed review and been authorized
  • Must have at least one impact assessment (business rule)
  • Ready for implementation planning
  • Can be implemented or rejectedif conditions change
  • Proposal declined by approvers
  • Considered inactive—cannot be reactivated
  • Retains all historical data
  • Final terminal state (along with implemented)
  • Proposal has been fully executed
  • Considered inactive
  • Retains complete audit trail
  • Final terminal state
  1. Approved proposals must have assessments: Proposals cannot transition to approved status without at least one completed impact assessment
  2. Implemented requires approval: Only approved proposals can move to implemented status
  3. Status validation: Only valid status values from the enum are accepted
  4. Type validation: Only recognized proposal types are permitted
  5. Cost constraints: Estimated cost must be non-negative if provided
  6. Active status check: Rejected and implemented proposals are considered inactive

The ProposalAggregate provides convenience properties for status checking:

proposal.isDraft // status.isDraft
proposal.isSubmitted // status.isSubmitted
proposal.isUnderReview // status.isUnderReview
proposal.isApproved // status.isApproved
proposal.isRejected // status.isRejected
proposal.isImplemented // status.isImplemented
proposal.isActive // !isRejected && !isImplemented

The root aggregate representing a single proposal with complete lifecycle management.

Identity & Core Properties:

final ProposalId proposalId; // Unique proposal identifier
final ProposalTitle title; // User-visible title
final ProposalDescription description; // Detailed description
final ProposalType proposalType; // Category/type of proposal
final UserId submittedByUserId; // User who submitted
final OrganizationId organizationId; // Organization context
final DateTime submittedAt; // Submission timestamp
final DateTime updatedAt; // Last modification time
final UserId submittedBy; // Submitter user reference
final ProposalStatus status; // Current state in workflow
final Money? estimatedCost; // Expected financial impact

Impact Assessment:

final Map<String, ImpactAssessment> _impactAssessments; // assessmentId → assessment

Extended Metadata:

final Metadata metadata; // Arbitrary key-value data
final List<ProposedChange> proposedChanges; // Detailed change log

Asset Replacement Planning:

final List<SiteId> siteIds; // Affected sites
final List<TrackableAssetId> retiringAssetIds; // Assets being retired
final List<ReplacementEdge> replacementEdges; // Mapping of retiring to new assets

Status & Lifecycle Queries:

  • isDraft, isSubmitted, isUnderReview, isApproved, isRejected, isImplemented, isActive: Status checking
  • canBeApproved(): Verify readiness (must be under review with assessments)
  • canBeImplemented(): Verify approved state
  • hasBeenApproved(): Check if ever approved (including if now implemented)

Impact Analysis:

  • impactAssessments: List of all completed assessments
  • getAssessment(assessmentId): Retrieve specific assessment
  • hasAssessments: Boolean check for assessment existence
  • totalCarbonImpact: Sum of carbon impact across all assessments
  • totalCostImpact: Sum of cost impact across all assessments
  • averageRiskScore: Mean risk score if assessments exist

Metadata:

  • ageInDays: Days since proposal submission
  • changeCount: Number of proposed changes

The aggregate applies events through the apply() method:

proposal = proposal.apply(ProposalSubmittedEvent(
proposalId: proposalId,
title: title,
description: description,
proposalType: proposalType,
submittedByUserId: userId,
organizationId: orgId,
submittedAt: now,
submittedBy: userId,
estimatedCost: Money.gbp(5000),
));

Represents a completed impact analysis for a proposal:

class ImpactAssessment {
final String assessmentId; // Unique assessment identifier
final ImpactAnalysis analysis; // Typed analysis data
final double carbonImpact; // CO2 reduction in tonnes
final double costImpact; // Financial impact (£)
final double riskScore; // Risk 0.0-10.0
final List<String> dependencies; // Linked requirements
final DateTime assessedAt; // Assessment timestamp
final UserId assessedBy; // Assessor user
}

Query Methods:

  • hasPositiveCarbonImpact: True if carbon impact > 0
  • hasCostSavings: True if cost impact < 0
  • isHighRisk: True if risk score > 7.0 or analysis indicates high risk
  • hasDependencies: True if dependencies exist or timeline shows blockers
  • ageInDays: Days since assessment
  • overallImpactScore: Composite score from typed analysis

Represents a specific change within a proposal:

class ProposedChange {
final String changeId; // Unique change identifier
final String changeType; // Category of change
final String targetEntityId; // Entity being changed
final String targetEntityType; // Entity type (e.g., 'Asset', 'Site')
final ChangeDiff diff; // Before/after comparison
final DomainText rationale; // Why this change
}

Maps retiring assets to replacement (new) assets:

class ReplacementEdge {
final ListingId listingId; // New asset listing
final int instancesRequired; // Quantity needed
final List<TrackableAssetId> retiringAssetIds; // Assets being replaced
}

The domain publishes events capturing all significant state changes:

Fired when a proposal is initially submitted (draft → submitted transition).

ProposalSubmittedEvent(
proposalId: ProposalId.fromString('prop-123'),
title: ProposalTitle('Replace HVAC Units'),
description: ProposalDescription('Fleet renewal in Building A'),
proposalType: ProposalType('asset_replacement'),
submittedByUserId: UserId.fromString('user-42'),
organizationId: OrganizationId.fromString('org-1'),
submittedAt: DateTime.now(),
submittedBy: UserId.fromString('user-42'),
estimatedCost: Money.gbp(50000),
metadata: {'priority': 'high'},
)

Payload:

  • proposalId: Identifier
  • title: Display name
  • description: Full description
  • proposalType: Category
  • submittedByUserId: Creator
  • organizationId: Organization context
  • submittedAt: Timestamp
  • submittedBy: User reference
  • estimatedCost: Financial estimate
  • metadata: Custom data

Fired when proposal status transitions between states.

ProposalStatusChangedEvent(
proposalId: ProposalId.fromString('prop-123'),
previousStatus: ProposalStatus.submitted,
newStatus: ProposalStatus.approved,
changedBy: UserId.fromString('user-42'),
changedAt: DateTime.now(),
reason: DomainText('Meets all criteria'),
typedApprovalData: ApprovalData(
approvedAt: DateTime.now(),
approver: UserId.fromString('user-42'),
),
)

Payload:

  • proposalId: Target proposal
  • previousStatus: Former state
  • newStatus: New state
  • changedBy: Actor making change
  • changedAt: Timestamp
  • reason: Optional reason/comment
  • approvalData: Typed approval metadata

Fired when impact assessment is completed.

ProposalImpactAssessedEvent(
proposalId: ProposalId.fromString('prop-123'),
assessmentId: 'assess-001',
analysis: ImpactAnalysis(
carbon: CarbonImpact(reduction: 150.5),
cost: CostImpact(savingsPerYear: 12000),
risk: RiskAssessment(score: 3.5),
timeline: TimelineAssessment(
estimatedMonths: 6,
hasDependencies: false,
),
),
carbonImpact: 150.5, // Tonnes CO2 equivalent
costImpact: 12000.0, // Annual savings in £
riskScore: 3.5, // 0-10 scale
dependencies: [],
assessedAt: DateTime.now(),
assessedBy: UserId.fromString('user-43'),
)

Payload:

  • proposalId: Target proposal
  • assessmentId: Unique assessment ID
  • analysis: Typed impact analysis object
  • carbonImpact: Numeric carbon reduction
  • costImpact: Numeric cost/savings
  • riskScore: Numeric risk rating
  • dependencies: List of dependent items
  • assessedAt: Assessment timestamp
  • assessedBy: Assessor user

Fired when sites are associated with a proposal.

ProposalSitesAddedEvent(
proposalId: ProposalId.fromString('prop-123'),
siteIds: [
SiteId.fromString('site-1'),
SiteId.fromString('site-2'),
],
)

Fired when sites are removed from proposal scope.

ProposalSitesRemovedEvent(
proposalId: ProposalId.fromString('prop-123'),
siteIds: [SiteId.fromString('site-1')],
)

Fired when assets scheduled for retirement are added to the proposal.

ProposalRetiringAssetsAddedEvent(
proposalId: ProposalId.fromString('prop-123'),
assetIds: [
TrackableAssetId.fromString('asset-100'),
TrackableAssetId.fromString('asset-101'),
],
)

Fired when retiring assets are removed from the proposal.

ProposalRetiringAssetsRemovedEvent(
proposalId: ProposalId.fromString('prop-123'),
assetIds: [TrackableAssetId.fromString('asset-100')],
)

Fired when mappings between retiring and replacement assets are added.

ProposalReplacementEdgesAddedEvent(
proposalId: ProposalId.fromString('prop-123'),
edges: [
ReplacementEdge(
listingId: ListingId.fromString('hvac-listing-v2'),
instancesRequired: 2,
retiringAssetIds: [
TrackableAssetId.fromString('asset-100'),
TrackableAssetId.fromString('asset-101'),
],
),
],
)

Fired when replacement mappings are removed.

ProposalReplacementEdgesRemovedEvent(
proposalId: ProposalId.fromString('prop-123'),
edges: [/* edges to remove */],
)

Directives are command handlers that validate inputs and produce events. The proposals domain defines directives for each major operation:

Creates a new proposal in draft status.

Payload:

class CreateProposalPayload {
final ProposalId proposalId;
final ProposalTitle title;
final ProposalDescription description;
final ProposalType proposalType;
final UserId createdBy;
final Priority priority;
final Money estimatedCost;
final ProposalDuration estimatedDuration;
final ProposalTargetDate targetImplementationDate;
final List<TrackableAssetId> affectedAssets;
final Metadata metadata;
final List<String> requirements;
final List<String> stakeholders;
}

Output Events:

  • ProposalSubmittedEvent with status set to submitted

Validation:

  • All required fields must be provided
  • Estimated duration must be valid format
  • Target date must be valid format

Submits a proposal for review.

Payload:

class SubmitProposalPayload {
final ProposalId proposalId;
final ProposalTitle title;
final ProposalDescription description;
final ProposalType proposalType;
final UserId submittedByUserId;
final OrganizationId organizationId;
final UserId submittedBy;
final Map<String, dynamic> metadata;
final TrackableAssetReplacementSet replacementSet;
final Money estimatedCost;
}

Output Events:

  • ProposalSubmittedEvent with complete metadata

Changes proposal status (e.g., draft → submitted, submitted → approved).

Payload:

class UpdateProposalStatusPayload {
final ProposalId proposalId;
final ProposalStatus previousStatus;
final ProposalStatus newStatus;
final UserId changedBy;
final DomainText? reason;
final Map<String, dynamic> approvalData;
}

Output Events:

  • ProposalStatusChangedEvent with previous and new status

Business Rule Enforcement:

  • Approved status requires existing assessments
  • Implemented status requires previous approval

Completes impact assessment for a proposal.

Payload:

class AssessProposalImpactPayload {
final ProposalId proposalId;
final String assessmentId;
final Map<String, dynamic> impactAnalysis;
final double carbonImpact;
final double costImpact;
final double riskScore;
final List<String> dependencies;
final UserId assessedBy;
}

Output Events:

  • ProposalImpactAssessedEvent with typed impact analysis

Adds sites to proposal scope.

Removes sites from proposal scope.

Adds assets marked for retirement to the proposal.

Removes retiring assets from the proposal.

Adds mappings between retiring and replacement assets.

Removes replacement asset mappings.

import 'package:proposals_v1/proposals_v1.dart';
import 'package:contracts_v1/contracts_v1.dart';
// Create proposal via directive
final createPayload = CreateProposalPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
title: ProposalTitle('Replace Building A HVAC System'),
description: ProposalDescription(
'Complete replacement of aging HVAC units in Building A. '
'Will improve energy efficiency and reduce carbon footprint.'
),
proposalType: ProposalType('asset_replacement'),
createdBy: UserId.fromString('user-alice'),
priority: Priority.high,
estimatedCost: Money.gbp(75000),
estimatedDuration: ProposalDuration('6 months'),
targetImplementationDate: ProposalTargetDate('2025-Q3'),
affectedAssets: [
TrackableAssetId.fromString('asset-hvac-01'),
TrackableAssetId.fromString('asset-hvac-02'),
],
metadata: {'department': 'facilities', 'budgetCode': 'FAC-2025'},
requirements: ['Energy audit completion', 'Vendor selection'],
stakeholders: ['Facilities Manager', 'Building Operations'],
);
final createDirective = CreateProposalDirective(payload: createPayload);
// Directive produces events that update the aggregate
// Submit impact assessment
final assessPayload = AssessProposalImpactPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
assessmentId: 'assess-20250101-001',
impactAnalysis: {
'carbon': {'reduction': 125.5, 'unit': 'tonnes'},
'cost': {'savings': 18000, 'payback_years': 4.2},
'risk': {'score': 2.5, 'factors': ['Vendor reliability']},
'timeline': {'months': 6, 'dependencies': []},
},
carbonImpact: 125.5,
costImpact: 18000.0,
riskScore: 2.5,
dependencies: [],
assessedBy: UserId.fromString('user-bob'),
);
final assessDirective = AssessProposalImpactDirective(payload: assessPayload);
// After assessment, transition to approved
final approvePayload = UpdateProposalStatusPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
previousStatus: ProposalStatus.underReview,
newStatus: ProposalStatus.approved,
changedBy: UserId.fromString('user-charlie'),
reason: DomainText('All criteria met. High impact and low risk.'),
approvalData: {
'approvalDate': DateTime.now().toIso8601String(),
'approver': 'user-charlie',
'conditions': ['Vendor contract review required before execution'],
},
);
final approveDirective = UpdateProposalStatusDirective(payload: approvePayload);
// Add sites affected by the proposal
final addSitesPayload = AddProposalSitesPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
siteIds: [
'site-london-hq',
'site-manchester-office',
],
);
// Add assets marked for retirement
final retiringPayload = AddProposalRetiringAssetsPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
assetIds: [
TrackableAssetId.fromString('asset-hvac-01'),
TrackableAssetId.fromString('asset-hvac-02'),
TrackableAssetId.fromString('asset-hvac-03'),
],
);
// Map retiring assets to replacement listing
final edgePayload = AddProposalReplacementEdgesPayload(
proposalId: ProposalId.fromString('prop-2024-001'),
edges: [
ReplacementEdge(
listingId: ListingId.fromString('hvac-unit-model-v3'),
instancesRequired: 3,
retiringAssetIds: [
TrackableAssetId.fromString('asset-hvac-01'),
TrackableAssetId.fromString('asset-hvac-02'),
TrackableAssetId.fromString('asset-hvac-03'),
],
),
],
);
// After applying events, query aggregate state
final proposal = /* hydrated from event store */;
// Check lifecycle state
if (proposal.isApproved && proposal.hasAssessments) {
print('Ready for implementation');
}
// Analyze impact
print('Total carbon reduction: ${proposal.totalCarbonImpact} tonnes');
print('Total cost savings: £${proposal.totalCostImpact}');
print('Average risk level: ${proposal.averageRiskScore}');
// Check planning data
print('Sites affected: ${proposal.siteIds.length}');
print('Assets retiring: ${proposal.retiringAssetIds.length}');
print('Replacement mappings: ${proposal.replacementEdges.length}');
// Get specific assessment
final assessment = proposal.getAssessment('assess-20250101-001');
if (assessment != null) {
print('Assessed by: ${assessment.assessedBy}');
print('High risk: ${assessment.isHighRisk}');
}

High-level user intents trigger proposal workflows:

  • CreateProposalIntent: Initiates proposal creation
  • ApproveProposalIntent: Requests approval with conditions
  • SubmitProposalIntent: Submits proposal for review
  • CreateResponsibilitiesFromProposalIntent: Creates work items from approved proposals

Cross-domain communication uses contract types:

  • ProposalId, ProposalTitle, ProposalDescription, ProposalType: Value objects
  • ProposalStatus: Status enumeration
  • ProposalPayloads: Structured payloads for inter-domain events
  • Money: Financial impact representation

Trackable Assets: Asset retirement and replacement planning

  • References retiring asset IDs
  • Maps to replacement catalogue listings

Estate Structures: Site and building context

  • Associates proposals with specific sites
  • Tracks scope within structural hierarchy

Catalogues: Replacement asset definitions

  • References catalogue listings for new assets
  • Uses listing specifications in replacement planning

When using the proposals domain, register types for serialization:

import 'package:proposals_v1/proposals_v1.dart';
// Register all proposals domain types
ProposalsV1().registerDomainTypes();
// Or explicitly:
registerProposalsV1();

This registers:

  • Aggregates: ProposalAggregate
  • Events: All 8 event types
  • Directives: All 10 directive types
  • Payloads: All 10 payload types

The ProposalAggregate.validate() method enforces business rules:

void validate() {
// Empty aggregates are valid (used during snapshot creation)
if (proposalId == ProposalId.unresolved &&
title == ProposalTitle.unresolved) {
return;
}
// ID must be resolved
if (proposalId == ProposalId.unresolved) {
throw StateError('Proposal ID cannot be unresolved');
}
// Title must not be empty
if (title.value.trim().isEmpty) {
throw StateError('Proposal title cannot be empty');
}
// Cost must be non-negative
if (estimatedCost != null && estimatedCost!.amount < 0) {
throw StateError('Estimated cost cannot be negative');
}
// Status must be valid
if (!ProposalStatus.values.contains(status)) {
throw StateError('Invalid proposal status: ${status.value}');
}
// Type must be valid
const validTypes = [
'asset_replacement',
'site_improvement',
'operational_change',
'compliance_update',
'maintenance_schedule',
'compliance_system',
'business_intelligence',
'technology_upgrade',
'mobile_operations',
'sustainability_initiative',
'office_improvement',
];
if (!validTypes.contains(proposalType.value)) {
throw StateError('Invalid proposal type: ${proposalType.value}');
}
// Approved proposals must have assessments
if (status.isApproved && impactAssessments.isEmpty) {
throw StateError(
'Approved proposals must have at least one impact assessment'
);
}
// Implemented requires prior approval
if (status.isImplemented && !isApproved) {
throw StateError('Proposals can only be implemented after approval');
}
}

The domain includes comprehensive tests for lifecycle and serialization:

import 'package:test/test.dart';
import 'package:proposals_v1/proposals_v1.dart';
group('ProposalAggregate', () {
test('lifecycle and equality', () {
final id = AggregateId('prop-1');
final now = DateTime(2023);
final proposal = ProposalAggregate(
proposalId: ProposalId('PROP-1'),
title: ProposalTitle('New Proposal'),
description: ProposalDescription('Description'),
proposalType: ProposalType('asset_replacement'),
submittedByUserId: UserId('USR-1'),
organizationId: OrganizationId('ORG-1'),
submittedAt: now,
updatedAt: now,
submittedBy: UserId('USR-1'),
status: ProposalStatus.draft,
)..setIdForFramework(id);
expect(proposal.isDraft, isTrue);
// Apply event
final submitted = proposal.apply(ProposalSubmittedEvent(
proposalId: ProposalId('PROP-1'),
title: ProposalTitle('New Proposal'),
description: ProposalDescription('Description'),
proposalType: ProposalType('asset_replacement'),
submittedByUserId: UserId('USR-1'),
submittedBy: UserId('USR-1'),
organizationId: OrganizationId('ORG-1'),
submittedAt: DateTime(2024),
estimatedCost: Money.gbp(100),
));
expect(submitted.status, ProposalStatus.submitted);
expect(submitted.isSubmitted, isTrue);
});
test('serialization and deserialization', () {
final json = {
'proposalId': 'PROP-1',
'title': 'Test Proposal',
'description': 'Test Description',
'proposalType': 'asset_replacement',
'submittedByUserId': 'USR-1',
'organizationId': 'ORG-1',
'submittedAt': '2023-01-01T00:00:00Z',
'updatedAt': '2023-01-01T00:00:00Z',
'submittedBy': 'USR-1',
'status': 'draft',
};
final proposal = ProposalAggregate.fromJson(
json,
AggregateId('prop-1'),
);
expect(proposal.proposalId.value, 'PROP-1');
expect(proposal.title.value, 'Test Proposal');
});
});

The proposals domain depends on:

  • nomos_core: Core event sourcing framework

    • Aggregate, Event, Directive, Payload base classes
    • Registry mechanisms for type serialization
    • AggregateId, AggregateBase infrastructure
  • contracts_v1: Cross-domain value objects

    • ProposalId, ProposalTitle, ProposalDescription, ProposalType, ProposalStatus
    • ProposalPayloads (all payload contracts)
    • UserId, OrganizationId, SiteId, TrackableAssetId
    • Money, ListingId, ReplacementEdge, etc.

No domain-specific dependencies—designed for loose coupling and cross-domain communication.

// Good: Type-safe
final proposalId = ProposalId.fromString('prop-123');
// Avoid: Prone to errors
final proposalId = 'prop-123';
// Good: Verify preconditions
if (proposal.canBeApproved()) {
// Execute approval
}
// Avoid: Assume state
// Execute approval directly
// Good: Use aggregate calculations
final totalCarbon = proposal.totalCarbonImpact;
final avgRisk = proposal.averageRiskScore;
// Avoid: Calculating manually
final totalCarbon = proposal.impactAssessments
.fold(0.0, (sum, a) => sum + a.carbonImpact);
// Good: Full context for each assessment
final assessment = ImpactAssessment(
assessmentId: 'assess-001',
analysis: ImpactAnalysis(...), // Typed analysis
carbonImpact: 125.5,
costImpact: 18000.0,
riskScore: 3.5,
dependencies: ['vendor-selection'],
assessedAt: now,
assessedBy: userId,
);
// Avoid: Partial or unstructured data
// Store only numeric values without context
// Good: Extend without schema changes
final proposal = ProposalAggregate(
// ... required fields ...
metadata: {
'customField': 'value',
'departmentCode': 'FAC-2025',
'approvalChain': ['manager', 'director'],
},
);
// Avoid: Adding new database columns or type modifications
  • Contracts Domain: Value object definitions and payloads
  • Intents Domain: High-level user intentions that trigger proposals workflows
  • Trackable Assets Domain: Asset lifecycle and retirement planning
  • Estate Structures Domain: Site and building context for proposals
  • Catalogues Domain: Asset definitions and specifications for replacements