Files
ztb/processors/jiandaoyun.py
ztb-system d823936436 feat: 添加招标估算金额字段并修复项目名称显示
- 移除台州招标文件公示相关配置
- 添加浙江招标公告招标估算金额字段
- 修复项目名称匹配规则,优先使用处理后的项目名称
- 更新简道云字段映射
- 添加测试文件
2026-02-24 19:55:56 +08:00

144 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
简道云数据上传模块
"""
import logging
import re
import requests
import urllib3
from config import JDY_CONFIG
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
logger = logging.getLogger("ztb")
class JiandaoyunUploader:
"""简道云 API 上传器"""
BASE_URL = "https://api.jiandaoyun.com/api/v5"
# 需要转换为数字的字段
NUMERIC_FIELDS = {"最高限价", "最高投标限价", "预估金额", "招标估算金额"}
def __init__(self, api_key: str = None):
self.api_key = api_key or JDY_CONFIG["api_key"]
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
def upload_records(self, region_name: str, records: list) -> dict:
"""
上传记录到对应的简道云表单
Args:
region_name: 区域名称(如 "浙江招标文件公示"
records: 数据记录列表
Returns:
{"total": N, "success": N, "failed": N}
"""
form_config = JDY_CONFIG["forms"].get(region_name)
if not form_config:
logger.warning(f"{region_name}: 未找到简道云表单配置,跳过上传")
return {"total": len(records), "success": 0, "failed": len(records)}
app_id = form_config["app_id"]
entry_id = form_config["entry_id"]
field_mapping = form_config.get("field_mapping", {})
success = 0
failed = 0
for i, record in enumerate(records):
name = record.get("名称", f"记录{i+1}")
try:
jdy_data = self._convert(record, field_mapping)
if not jdy_data:
logger.debug(f"[{i+1}/{len(records)}] {name}: 无有效数据")
failed += 1
continue
result = self._create_record(app_id, entry_id, jdy_data)
if result and result.get("success"):
success += 1
if (i + 1) % 10 == 0 or (i + 1) == len(records):
logger.info(
f" [{i+1}/{len(records)}] 上传进度: "
f"成功{success} 失败{failed}")
else:
failed += 1
err = result.get("error", "未知") if result else "无返回"
logger.warning(
f" [{i+1}/{len(records)}] {name[:25]}: "
f"上传失败 - {err}")
except Exception as e:
failed += 1
logger.error(f" [{i+1}/{len(records)}] {name[:25]}: 异常 - {e}")
logger.info(f" {region_name} 上传完成: 成功 {success}, 失败 {failed}")
return {"total": len(records), "success": success, "failed": failed}
# ---------- 内部方法 ----------
def _create_record(self, app_id: str, entry_id: str, data: dict) -> dict:
"""调用简道云 API 创建单条记录"""
url = f"{self.BASE_URL}/app/entry/data/create"
payload = {"app_id": app_id, "entry_id": entry_id, "data": data}
try:
resp = requests.post(url, headers=self.headers,
json=payload, timeout=30, verify=False)
if not resp.text:
return {"success": False,
"error": f"空响应, status={resp.status_code}"}
result = resp.json()
if resp.status_code == 200 and result.get("data", {}).get("_id"):
return {"success": True,
"data_id": result["data"]["_id"]}
return {"success": False,
"error": result.get("msg", str(result))}
except Exception as e:
return {"success": False, "error": str(e)}
def _convert(self, record: dict, field_mapping: dict) -> dict:
"""将记录转换为简道云 API 格式"""
jdy_data = {}
for local_field, jdy_field in field_mapping.items():
value = record.get(local_field)
if not value or value in ("文档未提及", "详见公告"):
continue
if local_field in self.NUMERIC_FIELDS:
num = self._parse_price(value)
if num is not None:
jdy_data[jdy_field] = {"value": num}
else:
jdy_data[jdy_field] = {"value": value}
return jdy_data
@staticmethod
def _parse_price(price_str) -> int | None:
"""将价格字符串转为纯数字(元)"""
if not price_str or price_str in ("文档未提及", "详见公告"):
return None
s = str(price_str).strip()
s = re.sub(r'^[约≈大概]*', '', s)
s = re.sub(r'[(].*?[)]', '', s)
s = re.sub(r'[元人民币¥¥\s]', '', s)
try:
if "亿" in s:
return int(float(s.replace("亿", "")) * 100_000_000)
elif "" in s:
return int(float(s.replace("", "")) * 10_000)
else:
s = s.replace(",", "").replace("", "")
return int(float(s))
except (ValueError, TypeError):
return None