from __future__ import annotations import hashlib import importlib.util import json import os import shutil import sys from pathlib import Path from typing import Any import mysql.connector ROOT = Path(__file__).resolve().parents[2] BASE_PATH = ROOT / "ai-valuation-analyst" / "tools" / "generate_semiconductor_batch_20260804.py" SPEC = importlib.util.spec_from_file_location("valuation_batch_missing_base", BASE_PATH) if SPEC is None or SPEC.loader is None: raise RuntimeError(f"cannot load {BASE_PATH}") base = importlib.util.module_from_spec(SPEC) sys.modules[SPEC.name] = base SPEC.loader.exec_module(base) BATCH_ID = "BATCH-STOCK-VALUATION-20260805-004" AS_OF = "2026-08-04" V2_ROOT = ROOT / "ai-valuation-analyst" / "tmp" / "valuation_batch_20260805_004" RESULT_ROOT = ROOT / "ana-data" / "result" / "股票估值" CASE_ROOT = ROOT / "ana-data" / "cases" / "股票估值" / BATCH_ID SUMMARY_DIR = RESULT_ROOT / "20260805_batch_missing_image_stocks_valuation" REGISTRY = ROOT / "dev" / "ana-dev" / "stock_valuation_pipeline" / "source_registry.json" SCREENSHOTS = [ Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-18c47d0e-86b7-405e-898b-8cfd914dc56f.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-079b32d3-ae55-4258-a57f-e2c9b80b960e.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-e280ac1b-4233-4c32-92d8-4d0dfdae9937.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-3ea98883-cef8-4dfe-8982-3d13b394ab4f.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-696dfa93-367e-48cf-82ef-acb885c47823.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-48dad5c9-84e7-49f7-8682-e9821ddbda44.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-afab7836-3f33-43bf-bf35-29086f48de80.png"), Path(r"C:\Users\Cai\AppData\Local\Temp\codex-clipboard-27e1624a-5bfa-40a0-8260-9909cb990a39.png"), ] def S(ticker: str, slug: str, business: str, driver: str, tier: str, risk: str): return base.Stock(ticker, slug, business, driver, tier, risk) STOCKS = [ S("000063.SZ", "zte", "通信设备、算力基础设施与政企业务", "运营商资本开支、服务器与芯片业务、海外份额和毛利率", "telecom_leader", "运营商投资周期、海外合规与供应链限制"), S("000066.SZ", "great_wall_computer", "自主计算整机、网络安全和行业信息化", "信创订单、产品结构、费用控制和资产整合", "defense_growth", "持续盈利能力、应收回款和资产整合不确定性"), S("000426.SZ", "xingye_silver_tin", "银、锡、铅锌等有色金属采选", "金属价格、矿山产量、品位、成本和技改爬坡", "resource_cycle", "金属价格周期、安全生产和资源项目投产"), S("000547.SZ", "aerospace_development", "电子蓝军、通信与信息安全装备", "军品订单恢复、项目验收、费用控制和资产质量", "defense_growth", "持续亏损、商誉减值、军品验收和应收回款"), S("000657.SZ", "zhongwu_hightech", "硬质合金、刀具与钨产业链", "钨价、刀具销量、产品结构和矿端资产整合", "resource_cycle", "钨价周期、重组进度和制造业需求"), S("000807.SZ", "yunnan_aluminium", "水电铝及铝加工", "电解铝价格、氧化铝和电力成本、产量与水电供应", "resource_cycle", "铝价周期、电力供应和原料成本"), S("000963.SZ", "huadong_medicine", "创新药、医药商业与医美", "创新药放量、医美产品增长、商业分销和研发费用", "pharma_growth", "研发失败、集采降价和医美竞争"), S("000977.SZ", "inspur_information", "AI服务器与通用服务器", "AI服务器需求、芯片供给、产品结构和规模效应", "ai_compute", "供应链限制、客户集中和算力资本开支波动"), S("001296.SZ", "changjiang_materials", "铸造用砂与相关材料", "下游铸造需求、销量、原料成本和新产能利用率", "stable_mfg", "制造业周期、原材料波动和产能消化"), S("002156.SZ", "tongfu_microelectronics", "集成电路封装测试", "先进封装产能利用率、客户订单、折旧和产品结构", "semi_growth", "半导体周期、重资产折旧和客户集中"), S("002185.SZ", "huatian_technology", "集成电路封装测试", "封测稼动率、先进封装占比、价格和规模效应", "semi_growth", "行业周期、扩产折旧和价格竞争"), S("002281.SZ", "accelink", "光通信器件与模块", "数通光模块需求、速率升级、良率和海外客户", "ai_compute", "AI资本开支、客户认证和高速产品迭代"), S("002294.SZ", "salubris", "创新药与心血管专科药", "创新药商业化、存量产品销量、研发里程碑和费用", "pharma_growth", "临床失败、集采和新品放量不及预期"), S("002384.SZ", "dongshan_precision", "PCB、精密电子与新能源零部件", "AI及消费电子PCB需求、客户新品和产能利用率", "semi_growth", "客户集中、消费电子周期和海外扩产"), S("002463.SZ", "wus_printed_circuit", "通信与汽车高端PCB", "AI服务器及交换机PCB、汽车板销量、产能与良率", "ai_compute", "AI需求波动、扩产爬坡和客户集中"), S("002738.SZ", "sinomine_resource", "锂、铯铷等矿产资源与材料", "锂价、矿山产量、自给率、扩产和单位成本", "resource_cycle", "锂价周期、海外资源和项目执行"), S("002773.SZ", "conba_hongri", "眼科与中枢神经创新药", "核心药品销量、研发管线进展、渠道和费用率", "pharma_growth", "研发失败、医保谈判和单品依赖"), S("002792.SZ", "mobi_antenna", "基站天线、射频器件与卫星通信部件", "运营商投资、卫星通信订单、产品结构和回款", "telecom_leader", "通信资本开支、客户集中和主题预期"), S("002859.SZ", "jiemei_technology", "电子元器件薄型载带与离型膜", "被动元件景气、载带销量、高端膜材放量和良率", "semi_growth", "消费电子周期、客户验证和扩产"), S("002916.SZ", "shennan_circuits", "高端PCB、封装基板与电子装联", "AI数通PCB、封装基板稼动率、产品结构和良率", "ai_compute", "高估值、扩产爬坡和算力需求波动"), S("003009.SZ", "zhongtian_rocket", "小型固体火箭、炭材料和测控产品", "军品订单、型号任务、民品销量和项目验收", "defense_growth", "军品订单波动、项目验收和盈利基数低"), S("300285.SZ", "sinocera", "先进陶瓷材料及电子材料", "MLCC粉体、齿科和电子陶瓷销量、产品结构与良率", "semi_growth", "下游景气、扩产和新材料认证"), S("300357.SZ", "wolwo_bio", "过敏原制品与脱敏治疗", "粉尘螨制剂患者渗透、销售恢复和新产品获批", "pharma_growth", "单品集中、渠道合规和新品审批"), S("300373.SZ", "yangjie_technology", "功率半导体器件与芯片", "功率器件销量、价格、产能利用率和产品升级", "semi_growth", "行业周期、价格竞争和扩产"), S("300408.SZ", "sunlord_ceramics", "电子陶瓷元件与材料", "MLCC、通信及汽车电子需求、产能利用率和良率", "semi_growth", "消费电子周期、扩产折旧和竞争"), S("300455.SZ", "aerospace_intelligent", "航天电子、轨交与智能装备", "航天型号订单、项目验收、资产整合和费用控制", "defense_growth", "盈利波动、商誉和项目回款"), S("300456.SZ", "sai_microelectronics", "MEMS晶圆制造与导航业务", "MEMS产能利用率、客户量产、工艺平台和费用", "semi_growth", "持续盈利能力、海外限制和重资产折旧"), S("300762.SZ", "shanghai_hanxun", "军用宽带通信设备", "军品订单恢复、项目验收、新型号和回款", "defense_growth", "持续亏损、军品验收和应收账款"), S("301583.SZ", "tolerance_precision", "半导体设备精密金属零部件", "本土设备客户扩产、零部件放量、良率和产能利用率", "ipo_semiconductor", "上市初期估值过高、客户集中、限售股和业绩兑现"), S("600111.SH", "northern_rare_earth", "稀土矿产品、冶炼分离与磁材", "稀土价格、配额、销量、成本和产品结构", "resource_cycle", "稀土价格周期、政策配额和库存波动"), S("600118.SH", "china_satellite", "小卫星研制及卫星应用", "卫星型号任务、交付节奏、应用业务和成本控制", "defense_stable", "项目验收、低利润率和商业航天预期"), S("600183.SH", "shengyi_technology", "覆铜板与粘结片", "AI高速覆铜板需求、产品升级、产能利用率和原料价差", "ai_compute", "高估值、原料价格和AI需求不及预期"), S("600276.SH", "hengrui_pharma", "创新药研发与商业化", "创新药销售、海外授权、研发管线和费用率", "pharma_growth", "临床失败、集采降价和授权收入波动"), S("600343.SH", "aerospace_power", "航天特种泵阀、发动机与流体装备", "型号订单、项目验收、产品结构和费用控制", "defense_growth", "长期低盈利、应收回款和资产质量"), S("600378.SH", "haohua_technology", "高端氟材料、电子化学品与特种化工", "高端材料销量、产能释放、产品价格和重组协同", "stable_mfg", "项目投产、化工周期和重组整合"), S("600487.SH", "hengtong_optic", "光纤通信、海洋通信与能源互联", "海缆订单、光通信需求、项目交付和海外业务", "telecom_leader", "项目回款、海外经营和资本开支"), S("600549.SH", "xiamen_tungsten", "钨钼、稀土与新能源材料", "钨钼价格、销量、材料加工费和产能利用率", "resource_cycle", "金属价格、新能源材料竞争和扩产"), S("600584.SH", "jcet", "集成电路封装测试", "先进封装需求、稼动率、产品结构和海外客户", "semi_growth", "半导体周期、客户集中和资本开支"), S("600699.SH", "joyson_electronics", "汽车安全与汽车电子", "全球汽车产量、订单转化、毛利率和债务下降", "auto_leader", "海外经营、汽车周期和整合效率"), S("600877.SH", "cet_chip", "特种集成电路与硅基模拟器件", "军民芯片订单、产品升级、资产整合和费用率", "defense_growth", "军品采购、业务整合和盈利波动"), S("601127.SH", "seres", "新能源汽车整车", "问界销量、单车盈利、产品周期和渠道效率", "auto_leader", "车型周期、价格竞争和估值预期过高"), S("601698.SH", "chinasatcom", "卫星通信运营与广播传输", "卫星资源利用率、客户续约、新业务和折旧", "defense_stable", "卫星更新资本开支、客户集中和成长性"), S("601869.SH", "yangtze_optical_fibre", "光纤光缆及预制棒", "运营商与数据中心需求、价格、海外份额和产能利用率", "telecom_leader", "光纤价格周期、海外贸易和资本开支"), S("601958.SH", "jinduicheng_molybdenum", "钼矿采选、冶炼与深加工", "钼价、产量、品位、成本和产品结构", "resource_cycle", "钼价周期、安全环保和资源税费"), S("603019.SH", "sugon", "高端计算机、算力基础设施与服务", "服务器与算力中心需求、芯片供给、订单和毛利率", "ai_compute", "供应链限制、应收回款和主题估值"), S("603239.SH", "zhejiang_xiantong", "汽车密封条", "汽车客户销量、单车价值量、海外拓展和原料成本", "stable_mfg", "客户集中、汽车周期和原料波动"), S("603596.SH", "bethel", "汽车制动与智能底盘系统", "线控制动渗透、客户车型放量、单车价值和产能利用率", "auto_leader", "客户集中、汽车价格战和扩产"), S("603799.SH", "huayou_cobalt", "镍钴锂资源与新能源材料", "金属价格、材料销量、海外项目和成本一体化", "resource_cycle", "金属价格、海外项目和高资本开支"), S("603893.SH", "rockchip", "智能应用处理器芯片", "AIoT芯片销量、产品升级、客户新品和库存周期", "semi_growth", "消费电子周期、客户集中和高估值"), S("603993.SH", "cmoc", "铜钴钼钨等全球矿业", "铜钴价格、矿山产量、成本、权益比例和汇率", "resource_cycle", "大宗商品周期、海外政策和运营风险"), S("688166.SH", "boryung_pharma", "高端仿制药与创新制剂", "核心制剂销量、研发管线、授权合作和费用控制", "pharma_growth", "研发失败、商业化不及预期和持续融资"), S("688180.SH", "junshi_bio", "创新生物药", "PD-1及新药销售、海外合作、研发管线和现金消耗", "pharma_growth", "持续亏损、临床失败和再融资"), S("688192.SH", "dizal_pharma", "肿瘤创新药", "核心产品放量、适应症拓展、海外商业化和研发费用", "pharma_growth", "单品依赖、持续亏损和临床审批"), S("688202.SH", "medicilon", "医药研发CRO服务", "订单恢复、实验产能利用率、价格和人员效率", "pharma_growth", "行业需求低迷、价格竞争和产能闲置"), S("688278.SH", "tebao_bio", "慢性乙肝等生物制品", "核心产品渗透、患者疗程、销售效率和新适应症", "pharma_growth", "单品集中、医保和竞争品上市"), S("688336.SH", "sunshine_guojian", "自身免疫与肿瘤生物药", "核心品种销量、新适应症、研发管线和费用率", "pharma_growth", "研发失败、市场竞争和产品集中"), S("688506.SH", "baili_tianheng", "创新生物药与传统制剂", "授权里程碑、临床管线、商业化进度和现金消耗", "pharma_growth", "授权收入波动、临床失败和高估值"), S("688521.SH", "verisilicon", "芯片IP与一站式芯片定制", "IP授权与版税、芯片设计订单、研发效率和客户需求", "semi_growth", "持续盈利能力、客户项目周期和高研发投入"), ] base.BATCH_ID = BATCH_ID base.AS_OF = AS_OF base.TMP = V2_ROOT base.CACHE = ROOT / "ai-valuation-analyst" / "tmp" / "v2_cache" / "blobs" / "sha256" base.RESULT_ROOT = RESULT_ROOT base.CASE_ROOT = CASE_ROOT base.REGISTRY = REGISTRY base.PE_MULTIPLES.update( { "telecom_leader": ((12, 18), (18, 28), (28, 40)), "ai_compute": ((20, 30), (30, 45), (45, 65)), "semi_growth": ((18, 28), (28, 42), (42, 60)), "auto_leader": ((15, 22), (22, 32), (32, 45)), "stable_mfg": ((12, 18), (18, 28), (28, 38)), "pharma_growth": ((18, 28), (28, 45), (45, 65)), "resource_cycle": ((8, 13), (12, 20), (18, 28)), "defense_stable": ((18, 25), (25, 35), (35, 48)), "defense_growth": ((22, 32), (32, 48), (48, 65)), "ipo_semiconductor": ((20, 30), (30, 45), (45, 60)), } ) ORIGINAL_READ_JSON = base.read_json ORIGINAL_ANNOUNCEMENT = base.announcement_record ORIGINAL_SCENARIO = base.scenario_for ORIGINAL_RUN_PIPELINE = base.run_pipeline def patched_read_json(path: Path) -> dict[str, Any]: value = ORIGINAL_READ_JSON(path) if path.name == "provider_results.json" and "301583_SZ" in str(path): value["announcements"]["identity"] = { "ticker": "301583.SZ", "company": "托伦斯精密制造(江苏)股份有限公司", "market": "深交所创业板", } return value def patched_announcement(provider: dict[str, Any], kind: str) -> dict[str, Any]: if provider["announcements"]["identity"]["ticker"] != "301583.SZ": records = provider["announcements"]["records"] if kind == "annual": matches = [ item for item in records if ("2025年度报告" in item["title"] or "2025年年度报告" in item["title"]) and "摘要" not in item["title"] ] elif kind == "half": matches = [item for item in records if "2026年半年度报告" in item["title"] and "摘要" not in item["title"]] else: matches = [item for item in records if "2026年" in item["title"] and "季度报告" in item["title"] and "摘要" not in item["title"]] if not matches: return ORIGINAL_ANNOUNCEMENT(provider, kind) return matches[0] if kind == "annual": return { "title": "托伦斯招股说明书(2025年度经审计财务数据)", "publish_date": "2026-06-18", "url": "https://disc.szse.cn/disc/disk03/finalpage/2026-06-18/09490a84-32ec-42b0-915b-bef1793d410a.PDF", } return { "title": "托伦斯招股意向书(截至2026年3月31日财务数据)", "publish_date": "2026-06-18", "url": "https://disc.szse.cn/disc/disk03/finalpage/2026-06-18/09490a84-32ec-42b0-915b-bef1793d410a.PDF", } def db_connection(): password = os.environ.get("STOCK_VALUATION_MYSQL_PASSWORD") if password is None: raise RuntimeError("missing STOCK_VALUATION_MYSQL_PASSWORD") return mysql.connector.connect( host=os.environ.get("STOCK_VALUATION_MYSQL_HOST", "127.0.0.1"), port=int(os.environ.get("STOCK_VALUATION_MYSQL_PORT", "3306")), user=os.environ.get("STOCK_VALUATION_MYSQL_USER", "root"), password=password, database="trading_xuntou", charset="utf8mb4", use_unicode=True, ) def parse_market(provider: dict[str, Any], ticker: str) -> tuple[dict[str, Any], dict[str, Any]]: objs = [base.blob(raw_hash) for raw_hash in provider["market"].get("raw_artifact_hashes", [])] quote = next((item["data"] for item in objs if item and (item.get("data") or {}).get("f84")), None) if quote is None: raise RuntimeError(f"missing quote shares for {ticker}") with db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute( "SELECT trade_date, close, amount, source FROM cn_stock_kline_1d_front " "WHERE symbol=%s AND trade_date<=%s AND trade_date>=DATE_SUB(%s, INTERVAL 20 DAY) " "ORDER BY trade_date", (ticker, AS_OF, AS_OF), ) rows = cursor.fetchall() if not rows: raise RuntimeError(f"missing front-adjusted kline for {ticker}") latest = rows[-1] shares = float(quote["f84"]) price = float(latest["close"]) market = { "price": price, "date": latest["trade_date"].isoformat(), "amount": float(latest["amount"] or 0), "shares": shares, "market_cap": price * shares, "window_start": rows[0]["trade_date"].isoformat(), "window_start_close": float(rows[0]["close"]), "window_return": price / float(rows[0]["close"]) - 1, } return market, { "source": "trading_xuntou.cn_stock_kline_1d_front:xtquant:front", "quote_raw_hashes": provider["market"].get("raw_artifact_hashes", []), "platform_market_cap_excluded": quote.get("f116"), } def scenario_for(stock, normalized: float, consensus: float | None, count: int, equity: float): if normalized <= 0 and (consensus is None or consensus <= 0): bands = ((0.6, 1.0), (1.0, 1.8), (1.8, 3.0)) assumptions = ( "亏损继续且现金消耗偏高,按归母净资产折价。", "业务逐步接近盈亏平衡,按成长型资产的中性净资产倍数定价。", "核心产品或订单放量并形成可持续盈利,给予成长溢价。", ) scenarios = [] for (name, role), (low, high), assumption in zip( (("悲观", "pessimistic"), ("基准", "base"), ("乐观", "optimistic")), bands, assumptions, ): scenarios.append( {"name": name, "role": role, "method": "pb", "multiple_low": low, "multiple_high": high, "assumption": assumption} ) return scenarios, bands[1], bands[2] return ORIGINAL_SCENARIO(stock, normalized, consensus, count, equity) def run_pipeline(snapshot_path: Path, calc_dir: Path, registry: Path): snapshot = ORIGINAL_READ_JSON(snapshot_path) snapshot["market"]["price_type"] = "trading_xuntou前复权日K收盘价" for source in snapshot["sources"]: if source["id"] == "SRC-EM-KLINE-20260804": source.update( { "id": "SRC-XT-FRONT-KLINE-20260804", "source_type": "quote_provider", "title": "trading_xuntou前复权专表2026-08-04收盘价", "url": "trading_xuntou.cn_stock_kline_1d_front", } ) if snapshot["meta"]["code"] == "301583.SZ" and source["id"] in { "SRC-ANNUAL-2025", "SRC-CURRENT-2026", "SRC-SHARES-A1-CHANGE", }: source["source_type"] = "szse" base.write_json(snapshot_path, snapshot) return ORIGINAL_RUN_PIPELINE(snapshot_path, calc_dir, registry) base.read_json = patched_read_json base.announcement_record = patched_announcement base.parse_market = parse_market base.scenario_for = scenario_for base.run_pipeline = run_pipeline def write_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def render_summary(rows: list[dict[str, Any]]) -> str: lines = [ "# 八张图片未评估股票价格合理性评估批次汇总(2026-08-05)", "", f"- 批次:`{BATCH_ID}`", "- 图片原始行数:120行;去重代码:111个", "- 排除行业指数:2个;有效股票:109只", "- 台账已有:51只;本批新增:58只", "- 合理区间基准日:2026-08-04;当日判定价格随后由MySQL前复权专表刷新至2026-08-05", "- 结论性质:条件化估值判断,不构成交易指令或收益承诺", "", "## 1. 新增估值总表", "", "| 序号 | 公司 | 代码 | 8月4日价 | TTM归一化利润 | 2026机构利润 | 基准合理区间 | 判断 |", "|---:|---|---|---:|---:|---:|---:|---|", ] for index, row in enumerate(rows, 1): forecast = "无可用预期" if row["consensus_2026"] is None else f"{row['consensus_2026']/1e8:.2f}亿元" report = f"../{row['formal_path']}" lines.append( f"| {index} | [{row['company']}]({report}) | {row['ticker']} | {row['price']:.2f}元 | " f"{row['normalized_profit']/1e8:.2f}亿元 | {forecast} | {row['base_low']:.2f}—{row['base_high']:.2f}元 | {row['label']} |" ) counts = {label: sum(item["label"] == label for item in rows) for label in ("偏低", "基本合理", "偏贵", "明显偏贵")} lines += [ "", "## 2. 结论分布", "", f"- 偏低:{counts['偏低']}只;基本合理:{counts['基本合理']}只;偏贵:{counts['偏贵']}只;明显偏贵:{counts['明显偏贵']}只。", "- 亏损且没有正向一致预期的公司使用PB与盈利修复情景,不计算无意义的PE。", "- 资源股使用中周期倍数,不能把高景气利润按成长股倍数外推。", "- 托伦斯为2026年7月新上市公司,股本使用深交所上市公告书;因上市历史短、估值溢价高,结论置信度低于成熟上市公司。", "", "## 3. 去重与排除", "", "- 图片内重复代码共9行,按证券代码去重。", "- `886015`(创新药)和`881140`(化学制药)是行业指数,不是上市公司,未生成个股估值。", "- `301583`图片简称误写为“托伦科技”,深交所正式简称为“托伦斯”。", "", "## 4. 数据边界", "", "- 法定财务和公告身份来自巨潮/交易所披露与东方财富结构化镜像;合理区间价格统一取本地 `trading_xuntou.cn_stock_kline_1d_front`。", "- 机构预期为公开一致预期汇总,缺逐家报告日期时只作为C1市场基准;每家公司报告保留覆盖缺口。", "- 合理区间仅在新财报、业绩预告、股本或业务重大变化时重估;日常只刷新价格与判定。", "", "## 5. 自动验收", "", f"- 正式报告、快照、来源manifest和V1计算结果:{len(rows)}/{len(rows)}。", f"- V1 QA无错误:{sum(item['qa_errors']==0 for item in rows)}/{len(rows)};业务警告总数:{sum(item['qa_warnings'] for item in rows)}。", "", ] return "\n".join(lines) def main() -> None: if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") proof_map = { "301583.SZ": { "ticker": "301583.SZ", "status": "MATCH", "title": "关于托伦斯精密制造(江苏)股份有限公司股票上市交易的公告及上市公告书", "publish_date": "2026-07-09", "url": "https://www.szse.cn/disclosure/notice/company/t20260709_621573.html", "shares": 185473692, } } rows = [base.build_one(stock, proof_map) for stock in STOCKS] SUMMARY_DIR.mkdir(parents=True, exist_ok=True) write_json(SUMMARY_DIR / "batch_results.json", {"batch_id": BATCH_ID, "as_of": AS_OF, "items": rows}) (SUMMARY_DIR / "八张图片未评估股票估值批次汇总_20260805.md").write_text(render_summary(rows), encoding="utf-8") screenshot_dir = CASE_ROOT / "screenshots" screenshot_dir.mkdir(parents=True, exist_ok=True) screenshot_items = [] for index, source in enumerate(SCREENSHOTS, 1): target = screenshot_dir / f"候选股票清单截图_{index:02d}.png" shutil.copy2(source, target) screenshot_items.append( { "index": index, "path": str(target.relative_to(ROOT)).replace("\\", "/"), "sha256": hashlib.sha256(target.read_bytes()).hexdigest(), } ) write_json( CASE_ROOT / "batch_manifest.json", { "batch_id": BATCH_ID, "source_rows": 120, "unique_codes": 111, "excluded_non_equity": ["886015 创新药指数", "881140 化学制药指数"], "already_evaluated": 51, "newly_evaluated": len(rows), "screenshots": screenshot_items, "results": rows, }, ) task_lines = [ "# 八张图片股票估值任务清单", "", f"- 批次:`{BATCH_ID}`", "- 原始行:120;去重代码:111;有效股票:109", "- 已有估值:51;本批新增:58;排除行业指数:2", "- 合理区间基准:2026-08-04", "- 汇总:`ana-data/result/股票估值/20260805_batch_missing_image_stocks_valuation/八张图片未评估股票估值批次汇总_20260805.md`", "", "| 序号 | 代码 | 公司 | 判断 | QA | 正式报告 |", "|---:|---|---|---|---|---|", ] for index, row in enumerate(rows, 1): task_lines.append( f"| {index} | {row['ticker']} | {row['company']} | {row['label']} | {row['qa']} | " f"`ana-data/result/股票估值/{row['formal_path']}` |" ) task_lines += ["", "## 完成条件", "", "- [x] 图片去重与证券身份核验", "- [x] 58份正式估值和来源证据", "- [x] 批次汇总、截图归档和机器manifest", ""] (CASE_ROOT / "估值任务清单.md").write_text("\n".join(task_lines), encoding="utf-8") print( json.dumps( { "batch_id": BATCH_ID, "count": len(rows), "labels": {label: sum(row["label"] == label for row in rows) for label in ("偏低", "基本合理", "偏贵", "明显偏贵")}, "qa_errors": sum(row["qa_errors"] for row in rows), "qa_warnings": sum(row["qa_warnings"] for row in rows), }, ensure_ascii=False, ) ) if __name__ == "__main__": main()