diff --git a/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs
new file mode 100644
index 0000000..873126d
--- /dev/null
+++ b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs
@@ -0,0 +1,55 @@
+using Data.SeaHavenIndustries;
+using Data.SeaHavenIndustries.Enums;
+
+namespace SeaHaven.Services.Helpers
+{
+ public static class WorkOrderDerivedFields
+ {
+ ///
+ /// Past due overlay — never a WorkOrderType. Matches SHOC board design.
+ ///
+ public static bool IsPastDue(WorkOrder workOrder, DateOnly? asOf = null)
+ {
+ var today = asOf ?? DateOnly.FromDateTime(DateTime.UtcNow);
+ if (workOrder.ScheduledDate == null)
+ return false;
+
+ if (LifecycleStatusMapper.IsTerminal(workOrder.LifecycleStatus))
+ return false;
+
+ return DateOnly.FromDateTime(workOrder.ScheduledDate.Value.Date) < today;
+ }
+
+ ///
+ /// Sets initial lifecycle on create. New WOs start Incomplete unless auto-scheduled.
+ ///
+ public static void SetInitialLifecycleStatus(WorkOrder workOrder)
+ {
+ workOrder.LifecycleStatus ??= LifecycleStatus.Incomplete;
+ ApplyAutoScheduleIfEligible(workOrder);
+ }
+
+ ///
+ /// SHOC rule: specific scheduled date + assignee + Incomplete → Scheduled.
+ /// Week-only targets do not auto-schedule.
+ ///
+ public static bool ApplyAutoScheduleIfEligible(WorkOrder workOrder)
+ {
+ if (workOrder.LifecycleStatus != LifecycleStatus.Incomplete)
+ return false;
+
+ if (workOrder.ScheduleWeekOnly == true)
+ return false;
+
+ if (workOrder.ScheduledDate == null)
+ return false;
+
+ if (string.IsNullOrWhiteSpace(workOrder.AssignTo))
+ return false;
+
+ workOrder.LifecycleStatus = LifecycleStatus.Scheduled;
+ return true;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/SeaHaven.Services/Helpers/WorkOrderTypeMapper.cs b/SeaHaven.Services/Helpers/WorkOrderTypeMapper.cs
new file mode 100644
index 0000000..64c355c
--- /dev/null
+++ b/SeaHaven.Services/Helpers/WorkOrderTypeMapper.cs
@@ -0,0 +1,48 @@
+using Data.SeaHavenIndustries.Enums;
+
+namespace SeaHaven.Services.Helpers
+{
+ public static class WorkOrderTypeMapper
+ {
+ /// Board FE types. Overdue is not a type — use .
+ public static bool TryParseFeLabel(string? label, out WorkOrderType type)
+ {
+ type = default;
+ if (string.IsNullOrWhiteSpace(label)) return false;
+
+ return label.Trim().ToLowerInvariant() switch
+ {
+ "pm" => Set(WorkOrderType.PM, out type),
+ "reactive" => Set(WorkOrderType.Reactive, out type),
+ "emergency" => Set(WorkOrderType.Emergency, out type),
+ "add-on" or "addon" => Set(WorkOrderType.AddOn, out type),
+ "po" => Set(WorkOrderType.PO, out type),
+ "project" => Set(WorkOrderType.Project, out type),
+ "inspection" => Set(WorkOrderType.Inspection, out type),
+ "other" => Set(WorkOrderType.Other, out type),
+ "overdue" => false,
+ _ => Enum.TryParse(label, true, out type)
+ };
+ }
+
+ public static string ToFeLabel(WorkOrderType type) => type switch
+ {
+ WorkOrderType.PM => "PM",
+ WorkOrderType.Reactive => "Reactive",
+ WorkOrderType.Emergency => "Emergency",
+ WorkOrderType.AddOn => "Add-On",
+ WorkOrderType.PO => "PO",
+ WorkOrderType.Project => "Project",
+ WorkOrderType.Inspection => "Inspection",
+ WorkOrderType.Other => "Other",
+ _ => type.ToString()
+ };
+
+ private static bool Set(WorkOrderType value, out WorkOrderType type)
+ {
+ type = value;
+ return true;
+ }
+ }
+}
+
\ No newline at end of file