From 2f64274d53ff1bb5b969ce180a4aba2b11c7fcfe Mon Sep 17 00:00:00 2001 From: zlt Date: Mon, 14 Sep 2026 12:18:58 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=94=B6=E5=B0=BE=E5=8C=85?= =?UTF-8?q?=EF=BC=9A=E5=8D=95=E7=A5=A8=E7=A0=94=E7=A9=B6=E9=9D=A2=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=A1=A5=E9=BD=90=E5=85=AD=E5=9D=97=EF=BC=8C=E6=8A=BD?= =?UTF-8?q?=E5=B1=89=E5=9B=9B=E8=8A=AF=E7=89=87=E3=80=81=E5=80=99=E9=80=89?= =?UTF-8?q?=E6=A0=8F=E8=8A=AF=E7=89=87=E3=80=81=E7=AE=A1=E7=90=86=E7=A0=94?= =?UTF-8?q?=E7=A9=B6=E9=9D=A2=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 评审发现六/七:研究面接口只给技术面一块,候选栏与管理视图缺研究面。补齐: - GET /api/research/{ts_code} 补成六块:公司质地/基本面/技术面/择时/合议/各路来源状态。 持仓票的买方评析从逻辑状态映射取,候选票从当日计划主榜与观察档取(_research_st), 四块用 consensus_service.assemble 凑齐,company 块复用 logic_state_service.company_view。 任一路取不到按无读数弃权,sources 逐路标 ok/无读数/错误。 - 单票抽屉那一节改成「研究面 · 三源合议」四枚芯片(基本面/技术面/择时/合议), 按立场红涨绿跌、悬停看理由,下面保留技术面详细读数与评析报告链接。 - 候选栏每行加质地芯片与技术芯片:disposition_snapshot 把候选处置快照按代码挂上合议摘要 (consensus_seen 加 overall),前端用 conOf(code) 取。 - 管理视图持仓总览加研究面列,与「我的持仓」那列同款。 前端三处由两道页面守卫(wiring 204/324 对齐、enum 通过)与本地 uvicorn 浏览器冒烟 (页面完整挂载不白屏)兜;真实数据视觉判收在 155 部署后过目。第三十三批5例, 全量871例 ALL SUITES PASS。未推未部署(随盘中确认包下批一起)。 Co-Authored-By: Claude Opus 4.8 --- app/services/proposal_service.py | 15 +++- app/web/main.py | 60 ++++++++++--- app/web/static/index.html | 79 +++++++++++++---- scripts/run_tests.py | 8 +- scripts/test_batch33_units.py | 147 +++++++++++++++++++++++++++++++ 5 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 scripts/test_batch33_units.py diff --git a/app/services/proposal_service.py b/app/services/proposal_service.py index c5f7700..19bb440 100644 --- a/app/services/proposal_service.py +++ b/app/services/proposal_service.py @@ -516,7 +516,9 @@ def _attach_consensus(cands, out=None, now=None) -> None: rec = {"ts_code": code, "fund": fund.get("stance"), "tech": tech.get("stance"), "timing": tm.get("stance"), "direction": con.get("direction"), "route": con.get("route"), - "phase": tech.get("phase")} + "phase": tech.get("phase"), + # 候选栏芯片用: 基本面质地档 (好/中/差), 从候选自带的买方评析取 + "overall": (c.get("company_review") or {}).get("overall")} if ikind: rec["intraday"] = ikind seen.append(rec) @@ -705,9 +707,18 @@ def disposition_snapshot(now=None) -> dict: if s.get("why"): res["notes"].append(s["why"]) else: # 单票原因; 不覆盖已判 would 的 - # 合议判观察的候选带 disp=wait_tech (等技术面开口), 其余不产出的按 deny。 + # 合议判观察的候选带 disp=wait_tech/wait_confirm, 其余不产出的按 deny。 res["by_code"].setdefault( code, {"disp": s.get("disp") or "deny", "why": s.get("why") or "未产出候选"}) + # 候选栏三小块 (2026-09-14 页面收尾包): 每只候选的合议摘要, 从 consensus_seen 来。 + # 基本面立场与质地档 / 技术面立场与相位 / 合议方向与路由。挂到对应代码的 by_code 行上。 + for rec in (sink.get("consensus_seen") or []): + code = rec.get("ts_code") + if code and code in res["by_code"]: + res["by_code"][code]["consensus"] = { + "fund": rec.get("fund"), "overall": rec.get("overall"), + "tech": rec.get("tech"), "phase": rec.get("phase"), + "direction": rec.get("direction"), "route": rec.get("route")} return res diff --git a/app/web/main.py b/app/web/main.py index f1ec179..5fc19d8 100644 --- a/app/web/main.py +++ b/app/web/main.py @@ -737,22 +737,60 @@ def api_tech_pull(): return ok_logged("tech_pull", tech_service.pull_and_map) +def _research_st(code): + """研究面的公司评析来源: 持仓票从逻辑状态映射取, 候选票从当日计划主榜与观察档取。 + 返回 (逻辑状态样式的 dict 或 None, 来源标签)。取不到就当无读数弃权, 绝不折成看空。""" + from app.services import logic_state_service, plan_feed + try: + st = logic_state_service.state_map().get(code) + if st and (st.get("company_review") or {}).get("overall"): + return st, "held" + except Exception as e: # noqa: BLE001 + logger.warning("[研究面] 取持仓逻辑状态失败 %s: %s", code, e) + try: + plan = plan_feed.get_plan() + for r in (plan.get("main") or []) + (plan.get("observe") or []): + if cs.normalize_code(r.get("ts_code") or "") == code and (r.get("company_review") or {}).get("overall"): + return {"company_review": r.get("company_review"), + "company_review_text": r.get("company_review_text")}, "candidate" + except Exception as e: # noqa: BLE001 + logger.warning("[研究面] 取候选计划失败 %s: %s", code, e) + return None, "none" + + @app.get("/api/research/{ts_code}") def api_research(ts_code: str): - """单票研究面 · 三源合议 (点击时调, 只读, 绝不进轮询)。 - - 工作包一先给技术面读数一块; 基本面评析、择时立场与三源合议由工作包二补 (现以 None 占位, - 页面按缺块处理)。代码归一后回。""" - from app.services import tech_service + """单票研究面 · 三源合议 (点击时调, 只读, 绝不进轮询)。六块: 公司质地 / 基本面 / 技术面 / + 择时 / 合议 / 各路来源状态。任何一路取不到按无读数弃权 (设计原则二), 页面按缺块处理。""" + from app.services import tech_service, consensus_service, logic_state_service def _build(code): code = cs.normalize_code(code) - t = tech_service.research_feed(code) - return {"ts_code": code, "company": None, "tech": t.get("tech"), - "tech_note": t.get("note") or t.get("error"), - "consensus": None, "timing": None, "feed": None, - "sources": {"tech": ("ok" if t.get("tech") - else (t.get("error") or t.get("note") or "无读数"))}} + st, src = _research_st(code) + cr = (st or {}).get("company_review") + company = logic_state_service.company_view(st) if st else None + # 四块意见: assemble 用 company_review 算基本面、用映射算技术面、用昨夜定性算择时, 再合议。 + nightly = consensus_service.nightly_map([code]).get(code) + tech_states = consensus_service.state_map() + blocks = consensus_service.assemble({"ts_code": code, "company_review": cr}, + nightly=nightly, tech_states=tech_states) + # 技术面详细读数 (点击时现读, 带最新一行与近日翻向次数); 缺则退回映射里的紧凑块。 + tfeed = tech_service.research_feed(code) + tech = tfeed.get("tech") or blocks["tech"] + tm = blocks["timing"] + timing = {"stance": tm.get("stance"), "nightly": tm.get("nightly"), + "trade_date": (nightly or {}).get("trade_date"), + "flip_at": tm.get("intraday_flip_at")} # 转多时刻点击不回扫, 留昨夜定性带的 + fund = blocks["fund"] + sources = { + "company": ("ok" if company else ("无读数" if src != "none" else "不在持仓也不在候选")), + "fund": ("ok" if fund.get("stance") != "无读数" else (fund.get("why") or "无读数")), + "tech": ("ok" if tfeed.get("tech") else (tfeed.get("error") or tfeed.get("note") or "无读数")), + "timing": ("ok" if tm.get("stance") != "无读数" else (tm.get("no_read_why") or "无读数")), + "consensus": "ok", + } + return {"ts_code": code, "company": company, "fund": fund, "tech": tech, + "timing": timing, "consensus": blocks["consensus"], "sources": sources} return ok(_build, ts_code) diff --git a/app/web/static/index.html b/app/web/static/index.html index 96e896a..9b56ca4 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -1260,6 +1260,11 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;} {{ r.name || nm(r.ts_code) }} {{ r.theme }} + + {{ dispOf(r.ts_code).label }}
@@ -1724,6 +1729,18 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;} class="muted">今天没有读数 + + + +