Created
February 16, 2026 21:17
-
-
Save achal7/7df6128e7d8e4ae6dd3ce140303ad447 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Medhavi.Domain.Calendar | |
| open System | |
| open System.Text.Json.Serialization | |
| open Medhavi.Domain.Ids | |
| [<JsonFSharpConverter>] | |
| type CalendarType = | |
| | ResourceCalendar | |
| | PlantCalendar | |
| | StockingPointCalendar | |
| | NetworkCalendar | |
| [<JsonFSharpConverter>] | |
| type EventId = private | EventId of string | |
| [<JsonFSharpConverter>] | |
| type EventType = | |
| | Holiday | |
| | Maintenance | |
| | Shift | |
| | Campaign | |
| | Custom of string | |
| type RecurrencePattern = | |
| | Daily of int | |
| | Weekly of int * string list // interval, weekdays | |
| | MonthlyDay of int // day of month | |
| | MonthlyWeek of string * string // week of month, day of week | |
| | YearlyDay of int * int // month, day | |
| | YearlyWeek of int * string * string // month, week of month, day of week | |
| type Calendar = | |
| { | |
| Id: CalendarId | |
| CalendarType: CalendarType | |
| BaseDate: DateTimeOffset | |
| WindowDays: int | |
| HistoryDays: int | |
| UpdateInterval: TimeSpan | |
| StartDateAsReal: float option | |
| Events: CalendarEvent list // Events stored in calendar for validation and querying | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| and CalendarEvent = | |
| { | |
| Id: EventId | |
| CalendarId: CalendarId | |
| Subject: string | |
| EventType: EventType | |
| CapacityFactor: float // 0.0–1.0 | |
| IsDefault: bool | |
| Window: Medhavi.Domain.Window | |
| Recurrence: RecurrencePattern option | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| module EventId = | |
| open Medhavi.Domain | |
| let create (value: string) : Result<EventId, DomainError> = | |
| if String.IsNullOrWhiteSpace value then | |
| Error(DomainError.validation "Event ID is required") | |
| else | |
| Ok(EventId value) | |
| let value (EventId v) = v | |
| type EventSchedule = | |
| { | |
| EventId: EventId | |
| CalendarId: CalendarId | |
| Window: Medhavi.Domain.Window | |
| CapacityFactor: float | |
| } | |
| type CalendarAvailability = | |
| { | |
| CalendarId: CalendarId | |
| Date: DateTimeOffset | |
| CapacityFactor: float | |
| IsAvailable: bool | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Medhavi.Domain.Calendar.CalendarWorkflows | |
| open Medhavi.Domain | |
| open Medhavi.Domain.Calendar | |
| open Medhavi.Domain.Ids | |
| open System | |
| open Medhavi.Common.ResultCE | |
| // Commands | |
| type CreateCalendarCmd = | |
| { | |
| Id: CalendarId | |
| CalendarType: CalendarType | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type AddCalendarEventCmd = | |
| { | |
| CalendarId: CalendarId | |
| Event: CalendarEvent | |
| } | |
| type RemoveCalendarEventCmd = | |
| { | |
| CalendarId: CalendarId | |
| EventId: EventId | |
| } | |
| type ClearCalendarCmd = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type ActivateCalendarCmd = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type DeactivateCalendarCmd = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type CalendarCommand = | |
| | CreateCalendar of CreateCalendarCmd | |
| | AddCalendarEvent of AddCalendarEventCmd | |
| | RemoveCalendarEvent of RemoveCalendarEventCmd | |
| | ClearCalendar of ClearCalendarCmd | |
| | ActivateCalendar of ActivateCalendarCmd | |
| | DeactivateCalendar of DeactivateCalendarCmd | |
| // Events | |
| type CalendarCreatedEvt = | |
| { | |
| Id: CalendarId | |
| CalendarType: CalendarType | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type CalendarEventAddedEvt = | |
| { | |
| CalendarId: CalendarId | |
| Event: CalendarEvent | |
| } | |
| type CalendarEventRemovedEvt = | |
| { | |
| CalendarId: CalendarId | |
| EventId: EventId | |
| Modified: DateTimeOffset | |
| } | |
| type CalendarClearedEvt = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type CalendarActivatedEvt = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type CalendarDeactivatedEvt = | |
| { | |
| CalendarId: CalendarId | |
| Modified: DateTimeOffset | |
| } | |
| type CalendarsEvent = | |
| | CalendarCreated of CalendarCreatedEvt | |
| | CalendarEventAdded of CalendarEventAddedEvt | |
| | CalendarEventRemoved of CalendarEventRemovedEvt | |
| | CalendarCleared of CalendarClearedEvt | |
| | CalendarActivated of CalendarActivatedEvt | |
| | CalendarDeactivated of CalendarDeactivatedEvt | |
| // Signatures | |
| type DecideResourceCalendar = Calendar option -> CalendarCommand -> Result<CalendarEvent list, DomainError> | |
| type EvolveCalendar = Evolve<Calendar, CalendarsEvent> | |
| // Note: Calendar aggregate doesn't have IsActive field, but commands/events do. | |
| // We'll track it separately or extend the Calendar type if needed. | |
| // For now, we'll work with the Calendar structure as defined. | |
| // Validation functions (includes business rules) | |
| let validateCreate (cmd: CreateCalendarCmd) : Result<unit, DomainError> = | |
| result { | |
| //let! _ = required "Calendar Id" cmd.Id | |
| return () | |
| } | |
| let validateAddEvent (cmd: AddCalendarEventCmd) : Result<unit, DomainError> = | |
| result { | |
| // Validate event has required fields | |
| let! _ = | |
| if String.IsNullOrWhiteSpace(cmd.Event.Subject) then | |
| Error(DomainError.validation "Calendar event subject is required") | |
| else | |
| Ok() | |
| // Validate window | |
| let! _ = | |
| if cmd.Event.Window.Start >= cmd.Event.Window.End then | |
| Error(DomainError.validation "Calendar event start time must be before end time") | |
| else | |
| Ok() | |
| // Validate capacity factor | |
| let! _ = | |
| if | |
| cmd.Event.CapacityFactor < 0.0 | |
| || cmd.Event.CapacityFactor > 1.0 | |
| then | |
| Error(DomainError.validation "Calendar event capacity factor must be between 0.0 and 1.0") | |
| else | |
| Ok() | |
| return () | |
| } | |
| let validateRemoveEvent (cmd: RemoveCalendarEventCmd) : Result<unit, DomainError> = result { return () } | |
| let validateClear (_cmd: ClearCalendarCmd) : Result<unit, DomainError> = | |
| // Clearing is always allowed | |
| Ok() | |
| let validateActivate (_cmd: ActivateCalendarCmd) : Result<unit, DomainError> = | |
| // Activation is always allowed | |
| Ok() | |
| let validateDeactivate (_cmd: DeactivateCalendarCmd) : Result<unit, DomainError> = | |
| // Deactivation is always allowed | |
| Ok() | |
| // State evolution functions (pure state transitions) | |
| // Note: Calendar type doesn't have IsActive, but events do. We'll use default values for missing fields. | |
| let applyCreated (evt: CalendarCreatedEvt) : Calendar = | |
| let now = DateTimeOffset.UtcNow | |
| { | |
| Id = evt.Id | |
| CalendarType = evt.CalendarType | |
| BaseDate = now | |
| WindowDays = 30 // Default window | |
| HistoryDays = 7 // Default history | |
| UpdateInterval = TimeSpan.FromHours(1.0) // Default update interval | |
| StartDateAsReal = None | |
| Events = [] // Initialize with empty events list | |
| Created = evt.Created | |
| Modified = evt.Created | |
| } | |
| // Add event to calendar, maintaining event list | |
| let applyEventAdded (evt: CalendarEventAddedEvt) (state: Calendar) : Calendar = | |
| // Check if event already exists (idempotency) | |
| let eventExists = | |
| state.Events | |
| |> List.exists (fun e -> e.Id = evt.Event.Id) | |
| if eventExists then | |
| state // Idempotent - event already exists | |
| else | |
| { state with | |
| Events = evt.Event :: state.Events | |
| Modified = DateTimeOffset.UtcNow | |
| } | |
| let applyEventRemoved (evt: CalendarEventRemovedEvt) (state: Calendar) : Calendar = | |
| { state with | |
| Events = | |
| state.Events | |
| |> List.filter (fun e -> e.Id <> evt.EventId) | |
| Modified = evt.Modified | |
| } | |
| let applyCleared (evt: CalendarClearedEvt) (state: Calendar) : Calendar = | |
| { state with | |
| Events = [] // Clear all events | |
| Modified = evt.Modified | |
| } | |
| let applyActivated (evt: CalendarActivatedEvt) (state: Calendar) : Calendar = { state with Modified = evt.Modified } | |
| let applyDeactivated (evt: CalendarDeactivatedEvt) (state: Calendar) : Calendar = { state with Modified = evt.Modified } | |
| let evolve (state: Calendar option) (event: CalendarsEvent) : Calendar option = | |
| match event, state with | |
| | CalendarCreated e, None -> Some(applyCreated e) | |
| | CalendarEventAdded e, Some s -> Some(applyEventAdded e s) | |
| | CalendarEventRemoved e, Some s -> Some(applyEventRemoved e s) | |
| | CalendarCleared e, Some s -> Some(applyCleared e s) | |
| | CalendarActivated e, Some s -> Some(applyActivated e s) | |
| | CalendarDeactivated e, Some s -> Some(applyDeactivated e s) | |
| | CalendarCreated _, Some _ -> state // Idempotent - calendar already exists | |
| | _, None -> None // Can't apply updates to non-existent calendar | |
| // let expandRecurrence (evt: CalendarEvent) (horizonStart, horizonEnd) = | |
| // match evt.Recurrence with | |
| // | None -> [ (horizonStart, horizonEnd) ] | |
| // | Daily interval -> | |
| // [ | |
| // for i in | |
| // 0 .. int ( | |
| // (horizonEnd - horizonStart).TotalDays | |
| // / float interval | |
| // ) -> | |
| // let start = horizonStart.AddDays(float (i * interval)) | |
| // (start, start.Add(evt.Duration.Value)) | |
| // ] | |
| // | Weekly(interval, weekdays) -> | |
| // // Simplified: generate weekly slots | |
| // [] | |
| // | _ -> [] | |
| // // Build availability buckets from schedules | |
| // let buildAvailability (schedules: EventSchedule list) (bucketSize: TimeSpan) = | |
| // schedules | |
| // |> List.collect (fun s -> | |
| // let mutable t = s.Start | |
| // let buckets = ResizeArray() | |
| // while t < s.End do | |
| // buckets.Add( | |
| // { | |
| // CalendarId = s.CalendarId | |
| // Date = t | |
| // CapacityFactor = s.CapacityFactor | |
| // IsAvailable = s.CapacityFactor > 0.0 | |
| // } | |
| // ) | |
| // t <- t.Add(bucketSize) | |
| // buckets |> List.ofSeq) | |
| // // |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Medhavi.Domain.Capacity.Reservation | |
| open System | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain | |
| open System.Text.Json.Serialization | |
| [<JsonFSharpConverter>] | |
| type CapacityReservationStatus = | |
| | Tentative | |
| | Confirmed | |
| | Released | |
| | Expired | |
| | Reduced | |
| type CapacityReservation = | |
| { | |
| Id: CapacityReservationId | |
| IdempotencyKey: string option | |
| ResourceId: PhysicalResourceId | |
| ReservedCapacity: TimeSpan | |
| Status: CapacityReservationStatus | |
| Window: Window | |
| Ttl: TimeSpan option | |
| OperationId: OperationId option | |
| CreatedDate: DateTimeOffset | |
| ModifiedDate: DateTimeOffset | |
| } | |
| // Commands | |
| type CreateCapacityReservationCmd = | |
| { | |
| Id: string | |
| IdempotencyKey: string option | |
| ResourceId: PhysicalResourceId | |
| ReservedCapacity: TimeSpan | |
| Window: Window | |
| Ttl: TimeSpan option | |
| OperationId: OperationId | |
| CreatedDate: DateTimeOffset | |
| } | |
| type ConfirmCapacityReservationCmd = | |
| { | |
| Id: CapacityReservationId | |
| ConfirmedDate: DateTimeOffset | |
| } | |
| type ReleaseCapacityReservationCmd = | |
| { | |
| Id: CapacityReservationId | |
| ReleasedDate: DateTimeOffset | |
| } | |
| type ReduceCapacityReservationCmd = | |
| { | |
| Id: CapacityReservationId | |
| NewCapacity: TimeSpan | |
| NewWindowEnd: DateTimeOffset option | |
| ReducedDate: DateTimeOffset | |
| } | |
| type ExpireCapacityReservationCmd = | |
| { | |
| Id: CapacityReservationId | |
| ExpiredDate: DateTimeOffset | |
| } | |
| type CapacityReservationCommand = | |
| | CreateCapacityReservation of CreateCapacityReservationCmd | |
| | ConfirmCapacityReservation of ConfirmCapacityReservationCmd | |
| | ReleaseCapacityReservation of ReleaseCapacityReservationCmd | |
| | ReduceCapacityReservation of ReduceCapacityReservationCmd | |
| | ExpireCapacityReservation of ExpireCapacityReservationCmd | |
| // Events | |
| type CapacityReservationCreatedEvt = | |
| { | |
| Id: CapacityReservationId | |
| IdempotencyKey: string option | |
| ResourceId: PhysicalResourceId | |
| ReservedCapacity: TimeSpan | |
| Window: Window | |
| Ttl: TimeSpan option | |
| OperationId: OperationId | |
| CreatedDate: DateTimeOffset | |
| } | |
| type CapacityReservationConfirmedEvt = | |
| { | |
| Id: CapacityReservationId | |
| ConfirmedDate: DateTimeOffset | |
| } | |
| type CapacityReservationReleasedEvt = | |
| { | |
| Id: CapacityReservationId | |
| ReleasedDate: DateTimeOffset | |
| } | |
| type CapacityReservationReducedEvt = | |
| { | |
| Id: CapacityReservationId | |
| NewCapacity: TimeSpan | |
| NewWindowEnd: DateTimeOffset option | |
| } | |
| type CapacityReservationExpiredEvt = | |
| { | |
| Id: CapacityReservationId | |
| ExpiredDate: DateTimeOffset | |
| } | |
| type CapacityReservationEvent = | |
| | CapacityReservationCreated of CapacityReservationCreatedEvt | |
| | CapacityReservationConfirmed of CapacityReservationConfirmedEvt | |
| | CapacityReservationReleased of CapacityReservationReleasedEvt | |
| | CapacityReservationReduced of CapacityReservationReducedEvt | |
| | CapacityReservationExpired of CapacityReservationExpiredEvt | |
| // Signatures | |
| type DecideCapacityReservation = | |
| CapacityReservation option -> CapacityReservationCommand -> Result<CapacityReservationEvent list, DomainError> | |
| type EvolveCapacityReservation = Evolve<CapacityReservation, CapacityReservationEvent> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Medhavi.Domain.Resources | |
| open System | |
| open Medhavi.Domain.Calendar | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain | |
| open ResourceGroup | |
| type CombinedResource = | |
| { | |
| Id: CombinedResourceId | |
| Name: string | |
| StandardResources: StandardResourceId list | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| type CombinedResourcePeriod = | |
| { | |
| Id: CombinedResourcePeriodId | |
| CombinedResourceId: CombinedResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| // Commands | |
| type DefineCombinedResourceCmd = | |
| { | |
| Id: string | |
| Name: string | |
| StandardResources: StandardResourceId list | |
| IsActive: bool | |
| } | |
| type RenameCombinedResourceCmd = | |
| { | |
| Id: CombinedResourceId | |
| NewName: string | |
| } | |
| type DeactivateCombinedResourceCmd = | |
| { | |
| Id: CombinedResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type DefineCombinedResourcePeriodCmd = | |
| { | |
| Id: string | |
| CombinedResourceId: CombinedResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| } | |
| type CombinedResourceCommand = | |
| | DefineCombinedResource of DefineCombinedResourceCmd | |
| | RenameCombinedResource of RenameCombinedResourceCmd | |
| | DeactivateCombinedResource of DeactivateCombinedResourceCmd | |
| | DefineCombinedResourcePeriod of DefineCombinedResourcePeriodCmd | |
| // Events | |
| type CombinedResourceDefinedEvt = | |
| { | |
| Id: CombinedResourceId | |
| Name: string | |
| StandardResources: StandardResourceId list | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type CombinedResourceRenamedEvt = | |
| { | |
| Id: CombinedResourceId | |
| NewName: string | |
| Modified: DateTimeOffset | |
| } | |
| type CombinedResourceDeactivatedEvt = | |
| { | |
| Id: CombinedResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type CombinedResourcePeriodDefinedEvt = | |
| { | |
| Id: CombinedResourcePeriodId | |
| CombinedResourceId: CombinedResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| Created: DateTimeOffset | |
| } | |
| type CombinedResourceEvent = | |
| | CombinedResourceDefined of CombinedResourceDefinedEvt | |
| | CombinedResourceRenamed of CombinedResourceRenamedEvt | |
| | CombinedResourceDeactivated of CombinedResourceDeactivatedEvt | |
| | CombinedResourcePeriodDefined of CombinedResourcePeriodDefinedEvt | |
| // Signatures | |
| type DecideCombinedResource = | |
| CombinedResource option -> CombinedResourceCommand -> Result<CombinedResourceEvent list, DomainError> | |
| type EvolveCombinedResource = CombinedResource option -> CombinedResourceEvent -> CombinedResource |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Medhavi.Domain.Capacity.Operation | |
| open System | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain | |
| type OperationState = | |
| | Created | |
| | Planned | |
| | Scheduled | |
| | InProgress | |
| | Completed | |
| | Cancelled | |
| type Operation = | |
| { | |
| Id: OperationId | |
| SequenceNumber: int | |
| RoutingStepId: RoutingStepId | |
| State: OperationState | |
| AddedLeadTime: TimeSpan | |
| IsFixed: bool | |
| CampaignTypeAssignment: CampaignTypeAssignmentId option | |
| // Capacity-related fields (mandatory when Scheduled/InProgress) | |
| Window: Window option // Mandatory when State = Scheduled or InProgress | |
| ResourceId: PhysicalResourceId option // Mandatory when State = Scheduled or InProgress | |
| Duration: TimeSpan option // Mandatory when State = Scheduled or InProgress | |
| CreatedDate: DateTimeOffset | |
| ModifiedDate: DateTimeOffset | |
| } | |
| // Commands | |
| type ScheduleOperationCmd = | |
| { | |
| Id: OperationId | |
| SequenceNumber: int | |
| Window: Window // Mandatory: when operation is scheduled, it must have a time window | |
| RoutingStepId: RoutingStepId | |
| ResourceId: PhysicalResourceId // Mandatory: which resource will perform this operation | |
| Duration: TimeSpan // Mandatory: how long the operation will take | |
| IsFixed: bool | |
| } | |
| type StartOperationCmd = | |
| { | |
| Id: OperationId | |
| StartedDate: DateTimeOffset | |
| } | |
| type CompleteOperationCmd = | |
| { | |
| Id: OperationId | |
| CompletedDate: DateTimeOffset | |
| } | |
| type CancelOperationCmd = | |
| { | |
| Id: OperationId | |
| CancelledDate: DateTimeOffset | |
| } | |
| type OperationCommand = | |
| | ScheduleOperation of ScheduleOperationCmd | |
| | StartOperation of StartOperationCmd | |
| | CompleteOperation of CompleteOperationCmd | |
| | CancelOperation of CancelOperationCmd | |
| // Events | |
| type OperationScheduledEvt = | |
| { | |
| Id: OperationId | |
| Window: Window // Time window when operation is scheduled | |
| ResourceId: PhysicalResourceId // Resource that will perform the operation | |
| Duration: TimeSpan // Duration of the operation | |
| } | |
| type OperationStartedEvt = | |
| { | |
| Id: OperationId | |
| StartedDate: DateTimeOffset | |
| } | |
| type OperationCompletedEvt = | |
| { | |
| Id: OperationId | |
| CompletedDate: DateTimeOffset | |
| } | |
| type OperationCancelledEvt = | |
| { | |
| Id: OperationId | |
| CancelledDate: DateTimeOffset | |
| } | |
| type OperationEvent = | |
| | OperationScheduled of OperationScheduledEvt | |
| | OperationStarted of OperationStartedEvt | |
| | OperationCompleted of OperationCompletedEvt | |
| | OperationCancelled of OperationCancelledEvt | |
| // Signatures | |
| type DecideOperation = Medhavi.Domain.Decide<Operation, OperationCommand, OperationEvent, DomainError> | |
| type EvolveOperation = Medhavi.Domain.Evolve<Operation, OperationEvent> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Medhavi.Domain.Resources.PhysicalResource | |
| open System | |
| open Medhavi.Domain.Calendar | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain.Validation | |
| open Medhavi.Domain | |
| open Medhavi.Common.ResultCE | |
| open ResourceGroup | |
| open StandardResource | |
| type PhysicalResource = | |
| { | |
| Id: PhysicalResourceId | |
| StandardResourceId: StandardResourceId | |
| Name: string | |
| SerialNumber: string option | |
| Location: string option | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| type PhysicalResourcePeriod = | |
| { | |
| Id: PhysicalResourcePeriodId | |
| PhysicalResourceId: PhysicalResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| // Commands | |
| type DefinePhysicalResourceCmd = | |
| { | |
| Id: string | |
| StandardResourceId: StandardResourceId | |
| Name: string | |
| SerialNumber: string option | |
| Location: string option | |
| IsActive: bool | |
| } | |
| type RenamePhysicalResourceCmd = | |
| { | |
| Id: PhysicalResourceId | |
| NewName: string | |
| } | |
| type DeactivatePhysicalResourceCmd = | |
| { | |
| Id: PhysicalResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type DefinePhysicalResourcePeriodCmd = | |
| { | |
| Id: string | |
| PhysicalResourceId: PhysicalResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| } | |
| type PhysicalResourceCommand = | |
| | DefinePhysicalResource of DefinePhysicalResourceCmd | |
| | RenamePhysicalResource of RenamePhysicalResourceCmd | |
| | DeactivatePhysicalResource of DeactivatePhysicalResourceCmd | |
| | DefinePhysicalResourcePeriod of DefinePhysicalResourcePeriodCmd | |
| // Events | |
| type PhysicalResourceDefinedEvt = | |
| { | |
| Id: PhysicalResourceId | |
| StandardResourceId: StandardResourceId | |
| Name: string | |
| SerialNumber: string option | |
| Location: string option | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type PhysicalResourceRenamedEvt = | |
| { | |
| Id: PhysicalResourceId | |
| NewName: string | |
| Modified: DateTimeOffset | |
| } | |
| type PhysicalResourceDeactivatedEvt = | |
| { | |
| Id: PhysicalResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type PhysicalResourcePeriodDefinedEvt = | |
| { | |
| Id: PhysicalResourcePeriodId | |
| PhysicalResourceId: PhysicalResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| Created: DateTimeOffset | |
| } | |
| type PhysicalResourceEvent = | |
| | PhysicalResourceDefined of PhysicalResourceDefinedEvt | |
| | PhysicalResourceRenamed of PhysicalResourceRenamedEvt | |
| | PhysicalResourceDeactivated of PhysicalResourceDeactivatedEvt | |
| | PhysicalResourcePeriodDefined of PhysicalResourcePeriodDefinedEvt | |
| // Signatures | |
| type DecidePhysicalResource = | |
| PhysicalResource option -> PhysicalResourceCommand -> Result<PhysicalResourceEvent list, DomainError> | |
| type EvolvePhysicalResource = Medhavi.Domain.Evolve<PhysicalResource, PhysicalResourceEvent> | |
| // Validation functions (includes business rules) | |
| let validateDefine (cmd: DefinePhysicalResourceCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = required "PhysicalResource Id" cmd.Id | |
| let! _ = required "PhysicalResource Name" cmd.Name | |
| return () | |
| } | |
| let validateRename (cmd: RenamePhysicalResourceCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = | |
| if String.IsNullOrWhiteSpace cmd.NewName then | |
| Error(DomainError.validation "Physical resource name cannot be empty") | |
| else | |
| Ok() | |
| return () | |
| } | |
| let validateDeactivate (_cmd: DeactivatePhysicalResourceCmd) : Result<unit, DomainError> = | |
| // Deactivation is always allowed | |
| Ok() | |
| // State evolution functions (pure state transitions) | |
| let applyDefined (evt: PhysicalResourceDefinedEvt) : PhysicalResource = | |
| { | |
| Id = evt.Id | |
| StandardResourceId = evt.StandardResourceId | |
| Name = evt.Name | |
| SerialNumber = evt.SerialNumber | |
| Location = evt.Location | |
| IsActive = evt.IsActive | |
| Created = evt.Created | |
| Modified = evt.Created | |
| } | |
| let applyRenamed (evt: PhysicalResourceRenamedEvt) (state: PhysicalResource) : PhysicalResource = | |
| { state with | |
| Name = evt.NewName | |
| Modified = evt.Modified | |
| } | |
| let applyDeactivated (evt: PhysicalResourceDeactivatedEvt) (state: PhysicalResource) : PhysicalResource = | |
| { state with | |
| IsActive = false | |
| Modified = evt.DeactivatedAt | |
| } | |
| let evolve (state: PhysicalResource option) (event: PhysicalResourceEvent) : PhysicalResource option = | |
| match event, state with | |
| | PhysicalResourceDefined e, None -> Some(applyDefined e) | |
| | PhysicalResourceRenamed e, Some s -> Some(applyRenamed e s) | |
| | PhysicalResourceDeactivated e, Some s -> Some(applyDeactivated e s) | |
| | PhysicalResourceDefined _, Some _ -> state // Idempotent - physical resource already exists | |
| | _, None -> None // Can't apply updates to non-existent physical resource | |
| | _ -> state // Other events not handled |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Medhavi.Domain.Resources.ResourceGroup | |
| open System | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain.Calendar | |
| open Medhavi.Domain.Validation | |
| open Medhavi.Domain | |
| open Medhavi.Common.ResultCE | |
| type CapacityBreakdown = | |
| { | |
| TotalCapacity: TimeSpan | |
| AvailableCapacity: TimeSpan | |
| UnavailableCapacity: TimeSpan | |
| ReservedCapacity: TimeSpan | |
| OccupiedCapacity: TimeSpan | |
| OverloadCapacity: TimeSpan | |
| FreeCapacity: TimeSpan | |
| } | |
| type ResourceGroup = | |
| { | |
| Id: ResourceGroupId | |
| PlantId: PlantId option | |
| Name: string | |
| Description: string option | |
| IsActive: bool | |
| CalendarId: CalendarId option | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| type ResourceGroupCapacity = | |
| { | |
| Id: ResourceGroupCapacityId | |
| ResourceGroupId: ResourceGroupId | |
| Window: Window | |
| Capacity: CapacityBreakdown | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| // Commands | |
| type DefineResourceGroupCmd = | |
| { | |
| Id: string | |
| PlantId: PlantId option | |
| Name: string | |
| Description: string option | |
| IsActive: bool | |
| } | |
| type RenameResourceGroupCmd = | |
| { Id: ResourceGroupId; NewName: string } | |
| type DeactivateResourceGroupCmd = | |
| { | |
| Id: ResourceGroupId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type DefineResourceGroupPeriodCmd = | |
| { | |
| Id: string | |
| ResourceGroupId: ResourceGroupId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| } | |
| type ResourceGroupCommand = | |
| | DefineResourceGroup of DefineResourceGroupCmd | |
| | RenameResourceGroup of RenameResourceGroupCmd | |
| | DeactivateResourceGroup of DeactivateResourceGroupCmd | |
| | DefineResourceGroupPeriod of DefineResourceGroupPeriodCmd | |
| // Events | |
| type ResourceGroupDefinedEvt = | |
| { | |
| Id: ResourceGroupId | |
| PlantId: PlantId option | |
| Name: string | |
| Description: string option | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type ResourceGroupRenamedEvt = | |
| { | |
| Id: ResourceGroupId | |
| NewName: string | |
| Modified: DateTimeOffset | |
| } | |
| type ResourceGroupDeactivatedEvt = | |
| { | |
| Id: ResourceGroupId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type ResourceGroupEvent = | |
| | ResourceGroupDefined of ResourceGroupDefinedEvt | |
| | ResourceGroupRenamed of ResourceGroupRenamedEvt | |
| | ResourceGroupDeactivated of ResourceGroupDeactivatedEvt | |
| // Signatures | |
| type DecideResourceGroup = ResourceGroup option -> ResourceGroupCommand -> Result<ResourceGroupEvent list, DomainError> | |
| type EvolveResourceGroup = Medhavi.Domain.Evolve<ResourceGroup, ResourceGroupEvent> | |
| // Validation functions (includes business rules) | |
| let validateDefine (cmd: DefineResourceGroupCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = required "ResourceGroup Id" cmd.Id | |
| let! _ = required "ResourceGroup Name" cmd.Name | |
| return () | |
| } | |
| let validateRename (cmd: RenameResourceGroupCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = | |
| if String.IsNullOrWhiteSpace cmd.NewName then | |
| Error(DomainError.validation "Resource group name cannot be empty") | |
| else | |
| Ok() | |
| return () | |
| } | |
| let validateDeactivate (_cmd: DeactivateResourceGroupCmd) : Result<unit, DomainError> = | |
| // Deactivation is always allowed | |
| Ok() | |
| // State evolution functions (pure state transitions) | |
| let applyDefined (evt: ResourceGroupDefinedEvt) : ResourceGroup = | |
| { | |
| Id = evt.Id | |
| PlantId = evt.PlantId | |
| Name = evt.Name | |
| Description = evt.Description | |
| IsActive = evt.IsActive | |
| CalendarId = None | |
| Created = evt.Created | |
| Modified = evt.Created | |
| } | |
| let applyRenamed (evt: ResourceGroupRenamedEvt) (state: ResourceGroup) : ResourceGroup = | |
| { state with | |
| Name = evt.NewName | |
| Modified = evt.Modified | |
| } | |
| let applyDeactivated (evt: ResourceGroupDeactivatedEvt) (state: ResourceGroup) : ResourceGroup = | |
| { state with | |
| IsActive = false | |
| Modified = evt.DeactivatedAt | |
| } | |
| let evolve (state: ResourceGroup option) (event: ResourceGroupEvent) : ResourceGroup option = | |
| match event, state with | |
| | ResourceGroupDefined e, None -> Some(applyDefined e) | |
| | ResourceGroupRenamed e, Some s -> Some(applyRenamed e s) | |
| | ResourceGroupDeactivated e, Some s -> Some(applyDeactivated e s) | |
| | ResourceGroupDefined _, Some _ -> state // Idempotent - resource group already exists | |
| | _, None -> None // Can't apply updates to non-existent resource group |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Medhavi.Domain.Resources.StandardResource | |
| open System | |
| open Medhavi.Domain.Calendar | |
| open Medhavi.Domain.Ids | |
| open Medhavi.Domain.Validation | |
| open Medhavi.Domain | |
| open Medhavi.Common.ResultCE | |
| open ResourceGroup | |
| type StandardResource = | |
| { | |
| Id: StandardResourceId | |
| ResourceGroupId: ResourceGroupId | |
| Name: string | |
| Description: string option | |
| PhysicalResources: PhysicalResourceId list | |
| CampaignTypes: CampaignTypeId list | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| type StandardResourcePeriod = | |
| { | |
| Id: StandardResourcePeriodId | |
| StandardResourceId: StandardResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| EfficiencyFactor: float | |
| Created: DateTimeOffset | |
| Modified: DateTimeOffset | |
| } | |
| // Commands | |
| type DefineStandardResourceCmd = | |
| { | |
| Id: string | |
| ResourceGroupId: ResourceGroupId | |
| Name: string | |
| Description: string option | |
| IsActive: bool | |
| } | |
| type RenameStandardResourceCmd = | |
| { | |
| Id: StandardResourceId | |
| NewName: string | |
| } | |
| type DeactivateStandardResourceCmd = | |
| { | |
| Id: StandardResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type DefineStandardResourcePeriodCmd = | |
| { | |
| Id: string | |
| StandardResourceId: StandardResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| EfficiencyFactor: float | |
| } | |
| type StandardResourceCommand = | |
| | DefineStandardResource of DefineStandardResourceCmd | |
| | RenameStandardResource of RenameStandardResourceCmd | |
| | DeactivateStandardResource of DeactivateStandardResourceCmd | |
| | DefineStandardResourcePeriod of DefineStandardResourcePeriodCmd | |
| // Events | |
| type StandardResourceDefinedEvt = | |
| { | |
| Id: StandardResourceId | |
| ResourceGroupId: ResourceGroupId | |
| Name: string | |
| Description: string option | |
| IsActive: bool | |
| Created: DateTimeOffset | |
| } | |
| type StandardResourceRenamedEvt = | |
| { | |
| Id: StandardResourceId | |
| NewName: string | |
| Modified: DateTimeOffset | |
| } | |
| type StandardResourceDeactivatedEvt = | |
| { | |
| Id: StandardResourceId | |
| DeactivatedAt: DateTimeOffset | |
| } | |
| type StandardResourcePeriodDefinedEvt = | |
| { | |
| Id: StandardResourcePeriodId | |
| StandardResourceId: StandardResourceId | |
| StartTime: DateTimeOffset | |
| EndTime: DateTimeOffset | |
| Capacity: CapacityBreakdown | |
| EfficiencyFactor: float | |
| Created: DateTimeOffset | |
| } | |
| type StandardResourceEvent = | |
| | StandardResourceDefined of StandardResourceDefinedEvt | |
| | StandardResourceRenamed of StandardResourceRenamedEvt | |
| | StandardResourceDeactivated of StandardResourceDeactivatedEvt | |
| | StandardResourcePeriodDefined of StandardResourcePeriodDefinedEvt | |
| // Signatures | |
| type DecideStandardResource = | |
| StandardResource option -> StandardResourceCommand -> Result<StandardResourceEvent list, DomainError> | |
| type EvolveStandardResource = Medhavi.Domain.Evolve<StandardResource, StandardResourceEvent> | |
| // Validation functions (includes business rules) | |
| let validateDefine (cmd: DefineStandardResourceCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = required "StandardResource Id" cmd.Id | |
| let! _ = required "StandardResource Name" cmd.Name | |
| return () | |
| } | |
| let validateRename (cmd: RenameStandardResourceCmd) : Result<unit, DomainError> = | |
| result { | |
| let! _ = | |
| if String.IsNullOrWhiteSpace cmd.NewName then | |
| Error(DomainError.validation "Standard resource name cannot be empty") | |
| else | |
| Ok() | |
| return () | |
| } | |
| let validateDeactivate (_cmd: DeactivateStandardResourceCmd) : Result<unit, DomainError> = | |
| // Deactivation is always allowed | |
| Ok() | |
| // State evolution functions (pure state transitions) | |
| let applyDefined (evt: StandardResourceDefinedEvt) : StandardResource = | |
| { | |
| Id = evt.Id | |
| ResourceGroupId = evt.ResourceGroupId | |
| Name = evt.Name | |
| Description = evt.Description | |
| PhysicalResources = [] | |
| CampaignTypes = [] | |
| IsActive = evt.IsActive | |
| Created = evt.Created | |
| Modified = evt.Created | |
| } | |
| let applyRenamed (evt: StandardResourceRenamedEvt) (state: StandardResource) : StandardResource = | |
| { state with | |
| Name = evt.NewName | |
| Modified = evt.Modified | |
| } | |
| let applyDeactivated (evt: StandardResourceDeactivatedEvt) (state: StandardResource) : StandardResource = | |
| { state with | |
| IsActive = false | |
| Modified = evt.DeactivatedAt | |
| } | |
| let evolve (state: StandardResource option) (event: StandardResourceEvent) : StandardResource option = | |
| match event, state with | |
| | StandardResourceDefined e, None -> Some(applyDefined e) | |
| | StandardResourceRenamed e, Some s -> Some(applyRenamed e s) | |
| | StandardResourceDeactivated e, Some s -> Some(applyDeactivated e s) | |
| | StandardResourceDefined _, Some _ -> state // Idempotent - standard resource already exists | |
| | _, None -> None // Can't apply updates to non-existent standard resource | |
| | _ -> state // Other events not handled |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Medhavi.Domain.Capacity.Telemetry | |
| open System | |
| open Medhavi.Domain.TelemetryContracts | |
| open Medhavi.Domain | |
| open Medhavi.Domain.Ids | |
| let utilization (telemetry: TelemetryProvider) (resource: string) (used: TimeSpan) (available: TimeSpan) = | |
| telemetry.RecordKpi( | |
| KpiEvent.CapacityUtilization(resource, int used.TotalMilliseconds, int available.TotalMilliseconds) | |
| ) | |
| let allocationLatency (telemetry: TelemetryProvider) (stepId: string) (latency: TimeSpan) = | |
| telemetry.RecordKpi(KpiEvent.CapacityAllocationLatency(stepId, int latency.TotalMilliseconds)) | |
| let bottleneckDetected (telemetry: TelemetryProvider) (resource: string) (stepId: string) = | |
| telemetry.RecordKpi(KpiEvent.CapacityBottleneckDetected(resource, stepId)) | |
| let churn (telemetry: TelemetryProvider) (cycleId: string) (allocationsChanged: int) = | |
| telemetry.RecordKpi(KpiEvent.CapacityChurn(cycleId, allocationsChanged)) | |
| let lockAdherence (telemetry: TelemetryProvider) (resource: string) (reserved: TimeSpan) (used: TimeSpan) = | |
| telemetry.RecordKpi( | |
| KpiEvent.CapacityLockAdherence(resource, int reserved.TotalMilliseconds, int used.TotalMilliseconds) | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment