落地报价模板主账与后台:合同映射、灌种 SQL/脚本、智能体填表,并补测服运维探针与合同文档对齐。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
"""
|
||||
报价模板访问壳。
|
||||
报价模板访问。
|
||||
|
||||
真相在主账:后台上传/启用/停用 → 对象存储存字节 → 主账存元数据。
|
||||
智能体生成报价前经 ledger 拉取已启用模板指针,再从对象存储取字节。
|
||||
真相在主账:后台上传/启用/停用 → 对象存储/本地上传路径 → 主账元数据 + mappingJson。
|
||||
智能体生成前经 ledger 拉取已启用模板,再解析本地/下载字节后交 quote_fill。
|
||||
|
||||
本文件职责:声明「从主账列模板」的空 API;可选本地缓存目录仅联调。
|
||||
禁止:把仓库 resources 当生产模板真相;禁止在本包发明价格或改 quoteVersion。
|
||||
"""
|
||||
|
||||
@@ -16,6 +15,7 @@ from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from agent.ledger import LedgerClient, LedgerResponse
|
||||
from agent.ledger.api_shell import LedgerApiShell
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -29,6 +29,12 @@ class TemplateEntry:
|
||||
object_key: str = ""
|
||||
enabled: bool = False
|
||||
source: str = "ledger"
|
||||
template_id: str = ""
|
||||
version_no: int = 0
|
||||
version_id: str = ""
|
||||
mapping_json: str = ""
|
||||
biz_type: str = ""
|
||||
category: str = ""
|
||||
|
||||
|
||||
def local_cache_dir() -> Path:
|
||||
@@ -43,20 +49,53 @@ def local_cache_dir() -> Path:
|
||||
|
||||
def list_templates_from_ledger(
|
||||
client: Optional[LedgerClient] = None,
|
||||
*,
|
||||
enabled_only: bool = True,
|
||||
) -> LedgerResponse:
|
||||
"""
|
||||
向主账查询已启用报价模板列表(壳)。
|
||||
向主账查询已启用报价模板列表。
|
||||
|
||||
正式路径以主账 OpenAPI 为准;当前返回 not_implemented,不假装本地文件即生产模板。
|
||||
正式路径:GET /inquiry/quoteTemplate/agent/listEnabled
|
||||
"""
|
||||
c = client or LedgerClient.from_settings()
|
||||
logger.info("quote_templates.list_from_ledger(壳,未打真实 API)")
|
||||
return LedgerResponse(
|
||||
ok=False,
|
||||
status_code=501,
|
||||
error="not_implemented:list_quote_templates",
|
||||
data={"hint": "admin_upload_via_ledger"},
|
||||
)
|
||||
shell = LedgerApiShell(client=client)
|
||||
return shell.list_quote_templates(enabled_only=enabled_only)
|
||||
|
||||
|
||||
def entries_from_ledger_response(resp: LedgerResponse) -> list[TemplateEntry]:
|
||||
"""把主账 listEnabled 结果转成 TemplateEntry。"""
|
||||
if not resp.ok or resp.data is None:
|
||||
return []
|
||||
raw = resp.data
|
||||
# Jeecg Result 可能包在 result / data
|
||||
if isinstance(raw, dict):
|
||||
items = raw.get("result") or raw.get("data") or raw.get("records") or []
|
||||
if isinstance(raw.get("result"), list):
|
||||
items = raw["result"]
|
||||
elif isinstance(raw, list):
|
||||
items = raw
|
||||
else:
|
||||
items = []
|
||||
out: list[TemplateEntry] = []
|
||||
for it in items:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
tid = str(it.get("templateId") or it.get("id") or "")
|
||||
out.append(
|
||||
TemplateEntry(
|
||||
code=tid or str(it.get("name") or ""),
|
||||
filename=str(it.get("fileName") or ""),
|
||||
object_key=str(it.get("objectKey") or ""),
|
||||
enabled=True,
|
||||
source="ledger",
|
||||
template_id=tid,
|
||||
version_no=int(it.get("versionNo") or 0),
|
||||
version_id=str(it.get("versionId") or ""),
|
||||
mapping_json=str(it.get("mappingJson") or ""),
|
||||
biz_type=str(it.get("bizType") or ""),
|
||||
category=str(it.get("category") or ""),
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def list_local_cache_files() -> list[TemplateEntry]:
|
||||
@@ -91,10 +130,12 @@ def list_local_cache_files() -> list[TemplateEntry]:
|
||||
|
||||
def list_template_registry() -> list[TemplateEntry]:
|
||||
"""
|
||||
兼容旧名:框架阶段返回本地缓存列表(可空)。
|
||||
|
||||
生产应改用 list_templates_from_ledger + 对象存储下载。
|
||||
优先主账;失败则回落本地缓存(可空)。
|
||||
"""
|
||||
resp = list_templates_from_ledger(enabled_only=True)
|
||||
entries = entries_from_ledger_response(resp)
|
||||
if entries:
|
||||
return entries
|
||||
return list_local_cache_files()
|
||||
|
||||
|
||||
@@ -107,6 +148,34 @@ def registry_as_dicts() -> list[dict[str, Any]]:
|
||||
"object_key": e.object_key,
|
||||
"enabled": e.enabled,
|
||||
"source": e.source,
|
||||
"template_id": e.template_id,
|
||||
"version_no": e.version_no,
|
||||
"biz_type": e.biz_type,
|
||||
"category": e.category,
|
||||
}
|
||||
for e in list_template_registry()
|
||||
]
|
||||
|
||||
|
||||
def pick_template_for_biz(
|
||||
*,
|
||||
biz_type: str,
|
||||
keywords: Optional[list[str]] = None,
|
||||
entries: Optional[list[TemplateEntry]] = None,
|
||||
) -> Optional[TemplateEntry]:
|
||||
"""
|
||||
按业务线 + 关键词挑选模板(优先 is_default 由主账排序保证)。
|
||||
|
||||
关键词命中任一即优;否则同 biz 第一条。
|
||||
"""
|
||||
items = entries if entries is not None else list_template_registry()
|
||||
same = [e for e in items if (e.biz_type == biz_type or not e.biz_type)]
|
||||
if not same:
|
||||
same = list(items)
|
||||
if keywords:
|
||||
kw_l = [k.lower() for k in keywords if k]
|
||||
for e in same:
|
||||
blob = f"{e.filename} {e.category} {e.code}".lower()
|
||||
if any(k in blob for k in kw_l):
|
||||
return e
|
||||
return same[0] if same else None
|
||||
|
||||
Reference in New Issue
Block a user