"""接口 4:从表 4 取未推行,组装文档报文。 同一非空 contractNo 必须在同一批 data 里。一批最多 500 条。 空 contractNo(失败行)按行独立。不改 push_status,不发 HTTP。 """ from __future__ import annotations from collections import OrderedDict MAX_BATCH = 500 def load_contract_agreement_envelope(repo, limit: int = MAX_BATCH) -> dict: return build_contract_agreement_envelope(repo.list_unpushed(), limit=limit) def build_contract_agreement_envelope(rows: list[dict], limit: int = MAX_BATCH) -> dict: chosen = _take_complete_groups(rows, limit) return {"data": [_to_item(row) for row in chosen]} def _group_key(row: dict): number = row.get("contract_no") if number is None or str(number).strip() == "": return ("fid", row["fid"]) return ("no", str(number).strip()) def _take_complete_groups(rows: list[dict], limit: int) -> list[dict]: groups: OrderedDict = OrderedDict() for row in rows: groups.setdefault(_group_key(row), []).append(row) batch: list[dict] = [] for group in groups.values(): if len(batch) + len(group) > limit and batch: break batch.extend(group) return batch def _to_item(row: dict) -> dict: return { "fid": _as_int(row["fid"]), "omsId": _as_int(row["oms_id"]), "actionCode": _as_int(row["action_code"]), "contractStatus": _as_int(row.get("contract_status")), "contractFailReason": _as_str(row.get("contract_fail_reason")), "contractNo": _as_str(row.get("contract_no")), "agreementStatus": _as_int(row.get("agreement_status")), "agreementFailReason": _as_str(row.get("agreement_fail_reason")), "agreementId": _as_str(row.get("agreement_id")), "agreementDtlId": _as_str(row.get("agreement_dtl_id")), "orgCode": _as_str(row.get("org_code")), } def _as_int(value): if value is None: return None return int(value) def _as_str(value): if value is None: return None text = str(value) if text.strip() == "": return None return text