89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
行业划分接口 (设计 §5 的可插拔适配器)
|
||
|
|
======================================
|
||
|
|
IndustryClassifier.get(ts_code) -> 行业名 | None
|
||
|
|
|
||
|
|
数据源经参数 PMS_SECTOR_SOURCE 切换:
|
||
|
|
custom_table 自定义映射表 pms_industry_map (默认建议; 用户后续灌何种划分都只是灌数)
|
||
|
|
gp_stock_category 决策系统生态已有的行业表 (映射表就绪前的临时数据源)
|
||
|
|
""(空) 未配置 → 行业约束自动停用, 页面显著提示 (不静默放行也不误拦)
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import threading
|
||
|
|
import time
|
||
|
|
|
||
|
|
from app.repo import downstream_repo, pms_repo
|
||
|
|
from app.services import param_store
|
||
|
|
|
||
|
|
logger = logging.getLogger("pms.industry")
|
||
|
|
|
||
|
|
_cache = {"at": 0.0, "map": {}, "source": None}
|
||
|
|
_lock = threading.Lock()
|
||
|
|
CACHE_TTL = 300.0
|
||
|
|
|
||
|
|
|
||
|
|
def source() -> str:
|
||
|
|
return (param_store.get("PMS_SECTOR_SOURCE", "") or "").strip()
|
||
|
|
|
||
|
|
|
||
|
|
def ready() -> bool:
|
||
|
|
"""行业约束是否生效 (数据源已配置)。"""
|
||
|
|
return source() in ("custom_table", "gp_stock_category")
|
||
|
|
|
||
|
|
|
||
|
|
def status() -> dict:
|
||
|
|
src = source()
|
||
|
|
st = {"source": src, "ready": ready(), "count": 0,
|
||
|
|
"hint": "行业划分数据源未配置 —— 行业集中度硬拦截已自动停用, "
|
||
|
|
"行业类命令置灰 (设计 §5/§13)"}
|
||
|
|
if src == "custom_table":
|
||
|
|
try:
|
||
|
|
st["count"] = len(_load_custom())
|
||
|
|
st["hint"] = f"自定义映射表 pms_industry_map 已加载 {st['count']} 条"
|
||
|
|
except Exception as e:
|
||
|
|
st["ready"] = False
|
||
|
|
st["hint"] = f"映射表读取失败: {type(e).__name__}: {e}"
|
||
|
|
elif src == "gp_stock_category":
|
||
|
|
st["hint"] = "临时数据源 gp_stock_category (逐票查询, 建议尽快切 custom_table)"
|
||
|
|
return st
|
||
|
|
|
||
|
|
|
||
|
|
def _load_custom() -> dict:
|
||
|
|
now = time.time()
|
||
|
|
if _cache["source"] == "custom_table" and now - _cache["at"] < CACHE_TTL:
|
||
|
|
return _cache["map"]
|
||
|
|
with _lock:
|
||
|
|
rows = pms_repo.list_industry()
|
||
|
|
m = {r["ts_code"]: r["industry"] for r in rows}
|
||
|
|
_cache.update({"at": now, "map": m, "source": "custom_table"})
|
||
|
|
return m
|
||
|
|
|
||
|
|
|
||
|
|
def get(ts_code: str):
|
||
|
|
"""返回行业名; 未配置数据源或查不到 → None (调用方据此跳过行业约束)。"""
|
||
|
|
src = source()
|
||
|
|
if not ts_code or src not in ("custom_table", "gp_stock_category"):
|
||
|
|
return None
|
||
|
|
try:
|
||
|
|
if src == "custom_table":
|
||
|
|
return _load_custom().get(ts_code)
|
||
|
|
return downstream_repo.fetch_sector_from_category(ts_code)
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning("行业查询失败 [%s]: %s", ts_code, e)
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def get_many(codes) -> dict:
|
||
|
|
src = source()
|
||
|
|
if src != "custom_table":
|
||
|
|
return {c: get(c) for c in (codes or [])}
|
||
|
|
m = _load_custom()
|
||
|
|
return {c: m.get(c) for c in (codes or [])}
|
||
|
|
|
||
|
|
|
||
|
|
def invalidate():
|
||
|
|
_cache["at"] = 0.0
|