nav-review-report
- 简介
- 基于采集数据与截图,串行生成现金、成本及损益三份交互式HTML复核报告,并上传至iGowin系统。
- 触发词
- 生成NAV报告,出NAV复核报告,上传iGowin
- 分发渠道
- ARK Engine
- 功能测试
- ✅ 通过 · 业务评审:✅ 通过
- 技能包文件
- nav-review-report/SKILL.md、nav-review-report/SKILL.md.bak、nav-review-report/SKILL_backup.md、nav-review-report/references/cash_review_template.md、nav-review-report/references/distribution_framework.md、nav-review-report/references/div_income_review_template.md、nav-review-report/references/igowin_upload.md、nav-review-report/references/inv_cost_review_template.md …共12个文件
SKILL.md 全文
Frontmatter
| name | nav-review-report |
|---|---|
| description | | |
--- name: nav-review-report description: 'NAV 复核第三阶段:基于 nav-review-identify 的 profile 与 nav-review-collect 的数据/截图,生成三份独立交互式 HTML 报告(Cash Review / Investment Cost Review / Investment Unrealized G/L Review),并分别上传至 iGowin 系统对应 subject。HTML 报告采用三栏布局(左侧导航栏 + 中间复核数据 + 右侧 Supporting 截图面板),截图以 base64 内嵌确保完全自包含。触发场景:被 nav-review 主调度 skill 调用,或用户单独要求"生成 NAV 报告"、"出 NAV 复核报告"、"上传到 iGowin"。' ---NAV Review Stage 3 — 三份 HTML 报告生成与上传
⛔ 模板强制遵循规则(最高优先级,不可跳过)
生成每份 HTML 报告前,必须先
read_file加载对应的 HTML 模板文件,然后严格按照模板的完整结构生成报告。禁止自行设计布局、样式或交互方式。| 报告 | 模板文件(必须 read_file 加载) | 主题色 | |------|------|--------| | Cash Review |
nav-review-cash-report/references/cash_review_html_template.html| 青色 #4fc3f7 | | Investment Cost Review |nav-review-cost-report/references/inv_cost_review_html_template.html| 橙色 #ff9800 | | Investment UGL Review |nav-review-ugl-report/references/inv_ugl_review_html_template.html| 紫色 #9c27b0 |强制要求: 1. 三栏布局(左侧导航 + 中间复核面板 + 右侧 Supporting 面板)不可简化为单栏或双栏 2. 交互式 JS(点击行 → 右侧面板切换证据、双击截图 → Lightbox 预览)不可省略 3. 每份报告的 Section 结构必须与模板定义一致,不得省略或重排 4. 生成简单单页文档式报告即为失败,必须重新按模板生成 5. Supporting 面板必须包含高清截图(base64 内嵌,红框高亮关键数字):截图范围按数据密度决定——数据集中在1-2行时裁剪标题+关键行;跨多行/多页时保留整页 6. 涉及测算时必须输出计算过程:在 Supporting 面板最下方用
.calc-block(混合样式:标题 + 等宽字体逐步展开 + 绿色高亮结果)展示完整计算步骤,每个输入值标注来源文件 7. ⚠️ Supporting 证据来源限制(强制):只有第三方/外部独立文件才能作为 Supporting 证据。基金自身内部文件严禁作为 Supporting:
- ❌ 禁止:Investment Position Appraisal (IPA/N4)、Trial Balance (TB) / TB Detail、Purchases and Sales Report、Nominal Ledger / Detailed General Ledger、Statement of Operations (SOFI)、Change in Unrealized Gains Losses Report(N4系统生成)
- ✅ 允许:银行对账单(如 DBS Bank Statement)、基金行政管理人报表(如 Citco Market Value Statement、Citco Capital Account Statement)、交易合同/确认函(如 Contract Note)、银行付款确认(如 Cash Payment Confirmation)、托管人报告
- 核对逻辑:中间面板的复核数据可引用 IPA/TB 数字进行对比,但右侧 Supporting 面板只展示第三方证据截图来佐证这些数字的正确性
---
When to Use
- 由
nav-review主调度 skill 在 Stage 3 自动调用 - 用户单独说"生成 NAV 报告"、"出 NAV 复核报告"、"上传到 iGowin"
- 已存在
<target_dir>/.cortex/nav_profile.json与<target_dir>/.cortex/nav_data/与<target_dir>/.cortex/screenshots/
本 skill 生成三份交互式 HTML 报告 + 三次 iGowin 上传。HTML 即为最终交付物,不再生成 Word 或 PDF。
---
输入
target_dir:基金期间文件夹绝对路径- profile:
<target_dir>/.cortex/nav_profile.json - 数据:
<target_dir>/.cortex/nav_data/*.json - 截图:
<target_dir>/.cortex/screenshots/{cash,inv_cost,inv_ugl}/*.png
输出
| 报告 | HTML 文件名 | iGowin Subject |
|------|------------|----------------|
| Cash Review | Cash Review - [基金名] [YYYYMMDD].html | Cash Long(或基金 Cash Subject) |
| Investment Cost Review | Investment Cost Review - [基金名] [YYYYMMDD].html | Investments Long 中的 Cost 子科目 |
| Investment Unrealized G/L Review | Investment Unrealized GL Review - [基金名] [YYYYMMDD].html | Unrealized G/L 子科目 |
三份 HTML 输出至 <target_dir>/ 根目录(用户双击可直接在浏览器中打开),并作为 iGowin 系统上传的最终交付物。中间脚本放 <target_dir>/.cortex/。
---
外置文件索引
| 文件 | 路径 | 加载时机 |
|------|------|---------|
| Cash Review 模板 | references/cash_review_template.md | Report 1 启动时 read_file |
| Investment Cost Review 模板 | references/inv_cost_review_template.md | Report 2 启动时 read_file |
| Investment UGL Review 模板 | references/inv_ugl_review_template.md | Report 3 启动时 read_file |
| Distribution 五步框架 + JE 矩阵 | references/distribution_framework.md | 按需:仅当存在 Distribution 时 read_file |
| Dividend Income Review 模板 | references/div_income_review_template.md | Report 4 启动时 read_file(按需) |
| iGowin 上传流程 | references/igowin_upload.md | Stage 3b 启动时 read_file |
所有路径相对于 skill 目录。
---
统一 HTML 三栏交互式布局规范(三份报告均遵守)
HTML 报告为最终交付物,必须采用响应式、现代化三栏交互式设计,提供极佳的屏幕核对体验:
1. 结构与排版布局
- 最左侧 - 导航栏 (
.sidebar): - 固定在左侧 (
position: fixed; width: 240px; height: 100vh; background: #1E293B; color: #CBD5E1;)。 - 包含导航标题(基金名称和 NAV 日期)以及各 Section 的快速定位链接(Section 数量随 period_type 与报告类型动态调整)。
- 当前活动 Section 链接高亮(
background: #334155; color: #F1F5F9; border-left: 3px solid #60A5FA;)。 - 底部显示报告生成时间戳。
- 中间 - 复核结果主面板 (
.main): - 位于导航右侧 (
margin-left: 240px; margin-right: 520px; padding: 24px 28px; min-width: 600px;)。 - 展示逐项复核表格、逻辑分析段落和 PASS/FAIL 结论。
- ⚠️ 强制规则:所有数据行必须与 Supporting 一一对应:每个 Section 的每一行数据都必须添加
.interactive-row类,并设置data-supporting-id="[ID]"属性,指向右侧 Supporting 面板中对应的证据项。不允许出现无 Supporting 映射的数据行。 - 最右侧 - Supporting 证据面板 (
.supporting-panel): - 宽度
520px,固定在右侧 (position: fixed; right: 0; top: 0; width: 520px; height: 100vh; overflow-y: auto; background: #F1F5F9; border-left: 2px solid #CBD5E1; padding: 16px;)。 - 默认可见:Supporting 面板始终展示在右侧,不需要点击按钮切换显示/隐藏。用户点击左侧数据行时,面板内容切换到对应证据。
- 集中展示复核截图。每个截图容器为
.supporting-item,并带有唯一的id(与主面板行的data-supporting-id对应)。 - 默认隐藏其他
.supporting-item,仅激活当前点击行对应的 Supporting 截图(.active类控制显示)。 - 面板顶部显示标题"Supporting 证据",初始状态显示提示文字"← 点击左侧数据行查看对应证据"。
2. 交互体验设计 (Vanilla JS)
- 页面加载时默认激活并高亮第一个有 Supporting 的表格行,并在右侧面板展示对应的截图。
- 当用户点击主面板中的
.interactive-row时,通过 JS 移除先前的激活状态,在当前点击行添加.highlighted(浅蓝色背景与蓝色边框),并在右侧面板中平滑滚动并展示对应的 Supporting 截图 (scrollIntoView({ behavior: 'smooth' }))。 - 全行覆盖强制规则:报告中所有 Section 的每一行数据都必须是
.interactive-row,包括但不限于表格数据行、核对汇总行、结论行等。 - 双击图片 → 全屏 Lightbox 预览(含 −/倍数/+/Reset 工具栏 + 滚轮缩放 + 拖拽平移):Supporting 面板中的截图默认以正常尺寸内嵌显示,hover 时光标变为
zoom-in提示可放大。双击任一截图弹出全屏半透明遮罩 lightbox(position: fixed; inset: 0; background: rgba(0,0,0,0.88)),图片居中展示。遮罩顶部固定一个工具栏(<div class="lightbox-toolbar">),含−、当前倍数(如100%)、+、Reset、✕五个元素。按钮+/−以 0.25× 步进调节 lightbox 内图片大小(范围 0.5×~4×,初始 1×);Reset恢复 1× 并清空平移偏移;✕、点击遮罩空白处或按Esc关闭 lightbox。在遮罩内使用鼠标 滚轮缩放(步进 0.1×,以光标点为锚点保持视觉中心);图片放大超出可视区时,按住左键可上下左右拖拽平移(cursor 变为grab/grabbing)。 - 左侧导航栏滚动跟随:监听中间面板的
scroll事件,使用IntersectionObserver检测当前可见 Section,自动更新导航栏中对应链接的高亮状态。
3. 颜色与视觉风格
- 主题背景:
#F8FAFC。 - 文字字体:
"Segoe UI", "Microsoft YaHei", Arial, sans-serif。 - 结论横幅:
- PASS:背景
#E2EFDA,文字#375623,左边框 5px 实线#548235 - FAIL:背景
#FCE4D6,文字#C65911,左边框 5px 实线#C65911 - 表格样式:边框
#E2E8F0,表头背景#F1F5F9加粗,奇偶行交替背景。 - 导航栏链接 hover:
background: #475569; border-radius: 4px;。
4. 截图内嵌强制规则
所有截图必须以 base64 data URI 内嵌到 HTML 文件中,禁止使用相对路径或外部文件引用。实现方式:
python import os, base64
def embed_image(base_dir, folder, filename): """读取截图文件并返回 base64 data URI""" path = os.path.join(base_dir, ".cortex", "screenshots", folder, filename) if os.path.exists(path): with open(path, "rb") as f: data = base64.b64encode(f.read()).decode("utf-8") return f"data:image/png;base64,{data}" return ""
def embed_all_pages(base_dir, folder, prefix): """读取所有匹配 prefix_pageN.png 的截图并返回 base64 data URI 列表""" shot_dir = os.path.join(base_dir, ".cortex", "screenshots", folder) pages = [] if os.path.isdir(shot_dir): for f in sorted(os.listdir(shot_dir)): if f.startswith(prefix) and f.endswith(".png"): pages.append((f, embed_image(base_dir, folder, f))) return pages
HTML 中使用:html <div class="sup-section-label">期末数据取值 — Prior IPA</div> <img src="..." alt="Prior IPA Page 1"> <img src="..." alt="Prior IPA Page 2"> <div class="sup-section-label">期间发生额 — Investment Movement</div> <img src="..." alt="Investment Movement Summary"><img src="{embed_image(BASE, 'inv_cost', 'prior_ipa.png')}" alt="Prior IPA">理由:内嵌截图使 HTML 文件完全自包含,双击即可在任何位置打开查看,不依赖.cortex/目录结构。上传至 iGowin 后也能直接在浏览器中完整展示,无需额外文件。5. 多页截图完整展示强制规则
当源 PDF 被转换为多页 PNG 截图时(如
prior_ipa_page1.png、prior_ipa_page2.png),必须全部嵌入到 Supporting 面板中,禁止只展示第一页而遗漏后续页面。使用embed_all_pages()函数遍历所有匹配页面并逐一嵌入。6. 截图清晰度强制规则
使用 PyMuPDF (fitz) 渲染 PDF 页面为 PNG 时,必须使用 3x 或更高 zoom(
fitz.Matrix(3, 3)或fitz.Matrix(4, 4)),确保文字和数字在浏览器中清晰可读。2x zoom 不足以保证小字清晰度。7. Supporting 面板分段标题规则
多张截图在同一
.supporting-item中时,使用分段标题将截图分组:
分段标题样式:python import os, json, base64padding:6px 10px; font-size:11px; color:#475569; background:#F8FAFC; border-bottom:1px solid #E2E8F0; border-top:1px solid #E2E8F0; font-weight:600; margin-top:8px;---
执行流程:Stage 3a 串行生成 3 份 HTML 报告 + Stage 3b 串行上传
⚠️ 串行顺序:Cash Review → Investment Cost Review → Investment UGL Review。与主调度 skill 一致,避免并行导致的 profile / nav_data 读取冲突。Stage 3a:串行生成 HTML(Cash → Cost → UGL)
每份报告独立: 1. 读取 profile + 对应 nav_data JSON + 对应 screenshots/ 2. 用 Python 脚本拼装三栏交互式 HTML(遵循上述统一布局规范) 3. 所有截图以 base64 data URI 内嵌,HTML 完全自包含
Python 脚本模板(每份报告共用骨架)
BASE = r"<target_dir>" PROFILE = json.load(open(os.path.join(BASE, ".cortex", "nav_profile.json"), encoding="utf-8"))
def embed_image(base_dir, folder, filename): path = os.path.join(base_dir, ".cortex", "screenshots", folder, filename) if os.path.exists(path): with open(path, "rb") as f: data = base64.b64encode(f.read()).decode("utf-8") return f"data:image/png;base64,{data}" return ""
def embed_all_pages(base_dir, folder, prefix): shot_dir = os.path.join(base_dir, ".cortex", "screenshots", folder) pages = [] if os.path.isdir(shot_dir): for f in sorted(os.listdir(shot_dir)): if f.startswith(prefix) and f.endswith(".png"): pages.append((f, embed_image(base_dir, folder, f))) return pages
---Report 1:Cash Review
读取:
nav_data/tb.json、cash_balances.json、bank_transactions.json、nominee_cashflow.json、payment_log.json、summary_log.json启动时加载:
read_file("references/cash_review_template.md")→ 获取全部 8 个 Section 模板 + 性质标签表 + 溯源决策树 + Multi-invoice 规则。左侧导航栏 Section 列表(根据 profile 动态调整):
- Section 1: Cash Balance Check
- Section 2~4: 银行流水逐月梳理(quarterly = 三个月 + Subsequent 一月;monthly = 当月 + Subsequent 一月)
- Section 5: Period Summary(quarterly 必须,monthly 可省)
- Section 6: Nominee 子账户(仅
has_nominee == true时显示) - Section 7: Payment Log Evidence
- Section 8: Conclusion
右侧 Supporting 面板截图映射:
- Section 1 行 →
cash/cash_balance_summary.png - Section 2~4 各月交易行 → 对应
cash/bank_statement_<month>.png、cash/payment_log_<PN>.png - Section 6 行 →
cash/nominee_*.png - Section 7 行 →
cash/payment_log.png
Cash Review 强制总则(不可破坏):
1. Cash Appraisal 余额检查必须放在报告最开头(Section 1),格式:Total Fund Balance = HSBC (Main Bank) + Nominee (Ark/Noah) + 其他账户,每个账户期末余额单列。
2. 覆盖期内全部月份 —— monthly 报告 = 当月 + Subsequent 一月;quarterly 报告 = 整季三个月 + Subsequent 一月,每月独立子节,不得只展示季末月。
3. Nominee 现金账项每月一节 —— 无现金活动月份明确标注"本月無現金活動,不適用"。
4. 绝不用 Journal Entry(JE)作为支撑证据。
截图禁忌:
- 绝不把 JE 作为支撑证据
- 可接受来源:Payment Log 行、PI 文件、银行对账单、标的基金 Capital Call / Distribution Notice、Nominee/Broker statement
- Payment Log 截图必出:任何经 Payment Log 溯源的交易,对应 PN 行必须有 PNG
---
Report 2:Investment Cost Review
读取:nav_data/sofi.json(或 ipa.json)、prior_cost.json、investment_movement.json、investment_support.json、mmf_reinvestment.json、payment_log.json、distribution_je.json
启动时加载:read_file("references/inv_cost_review_template.md") → 获取全部 5 个 Section 模板 + Cost Movement Decision Tree + Subscriptions In Advance 决策树。
左侧导航栏 Section 列表:
- Section 1: Portfolio Overview
- Section 2: MMF Cost Movement(仅
has_mmf == true) - Section 3: Partnership / HF Cost Movement(仅
has_partnership或has_hedge_fund) - Section 4: Subscriptions In Advance(仅有未确认认购时)
- Section 5: Conclusion
右侧 Supporting 面板截图映射:
- Portfolio Overview 各行 → 上期/当期 IPA 截图 + Investment Movement Summary + 各活动原始 Notice
- MMF 行 →
inv_cost/mmf_holdings_.png、inv_cost/mmf_reinvestment_.png - Partnership/HF 行 →
inv_cost/capital_call_.png、inv_cost/distribution_.png、inv_cost/ilpa_section_c_*.png - 汇总行 →
inv_cost/investment_movement.png、inv_cost/prior_ipa.png、inv_cost/current_ipa.png
Investment Cost Review 强制总则(不可破坏):
1. IPA / TB / FS Pack 不可自证:cost 与 movement 的原始证据必须来自第三方文件。
2. Investment Schedule 必先读:在比对 IPA / SOFI 之前,必须先读 investment_movement.json。
3. JE 的双向规则:❌ 不得用外包商出具的 JE 作为 cost 变动的"原始证据"。✅ 但反向需要验证。
Guard clauses:
- Section 2:
profile.has_mmf == true时输出 - Section 3:
profile.has_partnership == true或profile.has_hedge_fund == true时输出 - Section 4:本期存在已付未确认认购款时输出
Distribution 按需加载: if any(m["type"] == "Distribution" for m in investment_movement): read_file("references/distribution_framework.md")
额外强制产物:<target_dir>/.cortex/nav_data/verified_cost.json(供 UGL Review 消费)---
Report 3:Investment Unrealized G/L Review
读取:
nav_data/sofi.json、prior_mv.json、investment_support.json、tb.json、verified_cost.json启动时加载:
read_file("references/inv_ugl_review_template.md")→ 获取全部 4 个 Section 模板 + 6 列模板 + MV 取数规则 + MV 来源速查表。左侧导航栏 Section 列表:
- Section 1: MV & UGL Overview
- Section 2: Market Value Verification
- Section 3: Unrealized G/L Reconciliation
- Section 4: Conclusion
右侧 Supporting 面板截图映射:
- Section 1 各持仓行 →
inv_ugl/mv_support_*.png(每持仓/每 Series 一张) - Section 2 各持仓行 →
inv_ugl/mv_support_*.png+inv_ugl/prior_mv.png - Section 3 汇总行 →
inv_ugl/tb_ugl.png+inv_ugl/current_mv.png
Guard clauses:无(UGL Review 所有基金类型均需输出)。
---
Stage 3a 收尾
HTML 报告即为最终交付物。每份报告生成完成后验证:
- HTML 文件大小合理(含 base64 内嵌截图)
- 双击可在浏览器中正确打开
- 三栏布局正确渲染
- 所有 interactive-row 的点击联动正常
---
Stage 3b:上传 iGowin(主 agent 串行执行)
启动时加载:read_file("references/igowin_upload.md") → 获取 6 步流程(含上传前强制确认关卡) + subject 匹配策略 + 歧义处理。
对每份报告依次执行(Cash → Cost → UGL 串行):
1. get_nav_packs(fund_name) → 获取 applicationNo
2. get_subject_names(application_no) → 找到对应 subject_name
3. get_upload_url(file_name, content_type="text/html") → 拿到 uploadUrl + objectName
4. ⚠️ 上传前确认关卡(强制 ask_questions):在 PUT 之前必须用 ask_questions 弹窗向用户展示完整上传摘要并取得明确确认。摘要必须包含:基金名、NAV 日期、流程单号、目标 subject、文件名、文件大小(MB)、报告核对结论(PASS / FAIL,差异金额)。选项至少包含「确认上传」「跳过该报告」「全部取消,等我手动检查」。一次只为一份报告询问;多份报告时不允许使用一次"全部确认"豁免后续报告的关卡。
5. PUT 上传(仅在 Step 4 通过后执行):curl -X PUT -H "Content-Type: text/html" --data-binary @"$htmlPath" "$uploadUrl" 或 PowerShell Invoke-WebRequest -UseBasicParsing -Method Put -Uri $uploadUrl -InFile $htmlPath -ContentType "text/html"
6. save_check_result(object_name, file_name, subject_name, application_no)(仅在 Step 5 PUT 返回 200 OK 后执行)
iGowin 上传要串行,每份报告完整跑完 6 步再处理下一份,避免 applicationNo 状态错乱。
用户拒绝处理:
- 「跳过该报告」→ 不调用 PUT/save_check_result,记入未上传清单,继续处理下一份
- 「全部取消」→ 终止剩余所有报告上传,输出最终交付总结时清晰列出哪些已上传 / 哪些未上传 / 用户取消原因
---
统一 CSS 模板(三份报告共用)
css
- { margin: 0; padding: 0; box-sizing: border-box; }
/ 左侧导航栏 / .sidebar { position: fixed; left: 0; top: 0; width: 240px; height: 100vh; background: #1E293B; color: #CBD5E1; padding: 20px 12px; overflow-y: auto; z-index: 100; } .sidebar h2 { font-size: 13px; color: #F1F5F9; margin-bottom: 4px; } .sidebar .nav-subtitle { font-size: 11px; color: #94A3B8; margin-bottom: 16px; } .sidebar a { display: block; padding: 8px 12px; margin: 2px 0; font-size: 12px; color: #CBD5E1; text-decoration: none; border-radius: 4px; border-left: 3px solid transparent; } .sidebar a:hover { background: #475569; } .sidebar a.active { background: #334155; color: #F1F5F9; border-left-color: #60A5FA; } .sidebar .nav-footer { position: absolute; bottom: 12px; left: 12px; right: 12px; font-size: 10px; color: #64748B; }
/ 中间主面板 / .main { margin-left: 240px; margin-right: 520px; padding: 24px 28px; min-width: 600px; } .main h1 { font-size: 20px; margin-bottom: 6px; color: #0F172A; } .main h2 { font-size: 16px; margin: 24px 0 12px; color: #1E293B; border-bottom: 1px solid #E2E8F0; padding-bottom: 6px; } .main h3 { font-size: 14px; margin: 16px 0 8px; color: #334155; }
/ 表格 / table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; } th { background: #F1F5F9; font-weight: 600; text-align: left; padding: 8px 10px; border: 1px solid #E2E8F0; } td { padding: 8px 10px; border: 1px solid #E2E8F0; } tr:nth-child(even) { background: #FAFBFC; }
/ 可点击行 / .interactive-row { cursor: pointer; transition: background 0.15s, border-color 0.15s; } .interactive-row:hover { background: #EFF6FF; } .interactive-row.highlighted { background: #DBEAFE; border-left: 3px solid #3B82F6; }
/ 结论横幅 / .banner-pass { background: #E2EFDA; color: #375623; border-left: 5px solid #548235; padding: 12px 16px; margin: 16px 0; font-weight: 600; border-radius: 4px; } .banner-fail { background: #FCE4D6; color: #C65911; border-left: 5px solid #C65911; padding: 12px 16px; margin: 16px 0; font-weight: 600; border-radius: 4px; }
/ 右侧 Supporting 面板 / .supporting-panel { position: fixed; right: 0; top: 0; width: 520px; height: 100vh; overflow-y: auto; background: #F1F5F9; border-left: 2px solid #CBD5E1; padding: 16px; z-index: 100; } .supporting-panel h3 { font-size: 13px; color: #475569; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #E2E8F0; } .supporting-item { display: none; } .supporting-item.active { display: block; } .supporting-item img { width: 100%; margin: 6px 0; border-radius: 4px; border: 1px solid #E2E8F0; cursor: zoom-in; transition: opacity 0.15s; } .supporting-item img:hover { opacity: 0.9; } .sup-section-label { padding: 6px 10px; font-size: 11px; color: #475569; background: #F8FAFC; border-bottom: 1px solid #E2E8F0; border-top: 1px solid #E2E8F0; font-weight: 600; margin-top: 8px; } #no-supporting-placeholder { color: #94A3B8; font-size: 12px; text-align: center; margin-top: 40px; }
/ Lightbox / .lightbox-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.88); z-index: 9999; overflow: hidden; } .lightbox-overlay.active { display: block; } .lightbox-toolbar { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: rgba(255,255,255,0.95); border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.35); font-size: 12px; color: #1E293B; z-index: 10000; user-select: none; } .lightbox-btn { border: 1px solid #CBD5E1; background: #F8FAFC; color: #1E293B; width: 30px; height: 26px; line-height: 1; font-size: 14px; border-radius: 4px; cursor: pointer; padding: 0; } .lightbox-btn:hover { background: #E2E8F0; } .lightbox-btn.reset, .lightbox-btn.close { width: auto; padding: 0 10px; font-size: 11px; } .lightbox-btn.close { border-color: #FCA5A5; color: #B91C1C; background: #FEF2F2; } .lightbox-level { min-width: 46px; text-align: center; font-variant-numeric: tabular-nums; } .lightbox-stage { position: absolute; inset: 0; overflow: auto; cursor: grab; display: flex; align-items: center; justify-content: center; } .lightbox-stage.grabbing { cursor: grabbing; } .lightbox-stage img { max-width: 95vw; max-height: 95vh; transform-origin: center center; user-select: none; -webkit-user-drag: none; transition: transform 0.05s linear; box-shadow: 0 0 30px rgba(0,0,0,0.5); }
---html <!-- Lightbox HTML(在 </body> 之前注入) --> <div class="lightbox-overlay" id="lightbox" aria-hidden="true"> <div class="lightbox-toolbar"> <button class="lightbox-btn" data-zoom="out" title="缩小">−</button> <span class="lightbox-level">100%</span> <button class="lightbox-btn" data-zoom="in" title="放大">+</button> <button class="lightbox-btn reset" data-zoom="reset" title="重置">Reset</button> <button class="lightbox-btn close" data-zoom="close" title="关闭">✕ 关闭</button> </div> <div class="lightbox-stage" id="lightbox-stage"> <img id="lightbox-img" src="" alt="Zoomed view" /> </div> </div>统一 JS 模板(三份报告共用)
<script> document.addEventListener('DOMContentLoaded', function() { // ── 导航栏联动(IntersectionObserver)── const sections = document.querySelectorAll('.section-anchor'); const navLinks = document.querySelectorAll('.sidebar a[href^="#"]'); const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { navLinks.forEach(l => l.classList.remove('active')); const link = document.querySelector('.sidebar a[href="#' + entry.target.id + '"]'); if (link) link.classList.add('active'); } }); }, { rootMargin: '-20% 0px -60% 0px' }); sections.forEach(s => observer.observe(s));
// ── Supporting 面板联动 ── const rows = document.querySelectorAll('.interactive-row'); const items = document.querySelectorAll('.supporting-item'); const placeholder = document.getElementById('no-supporting-placeholder');
if (items.length > 0) { activateSupporting(items[0].id); const firstRow = document.querySelector('.interactive-row[data-supporting-id="' + items[0].id + '"]'); if (firstRow) firstRow.classList.add('highlighted'); }
rows.forEach(row => { row.addEventListener('click', function() { rows.forEach(r => r.classList.remove('highlighted')); this.classList.add('highlighted'); activateSupporting(this.getAttribute('data-supporting-id')); }); });
function activateSupporting(id) { if (!id) return; const targetItem = document.getElementById(id); if (targetItem) { if (placeholder) placeholder.style.display = 'none'; items.forEach(i => i.classList.remove('active')); targetItem.classList.add('active'); targetItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }
// ── Lightbox ── const ZOOM_MIN = 0.5, ZOOM_MAX = 4.0, STEP_BTN = 0.25, STEP_WHEEL = 0.1; const overlay = document.getElementById('lightbox'); const stage = document.getElementById('lightbox-stage'); const lbImg = document.getElementById('lightbox-img'); const lbLevel = overlay ? overlay.querySelector('.lightbox-level') : null; let scale = 1;
const apply = () => { if (lbImg) lbImg.style.transform = 'scale(' + scale + ')'; if (lbLevel) lbLevel.textContent = Math.round(scale * 100) + '%'; }; const setScale = v => { scale = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, v)); apply(); };
function openLightbox(src, alt) { if (!overlay || !lbImg) return; lbImg.src = src; lbImg.alt = alt || 'Zoomed view'; scale = 1; apply(); if (stage) { stage.scrollLeft = 0; stage.scrollTop = 0; } overlay.classList.add('active'); overlay.setAttribute('aria-hidden', 'false'); } function closeLightbox() { if (!overlay) return; overlay.classList.remove('active'); overlay.setAttribute('aria-hidden', 'true'); if (lbImg) lbImg.src = ''; }
document.querySelectorAll('.supporting-item img').forEach(img => { img.addEventListener('dblclick', function(e) { e.preventDefault(); e.stopPropagation(); openLightbox(this.src, this.alt); }); });
if (overlay) { overlay.querySelectorAll('.lightbox-btn').forEach(btn => { btn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); const action = btn.getAttribute('data-zoom'); if (action === 'in') setScale(scale + STEP_BTN); if (action === 'out') setScale(scale - STEP_BTN); if (action === 'reset') { if (stage) { stage.scrollLeft = 0; stage.scrollTop = 0; } setScale(1); } if (action === 'close') closeLightbox(); }); }); overlay.addEventListener('click', e => { if (e.target === overlay || e.target === stage) closeLightbox(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && overlay.classList.contains('active')) closeLightbox(); });
stage.addEventListener('wheel', e => { if (!overlay.classList.contains('active')) return; e.preventDefault(); const rect = stage.getBoundingClientRect(); const px = e.clientX - rect.left + stage.scrollLeft; const py = e.clientY - rect.top + stage.scrollTop; const oldScale = scale; setScale(scale + (e.deltaY < 0 ? STEP_WHEEL : -STEP_WHEEL)); const ratio = scale / oldScale; stage.scrollLeft = px * ratio - (e.clientX - rect.left); stage.scrollTop = py * ratio - (e.clientY - rect.top); }, { passive: false });
let dragging = false, sx = 0, sy = 0, ssl = 0, sst = 0; stage.addEventListener('mousedown', e => { if (e.button !== 0 || e.target.closest('.lightbox-toolbar')) return; dragging = true; sx = e.clientX; sy = e.clientY; ssl = stage.scrollLeft; sst = stage.scrollTop; stage.classList.add('grabbing'); e.preventDefault(); }); document.addEventListener('mousemove', e => { if (!dragging) return; stage.scrollLeft = ssl - (e.clientX - sx); stage.scrollTop = sst - (e.clientY - sy); }); document.addEventListener('mouseup', () => { if (!dragging) return; dragging = false; stage.classList.remove('grabbing'); }); } }); </script>
---完成检查表
每份报告:
- [ ] HTML 交互式报告已落盘,双击可在浏览器中正确打开
- [ ] 三栏固定布局正确:左侧导航(240px) + 中间数据面板 + 右侧 Supporting 面板(520px,默认可见)
- [ ] 左侧导航栏包含所有 Section 链接,滚动时自动高亮当前 Section
- [ ] 所有数据行与 Supporting 一一对应:每个 Section 的每一行数据都有
.interactive-row+data-supporting-id,点击后右侧面板切换到对应证据 - [ ] Supporting 截图准确且完整:每个 interactive-row 对应的截图是正确的证据来源,多页截图全部嵌入
- [ ] 中间复核行与右侧 Supporting 截图具备联动点击高亮、平滑滚动体验
- [ ] 双击 Supporting 截图打开全屏 Lightbox 预览:遮罩顶部含工具栏(0.25× 步进、范围 0.5×~4×);遮罩内支持滚轮缩放(0.1× 步进、光标锚点)与拖拽平移;Esc 关闭
- [ ] 所有截图以 base64 data URI 内嵌:HTML 完全自包含,不依赖外部文件
- [ ] 截图清晰度:使用 3x+ zoom 渲染确保文字可读
- [ ] 结论横幅颜色正确(PASS 绿 / FAIL 红)
- [ ] iGowin applicationNo 匹配当期
- [ ] iGowin subject_name 与报告类型对应
- [ ] iGowin 上传前确认关卡已执行:在 PUT 上传前已用
ask_questions向用户展示上传摘要并取得明确「确认上传」回应 - [ ] iGowin save_check_result 返回成功
- [ ] verified_cost.json 已产出(Cost Review 完成后)
---
关键注意事项
1. 三份报告内容互不重叠:
- Cash Review = 现金 + 流水 + Payment Log,不谈投资市值
- Investment Cost Review = 成本变动,不谈市值/UGL
- Investment Unrealized G/L Review = 市值 + UGL,不重复谈成本变动(仅引用 Cost 总额)
content_type="text/html"。
5. 不要使用任何第三方繁重 JS 库,交互动作必须全部由精简的原生 Vanilla JS 编写。
6. HTML 文件名与命名规范:[报告类型] - [基金名] [YYYYMMDD].html(仅扩展名为 .html)。
7. 结论横幅一律先 PASS 再列发现,FAIL 必须说明具体差异金额与建议跟进项。
8. Stage 3a 串行执行:Cash → Cost → UGL,与主调度 skill 一致。
9. verified_cost.json 产出:Investment Cost Review 完成后,将逐持仓核对结果写入 <target_dir>/.cortex/nav_data/verified_cost.json,供 UGL Review 消费。
10. Supporting 截图必须准确对应数据行:每个 data-supporting-id 指向的截图必须是该数据行的直接证据来源,不允许错误映射或泛化映射(如把所有行都指向同一张截图)。