match_plan.mojo (4812B)
1 from std.collections import List 2 3 from hyf_core.domain.plan import ( 4 Allocation, 5 LotCapacity, 6 MatchPlan, 7 allocation, 8 match_plan, 9 plan_conservation_violations, 10 ) 11 12 13 def group_lots_by_supplier( 14 lot_ids: List[String], supplier_ids: List[String] 15 ) raises -> List[String]: 16 if len(lot_ids) != len(supplier_ids): 17 raise Error("lot ids and supplier ids must align") 18 var suppliers = List[String]() 19 for supplier in supplier_ids: 20 if String(supplier).strip().byte_length() == 0: 21 raise Error("supplier id must not be empty") 22 var seen = False 23 for existing in suppliers: 24 if existing == supplier: 25 seen = True 26 if not seen: 27 suppliers.append(String(supplier)) 28 return suppliers^ 29 30 31 def allocate_single_line( 32 plan_id: String, 33 line_id: String, 34 supplier_id: String, 35 required_value: Int, 36 required_scale: Int, 37 lots: List[LotCapacity], 38 ) raises -> MatchPlan: 39 var remaining = required_value 40 var allocations = List[Allocation]() 41 for lot in lots: 42 if remaining <= 0: 43 break 44 var capacity = lot.value 45 var take = remaining if remaining < capacity else capacity 46 allocations.append( 47 allocation(lot.lot_id, lot.revision, line_id, take, lot.scale) 48 ) 49 remaining -= take 50 return match_plan(plan_id, supplier_id, allocations) 51 52 53 def multi_supplier_is_supported() -> Bool: 54 return False 55 56 57 @fieldwise_init 58 struct PlannerBounds(Copyable, Movable): 59 var max_candidates: Int 60 var max_plans: Int 61 62 63 def planner_bounds(max_candidates: Int, max_plans: Int) raises -> PlannerBounds: 64 if max_candidates <= 0 or max_plans <= 0: 65 raise Error("planner bounds must be positive") 66 return PlannerBounds(max_candidates=max_candidates, max_plans=max_plans) 67 68 69 def enforce_plan_bound(plan_count: Int, bounds: PlannerBounds) raises: 70 if plan_count > bounds.max_plans: 71 raise Error("plan enumeration exceeds the bound") 72 73 74 def allocate_multiple_lines( 75 plan_id: String, 76 supplier_id: String, 77 line_ids: List[String], 78 required_values: List[Int], 79 lots: List[LotCapacity], 80 ) raises -> MatchPlan: 81 if len(line_ids) != len(required_values): 82 raise Error("line ids and requirements must align") 83 var remaining = List[Int]() 84 for lot in lots: 85 remaining.append(lot.value) 86 var allocations = List[Allocation]() 87 for index in range(len(line_ids)): 88 var needed = required_values[index] 89 for lot_index in range(len(lots)): 90 if needed <= 0: 91 break 92 if remaining[lot_index] <= 0: 93 continue 94 var capacity = remaining[lot_index] 95 var take = needed if needed < capacity else capacity 96 allocations.append( 97 allocation( 98 lots[lot_index].lot_id, 99 lots[lot_index].revision, 100 line_ids[index], 101 take, 102 lots[lot_index].scale, 103 ) 104 ) 105 remaining[lot_index] -= take 106 needed -= take 107 return match_plan(plan_id, supplier_id, allocations) 108 109 110 def shared_lot_allows_concurrent_over_allocation() -> Bool: 111 return False 112 113 114 @fieldwise_init 115 struct PartialOutcome(Copyable, Movable): 116 var fulfilled: Bool 117 var uncovered_value: Int 118 var permitted: Bool 119 120 121 def partial_outcome( 122 required_value: Int, allocated_value: Int, partial_allowed: Bool 123 ) raises -> PartialOutcome: 124 if required_value < 0 or allocated_value < 0: 125 raise Error("partial outcome requires non-negative quantities") 126 if allocated_value >= required_value: 127 return PartialOutcome(fulfilled=True, uncovered_value=0, permitted=True) 128 var uncovered = required_value - allocated_value 129 return PartialOutcome( 130 fulfilled=False, uncovered_value=uncovered, permitted=partial_allowed 131 ) 132 133 134 def partial_discloses_deficit() -> Bool: 135 return True 136 137 138 @fieldwise_init 139 struct PlannerOffer(Copyable, Movable): 140 var plans: List[MatchPlan] 141 var supported_mode: String 142 var unsupported: List[String] 143 var alternatives_are_simultaneous: Bool 144 145 146 def alternative_plans( 147 plans: List[MatchPlan], unsupported: List[String] 148 ) raises -> PlannerOffer: 149 var copied = List[MatchPlan]() 150 for plan in plans: 151 copied.append(plan.copy()) 152 var unsupported_copied = List[String]() 153 for entry in unsupported: 154 unsupported_copied.append(String(entry)) 155 return PlannerOffer( 156 plans=copied^, 157 supported_mode="single_supplier_compatible_lots", 158 unsupported=unsupported_copied^, 159 alternatives_are_simultaneous=False, 160 ) 161 162 163 def unsupported_mode_means_no_supply() -> Bool: 164 return False