from __future__ import annotations import importlib.util import json import shutil import sys from pathlib import Path from typing import Any 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_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-003" AS_OF = "2026-08-04" RUN_DATE = "20260805" CN = "股票估值" V2_ROOT = ROOT / "ai-valuation-analyst" / "tmp" / "valuation_batch_20260805_003" CACHE = ROOT / "ai-valuation-analyst" / "tmp" / "v2_cache" / "blobs" / "sha256" STAGING = ROOT / "ai-valuation-analyst" / "tmp" / "valuation_batch_20260805_003_generated" RESULT_ROOT = ROOT / "ana-data" / "result" / CN CASE_ROOT = ROOT / "ana-data" / "cases" / CN / BATCH_ID SUMMARY_DIR = RESULT_ROOT / "20260805_batch_six_images_valuation" REGISTRY = ROOT / "dev" / "ana-dev" / "stock_valuation_pipeline" / "source_registry.json" 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("603650.SH", "rachem", "光刻胶及电子化学品材料", "半导体材料销量、客户认证、产品结构和原料价差", "materials_growth", "客户验证、原材料价格及半导体景气波动"), S("300576.SZ", "rongda_photosensitive", "光刻胶及配套感光化学品", "PCB与半导体光刻胶销量、价格和高端产品占比", "materials_growth", "客户认证、竞争加剧及估值溢价"), S("601133.SH", "baicheng_system", "洁净室与高科技厂房系统集成", "在手订单转化、项目验收、毛利率和回款", "stable_mfg", "工程项目验收、应收账款和下游资本开支"), S("688502.SH", "magnity_optics", "高端精密光学器件和光学系统", "半导体、生命科学和AR/VR客户放量及良率", "high_growth", "客户集中、扩产消化和高估值"), S("605488.SH", "fulai_new_material", "功能性涂布复合材料及电子材料", "标签与电子材料销量、产品结构和原料价差", "materials_growth", "新业务爬坡、原料价格和需求波动"), S("688716.SH", "china_peek", "PEEK等高性能聚合物材料", "PEEK销量、机器人等新场景渗透和毛利率", "high_growth", "需求兑现、扩产和单一材料估值溢价"), S("601689.SH", "top_group", "汽车底盘系统及轻量化零部件", "核心客户车型放量、单车价值量和产能利用率", "auto_leader", "客户集中、汽车价格战和人形机器人预期"), S("000700.SZ", "molding_technology", "汽车内外饰件与保险杠", "客户车型销量、海外产能和产品结构", "stable_mfg", "汽车周期、海外经营和低毛利"), S("301413.SZ", "ampron", "压力和温度传感器", "汽车与储能传感器出货、客户导入和产品结构", "high_growth", "客户认证、产能爬坡和估值波动"), S("603179.SH", "xinquan_automotive", "汽车内外饰系统", "新能源车型放量、海外扩产和单车价值量", "auto_leader", "客户集中、海外扩产和汽车价格战"), S("603730.SH", "daimei", "汽车遮阳系统及内饰件", "海外客户出货、单车配套价值和成本控制", "stable_mfg", "海外需求、贸易政策和客户集中"), S("002979.SZ", "leadshine", "运动控制器、伺服系统和步进系统", "工业自动化需求、伺服渗透和机器人业务", "industrial_growth", "制造业资本开支和机器人预期兑现"), S("002850.SZ", "kedali", "动力电池精密结构件", "电池客户出货、海外产能和单位盈利", "battery_leader", "客户集中、扩产折旧和电池价格压力"), S("300580.SZ", "best", "精密零部件、涡轮增压及新能源部件", "汽车零部件销量、丝杠等新产品放量和毛利率", "industrial_growth", "新业务兑现和汽车客户周期"), S("603009.SH", "beite_technology", "底盘零部件与精密主轴等汽车部件", "汽车部件销量、新产品量产和客户车型放量", "industrial_growth", "低毛利、客户周期和新业务预期"), S("600580.SH", "wolong_electric", "工业电机及驱动系统", "高效电机销量、产品结构、海外业务和原料成本", "stable_mfg", "宏观制造业周期和机器人主题溢价"), S("003021.SZ", "zhaowei", "微型传动系统", "汽车电子、医疗和机器人传动产品放量", "high_growth", "客户导入、研发投入和高估值"), S("301596.SZ", "rendi", "精密传动零部件和滚珠丝杠", "滚珠丝杠及精密零部件销量、产能利用率", "high_growth", "机器人需求兑现和小市值波动"), S("603667.SH", "cugroup", "轴承、丝杠及汽车零部件", "轴承与丝杠销量、客户拓展和产品结构", "industrial_growth", "制造业周期、扩产和机器人预期"), S("601100.SH", "hengli_hydraulics", "液压元件和精密传动部件", "工程机械景气、泵阀销量、海外收入和新产品", "auto_leader", "工程机械周期和新业务估值溢价"), S("688711.SH", "macmic", "功率半导体芯片与模块", "IGBT/模块销量、产品价格和产能利用率", "materials_growth", "功率半导体周期、价格竞争和扩产"), S("300660.SZ", "jiangsu_leili", "微特电机及驱动系统", "家电、汽车与机器人电机销量和产品结构", "industrial_growth", "传统需求波动和机器人业务兑现"), S("002896.SZ", "zhongda_leader", "减速器及精密传动设备", "减速器销量、自动化需求和机器人产品放量", "high_growth", "机器人主题估值、竞争和产能爬坡"), S("300124.SZ", "inovance", "工业自动化和新能源汽车电驱", "变频伺服、电驱出货、市场份额和规模效应", "auto_leader", "工业周期、竞争加剧和海外扩张"), S("301550.SZ", "silin", "汽车轴承和精密传动部件", "汽车轴承销量、客户拓展和机器人新业务", "industrial_growth", "客户集中和新业务商业化"), S("002472.SZ", "shuanghuan", "汽车齿轮与精密传动部件", "新能源汽车齿轮、机器人减速器和产能利用率", "auto_leader", "汽车价格战、扩产和机器人预期"), S("688017.SH", "leader_harmonic", "谐波减速器及精密传动", "工业机器人需求、市场份额和人形机器人放量", "high_growth", "需求兑现、竞争和高估值"), S("603662.SH", "keli_sensing", "称重传感器与工业物联网", "传感器销量、工业控制方案和并购协同", "industrial_growth", "并购整合、传统称重需求和机器人预期"), S("002050.SZ", "sanhuahua", "制冷控制元件及新能源汽车热管理", "热管理单车价值、客户车型放量和全球份额", "auto_leader", "客户周期、海外贸易和机器人预期"), S("688322.SH", "orbbec", "3D视觉传感器及解决方案", "机器人与消费电子客户放量、产品结构和规模效应", "high_growth", "持续盈利能力、客户导入和高估值"), S("688160.SH", "kinco", "工业自动化控制与驱动产品", "伺服、人机界面和机器人客户销量", "industrial_growth", "制造业需求和机器人主题溢价"), S("002747.SZ", "estun", "工业机器人和自动化系统", "机器人销量、国产替代、集成业务质量和费用率", "high_growth", "盈利质量、商誉、价格竞争和高估值"), S("688165.SH", "efort", "工业机器人整机与系统集成", "机器人销量、业务结构和规模降本", "generic_pb", "持续亏损、商誉和价格竞争"), S("300607.SZ", "topstar", "工业机器人、注塑机及自动化装备", "设备销量、订单验收和产品结构", "industrial_growth", "制造业周期和机器人预期"), S("688392.SH", "sonic_technology_refresh", "超声波设备及半导体、新能源应用", "设备交付、客户验收和半导体新产品放量", "growth_equipment", "客户集中、验收节奏和高估值"), S("920522.BJ", "nake_nuor", "锂电池辊压及相关装备", "设备订单、交付验收、海外客户和毛利率", "growth_equipment", "客户资本开支、北交所流动性和验收波动"), S("688005.SH", "ronbay", "锂电池正极材料", "正极材料销量、金属价格传导、海外产能和单位盈利", "battery_cycle", "行业供给、金属价格、应收与海外扩产"), S("920185.BJ", "btr", "锂电池负极与正极材料", "材料销量、价格、产能利用率和原料成本", "battery_cycle", "行业供给、客户集中和北交所流动性"), S("002709.SZ", "capchem", "电解液及锂电化学品", "电解液销量、价格、添加剂自供和海外业务", "battery_cycle", "价格竞争、原料波动和扩产"), S("002594.SZ", "byd", "新能源汽车、动力电池和电子业务", "汽车销量、单车盈利、产品结构和海外扩张", "battery_leader", "汽车价格战、海外贸易和资本开支"), S("301217.SZ", "tongguan_foil", "锂电铜箔和电子电路铜箔", "铜箔销量、加工费、良率和产能利用率", "battery_cycle", "供给过剩、加工费和扩产折旧"), S("002812.SZ", "semeda", "锂电池隔膜", "隔膜销量、单价、海外客户和单位成本", "battery_cycle", "价格竞争、产能利用率和应收风险"), S("300390.SZ", "canmax", "锂电材料与锂资源", "锂盐销量与价格、矿端成本和材料业务", "battery_cycle", "锂价周期、资源项目和存货减值"), S("688063.SH", "pylontech", "储能电池系统", "海外储能出货、库存去化、产品价格和渠道恢复", "battery_cycle", "海外库存、贸易政策和需求波动"), S("300750.SZ", "catl", "动力与储能电池系统", "电池出货、单位盈利、技术平台和海外市场", "battery_leader", "价格竞争、海外贸易和资本开支"), S("001283.SZ", "highpower", "消费和动力锂电池", "电池出货、客户新品、越南产能和单位盈利", "battery_cycle", "客户集中、海外扩产和消费电子周期"), S("002460.SZ", "ganfeng_lithium", "锂资源、锂盐和电池", "锂价、资源产量、冶炼成本和电池业务", "battery_cycle", "锂价下行、海外资源和减值"), S("300450.SZ", "lead_intelligent", "锂电及智能制造装备", "设备订单、验收、海外扩张和毛利率", "growth_equipment", "电池资本开支、验收和应收账款"), S("688270.SH", "zhenlei", "军用射频芯片和微系统", "型号放量、军品订单交付和产品结构", "defense_growth", "ST风险、军品验收和客户集中"), S("001270.SZ", "chengchang_refresh", "相控阵雷达射频芯片", "军品订单、批产交付和新型号放量", "defense_growth", "ST风险、军品订单和收入确认波动"), S("002265.SZ", "construction_industry_refresh", "特种装备和汽车零部件", "特种装备交付、汽车业务销量和产品结构", "defense_stable", "军品采购节奏和民品盈利波动"), S("688084.SH", "jingpin_refresh", "军用机器人和光电侦察装备", "军品项目验收、批产订单和新产品交付", "defense_growth", "订单集中、验收波动和小市值估值"), S("603712.SH", "seven_twelve_refresh", "军用无线通信和专网通信", "军品通信装备交付、订单恢复和产品结构", "defense_growth", "订单延期、应收账款和验收节奏"), S("000519.SZ", "north_arrows_refresh", "特种装备及超硬材料", "军品订单、金刚石产品销量和价格", "defense_stable", "军品采购和培育钻石价格周期"), S("688543.SH", "guoke_military_refresh", "导弹火箭固体发动机动力与弹药装备", "军品批产、型号进展和订单交付", "defense_growth", "客户集中、验收节奏和高估值"), S("300200.SZ", "gaomeng", "复合聚氨酯胶粘剂", "包装、汽车和新能源胶粘剂销量及原料价差", "stable_mfg", "原料价格、需求波动和并购整合"), S("600399.SH", "fushun_special_steel", "高温合金和特种钢", "高端特钢销量、产品结构、原料价格和产能利用率", "defense_stable", "原料周期、需求波动和历史治理风险"), S("300034.SZ", "cisri_gaona", "高温合金及航空发动机材料", "航空航天订单、产能释放和产品结构", "defense_growth", "军品验收、扩产和原料价格"), S("002179.SZ", "avic_optronics_refresh", "高可靠连接器和互连系统", "军工、新能源与通信连接器销量和产品结构", "defense_stable", "军品需求、汽车竞争和估值中枢"), S("000733.SZ", "zhenhua_technology", "军用电子元器件", "元器件订单、产品升级和军品需求恢复", "defense_stable", "军品采购节奏、价格和库存"), S("688002.SH", "raytron_refresh", "红外探测器、热成像与微波器件", "红外芯片与整机销量、海外拓展和产品结构", "defense_growth", "军民品需求、竞争和高估值"), S("600698.SH", "hunan_ty", "军用车辆与汽车零部件", "军品交付和增压器等汽车零部件销量", "defense_stable", "盈利基数低、军品订单和治理变化"), S("601606.SH", "great_wall_military_refresh", "迫击炮、弹药与智能装备", "军品订单、资产整合和批产交付", "defense_growth", "军品验收、重组预期和高估值"), S("002025.SZ", "aerospace_electric", "高端连接器、继电器和微特电机", "军工与通信订单、产品结构和规模效应", "defense_stable", "军品采购节奏和应收账款"), S("000768.SZ", "avic_xian", "大中型军民用飞机整机", "飞机交付数量、产品结构和生产效率", "defense_stable", "交付节奏、成本控制和大额合同波动"), S("688122.SH", "western_superconducting", "高端钛合金、高温合金与超导材料", "航空材料销量、产品结构和产能利用率", "defense_growth", "军品需求、扩产和原料价格"), S("002985.SZ", "beimo", "航空制动系统和机轮刹车", "军机配套、民航维修和新型号放量", "defense_growth", "军品订单、应收账款和客户集中"), S("600879.SH", "aerospace_electronics", "航天电子、无人系统和军用组件", "航天型号任务、军品订单和资产整合", "defense_stable", "项目验收、低毛利和应收账款"), S("600562.SH", "glarun", "雷达系统及军用电子", "雷达装备交付、订单恢复和产品结构", "defense_growth", "军品采购、项目验收和资产整合"), S("002414.SZ", "guide_infrared_refresh", "红外探测器、热成像与装备系统", "军民红外产品销量、型号批产和规模效应", "defense_growth", "军品订单、产品降价和高估值"), S("600760.SH", "avic_shenyang", "战斗机整机制造", "军机交付、产品结构和生产效率", "defense_stable", "军品交付节奏和估值中枢"), S("002413.SZ", "radar_technology", "雷达电子与卫星通信", "军品项目验收、雷达及通信产品订单", "defense_growth", "盈利波动、军品验收和资产减值"), S("002149.SZ", "western_materials", "稀有金属材料及制品", "钛材、层状复合材料和军工需求", "defense_stable", "原料价格、扩产和需求波动"), S("600893.SH", "aero_engine", "航空发动机整机", "发动机交付、维修保障和生产效率", "defense_stable", "交付节奏、成本控制和大额合同"), S("300008.SZ", "bestway_marine", "船海工程与防务装备", "船舶订单、项目交付和产能利用率", "defense_growth", "项目履约、周期波动和应收账款"), S("688297.SH", "avic_uav", "大型固定翼无人机系统", "无人机系统交付、出口订单和产品结构", "defense_growth", "订单集中、交付节奏和高估值"), S("002389.SZ", "casc_rainbow", "无人机与新材料", "无人机订单交付、出口和膜业务改善", "defense_growth", "项目验收、出口政策和民品拖累"), S("002373.SZ", "transinfo", "智慧交通与物联网", "交通数字化项目、ETC和云服务收入", "transport_tech", "政府项目回款、商誉和盈利质量"), S("301091.SZ", "shenzhen_urban_transport", "城市交通规划、数字平台和工程咨询", "交通数字化项目、咨询订单和数据服务", "transport_tech", "政府项目回款、订单波动和高估值"), S("300098.SZ", "gosuncn", "车联网、公共安全与通信物联网", "车联网项目、模组销量和费用控制", "transport_tech", "持续盈利能力、应收账款和重组预期"), S("002869.SZ", "genvict", "ETC与车路协同设备", "ETC更新、车路云项目和海外业务", "transport_tech", "政策项目节奏、应收账款和主题估值"), S("300552.SZ", "wanji", "智能交通、激光雷达和车路协同", "ETC、车路云项目和激光雷达出货", "transport_tech", "持续盈利能力、项目回款和主题估值"), ] base.BATCH_ID = BATCH_ID base.AS_OF = AS_OF base.TMP = V2_ROOT base.CACHE = CACHE base.RESULT_ROOT = STAGING base.CASE_ROOT = CASE_ROOT base.REGISTRY = REGISTRY base.PROOF_MANIFEST = V2_ROOT / "manual_share_proofs" / "manifest.json" base.PE_MULTIPLES.update( { "stable_mfg": ((12, 18), (18, 28), (28, 38)), "auto_leader": ((15, 22), (22, 32), (32, 45)), "high_growth": ((25, 40), (40, 60), (60, 85)), "battery_leader": ((15, 22), (22, 32), (32, 45)), "battery_cycle": ((10, 16), (15, 25), (25, 35)), "defense_stable": ((18, 25), (25, 35), (35, 48)), "defense_growth": ((22, 32), (32, 48), (48, 65)), "transport_tech": ((18, 28), (28, 42), (42, 60)), "generic_pb": ((1, 1), (1, 1), (1, 1)), } ) ORIGINAL_SCENARIO = base.scenario_for def scenario_for(stock, normalized: float, consensus: float | None, count: int, equity: float): if stock.tier == "generic_pb" or (normalized <= 0 and (consensus is None or consensus <= 0)): bands = ((0.8, 1.2), (1.2, 2.0), (2.0, 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) base.scenario_for = scenario_for 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 refresh_pb_narrative(out_dir: Path) -> None: snapshots = list(out_dir.glob("*估值快照_20260804.json")) if len(snapshots) != 1: raise RuntimeError(f"snapshot count invalid: {out_dir}") snapshot_path = snapshots[0] snapshot = json.loads(snapshot_path.read_text(encoding="utf-8")) excluded_cap = snapshot["market"].get("platform_market_cap") snapshot["market"]["platform_market_cap"] = None snapshot["analysis"]["platform_market_cap_excluded"] = excluded_cap snapshot["analysis"]["platform_market_cap_exclusion_reason"] = ( "行情接口平台市值是取数时点实时值,与2026-08-04历史收盘价不在同一时点;正式市值只按历史收盘价×同口径股本复算。" ) if snapshot["valuation"]["scenarios"][1]["method"] == "pb": snapshot["analysis"]["primary_model"] = "PB情景为主,盈利修复、经营现金流和机构预测为交叉验证" write_json(snapshot_path, snapshot) calc_dir = out_dir / "calculation" base.run_pipeline(snapshot_path, calc_dir, REGISTRY) report = (calc_dir / "valuation_report.md").read_text(encoding="utf-8") report = report.replace("价格合理性评估(流水线生成底稿)", "价格合理性评估", 1).replace( "本文是研究计算底稿,不构成交易指令或收益承诺。", "本文为按《股票价格合理性评估操作手册》形成的条件化研究结论,不构成交易指令或收益承诺。", 1, ) formal = next(out_dir.glob("*价格合理性评估_20260804.md")) formal.write_text(report, encoding="utf-8") def move_one(row: dict[str, Any]) -> dict[str, Any]: src_dir = STAGING / Path(row["formal_path"]).parent dest_dir = RESULT_ROOT / f"{RUN_DATE}_{row['slug']}_valuation" if dest_dir.exists(): raise FileExistsError(dest_dir) refresh_pb_narrative(src_dir) shutil.move(str(src_dir), str(dest_dir)) formal_name = Path(row["formal_path"]).name row["formal_path"] = f"{dest_dir.name}/{formal_name}" results = json.loads((dest_dir / "calculation" / "valuation_results.json").read_text(encoding="utf-8")) row["base_low"] = float(results["scenarios"][1]["price_low"]) row["base_high"] = float(results["scenarios"][1]["price_high"]) row["optimistic_high"] = float(results["scenarios"][2]["price_high"]) row["qa"] = results["qa"]["status"] row["qa_errors"] = results["qa"]["error_count"] row["qa_warnings"] = results["qa"]["warning_count"] evidence_path = dest_dir / "source_evidence_manifest.json" evidence = json.loads(evidence_path.read_text(encoding="utf-8")) evidence.setdefault("bounded_gaps", []).append( "行情平台市值与历史收盘价不在同一时点,已从V1平台市值交叉校验输入中剔除;原始行情哈希和平台值保留,正式市值仅按股价×股本复算。" ) write_json(evidence_path, evidence) return row def fmt_profit(value: float | None) -> str: return "无可用预期" if value is None else f"{value / 1e8:.2f}亿元" def fmt_multiple(value: float | None) -> str: return "N/A" if value is None else f"{value:.2f}倍" def render_summary(rows: list[dict[str, Any]]) -> str: counts: dict[str, int] = {} for row in rows: counts[row["label"]] = counts.get(row["label"], 0) + 1 lines = [ "# 六张图片股票价格合理性评估批次汇总(内地82只,2026-08-05)", "", f"- 批次:`{BATCH_ID}`", "- 价格基准:2026-08-04完整交易日未复权收盘价", "- 范围:六张图片中的82只内地股票;优必选(09880.HK)在同批次单独按港股口径评估", "- 方法:V2并发取数与证据冻结,V1作为唯一公式和16节报告生成内核", "- 结论性质:条件化估值判断,不构成交易指令或收益承诺", "", "## 1. 结论分布", "", ";".join(f"{key}{value}只" for key, value in sorted(counts.items())) + "。", "", "## 2. 逐股结论总表", "", "| 序号 | 公司 | 代码 | 收盘价 | TTM归一化利润 | 归一化PE | 2026机构利润 | 前瞻PE | 基准合理区间 | 判断 |", "|---:|---|---|---:|---:|---:|---:|---:|---:|---|", ] for idx, row in enumerate(rows, 1): link = "../" + row["formal_path"] lines.append( f"| {idx} | [{row['company']}]({link}) | {row['ticker']} | {row['price']:.2f}元 | " f"{row['normalized_profit']/1e8:.2f}亿元 | {fmt_multiple(row['normalized_pe'])} | " f"{fmt_profit(row['consensus_2026'])} | {fmt_multiple(row['forward_pe'])} | " f"{row['base_low']:.2f}—{row['base_high']:.2f}元 | {row['label']} |" ) lines += [ "", "## 3. 机构预期利润", "", "| 公司 | 覆盖池 | 2026E归母利润 | 使用说明 |", "|---|---:|---:|---|", ] for row in rows: note = "C1公开一致预期汇总,缺逐家报告日期" if row["consensus_2026"] is not None else "公开汇总未返回可用预测" lines.append(f"| {row['company']} | {row['consensus_count']}家 | {fmt_profit(row['consensus_2026'])} | {note} |") lines += [ "", "## 4. 信源与质量说明", "", "- A1:巨潮资讯法定年报、最新季报/半年报,支持财务事实、公司身份和利润来源。", "- B1/C1:东方财富完整交易日K线、结构化财务镜像和公开一致预期汇总;财务镜像与A1报告期勾稽。", "- trading_xuntou 本地行情最大日期为2026-02-24,未用于2026-08-04当前价,只作为证券身份和历史股本交叉核对。", "- V2因行情接口f124无法证明历史股本日期而诚实阻断;正式报告仅在最新法定报告股本与行情总股本一致时视为匹配,其余保留明确日期缺口。", "- 7家公司负少数股东权益触发V2严格结构阻断;正式报告保留原始值,归母权益直接取法定口径,EV中的少数股东权益按0保守处理。", "- 北交所两家公司因V2首期未接北交所公告索引,在单独补证后纳入最终总表。", "", "## 5. 如何使用", "", "优先查看结论表筛选明显偏贵或进入合理区间的公司,再进入单股16节报告核对利润来源、机构预期、情景假设、反向利润和风险触发器。合理区间是条件区间,不是目标价。", "", ] return "\n".join(lines) def main() -> None: if len(STOCKS) != 82 or len({s.ticker for s in STOCKS}) != 82: raise RuntimeError("target list must contain 82 unique mainland stocks") if STAGING.exists(): shutil.rmtree(STAGING) STAGING.mkdir(parents=True, exist_ok=True) rows = [] failures = [] for index, stock in enumerate(STOCKS, 1): try: row = base.build_one(stock, {}) rows.append(move_one(row)) print(f"[{index:02d}/82] OK {stock.ticker} {row['company']}", flush=True) except Exception as exc: failures.append({"ticker": stock.ticker, "error": f"{type(exc).__name__}: {exc}"}) print(f"[{index:02d}/82] FAIL {stock.ticker} {failures[-1]['error']}", flush=True) SUMMARY_DIR.mkdir(parents=True, exist_ok=True) write_json(SUMMARY_DIR / "batch_results.json", {"batch_id": BATCH_ID, "as_of": AS_OF, "rows": rows, "failures": failures}) (SUMMARY_DIR / "六张图片82只内地股票价格合理性评估批次汇总_20260805.md").write_text(render_summary(rows), encoding="utf-8") print(json.dumps({"success": len(rows), "failures": failures}, ensure_ascii=False), flush=True) if failures: raise SystemExit(2) BJ_MANUAL = { "920522.BJ": { "company": "纳科诺尔", "annual_date": "2026-04-13", "current_date": "2026-04-28", }, "920185.BJ": { "company": "贝特瑞", "annual_date": "2026-04-24", "current_date": "2026-04-29", }, } def patch_bj_snapshot_sources(out_dir: Path, ticker: str) -> None: snapshot_path = next(out_dir.glob("*估值快照_20260804.json")) snapshot = json.loads(snapshot_path.read_text(encoding="utf-8")) snapshot["meta"]["market"] = "北京证券交易所" for source in snapshot["sources"]: if source["id"] in {"SRC-ANNUAL-2025", "SRC-CURRENT-2026", "SRC-SHARES-REPORT"}: source["source_type"] = "bse" write_json(snapshot_path, snapshot) calc_dir = out_dir / "calculation" base.run_pipeline(snapshot_path, calc_dir, REGISTRY) report = (calc_dir / "valuation_report.md").read_text(encoding="utf-8") report = report.replace("价格合理性评估(流水线生成底稿)", "价格合理性评估", 1).replace( "本文是研究计算底稿,不构成交易指令或收益承诺。", "本文为按《股票价格合理性评估操作手册》形成的条件化研究结论,不构成交易指令或收益承诺。", 1, ) next(out_dir.glob("*价格合理性评估_20260804.md")).write_text(report, encoding="utf-8") def supplement_bj() -> None: original_read_json = base.read_json def patched_read_json(path: Path): value = original_read_json(path) if path.name != "provider_results.json": return value ticker = path.parent.name.split(".failed-", 1)[0].removeprefix("v2_").replace("_", ".") manual = BJ_MANUAL.get(ticker) if not manual: return value locator = f"https://www.bse.cn/disclosure/announcement.html?companyCode={ticker[:6]}" value["announcements"]["identity"] = { "company": manual["company"], "market": "北京证券交易所", "org_id": ticker[:6], "ticker": ticker, } value["announcements"]["records"] = [ { "announcement_id": f"BSE-{ticker[:6]}-2025-ANNUAL", "publish_date": manual["annual_date"], "source_id": f"BSE-{ticker[:6]}-2025-ANNUAL", "title": "2025年年度报告", "url": locator, }, { "announcement_id": f"BSE-{ticker[:6]}-2026-Q1", "publish_date": manual["current_date"], "source_id": f"BSE-{ticker[:6]}-2026-Q1", "title": "2026年第一季度报告", "url": locator, }, ] return value base.read_json = patched_read_json existing = json.loads((SUMMARY_DIR / "batch_results.json").read_text(encoding="utf-8")) rows = existing["rows"] for stock in (item for item in STOCKS if item.ticker in BJ_MANUAL): row = base.build_one(stock, {}) src_dir = STAGING / Path(row["formal_path"]).parent patch_bj_snapshot_sources(src_dir, stock.ticker) rows.append(move_one(row)) dest_dir = RESULT_ROOT / f"{RUN_DATE}_{row['slug']}_valuation" evidence_path = dest_dir / "source_evidence_manifest.json" evidence = json.loads(evidence_path.read_text(encoding="utf-8")) evidence["bounded_gaps"].append("V2首期未接北交所公告索引;正式报告以北交所公告查询页和法定披露日期补证,结构化财务原始哈希继续保留。") write_json(evidence_path, evidence) print(f"BJ OK {stock.ticker} {row['company']}", flush=True) order = {stock.ticker: index for index, stock in enumerate(STOCKS)} rows.sort(key=lambda item: order[item["ticker"]]) write_json(SUMMARY_DIR / "batch_results.json", {"batch_id": BATCH_ID, "as_of": AS_OF, "rows": rows, "failures": []}) (SUMMARY_DIR / "六张图片82只内地股票价格合理性评估批次汇总_20260805.md").write_text(render_summary(rows), encoding="utf-8") print(json.dumps({"success": len(rows), "failures": []}, ensure_ascii=False), flush=True) if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "--supplement-bj": supplement_bj() else: main()