|
8 | 8 |
|
9 | 9 | from holiday_peak_lib.adapters.acp_mapper import AcpCatalogMapper |
10 | 10 | from holiday_peak_lib.adapters.ucp_mapper import UcpProtocolMapper |
11 | | -from holiday_peak_lib.integrations import PIMWritebackManager |
| 11 | +from holiday_peak_lib.integrations import PIMWritebackManager, WritebackResult, WritebackStatus |
12 | 12 |
|
13 | 13 | from .schemas_compat import AuditAction, AuditEvent, ExportResult |
14 | 14 |
|
@@ -118,6 +118,63 @@ async def writeback_entity( |
118 | 118 | summary["pim_response_summary"] = self._build_pim_response_summary(summary) |
119 | 119 | return summary |
120 | 120 |
|
| 121 | + async def writeback_to_pim( |
| 122 | + self, |
| 123 | + manager: PIMWritebackManager, |
| 124 | + truth_store: Any, |
| 125 | + entity_id: str, |
| 126 | + *, |
| 127 | + approved_attributes: list[str] | None = None, |
| 128 | + dry_run: bool = False, |
| 129 | + ) -> dict[str, Any]: |
| 130 | + """Write back approved attributes only, preserving manager safety guards.""" |
| 131 | + if not approved_attributes: |
| 132 | + return await self.writeback_entity(manager, entity_id, dry_run=dry_run) |
| 133 | + |
| 134 | + approved_set = {field for field in approved_attributes if field} |
| 135 | + if dry_run: |
| 136 | + preview = await self.writeback_entity(manager, entity_id, dry_run=True) |
| 137 | + return self._filter_writeback_summary(preview, approved_set) |
| 138 | + |
| 139 | + raw_attributes = await truth_store.get_attributes(entity_id) |
| 140 | + selected = [ |
| 141 | + attr |
| 142 | + for attr in raw_attributes |
| 143 | + if attr.get("field") in approved_set and attr.get("writeback_eligible", False) |
| 144 | + ] |
| 145 | + |
| 146 | + if not selected: |
| 147 | + skipped_summary: dict[str, Any] = { |
| 148 | + "entity_id": entity_id, |
| 149 | + "total": 0, |
| 150 | + "succeeded": 0, |
| 151 | + "skipped": 0, |
| 152 | + "conflicts": 0, |
| 153 | + "errors": 0, |
| 154 | + "timestamp": datetime.now(timezone.utc).isoformat(), |
| 155 | + "results": [], |
| 156 | + "dry_run": False, |
| 157 | + "status": "skipped", |
| 158 | + } |
| 159 | + skipped_summary["pim_response_summary"] = self._build_pim_response_summary( |
| 160 | + skipped_summary |
| 161 | + ) |
| 162 | + return skipped_summary |
| 163 | + |
| 164 | + async def _write_one(attr: dict[str, Any]) -> WritebackResult: |
| 165 | + return await manager.writeback_attribute( |
| 166 | + entity_id, |
| 167 | + field=str(attr.get("field")), |
| 168 | + value=attr.get("value"), |
| 169 | + truth_version=attr.get("version"), |
| 170 | + ) |
| 171 | + |
| 172 | + results = list(await asyncio.gather(*[_write_one(attr) for attr in selected])) |
| 173 | + summary = self._summarize_field_results(entity_id, results, dry_run=False) |
| 174 | + summary["status"] = self._resolve_writeback_status(summary) |
| 175 | + summary["pim_response_summary"] = self._build_pim_response_summary(summary) |
| 176 | + return summary |
| 177 | + |
121 | 178 | async def writeback_batch( |
122 | 179 | self, |
123 | 180 | manager: PIMWritebackManager, |
@@ -185,3 +242,71 @@ def _build_pim_response_summary(result: dict[str, Any]) -> dict[str, Any]: |
185 | 242 | "errors": result.get("errors", 0), |
186 | 243 | "messages": messages, |
187 | 244 | } |
| 245 | + |
| 246 | + def _summarize_field_results( |
| 247 | + self, |
| 248 | + entity_id: str, |
| 249 | + field_results: list[WritebackResult], |
| 250 | + *, |
| 251 | + dry_run: bool, |
| 252 | + ) -> dict[str, Any]: |
| 253 | + succeeded = 0 |
| 254 | + skipped = 0 |
| 255 | + conflicts = 0 |
| 256 | + errors = 0 |
| 257 | + result_items: list[dict[str, Any]] = [] |
| 258 | + |
| 259 | + for item in field_results: |
| 260 | + status = item.status |
| 261 | + if status == WritebackStatus.SUCCESS: |
| 262 | + succeeded += 1 |
| 263 | + elif status in (WritebackStatus.SKIPPED, WritebackStatus.DRY_RUN): |
| 264 | + skipped += 1 |
| 265 | + elif status == WritebackStatus.CONFLICT: |
| 266 | + conflicts += 1 |
| 267 | + else: |
| 268 | + errors += 1 |
| 269 | + result_items.append(item.model_dump()) |
| 270 | + |
| 271 | + return { |
| 272 | + "entity_id": entity_id, |
| 273 | + "total": len(field_results), |
| 274 | + "succeeded": succeeded, |
| 275 | + "skipped": skipped, |
| 276 | + "conflicts": conflicts, |
| 277 | + "errors": errors, |
| 278 | + "timestamp": datetime.now(timezone.utc).isoformat(), |
| 279 | + "results": result_items, |
| 280 | + "dry_run": dry_run, |
| 281 | + } |
| 282 | + |
| 283 | + def _filter_writeback_summary( |
| 284 | + self, |
| 285 | + summary: dict[str, Any], |
| 286 | + approved_set: set[str], |
| 287 | + ) -> dict[str, Any]: |
| 288 | + filtered_results = [ |
| 289 | + result for result in summary.get("results", []) if result.get("field") in approved_set |
| 290 | + ] |
| 291 | + filtered = dict(summary) |
| 292 | + filtered["results"] = filtered_results |
| 293 | + filtered["total"] = len(filtered_results) |
| 294 | + filtered["succeeded"] = len( |
| 295 | + [r for r in filtered_results if r.get("status") == WritebackStatus.SUCCESS.value] |
| 296 | + ) |
| 297 | + filtered["skipped"] = len( |
| 298 | + [ |
| 299 | + r |
| 300 | + for r in filtered_results |
| 301 | + if r.get("status") in {WritebackStatus.SKIPPED.value, WritebackStatus.DRY_RUN.value} |
| 302 | + ] |
| 303 | + ) |
| 304 | + filtered["conflicts"] = len( |
| 305 | + [r for r in filtered_results if r.get("status") == WritebackStatus.CONFLICT.value] |
| 306 | + ) |
| 307 | + filtered["errors"] = len( |
| 308 | + [r for r in filtered_results if r.get("status") == WritebackStatus.ERROR.value] |
| 309 | + ) |
| 310 | + filtered["status"] = self._resolve_writeback_status(filtered) |
| 311 | + filtered["pim_response_summary"] = self._build_pim_response_summary(filtered) |
| 312 | + return filtered |
0 commit comments