news 2026/10/25 1:59:45

从「一个数」到「一份证据链」,问数平台结果可核对的工程实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从「一个数」到「一份证据链」,问数平台结果可核对的工程实现

这是园区数字化落地系列的技术第六篇。前五篇分别讲四层架构与数据建模、四端协同的事件驱动架构、指标口径中心、读时建模与多源异构接入、NL2SQL 的落地链路。第五篇把一句人话怎么变成 SQL 讲完了,这一篇接着往后看,SQL 跑完之后,平台怎么把结果变成业务方敢拍板的东西。

第五篇末尾那张踩坑表里有一行,叫「结果不可核对」,写的是只给一个数,业务方问哪来的答不上。这一行不是随手写的。我们上线头两个月,被老板问住的次数,八成出在「数给你了,但你说不清它怎么来的」。本篇就拆这件事,看平台怎么把一次查询变成四样东西,让每条回答都带证据链。


一、只给结论的产品为什么没人敢用

问数平台最容易做成黑盒。用户问一句,屏幕弹一个数,完事。这个数对不对,全凭信任。

园区里信不过黑盒。财务拿到「本月收缴率 92%」,第一反应不是高兴,是问「按哪个口径,含不含历史欠费」。运营拿到「空置房源 37 间」,会去翻房源表核对楼栋分布。只要答不上来源,这个数就不算数,还不如当初让人自己查。

我们后来把验收标准定死,一次问答必须同时产出四样,缺一样业务方就拒收。这一条比模型准不准还靠前。


二、一次问答的四件套产物

这四样分别是结论、图表、明细、来源。

结论是一句话的答案,比如「本月收缴率 92%,较上月升 3 个点」。它必须能被图表和明细支撑,不能单独出现。

图表是结论的可视化,折线看趋势,柱状看对比,饼图看占比。图表的数据点要能点开,不能只是装饰。

明细是支撑结论的原始行,业务方想深挖就能下载到每一笔账单、每一间房。业务方要核对原始账,就得靠它。没有明细,结论再漂亮也没法验证。

来源标注这条结论从哪个库、哪张表、哪个字段、哪个口径版本、什么时间窗口算出来的。来源说不清,前三项都站不住。

四件套里,明细和来源最容易被偷懒省掉,却恰恰是业务方最看重的。我们强制在服务端塑形,不依赖前端兜。


三、产物怎么从 SQL 长出来

第五篇的 L4 叫执行回填,本篇把它拆细。一次查询从 SQL 到四件套,走四步。

第一步,只读账号执行 SQL,拿到原始结果集。这一步不碰原业务库写权限,纯粹读。

第二步,结果塑形。把结果集按意图拆成聚合层和明细层,聚合层喂给结论和图表,明细层落一张临时导出表。

第三步,口径挂接。从口径中心拉出这条指标的定义、计算 SQL、数据时间戳、版本号,贴到来源标签上。

第四步,三级下钻装配。结论卡、图表、明细入口、来源标签组装成一个回答页,前端各取所需。

塑形这一步我们踩过坑。最初让前端自己决定哪些字段做图、哪些做表,结果不同端展示不一致,同一个数在网页和手机上长的两样。后来把塑形逻辑收归服务端,前端只负责渲染,一致性才稳住。


四、支撑四件套的数据模型

四件套不能只在内存里拼一下就返回,得落库,不然没法审计、没法回溯、没法版本化。我们建了三张表。

sql

-- 产物元数据, 每次问答的产物登记, 四件套各挂一项 CREATE TABLE answer_artifact ( answer_id VARCHAR(64) NOT NULL COMMENT '一次问答的编号', kind VARCHAR(16) NOT NULL COMMENT 'conclusion/chart/detail/source', payload MEDIUMTEXT NOT NULL COMMENT '结论文本 或 图表配置 或 明细表名 或 来源 JSON', metric_code VARCHAR(64) COMMENT '关联的指标编码', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (answer_id, kind), INDEX idx_metric (metric_code) ) COMMENT='问答产物'; -- 口径血缘, 每个指标标注取数路径与版本 CREATE TABLE metric_lineage ( metric_code VARCHAR(64) NOT NULL COMMENT '指标编码, 如 collection_rate', version VARCHAR(16) NOT NULL COMMENT '口径版本, 如 v202610', catalog_path VARCHAR(128) NOT NULL COMMENT 'catalog 路径, 如 park/room/bill', calc_sql TEXT NOT NULL COMMENT '该版本的计算 SQL', data_as_of DATETIME NOT NULL COMMENT '数据时间戳, 取数快照点', owner VARCHAR(32) NOT NULL COMMENT '口径负责人', PRIMARY KEY (metric_code, version) ) COMMENT='指标口径血缘'; -- 明细导出任务, 支撑下载的原始行 CREATE TABLE detail_export_job ( job_id VARCHAR(64) NOT NULL PRIMARY KEY, answer_id VARCHAR(64) NOT NULL COMMENT '所属问答', src_sql TEXT NOT NULL COMMENT '取明细的原始 SQL, 含权限裁剪', row_count INT COMMENT '导出行数', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待跑 1完成 2失败', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT='明细导出任务';

三张表各管一段,answer_artifact记产物,metric_lineage记口径来历,detail_export_job记明细出处。业务方质疑时,拿 answer_id 一查,四件套和来源全在。


五、口径来源怎么标注

来源标签是业务方最在意的,我们把它做成结构化字段,而不是一句话描述。每个指标在metric_lineage里登记 catalog 路径、计算 SQL、数据时间戳、版本。

查询时,平台先取指标的当前生效版本,再把血缘拼进来源标签。

sql

-- 取指标的当前生效口径, 拼进来源标签 SELECT l.catalog_path, l.calc_sql, l.data_as_of, CONCAT(l.metric_code, '@', l.version) AS lineage_tag FROM metric_lineage l WHERE l.metric_code = 'collection_rate' AND l.version = ( SELECT MAX(version) FROM metric_lineage WHERE metric_code = 'collection_rate' );

来源标签返回到前端长这样,业务方一眼看到数从哪张表、哪个版本、什么时点算的。

json

{ "metric": "collection_rate", "lineage_tag": "collection_rate@v202610", "catalog_path": "park/bill/collection", "data_as_of": "2026-10-22 23:59:59", "calc": "实收金额 / 应收金额, 含本期及历史欠费" }

数据时间戳这道关很关键。园区口径常按月结,十月二十二号问的数,应当快照到九月三十号的账期,而不是实时库里还在变的流水。data_as_of就是把这点钉死,避免同一个人早晚问出两个收缴率。


六、明细下钻的取数

结论和图表是聚合层,明细是原始层。下钻时不能再去重算一遍聚合 SQL,那样慢且易错。我们的做法是聚合层和明细层共用同一条带权限的底层 SQL,聚合做 GROUP BY,明细直接 SELECT 原始行。

sql

-- 底层带权限的取数 SQL, 聚合与明细共用 SELECT b.building_name, bil.tenant_id, bil.bill_no, bil.receivable, bil.received, bil.due_date FROM fact_bill bil JOIN dim_building b ON bil.building_id = b.building_id WHERE bil.period_month = '2026-09' AND bil.building_id IN ( SELECT building_id FROM user_visible_building WHERE user_id = 'u_1024' ); -- 结论与图表, 在底层结果上聚合 SELECT building_name, SUM(received) / NULLIF(SUM(receivable), 0) AS rate FROM ( /* 上面的底层 SQL */ ) t GROUP BY building_name; -- 明细, 直接取底层原始行, 落导出任务 INSERT INTO detail_export_job (job_id, answer_id, src_sql, status) VALUES ('dj_8841', 'ans_5520', '/* 底层 SQL */', 0);

这样聚合和明细永远对得上,不会出现图显示 92% 但下载的明细加起来是 88% 的尴尬。明细落任务表后异步跑,前端拿 job_id 轮询,大表也不卡界面。


七、口径版本怎么治理

口径会变。九月收缴率含历史欠费,十月财务改主意要剔除非本期账单,口径就漂移了。漂移不可怕,怕的是悄悄变,昨天答的和今天答的用的不是一套算法,业务方却不知道。

我们把口径当成带版本的配置管,变更要走灰度、通知、回滚、审计四步。

yaml

metrics: - code: collection_rate versions: - tag: v202609 calc_sql: | SELECT SUM(received)/SUM(receivable) FROM fact_bill WHERE period_month='2026-09' data_as_of: "2026-09-30 23:59:59" owner: 财务-王磊 status: archived - tag: v202610 calc_sql: | SELECT SUM(received)/SUM(receivable) FROM fact_bill WHERE period_month='2026-09' AND is_current_period=1 data_as_of: "2026-10-22 23:59:59" owner: 财务-王磊 status: active rollout: gray_ratio: 0.2 # 先放 20% 查询命中新口径 notify: [财务群, 运营群] # 变更群内播报 rollback: v202609 # 出问题一键回退 audit: true # 每次口径命中留审计记录

灰度让新口径先在小流量验证,通知让相关方知情,回滚保底,审计兜底。口径变更走配置,某个工程师改条 SQL 就自动生效的事不会再发生。历史回答挂的是当时的版本号,回头查九月的数,拉 v202609,不会被十月的改动污染。


八、权限与审计

可核对的前提是可查,可查的前提是权限守得住。平台用专用只读账号连库,不等于能翻任意一张表。我们在三个层面收口。

账号层,给问数平台单独开一个只读账号,权限范围由 DBA 配,原系统读写账号绝不共用。

对象层,可查对象走白名单。语义层只登记允许被问的表和字段,没登记的表,生成层根本看不到,更不会进 SQL。

审计层,每次查询落一条记录,谁问的、问了什么、跑了哪条 SQL、命中哪个可见范围、返回多少行、耗时多少,全记下来。

json

{ "account": "rd_askbot_ro", "privilege": "read_only", "visible_objects": ["dim_room", "dim_building", "fact_lease", "fact_bill"], "query_log": { "user_id": "u_1024", "question": "本月各楼栋空置房源数", "final_sql": "SELECT ... AND building_id IN (SELECT building_id FROM user_visible_building WHERE user_id='u_1024')", "row_scope": "building=1,3,7", "result_rows": 42, "elapsed_ms": 318, "asked_at": "2026-10-22 14:05:11" } }

权限这道锁宁可多一道不嫌烦。我们试过只靠提示词约束模型别查敏感字段,十次里有两次还是查了。改成账号层只读、对象层白名单、召回层剔除三层同时收口,敏感字段从一开始就进不来。


九、工程踩坑表

按踩到的频率排,长这样。

坑现象解法
只给结论弹一个数,业务方问来源答不上强制四件套,结论图表明细来源一个不少
明细缺位图显示 92%,下载明细对不上聚合与明细共用同一条带权限底层 SQL
口径漂移同日问同指标前后两版算法口径带版本号,变更走灰度回滚审计
数据时点乱早晚问出两个收缴率口径钉死 data_as_of 快照点
前端各做各网页和手机展示不一致塑形逻辑收归服务端,前端只渲染
敏感字段泄露提示词约束,模型偶尔还查账号只读+对象白名单+召回剔除三层收口
明细卡界面大表直接查,界面转圈明细落导出任务异步跑,前端轮询
来源一句话来源写成自然语言,不可机读来源结构化,catalog 路径+SQL+时间戳+版本
口径改动无痕工程师改条 SQL 就生效口径当配置管,变更群内播报留痕
历史污染查九月的数被十月改动影响历史回答挂当时版本号,回溯不受影响


十、关于本文涉及的两套系统

这套可核对工程不是凭空长出来的,它的输入和运行环境靠两套系统配合。

  • 智慧园区系统,四端一体的运营平台,负责把业务在线办完、过程留痕、数据结构化沉淀。房源、租客、合同、账单都从这里出,是问数平台的唯一数据源。没有它,语义层就没有可问的对象,明细和来源也无从谈起。
  • 数智安全问数平台(鲁联数智提供),在园区系统之上加一个问数入口。只读接入,不改原业务表结构,NL2SQL 把人话翻成 SQL,本篇讲的四件套塑形、口径血缘、明细下钻、版本治理,是它把数变成证据链的实打实工程。

一句话,智慧园区系统管发生,问数平台管看懂,可核对是看懂这件事能不能让人服气的那道关。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 1:57:46

Linux 开发工具(六) 版本管理器git的简单使用

目录 一. 简单认识一下 git 1.1 本地仓库和远端仓库 1.2 去中心化、分布式的版本控制 二. git 实操 2.1 git 的创建仓库 2.2 拉取远端的仓库到本地仓库 2.2.1 .git 2.2.2 代码版本的修改 2.3 在本地仓库添加新的文件 2.3.1 工作区和本地仓库 2.3.2 添加文件(续) 2.…

作者头像 李华
网站建设 2026/10/25 1:57:46

Python变量的基础类型

在学习编程语言的过程之中, 人们常常无法不去掉对变量这个概念的关注。所谓的变量, 它是用来给数据起名字的这样一种存在。你能够把具体的数值赋值到变量上面, 然后你可以在程序的其他各个地方去使用这个变量, 从而来引用之前已经赋值的那个具体数值。总而言之, 变量在整个程序…

作者头像 李华
网站建设 2026/10/25 1:57:14

大小端自我学习理解

大小端学习理解 1.什么是大小端 简单来说: 大端:高位字节放在低地址 小端:低位字节放在低地址 例如: 0x12345678 拆开是四个字12,34,56,78 放在大端小端的地址中:地址0x10000x10010x…

作者头像 李华
网站建设 2026/10/25 1:56:43

回顾自己2年的博客,发现了流量的真相

起因写博客两年了,发了 21 篇原创。前几天,我把文章重新整理了一下,发现了一些问题。如果你也在 CSDN 上写东西,希望这些经验能给你一些帮助。数据看着数据感觉还行,其实平均数据的水分还是比较大的。原创文章21篇总阅…

作者头像 李华
网站建设 2026/10/25 1:56:19

解决:使用Burp Suite工具实现防重放攻击的过程及遇到的代理等问题

Ⅰ、Burp Suite实现防重放攻击: 其一、打开Burp Suite工具: a、双击 Start.vbs: b、可以删除之前文件: c、选择临时项目并点击下一个: d、选择使用Burp的默认值并点击进入Burp: e、此时就进入Burp Suite工具了: 其二、获取目标下发报文: a…

作者头像 李华
网站建设 2026/10/25 1:55:36

笔记--浅谈内存,存储空间,BIOS芯片,显卡GPU、VRAM(1)

内存,存储空间,BIOS芯片,显卡---这几者名字各不相同,但是被一个东西串联在一起,那就是G(GB)GB(G)本质就是容量单位,1GB等于1024MB,1MB又等于1024KB,1KB又又等于1024字节(…

作者头像 李华