# -*- coding: utf-8 -*- """Manual ingestion task that replays archived JSON into ODS tables.""" from __future__ import annotations import json import os from datetime import datetime from typing import Iterable, Iterator from .base_task import BaseTask class ManualIngestTask(BaseTask): """ Load archived API responses (tests/source-data-doc) into billiards_ods.* tables. Used when upstream API is unavailable and we need to replay captured payloads. """ FILE_MAPPING: list[tuple[tuple[str, ...], str]] = [ (("会员档案",), "billiards_ods.ods_member_profile"), (("储值卡列表", "储值卡"), "billiards_ods.ods_member_card"), (("充值记录",), "billiards_ods.ods_recharge_record"), (("余额变动",), "billiards_ods.ods_balance_change"), (("助教账号",), "billiards_ods.ods_assistant_account"), (("助教流水",), "billiards_ods.ods_assistant_service_log"), (("助教废除", "助教作废"), "billiards_ods.ods_assistant_cancel_log"), (("台桌列表",), "billiards_ods.ods_table_info"), (("台费流水",), "billiards_ods.ods_table_use_log"), (("台费打折",), "billiards_ods.ods_table_fee_adjust"), (("商品档案",), "billiards_ods.ods_store_product"), (("门店商品销售", "销售记录"), "billiards_ods.ods_store_sale_item"), (("团购套餐定义", "套餐定义"), "billiards_ods.ods_group_package"), (("团购套餐使用", "套餐使用"), "billiards_ods.ods_group_package_log"), (("平台验券", "验券记录"), "billiards_ods.ods_platform_coupon_log"), (("库存汇总",), "billiards_ods.ods_inventory_stock"), (("库存变化记录1",), "billiards_ods.ods_inventory_change"), (("库存变化记录2", "分类配置"), "billiards_ods.ods_goods_category"), (("结账记录",), "billiards_ods.ods_order_settle"), (("小票详情", "小票明细", "票详"), "billiards_ods.ods_order_receipt_detail"), (("支付记录",), "billiards_ods.ods_payment_record"), (("退款记录",), "billiards_ods.ods_refund_record"), ] WRAPPER_META_KEYS = {"code", "message", "msg", "success", "error", "status"} def get_task_code(self) -> str: return "MANUAL_INGEST" def execute(self) -> dict: self.logger.info("Starting Manual Ingest Task") data_dir = self.config.get( "manual.data_dir", r"c:\dev\LLTQ\ETL\feiqiu-ETL\etl_billiards\tests\testdata_json", ) if not os.path.exists(data_dir): self.logger.error("Data directory not found: %s", data_dir) return {"status": "error", "message": "Directory not found"} counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0} for filename in sorted(os.listdir(data_dir)): if not filename.endswith(".json"): continue filepath = os.path.join(data_dir, filename) try: with open(filepath, "r", encoding="utf-8") as fh: raw_entries = json.load(fh) except Exception: counts["errors"] += 1 self.logger.exception("Failed to read %s", filename) continue if not isinstance(raw_entries, list): raw_entries = [raw_entries] records = self._normalize_records(raw_entries) if not records: counts["skipped"] += 1 continue target_table = self._match_by_filename(filename) or self._match_by_content( records, raw_entries ) if not target_table: self.logger.warning("No mapping found for file: %s", filename) counts["skipped"] += 1 continue self.logger.info("Ingesting %s into %s", filename, target_table) try: rows = [] for record in records: site_id = self._extract_store_id(record) or self.config.get( "app.store_id" ) pk_value = self._extract_pk(record, target_table) pk_tuple = self._ensure_tuple(pk_value) if not all(value not in (None, "") for value in pk_tuple): continue row = { "site_id": site_id, "payload": json.dumps(record, ensure_ascii=False), "source_file": filename, "fetched_at": datetime.now(), } for column, value in zip( self._get_conflict_columns(target_table), pk_tuple ): row[column] = value self._enrich_row(row, record, target_table) rows.append(row) if rows: self._bulk_insert(target_table, rows) counts["inserted"] += len(rows) else: counts["skipped"] += 1 counts["fetched"] += 1 except Exception: counts["errors"] += 1 self.logger.exception("Error processing %s", filename) self.db.rollback() try: self.db.commit() except Exception: self.db.rollback() raise return self._build_result("SUCCESS", counts) # ------------------------------------------------------------------ helpers def _match_by_filename(self, filename: str) -> str | None: for keywords, table in self.FILE_MAPPING: if any(keyword and keyword in filename for keyword in keywords): return table return None def _match_by_content( self, records: list[dict], raw_entries: list[dict] ) -> str | None: """ Map content to PRD ODS tables. """ sample_record = records[0] if records else None wrapper = self._extract_sample(raw_entries) data_node = wrapper.get("data") if isinstance(wrapper, dict) else None data_keys = set(data_node.keys()) if isinstance(data_node, dict) else set() record_keys = set(sample_record.keys()) if isinstance(sample_record, dict) else set() # Data node based hints if "tenantMemberInfos" in data_keys: return "billiards_ods.ods_member_profile" if "tenantMemberCards" in data_keys: return "billiards_ods.ods_member_card" if "queryDeliveryRecordsList" in data_keys: return "billiards_ods.ods_inventory_change" if "goodsStockA" in data_keys or "rangeStartStock" in data_keys: return "billiards_ods.ods_inventory_stock" if "goodsCategoryList" in data_keys: return "billiards_ods.ods_goods_category" if "orderAssistantDetails" in data_keys: return "billiards_ods.ods_assistant_service_log" if "abolitionAssistants" in data_keys: return "billiards_ods.ods_assistant_cancel_log" if "siteTableUseDetailsList" in data_keys: return "billiards_ods.ods_table_use_log" if "taiFeeAdjustInfos" in data_keys: return "billiards_ods.ods_table_fee_adjust" if "orderGoodsLedgers" in data_keys or "orderGoodsList" in data_keys: return "billiards_ods.ods_store_sale_item" if "tenantGoodsList" in data_keys: return "billiards_ods.ods_store_product" if "packageCouponList" in data_keys: return "billiards_ods.ods_group_package" if "settleList" in data_keys and "total" in data_keys: return "billiards_ods.ods_order_settle" # Record key based hints if sample_record: if {"pay_amount", "pay_status"} <= record_keys or {"payAmount", "payStatus"} <= record_keys: return "billiards_ods.ods_payment_record" if "refundAmount" in record_keys or "refund_amount" in record_keys: return "billiards_ods.ods_refund_record" if "orderSettleId" in record_keys or "order_settle_id" in record_keys: return "billiards_ods.ods_order_receipt_detail" if "coupon_channel" in record_keys or "groupPackageId" in record_keys: return "billiards_ods.ods_platform_coupon_log" if "packageId" in record_keys or "package_id" in record_keys: return "billiards_ods.ods_group_package_log" if "memberCardId" in record_keys or "cardId" in record_keys: return "billiards_ods.ods_member_card" if "memberId" in record_keys: return "billiards_ods.ods_member_profile" if "siteGoodsId" in record_keys and "currentStock" in record_keys: return "billiards_ods.ods_inventory_stock" if "goodsId" in record_keys: return "billiards_ods.ods_product" return None def _extract_sample(self, payloads: Iterable[dict]) -> dict: for item in payloads: if isinstance(item, dict): return item return {} def _normalize_records(self, payloads: list[dict]) -> list[dict]: records: list[dict] = [] for payload in payloads: records.extend(self._unwrap_payload(payload)) return records def _unwrap_payload(self, payload) -> list[dict]: if isinstance(payload, dict): data_node = payload.get("data") extra_keys = set(payload.keys()) - {"data"} - self.WRAPPER_META_KEYS if isinstance(data_node, dict) and not extra_keys: flattened: list[dict] = [] found_list = False for value in data_node.values(): if isinstance(value, list): flattened.extend(value) found_list = True if found_list: return flattened return [data_node] return [payload] if isinstance(payload, list): flattened: list[dict] = [] for item in payload: flattened.extend(self._unwrap_payload(item)) return flattened return [] def _extract_store_id(self, item: dict): """Extract site_id from record/siteProfile wrappers.""" site_profile = item.get("siteProfile") or item.get("site_profile") if isinstance(site_profile, dict) and site_profile.get("id"): return site_profile["id"] for key in ("site_id", "siteId", "register_site_id"): if item.get(key): return item[key] data_node = item.get("data") if isinstance(data_node, dict): return data_node.get("siteId") or data_node.get("site_id") return None def _extract_pk(self, item: dict, table: str): if "ods_order_receipt_detail" in table: return item.get("orderSettleId") or item.get("order_settle_id") or item.get("id") if "ods_order_settle" in table: settle = item.get("settleList") or item.get("settle") or item if isinstance(settle, dict): return settle.get("id") or settle.get("settleId") or item.get("id") return item.get("id") if "ods_payment_record" in table: return item.get("payId") or item.get("id") if "ods_refund_record" in table: return item.get("refundId") or item.get("id") if "ods_platform_coupon_log" in table: return item.get("couponId") or item.get("id") if "ods_assistant_service_log" in table or "ods_table_use_log" in table: return item.get("ledgerId") or item.get("ledger_id") or item.get("id") if "ods_assistant_cancel_log" in table: return item.get("cancel_id") or item.get("cancelId") or item.get("abolishId") or item.get("id") if "ods_store_sale_item" in table: return ( item.get("sale_item_id") or item.get("saleItemId") or item.get("orderGoodsId") or item.get("order_goods_id") or item.get("id") ) if "ods_inventory_change" in table: return item.get("siteGoodsStockId") or item.get("id") if "ods_inventory_stock" in table: return ( item.get("siteGoodsId") or item.get("id"), item.get("snapshotKey") or item.get("snapshot_key") or "default", ) if "ods_member_card" in table: return item.get("cardId") or item.get("memberCardId") or item.get("id") if "ods_member_profile" in table: return item.get("memberId") or item.get("id") if "ods_group_package_log" in table: return item.get("usage_id") or item.get("usageId") or item.get("couponId") or item.get("id") if "ods_group_package" in table: return item.get("package_id") or item.get("packageId") or item.get("groupPackageId") or item.get("id") if "ods_goods_category" in table: return item.get("category_id") or item.get("categoryId") or item.get("id") if "ods_table_fee_adjust" in table: return item.get("adjust_id") or item.get("adjustId") or item.get("id") if "ods_table_info" in table: return item.get("table_id") or item.get("tableId") or item.get("id") if "ods_assistant_account" in table: return item.get("assistantId") or item.get("assistant_id") or item.get("id") if "ods_store_product" in table: return item.get("siteGoodsId") or item.get("site_goods_id") or item.get("id") if "ods_product" in table: return item.get("goodsId") or item.get("goods_id") or item.get("id") if "ods_balance_change" in table: return item.get("change_id") or item.get("changeId") or item.get("id") if "ods_recharge_record" in table: return item.get("recharge_id") or item.get("rechargeId") or item.get("id") return item.get("id") def _get_conflict_columns(self, table: str) -> list[str]: if "ods_order_receipt_detail" in table: return ["order_settle_id"] if "ods_payment_record" in table: return ["pay_id"] if "ods_refund_record" in table: return ["refund_id"] if "ods_platform_coupon_log" in table: return ["coupon_id"] if "ods_assistant_service_log" in table or "ods_table_use_log" in table: return ["ledger_id"] if "ods_assistant_cancel_log" in table: return ["cancel_id"] if "ods_store_sale_item" in table: return ["sale_item_id"] if "ods_order_settle" in table: return ["order_settle_id"] if "ods_inventory_change" in table: return ["change_id"] if "ods_inventory_stock" in table: return ["site_goods_id", "snapshot_key"] if "ods_member_card" in table: return ["card_id"] if "ods_member_profile" in table: return ["member_id"] if "ods_group_package_log" in table: return ["usage_id"] if "ods_group_package" in table: return ["package_id"] if "ods_goods_category" in table: return ["category_id"] if "ods_table_info" in table: return ["table_id"] if "ods_table_fee_adjust" in table: return ["adjust_id"] if "ods_assistant_account" in table: return ["assistant_id"] if "ods_store_product" in table: return ["site_goods_id"] if "ods_product" in table: return ["goods_id"] if "ods_balance_change" in table: return ["change_id"] if "ods_recharge_record" in table: return ["recharge_id"] return ["id"] def _enrich_row(self, row: dict, record: dict, table: str): """Best-effort populate important columns from payload for PRD ODS schema.""" def pick(obj, *keys): for k in keys: if isinstance(obj, dict) and obj.get(k) not in (None, ""): return obj.get(k) return None if "ods_member_profile" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["member_name"] = pick(record, "name", "memberName") row["nickname"] = record.get("nickname") row["mobile"] = record.get("mobile") row["gender"] = record.get("sex") row["birthday"] = record.get("birthday") row["register_time"] = record.get("register_time") or record.get("registerTime") row["member_type_id"] = pick(record, "cardTypeId", "member_type_id") row["member_type_name"] = record.get("cardTypeName") row["status"] = pick(record, "status", "state") row["balance"] = record.get("balance") row["points"] = record.get("points") or record.get("point") row["last_visit_time"] = record.get("lastVisitTime") row["wechat_id"] = record.get("wechatId") row["alipay_id"] = record.get("alipayId") row["member_card_no"] = record.get("cardNo") row["remarks"] = record.get("remark") if "ods_member_card" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["member_id"] = pick(record, "memberId", "member_id") row["card_type_id"] = record.get("cardTypeId") row["card_type_name"] = record.get("cardTypeName") row["card_balance"] = record.get("balance") row["discount_rate"] = record.get("discount") or record.get("discount_rate") row["valid_start_date"] = record.get("validStart") row["valid_end_date"] = record.get("validEnd") row["last_consume_time"] = record.get("lastConsumeTime") row["status"] = record.get("status") row["activate_time"] = record.get("activateTime") row["deactivate_time"] = record.get("cancelTime") row["issuer_id"] = record.get("issuerId") row["issuer_name"] = record.get("issuerName") if "ods_recharge_record" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["member_id"] = pick(record, "memberId", "member_id") row["recharge_amount"] = record.get("amount") or record.get("rechargeAmount") row["gift_amount"] = record.get("giftAmount") row["pay_method"] = record.get("payType") or record.get("pay_method") row["pay_trade_no"] = record.get("payTradeNo") row["order_trade_no"] = record.get("orderTradeNo") row["recharge_time"] = record.get("createTime") or record.get("rechargeTime") row["status"] = record.get("status") row["operator_id"] = record.get("operatorId") row["operator_name"] = record.get("operatorName") if "ods_balance_change" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["site_id"] = row.get("site_id") or pick(record, "siteId", "site_id") row["member_id"] = pick(record, "memberId", "member_id") row["change_amount"] = record.get("change_amount") row["balance_before"] = record.get("before_balance") row["balance_after"] = record.get("after_balance") row["change_type"] = record.get("from_type") or record.get("type") row["relate_id"] = record.get("relate_id") row["pay_method"] = record.get("pay_type") row["remark"] = record.get("remark") row["operator_id"] = record.get("operatorId") row["operator_name"] = record.get("operatorName") row["change_time"] = record.get("create_time") or record.get("changeTime") row["is_deleted"] = record.get("is_delete") or record.get("is_deleted") row["source_file"] = row.get("source_file") row["fetched_at"] = row.get("fetched_at") if "ods_assistant_account" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["assistant_name"] = record.get("assistantName") or record.get("name") row["mobile"] = record.get("mobile") row["team_id"] = record.get("teamId") row["team_name"] = record.get("teamName") row["status"] = record.get("status") row["hired_date"] = record.get("hireDate") row["left_date"] = record.get("leaveDate") if "ods_assistant_service_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["assistant_id"] = record.get("assistantId") row["service_type"] = record.get("serviceType") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["start_time"] = record.get("startTime") row["end_time"] = record.get("endTime") row["duration_minutes"] = record.get("duration") row["original_fee"] = record.get("originFee") or record.get("original_fee") row["discount_amount"] = record.get("discountAmount") row["final_fee"] = record.get("finalFee") or record.get("final_fee") row["member_id"] = record.get("memberId") row["status"] = record.get("status") if "ods_assistant_cancel_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["ledger_id"] = record.get("ledgerId") row["assistant_id"] = record.get("assistantId") row["order_trade_no"] = record.get("orderTradeNo") row["reason"] = record.get("reason") row["cancel_time"] = record.get("cancel_time") or record.get("cancelTime") row["operator_id"] = record.get("operatorId") row["operator_name"] = record.get("operatorName") if "ods_table_info" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["table_code"] = record.get("tableCode") row["table_name"] = record.get("tableName") row["table_type"] = record.get("tableType") row["area_name"] = record.get("areaName") row["status"] = record.get("status") row["created_time"] = record.get("createTime") row["updated_time"] = record.get("updateTime") if "ods_table_use_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["table_id"] = record.get("tableId") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["start_time"] = record.get("startTime") row["end_time"] = record.get("endTime") row["duration_minutes"] = record.get("duration") row["original_table_fee"] = record.get("originFee") or record.get("original_table_fee") row["discount_amount"] = record.get("discountAmount") row["final_table_fee"] = record.get("finalFee") or record.get("final_table_fee") row["member_id"] = record.get("memberId") row["status"] = record.get("status") if "ods_table_fee_adjust" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["ledger_id"] = record.get("ledgerId") row["order_trade_no"] = record.get("orderTradeNo") row["discount_amount"] = record.get("discountAmount") row["reason"] = record.get("reason") row["operator_id"] = record.get("operatorId") row["operator_name"] = record.get("operatorName") row["created_at"] = record.get("created_at") or record.get("createTime") if "ods_store_product" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["goods_id"] = record.get("goodsId") row["goods_name"] = record.get("goodsName") row["category_id"] = record.get("categoryId") row["category_name"] = record.get("categoryName") row["sale_price"] = record.get("salePrice") row["cost_price"] = record.get("costPrice") row["status"] = record.get("status") if "ods_store_sale_item" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["goods_id"] = record.get("goodsId") row["goods_name"] = record.get("goodsName") row["category_id"] = record.get("categoryId") row["quantity"] = record.get("quantity") row["original_amount"] = record.get("originalAmount") row["discount_amount"] = record.get("discountAmount") row["final_amount"] = record.get("finalAmount") row["is_gift"] = record.get("isGift") row["sale_time"] = record.get("saleTime") if "ods_group_package_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["package_id"] = record.get("packageId") row["coupon_id"] = record.get("couponId") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["member_id"] = record.get("memberId") row["status"] = record.get("status") row["used_time"] = record.get("usedTime") row["deduct_amount"] = record.get("deductAmount") row["settle_price"] = record.get("settlePrice") if "ods_group_package" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["package_name"] = record.get("packageName") row["platform_code"] = record.get("platformCode") row["status"] = record.get("status") row["face_price"] = record.get("facePrice") row["settle_price"] = record.get("settlePrice") row["valid_from"] = record.get("validFrom") row["valid_to"] = record.get("validTo") if "ods_platform_coupon_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["platform_code"] = record.get("platformCode") row["verify_code"] = record.get("verifyCode") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["member_id"] = record.get("memberId") row["status"] = record.get("status") row["used_time"] = record.get("usedTime") row["deduct_amount"] = record.get("deductAmount") row["settle_price"] = record.get("settlePrice") if "ods_payment_record" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["member_id"] = record.get("memberId") row["pay_method_code"] = record.get("payMethodCode") or record.get("pay_type") row["pay_method_name"] = record.get("payMethodName") row["pay_amount"] = record.get("payAmount") row["pay_time"] = record.get("payTime") row["relate_type"] = record.get("relateType") row["relate_id"] = record.get("relateId") if "ods_refund_record" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["order_trade_no"] = record.get("orderTradeNo") row["order_settle_id"] = record.get("orderSettleId") row["member_id"] = record.get("memberId") row["pay_method_code"] = record.get("payMethodCode") row["refund_amount"] = record.get("refundAmount") row["refund_time"] = record.get("refundTime") row["status"] = record.get("status") if "ods_inventory_change" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["site_goods_id"] = record.get("siteGoodsId") row["goods_id"] = record.get("goodsId") row["change_amount"] = record.get("changeAmount") row["before_stock"] = record.get("beforeStock") row["after_stock"] = record.get("afterStock") row["change_type"] = record.get("changeType") row["relate_id"] = record.get("relateId") row["remark"] = record.get("remark") row["operator_id"] = record.get("operatorId") row["operator_name"] = record.get("operatorName") row["change_time"] = record.get("changeTime") if "ods_inventory_stock" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["goods_id"] = record.get("goodsId") row["current_stock"] = record.get("currentStock") row["cost_price"] = record.get("costPrice") if "ods_goods_category" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["category_name"] = record.get("categoryName") row["parent_id"] = record.get("parentId") row["level_no"] = record.get("levelNo") row["status"] = record.get("status") row["remark"] = record.get("remark") if "ods_order_receipt_detail" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["order_trade_no"] = record.get("orderTradeNo") row["receipt_no"] = record.get("receiptNo") row["receipt_time"] = record.get("receiptTime") row["total_amount"] = record.get("totalAmount") row["discount_amount"] = record.get("discountAmount") row["final_amount"] = record.get("finalAmount") row["member_id"] = record.get("memberId") row["snapshot_raw"] = record.get("siteProfile") or record.get("site_profile") if "ods_order_settle" in table: settle = record.get("settleList") if isinstance(record.get("settleList"), dict) else record if isinstance(settle, dict): row["tenant_id"] = pick(settle, "tenantId", "tenant_id") row["settle_relate_id"] = settle.get("settleRelateId") row["settle_name"] = settle.get("settleName") row["settle_type"] = settle.get("settleType") row["settle_status"] = settle.get("settleStatus") row["member_id"] = settle.get("memberId") row["member_phone"] = settle.get("memberPhone") row["table_id"] = settle.get("tableId") row["consume_money"] = settle.get("consumeMoney") row["table_charge_money"] = settle.get("tableChargeMoney") row["goods_money"] = settle.get("goodsMoney") row["service_money"] = settle.get("serviceMoney") row["assistant_pd_money"] = settle.get("assistantPdMoney") row["assistant_cx_money"] = settle.get("assistantCxMoney") row["pay_amount"] = settle.get("payAmount") row["coupon_amount"] = settle.get("couponAmount") row["card_amount"] = settle.get("cardAmount") row["balance_amount"] = settle.get("balanceAmount") row["refund_amount"] = settle.get("refundAmount") row["prepay_money"] = settle.get("prepayMoney") row["adjust_amount"] = settle.get("adjustAmount") row["rounding_amount"] = settle.get("roundingAmount") row["payment_method"] = settle.get("paymentMethod") row["create_time"] = settle.get("createTime") row["pay_time"] = settle.get("payTime") row["operator_id"] = settle.get("operatorId") row["operator_name"] = settle.get("operatorName") if "ods_product" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") row["goods_id"] = record.get("goodsId") row["goods_name"] = record.get("goodsName") row["goods_code"] = record.get("goodsCode") row["category_id"] = record.get("categoryId") row["category_name"] = record.get("categoryName") row["unit"] = record.get("unit") row["price"] = record.get("price") row["status"] = record.get("status") if "ods_platform_coupon_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") if "ods_table_use_log" in table: row["tenant_id"] = pick(record, "tenantId", "tenant_id") def _ensure_tuple(self, value): if isinstance(value, tuple): return value return (value,) def _bulk_insert(self, table: str, rows: list[dict]): if not rows: return columns = list(rows[0].keys()) col_clause = ", ".join(columns) val_clause = ", ".join(f"%({col})s" for col in columns) conflict_cols = ["site_id"] + self._get_conflict_columns(table) conflict_clause = ", ".join(conflict_cols) sql = f""" INSERT INTO {table} ({col_clause}) VALUES ({val_clause}) ON CONFLICT ({conflict_clause}) DO UPDATE SET payload = EXCLUDED.payload, fetched_at = EXCLUDED.fetched_at, source_file = EXCLUDED.source_file """ self.db.batch_execute(sql, rows)