Cai
2026-08-25 e68fa20b12df791a74cc31cd1cd84fd623b65152
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
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()