(offer, nmi)
| 139 | |
| 140 | |
| 141 | def normalize_offer(offer, nmi) -> tuple[tuple[int, int, int] | None, str | None]: |
| 142 | if offer is None: |
| 143 | return None, None |
| 144 | if not isinstance(offer, (list, tuple)) or len(offer) != 3: |
| 145 | return None, "offer must be a 3-item list: [quantity, time, unit_price]" |
| 146 | issues = getattr(nmi, "issues", None) |
| 147 | if issues is None or len(issues) < 3: |
| 148 | return None, "missing SCML issue ranges" |
| 149 | normalized = [] |
| 150 | for idx, raw_value in enumerate(offer): |
| 151 | if isinstance(raw_value, np.generic): |
| 152 | raw_value = raw_value.item() |
| 153 | if isinstance(raw_value, bool): |
| 154 | return None, f"offer item {idx} must be an integer" |
| 155 | if isinstance(raw_value, float) and raw_value.is_integer(): |
| 156 | raw_value = int(raw_value) |
| 157 | if not isinstance(raw_value, int): |
| 158 | return None, f"offer item {idx} must be an integer" |
| 159 | issue = issues[idx] |
| 160 | min_value = getattr(issue, "min_value", None) |
| 161 | max_value = getattr(issue, "max_value", None) |
| 162 | if min_value is not None and raw_value < int(min_value): |
| 163 | return None, f"offer item {idx} below minimum {int(min_value)}" |
| 164 | if max_value is not None and raw_value > int(max_value): |
| 165 | return None, f"offer item {idx} above maximum {int(max_value)}" |
| 166 | normalized.append(int(raw_value)) |
| 167 | return (normalized[QUANTITY], normalized[TIME], normalized[UNIT_PRICE]), None |
| 168 | |
| 169 | |
| 170 | def normalize_response(response) -> tuple[ResponseType | None, str | None]: |
no outgoing calls
no test coverage detected