Files
ln-bi/docs/asset-history.md
T
shishengliang 3ef30215e0
ci/woodpecker/push/woodpecker Pipeline was successful
feat(assets): 每天23:55采集资产状态快照
2026-10-30 16:57:33 +08:00

18 KiB
Raw Blame History

当前展示口径(2026-10-24 更新)

按用户最新要求,停止展示交还记录推算的历史资产数据。正式路由只提供当前实时数据和真实归档快照,已取消 ASSET_HISTORY_RECONSTRUCTION_FILE 的接入;即使保留旧环境变量,也不能通过趋势或明细接口读取回溯文件。旧核对表和私有回溯文件保留用于离线核查,不删除、不写入正式快照。

2026年10月前的缺失期间返回空值,不补零、不提供估算明细。页面仅显示一句提示:“历史库存状态快照缺失,核对中,暂不展示。” 当前10月继续读取实时数据;今后只有采集成功的真实快照才能归档展示。服务端仍返回存储和采集状态供运维检查,页面不再重复展示部署及回溯说明。

本地验收服务同步停用回溯文件。生产迁移、部署和自动采集仍未执行;该展示调整不等于采集已经启用。下文的回溯回填章节保留为历史实施记录,其环境变量接入和页面展示说明已被本节取代。

资产历史趋势

资产概览顶部指标之后新增历史趋势。月度选择年份,年度每年一个年末点;运营和库存为堆叠柱,总数为折线。下方期间按钮支持键盘和触摸选点,月度明细可分类、搜索车牌/VIN及分页。

口径及时间

  • 复用资产仓储 MAIN_SQL、transformRow、isOperatingVehicle、isInventoryVehicle。
  • 总数为未删除、未退出运营的车辆;运营为租赁/自营,库存为可运营/待运营;异动、待交车不另加。
  • 每天 Asia/Shanghai 23:55 为计划时点,归属当天;只允许在 23:55:00(含)到 23:56:00(不含)内开始读取。每天独立保存完整逐车状态;月度展示月末当天、年度展示12月31日快照,不累加每日存量。
  • captured_at 是程序记录的实际开始读取时间,scheduled_for 是计划时点;两者不宣称完全相同。
  • 一致性读取可在窗口内开始后继续执行;持久化失败最多重试三次,始终使用同一份已取得数据,不重新读晚到的台账。
  • 当月在截止前显示实时点;年末前当年年度点显示实时点。截止后只能显示归档、归档中、失败或缺失。12 月快照直接用作年度年末点。
  • 实时点每五秒走 /live,不重读全部历史;切换年份/公司/模式、手动刷新、跨月或归档边界时更新历史。
  • 实时数据沿用现有资产仓储的五秒缓存,实时 asOf 为本次服务端查询时间,不代表源台账所有记录的最后修改时间;固定快照绕过该缓存。
  • 缺失及未来数量为 null,合法零数量为 0,不插值、不跨缺失点连线。比较只取相邻期间;一月使用上年十二月作为基期。
  • 默认年度显示最近五年;更早有归档或导入时自动扩展年份。月度允许查询 2000 年至当前年份。

权限及历史字段

本功能是资产存量统计。实时和归档都复用 filterAssetVehiclesByPermission:库存对账号公共可见,其余车辆沿用全量、部门或个人权限。公共库存按所查询时点的运营状态判断;历史公司、部门和负责人取快照,角色及个人身份别名取当前认证系统。公司筛选在权限过滤范围内取 subjectOrg。

不连接今日车辆列表补字段,不因车辆退出、删除或转部门丢失历史。当前来源的部门权限使用部门名称,快照如实保存该名称,不虚构来源没有的部门 ID。未来源系统若增加稳定部门 ID,需版本化升级口径。

归档汇总从授权后的同一批逐车数据计算,不返回批次全量计数。明细客户名称复用既有脱敏。历史公司仅出现在有权限的相关历史数据中;顶部公司列表中的历史独有公司标注“历史主体”,不改变现有实时公司接口含义。权限视角切换会由现有认证容器重建页面并取消旧请求。

存储

bi_asset_history_settings.enabled_at 保留旧采集首次启用时间;daily_enabled_at 保存每日采集首次实际启用时间。重复启用保留首次时间,升级前的日期不会被标记为漏采。

bi_asset_history_batches 保存期间键、口径版本、计划及实际时间、来源、状态、失败码、全量校验计数及导入追溯信息。period_key 与口径版本唯一;新快照使用 YYYY-MM-DD,旧月度 YYYY-MM 键和明细原样保留。月度查询仅选月末日及旧月度记录;同月优先新日快照,但失败或缺失不会遮蔽旧 COMPLETE 归档。正式归档为 COMPLETE,失败为 FAILED,错过为 MISSED。

bi_asset_history_vehicles 保存车辆字符串 ID、原始运营/车辆状态、归档分类、公司、部门、负责人 ID 和冻结车辆 JSON。批次内车辆唯一;不以当前车辆表为外键。JSON 保留车牌、VIN、型号、品牌、业务归属等明细信息,排除无关的异动工作详情和待交车附件信息。

采集用 MySQL 同一连接的命名锁防止多实例并发,以只读一致性事务执行原始 SQL,再以独立事务原子发布头表、全部明细和 COMPLETE 状态。窗口外不读源数据。读接口、普通模块导入和只读预览不建表、不采集。读到不存在的表时显示部署提示并保留当前实时点;真实数据库故障返回错误,不伪装成空数据。

归档读取会校验车辆唯一性、分类、明细计数和批次计数,不把损坏的明细显示为合法零快照。已提交但提交确认丢失时,重试先检查 COMPLETE,不覆盖已发布数据。

API

全部接口经现有认证和权限预览中间件,响应 Cache-Control: private, no-store。

GET 参数 用途
/api/asset-history/trend mode=monthly|annual、year、subject 趋势点、相邻差值、可选年份及相关历史公司
/api/asset-history/live mode、subject 仅更新当前实时点,不查历史明细
/api/asset-history/details month=YYYY-MM、batchId、subject、category=all|operating|inventory、search、page、pageSize 月度明细,默认 50 台,最大 100 台;页面每页 30 台

归档明细入口附带批次 ID,服务端同时核对月份。没有可用快照返回 409,不回退到实时台账。400 为参数错误,401 为未认证,503 为读取或数据质量异常。没有 HTTP 采集、写入和导入接口。

迁移与启用

默认关闭:ASSET_HISTORY_CAPTURE_ENABLED 只有精确设为 1 才启动采集。迁移不等于启用,迁移本身不会写入首次启用时间。

在项目根目录审阅迁移(不连接数据库):

node --import tsx scripts/migrate-asset-history.ts

取得目标数据库迁移授权后,使用受控环境变量配置正确数据库,再显式执行:

node --import tsx scripts/migrate-asset-history.ts --apply

如部分建表失败,修正原因后可重复执行;脚本使用 CREATE TABLE IF NOT EXISTS。新建空表不会伪造以前历史。

每日采集升级(2026-10-30):先停止旧版采集服务,执行基础建表迁移(已有表不变),再审阅并执行一次增量迁移:

node --import tsx scripts/migrate-asset-history.ts --daily
node --import tsx scripts/migrate-asset-history.ts --daily --apply

增量迁移把 period_month CHAR(7) 改为 period_key CHAR(10),原唯一索引继续约束期间键和版本;新增可空 daily_enabled_at。这是一次性 ALTER,不能重复执行。应在目标库完成迁移后启动新版服务并设置采集开关;旧版程序不能直接使用新字段名。迁移不启动采集,也不修改旧归档的计划/实际时间。当前页面仍提供月度和年度浏览,普通日期快照保存在数据库中。

授权启用后,将服务进程环境变量设为 ASSET_HISTORY_CAPTURE_ENABLED=1 并按现有流程重启服务。调度器首次成功连接时记录 daily_enabled_at,启用前的时点不会追补。如果在当天 23:55 之后才首次启用,即使仍处于该分钟,也从次日开始采集。

按每日采集需求确认日期 2026-10-30,如果在当天 23:55 前成功启用,首个计划时点为北京时间 2026-10-30 23:55;实际计划以每日采集首次启用时间和服务时间计算,不硬编码。

保持服务在计划时点在线。停止期间错过的 eligible 日期会在重启时记为 MISSED;已 FAILED 的记录保留诊断标记。记录到启用前没有归档,不能用重启时的实时数回填。日志前缀为 [asset-history],包含日期及 captured/already-complete/locked/failed/missed 结果,不记录客户明细或连接凭据。

建议仅让受控服务账号写快照表;源表仍只需读权限。多实例可启用同一数据库下的同一采集配置,命名锁和数据库唯一约束共同保证幂等。启用开关为进程级配置,daily_enabled_at 是历史启用记录,不能仅根据该记录判断某个进程现在是否运行调度器。

管理员可用以下只读 SQL 检查,不通过普通用户接口暴露全量计数:

SELECT enabled_at FROM bi_asset_history_settings WHERE id=1;
SELECT period_key, scheduled_for, captured_at, completed_at,
       definition_version, source, status, failure_code,
       total_count, operating_count, inventory_count
FROM bi_asset_history_batches ORDER BY period_key DESC LIMIT 31;
SELECT b.period_key, b.total_count, COUNT(v.vehicle_id) AS detail_count
FROM bi_asset_history_batches b
LEFT JOIN bi_asset_history_vehicles v ON v.batch_id=b.id
WHERE b.status='COMPLETE'
GROUP BY b.id, b.period_key, b.total_count;

回退

  1. 设置 ASSET_HISTORY_CAPTURE_ENABLED=0 并重启服务,停止后续采集。
  2. 回退应用代码;已有归档表可原样保留,不影响旧代码。
  3. 若确需撤销表,先备份并取得数据库删除授权;依次删除明细表、批次表、设置表。默认不删除任何归档数据。

读接口不自动建表,因此回退后也不会被请求重新创建。再次上线可继续读取既有归档;首次启用时间不自动重置。

后续逐车导入契约(本次不提供导入写接口)

提供仅表头模板 docs/asset-history-import-template.csv。使用 UTF-8 CSV,每行一辆车,同一文件只允许一个年月、一个口径版本和一个统计时点。

必填字段:period_month、scheduled_for、captured_at、definition_version、source_reference、vehicle_id、operation_status。空缺车牌/VIN使用空值,不造车辆 ID。ID 均按文本读取。旧版月度导入契约的 scheduled_for 是指定年月最后一天北京时间 17:00(保留旧格式,不能作为每日自动采集规则),captured_at 必须来自可信历史来源,不能把导入执行时间当作历史读取时间。

其余字段见模板。部门、负责人、主体缺失须显式留空,不能用今日台账补齐;部门/个人权限因此可能看不到未归属的运营车辆,这是数据缺失而非自动扩大授权的理由。展示型号、品牌、原始车辆状态按历史来源保存;历史状态映射复用现有分类函数,不接受退出运营车辆或未知运营状态作为有效存量。归档分类由导入校验程序计算,不直接信任文件中的分类列。

状态映射:1/租赁、2/自营属于运营;3/可运营/在库、4/待运营属于库存;5/退出运营及未知值拒绝计入存量。vehicle_status 为历史原值,待交车或异动不改变上述归档分类。展示用 status 应按现有 mapStatus 生成。

后续导入器必须先提供 dry-run:校验日期和时区、来源、未来时间、车辆唯一性、状态、权限所需字段、计数对平及同年月冲突,再原子发布来源为 IMPORT 的批次和逐车数据。import_provenance 至少保存文件校验和、来源引用、导入人、导入时间、校验报告与修订原因;不要在日志保存原始敏感明细。

现有 COMPLETE 批次不自动覆盖;相同口径同月冲突应拒绝。修订需新增可追溯版本并明确有效版本切换及跨版本读取策略,不直接改已归档车辆。版本切换功能属于后续导入工作,本次只提供存储空间及契约。

仅三个汇总数字的文件不符合逐车导入规范,不能提供个人/部门历史明细。若将来支持汇总导入,需另设权限和“无明细”模型。

交还业务回溯的本地回填

2026-10-19 按用户确认的回溯核对表,将 2022-06 至 2026-09 共52个月、22,319条车辆月度记录 发布到本地私有 JSON 文件。原始核查资料截至北京时间 2026-10-19 14:38:33。2026-10 未结期不回填,继续走当前实时查询。未向真实数据库建表、插入或更新。

这批数据保留 HANDOVER_REVIEW_V1 方法、核对表文件名与 SHA-256、原始核查时间、逐车交还单号、业务时间、分类依据及未判状态数量。车辆 ID 使用字符串,候选车辆的车牌/VIN来自交还单原值;未用当前台账补公司、部门、负责人、客户或未知运营状态。

它是独立的 RECONSTRUCTED(历史回溯·估算) 来源,不发布为 AUTO / IMPORT 正式快照,也不填写月末实际采集时间。counts 为 null;运营/库存推算数、未分类数、当前已退出子集和来源信息在 point.reconstruction 中返回。总数折线保持空白,页面明确显示“总数待核实”。正式归档始终优先,不覆盖已有归档或归档失败诊断。

权限单独收紧:只有当前全量权限且未选择公司时可读取这批估算。它缺少可靠历史主体和个人/部门归属,不能按今天的车辆权限、公司或库存状态补足历史授权。部门、个人及公司筛选继续显示原有正式快照或空白;直接访问回溯明细、篡改批次或公司参数也不能放宽权限。正式归档和实时数据原有权限规则不变。

年度仅复用各年12月的回溯记录。相邻两个回溯点可比较推算运营/库存,不计算总数差值;回溯点与实时或正式快照不作差,防止把不同覆盖范围的差异显示为业务增长。回溯明细支持分类、搜索、分页,汇总与逐车记录在加载前对平;未知归属明确标为“历史未提供”。

显式本地发布命令(没有数据库依赖,默认只校验):

node --import tsx scripts/backfill-asset-reconstruction.ts --input /private/reviewed-reconstruction.json
node --import tsx scripts/backfill-asset-reconstruction.ts --input /private/reviewed-reconstruction.json --output /private/asset-history/reconstruction-v1.json --apply

数据文件格式为 ReconstructionData(src/server/routes/asset-history/reconstruction.ts)。必须提供来源及文件校验和,每月车辆唯一、逐车分类与计数对齐、参考月份早于原始核查时间。合法的0与未知总数区分。发布使用私有文件权限和原子创建;相同内容幂等,不同内容拒绝覆盖。文件不能放入公开静态目录或源码目录。

启动时显式设置 ASSET_HISTORY_RECONSTRUCTION_FILE 为已发布文件的绝对路径;没有该变量时不读取估算数据。不会启用自动采集、初始化快照表或运行其他后台任务。读取结果在进程内缓存;修订时保留原文件和核对表,发布到新文件后更新变量并重启。回退时移除该变量并重启,正式归档及实时数据不受影响。本地验收启动脚本单独注入该变量,回退需同步移除该脚本的配置。

本地验收地址仍为 http://127.0.0.1:3000/asset#assets。当前仅本地回填,生产未部署、采集未启用;正式存储迁移及生产回填仍需明确指定目标和生产授权。

验证入口

node --import tsx --test src/server/routes/asset-history/*.test.ts src/modules/assets/history/*.test.ts
npm run lint
npm test
npm run build

单元、接口和事务故障测试只用模拟仓储与事务数据库替身,不访问真实数据库。开发启动若加载现有 src/server/index.ts,会执行其他模块的后台初始化;验收应使用隔离服务、不导入 bootstrap,并阻断所有数据库连接,避免顺带运行现有后台任务。

本次交付验证(2026-10-19)

  • 新增 32 项针对性测试,全量 452 项通过;npm run lint、npm test、npm run build、git diff --check 均通过。
  • 构建仍报告部分现有重依赖分包超过 500 kB 的体积提示;不影响构建,本次不扩展为全站拆包重构。
  • 使用已有 Playwright Chromium(当前会话没有 Browser 插件),在 http://127.0.0.1:3146/asset#assets 隔离服务验收;测试服务未导入 bootstrap,数据库入口被阻断。该地址为临时验收环境,非生产部署。
  • 1440×1200、390×844 验证交互,补充 390×1400 截图。13 组浏览器检查通过:页面身份/非空/无框架错误、月度归档及明细、分页搜索、年度、缺失/未来/零值、公司筛选、请求竞态、实时刷新不重读历史、部门及个人权限、存储未部署、503 重试、原有资产分析、手机触摸及无横向溢出。
  • 浏览器无未解释的控制台错误或警告;模拟 503 的错误属于已验证的故障注入。最终验收记录中数据库访问尝试为 0。
  • 参考图的卡片、堆叠柱/折线和选中期间指标保留;历史曲线按真实归档缺失留空,不复制示例数值。顶部现有资产样式保留;手机明细使用卡片而非横向表格。
  • 迁移审阅命令已运行,未执行 --apply。事务、锁、故障和提交确认丢失在隔离数据库替身中验证,未向真实 MySQL 实例执行 DDL 或采集。生产迁移和启用需按上述授权步骤执行。
  • 当前交付状态:本地实现完成,未发布,自动采集默认关闭;正式逐车快照导入仅提供契约和模板。用户确认的交还业务回溯另用上述显式本地文件发布入口,不混入正式快照。
  • 回溯回填补充验证:新增8项测试,全量460项通过;类型检查与构建通过。52个月运营/库存与核对表逐月对齐,API明细分类数及分页核对通过。1440×1200、390×844完成月度、年度、明细、搜索分页、触摸、焦点关闭、公司范围及缺失月份验收;无未解释控制台错误、无横向溢出。本地只读保护记录的写入尝试为0,采集及后台任务均关闭。