报告查询与导出¶
任务型入口:案例 6:保存后继续查询 · 案例 7:同次分析的多种交付。
外部 Agent、业务元数据、证据与跨会话使用见外部 Agent 指南。 评分卡另见评分卡,本页旧章节及锚点继续保留。
Reporting:Stable
本页的结构化 report 读取和 Excel/HTML 导出属于 Stable Reporting 能力。评分卡能力的状态 单独标注在对应章节。
适用场景¶
你可以继续筛选、复核和组合 Report 中的结果,再导出 Excel/HTML 归档或交付。 Scorecard 则将已拟合的分箱规则和逻辑回归系数转换为评分映射和 SQL。
1. 获得 Report¶
下面的示例已纳入文档测试。它会定义 report,后续导出调用都使用这个对象:
import polars as pl
from mars.analysis import profile_risk
df = pl.DataFrame(
{
"apply_dt": [
"2026-01-03",
"2026-01-10",
"2026-01-17",
"2026-01-24",
"2026-02-03",
"2026-02-10",
"2026-02-17",
"2026-02-24",
"2026-03-03",
"2026-03-10",
"2026-03-17",
"2026-03-24",
],
"month": ["2026-01"] * 4 + ["2026-02"] * 4 + ["2026-03"] * 4,
"income": [
3200,
3600,
-999,
None,
3300,
4200,
-999,
5800,
3400,
4300,
None,
6100,
],
"utilization": [
0.12,
0.18,
0.52,
0.61,
0.14,
0.29,
0.54,
0.58,
0.16,
0.31,
0.56,
0.63,
],
"segment": [
"new",
"repeat",
"vip",
"vip",
"new",
"repeat",
"vip",
"vip",
"new",
"repeat",
"vip",
"vip",
],
"target": [0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1],
}
)
risk_profile = profile_risk(
df,
target="target",
features=["income", "utilization", "segment"],
group_col="month",
time_col="apply_dt",
binning_type="native",
method="quantile",
n_bins=4,
missing_values=[-999],
special_values=[-999],
psi_include_missing=False,
psi_include_special=False,
)
report = risk_profile.report
summary = report.summary_table
binner = risk_profile.binner
常见对象与字段:
| Report | 状态 | 常用字段 |
|---|---|---|
MarsProfileReport |
Stable | overview_table、dq_tables、stats_tables、comparison_tables、report_meta |
MarsBinningReport |
Stable | summary_table、detail_table、trend_tables |
CorrelationReport |
Stable | features、pairs、correlation_decisions 等公共表;相关性指南 |
ScoreCrossReport(mars.analysis) |
Stable | cells、边际、overall、bins;模型分交叉指南 |
MarsRuleReport |
Experimental | summary_table、detail_tables、metadata;公共查询、桥接关联、保存恢复 |
MarsMonitoringReport |
Experimental | 监控汇总、分箱统计、表现覆盖率和元数据 |
MarsModelingReport |
Experimental | 多样本切片的汇总、明细、趋势和元数据 |
查询已有报告和交给 AI¶
画像、分箱、相关性与模型分交叉报告提供 Stable describe()、get_table()、to_ai_context() 和 get_feature()。
这些方法只查询已计算结果,不调用 LLM、不重新计算统计,也不修改原报告。
规则报告也满足公共 Report 契约,summary 汇总一次挖掘的结果。 按成员特征查询时,接口先找到相关规则,再读取规则指标,不为每个特征复制指标。 候选、切片、显式高级分析和 no_rules 状态见 规则报告与外部 Agent。
"""报告查询与 AI 上下文示例,不调用模型,不读写原始数据文件。"""
import json
import polars as pl
from mars.analysis import MarsBinEvaluator, profile_stats
data = pl.DataFrame(
{
"income": [2000.0, 3000.0, 4000.0, None, 5000.0, 6000.0, 7000.0, 8000.0],
"target": [1, 1, 0, None, 1, 0, 0, 0],
"month": ["2026-01"] * 4 + ["2026-02"] * 4,
}
)
result = MarsBinEvaluator(binner_params={"n_bins": 3}).evaluate(
data,
target="target",
features=["income"],
group_col="month",
batch_size=1,
feature_data_source={"application": ["income"]},
feature_metadata={"income": {"display_name": "月收入"}},
)
report = result.report
description = report.describe()
top_features = report.get_table("summary", sort_by="iv", descending=True, limit=10)
bins = report.get_table(
"detail", features="income", filters={"bin_index": {"op": "ge", "value": 0}}
)
context_json = report.to_ai_context(features="income", tables=["summary"], max_chars=16000)
assert json.loads(context_json)["evidence"][0]["reference"] == "summary"
for evidence in json.loads(context_json)["evidence"]:
replayed = report.get_table(evidence["reference"], **evidence["query"])
assert replayed.to_dicts() == evidence["rows"]
feature_data = report.get_feature("income")
summary_view = report.show_summary(sources="application", columns=["feature", "iv", "ks"], limit=10)
assert summary_view.data.columns.tolist() == ["feature", "iv", "ks"]
native_summary = report.get_table("summary")
assert native_summary.columns[:4] == ["feature", "display_name", "dtype", "data_source"]
profile = profile_stats(data, features=["income"], metrics=["missing", "mean"], group_col="month")
overview = profile.get_table("overview", columns=["feature", "missing_rate", "mean"], limit=10)
profile_context = profile.to_ai_context(tables=["dq.missing"], features="income")
numeric_context = json.loads(profile.to_ai_context(columns=["mean"], limit=1))
assert numeric_context["evidence"][0]["identities"] == [{"feature": "income"}]
assert set(numeric_context["description"]["feature_metadata"]) == {"income"}
describe() 返回表粒度、行数、字段类型、指标单位、实际参数和限制。画像表名为 overview、
dq.<metric>、stats.<metric>、comparison.<metric>;分箱表名为 summary、detail、
trend.<metric>,可选附表为 missing_by_day 和 risk_corr_reference。没有计算的表不在目录中。
get_table() 保持所选表的 Pandas/Polars 类型。支持 features、columns、filters、
sort_by、descending、offset、limit 和 sources。筛选是字段到标量相等条件,或
{"op": "gt", "value": 0.1};操作符只允许 eq/ne/lt/le/gt/ge/in/not_in/is_null/is_not_null。
没有 SQL 或表达式执行入口。排序在列投影之前,排序配合 limit 就是 Top-K。
sources 使用 feature_metadata 中的来源,旧来源参数会转换为同一格式后合并;未知来源会报错。
画像和分箱报告中,按 feature 统计的原生表(包括直接访问的属性)默认采用以下列顺序:
feature → display_name → dtype → data_source → 其他维度与指标。已有 target/y
前缀保留。dtype 来自当前输入的原始 schema,参考类型另见 benchmark_dtype;
用户的 feature_metadata 仍只接受 display_name、description、data_source、unit。
未提供显示名时,原表保留 null,展示时可以使用英文名;未知来源记为 UNMAPPED。
非空身份信息存在冲突时会报错。长描述和单位保存在集中字典中,不重复写入每个分箱行。
关系表保留 feature_a/feature_b 等原有角色字段。
columns=None 返回默认 schema;显式 columns=[...] 仅返回所列字段并保持指定顺序,
show_* 与快照 show_table() 也遵守这个规则。可以按未输出的列筛选/排序,错误字段会报错。
例如 profile.show_overview(sort_by="missing_rate", columns=["feature", "display_name", "missing_rate"])
按真实缺失率全局排序,不会被 dtype 前置排序掩盖。组值不能与身份等保留列同名。
新报告的分箱趋势目录没有 trend.bad_rate;读取 detail.bad_rate 或同组 bin_type="汇总组"
的总体箱统计。历史快照保存过的趋势表仍保留。读取旧文件先严格校验磁盘版本、schema 和完整性,
再在内存补齐身份列;没有可靠原始类型时 dtype 保留 null,不采用旧趋势的聚合 Float64。
画像/分箱的 AI JSON 默认只包含 overview/summary 前 10 行;规则报告默认包含 summary 一行和最多三条
最终验证证据,不默认输出 expression。预算为 16,000 个 Unicode 字符,按字符计数,不按 token 计数。
可以按表、特征和列缩小范围。省略的参数和行有明确引用及原因;预算连说明都容不下时抛
ValueError,不会输出无效 JSON。日期使用 ISO-8601;浮点非有限值使用带类型的 $mars 标记;
Null 使用 JSON null,0 保留数值。Null 的业务原因需要结合标签状态、诊断与计算参数解释。
KS 是 0–100 百分制指标,缺失率和坏率是 0–1 比例,IV/PSI 是无量纲数值;币种、标签定义和观察
窗口未登记时标为 unknown。比较前核对拟合来源、参考、权重、箱范围和指标排序;JSON 不生成归因结论。
get_feature("income", limit=100) 返回关联表、每表省略行数和 unavailable 类别。
show_overview、show_summary、show_trend 也支持 columns、limit、sources,复用同一查询。
这些旧展示方法保持不传 limit 时的历史默认;新增 get_feature 与 AI 上下文有默认规模上限。
2. 导出 Excel 或 HTML¶
以下代码继续使用上一步定义的 report:
report.write_excel("risk_report.xlsx", engine="openpyxl")
report.write_html(
"risk_report.html",
report_name="Current-period risk review",
max_plots=100,
chart_embed_mode="auto",
)
| HTML 模式 | 行为 |
|---|---|
auto |
小报告内嵌图片,大报告生成同级资产目录并懒加载 |
inline |
所有图片内嵌,适合必须单文件离线转发的报告 |
asset |
强制使用相对路径图片目录,适合大报告归档 |
风险趋势图和 HTML Charts 需要评估阶段已经提供有效 time_col。group_col 不能替代日期范围。
MarsProfileReport.write_html() 始终生成无外部资源的单文件,包含 Metadata、Overview、
DQ、Stats 和 Comparisons 页面,不生成图表。
所有 Stable 报告导出都会在资源缺失、请求内容未生成或写入失败时抛出异常。
全局搜索与每张表的局部搜索按交集生效;清空其中一个仍保留另一个条件。
局部搜索只影响所属表,排序和页面切换保留条件。当前是页面导航,没有行级分页。
DQ/Stats 页面内部只展示当前指标,指标页签支持键盘方向键和 Home/End,并在外层切换后保留选择。
比例显示为保留两位小数的百分比,浮点统计显示两位小数,整数计数保留整数,小数权重不截断;
PSI/IV/Lift/WOE 保持各自数值,AUC 使用自身数值格式,KS 为 0–100 点。
缺失显示 —,原始数值仍可从 tooltip 或 get_table() 查看。排序使用未舍入原值,
全局和局部搜索可检索英文、中文名及来源;元数据、日期、状态和类别不参与数值色阶。
Notebook、画像 HTML 与画像 Excel 均按指标语义决定单位。宽趋势的列名是业务分组,
即使包含 rate、ratio、auc 或 ks 也不改变该指标的单位。比例端点 0/1 仍显示为
0.00%/100.00%,整数计数不带小数,有小数的加权计数保留小数。
原生图在原说明框首行显示已登记中文名,保留英文 ID 和原指标。显式 group_col 时,
各面板首行显示本次分组全样本的有效 time_col 日期范围,包括未表现标签和缺失特征样本。
没有合法日期的组显示未知;旧报告缺少逐组范围时省略新增行,不使用全局日期代替。
report.plot_risk_trends(features="income", show_risk="both", include_total=False)
report.save_risk_trend_images(
"charts", features="income", image_format="png", show_risk="both",
include_total=False, dpi=300,
)
report.write_html("risk_report.html", max_plots=3, include_total=False)
分箱 HTML 的 include_detail 控制 Grouped Pivot 明细区域,include_charts 独立控制
原生趋势图。include_detail=False 会省去 Pivot 页面、导航、控件及其明细数据;图表仍可
读取原报告明细。两项均为 False 时可以导出纯摘要页面,无需 time_col;启用图表时仍需有效
时间信息。旧 #pivot / #pivot-section 链接会回退到有效页面。
这些开关不改变公共表、查询、保存内容或统计结果。
Charts 搜索支持已登记的中文名、英文 ID、目标及来源;重名中文名会命中所有对应特征, 全局与局部条件按交集生效。英文 feature 继续作为查询、证据和文件名的身份。 Notebook 与完整 HTML 的 Features 均按唯一英文特征计数;PSI 报警计数为 PSI > 0.25 的特征(任一标签触发),同一特征多标签触发只计一次。 未计算值不触发报警,报警数为零不能证明全部特征正常。
build/plot/save/HTML 原生图入口都接受仅限关键字参数 include_total=True。
该参数只控制是否显示 Total 面板。全局 IV/KS/AUC、样本数、Missing、RC 参考和底层明细仍来自完整统计。
关闭后没有任何面板时明确报错。普通业务组名称包含 Total 仍保留。
MarsBinningReport.write_excel() 无论源报告的 ks_method 是 raw 还是 binned,都复用同一
22 列原生模板,主展示统一为分箱 KS(0–100 点)。导出副本的 ks 按特征、目标和实际分组
取 max(detail.ks_bin),广播到同组全部箱行;ks_bin 保留原逐箱值。KS 与综合指标透视
读取导出的 ks,综合指标保留模板的 average 聚合。最终汇总使用对应 Total 的分箱 KS,
最终 KS 趋势保留首目标、实际分组与身份列。全部逐箱 KS 未定义的组在 Excel 中保持空值。
原生 Excel 只根据已计算的数据构建导出副本,不重新拟合或评估,也不改写源报告。
口径说明分别记录源报告 ks_method、导出 binned 口径、max(detail.ks_bin) 来源及单位;
raw 计算诊断明确属于源报告。Python 公共查询、describe()、AI 上下文、保存/加载、HTML
和绘图继续遵循源报告语义,raw 模式数值特征的最终 ks 与类别特征的 binned 回退保持原样。
xlwings 可由 Excel 刷新透视和重算;openpyxl 只写结构/数据并标记打开时刷新,不执行公式或透视计算。
两种输出应使用不同文件名。snapshot_report(...).write_excel() 仍忠实静态导出保存的原报告
数值,没有模板透视交互;源报告为 raw 时,其中的 ks 仍按原报告 raw 语义保存。
两引擎的原生附表按实际导出字段设置数值格式,比例显示百分比,KS 保持 0–100 点,
AUC、IV/PSI/Lift/WOE 保持数值;整数计数带千分位,小数权重与金额保留两位小数,金额不猜币种。
有内容的“最终汇总”作为默认打开页,保留原模板工作表及透视引用。
原生分箱、画像与静态快照 Excel 均将业务字符串按原文保存,包括 =1+1、00123 和日期形文字;
数值与真实日期保持各自类型,模板公式保持原有行为。
分箱报告的原始 Excel 入口保留透视模板;缓存可能仍是模板占位值,需要在原生 Excel 中
刷新透视表并保存。openpyxl.load_workbook(..., data_only=True) 不会执行这个刷新。
向人工用户交付本次当前数值时,使用已有静态快照入口:
from mars.reporting import snapshot_report, load_report
snapshot_report(report).write_excel("risk_static.xlsx")
report.save("risk.marsreport")
# 另一进程只持有保存文件时,也能直接静态导出。
restored = load_report("risk.marsreport")
restored.write_excel("risk_restored_static.xlsx")
静态工作簿逐公共表写出当前值,不依赖透视缓存;它不提供原模板的可刷新透视交互。 最短完整示例同时包含保存、加载和实际 Notebook 比较表渲染:
"""静态 Excel 交付与比较表 Notebook 展示的最短完整示例。"""
from pathlib import Path
import polars as pl
from openpyxl import load_workbook
from mars.analysis import profile_risk, profile_stats
from mars.reporting import load_report, snapshot_report
data = pl.DataFrame({
"income": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
"bad": [0, 0, None, 0, 1, 1, None, 1],
})
report = profile_risk(data, target="bad", n_bins=2).report
# 静态工作簿直接写出当前统计值,不需要 Excel 刷新透视缓存。
snapshot = snapshot_report(report)
snapshot.write_excel("risk_static.xlsx")
workbook = load_workbook("risk_static.xlsx", data_only=True)
assert workbook["000_summary"]["A1"].value == "feature"
workbook.close()
# 只有 marsreport 文件也能导出;可将此步骤放到另一 Python 进程。
archive = Path("risk.marsreport")
report.save(archive)
restored = load_report(archive)
restored.write_excel("risk_restored_static.xlsx")
current = pl.DataFrame({"category": ["甲", "新类别", None], "number": [1, 2, 3]})
benchmark = pl.DataFrame({"category": ["甲", "乙"], "number": ["one", "two"]})
comparison = profile_stats(
current, benchmark_df=benchmark, metrics=["schema", "unseen"],
categorical_features=["number"],
)
# Notebook 中可直接显示 Styler;这里实际渲染,确保文本状态保持原样。
schema = comparison.show_trend("schema")
unseen = comparison.show_trend("unseen", sort_by="feature", sort_ascending=True)
assert "incompatible_change" in schema.to_html()
assert "incompatible_dtype" in unseen.to_html()
comparison.write_html("comparison.html")
3. 单独复用趋势图¶
继续使用上一步定义的 report:
figures = report.build_risk_trend_figures(features=["income"])
fragment = report.render_risk_trends_html(
features=["income"],
image_format="svg",
embed_mode="inline",
)
fragment.html 是可嵌入现有模板的 HTML 片段;资产模式同时返回已写入的图片路径。
中文名和分组日期按实际绘制时的 renderer 与 DPI 排布;取得 Figure 后修改 DPI,或在
PNG、SVG 之间反复导出,也会重新测量新增文字。英文身份、业务标题和统计沿用原有字体与
字号,$、反斜杠等业务文字按字面显示。
4. 构建评分卡¶
评分卡为 Experimental,暂时停止功能迭代;现有转换与部署能力继续保留。 必要的正确性、运行修复与上游适配继续处理,规则见稳定性。
Scoring:Experimental
评分映射、刻度参数和 SQL 输出仍可能调整。本文对应 0.0.28,发布后按
安装指南固定该版本;若 PyPI 尚未提供,使用 main 源码
并记录核验的提交。为 points_table 和生成 SQL 增加契约测试。
import polars as pl
from mars.feature import MarsNativeBinner
from mars.scoring import build_scorecard
X = pl.DataFrame(
{
"income": [2600, 3100, 3500, 3900, 4500, 5200, 6100, 7200],
"utilization": [0.72, 0.64, 0.55, 0.48, 0.36, 0.28, 0.19, 0.11],
}
)
y = pl.Series("target", [1, 1, 1, 0, 1, 0, 0, 0])
binner = MarsNativeBinner(method="quantile", n_bins=4).fit(
X,
y,
features=["income", "utilization"],
)
scorecard = build_scorecard(
binner,
coefficients={"income": -0.35, "utilization": 0.70},
intercept=-1.2,
pdo=20,
base_score=600,
base_odds=50,
)
points_table = scorecard.points_table
sql = scorecard.generate_sql(
features=["income", "utilization"],
table_prefix="applications",
score_name="credit_score",
)
评分卡要求分箱器已经使用 target 拟合并具备 WOE 映射。系数字典的特征必须与分箱器规则一致。
常见失败¶
- HTML 图表为空:确认生成 report 时传入了有效
time_col。 - 大报告单文件打开缓慢:使用
auto或asset,不要强制内嵌数百张图片。 - 评分卡提示缺少映射:确认 binner 已拟合、特征名一致且包含 WOE 统计。
下一步¶
- 理解 report 与 artifact 的边界:Report 与 Artifact。
- 查询导出对象:Reporting API。
- 查询评分卡签名:Scoring API。
可携带的公共分析报告¶
状态:Stable。 画像与分箱报告实现 mars.reporting.Report 契约。外部建模 Agent、诊断归因
Agent 可以直接调用 describe()、search_features()、get_table()、query_page()、
get_feature()、to_ai_context() 和 save(),无需 MarsAgentSession、Notebook 或原始宽表。
完整可运行示例(包括新 Python 进程恢复、两种 Agent 消费方式及 HTML/Excel 导出):
"""完整报告跨进程使用示例;业务定义仅为模拟数据定义。"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
from tempfile import TemporaryDirectory
import pandas as pd
import polars as pl
from mars.analysis import profile_risk, profile_stats
from mars.reporting import load_report
def main() -> None:
"""生成、展示、导出、保存和恢复报告,并演示两种 Agent 消费方式。"""
df = pl.DataFrame(
{
"income": [2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000],
"salary": [2100, 2800, 4200, 4700, 5900, 7100, 8100, 8900],
"y30": [0, 1, 0, 1, None, None, None, None],
"y60": [None] * 8,
"split": ["train"] * 4 + ["oot"] * 4,
}
)
dictionary = {
"income": {
"display_name": "月收入",
"description": "模拟申请的申报税后月收入",
"data_source": "application",
"unit": "CNY/month",
},
"salary": {
"display_name": "月收入",
"description": "模拟授权流水的入账月收入",
"data_source": "bank",
"unit": "CNY/month",
},
"other_project_feature": {"display_name": "其他项目字段"},
}
business_context = {
"labels": {
"y30": {
"definition": "模拟首期还款违约",
"positive_class": "违约",
"negative_class": "履约",
"performance_window": "放款后30天",
},
"y60": {"definition": "模拟第二期还款违约", "performance_window": "放款后60天"},
},
"sample": {
"scope": "示例模拟申请",
"filter": "全部8条模拟样本",
"time_range": ["2026-01-01", "2026-02-28"],
},
"splits": {"train": "模拟开发样本", "oot": "模拟未来样本"},
"currency": "CNY",
"score_direction": "unknown",
}
rows = [{"feature": feature, **metadata} for feature, metadata in dictionary.items()]
# DataFrame 必须含明确的 feature 标识列;两种后端使用同一套校验。
pandas_dictionary = pd.DataFrame(rows)
polars_dictionary = pl.DataFrame(rows)
profile = profile_stats(
df,
features=["income", "salary"],
metrics=["missing", "mean"],
group_col="split",
feature_metadata=pandas_dictionary,
business_context=business_context,
)
polars_profile = profile_stats(
df, features=["income", "salary"], metrics=["mean"], feature_metadata=polars_dictionary
)
assert polars_profile.get_feature("income")["metadata"]["display_name"] == "月收入"
displayed = profile.show_overview(limit=2).data
print(displayed.loc[:, ["feature", "display_name", "mean"]])
run = profile_risk(
df,
target=["y30", "y60"],
features=["income", "salary"],
group_col="split",
n_bins=2,
feature_metadata=dictionary,
business_context=business_context,
)
report = run.report
print(report.show_summary(limit=4).data)
candidates = report.search_features("月收入")
assert {item["feature"] for item in candidates} == {"income", "salary"}
identifier = report.search_features("授权流水", sources="bank")[0]["feature"]
print(report.get_feature(identifier)["metadata"])
print(report.get_table("calculation_status", filters={"target": "y60"}))
persistent_id = report.report_id
with TemporaryDirectory() as directory:
root = Path(directory)
report.write_html(str(root / "analysis.html"), include_charts=False)
report.write_excel(str(root / "analysis.xlsx"))
path = root / "analysis.marsreport"
report.save(path)
# 新 Python 进程只读取文件;没有原宽表、分箱器或会话。
code = (
"from mars.reporting import load_report; import sys; "
"r=load_report(sys.argv[1]); "
"assert r.report_id == sys.argv[2]; "
"print(r.get_table('summary', features='income', limit=2))"
)
subprocess.run([sys.executable, "-c", code, str(path), persistent_id], check=True)
del report, run, df
restored = load_report(path)
assert restored.report_id == persistent_id
# 外部建模/诊断 Agent 直接使用公共契约,不需要 MarsAgentSession。
catalog = restored.describe()
print(list(catalog["tables"]))
page = restored.query_page(
"detail",
features=identifier,
filters={"y": "y30"},
sort_by="bin_index",
offset=0,
limit=3,
)
print(page["reference"], page["total_rows"], page["next_offset"])
context = restored.to_ai_context(
features=identifier,
max_chars=8000,
queries={
"summary": {"sort_by": "iv", "descending": True, "limit": 2},
"detail": {
"filters": {"y": "y30"},
"columns": ["feature", "y", "bin_index", "count", "bad_rate"],
"limit": 3,
},
"calculation_status": {"filters": {"target": "y60"}, "limit": 3},
},
)
assert len(context) <= 8000
assert json.loads(context)["evidence"]
# 内部 Agent 也是该契约的消费者;句柄与持久标识分别记录。
if sys.version_info >= (3, 10):
from mars.agent import MarsAgentSession, MarsRiskAgent
original_session = MarsAgentSession()
original_session.register_report(restored)
del original_session
session = MarsAgentSession()
handle = session.register_report(restored)
result = MarsRiskAgent().execute_tool(
"get_report_table",
{"report_id": handle, "table": "summary", "features": [identifier], "limit": 2},
session=session,
)
assert result.success
assert result.data["persistent_report_id"] == persistent_id
print(handle, persistent_id)
restored.write_html(str(root / "restored.html"))
restored.write_excel(str(root / "restored.xlsx"))
if __name__ == "__main__":
main()
特征元数据与业务上下文¶
profile_stats、MarsDataProfiler.generate_profile、profile_risk、MarsBinEvaluator.evaluate、
MarsStatsSelector.fit/fit_transform 和两个报告构造器都接受 feature_metadata、
business_context。内部 Agent 的 register_dataset 也可登记这些信息。
特征字典以原始英文字段名为稳定键,支持 display_name/description/data_source/unit 四个
可选字符串字段,缺失值允许 null。Pandas/Polars DataFrame 必须有 feature 列,其余列对应上述
字段;重复记录、非字符串值和未知字段报错。字典可覆盖整个项目,报告按实际分析特征裁剪。
重复中文名合法,检索返回全部候选及英文标识;features= 和 get_feature 仍只查询原始标识。
缺少显示名时,人类展示回退英文名,元数据中仍保留“未提供”的事实。中文名称按展示查询后的
小表附加,不重命名原始输入和统计表,也不把业务定义重复到每个日期或箱。来源旧参数在分析/
selector 中为“来源 → 特征列表”,在分箱报告构造器中为“特征 → 来源”。两者归一到元数据;
与新字典来源相同则合并,不同则报错。旧参数仍要求仅包含 active features;全项目字典请改用
feature_metadata。UNMAPPED 仅为旧统计表展示标记,不会作为已知业务来源写入元数据。
业务上下文是字符串键的 JSON 对象,支持字符串、布尔、数值、null、嵌套字典、列表和 ISO 可编码
的 date/datetime;不支持 tuple、集合、自定义对象或可执行 Python 对象。$mars 是保留键。
多标签定义使用 labels={target: {definition, positive_class, negative_class, performance_window}};
sample 描述范围/筛选/时间区间,splits 描述 train/val/test/oot,currency 和
score_direction 保存调用方解释。其他 JSON 业务字段也可保存。不提供的信息保持 unknown;
不根据字段名推测业务定义。describe().context_source 区分用户信息与实际计算参数,
parameters 记录真正执行的权重、排序、分箱、基准和诊断;用户上下文不改变计算公式。
unit 是特征值单位,不是 KS/IV/PSI 的指标单位。
分页查询、证据与上下文预算¶
get_table 保留原生 DataFrame 类型,筛选、排序和分页后返回独立容器。支持原标识、
来源、列投影、eq/ne/lt/le/gt/ge/in/not_in/is_null/is_not_null 筛选、稳定排序、offset、limit。
日期/分组宽表范围通过 columns 选择,长明细通过 filters 选择。真实日期类型列支持 ISO-8601
字符串筛选,因此可以直接重放 JSON 证据查询;普通字符串列保持字符串类型。Pandas 索引仍保留,但不作为
查询列;需要筛选索引时先在自己的消费代码中处理。
query_page 使用相同参数,额外返回 total_rows/returned_rows/omitted_rows/truncated、
省略原因、next_offset 与 reference={report_id, table, query}。排序、投影和分页条件
可在保存恢复后重放。持久 report_id 是分析产物身份;会话登记返回的 report_id 参数实际是
会话句柄,Agent 工具另外提供 persistent_report_id/evidence_reference。外部报告如实记录
原 source,dataset_id 为 None;登记后修改原报告数据或元信息不影响已登记快照。
query_page() 默认 limit=10,显式页长只接受正整数或 None;0、负数、bool、浮点数
会抛出 ValueError。None 返回 offset 之后的全部剩余行,不再提供续页。
有续页时 next_offset 严格前进,结束时为 None。
get_table(limit=0)、get_feature(limit=0)、to_ai_context(limit=0) 及每表 queries
中的零行请求仍有效,用于取得 schema、匹配数及空证据。零行证据的 query.limit=0
保留,可通过 get_table(evidence["reference"], **evidence["query"]) 精确回放;需要继续取证时,使用省略记录
continue_at 中的正页长查询调用 query_page()。
to_ai_context(queries={table: get_table_options}, max_chars=16000) 支持不同表使用不同条件;
旧的 features/columns/filters/limit 等共同参数会适配到相同查询路径。摘要不能替代完整文件。
成员关系的 key 和 feature 可以指向同一列。原报告和保存后的快照都支持筛选、分页和生成 AI 证据,
统计行不会按成员重复展开。有限上下文只保留实际返回页涉及的成员和关系两端;
报告声明的 feature_scope 用于解释实际返回的汇总行,不附带未返回特征的字典。
未选择身份列时,evidence.identities 会保留既有身份字段,与 rows 一一对应;减少返回行数时,身份信息同步缩减。
宽趋势表只定义一次指标,日期/分组仍用原始列名标识,证据行与原表保持对应。
evidence.query 记录实际展示子集的查询。预算裁剪后,columns 和 limit 会同步缩减,
直接传回 get_table 即可重建展示的行和列。结果仍保留原筛选后的 total_rows 和省略说明。
非有限值筛选使用既有 $mars float 标记,标量与 in 列表均可 JSON 回放;畸形标签明确报错,
普通字符串不转换,NaN 相等和空判断沿用各后端现有规则。
预算按最终 JSON 的 Unicode 字符计数,包含查询和身份,不是 token 数。
超出预算时,接口依次裁剪完整时间列、行和说明块,并记录省略数量、原因及 describe / get_table 查询位置。
必要身份和引用仍超过预算时,接口会抛出 ValueError。上下文默认不包含未选择特征的项目字典。
单文件格式与恢复¶
.marsreport 是 ZIP,格式版本 1:manifest.json 保存持久身份、报告类型、全部表目录、
粒度、字段类型、指标定义、参数、上下文、元数据、状态/诊断及来源;tables/0000.parquet 等
文件保存每张完整统计表,清单把编号路径映射回公共表名。文件不使用 pickle,也不反序列化代码。
外部程序可以用支持 ZIP、JSON 和 Parquet 的工具读取,表顺序和列顺序会明确保留。
保存时逐表处理,不把整个报告转换为 Pandas 或大 JSON。父目录必须存在,默认拒绝覆盖已有文件。
设置 overwrite=True 时,先写入同目录的临时文件,再原子替换目标文件;失败会清理临时文件,
不改变已有完整文件。无效 ZIP、清单、缺失表、schema/顺序/行数不一致和非 1 版本明确报错;
当前没有历史版本迁移器。
清单记录各表后端及版本。跨后端版本若不能保留声明的 dtype,会明确拒绝恢复; 尚未承诺任意 Pandas/Polars 版本组合之间的 schema 迁移。格式仍可由标准 Parquet 工具读取。
恢复返回 ReportSnapshot,保留原报告类型、身份、目录语义和每张表的后端,无需原始宽表、
分箱器或会话,也不会重新计算。show_table 可查看中文名与英文标识;
write_html/write_excel 导出全部公共表。原报告的 HTML/Excel 继续使用现有展示方式,并集中新增
FeatureMetadata/BusinessContext/ReportSemantics 信息。Excel 是人类展示产物,
需要无损复用统计值时使用 marsreport 文件。
Pandas 索引(包括 MultiIndex)、类别、日期和时区依赖 Arrow pandas schema 元数据保留; Polars 使用原生 Parquet 类型。清单记录规则并验证恢复 schema。表列必须是唯一字符串名, 无法编码的对象报错。Parquet 区分 null、浮点 NaN、±Infinity 和零;若原 Pandas 浮点列已经将 缺失折叠为 NaN,恢复不会伪造二者之间的区分。日期 JSON 使用 ISO-8601,date 与 datetime 依其实际 DataFrame 类型分别输出日期或完整时间戳。
状态与迁移¶
JSON 中的浮点非有限值统一为 {"$mars":"float","value":"nan"}、
{"$mars":"float","value":"inf"} 或 {"$mars":"float","value":"-inf"};null 表示缺失值,
零仍是数值零,普通字符串 "NaN"/"Infinity" 不转换。描述、AI 上下文、Agent 工具及文件清单
共用此规则。原消费者需停止把报告字符串标记或 Agent null 当作统一非有限数编码。
calculation_status 按目标、特征、分组和指标族保存计算状态及原因。未提供标签时为 not_computed,
标签存在但全部未表现时为 unobserved。只有一个箱、正常箱样本不足或坏率恒定时,mono 未定义,
NaN/null 会保持缺失。旧代码若依赖 1.0 占位值筛选单调性,应改为先检查状态,再处理缺失。
IV、PSI 和原始/分箱 KS 公式保持现有口径;KS 为 0–100 点数,坏率为 0–1 比例,
count 字段根据实际 weights_col 标明原始样本数或权重和。类别原始 KS 回退和失败仍见
ks_source_by_feature/raw_ks_diagnostics;分箱失败/跳过继续见已有 fit/diagnostics 信息。
画像的状态表只记录全量统计例外(样本不足、非数值字段跳过、未定义值及已有 PSI 失败诊断),
不生成每个趋势单元格的状态对象;缺少状态行不应代替核对实际值和诊断。
HTML/Excel 也集中导出该状态表;原报告 Excel 工作表名为 CalculationStatus。