nav-review-ugl-report
SKILL_203551471 · vv1.1 · Olive 海外运营 · Owner:Olive 基金运营 · 发布于 2026-08-10
调用 17
下载 0
点赞 0
浏览 0
- 简介
- 重算NAV未实现损益并与管理员对账,生成自包含交互式HTML复核报告。
- 触发词
- UGL复核,未实现损益,生成报告,NAV对账
- 分发渠道
- ARK Engine
- 功能测试
- ✅ 通过 · 业务评审:✅ 通过
- 技能包文件
- nav-review-ugl-report/SKILL.md、nav-review-ugl-report/references/inv_ugl_review_html_template.html、nav-review-ugl-report/references/inv_ugl_review_template.md
使用示例:生成某基金本期的未实现损益UGL复核HTML报告。
SKILL.md 全文
Frontmatter
| name | nav-review-ugl-report |
|---|---|
| description | | |
NAV Review Stage 3.3 — Investment Unrealized G/L Review 报告生成与上传 (HTML 版)
⛔ 模板强制遵循规则(最高优先级,不可跳过)
生成 HTML 报告时,必须先read_file("references/inv_ugl_review_html_template.html") 加载模板,然后严格按照模板的完整结构生成报告。禁止自行设计布局、样式或交互方式。
具体要求:
1. 三栏布局不可变更:左侧导航栏(240px, #2F3542 深色背景) + 中间复核面板(flex:1) + 右侧 Supporting 证据面板(500px, sticky, #F1F5F9),三者缺一不可
2. CSS/JS 必须从模板复制:不得自行编写替代样式或脚本,必须使用模板中定义的完整 CSS 和 Vanilla JS 交互逻辑(含 Lightbox)
3. Section 结构必须与模板一致:UGL Roll-forward → MV Verification (MV=Cost+UGL) → Period Movement → MV Sources → MMF UGL → Conclusion,不得省略、合并或重新排序
4. 紫色主题色(#9c27b0)不可变更:导航栏标题、Section 标题下划线、交互高亮等均使用模板定义的紫色主题
5. 交互行为必须保留:.interactive-row + data-supporting-id 的点击联动、双击 Lightbox 预览等交互功能必须完整实现
6. 禁止降级为单栏/双栏布局:任何情况下不得生成简单的单页报告或传统文档式布局
违反上述任何一条即视为报告生成失败,必须重新生成。
---
⛔ Supporting 证据来源限制(强制,最高优先级)
只有第三方/外部独立文件才能作为 Supporting 证据。基金自身内部系统生成的文件严禁作为 Supporting。 | 禁止作为 Supporting | 允许作为 Supporting | |---|---| | ❌ Investment Position Appraisal (IPA/N4) | ✅ 基金行政管理人报表(Citco Market Value Statement) | | ❌ Trial Balance (TB) / TB Detail | ✅ Citco Capital Account Statement | | ❌ Purchases and Sales Report | ✅ 交易合同/确认函(Contract Note) | | ❌ Nominal Ledger / Detailed General Ledger | ✅ 银行付款确认(Cash Payment Confirmation) | | ❌ Statement of Operations (SOFI) | ✅ 托管人报告 | | ❌ Change in Unrealized Gains Losses Report(N4系统生成) | ✅ 银行对账单(DBS Bank Statement 等) | 核对逻辑:中间面板的复核数据可引用 IPA/TB/Change in UGL Report 数字进行对比展示,但右侧 Supporting 面板只展示第三方证据截图来佐证这些数字的正确性。对于 Unrealized G/L 核对,典型第三方证据为:Citco Market Value Statement(确认每只持仓的 NAV Price 和 Market Value)。如果某个核对项无对应第三方文件,则该行不设置 Supporting 证据。 ---⛔ Supporting 面板截图与计算过程规则(强制)
规则 A:高清截图 + 红框高亮关键数据
每个.supporting-item 必须包含对应源文件的高清截图(base64 内嵌),且用红框高亮标注关键数字/字段:
1. 截图范围 — 按数据密度决定:
- 数据集中在 1-2 行的(如某个持仓的 MV 行、单个 NAV Price)→ 使用
clip_box裁剪,只截取文档标题行 + 关键数据行区域,无需整页 - 数据分布跨多行或多页的(如完整 Citco MV Statement、完整 Change in UGL 报告)→ 保留整页或多页展示
- 判断标准:如果关键数据能在 PDF 页面高度的 30% 以内完整展示,则裁剪;否则整页
- 使用
page.get_text("words")精确定位目标金额/文字坐标 - 红色描边 3px(PIL ImageDraw),无填充,边距外扩 3px
- 只标注与当前核对项直接相关的 1-3 个关键数字(如市值、UGL 金额、NAV Price),禁止圈整行或大面积区域
- 截图渲染 DPI ≥ 150(裁剪截图用 4x zoom 确保清晰)
<div class="sup-section-label"> 标注来源文件名和取数用途,如 "Citco MV Statement — SR13 Market Value (May-31)"
规则 B:计算过程 Supporting(涉及测算时强制输出)
当复核过程中涉及数值测算(而非简单读取对比)时,必须在该行对应的.supporting-item 最下方新增一个计算过程区块。典型场景包括但不限于:
- UGL 验证(= Market Value − Verified Cost),逐持仓计算
- Change in UGL(= 期末 UGL − 期初 UGL)
- MV 计算验证(= Units × NAV Price)
- 月度回报率计算(= (End Price − Begin Price) / Begin Price)
- Standard vs Side Pocket 分类汇总
<div class="calc-block">
<div class="calc-title">📐 Calculation: [计算目的,如 "UGL Verification for SR13"]</div>
<pre class="calc-steps">
Step 1: Identify Market Value
MV = Units × NAV Price
= 14,046.3646 × 1,074.11
= 15,087,340.68 (from Citco MV Statement)
Step 2: Identify Verified Cost
Cost = 12,878,128.21 (from verified_cost.json)
Step 3: Calculate UGL
UGL = MV − Cost
= 15,087,340.68 − 12,878,128.21
= 2,209,212.47
</pre>
<div class="calc-result">✓ Result: USD 2,209,212.47 — matches N4 UGL</div>
</div>
CSS 样式(须写入 HTML <style> 中):
.calc-block {
margin-top: 12px;
border: 1px solid #E2E8F0;
border-radius: 6px;
overflow: hidden;
background: #FAFBFC;
}
.calc-title {
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: #1E293B;
background: #F1F5F9;
border-bottom: 1px solid #E2E8F0;
}
.calc-steps {
padding: 10px 14px;
font-size: 11px;
font-family: 'Consolas', 'Courier New', monospace;
line-height: 1.6;
color: #334155;
white-space: pre-wrap;
margin: 0;
background: #FAFBFC;
}
.calc-result {
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: #0A8754;
background: #E6F4ED;
border-top: 1px solid #B7E4C7;
}
执行规则:
- 简单读取对比(TB UGL = N4 UGL)→ 不需要计算过程,只需截图
- 涉及乘除运算(MV = Units × Price、UGL = MV − Cost)→ 必须输出计算过程
- 计算过程中引用的每个输入值须标注来源文件(如 "from Citco MV Statement"、"from verified_cost.json")
- 最终结果行必须标注验证状态(matches / differs from 目标值)
When to Use
- 由
nav-review-report调度 skill 在 Stage 3.3 自动调用 - 用户单独说"生成 UGL Review"、"出未实现损益复核报告"
- 已存在
<target_dir>/.cortex/nav_profile.json与<target_dir>/.cortex/nav_data/与<target_dir>/.cortex/screenshots/inv_ugl/ - 前置条件:Investment Cost Review 已完成且
<target_dir>/.cortex/nav_data/verified_cost.json存在
输入
target_dir:基金期间文件夹绝对路径- profile:
<target_dir>/.cortex/nav_profile.json - 数据:
<target_dir>/.cortex/nav_data/sofi.json(或 ipa.json)、prior_mv.json、investment_support.json、tb.json、verified_cost.json(来自 nav-review-cost-report) - 截图:
<target_dir>/.cortex/screenshots/inv_ugl/*.png
输出
| 报告 | HTML 文件名 | iGowin Subject | |------|------------|----------------| | Investment Unrealized GL Review |Investment Unrealized GL Review - [基金名] [YYYYMMDD].html | Unrealized G/L 子科目 |
HTML 交互式报告输出至 <target_dir>/ 根目录(用户双击可直接在浏览器中打开),并作为 iGowin 系统上传的最终交付物。中间脚本放 <target_dir>/.cortex/。
---
外置文件索引
| 文件 | 路径 | 加载时机 | |------|------|---------| | Investment UGL Review 内容模板 |references/inv_ugl_review_template.md | 启动时 read_file(Section 结构 + 核对逻辑) |
| Investment UGL Review HTML 模板 | references/inv_ugl_review_html_template.html | 启动时 read_file(固定 HTML/CSS/JS 三栏结构) |
| iGowin 上传流程 | ../nav-review-report/references/igowin_upload.md | 上传阶段 read_file |
所有路径相对于 skill 目录:c:\Users\syy10787\.cortex\skills\nav-review-ugl-report\
---
UI 与格式约束
HTML 报告为最终交付物,必须采用响应式、现代化双栏交互式设计,提供极佳的屏幕核对体验:1. 结构与排版布局
- 最左侧 - 导航栏 (
.sidebar): - 固定在左侧 (
position: fixed; width: 240px; height: 100vh; background: #2F3542; color: #FFF;)。 - 包含导航标题(基金名称和 NAV 日期)以及 4 个 Section 的快速定位链接。
- 中间 - 复核结果主面板 (
.review-panel): - 位于导航右侧 (
margin-left: 240px; flex: 1; padding: 30px;)。 - 展示逐项复核表格、逻辑分析段落和 PASS/FAIL 结论。
- 表格行若有对应 Supporting 证据,必须添加
.interactive-row类,并设置data-supporting-id="[ID]"属性。 - 最右侧 - Supporting 证据直观查看面板 (
.supporting-panel): - 宽度
500px,贴在右侧 (position: sticky; top: 0; height: 100vh; overflow-y: auto; background: #F1F5F9;)。 - 集中展示复核截图。每个截图容器为
.supporting-item,并带有唯一的id(与主面板行的data-supporting-id对应)。 - 默认隐藏其他,仅激活并高亮当前点击或 hover 行对应的 Supporting 截图。
2. 交互体验设计 (Vanilla JS)
- 页面加载时默认激活并高亮第一个有 Supporting 的表格行,并在右侧面板展示对应的截图。
- 当用户点击主面板中的
.interactive-row时,通过 JS 移除先前的激活状态,在当前点击行添加.highlighted(浅蓝色背景与蓝色边框),并在右侧面板中平滑滚动并展示对应的 Supporting 截图 (scrollIntoView({ behavior: 'smooth' }))。 - 双击图片 → 全屏 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)。
3. 颜色与视觉风格
- 主题背景:
#F8FAFC。 - 文字字体:
"Segoe UI", "Microsoft YaHei", Arial, sans-serif。 - 结论横幅:
- PASS:背景
#E2EFDA,文字#375623,左边框 5px 实线#548235 - FAIL:背景
#FCE4D6,文字#C65911,左边框 5px 实线#C65911 - 表格样式:边框
#E2E8F0,表头背景#F1F5F9加粗,奇偶行交替背景。
执行流程
Step 1:加载 profile + 数据 + 模板
编写 Python 脚本加载数据:import os, json, sys BASE = r"<target_dir>" PROFILE = json.load(open(os.path.join(BASE, ".cortex", "nav_profile.json"), encoding="utf-8")) VERIFIED_COST = json.load(open(os.path.join(BASE, ".cortex", "nav_data", "verified_cost.json"), encoding="utf-8"))
⚠️ Guard:若 verified_cost.json 不存在,立即停止并提示用户先完成 Investment Cost Review,不允许 fallback 自行验证 cost。
⚠️ 必须 read_file 加载 references/inv_ugl_review_html_template.html,生成报告时严格遵循此 HTML 模板的三栏布局结构、CSS 样式(紫色主题色 #9c27b0)、交互式 JS 脚本。模板定义了完整的 Section 结构(TB vs N4 → UGL Roll-forward → MV Verification → Period Movement → MV Sources → MMF UGL → Conclusion),不得省略或改变顺序。
Step 2:生成交互式 HTML 报告
使用 Python 脚本拼装 HTML 字符串,将数据填入 HTML/CSS 模板中: 1. 渲染左侧导航栏:显示 4 个 Section 链接:- Section 1: MV & UGL Overview
- Section 2: Market Value Verification
- Section 3: Unrealized G/L Reconciliation
- Section 4: Conclusion
verified_cost.json 和 investment_support.json 逐持仓/Series 循环生成表格行。行中插入 class="interactive-row" data-supporting-id="support-[持仓名]_ugl"。
3. 渲染右侧 Supporting 面板:在 .supporting-panel 中,针对每个持仓的截图,渲染 <div id="support-[持仓名]_ugl" class="supporting-item">,内部包含来自 .cortex/screenshots/inv_ugl/ 目录的图片。
⚠️ 截图内嵌强制规则:所有截图必须以 base64 data URI 内嵌到 HTML 文件中,禁止使用相对路径或外部文件引用。实现方式:在 Python 脚本中,对每个 PNG 文件读取二进制内容并转为 base64 编码,然后以 data:image/png;base64,<base64_string> 格式写入 <img src="...">。
import 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 "" # 文件不存在时返回空字符串
HTML 中使用:<img src="{embed_image(BASE, 'inv_ugl', 'prior_mv.png')}" alt="Prior MV">
> 理由:内嵌截图使 HTML 文件完全自包含,双击即可在任何位置打开查看,不依赖 .cortex/ 目录结构。上传至 iGowin 后也能直接在浏览器中完整展示,无需额外文件。
⚠️ 截图清晰度强制规则:使用 PyMuPDF (fitz) 渲染 PDF 页面为 PNG 时,必须使用 4x zoom(fitz.Matrix(4, 4)),确保文字和数字在浏览器中清晰可读。2x zoom 不足以保证小字清晰度。
⚠️ MV & UGL Overview(Section 1)Supporting 强制规则:Section 1 每行的 Supporting 面板必须同时展示两类截图,缺一不可:
- 期末数据取值截图(IPA 类):上期 IPA/SOFI(
prior_mv.png,Prior MV/UGL 列取值来源)+ 当期 IPA/SOFI(current_mv.png,Current MV/UGL 列取值来源) - MV Supporting File 截图(第三方原始证据):每个持仓 / 每个 Series 对应的第三方 MV 来源截图(
mv_support_<持仓名>.png),来源依据持仓类型按inv_ugl_review_template.mdMV 来源速查表确定(CAS、Investor Statement、monthlyStatement、Board Memo、邮件估值等)
.supporting-item,内部用分段标题(<div class="img-title">,样式见下文)将多张截图分组展示,每段标题清晰标注取数用途。所有 <img> 标签的 src 均使用 embed_image() 返回的 base64 data URI。
> 分段标题样式:padding:8px 12px; font-size:12px; color:#475569; background:#F8FAFC; border-bottom:1px solid #E2E8F0; border-top:1px solid #E2E8F0; margin: 10px -15px;(首段无 border-top)。
> 强制原因:MV 不可自证,IPA/TB/FS Pack 不能作为唯一证据。Section 1 是 MV & UGL 整体陈述,必须同时呈现 IPA 取值和第三方原始 MV 证据,让审阅人一眼判断"IPA MV = 第三方 MV"是否成立。仅展示 IPA 截图等同于自证,违反 MV 取数强制规则。
其他 Section(Market Value Verification、Unrealized G/L Reconciliation 等)的 Supporting 面板按各自需求展示对应单一或少量截图,无需复合,但同样必须使用 base64 内嵌。
4. 嵌入交互式 JS 脚本(双击截图 → 全屏 Lightbox 预览 + 工具栏缩放 + 滚轮缩放 + 拖拽平移):
CSS 新增(在 <style> 末尾添加):
/ ── Supporting 图片悬停提示 ── /
.supporting-item img {{
cursor: zoom-in;
transition: opacity 0.15s;
}}
.supporting-item img:hover {{ opacity: 0.9; }}
/ ── 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 结构(在 </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() {
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' });
}
}
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) return;
if (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>
5. 将完整 HTML 内容写入 <target_dir>/Investment Unrealized GL Review - [基金名] [YYYYMMDD].html。
Step 3:上传 iGowin
read_file("../nav-review-report/references/igowin_upload.md") 获取上传流程,然后执行 6 步(上传 HTML 文件本身,不再做 PDF 转换):
1. get_nav_packs(fund_name) → 获取 applicationNo
2. get_subject_names(application_no) → 找到 Unrealized G/L 类 subject_name
3. get_upload_url(file_name, content_type="text/html") → 拿到 uploadUrl + objectName
4. ⚠️ 上传前确认关卡(强制):调用 ask_questions 弹窗向用户展示完整上传摘要并取得明确确认。摘要必须包含:基金名、NAV 日期、流程单号、目标 subject、文件名、文件大小(MB)、报告核对结论(PASS / FAIL,及差异金额)。选项至少包含「确认上传」「跳过该报告」「全部取消」。未取得确认前,不得执行 Step 5 的 PUT 上传或 Step 6 的 save_check_result。即便此前在主 skill 入口已确认整体计划,此处仍须再次确认(理由见 igowin_upload.md 的"强制原则"段)。
5. PowerShell 上传(仅在 Step 4 通过后执行):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 后执行)
---
完成检查表
- [ ] HTML 交互式报告已落盘,能双击打开且最左侧包含清晰导航栏
- [ ] 中间复核行与右侧 Supporting 截图具备联动点击高亮、平滑滚动体验
- [ ] 双击 Supporting 截图打开全屏 Lightbox 预览:遮罩顶部含
−/ 倍数 /+/Reset/✕工具栏(按钮 0.25× 步进、范围 0.5×~4×);遮罩内支持鼠标滚轮缩放(步进 0.1×、以光标点为锚点)与上下左右拖拽平移;点击遮罩空白处、✕或按Esc关闭 - [ ] **MV & UGL Overview(Section 1)每行的 Supporting 面板同时包含期末 IPA 取值截图(prior_mv.png + current_mv.png)和 MV Supporting File 第三方原始证据截图(mv_support_*.png)**,分段标题清晰标注取数用途
- [ ] Book Cost 列数据全部来自
verified_cost.json(非重新推导或从 SOFI / TB 直接核对) - [ ] 持仓清单一致性:UGL Review 中持仓清单必须等于
verified_cost.json的 investments 清单(同名同顺序);不允许遗漏(如已退出但 Investment 文件夹仍残留 → 须排除)或新增(无 cost 数据的孤儿持仓 → 须排除)。每条持仓在 SOFI / IPA / TB 三方中至少有一处确认有当期持仓。 - [ ] MMF 类持仓 MV Support 必出:即使是货基(如 Goldman Sachs USD Liquid Reserves Fund),也必须有 MV Support 截图(取自 Ark monthlyStatement Holdings 页或 GP Statement),不允许仅给 4 张图(Prior MV + Current MV 各 page1/page2)就交付。MV Support 截图来源 = mv_support_<fund_id>.png。
- [ ] MMF Book Cost 与 Cost Review 一致:Section 1 Book Cost 列对 MMF 持仓显示的金额必须等于
verified_cost.json中 MMF 行的verified_cost,且与 Cost Review 报告 Section 2 末尾的 Closing Cost 完全一致(口径:statement holdings + next-period reinvestment)。两份报告 MMF cost 不一致即 FAIL。 - [ ] 持仓清单一致性:UGL Review 中持仓清单必须等于
verified_cost.json的 investments 清单(同名同顺序);不允许遗漏(如已退出但 Investment 文件夹仍残留 → 须排除)或新增(无 cost 数据的孤儿持仓 → 须排除)。每条持仓在 SOFI / IPA / TB 三方中至少有一处确认有当期持仓。 - [ ] MMF 类持仓 MV Support 必出:即使是货基(如 Goldman Sachs USD Liquid Reserves Fund),也必须有 MV Support 截图(取自 Ark monthlyStatement Holdings 页或 GP Statement),不允许仅给 4 张图(Prior MV + Current MV 各 page1/page2)就交付。MV Support 截图来源 = mv_support_<fund_id>.png。
- [ ] MMF Book Cost 与 Cost Review 一致:Section 1 Book Cost 列对 MMF 持仓显示的金额必须等于
verified_cost.json中 MMF 行的verified_cost,且与 Cost Review 报告 Section 2 末尾的 Closing Cost 完全一致(口径:statement holdings + next-period reinvestment)。两份报告 MMF cost 不一致即 FAIL。 - [ ] 所有截图以 base64 data URI 内嵌到 HTML 文件中,HTML 文件完全自包含,双击即可在任何位置打开查看;截图渲染使用 4x zoom 确保清晰度
- [ ] 上传前确认关卡已执行:在 PUT 上传前已用
ask_questions向用户展示上传摘要(基金名 / NAV 日期 / 流程单号 / subject / 文件名 / 大小 / 核对结论)并取得明确「确认上传」回应;如用户选择跳过则未调用save_check_result,并在交付总结中标注"用户选择不上传" - [ ] iGowin applicationNo 匹配当期,并且 save_check_result 返回成功(HTML 文件已成功上传)
截图质量与标注规则
截图清晰度要求(最高质量)
清晰度为第一优先级。所有内嵌 HTML 的截图必须确保数字、文字完全清晰可读。 | 参数 | 值 | 说明 | |------|-----|------| | DPI | 150 | HTML 内嵌 JPEG 渲染分辨率 | | JPEG quality | 85 | 高品质,文字无锯齿 | | 红框线宽 | 3px | PIL ImageDraw,高清图上清晰可见 | | 落盘 PNG | 300 DPI | 独立 PNG 文件最高清晰度 | | 宽表 zoom | 4× | IPA 等宽表裁剪用 4x zoom | 不设单文件体积上限。如需控制体积,通过clip_box 裁剪区域(只截关键数据行),禁止降低 DPI 或 quality。
import fitz, io, base64
from PIL import Image, ImageDraw
def screenshot_for_embed(pdf_path, page_num, red_rects=None, clip_box=None, dpi=150, quality=85):
"""高清 JPEG 截图用于 HTML 内嵌"""
doc = fitz.open(pdf_path)
page = doc[page_num]
mat = fitz.Matrix(dpi/72, dpi/72)
pix = page.get_pixmap(matrix=mat, clip=fitz.Rect(*clip_box) if clip_box else None)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
if red_rects:
draw = ImageDraw.Draw(img)
scale = dpi / 72.0
ox, oy = (clip_box[0], clip_box[1]) if clip_box else (0, 0)
for box in red_rects:
draw.rectangle([(box[0]-ox)scale-1, (box[1]-oy)scale-1,
(box[2]-ox)scale+1, (box[3]-oy)scale+1], outline='red', width=3)
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=quality, optimize=True)
doc.close()
return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"
截图红框标注规则
所有 Supporting 截图必须用红框高亮关键数字: 1. 定位方法:使用page.get_text("words") 精确提取数字坐标
2. 标注内容:仅标注 MV、NAV/share、closing shares、period change 等核对关键金额
3. 红框样式:红色描边 3px(PIL),无填充,略扩展 3px 边距
4. 禁止:圈整行/大区域、不透明填充、标注非关键信息
数据层级标注强制规则 (Data Hierarchy)
报告中必须严格区分 Reference(被核对对象) 与 Supporting(第三方证据): | Category | Documents | 标签色 | |----------|-----------|--------| | Reference(被核对) | TB, IPA, Change in UGL Report, Realized GL Report, Maples NAV Per Share, Statement of Operations | 紫色#f3e5f5 |
| Supporting(第三方证据) | SS&C / GP Investor Statement, Contract Note, Bank Statement | 绿色 #e8f5e9 |
强制规则:
- FS Pack 内部报告(TB/IPA/Change in UGL 等)永远不能标记为 Supporting
- MV 取数规则:期末 MV 必须从第三方文件(Investor Statement / GP Statement / CAS)取值,IPA 仅作为被比对对象
- UGL 验证逻辑:MV(来自 Supporting 第三方)- Cost(来自 Cost Review 已验证)= UGL → 与 TB/IPA UGL 比对
- Total Return Cross-Check:UGL变动 + 已实现收益 = Investor Statement 期间 MV 变动(第三方确认)
- PASS 结论必须基于 Supporting 验证 Reference,不可 Reference 自证
关键注意事项
1. 本 skill 只负责 Investment Unrealized G/L Review,不涉及 Cash 或 Cost。 2. HTML 即最终交付物:不再生成 PDF,所有上传与展示统一基于 HTML 文件。 3. 所有截图必须以 base64 data URI 内嵌到 HTML 文件中,使 HTML 完全自包含,不依赖外部文件。禁止使用相对路径或外部文件引用。 4. 不要使用任何第三方繁重 JS 库,交互动作必须全部由精简的原生 Vanilla JS 编写。 5. Book Cost 必须来自 verified_cost.json,这是与 Cost Review 的强制契约,违反即 FAIL。- MMF 口径强制对齐:MMF 持仓的 Book Cost 必须 = Cost Review 末尾 Closing Cost = statement holdings + next-period reinvestment(如 Q4-2025 Goldman MMF = 3,000,000 + 5,211.36 = 3,005,211.36);切勿因看到 statement holdings 仅 3,000,000 就把 UGL 报告 cost 改成 3,000,000,这会与 IPA/SOFI/TB 失去 5,211.36 的口径差。
- 持仓清单 = verified_cost.json:UGL 不能新增 / 遗漏 verified_cost 之外的持仓。如发现 Investment 文件夹下有 fund 但无 cost 数据(如已退出的 SCHF Cayman LP),属于孤儿持仓,不纳入 UGL 报告,孤儿截图(如 mv_support_schf.png)也不嵌入。如需要一致性记录,只在 SKILL 完成检查表里标注"已识别孤儿,未纳入"。
- MMF 口径强制对齐:MMF 持仓的 Book Cost 必须 = Cost Review 末尾 Closing Cost = statement holdings + next-period reinvestment(如 Q4-2025 Goldman MMF = 3,000,000 + 5,211.36 = 3,005,211.36);切勿因看到 statement holdings 仅 3,000,000 就把 UGL 报告 cost 改成 3,000,000,这会与 IPA/SOFI/TB 失去 5,211.36 的口径差。
- 持仓清单 = verified_cost.json:UGL 不能新增 / 遗漏 verified_cost 之外的持仓。如发现 Investment 文件夹下有 fund 但无 cost 数据(如已退出的 SCHF Cayman LP),属于孤儿持仓,不纳入 UGL 报告,孤儿截图(如 mv_support_schf.png)也不嵌入。如需要一致性记录,只在 SKILL 完成检查表里标注"已识别孤儿,未纳入"。
- 收集每个持仓的 MV 时,必须验证 Price Date = 报告期末日(如 Q1 报告期末日 = 31-Mar-2026)。
- Nominee monthlyStatement 陷阱:当期结单(如 Mar statement)中 Hedge Fund / Open-end Fund 的 Price Date 经常滞后(如仅显示 28-Feb NAV),此时必须从次月结单(subsequent month = Apr statement)中获取 Price Date = 期末日的 NAV 数据。
- 取数优先级:
ask_questions 向用户确认:"[持仓名] 无法找到截至 [期末日] 的 MV 数据,最近可用日期为 [X],是否使用该日期数据继续?"
- MMF 豁免:货币基金 NAV = 1.00 恒定,Price Date 不影响 MV 计算(MV = Units = Cost),无需验证 Price Date。
- PE 豁免:PE 基金按 BM Resolution / 独立估值报告定价,不依赖 monthlyStatement Price Date。
- 示例(Gopher Season Q1 2026):Mar 结单中 Noah CMI Price Date = 28-Feb-2026(非期末)→ 从 Apr 结单获取 Price Date = 31-Mar-2026 的 NAV → Series 03: $828.68, Series 02: $793.39, Series 01: $794.38。
fitz.Matrix(4, 4)(最高清晰度),2x zoom 不足以保证小字清晰度。双击 Supporting 截图打开 Lightbox 预览(顶部 −/倍数/+/Reset/✕ 工具栏 + 滚轮缩放 + 拖拽平移)让审阅人可进一步查看细节。
9. iGowin 上传前必须经过 ask_questions 强制确认:PUT 上传与 save_check_result 都是不可逆操作,因此在调用 get_upload_url 之后、PUT 之前必须用 ask_questions 弹窗向用户确认(含基金名 / NAV 日期 / 流程单号 / subject / 文件名 / 文件大小 / 核对结论)。即使本 skill 被 nav-review 主 skill 调用、用户已在主入口确认过整体计划,此处仍须再次确认;本 skill 单独运行时同样适用。用户选择「跳过」则不上传该报告,「全部取消」则终止。不允许通过任何"批处理静默上传"绕过此关卡。
11. 持仓数据必须来自 N4 IPA 原始报告(强制,禁止虚构):
- 每个持仓的 Cost、Market Value、UGL 三项数字必须从 N4 Investment Position Appraisal (IPA) 报告中逐行读取,不得凭记忆、猜测或人工编造任何持仓名称或金额。
- 禁止出现 IPA 中不存在的持仓:如 IPA 中只有 "Bytedance Series D" 和 "Bytedance Series E-1",则报告中不得出现 "Series C1 Preferred"、"Series B" 等虚构持仓。
- PE 持仓验证:对于 PE 类持仓(如 Bytedance),必须交叉验证 N4 IPA 合计 MV 与 BM Resolution 中的 Fair Value 总额一致(允许 ±$1 四舍五入差异)。若不一致,必须用
ask_questions向用户确认差异原因后才能继续。 - Nominee 持仓区分:Nominee 持仓(如通过 Ark Group 持有的 Noah CMI)不出现在 N4 IPA 中,其 MV 数据从 monthlyStatement 获取(遵循规则 10 的 Price Date 验证)。Nominee 持仓的 Prior UGL 在 TB 口径下为 $0(首次确认期认列),Current UGL = MV − Cost。
- 数据源优先级:N4 IPA(直接持仓)> monthlyStatement(Nominee 持仓)> BM Resolution(PE 估值确认)。任何持仓的 MV/Cost/UGL 不得从训练知识或历史对话中获取。
- 对于 PE 持仓(如 Bytedance),BM Resolution 是 MV 的核心第三方证据。其 Supporting 截图必须完整包含估值金额所在段落(如 "US$451,280,022"),不得裁剪掉关键数字。
- 截图范围要求:BM Resolution 通常为 DOCX 格式,需先转换为 PDF(
soffice --headless --convert-to pdf),然后定位估值金额所在页面,使用page.get_text("words")查找金额坐标,确保clip_box范围覆盖该金额及其上下文(至少包含 "proposed fair value" 到 "IT WAS RESOLVED" 段落)。 - 红框高亮:对估值总金额(如 US$451,280,022)进行红色描边标注(3px),确保审阅人一眼可见。
- 多页展示:若 BM 内容跨页(如第 1 页为 Series D/E-1 认购背景,第 2 页为估值决议),则两页都需截图并在 Supporting 中顺序展示,让审阅人可完整追溯从持仓结构到估值金额的完整逻辑链。
- 验证检查:截图生成后,确认金额文字(如 "451,280,022")的 y 坐标落在 clip_box 的 y0~y1 范围内;若超出则扩大裁剪范围或使用整页截图。