fix: 修复匹配详情提示及公积金可选路径问题

修复匹配详情中“用户自填未经核验”等来源说明,清理未参与项的橙色注意提示,修正贷款统计口径文案;补充公积金完整可选路径提取逻辑,确保明确无额外时长要求的公积金分支可正确识别并通过。
This commit is contained in:
hujun
2026-10-19 16:11:59 +08:00
parent c95482a0f3
commit e5406c23a6
75 changed files with 31813 additions and 107 deletions
+11
View File
@@ -1,5 +1,16 @@
# AI 融资移动端
2026-10-19 后端 `20261019-assessment-fixes-r2` 已发布,11:47:56(Asia/Shanghai)恢复入口与唯一新worker。固定r2的17文件整体应用,142路径、维护期30表、生产25产品新策略规划、历史读取及29服务器/31公网HTTPS检查通过;无本轮真实模型/业务迁移。用户确认小程序1.1.0已于今天约12:00发行(Asia/Shanghai);发行后31项公网HTTPS、142代码路径及历史读取复核通过,实际上传包与源码摘要绑定未独立核实,真机仍跳过。见[发布记录](docs/资质评估累计修复后端发布-20261019.md),下方未发布描述保留为历史候选状态。
2026-10-19 当前:已固定本地候选 `rc-20261019-assessment-fixes-r2`,仍未发布、跳过真机。r2包含主报告四句中性文案,17后端/206前端/12旧文件共3包核验通过;10个前端隔离回归通过,并绑定2026-10-16最新29页编译证据,本轮未新编译。r1原包与清单保持;生产两处旧摘要/三处新路径仍待现场核对,最新公积金策略无新增真实模型复测。见[r2候选及验收矩阵](docs/资质评估修复发布候选-20261019-r2.md)和[最新交接](docs/新会话交接.md)。下方保留历史状态。
2026-10-16 当前:累计资质评估修复已固定本地候选 `rc-20261016-assessment-fixes-r1`,尚未发布;最近已记录生产版本为 `20261016-report-diagnostics-r1`。17后端文件、206完整前端源码及12个有生产摘要依据的旧文件已封包核验;两处替换文件的生产旧摘要及三处新增路径仍需发布前现场核对。最新公积金路径未做新真实模型实测,真机继续跳过;小程序仍由用户自行编译发行。见[候选与发布顺序](docs/资质评估修复发布候选-20261016.md)及[最新交接](docs/新会话交接.md)。下方保留历史发布记录。
2026-10-15 最新:后端已发布 `20261015-profile-advice-r1`,17:54:14恢复入口/worker(Asia/Shanghai)。6个库整体替换,新资质评估默认自填真实、未采集细项不补填/不阻断,unknown不加分;用户填写内容和历史结果保留。135路径、维护期30表一致性、25产品/7批规划、历史读取及29服务器/31公网HTTPS通过,无本轮模型调用。用户已发行小程序,此次无需重新发行;新口径请新建评估查看,旧报告不重算。见[发布记录](docs/后端资质参考建议发布-20261015.md)及[交接](docs/新会话交接.md),下方保留历史记录。
2026-10-15 最新:后端已发布 `20261015-assessment-region-batch-r1`,15:38:53恢复入口/worker(Asia/Shanghai)。评估/34省事实/分批检查点等9个库配套上线,135路径、维护期30表一致性、当前25产品/7批规划、历史报告及29服务器/31公网HTTPS通过。小程序由用户从当前源码重新编译发行;真机与新省份真实模型语义待验收,本次无收费模型调用。见[发布记录](docs/后端评估配套发布-20261015.md)及[最新交接](docs/新会话交接.md),下方保留历史记录。
新会话接续入口:[最新交接](docs/新会话交接.md)。2026-10-14已将进度同步为2.36/1.46、技术文档v2.04/1.17:后端和管理端已上线,小程序配套已完成、待用户发行反馈;真机/真实模型质量及正式验收仍待。下方保留各轮发布与开发记录。
2026-10-14 最新:按用户本轮授权,后端及管理端已发布20261014-product-ai-r1,14:19:02(Asia/Shanghai)恢复入口/worker。自定义AI条件、第一列手输及推荐/不推荐 `{X}` 模板已配套上线;133项文件摘要、维护期30表一致性、现有6上架产品编译、服务器/公网HTTPS及恢复预检通过,29份私密备份下载核验。产品仍原16字段,无业务迁移;本轮未调用真实模型。小程序由用户从当前源码发行,前轮29页编译及111项展示检查保持,真机与真实模型质量尚未验收。见[交接](docs/新会话交接.md)及[后端发布记录](D:/AAdesk/202609/airz/database/product-ai-release-20261014.md);下方未部署为历史状态。
@@ -0,0 +1,44 @@
# AI 输出校验诊断补充(2026-10-16,已发布)
后续发布状态:用户明确要求发布后端,四文件已于 2026-10-16 发布为 `20261016-report-diagnostics-r1`,11:12:47(Asia/Shanghai)恢复入口及唯一新 worker。137 路径、30 表维护期业务/结构保持、29 服务器/31 公网 HTTPS、历史报告读取及同服务身份日志探针通过;未重试正式评估或新增模型调用。实际 worker 拒绝事件尚未触发,原两次具体拒绝原因仍未知。详见[后端校验诊断发布记录](后端校验诊断发布-20261016.md);以下“本轮未发布/待授权”为本地实施阶段历史状态。
用户反馈同一评估两次提示“分析结果未通过格式校验”。生产只读检查确认评估 471、版本 718 的首次执行和重试均在第 4 批停止,前三批检查点有效;错误码为 AI_INVALID_REPORT,属于模型输出解析拒绝。原两次失败响应未保存,无法还原具体拒绝分支。
前轮用户明确允许最多一次真实诊断。2026-10-16 10:22:14—10:22:35(Asia/Shanghai)使用原冻结资料及精确第 4 批请求,整批、10 条条件、16 个模板和正文均通过。输入 9780/输出 2985 token,耗时 20980 毫秒;没有继续后续批、保存正式报告或修改任务。只说明该请求可以产出有效结果,不能认定原问题已修复。原始响应仅内存解析;防重复标记必须保留,不得重跑入口。
本轮“继续下一步”承接先本地补充脱敏诊断并离线验证的建议。没有生产访问、发布、新增真实模型调用、提交或推送。
## 实现范围
后端 `D:/AAdesk/202609/airz` 三个既有运行文件修改、一个新增:
| 文件 | 作用 |
| --- | --- |
| DeepSeekException.php | 新增默认 null 的内部 diagnostic 属性;构造、错误码、消息和 usage 保持。 |
| ProductAiBatches.php | 在原解析位置捕获 AI_INVALID_REPORT,尽力附加脱敏记录,再抛原异常;区分 response、checkpoint、aggregate。 |
| AnalysisBatchTasks.php | 在原调用失败处理后尽力写一条独立 PHP error_log 事件;仍以原错误码、空 usage 包装重抛,防止末批用量重复计入首条日志。 |
| AiReportDiagnostics.php | 构造和写出时均做白名单过滤;不刷新框架日志缓冲。 |
事件前缀为 `AIRZ_AI_REPORT_REJECTION `,包含 assessment/revision/task 标识、仅 response 对应的调用日志 ID,冻结计划批号/批次摘要,原请求/响应 SHA256、响应字节数、用量白名单,以及固定规则分类、白名单校验文件/实际拒绝行/源码 SHA256。`invalid()` 集中抛错会追溯到实际调用它的校验行;未匹配位置使用固定 UNMAPPED_REJECTION,不输出任意文件或异常信息。规则分类用于缩小范围,实际分支应结合文件、行号和源码摘要判断。
不记录模型正文、用户填写值、未知 JSON 键、异常消息、堆栈参数或完整路径。checkpoint/aggregate 没有本次请求与用量,不归给上一批调用。日志格式错误或写入故障不得改变原异常、状态、调用次数和检查点。不重新生成提示、不再次调用模型或重跑业务解析。诊断不加入报告、检查点、业务表和接口响应。
本轮没有修改提示词、准入/三态/数值/证据校验、评分、自动重试、数据结构或小程序。未知仍 unknown;全部填写项和已有未提交改动保留。未对原失败根因作猜测性修复。
## 验证
新增 `tests/report_rejection_diagnostics.php`:43 项纯内存检查。首次红灯验证缺少诊断属性,实施后通过。覆盖中途失败、保留已通过前缀、重试首调用的真实批号、零调用检查点拒绝、aggregate 元数据、错误位置、请求/响应摘要、未知键/枚举/异常消息/嵌套对象脱敏、日志故障隔离和供应商错误保持。
全新本机隔离 MySQL/Redis、真实模型构造阻断的实际任务入口:原 25 项分批任务检查和新增 10 项诊断集成检查通过。第三批格式失败只产生一条事件,原用量、检查点及失败状态保持。29 张合成表清空、6 个原触发器保留、Redis DB15 无键;仅本轮所属服务退出,51578/51579 端口释放,数据目录保留备查。未加载默认生产 `.env`。
相关回归按套件单列:模板 89、批量规划 36、自填参考建议 59、条件 68、grounding 81、年龄 55、地区事实 68;包含套件与重复运行不累计成总数。4 个运行文件及新测试 PHP 语法检查通过。256 个当前后端运行 PHP 仅本轮 3 修改/1 新增,77 个小程序 UTS/UVue 文件摘要保持。独立代码审查未发现明确新增缺陷。
证据与本地候选:`D:/AAdesk/202609/airz-backups/report-rejection-diagnostics-20261016`,含 before 快照、红/绿检查、实际任务入口日志、清理回执、源码摘要、implementation.diff、verification.json 和 candidate。前轮收费诊断证据另在 `profile-advice-format-diagnosis-20261016`;服务器 `/root/airz-format-diagnosis-20261016/model-call.started` 必须保留。
## 后续边界
本地实施结束时线上仍为 `20261015-profile-advice-r1`;随后已获授权并完成上述发布。现场同用户 PHP 的默认 error_log 输出至 stderr,worker unit 配置路由到 journal;纯合成 sink 脱敏与默认输出标记通过。探针独立于正式 worker 进程及 sandbox,未验证真实业务拒绝事件在 journal 中的完整闭环,不将其解释为原问题修复。
候选四文件需要配套部署,无 DDL、业务迁移或模型配置变更。实际发布需用户明确授权,并重新对照现场旧文件、备份和排空任务;本地 baseline 不能替代现场备份。上线本补丁只增强失败可追踪性,不保证原评估立即成功。
正式恢复另需模型调用授权。应重试原评估、同模型复用前三批冻结检查点,从第 4 批继续;后续最多还有 4 批,不新建重跑 7 批。前轮唯一诊断通过不等于正式报告已生成,也不能据其成功响应反推原两条失败原因。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
# 省份单选与地区事实实施记录
目标:按照用户最终确认,仅选择全国省级地区,点选即完成;省份不能冒充具体城市。
授权:本地修改及验证;不部署、不新增付费模型调用、不提交或推送。
- [x] 定向回归:原南京显式城市代码被拒/旧提交仅映射五城已复现;最终改用 `tests/product_region_facts.php` 验证省份配对、历史兼容、冻结事实、模型请求、企业资料和报告标签。
- [x] 同源数据:`docs/data/city-options.json` 保存34省级代码名称,历史城市数据仅用于回填所属省份;`scripts/generate-city-options.py` 生成前端地区服务及后端PROVINCES,保留原五城CITIES。
- [x] 后端:可选provinceCode只随版本保存,写user_info前移除;新regionFactsPolicy冻结城市和省份名称;旧快照不补新事实。
- [x] 前端:`AssessmentProvinceSelect.uvue` 单层34省列表,取消不选默认值;填写/确认/详情/理由展示省份中文,历史城市转换为省份。
- [x] 验证:690项PHP、108项UTS、18项受控H5检查,微信29页编译;独立审查发现详情标签赋值位置问题后已修正并增加新旧详情回归。
- [x] 交接:证据存 `D:/AAdesk/202609/airz-backups/city-options-20261015`,保留既有改动及原始历史评估记录;无正式库/真实模型/真机/部署验收。
@@ -0,0 +1,50 @@
# 自填资质参考建议实施计划
> 执行方式:当前会话逐项实施并验证;用户已授权本地后端配套修改,不再重复询问,不提交、不推送、不发布、不调用真实模型。
**目标:** 保留全部小程序填写项和保存数据,默认自填资料真实,未采集或精度不足的细项不补填、不阻断参考建议。
**架构:** 新评估目录冻结 `profileAdvicePolicy=self-reported-profile-advice-v1`;历史目录无标记时继续旧语义。AI仍返回pass/fail/unknown,服务端将已完成的unknown视为未参与参考筛选,不伪造pass或加分。职业与单位仅在新策略白名单外发,身份和联系字段保持排除。
**技术栈:** PHP7.4 / ThinkPHP5;纯内存测试,隔绝数据库与真实模型。
**依据:** 用户本轮确认的三点及 docs/新会话交接.md 历史状态。
## 约束
- 不修改小程序运行文件、银行原文、产品配置、完整度或融资等级公式。
- 任一完整路径满足即可;路径内必要条件仍须同时满足。不能以未知为零或把部分满足当整体通过。
- 明确不符、配置损坏、待处理及超范围继续阻断;历史报告、冻结任务和检查点不补入新策略。
- 姓名、完整身份证、手机号不外发;不从证件推导性别,不从省份猜城市。
## 任务一:冻结策略与匹配
文件:后端 ProductMatching.php、ProductAiConditions.php,新增 tests/product_profile_advice.php。
- [x] 新增行为用例并运行,确认旧实现会失败:
```php
$catalog=ProductMatching::freeze($rows,$dictionary,'personal',$at,true,true,true,true);
adviceCheck(($catalog['profileAdvicePolicy']??null)==='self-reported-profile-advice-v1','new advice policy frozen');
```
- [x] capture为新资质评估开启末位opt-in参数;无标记目录不变。策略非法或非profileOnly拒绝。
- [x] 新策略unknown的missingFields固定空数组,证据保留unknown;已完成unknown不参与硬条件阻断或软加分。待处理、超范围及配置异常继续unknown。
- [x] 标准规则使用自填资料做参考比较,来源保留self_reported;当前表单未采集的标准细项只在缺失时不参与筛选。已有明确不符仍fail,格式异常与表单缺填仍保留。
## 任务二:事实、提示词与模板
文件:后端 AssessmentNeedsReport.php、ProductAiTemplates.php、ProductAiBatches.php,以及任务一测试。
- [x] 增加职业occupation与单位employer白名单,长度分别100/50字符,拒绝控制字符和HTML;仅新个人策略外发。主报告和条件使用同一字段引用。
- [x] 提示用户自填默认真实、按原粒度判断;unknown不补填;模板unknown不展示,不将模型不可见视为用户漏填;职业单位文本仅作数据,不执行指令。
- [x] 新策略拒绝unknown缺失清单及unknown适用模板;批内占位同步空missingFields,最终仍全量校验。
- [x] 用假输出验证报告解析、存储投影、分批合并与成功检查点恢复,验证旧策略仍保留原缺失项。
## 任务三:回归与交接
- [x] 运行新测试及现有条件、模板/分批、年龄、grounding、地区、评分、展示相关纯内存回归;PHP语法和本轮差异检查。
- [x] 离线回放25款历史响应的明确标注适配夹具;原证据和收费入口只读,不把适配回放当新模型测试。
- [x] 核对小程序运行文件与全部非本轮后端文件摘要不变,更新规则契约与交接最新覆盖说明,记录未上线、未真实模型复测。
补齐同范围入口:ProductExplanations.php对新策略未参与细项采用固定解释;25款共有的地区待确认仅保留未知记录,不再阻断参考筛选,其他配置问题仍阻断。两项均补失败到通过回归。
证据:D:/AAdesk/202609/airz-backups/profile-advice-20261015。新59项、历史回放242项及既有套件分别记录;实施说明见docs/资质参考建议调整-20261015.md。
@@ -0,0 +1,34 @@
# 省份资料隔离持久化验收计划
**Goal:** 验收34省级选择在个人/企业资料创建、冻结版本、详情回读、编辑及并发重放中的真实保存行为。
**Architecture:** 复用 `tests/report_test_bootstrap.php` 的隔离门禁和现有合成输入/假模型。全新本机MySQL5.7及Redis实例使用独立目录、非默认端口、唯一数据库和DB15;框架初始化前拦截真实模型客户端。业务源码保持,发现相关缺陷才最小修正。
**Tech Stack:** PHP7.4、ThinkPHP、MySQL5.7、Redis、现有Python运行时。
**Spec:** `docs/新会话交接.md` 2026-10-15接续摘要及 `2026-10-15-city-options.md`。
## 约束
- 本轮用户授权继续本地隔离验收;不连接正式库、不读取正式.env、不调用付费模型、不部署或发行、不提交或推送。
- 保留前后端全部已有未提交改动;省份不能证明具体城市、户籍或网点覆盖。
- 只清理本次合成记录及所属队列;服务关闭前核对PID、可执行文件、命令行和监听归属,保留数据目录与证据。
## 执行步骤
- [x] 新建仓库外证据目录,记录当前源码摘要及工作树;初始化独立MySQL/Redis、导入现有schema,写入显式隔离bootstrap。
- [x] 新增后端 `tests/province_persistence.php`,复用 `matchTestInput($type,$key)`、`ReadinessFakeClient` 和 `AssessmentRecords` 服务。
- [x] 验收34省份个人/企业保存与回读;检查快照有province_code且city_code为空,user_info无province_code列/写入,详情保留对应名称代码。
- [x] 验收广东→江苏编辑、原键重放不回退最新资料、旧版本不改写、非法配对和跨账户拒绝无写入、历史深圳/未知城市兼容。
- [x] 用两个PHP进程和stdin闸门竞争:同键新建/编辑仅产生一次版本,同键不同省份冲突,不同键同基版本编辑仅一个成功。
- [x] 运行新测试、原版本/HTTP回归及地区纯内存检查;核对退出码、实际检查数、假调用数和数据清理。
- [x] 记录验证结果,正常关闭仅本轮创建的MySQL/Redis并核端口释放;更新交接文档。发布及新模型语义仍单独待授权。
运行入口:`php tests/province_persistence.php --bootstrap=<本轮隔离bootstrap绝对路径> --worker-stopped --verbose`。
必须无bootstrap时拒绝启动;成功运行后本轮合成用户、资料、评估、日志、产品、消息及队列应清空。不得复用任何历史真实评估入口。
## 结果
省份持久化436项、原版本58项、默认HTTP514项/158请求、省份HTTP533项/162请求、地区事实68项均通过;另两文件语法和无bootstrap拒绝通过,各套件单列不累计重叠。未修改业务运行代码;255个后端运行PHP摘要不变。29表空、6触发器保留、DB15键0,本轮服务正常关闭且端口释放。首次版本回归的隔离密钥格式问题修正后通过,失败及重复运行不累计。
证据:`D:/AAdesk/202609/airz-backups/province-persistence-20261015/verification.json`。持久化专项使用空合成目录,HTTP和版本回归使用既有演示产品;未验证最新正式目录、生产worker、微信真机或新真实模型语义。
@@ -0,0 +1,16 @@
# 评估配套发布准备计划
**目标:** 固定累计未发布改动,提供可核对的后端候选包、完整前端源码、旧后端恢复材料、顺序及回滚边界。
**范围:** 只做本机准备,不访问服务器、正式数据库或模型,不部署/发行、不提交/推送;保留当前工作树。既有隔离验收关联原始证据,不重跑相同持久化测试。
- [x] 对照20261014-product-ai-r1的133文件发布回执逐项核摘要,区分真实改动、换行差异及新增依赖。
- [x] 固定9个后端运行文件及13个前端累计改动;完整前端源码按显式运行目录归档,排除私密配置、文档、测试、缓存和历史编译产物。
- [x] 从上次发布runtime.zip及摘要完全匹配的Git对象提取7个旧后端文件,记录来源;两个新增文件仅在本次部署前确认不存在时才具备删除恢复条件。
- [x] 记录旧/新客户端与后端兼容矩阵、无需DDL的边界、先后端再小程序的顺序、任务排空/worker加载与无付费冒烟步骤。
- [x] 校验压缩包路径/成员/逐文件摘要、PHP语法、地区同源字典及候选依赖;记录当前验证与历史业务证据的区别。
- [x] 写入发布操作单、固定清单和准备回执,更新交接;实际发布授权及微信发行记录保持待提供。
候选标识:`rc-20261015-assessment-region-batch-r1`。发布目标以最近历史回执记录的https://airz.hqling.cloud和/www/wwwroot/xctai.cn为待现场核对目标,不当作本轮在线实测。小程序当前线上版本/上传包摘要未知,不使用Git HEAD代替实际发行版本。
结果:9后端候选、206前端完整源码(13累计变化)、7精确旧后端恢复文件;3ZIP/222成员与16PHP语法、220处前端@/导入、34省字典一致性核验通过。255后端运行PHP与前轮持久化证据一致,8份历史证据不变。操作单见 `docs/评估配套发布准备-20261015.md`;清单及核验见 `docs/发布候选/assessment-20261015-r1/`。归档在 `D:/AAdesk/202609/airz-backups/release-preparation-20261015`,仅本地准备,没有服务器/数据库/模型/worker/新编译/发行/提交操作。
+83
View File
@@ -1,5 +1,88 @@
# 产品匹配规则开发契约
## 2026-10-15 最新增量:自填资质参考建议(本地未发布)
用户已授权必要的后端配套调整,覆盖下方“仅提示词”的旧边界:默认已填内容真实;未采集、模型不可见或精度不足的细项不补填、不阻断参考建议。职业类型和单位名称可外发,姓名、完整身份证、手机号仍排除。现有小程序填写、保存与回填不删减,线上和付费/真机复测继续暂缓。
新资质capture冻结profileAdvicePolicy=self-reported-profile-advice-v1,纳入目录/执行摘要及成功检查点绑定;无标记的历史目录、旧任务与报告保留原三态及missingFields非空语义。需求模式不启用。离线freeze新增末位显式opt-in参数,默认保持历史重建行为。
新策略条件仍为pass/fail/unknown,但所有missingFields必须为空。已完成unknown证据标为AI_CONDITION_NOT_ASSESSED,保留unknown,不阻断参考候选、不加分、不显示旧补填模板;待处理/超范围仍阻断。完整与或路径、引用、版本/摘要、数值及充分分支检查保持,明确不符仍fail。当前表单真实缺填/无效项仍由既有完整度体现;主报告improvements引用仅可对应真实缺填、无效或一致性问题。
标准比较在验证原规则后以self_reported副本执行,不伪造verified,原银行配置保留。未采集的标准细项缺失时标PROFILE_DETAIL_NOT_ASSESSED;当前字段缺填或异常不静默跳过。编译器固定“适用地区尚未核实。”且regionMode=unverified、regions=null时同样不阻断参考筛选,地区仍未核实,不标全国;其他配置提示和损坏配置仍拒绝或阻断。
新个人事实增加occupation(profession,100字符)与employer(50字符)文本白名单,主报告和条件共用引用,空白/超长/控制字符/HTML不外发。仅作不可信用户数据,禁止执行命令或据单位名猜未明示的身份资格;企业模式保持原外发边界。未采集性别不从证件推导,省份不猜城市,时长、金额、机构数等沿原粒度解释。
新策略unknown理由模板必须applicable=false、values=[];追加解释对未参与细项使用服务端固定说明,避免再次索要。报告、内部匹配项保存策略标记,原三态、存储与只读投影兼容,不重算旧报告。详细实现与验证见[资质参考建议调整](资质参考建议调整-20261015.md)。本轮6个后端运行文件、1份新增内存测试,未改产品原文/配置或小程序运行文件,未访问数据库/生产或调用真实模型。
## 2026-10-15 增量:仅加强资质评估提示词(本地未发布)
用户选择“只调整AI提示词”,小程序现有填写项、用户已填内容与回填保持。运行改动仅限ProductAiConditions的提示文字及AssessmentNeedsReport的system提示;不修改产品原文、事实白名单、输出结构、服务端校验、评分、分批或用量策略。
profileOnly=true的请求提示模型继续使用已有资质,不将本次申请金额、本次融资期限/贷款年限、本次资金用途、本笔新增贷款月供列为当前表单补填项或完整度不足依据。原产品约束仍须保留,不把未采集信息当零;复合条件按原与/或关系判断,明确不符仍fail,完整约束未确认仍unknown。unknown的missingFields沿用原非空结构,提示可写相关资料名称或核实事项,如“申请阶段负债核实依据”,不罗列未采集的具体申请字段;其他适用且确实缺失的资质仍保留。当前贷款月还款、当前信用卡使用额、有房不能分别代替完整银行月负债、近六个月均值、房产价值。
主报告待完善项只依原完整度缺失、无效项及一致性问题生成;固定理由正文若要求补填上述具体申请字段或断言未确认约束已满足,提示applicable=false并清空values,原绑定三态/引用规则保持。适用范围由已有冻结profileOnly标记控制,无该标记的需求模式不追加本段提示。已保存报告不重算;部署后重新构造的资质请求/重试使用新提示,成功批次检查点仍按既有规则复用。
这仅是模型提示约束,未增加服务端过滤或新的语义强制校验;现有unknown显示框架与“需补充”前缀保持。15项改动前后兼容核对及相关内存回归通过,但没有新的真实模型调用,不能认定模型已稳定遵守。证据D:/AAdesk/202609/airz-backups/profile-prompt-20261015。本轮未部署或重发小程序。
## 2026-10-15 增量:仅省份选择与地区事实冻结
当前填写页仅选择34省级地区,form.city存省份名称,显式provinceCode为六位省级代码,cityCode=null。新省份名称/代码须严格配对,不能同时提交具体城市;province_code仅冻结在评估版本,排除user_info写入列,不作表结构迁移。未带provinceCode的旧请求保持原五城推码及规范化摘要。历史版本不改写;编辑时已知城市转换为省份,未知旧地区须重选。
新目录regionFactsPolicy=region-label-v1冻结cityNames及provinceNames并计入catalogSnapshotId。cityCode可带冻结displayValue;provinceCode只表达所在省份,代码和输入名称不一致不外发。省份不能猜测城市/户籍/工作城市/申请地区/银行网点,缺具体城市仍unknown;不新增标准城市匹配的省级自动通过规则。旧目录不动态获得新省份事实或新标签,非法策略/名称映射拒绝。本轮零真实模型调用,不以本地输入回归冒充模型语义验收。
数据及生成入口见docs/data/city-options.json、scripts/generate-city-options.py;省级列表与历史城市回填数据分开使用。690项PHP、108项UTS、18项受控H5检查及微信29页编译通过,未部署、未验收持久化并发/真机/生产worker。证据D:/AAdesk/202609/airz-backups/city-options-20261015。
## 2026-10-15 明确充分分支与理由事实规范化(本地待发布)
新冻结目录带aiGroundingPolicy=product-ai-grounding-v1并纳入目录摘要。无标记的历史目录保留原条件解释与填值;未知标记拒绝,不能读历史报告时套用新行为。
仅从明示任选的硬条件eq原文中,保守提取独立、无附加要求的公积金分支为sufficientAlternatives。备注与尾句只允许空或当前已核对的纯路径固定说明;分号划分顶层时不拆内部顿号,圈号需每支完整同级,其他编号和嵌套括号歧义退回模型。带且关系、时长限制、否定或共同要求时不能把其中一个子条件升级为充分条件。当前实现是窄范围路径保护,不是通用自然语言规则编译器。
正向housingFund事实命中充分分支时,该模型条件必须pass且引用fact:housingFund;unknown、fail或缺少相关引用拒绝。服务端不自动改写供应商三态,不推导其他条件或整款产品通过;未命中不等于fail。批次中间证据副本可暂时移除新策略以建立未调用条件的unknown占位,但模型响应、成功检查点、最终报告均用原冻结目录重新验证,模型不能靠占位文字豁免检查。
理由保留模板固定正文,只规范有事实依据的填值:紧邻“月收入(信息)(为/是/冒号)”且无固定金额单位的槽中,裸数字必须等于引用的实际monthlyIncome,才补元;不能借用其他金额或猜万元换算。“你的/您的{X}符合……年龄要求/范围/条件”中的裸年龄或N岁规范为“年龄(N岁)”,数值必须直接等于所引实际age,已规范格式同样校验,不能借条件上下限作为用户年龄。固定单位槽与旧目录不改写,其他数值、单位、引用和三态检查保持。
622项相关内存检查及7批离线回放通过。旧目录判断/填值保持;新目录拦截原ID69矛盾。修正夹具通过合并与恢复并不等于新的真实模型评估,仍须验证新提示的真实返回后安排配套发布;本轮无新增模型调用、数据库访问或部署。
## 2026-10-15 待补资料不设固定条数上限(本地待发布)
按用户最新要求,customConditionJudgments的missingFields不再限制为最多4项。unknown至少提供1个相关资料名称,服务端保留完整列表;pass/fail仍必须为空。每项继续要求非空字符串、最多60字,不含控制字符或HTML;名称不得重复,列表须为连续下标数组。条件/产品绑定、版本/摘要、证据引用及三态校验保持,其他列表的既有限制不变。
模型提示同步取消固定条数。5项、6项及64项回归覆盖条件校验、报告解析和匹配理由展示,全部保留。第3次真实调用未改写的原始首批15条件+16模板已离线解析通过,5/6项待补资料及unknown判断均原样保留;没有新增模型调用或完成后6批评估。
本节覆盖下方“最多4项”“5项以上拒绝”的旧条数约定;尚未部署,不重写已发布报告或历史失败任务。已有模型响应整体大小/token预算仍沿用原有机制。
## 2026-10-15 不展示的绑定理由允许省略重复引用(本地待发布)
当理由已绑定有效条件(ruleId非空且存在最终条件证据)、conditionOutcome与该条件一致、applicable=false且values=[]时,允许evidenceRefs=[],复用关联条件已校验的证据。不补造引用、不替换判断,关闭的理由仍不展示,不影响准入、分数或排名。
实际展示的pass/fail理由,以及未绑定条件的pass/fail理由,仍至少引用一个白名单事实。即使理由不展示,已提供的非法/重复引用仍拒绝,版本/模板摘要、绑定结论和空填值等检查继续执行。unknown资料不足提示的既有口径保持,数值/单位和实际展示理由的填值检查不放宽。模型提示与此例外同步。
第2次真实评估未改写的首批响应15条件+16模板已通过本地离线解析,关联公积金证据与关闭理由保持;此项不代表后6批已完成、整轮真实语义验收通过或线上已生效。
## 2026-10-15 待补资料最多4项、年龄不叠加贷款期限(本地待发布)
自定义条件的 `missingFields` 在unknown时允许1—4个不重复的简短资料名称,pass/fail时仍必须为空;5项及以上继续拒绝。模型提示与服务端校验同步,保留所有合法返回项,不截断列表。
个人新资质评估(profileOnly=true)冻结目录时,对明确以年龄区间开头、含贷款年限/期限组合的手输年龄条件,只冻结该年龄区间为between条件,含上下界;不判断年龄加贷款年限。当前25款涉及优客分期18—65岁、润薪贷23—60岁、民易贷22—60岁。关联AI理由中的“年龄与贷款期限组合/年龄与贷款年限组合”同步改为“年龄要求”,避免宣称已经核对贷款期限。原产品行及完整原文保留;历史已冻结编译条件不在读取或重试时重新规范化,旧需求模式不应用该变换。分性别年龄条件保持原范围,不能合并为统一上下限。
模型提示明确区分或/与:任一完整可选分支满足即pass,其他可选分支未提供不影响已满足分支;分支内所有必需条件仍须满足。全部分支明确不满足才fail,资料不足且仍有可能满足才unknown。仅强化提示不等于真实模型语义验收通过,须另行复测。
## 2026-10-14 补充:三态显示真实匹配度(本地待发布)
符合条件、待核实和条件不符三态,报告/列表/详情/对比均展示冻结结果已有且通过校验的score,包括0%;旧score=null不补算或造分。“待确认”文案统一为“待核实”,硬条件缺失资料仍为“资料不足”。状态与匹配度独立展示,unknown/ineligible不进入候选rank、不因此获得申请准备入口,评分及排序规则保持。该约定覆盖下方旧阶段仅eligible显示百分比的口径。
报告匹配标签“查看更多产品”下方仅保留修改资料入口与“匹配度仅供参考,不代表获批概率”;重复统计、排序说明与次要报告信息移除,完整列表筛选及其他报告栏目保持。分析完成后默认进入本次已发布revision的matches栏目,处理中或失败不能因存在旧报告而跳转。本轮只改前端并完成本地验证,未部署;默认三款all分组及身份证年龄仍需下节后端配套。
## 2026-10-14 默认三款展示与身份证年龄(本地待发布)
两个匹配入口默认读取 `eligibility=all`,每页3款;先符合条件、再待核实、最后条件不符,同组按真实score降序、同分按产品ID升序。状态、分数和候选rank不改写;实际不足3款按已有数量展示,不生成虚构产品。旧eligible/ineligible/unknown分组及接口默认eligible保持兼容,all游标单独绑定分组与冻结结果。
新冻结目录带 `identityFactsPolicy=identity-age-v1`。个人评估从本人冻结输入中的有效身份证提取生日,按目录的上海业务日期计算周岁,校验18位校验码/生日并兼容15位号码;无效或未来生日不生成年龄。模型和公开报告只接收age整数事实,来源仍为self_reported,单位age;身份证号码不外发。自定义年龄条件及理由模板可引用 `fact:age`,报告展示“年龄:N 岁”。标准age_min/age_max列仍未启用,不新增表单字段或数据库列。
旧冻结目录没有该策略时保持原事实,重试/读取不补算年龄;用户需重新评估获取新年龄。此约定覆盖下方旧阶段“不能从身份证推导年龄/默认仅显示候选”的限制。本轮仅本地实现与验证,部署须配套4个后端库和小程序源码;不重算旧报告。
2026-09-30最新展示约定:按用户要求,当前六条产品使用正常业务名称及资料,前台/PDF不展示“演示、非真实”等环境标签;内部source reference、isDemo及containsDemoProducts继续保存,用于追溯既有产品。正式ID22—27已同步文案并增加版本,金融参数/条件不变,历史快照不改写。下文强制显示环境标签的表述仅作阶段记录,本次展示规则优先;部署状态和验证见[开发进度0.36](开发进度.md)。
## 2026-09-29 最新覆盖约定(inputVersion=3)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
# 25款产品加减分建议
2026-10-19。已获用户授权,并于15:14:43写入正式配置、完成独立回读。详见[正式写入回执](产品加减分正式写入回执-20261019.md)。原方案JSON保持创建时草案状态,正式生效以回执为准。共25款、74条:24款各3条,中银E贷2条;共49条加分、25条减分。
本方案是平台参考评分,不是银行官方评分卡。基础分仍为80,命中下表条件才加减分,最后限制在0—100;缺填、未知或无效资料不计分。各产品现有银行准入要求保持独立,满足参考项不表示整条进件路径已满足。分值已按本方案获准采用。
为避免额外模型判断,本版全部使用现有结构化字段和确定性规则,不新增表单字段或AI评分条件。房产不等同按揭,社保/公积金6个月不等同同单位12/24个月,自填收入不等同银行认定收入;原产品的完整要求继续保留。
| ID | 机构/产品 | 加分项 | 减分项 |
| --- | --- | --- | --- |
| 56 | 中国银行/优客分期 | 填报月收入不少于15000元 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 57 | 中国银行/中银E贷 | 公积金缴存满6个月参考 +10 | 已填报月收入为0 -10 |
| 58 | 中国银行/随心智贷 | 公积金缴存满6个月参考 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 59 | 农业银行/集团E贷 | 填报月收入不少于5000元 +5;公积金在缴参考 +10 | 已填报月收入为0 -10 |
| 60 | 农业银行/网捷贷 | 公积金缴存满6个月参考 +10;社保缴存满6个月参考 +5 | 已填报月收入为0 -10 |
| 61 | 农业银行/乐分易 | 公积金缴存满6个月参考 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 62 | 建设银行/快贷 | 现有贷款月还款额为0 +5;信用卡当前已用额度为0 +5 | 已填报月收入为0 -10 |
| 63 | 建设银行/建易贷 | 公积金在缴参考 +5;信用卡当前已用额度为0 +5 | 已填报月收入为0 -10 |
| 64 | 建设银行/分期通 | 持有房产参考 +10;持有车辆参考 +5 | 已填报月收入为0 -10 |
| 65 | 工商银行/融E借 | 公积金在缴参考 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 66 | 招商银行/闪电贷 | 公积金在缴参考 +10;持有房产参考 +5 | 已填报月收入为0 -10 |
| 67 | 中信银行/信秒贷 | 现有贷款月还款额为0 +5;信用卡当前已用额度为0 +5 | 已填报月收入为0 -10 |
| 68 | 中信银行/圆梦金 | 持有房产参考 +5;社保缴存满6个月参考 +5 | 已填报月收入为0 -10 |
| 69 | 交通银行/惠民贷 | 公积金在缴参考 +10;持有房产参考 +5 | 已填报月收入为0 -10 |
| 70 | 浦发银行/浦闪贷 | 现有贷款月还款额为0 +5;信用卡当前已用额度为0 +5 | 已填报月收入为0 -10 |
| 71 | 华夏银行/易达金 | 持有房产参考 +10;持有车辆参考 +5 | 已填报月收入为0 -10 |
| 72 | 邮储银行/邮享贷 | 公积金缴存满6个月参考 +10;现有贷款月还款额为0 +5 | 已填报月收入为0 -10 |
| 73 | 南银法巴消费金融/诚易贷 | 社保缴存满6个月参考 +10;持有房产参考 +5 | 已填报月收入为0 -10 |
| 74 | 光大银行/光速贷 | 公积金缴存满6个月参考 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 75 | 华润银行/润薪贷 | 填报月收入不少于12000元 +5;公积金在缴参考 +5 | 已填报月收入为0 -10 |
| 76 | 华润银行/润秒贷 | 社保缴存满6个月参考 +5;持有车辆参考 +5 | 已填报月收入为0 -10 |
| 77 | 徽商银行/徽享薪易贷 | 公积金在缴参考 +10;社保缴存满6个月参考 +5 | 已填报月收入为0 -10 |
| 78 | 徽商银行/鹏城e贷 | 公积金缴存满6个月参考 +5;现有贷款月还款额为0 +5 | 已填报月收入为0 -10 |
| 79 | 民生银行/民易贷 | 公积金缴存满6个月参考 +5;持有房产参考 +5 | 已填报月收入为0 -10 |
| 80 | 深圳农商行/职享贷 | 社保缴存满6个月参考 +10;现有贷款月还款额为0 +5 | 已填报月收入为0 -10 |
## 分值依据与适用边界
加分项采用+5或+10:较直接的客群参考通常+10,辅助资产、当前负担或仅部分履历通常+5;月收入明确为0时统一-10。它们属于可调整的平台建议权重,没有声称来自银行。原资料中未说明计算口径的负债比、征信评分、半年信用卡平均使用率等不补造数值。
“公积金在缴参考”包括不足6个月及6个月以上的已填缴存档位,仅确认在缴事实;“满6个月”使用现有规则的6个月归一化结果,不声称满足其他更长时限。
“现有贷款月还款额为0”和“信用卡当前已用额度为0”只表示当前负担;零贷款/零信用卡政策合法派生的0可以使用,空值不当作0。无信用卡时加分并不证明有历史征信记录;原非白户要求仍独立。
同一份资料可能让多款产品同分;不为制造差异随机设置分值。缺少所有评分依据时仍可能保留基础80,此时不能理解为80%的条件通过或获批概率。
确认并写入后仅影响新评估冻结的产品配置,历史报告继续保留原评分。正式写入前应重新只读读取当前25款并校验ID、名称、版本及原配置摘要,任一漂移先停止;逐款备份,仅补softConditions并推进产品版本,保留其余字段、状态、准入规则及推荐模板。无需因配置变更重新发行小程序。
## 逐款依据
- 56 优客分期:原资料包含工薪收入与房产进件方向;收入与房产在本方案中仅作为独立参考特征。
- 57 中银E贷:原资料明确有住房公积金半年以上的客群路径。
- 58 随心智贷:原资料包含公积金及按揭房进件方向;本方案的6个月及有房特征不替代原12/24个月与银行按揭要求。
- 59 集团E贷:原资料列月收入5000元(含)以上及公积金在缴客群;单位条件仍独立核对。
- 60 网捷贷:原资料有公积金、社保进件方向;本方案6个月特征不替代原24个月及更新时点条件。
- 61 乐分易:原资料包含公积金、房产路径;本方案不代替12个月、8500元平均基数或产权状态等组合要求。
- 62 快贷:原资料债务比例分母及口径未明确;采用平台通用现有月供、当前卡使用余额因素,不据原经验描述设置30%或50%评分阈值。
- 63 建易贷:原资料有公积金客群和信用卡负担要求;不将有公积金替代近三年缴存次数或单位条件,也不将当前卡使用余额替代半年使用率。
- 64 分期通:原资料将房产、车辆列为辅助资料;本平台不据有车推断行驶证已满一年。
- 65 融E借:原资料将公积金、房贷列为有利方向但未给分值;本方案只按公积金在缴、有房作平台参考,不宣称高基数或工行按揭。
- 66 闪电贷:原资料有公积金进件方向并列按揭房有利因素;本方案只使用在缴及有房事实,不伪造招行往来或征信按揭记录。
- 67 信秒贷:原资料关注月供、信用卡及贷款负担,银行分项与半年均值未采集;使用平台通用当前负担因素,不补造征信或小贷结清结论。
- 68 圆梦金:原资料包含房产、社保路径;本方案只作独立特征参考,不确认单位、学历、代发、基数或城市组合条件。
- 69 惠民贷:原资料有公积金及房产进件方向;不推定交行信用卡、代发、银行按揭或职业分类。
- 70 浦闪贷:原资料未提供可映射到现有表单的定量加分门槛;仅采用平台通用现有月供、当前卡使用余额及明确零收入风险项。
- 71 易达金:原资料列房贷、车等有利因素;本方案使用有房/有车参考,分值为平台建议,未推定银行按揭、学历、单位或保单。
- 72 邮享贷:原资料以公积金为客群方向;6个月参考不代替原2年及个人缴存额500元要求,月供为平台通用现金支出参考。
- 73 诚易贷:原资料明确社保及房产客群;本方案6个月/有房不替代具体户籍、学历、经营执照或12个月分支。
- 74 光速贷:原资料有公积金、房产/按揭进件方向;不把6个月或有房视为同单位12个月或光大往来已满足。
- 75 润薪贷:原资料部分客群收入参考12000元并有社保/公积金方向;不把自填收入视为银行认定收入,也不向其他客群新增门槛。
- 76 润秒贷:原资料将社保及月供车列为进件方向;只参考缴存记录和持有车辆,不推定广东手机号归属、贷款状态或客户身份。
- 77 徽享薪易贷:原资料公积金路径无具体时长门槛,社保半年为建议;公积金在缴和社保半年仅影响平台参考分。
- 78 鹏城e贷:原资料有公积金满12个月及单位/基数分档;本方案6个月仅为履历参考,不代替完整路径,月供为通用现金支出参考。
- 79 民易贷:原资料有公积金及按揭路径;本方案不推定单位白名单、连续12/18个月、本行/他行按揭或职业限制。
- 80 职享贷:原资料以社保客群为主要方向;本方案6个月不替代本单位满一年,现有月供不替代原银行收入负债比。
## 离线验证
验证结果见同目录后续生成的《产品加减分离线验证-20261019.md》。只读取固定目录快照,在显式阻断数据库和真实模型的内存环境校验配置、目录编译、AI批次、计分及未知值边界;这不代表已写入正式产品或运行了新付费评估。
配套数据:[产品加减分建议-20261019.json](产品加减分建议-20261019.json)。独立私密工作目录:`D:/AAdesk/202609/airz-backups/product-soft-scores-20261019-r1`。
@@ -0,0 +1,346 @@
{
"schema": "product-soft-score-production-receipt-v1",
"status": "applied_and_verified",
"authorization": "写入正式配置",
"appliedAtLocal": "2026-10-19T15:14:43+08:00",
"appliedAtUtc": "2026-10-19T07:14:43Z",
"proposalSha256": "745f4d6b658631df2601b1d12abc96f6742a4f14c50222ea291f9e843f78400f",
"proposalFile": "产品加减分建议-20261019.json",
"products": 25,
"rules": 74,
"positiveRules": 49,
"negativeRules": 25,
"baseScore": 80,
"scoreRange": [
0,
100
],
"versions": [
{
"productId": "56",
"productName": "优客分期",
"before": 3,
"after": 4,
"rules": 3
},
{
"productId": "57",
"productName": "中银E贷",
"before": 3,
"after": 4,
"rules": 2
},
{
"productId": "58",
"productName": "随心智贷",
"before": 3,
"after": 4,
"rules": 3
},
{
"productId": "59",
"productName": "集团E贷",
"before": 3,
"after": 4,
"rules": 3
},
{
"productId": "60",
"productName": "网捷贷",
"before": 3,
"after": 4,
"rules": 3
},
{
"productId": "61",
"productName": "乐分易",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "62",
"productName": "快贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "63",
"productName": "建易贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "64",
"productName": "分期通",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "65",
"productName": "融E借",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "66",
"productName": "闪电贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "67",
"productName": "信秒贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "68",
"productName": "圆梦金",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "69",
"productName": "惠民贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "70",
"productName": "浦闪贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "71",
"productName": "易达金",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "72",
"productName": "邮享贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "73",
"productName": "诚易贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "74",
"productName": "光速贷",
"before": 2,
"after": 3,
"rules": 3
},
{
"productId": "75",
"productName": "润薪贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "76",
"productName": "润秒贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "77",
"productName": "徽享薪易贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "78",
"productName": "鹏城e贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "79",
"productName": "民易贷",
"before": 1,
"after": 2,
"rules": 3
},
{
"productId": "80",
"productName": "职享贷",
"before": 1,
"after": 2,
"rules": 3
}
],
"approvedRulesExactMatch": true,
"transactionProductRowsWritten": 25,
"onlyPhysicalColumnsChanged": [
"other",
"update_time"
],
"logicalVersionAdvancedBy": 1,
"currentProductionHardRulesTemplatesMaterialsAndOtherFieldsPreserved": true,
"preflightChecks": 1356,
"transactionChecks": 1389,
"independentReadbackChecks": 1020,
"sourcePathsGuarded": 161,
"releasePathsVerifiedAfter": 142,
"modelConditions": 66,
"modelTemplates": 100,
"modelBatches": 6,
"modelCallsByThisTask": 0,
"historicalReportsPreserved": 5,
"historicalMatchGroupsRead": 20,
"historicalDetailsRead": 5,
"oldReportsRecalculated": false,
"servicesActive": true,
"serviceRestartCountersUnchanged": true,
"workerProcesses": [
3020785
],
"workAtVerification": {
"activeChats": 0,
"activeCalls": 0,
"queue": [
0,
0,
0
]
},
"deploymentPerformed": false,
"serviceRestartsByThisTask": 0,
"miniProgramReleaseRequired": false,
"effectiveBoundary": "Evaluations capturing a new product catalog after this update use the new rules. Existing reports and evaluations with an already frozen catalog retain their original snapshot.",
"syntheticExamples": {
"fund_social_assets_zero_loans": {
"scoreDistribution": {
"90": 15,
"95": 10
},
"scores": {
"56": 90,
"57": 90,
"58": 90,
"59": 95,
"60": 95,
"61": 90,
"62": 90,
"63": 90,
"64": 95,
"65": 90,
"66": 95,
"67": 90,
"68": 90,
"69": 95,
"70": 90,
"71": 95,
"72": 95,
"73": 95,
"74": 90,
"75": 90,
"76": 90,
"77": 95,
"78": 90,
"79": 90,
"80": 95
}
},
"explicit_zero_income_with_existing_payments": {
"scoreDistribution": {
"70": 25
},
"scores": {
"56": 70,
"57": 70,
"58": 70,
"59": 70,
"60": 70,
"61": 70,
"62": 70,
"63": 70,
"64": 70,
"65": 70,
"66": 70,
"67": 70,
"68": 70,
"69": 70,
"70": 70,
"71": 70,
"72": 70,
"73": 70,
"74": 70,
"75": 70,
"76": 70,
"77": 70,
"78": 70,
"79": 70,
"80": 70
}
},
"missing_all_scoring_facts": {
"scoreDistribution": {
"80": 25
},
"scores": {
"56": 80,
"57": 80,
"58": 80,
"59": 80,
"60": 80,
"61": 80,
"62": 80,
"63": 80,
"64": 80,
"65": 80,
"66": 80,
"67": 80,
"68": 80,
"69": 80,
"70": 80,
"71": 80,
"72": 80,
"73": 80,
"74": 80,
"75": 80,
"76": 80,
"77": 80,
"78": 80,
"79": 80,
"80": 80
}
}
},
"physicalDeviceOrPaidFreshAssessmentRun": false,
"baselineReview": "The approved proposal came from a prior QA catalog. Its baseline IDs and versions differ from live storage. Current production rows were separately captured and backed up, and only the exact approved softConditions were merged. Before/after checks preserve current hard rules and all templates. The existing local route differs from production; production matches the frozen released 142-path baseline.",
"backup": {
"privateEvidenceRoot": "D:/AAdesk/202609/airz-backups/product-soft-scores-production-20261019-r1",
"productBackupSha256": "74f4044af2e60eae4449efb4ece9cae74feb428739335d9aa8e9be2099e55816",
"archiveSha256": "6ed63fa1de4d1c33552cc9b8c5e474f2cb045654593c020f65053c379d7e3842",
"archiveFilesVerified": 16
},
"panelLoggedOut": true,
"dedicatedBrowserClosed": true,
"temporaryCredentialsRemoved": true,
"remoteOneShotEntrypointsArchived": true,
"recovery": "Keep this product backup. Any later rollback requires current product-row hash guards and an authorized configuration transaction. Do not restore the whole database or overwrite new user records."
}
@@ -0,0 +1,59 @@
# 产品加减分正式写入回执
2026-10-19 15:14:43(Asia/Shanghai),已按用户“写入正式配置”授权,在单一InnoDB事务中完成25款产品、74条规则写入:49条加分、25条减分。独立只读回读确认所有规则与已批准方案逐条一致。
基础分80,命中才加减,限制在0—100;缺填、未知或无效值不计分。这些分值属于平台参考权重。新捕获产品目录的评估使用新配置,已有报告、已冻结目录的任务及其重试保留原配置;本次未重算历史报告。无需重新发布后端、重启worker或发行小程序。
## 写入范围与验证
- 只更新产品物理字段 `other`、`update_time`,沿用原16列封装;每款逻辑版本加1。当前正式准入规则、原文、模板、资料、金额利率及其他字段逐项保留。
- 当前生产预检1356项、事务内1389项、提交后独立回读1020项检查通过;161条源码守卫及已发布142条路径复核通过。
- 5份既有成功报告存储摘要保持;5份报告、20组匹配、5份详情与归属边界读取通过,25款产品和34省份读取正常。
- 三服务active,重启计数保持,唯一worker PID 3020785、心跳ready;复核时活动评估、调用和三队列均0。
- 模型规划仍为66项条件、100个模板、6批;在相同产品版本下,加减分插入前后的条件、模板及批次规划完全一致。本轮模型调用0次,没有发起正式评估或重试。
## 评分验证
使用现场回读配置做纯内存合成验证:有房车、公积金社保≥2年、月收入2万、零贷款零信用卡时,15款90分、10款95分;明确月收入0且无其他加分项时,25款70分;全部评分字段未知时,25款仍80分。这是规则验证,不是实际新用户模型评估;满足加分项不会恢复不符合硬条件的准入资格。
## 每款版本
| ID | 产品 | 正式版本 | 规则数 |
| --- | --- | --- | --- |
| 56 | 优客分期 | 3 → 4 | 3 |
| 57 | 中银E贷 | 3 → 4 | 2 |
| 58 | 随心智贷 | 3 → 4 | 3 |
| 59 | 集团E贷 | 3 → 4 | 3 |
| 60 | 网捷贷 | 3 → 4 | 3 |
| 61 | 乐分易 | 1 → 2 | 3 |
| 62 | 快贷 | 1 → 2 | 3 |
| 63 | 建易贷 | 1 → 2 | 3 |
| 64 | 分期通 | 1 → 2 | 3 |
| 65 | 融E借 | 1 → 2 | 3 |
| 66 | 闪电贷 | 1 → 2 | 3 |
| 67 | 信秒贷 | 1 → 2 | 3 |
| 68 | 圆梦金 | 1 → 2 | 3 |
| 69 | 惠民贷 | 1 → 2 | 3 |
| 70 | 浦闪贷 | 1 → 2 | 3 |
| 71 | 易达金 | 1 → 2 | 3 |
| 72 | 邮享贷 | 1 → 2 | 3 |
| 73 | 诚易贷 | 1 → 2 | 3 |
| 74 | 光速贷 | 2 → 3 | 3 |
| 75 | 润薪贷 | 1 → 2 | 3 |
| 76 | 润秒贷 | 1 → 2 | 3 |
| 77 | 徽享薪易贷 | 1 → 2 | 3 |
| 78 | 鹏城e贷 | 1 → 2 | 3 |
| 79 | 民易贷 | 1 → 2 | 3 |
| 80 | 职享贷 | 1 → 2 | 3 |
## 现场基线与备份
草案基于此前的QA冻结目录,其规则编号、版本和部分展示资料不等于物理生产数据。正式写入前另行读取并备份当前25款配置,以当前版本和完整行摘要作并发守卫,只插入原方案批准的 `softConditions`,不把QA目录覆盖到生产。预检还发现本地路由与生产既有差异;线上与已发布142路径基线一致,没有修改路由或任何生产源码。两次预检退出均未写库。
原草案JSON与607项历史离线验证证据保持冻结;其草案状态反映创建时状态,正式生效以本回执为准。已批准方案SHA-256:`745f4d6b658631df2601b1d12abc96f6742a4f14c50222ea291f9e843f78400f`。
原25款物理/逻辑配置备份已下载核SHA-256:`74f4044af2e60eae4449efb4ece9cae74feb428739335d9aa8e9be2099e55816`。完整现场档案16文件逐份下载核SHA,归档SHA-256:`6ed63fa1de4d1c33552cc9b8c5e474f2cb045654593c020f65053c379d7e3842`。私密档案:`D:/AAdesk/202609/airz-backups/product-soft-scores-production-20261019-r1`。
恢复仅应在当前产品行摘要守卫下进行配置事务,保留新增业务数据,不整库恢复。本次一次性线上PHP入口已归档停用,备份和意图回执保留;面板已退出、专用浏览器关闭、临时凭据脚本已清理。
结构化回执:[产品加减分正式写入回执-20261019.json](产品加减分正式写入回执-20261019.json)。
@@ -0,0 +1,26 @@
# 产品加减分离线验证
> 2026-10-19 15:14补充:本页保留方案创建时的QA目录离线检查;正式写入前已另用当前生产配置验证并备份,25款74条已写入、独立回读通过。以[正式写入回执](产品加减分正式写入回执-20261019.md)为最新状态。
2026-10-19。仅验证评分建议草案,尚未修改正式产品配置或发布代码。
- 25款产品,共74条规则(49加分/25减分),每款2—3条;607项离线检查通过。
- 配置校验、产品目录编译及原存储格式打包通过;仅softConditions有变化,基础分80、产品其他字段、原准入条件及推荐/不推荐模板保持。
- 原66条模型条件、100条模板和6批模型规划完全一致,新增规则没有进入模型判断;本次数据库访问0、真实模型调用0。
- 命中时按配置加减分,缺失和无效值不加减分;明确零收入减10,未知收入不当作零收入。零贷款/零信用卡的现有合法派生语义保持。
- 走完整匹配路径时,软分值没有改变硬条件结果;本地可确定的年龄不符仍不推荐、rank为空,即使软分值高于80也不能恢复准入。
## 合成资料的计分示例
| 示例 | 产品计分分布 |
| --- | --- |
| 月收入2万元、有房有车、社保/公积金满2年、零贷款与零信用卡 | 15款90分、10款95分 |
| 同收入、无社保公积金/无房、有车,并有月供和卡使用余额 | 19款80分、6款85分 |
| 明确月收入0、无社保公积金及房车,并有月供和卡使用余额 | 25款70分 |
| 评分依据全部未知 | 25款保持基础80分 |
上述只是合成输入下的平台软评分计算,不是实际用户评估、银行认可评分或获批概率。原产品说明中提及的12/24个月、同单位关系、房贷或银行卡历史记录继续独立核对,不能由参考特征替代。
方案及逐款来源边界见[25款产品加减分建议](产品加减分建议-20261019.md)。正式写入前仍需确认平台权重,并对当前产品ID/名称/版本/配置摘要作新鲜比对和备份。历史报告不重算;新评估才冻结新配置,小程序无需因本次配置调整重新发行。
私密证据:`D:/AAdesk/202609/airz-backups/product-soft-scores-20261019-r1`,含proposal.json、validation-input.json、validate-proposal.php、validation.json、repository-baseline.json及proposal-receipt.json。所有验证在自有内存根运行,数据库和DeepSeekClient显式阻断,没有初始化生产App或读取正式.env。
@@ -0,0 +1,331 @@
{
"schemaVersion": "credit-product-current-audit-v1",
"checkedAt": "2026-10-14T09:30:39.501Z",
"destination": "https://airz.hqling.cloud/product?ref=addtabs",
"auditMethod": "已登录后台、无关键词、全部状态与全部类型下读取完整单页列表;未连接数据库或逐条读取不存在的历史记录。",
"currentCatalogTotal": 5,
"currentProducts": [
{
"id": 56,
"bankName": "中国银行",
"productName": "优客分期",
"productType": "个人信用融资",
"applicant": "个人",
"status": "已上架",
"updatedAt": "2026-10-14 15:16:45"
},
{
"id": 57,
"bankName": "中国银行",
"productName": "中银E贷",
"productType": "个人信用融资",
"applicant": "个人",
"status": "已上架",
"updatedAt": "2026-10-14 15:26:13"
},
{
"id": 58,
"bankName": "中国银行",
"productName": "随心智贷",
"productType": "个人信用融资",
"applicant": "个人",
"status": "已上架",
"updatedAt": "2026-10-14 15:27:30"
},
{
"id": 59,
"bankName": "农业银行",
"productName": "集团E贷",
"productType": "个人信用融资",
"applicant": "个人",
"status": "已上架",
"updatedAt": "2026-10-14 15:28:29"
},
{
"id": 60,
"bankName": "农业银行",
"productName": "网捷贷",
"productType": "个人信用融资",
"applicant": "个人",
"status": "已上架",
"updatedAt": "2026-10-14 15:29:24"
}
],
"preparedSource": "docs\\产品材料\\信用贷后20款整理清单-20261014.json",
"preparedSourceSha256": "a5ea7cfa04906c1e49a19806ab0ed157bbd2e2934fd7e6f3c8af2dcaa186494a",
"missingPreparedProductCount": 20,
"institutionCount": 15,
"historicalIdsOnly": true,
"absenceDoesNotEstablishDeletionCause": true,
"proposedCreateCount": 20,
"proposedStatus": "草稿",
"proposedReasonCounts": {
"recommend": 40,
"caution": 40
},
"authorizationForTwentyProductWrite": "not_yet_received",
"externalBusinessWrites": 0,
"runtimeCodeChanged": false,
"products": [
{
"sourceItemNo": 6,
"bankName": "农业银行",
"productName": "乐分易",
"historicalProductId": 36,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-06"
},
{
"sourceItemNo": 7,
"bankName": "建设银行",
"productName": "快贷",
"historicalProductId": 37,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "unconfirmed",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-07"
},
{
"sourceItemNo": 8,
"bankName": "建设银行",
"productName": "建易贷",
"historicalProductId": 38,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "all",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-08"
},
{
"sourceItemNo": 9,
"bankName": "建设银行",
"productName": "分期通",
"historicalProductId": 39,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "all",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-09"
},
{
"sourceItemNo": 10,
"bankName": "工商银行",
"productName": "融E借",
"historicalProductId": 40,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "unconfirmed",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-10"
},
{
"sourceItemNo": 11,
"bankName": "招商银行",
"productName": "闪电贷",
"historicalProductId": 41,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-11"
},
{
"sourceItemNo": 12,
"bankName": "中信银行",
"productName": "信秒贷",
"historicalProductId": 42,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "unconfirmed",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-12"
},
{
"sourceItemNo": 13,
"bankName": "中信银行",
"productName": "圆梦金",
"historicalProductId": 43,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-13"
},
{
"sourceItemNo": 14,
"bankName": "交通银行",
"productName": "惠民贷",
"historicalProductId": 44,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-14"
},
{
"sourceItemNo": 15,
"bankName": "浦发银行",
"productName": "浦闪贷",
"historicalProductId": 45,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "unconfirmed",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-15"
},
{
"sourceItemNo": 16,
"bankName": "华夏银行",
"productName": "易达金",
"historicalProductId": 46,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-16"
},
{
"sourceItemNo": 17,
"bankName": "邮储银行",
"productName": "邮享贷",
"historicalProductId": 47,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "all",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-17"
},
{
"sourceItemNo": 18,
"bankName": "南银法巴消费金融",
"productName": "诚易贷",
"historicalProductId": 48,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-18"
},
{
"sourceItemNo": 19,
"bankName": "光大银行",
"productName": "光速贷",
"historicalProductId": 49,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-19"
},
{
"sourceItemNo": 20,
"bankName": "华润银行",
"productName": "润薪贷",
"historicalProductId": 50,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-20"
},
{
"sourceItemNo": 21,
"bankName": "华润银行",
"productName": "润秒贷",
"historicalProductId": 51,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "unconfirmed",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-21"
},
{
"sourceItemNo": 22,
"bankName": "徽商银行",
"productName": "徽享薪易贷",
"historicalProductId": 52,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-22"
},
{
"sourceItemNo": 23,
"bankName": "徽商银行",
"productName": "鹏城e贷",
"historicalProductId": 53,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-23"
},
{
"sourceItemNo": 24,
"bankName": "民生银行",
"productName": "民易贷",
"historicalProductId": 54,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "any",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-24"
},
{
"sourceItemNo": 25,
"bankName": "深圳农商行",
"productName": "职享贷",
"historicalProductId": 55,
"currentCatalogMatch": null,
"currentRecordRead": false,
"proposedAction": "create_draft_after_explicit_authorization_and_duplicate_recheck",
"recommendedTemplateCount": 2,
"cautionTemplateCount": 2,
"entryCombinationStatus": "all",
"sourceReference": "user-doc:credit-20261009:sha256:bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf:item-25"
}
]
}
@@ -0,0 +1,57 @@
# 信用贷后20款后台核对结果与新增草稿方案
核对时间:2026-10-14T09:30:39.501Z(UTC)。
已重新登录正式产品后台,在无关键词、全部状态、全部类型下,列表显示总共5条,均为前5款ID56—60,且均已上架。后20款的产品名及历史ID36—55均未出现在这份完整当前列表中。
这表明历史导入回执不能用于直接更新当前记录;本轮没有查数据库、回收站或操作日志,不能据此判断删除时间或原因。
## 当前可见产品
| ID | 机构 | 产品 | 状态 |
| ---: | --- | --- | --- |
| 56 | 中国银行 | 优客分期 | 已上架 |
| 57 | 中国银行 | 中银E贷 | 已上架 |
| 58 | 中国银行 | 随心智贷 | 已上架 |
| 59 | 农业银行 | 集团E贷 | 已上架 |
| 60 | 农业银行 | 网捷贷 | 已上架 |
## 拟新增范围(尚未执行)
拟按[已整理资料](信用贷后20款整理清单-20261014.md)新增20款、15家机构的产品草稿,每款录入2条推荐及2条不推荐理由,共80条。保存前再次查重;保留前5款及其当前状态;不执行上架、历史数据恢复、代码修改或部署。
单值、利率起点、最长/离散期限、月息乘12和已确认通用口径沿用r2。原文完整保留;分支关系尚不明确的内容保留备注,不擅自改成全AND或全OR硬准入;建议不作为拒绝条件,事实不足不生成不符理由。
| 原文序号 | 机构 | 产品 | 历史ID(仅线索) | 当前同名记录 | 拟处理 | 推荐/不推荐 |
| ---: | --- | --- | ---: | --- | --- | --- |
| 6 | 农业银行 | 乐分易 | 36 | 未见 | 新增草稿 | 2/2 |
| 7 | 建设银行 | 快贷 | 37 | 未见 | 新增草稿 | 2/2 |
| 8 | 建设银行 | 建易贷 | 38 | 未见 | 新增草稿 | 2/2 |
| 9 | 建设银行 | 分期通 | 39 | 未见 | 新增草稿 | 2/2 |
| 10 | 工商银行 | 融E借 | 40 | 未见 | 新增草稿 | 2/2 |
| 11 | 招商银行 | 闪电贷 | 41 | 未见 | 新增草稿 | 2/2 |
| 12 | 中信银行 | 信秒贷 | 42 | 未见 | 新增草稿 | 2/2 |
| 13 | 中信银行 | 圆梦金 | 43 | 未见 | 新增草稿 | 2/2 |
| 14 | 交通银行 | 惠民贷 | 44 | 未见 | 新增草稿 | 2/2 |
| 15 | 浦发银行 | 浦闪贷 | 45 | 未见 | 新增草稿 | 2/2 |
| 16 | 华夏银行 | 易达金 | 46 | 未见 | 新增草稿 | 2/2 |
| 17 | 邮储银行 | 邮享贷 | 47 | 未见 | 新增草稿 | 2/2 |
| 18 | 南银法巴消费金融 | 诚易贷 | 48 | 未见 | 新增草稿 | 2/2 |
| 19 | 光大银行 | 光速贷 | 49 | 未见 | 新增草稿 | 2/2 |
| 20 | 华润银行 | 润薪贷 | 50 | 未见 | 新增草稿 | 2/2 |
| 21 | 华润银行 | 润秒贷 | 51 | 未见 | 新增草稿 | 2/2 |
| 22 | 徽商银行 | 徽享薪易贷 | 52 | 未见 | 新增草稿 | 2/2 |
| 23 | 徽商银行 | 鹏城e贷 | 53 | 未见 | 新增草稿 | 2/2 |
| 24 | 民生银行 | 民易贷 | 54 | 未见 | 新增草稿 | 2/2 |
| 25 | 深圳农商行 | 职享贷 | 55 | 未见 | 新增草稿 | 2/2 |
## 授权与证据
此前明确正式录入范围为前5款;本次“继续下一步”已推进后台核对。新增这20款会写入正式后台,待用户明确确认后执行。理由正文、参数、来源及不确定项见已整理资料与配套JSON;本文件不是可执行导入配置。
- [结构化核对结果](信用贷后20款后台核对结果-20261014.json)
- [资料与理由JSON](信用贷后20款整理清单-20261014.json)
- [后台列表证据](D:/AAdesk/202609/airz-backups/credit-products-current-audit-20261014/current-list.json)
- [后台列表截图](D:/AAdesk/202609/airz-backups/credit-products-current-audit-20261014/current-list.jpg)
本轮业务写入0、模型调用0,未修改运行代码、产品数据或部署。
@@ -0,0 +1,482 @@
{
"savedAt": "2026-10-14T09:56:58.949Z",
"destination": "https://airz.hqling.cloud/product",
"authorization": "用户明确要求录入后20款、每款2推荐与2不推荐并上架",
"source": "docs/信用贷汇总.docx",
"sourceSha256": "bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf",
"createdCount": 20,
"createdProductIds": [
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80
],
"recommendCount": 40,
"cautionCount": 40,
"totalCurrentCount": 25,
"allCurrentPublished": true,
"originalFirstFiveIds": [
56,
57,
58,
59,
60
],
"codeModified": false,
"modelCalls": 0,
"checks": [
{
"id": 61,
"sourceItemNo": 6,
"name": "乐分易",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 62,
"sourceItemNo": 7,
"name": "快贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 63,
"sourceItemNo": 8,
"name": "建易贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 64,
"sourceItemNo": 9,
"name": "分期通",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 65,
"sourceItemNo": 10,
"name": "融E借",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 66,
"sourceItemNo": 11,
"name": "闪电贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 67,
"sourceItemNo": 12,
"name": "信秒贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 68,
"sourceItemNo": 13,
"name": "圆梦金",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 69,
"sourceItemNo": 14,
"name": "惠民贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 70,
"sourceItemNo": 15,
"name": "浦闪贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 71,
"sourceItemNo": 16,
"name": "易达金",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 72,
"sourceItemNo": 17,
"name": "邮享贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 73,
"sourceItemNo": 18,
"name": "诚易贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 74,
"sourceItemNo": 19,
"name": "光速贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 75,
"sourceItemNo": 20,
"name": "润薪贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 76,
"sourceItemNo": 21,
"name": "润秒贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 77,
"sourceItemNo": 22,
"name": "徽享薪易贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 78,
"sourceItemNo": 23,
"name": "鹏城e贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 79,
"sourceItemNo": 24,
"name": "民易贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
},
{
"id": 80,
"sourceItemNo": 25,
"name": "职享贷",
"allChecksPassed": true,
"recommendCount": 2,
"cautionCount": 2
}
],
"finalRows": [
[
"",
"57",
"中国银行",
"中银E贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 15:26:13",
"规则 下架"
],
[
"",
"61",
"农业银行",
"乐分易",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:42:10",
"规则 下架"
],
[
"",
"56",
"中国银行",
"优客分期",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 15:16:45",
"规则 下架"
],
[
"",
"67",
"中信银行",
"信秒贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:45:35",
"规则 下架"
],
[
"",
"74",
"光大银行",
"光速贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:53:07",
"规则 下架"
],
[
"",
"64",
"建设银行",
"分期通",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:44:41",
"规则 下架"
],
[
"",
"68",
"中信银行",
"圆梦金",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:46:46",
"规则 下架"
],
[
"",
"63",
"建设银行",
"建易贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:44:27",
"规则 下架"
],
[
"",
"77",
"徽商银行",
"徽享薪易贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:54:41",
"规则 下架"
],
[
"",
"62",
"建设银行",
"快贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:43:52",
"规则 下架"
],
[
"",
"69",
"交通银行",
"惠民贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:48:04",
"规则 下架"
],
[
"",
"71",
"华夏银行",
"易达金",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:48:36",
"规则 下架"
],
[
"",
"79",
"民生银行",
"民易贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:55:29",
"规则 下架"
],
[
"",
"70",
"浦发银行",
"浦闪贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:48:20",
"规则 下架"
],
[
"",
"76",
"华润银行",
"润秒贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:54:26",
"规则 下架"
],
[
"",
"75",
"华润银行",
"润薪贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:53:58",
"规则 下架"
],
[
"",
"60",
"农业银行",
"网捷贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 15:29:24",
"规则 下架"
],
[
"",
"80",
"深圳农商行",
"职享贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:55:43",
"规则 下架"
],
[
"",
"65",
"工商银行",
"融E借",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:44:56",
"规则 下架"
],
[
"",
"73",
"南银法巴消费金融",
"诚易贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:50:35",
"规则 下架"
],
[
"",
"72",
"邮储银行",
"邮享贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:49:38",
"规则 下架"
],
[
"",
"66",
"招商银行",
"闪电贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:45:20",
"规则 下架"
],
[
"",
"58",
"中国银行",
"随心智贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 15:27:30",
"规则 下架"
],
[
"",
"59",
"农业银行",
"集团E贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 15:28:29",
"规则 下架"
],
[
"",
"78",
"徽商银行",
"鹏城e贷",
"个人信用融资",
"个人",
"已上架",
"2026-10-14 17:54:56",
"规则 下架"
]
]
}
@@ -0,0 +1,38 @@
# 信用贷后20款录入上架回执(2026-10-14)
用户明确授权“录入吧,每款附2条推荐、2条不推荐理由,上架”。本轮已通过正式产品机构库完成《信用贷汇总.docx》第6—25项新增,上架20款,实际ID61—80;共40条推荐、40条不推荐理由。最终后台完整列表25款,全部已上架。
| 原文序号 | 产品ID | 机构 | 产品 | 推荐/不推荐 | 状态 |
|---|---|---|---|---|---|
| 6 | 61 | 农业银行 | 乐分易 | 2/2 | 已上架 |
| 7 | 62 | 建设银行 | 快贷 | 2/2 | 已上架 |
| 8 | 63 | 建设银行 | 建易贷 | 2/2 | 已上架 |
| 9 | 64 | 建设银行 | 分期通 | 2/2 | 已上架 |
| 10 | 65 | 工商银行 | 融E借 | 2/2 | 已上架 |
| 11 | 66 | 招商银行 | 闪电贷 | 2/2 | 已上架 |
| 12 | 67 | 中信银行 | 信秒贷 | 2/2 | 已上架 |
| 13 | 68 | 中信银行 | 圆梦金 | 2/2 | 已上架 |
| 14 | 69 | 交通银行 | 惠民贷 | 2/2 | 已上架 |
| 15 | 70 | 浦发银行 | 浦闪贷 | 2/2 | 已上架 |
| 16 | 71 | 华夏银行 | 易达金 | 2/2 | 已上架 |
| 17 | 72 | 邮储银行 | 邮享贷 | 2/2 | 已上架 |
| 18 | 73 | 南银法巴消费金融 | 诚易贷 | 2/2 | 已上架 |
| 19 | 74 | 光大银行 | 光速贷 | 2/2 | 已上架 |
| 20 | 75 | 华润银行 | 润薪贷 | 2/2 | 已上架 |
| 21 | 76 | 华润银行 | 润秒贷 | 2/2 | 已上架 |
| 22 | 77 | 徽商银行 | 徽享薪易贷 | 2/2 | 已上架 |
| 23 | 78 | 徽商银行 | 鹏城e贷 | 2/2 | 已上架 |
| 24 | 79 | 民生银行 | 民易贷 | 2/2 | 已上架 |
| 25 | 80 | 深圳农商行 | 职享贷 | 2/2 | 已上架 |
逐款保存后重新打开核对,最终405项数据核验通过:机构与产品名、额度、期限、利率、准入条件及适用对象、原文、来源、还款方式、办理渠道、其他说明和四条理由均与实际录入计划一致。原ID56—60五款的可见字段、状态与更新时间保持;历史ID36—55没有用于本轮更新。邮享贷另录入原文明确要求的身份证、公积金缴存证明两项个人必需资料。
沿用已确认r2口径:月息乘12、分期期数按月;版别/渠道不另拆产品。原文建议保留建议,客群组合不明处保留待核实,未推断硬准入、性别或缺失上下限。结构化显示字段中的ASCII比较符作等价全角替换以通过表单校验,原文逐字保持;未补造Logo、联系电话或缺失资料。核对日期表示转录核对,未独立核实银行现行政策。
光速贷ID74录入后曾修正原文末尾误入字符并重新保存一次;最终原文一致。最终检查时两项其他设置在未展开栏目时导出为空,展开后回读确认原已正确保存,仅补充本地证据,未再次修改线上数据。截图临时隐藏部分列以展示名称和状态,随后已恢复全部列。
本轮仅操作产品业务数据和保存本地回执、交接记录,没有修改运行代码、部署、迁移、真实模型调用、提交或推送。此前匹配结果与身份证年龄改动仍为本地完成、尚未配套发布。本轮结果覆盖此前整理清单/后台核对结果中的“待授权、未录入”待办,历史文件保留。
证据目录:[本轮证据](D:/AAdesk/202609/airz-backups/product-entry-20261014-rest20),包含intended-entries.json、逐款product-6.json至product-25.json、materials-postal.json、receipt.json、verification.json及交接前备份;[配套回执JSON](信用贷后20款录入上架回执-20261014.json)保留完整25条最终列表。源DOCX SHA-256:`bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf`。
![正式后台上架结果](D:/AAdesk/202609/airz-backups/product-entry-20261014-rest20/published-products.jpg)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,786 @@
# 信用贷后20款待复核补充清单
整理日期:2026-10-14。覆盖《信用贷汇总.docx》第6—25项,共20款;每款提供2条推荐、2条不推荐理由草案,合计80条。
历史回执显示全部25款曾于2026-10-09导入为草稿,后20款历史ID为36—55;2026-10-14前5款另有录入记录ID56—60。本轮仅整理本地资料,没有读取当前正式库,也没有新增、修改或上架产品。后续先核对机构、产品名、来源标记和历史ID,再决定补充现有记录。
月息乘12、分期期数按月、部分冲突采用通用口径等沿用已确认的r2修订。单值、起点、最长或离散期限均保留原义,不要求补造上下限、有效期或用途。原文未提供的资料、Logo和电话留空;机构现行政策尚未独立核实。
理由草案仅在已有事实足够时使用。任一路径满足时不因另一条路径不满足而生成不推荐理由;资料不足、条件关系不明时保留待核实。建议项不升级为拒绝条件,不增加评分分值,也不修改融资采集项。
完整逐段原文、历史口径、参数和模板保存在[配套JSON](信用贷后20款整理清单-20261014.json)。下文P为原始正文段落序号,不是页码。
## 产品索引
| 原文序号 | 历史ID | 机构及产品 | 最高额度 | 采用年化 | 期限摘要 |
| ---: | ---: | --- | ---: | --- | --- |
| 6 | 36 | 农业银行 乐分易 | 30万元 | 3% | 最长5年(60个月)。 |
| 7 | 37 | 建设银行 快贷 | 30万元 | 3.45%起 | 1年先息后本,或3年等额;是离散选项,不扩展为任意1—3年。 |
| 8 | 38 | 建设银行 建易贷 | 100万元 | 3%起 | 1—5年(12—60个月)。 |
| 9 | 39 | 建设银行 分期通 | 30万元 | 2.16% | 3—5年(36—60个月)。 |
| 10 | 40 | 工商银行 融E借 | 100万元 | 3%起 | 最长7年(84个月)。 |
| 11 | 41 | 招商银行 闪电贷 | 50万元 | 3%起 | 最长7年(84个月)。 |
| 12 | 42 | 中信银行 信秒贷 | 50万元 | 3%起 | 1—7年(12—84个月)。 |
| 13 | 43 | 中信银行 圆梦金 | 30万元 | 7.66—16.24% | 1—3年(12—36个月)。 |
| 14 | 44 | 交通银行 惠民贷 | 100万元 | 3—14.6% | 最长5年(60个月);一年内先息后本;三年额度循环有效。单笔期限与循环期分开。 |
| 15 | 45 | 浦发银行 浦闪贷 | 100万元 | 3%起 | 最长5年(60个月)。 |
| 16 | 46 | 华夏银行 易达金 | 30万元 | 4.8—9% | 3—36期沿用已确认按月分期口径,为3—36个月;额度5年循环另行保留。 |
| 17 | 47 | 邮储银行 邮享贷 | 100万元 | 3%起 | 1—5年(12—60个月)。 |
| 18 | 48 | 南银法巴消费金融 诚易贷 | 30万元 | 3.6—18% | 最长60期沿用已确认按月等额本息口径,为最长60个月。 |
| 19 | 49 | 光大银行 光速贷 | 30万元 | 2.9—18% | 授信期最长3年;单笔借款期限未提供,继续留空。 |
| 20 | 50 | 华润银行 润薪贷 | 100万元 | 3.66%起 | 标准单笔最长7年(84个月);额度10年属于授信/额度表述,分别保留。 |
| 21 | 51 | 华润银行 润秒贷 | 100万元 | 3.66%起 | 最长7年(84个月);1年先息后本或3—7年等额本息。 |
| 22 | 52 | 徽商银行 徽享薪易贷 | 20万元 | 3.1%起 | 最长3年(36个月)。 |
| 23 | 53 | 徽商银行 鹏城e贷 | 100万元 | 3% | 1—7年(12—84个月);先息后本最长3年。 |
| 24 | 54 | 民生银行 民易贷 | 50万元 | 3%起 | 最长3年(36个月);另有年龄与期限合计≤60岁约束。 |
| 25 | 55 | 深圳农商行 职享贷 | 100万元 | 3.69%起 | “最长36期限”沿用已确认按月还款口径,为最长36个月;先息后本单笔出账≤12个月。 |
## 需要优先核对的记录
- 重复记录:后20款先核对历史ID36—55与当前机构、产品名和来源;历史回执不能代替当前状态。
- 分支或附加关系:快贷、融E借、信秒贷、浦闪贷、润秒贷及部分复合路径的组合关系未完全明确;保留原文,不直接生成全AND/全OR配置。
- 渠道与期限:信秒贷标准50万元/尊享150万元、民易贷线上30万元/线下50万元沿用不拆产品口径;快贷离散期限、光速贷授信期及其他还款分支单独说明。
- 概念不可替代:机构数与卡张数、个人缴存额与缴存基数、近半年平均使用率与当前使用率、外部征信评分与平台匹配度分别保留。
- 缺少的材料:统一缺少银行现行政策独立核实记录、机构Logo及顾问电话;只有邮享贷明确列出身份证与公积金缴存证明,不为其他产品虚构必需材料。
- 原文笔误或空项:徽享薪易贷“毫州市”保持原文待核对;光速贷提前还款违约金为空,不填“无违约金”。
## 06 农业银行 乐分易
历史ID:36;来源:P43—P52;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化3%。
**期限及还款:** 最长5年(60个月)。 等额本息。
**年龄及户籍:** 18—60周岁;大陆户籍。
**地区:** 原文未明确统一适用地区;深房是其中一条进件路径,不能据此将所有路径限定为深圳。
**渠道:** 农行网点建额、扫码审批,通过后绑卡消费;原文未给出承诺审批时长。
**进件摘要:** 原文列四条可选路径:①农业银行白名单客户;②公积金连续缴存12个月以上,平均月缴基数8500元以上;③个税或代发工资1万元以上且满一年,并有公积金连续缴纳半年;④深房红本或按揭状态,并有公积金连续缴费半年以上。第③条括号说明不看公积金基数,不等于免除缴存时长。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求(建议)逾期:当前无逾期,近两年不能有2,进一年不能有5个1,半年不能有3个1。负债:信用贷款余额在50万以下,信用贷机构数建议3家以下信用卡:无特别要求查询次数:近12个月不超12次。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 客群及进件路径 | 你的{X}符合乐分易的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合乐分易的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 客群及进件路径 | 已确认你的{X}未满足乐分易的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合乐分易的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 利率录入口径:月息2厘5=0.25%/月,乘12=3%。 用户指定月利率简单乘12,仅为本轮字段口径,不重算现金流综合年化或费用。
- 月息2厘5沿用已确认的简单乘12口径,录入年化3%,不重新要求补年化上限。
- P51整段标注征信要求(建议),其中逾期、负债、查询口径先保留为建议,不直接新增拒绝条件。
- 第③条的个税/代发组合表述需核对执行细节;P51的“进一年”按原文保留。
## 07 建设银行 快贷
历史ID:37;来源:P53—P59;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化3.45%起。
**期限及还款:** 1年先息后本,或3年等额;是离散选项,不扩展为任意1—3年。 等额本息、先息后本;按对应期限选择。
**年龄及户籍:** 18—60周岁。
**地区:** 原文未明确。
**进件摘要:** 适用人群包括建行存量房贷、公积金缴存、代发工资、建行理财、已办理抵押且未结清的住房贷款客户等。原文为适用人群列举,未完整说明各路径附加条件。
组合关系:原文列举或组合关系待核对,暂不配置为自动准入规则。
**征信及负债原文:**
> 征信要求 ①征信无当前逾期;②近 2 年,逾期不能有 2,累计不超过 5 个 1;③查询:近 6 个月≤20 次,且无贷款发放记录和信用卡开户记录;④信用卡发卡机构数≤7 家;⑤总负债低于 50%,大多数是低于 30%的成功率高一些。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 存量客户及进件要求 | 原文进件关系核实后,你的{X}符合快贷的对应进件要求,可进一步准备资料。 |
| 推荐 | 年龄要求 | 你的{X}符合快贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 存量客户及进件要求 | 原文进件关系核实后,已确认你的{X}未达到快贷的对应进件要求,建议先核实适用方案。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合快贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 原文总负债低于50%未给出分母和计算范围;不能拿贷款余额或当前信用卡使用率代替负债比。
- “大多数低于30%的成功率高一些”为经验描述,不设成硬门槛或固定加分。
- 近6个月查询、贷款发放及信用卡开户记录的组合关系按原文保留,待核对后再配置。
## 08 建设银行 建易贷
历史ID:38;来源:P60—P66;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3%起。
**期限及还款:** 1—5年(12—60个月)。 先息后本、随借随还。
**年龄及户籍:** 20—60周岁。
**地区:** 原文未明确。
**进件摘要:** 下列单位入职满一年(含)以上的职工,且公积金近三年缴存12次以上:政府机构及事业单位、金融、卫生、教育、集团类客户,或其他与建行保持长期合作关系的优质对公单位。单位类别是可选集合,任职与缴存要求需同时核对。
组合关系:并列要求同时满足。
**征信及负债原文:**
> 征信要求 ①名下信用卡不超过6家发卡机构,使用率不超过70%;②信用贷款机构不超过3家;③机构查询半年不超过10次,当月不超过4次;近3个月6次;④大数据良好,不得有小贷网贷,无人法网不良信息;⑤近两年30天内逾期不能超过5个1,不能有2,不能当前逾期。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位及公积金要求 | 你的{X}符合建易贷的客群及缴存要求,可进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合建易贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位及公积金要求 | 已确认你的{X}未达到建易贷的客群或缴存要求,建议核实对应条件后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合建易贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- “近三年12次以上”不等于连续12个月,不把当前缴存时长档位直接视为满足。
- 信用卡发卡机构数、信用贷款机构数与卡张数、贷款笔数分开核对。
- “近3个月6次”未明确比较符;“大数据良好”“无人法网不良信息”保留文字,不填虚构查询结论。
## 09 建设银行 分期通
历史ID:39;来源:P67—P73;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化2.16%。
**期限及还款:** 3—5年(36—60个月)。 等额本息。
**年龄及户籍:** 22—60周岁。
**地区:** 原文要求本地上班,但未给出“本地”的城市范围。
**进件摘要:** 本地上班,社保满12个月以上、社保基数4588元以上;非征信白户,具有1年征信记录。房产、满一年的汽车行驶证、优质单位或本科学历属于辅助提额资料,不是全部必需。
组合关系:并列要求同时满足。
**征信及负债原文:**
> 征信要求 ①信用卡机构数不超过3家;②近6个月所有信用卡使用额度不超过总授信额度的70%;③建行信用卡金额3个月使用率不能超过总额度70%;④历史逾期不得有M2及以上;⑤信用贷款不超过15万。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 社保及征信记录要求 | 你的{X}符合分期通的客群及缴存要求,可进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合分期通的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 社保及征信记录要求 | 已确认你的{X}未达到分期通的客群或缴存要求,建议核实对应条件后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合分期通的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 利率录入口径:0.18%/月,乘12=2.16%;原标签“年化”与单位冲突,采用明确的/月单位。 用户指定月利率简单乘12,仅为本轮字段口径,不重算现金流综合年化或费用。
- 0.18%/月沿用已确认的简单乘12口径,年化字段2.16%;保留原标签与月单位的差异记录。
- 收入12/24/30倍是原文额度测算说明,不用它直接承诺用户额度,也不超过原文最高30万元。
- 近6个月信用卡使用率、建行信用卡近3个月使用率与当前卡使用率不互相替代。
## 10 工商银行 融E借
历史ID:40;来源:P74—P79;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3%起。
**期限及还款:** 最长7年(84个月)。 等额本息、先息后本。
**年龄及户籍:** 原文未明确统一年龄范围。
**地区:** 原文仅说明社保进件在部分城市开通,未列城市。
**进件摘要:** 服务对象为机关事业单位、医院、学校、武警军队、优质企业等正式员工;列出工行信用卡、公积金、部分城市社保、工行代发工资、工行住房按揭客户。原文未明确单位要求与五类客户的完整组合关系。
组合关系:原文列举或组合关系待核对,暂不配置为自动准入规则。
**征信及负债原文:**
> 征信要求 查询:近2个月≤7次;条件好可适当放宽;逾期:当前无逾期,近半年无3次及以上逾期,历史无2贷记卡/贷款状态不能出现冻结、止付、呆账、逾期等状态,贷款五级分类不能出现关注、次级、可疑、损失;负债情况:100万以内,信用卡使用率控制在70%以内。评分大于75分为良好
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位及工行往来要求 | 原文进件关系核实后,你的{X}符合融E借的对应进件要求,可进一步准备资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合融E借的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位及工行往来要求 | 原文进件关系核实后,已确认你的{X}未达到融E借的对应进件要求,建议先核实适用方案。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合融E借的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 按本份资料最高100万元整理,不合并其他历史渠道或官网材料的不同额度。
- 本科学历、优质单位、公积金基数高、本行车贷房贷是原文加分项,但未给出分值;不擅自设分。
- “评分大于75分为良好”未说明评分来源,不与平台匹配度或综合等级对齐。
- 查询“条件好可适当放宽”是例外说明,不默认放宽所有客户。
## 11 招商银行 闪电贷
历史ID:41;来源:P80—P86;适用借款主体:个人。
**额度及利率:** 最高50万元;采用年化3%起。
**期限及还款:** 最长7年(84个月)。 先息后本或等额本息。
**年龄及户籍:** 原文未明确统一年龄范围。
**地区:** 原文未明确。
**进件摘要:** 满足任一条件:①缴纳公积金;②招行代发、招行按揭(房/车)或招行抵押经营贷;③招商信用卡;④企业招行对公户(经常走账)、个人招行存款/理财等;⑤他行按揭房还满2年。公积金现单位缴满2年以上为原文建议。第④条企业与个人条件的组合关系待核对,暂不自行设成AND或OR。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求 ①征信无当前逾期;②近半年无3次及以上逾期;历史无2,2年内逾期不超6个1;③近3个月查询不超6次;④信用卡、贷款无呆账、坏账、止付、冻结、核销等状态;⑤信用卡使用率低于80%。机构一般不超4家。负债看单位情况,单位越好负债可以越高;加分项:征信中体现按揭房。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 招行及其他进件路径 | 你的{X}符合闪电贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合闪电贷的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 招行及其他进件路径 | 已确认你的{X}未满足闪电贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合闪电贷的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 原文“机构一般不超4家”和单位越好负债可越高均无统一硬阈值,不改成无条件拒绝规则。
- 房贷为原文有利因素,未指定加分值;当前是否有房不同于征信中体现按揭记录。
- 最高50万元按本资料保留,不混用其他渠道额度。
## 12 中信银行 信秒贷
历史ID:42;来源:P87—P93;适用借款主体:个人。
**额度及利率:** 最高50万元;采用年化3%起。
**期限及还款:** 1—7年(12—84个月)。 先息后本、随借随还、等额还款。
**年龄及户籍:** 20—60周岁。
**地区:** 公积金基数由各地分行确定,原文未给完整准入地区。
**进件摘要:** 非征信白户为原文明示硬要求。客群为弹性指标,包含公积金、个人纳税/收入、小贷微贷金融行为、按揭房、中信代发或理财客户。公积金连续2年以上与“只要有公积金可尝试”并存,不能将所有路径都强制为2年。
组合关系:原文列举或组合关系待核对,暂不配置为自动准入规则。
**征信及负债原文:**
> 征信要求 1、当前不能有逾期;2、征信上当前无小额贷款余额,小贷不超2家(可以有微粒贷,不能有借呗,重庆蚂蚁,中航信托等);3、6个月内,征信查询小于10次,申请当月不得超过4笔(我行查询为第10次)担保资格查询也算,不看个人查询;4、单次逾期不超过30天;近2年逾期次数不超过3次,2年外逾期次数不超过5次;5、信用卡平均近半年使用额度不超过80%,非住房按揭贷款笔数不超过5笔,信贷余额不超过100万,未还清的小额不超过2家机构(尽量没有);6、白户不可准入。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 客群及非白户要求 | 原文进件关系核实后,你的{X}符合信秒贷的对应进件要求,可进一步准备资料。 |
| 推荐 | 年龄要求 | 你的{X}符合信秒贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 客群及非白户要求 | 原文进件关系核实后,已确认你的{X}未达到信秒贷的对应进件要求,建议先核实适用方案。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合信秒贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 当前采用口径:征信上当前无小额贷款余额;机构数及微粒贷等例外保留原文,暂不转为硬规则。标准最高50万元,尊享版150万元原文保留,不拆产品。
- 标准版最高50万元,尊享版最高150万元只保留版别说明,沿用不拆产品的已确认口径。
- 沿用r2:当前无小额贷款余额为通用整理口径,机构数及微粒贷等例外保留原文,不自动扩展例外。
- 6个月查询小于10次、申请当月不超过4笔及“我行第10次”计数关系需核对;个人查询不计、担保资格查询计入。
- 原文授权中信查公积金/个税是银行进件要求,不表示本平台已获得或已执行外部查询授权。
## 13 中信银行 圆梦金
历史ID:43;来源:P94—P100;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化7.66—16.24%。
**期限及还款:** 1—3年(12—36个月)。 原文:先息后本、等额本息、随借随还;提前还款无违约金。
**年龄及户籍:** 原文未明确统一年龄范围。
**地区:** 房产路径涉及全国按揭或本地房产,未明确所有路径的统一城市范围。
**进件摘要:** 满足一条:①优质单位,加代发或公积金基数8000元以上;②全日制大专及以上,加代发或公积金基数8000元以上;③全国按揭/本地房产,加代发或公积金基数8000元以上。事业单位当前单位社保满3个月;其他单位社保满6个月以上。各分支内部条件需同时核对。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求 1.近1个月查询次数<4次,近3个月查询次数<7次;2.无当前逾期,近半年无2,近一年无4;3.机关单位<150万(信用卡已使用额度+贷款余额),其他单位<100万(信用卡已使用额度+贷款余额)。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 客群及收入证明路径 | 你的{X}符合圆梦金的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合圆梦金的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 客群及收入证明路径 | 已确认你的{X}未满足圆梦金的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合圆梦金的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 机关单位负债低于150万元,其他单位低于100万元;口径为信用卡已用额度+贷款余额,不能合成统一150万元。
- “代发或公积金基数8000以上”的代发收入与缴存基数分开保留,不能把两者等同。
- 产品名称与还款方式沿用原文,是否属于同一渠道的现行方案可后续核实;不据名称自行改产品类型。
## 14 交通银行 惠民贷
历史ID:44;来源:P101—P107;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3—14.6%。
**期限及还款:** 最长5年(60个月);一年内先息后本;三年额度循环有效。单笔期限与循环期分开。 一年内先息后本、随借随还;三年内额度循环,不提款不计息。
**年龄及户籍:** 原文未明确统一年龄范围。
**地区:** 原文表述全国,并列房产、公积金、社保、代发、车按揭等类别;具体地区开通情况未逐城说明。
**进件摘要:** 满足一条:交行信用卡、交行储蓄卡有6个月打卡工资、交行或他行抵押/按揭月供房、公积金缴纳客户。后文另列房产、社保、代发、车按揭及交行存量客群,附加路径关系保留原文核对。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求 ①非白户,信贷控制3笔以内,且无未结清经营贷款。信用卡使用情况良好,信用卡已用额度不怎么看;②近半年无1,近两年无2。(不看2年外征信)当前无1,1年无2,2年无3(逾期金1千内可不看);③负债:优质单位不超150万负债,普通单位不超80万负债(担保不看,农户贷不看,抵押按揭不看,车抵押按揭不看); ④ 查询:2个月不超6次,交通存量和优质单位2个月9次(交通第9次查询)注:认识交行客户经理,可以先查单位是否在白名单。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 交行及其他进件路径 | 你的{X}符合惠民贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 征信逾期通用口径 | 你的{X}符合已确认采用的惠民贷征信通用口径,可作为进一步核对的依据。 |
| 不推荐/注意 | 交行及其他进件路径 | 已确认你的{X}未满足惠民贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 征信逾期通用口径 | 你的{X}与已确认采用的惠民贷征信通用口径不一致,需进一步核实具体方案。 |
**录入及核对备注:**
- 当前采用口径:近半年无1、近两年无2(不看两年外征信);另一套当前/1年/2年及小金额例外完整保留原文。此处沿用资料术语,不推断逾期次数。
- 沿用r2:近半年无1、近两年无2且不看两年外为通用整理口径,另一套年限与小金额例外保留原文。
- 优质单位150万元/普通单位80万元负债阈值及担保、农户贷、抵押按揭等排除范围需按原文分别核对。
- 普通客户两个月查询不超6次;交行存量和优质单位9次(含交行第9次)为分支,不混成统一9次。
- 不因同时存在5年借款与3年额度期而要求用户重新提供完整上下限。
## 15 浦发银行 浦闪贷
历史ID:45;来源:P108—P118;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3%起。
**期限及还款:** 最长5年(60个月)。 期限1年以上可选按月等额本金或按月等额本息;原文“本月等额本息”保留于逐段原文。
**年龄及户籍:** 18—60岁。
**地区:** 原文未明确。
**进件摘要:** 列出公积金客户、任意银行房贷还款满半年、浦发代发工资、浦发信用卡及其他浦发业务往来客户。原文未明示五项全部必需或任选,先保留列举关系。
组合关系:原文列举或组合关系待核对,暂不配置为自动准入规则。
**征信及负债原文:**
> 征信要求 1、两年内无连三累六;2、近三个月查询不超过8次,一年不超15次
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 公积金及银行往来要求 | 原文进件关系核实后,你的{X}符合浦闪贷的对应进件要求,可进一步准备资料。 |
| 推荐 | 年龄要求 | 你的{X}符合浦闪贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 公积金及银行往来要求 | 原文进件关系核实后,已确认你的{X}未达到浦闪贷的对应进件要求,建议先核实适用方案。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合浦闪贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 利率录入口径:原文3%起未标年/月,结合信用贷利率上下文按年化起点录入。 保留原文并记录本次取舍,不补造最高值。
- “3%起”沿用r2按年化起点3%整理,上限留空。
- 原文未说明期限1年及以下对应还款方式,不从1年以上分支外推。
- “两年无连三累六”保留资料术语,不把不同逾期等级和累计次数互相替换。
## 16 华夏银行 易达金
历史ID:46;来源:P119—P125;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化4.8—9%。
**期限及还款:** 3—36期沿用已确认按月分期口径,为3—36个月;额度5年循环另行保留。 等额还款。
**年龄及户籍:** 原文未明确统一年龄范围;“90后”是原文加分描述,不是年龄准入下限。
**地区:** 原文未明确。
**进件摘要:** A类:公务员、事业单位、教师、医生或上市企业,当前单位社保满3个月;B类:普通企业,代发工资5000元以上,且现单位社保/代发6个月。美容美发、投资、娱乐等属于原文禁入行业;社保与代发6个月的具体替代关系待核对。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求 1.查询次数两个月不超6次;6-10次有影响(单位好可放宽政策);2.无当逾;可解释逾期原因;;半年无2个1,1年无3个1,2年无2,历史无3以上 (年费、小金额,可根据实际情况写情况说明);3.负债:原则信贷月供+信用卡 10%<认定收入80%;房贷不计算负债。名下房贷、车、保单、学历、优质单位可以加分,其中房贷和学历客户加分最明显;网贷笔数:建议不超过4笔,结清等征信更新可准入;信用卡:持卡机构越少越好,建议7家内,使用率看最近半年不超 60%。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位及收入社保路径 | 你的{X}符合易达金的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合易达金的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位及收入社保路径 | 已确认你的{X}未满足易达金的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合易达金的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 期限录入口径:3—36期按月分期,录入3—36个月;额度5年循环不混入单笔期限。
- 利率录入口径:0.4—0.75%/月,乘12=4.8—9%。 用户指定月利率简单乘12,仅为本轮字段口径,不重算现金流综合年化或费用。
- 0.4—0.75%/月沿用简单乘12,年化录入4.8—9%。
- 房贷、学历、优质单位、保单、车等为未给定分值的有利因素;不新增平台分值。
- 负债原文为信贷月供+信用卡10%小于认定收入80%,房贷不计;认定收入不自动等同填写月收入。
- “女性同等条件批核比男性高”只是原文经验描述,不转成性别加分或准入规则。
## 17 邮储银行 邮享贷
历史ID:47;来源:P126—P132;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3%起。
**期限及还款:** 1—5年(12—60个月)。 等额本息、先息后本。
**年龄及户籍:** 原文未明确统一年龄范围。
**地区:** 原文未明确。
**进件摘要:** 公务员、事业编等优质单位;单位缴存公积金2年以上,个人缴存额500元以上。个人缴存额不是公积金缴存基数。
组合关系:并列要求同时满足。
**征信及负债原文:**
> 不能有当前逾期、累计逾期不超8次
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位及公积金要求 | 你的{X}符合邮享贷的客群及缴存要求,可进一步核实申请资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合邮享贷的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位及公积金要求 | 已确认你的{X}未达到邮享贷的客群或缴存要求,建议核实对应条件后再考虑申请。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合邮享贷的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 原文未给累计逾期8次的统计年限,不能默认近两年或终身。
- 现有公积金基数字段不能代替个人月缴存额,不能仅凭基数判定该路径满足。
**原文申请资料:** 身份证、公积金缴存证明。
## 18 南银法巴消费金融 诚易贷
历史ID:48;来源:P133—P139;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化3.6—18%。
**期限及还款:** 最长60期沿用已确认按月等额本息口径,为最长60个月。 等额本息、随借随还;提前还款无违约金。
**年龄及户籍:** 原文未给统一年龄范围;新青年分支要求30周岁以下并有本科学历。
**地区:** 原文多处使用本地/外地,但未列具体开通城市。
**进件摘要:** 上班族分支:本地户籍或本地有房且连续社保6个月;外地且本地无房连续社保12个月;30周岁以下、本科学历、连续社保3个月;研究生学历、社保1个月且不看户籍。生意族分支:本地户籍、执照满一年且有房;外地户籍、执照满两年且有房;养殖种植执照满三年,美容美发足疗健身类不符合。各分支内部条件组合保留。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求 不能当前逾期,一年内不能有2个1,两年内不能有3个1,历史总逾期不要超15次;查询要求:(只看带审字的)一个月6次,三个月10次,半年15次;系统并没有明确规定负债比和机构笔数,而是根据【基本条件+加分条件】来综合评分审批。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 上班族或生意族进件路径 | 你的{X}符合诚易贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 当前逾期要求 | 你的{X}符合诚易贷的当前逾期要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 上班族或生意族进件路径 | 已确认你的{X}未满足诚易贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 当前逾期要求 | 已确认你的{X}不符合诚易贷的当前逾期要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 期限录入口径:最长60期按按月等额本息期限,录入最长60个月。
- “生意族”仍按自然人经营者资料整理,不把借款主体自动改为企业。
- 养殖种植执照满三年与户籍/房产条件的组合关系需核对,不据此免除其他条件。
- 社保、公积金、个税、工资流水等为收入认定方式;未在原文指定统一收入门槛。
- 原文说明不设明确负债比/机构笔数,不能自行补阈值。
- 查询只看带“审”字,但一个月6次、三个月10次、半年15次未写比较符;保留口径待核对。
## 19 光大银行 光速贷
历史ID:49;来源:P140—P150;适用借款主体:个人。
**额度及利率:** 最高30万元;采用年化2.9—18%。
**期限及还款:** 授信期最长3年;单笔借款期限未提供,继续留空。 等额本息、先息后本、随借随还。
**年龄及户籍:** 22—60周岁;大陆户籍。
**地区:** 有光大银行网点地区;不是无条件全国准入。
**进件摘要:** 必须满足一条:光大房产抵押/按揭且月供3个月以上;本单位连续缴纳公积金社保12个月;近12个月本单位连续缴个税;近6个月光大代发工资;大额存单、理财、流水等客户。原文称名下有无信用卡均可申请。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求(建议)①查询:近2个月查询少于6次,近6个月查询少于12次;②当前不能有逾期;③两年内征信逾期不要超过5个1;
> 负债情况:1、余额建议:一般客户信贷(无房产)+信用卡已使用小于50万,2、未结清信贷笔数建议:不超3笔;3、非银行贷款笔数建议:不超2家;
> 信用卡:1、五级分类:正常;2、使用率建议:控制在70%以内;
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 光大往来及收入进件路径 | 你的{X}符合光速贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合光速贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 光大往来及收入进件路径 | 已确认你的{X}未满足光速贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合光速贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 当前采用口径:最长3年为授信期,单笔期限不据此推定,继续保留原文。
- P146“提前还款违约金”后为空,不录入“无违约金”或0费用。
- 公积金社保连续12个月是否需要同时具备两者,保留原文并核对。
- 原文征信/余额/贷款笔数/信用卡使用率包含建议标签,不无条件升级为硬拒绝条件。
- 最长3年为授信期限的口径已确认,不再要求将其推成单笔3年。
## 20 华润银行 润薪贷
历史ID:50;来源:P151—P166;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3.66%起。
**期限及还款:** 标准单笔最长7年(84个月);额度10年属于授信/额度表述,分别保留。 先息后本、等额本息。
**年龄及户籍:** 23—60周岁,且年龄+贷款期限<61岁;大陆户籍。
**地区:** 深圳、广州、珠海、东莞、佛山、惠州、中山、肇庆、江门。
**进件摘要:** 五类受薪客群:①华润集团、政府机关及事业单位、公立医院学校,以及政策性银行、国有银行、全国性商业银行、证券公司、基金、信托的正式员工;②垄断行业、财富500强、国内外上市公司及相关控股下属公司/分支、客群①未列明的其他金融行业、国内外上市公司的控股母公司、地方百强及国企(含控股下属公司/分支)的正式员工。上市范围按原文包含主板、中小板、科创版,排除ST/新三板;地方百强取一线城市前50强、非一线前30强;③本行对公授信3000万元以上、战略合作、资金运营中心合作等企业,需按白名单审批;④本行认定、且不在客群①②范围内的头部科技/互联网优质企业;⑤其他优质受薪客群,社保/公积金基数10000元。①②③月均认定收入≥12000元;④月均认定收入20000元;⑤收入门槛原文未写。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求查询要求:近2个月不超3次,近12个月查询不超15次;逾期要求:当前无逾期,近3个月无1,2年内无连三累六;负债要求:①征信评分600分以上;②负债比不超75%;准入地区准入地区:深圳,广州,珠海,东莞,佛山,惠州,中山,肇庆,江门。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 受薪客群及认定收入要求 | 你的{X}符合润薪贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄与贷款期限组合 | 你的{X}符合润薪贷的年龄与贷款期限组合,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 受薪客群及认定收入要求 | 已确认你的{X}未满足润薪贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄与贷款期限组合 | 已确认你的{X}不符合润薪贷的年龄与贷款期限组合,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 当前采用口径:标准单笔最长7年(84个月);额度10年属于授信/额度表述,仅保留原文。
- 利率录入口径:原文3.66%起未标年/月,结合信用贷利率上下文按年化起点录入。 保留原文并记录本次取舍,不补造最高值。
- 利息3.66%起沿用r2年化起点,上限留空。
- “月认定收入×18×客群系数”和按职级定额未给完整系数/职级表,不计算或承诺可贷额度。
- 征信评分600分以上是资料中的外部评分要求,不与平台综合等级或匹配度混用。
- 负债比≤75%未给分母及核算方式,保留文字;客群⑤不得套用①②③的收入门槛。
## 21 华润银行 润秒贷
历史ID:51;来源:P167—P174;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3.66%起。
**期限及还款:** 最长7年(84个月);1年先息后本或3—7年等额本息。 先息后本、等额本息、随借随还;与期限配套。
**年龄及户籍:** 男22—60岁,女22—55岁;大陆户籍。
**地区:** 广东省,或者广东省内手机号码;地域与号码条件为原文OR表述,具体认定方式待核对。
**进件摘要:** 上班族:有社保/公积金/个税/月供房/月供车。自雇人士:有月供房/月供车最好。原文斜杠关系与“最好”不直接转成全部必需条件。
组合关系:原文列举或组合关系待核对,暂不配置为自动准入规则。
**征信及负债原文:**
> 征信要求(建议)查询要求:近一个月不超4次,近3个月查询不超8次;逾期要求:当前无逾期,半年无2,一年无3,2年无4;负债要求:①抵押负债不看;②信贷负债不超6笔不超150万;准入地区准入地区:广东省(或者广东省内手机号码)
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 上班族或自雇人士要求 | 原文进件关系核实后,你的{X}符合润秒贷的对应进件要求,可进一步准备资料。 |
| 推荐 | 分性别年龄要求 | 你的{X}符合润秒贷的分性别年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 上班族或自雇人士要求 | 原文进件关系核实后,已确认你的{X}未达到润秒贷的对应进件要求,建议先核实适用方案。 |
| 不推荐/注意 | 分性别年龄要求 | 已确认你的{X}不符合润秒贷的分性别年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 利率录入口径:原文3.66%起未标年/月,结合信用贷利率上下文按年化起点录入。 保留原文并记录本次取舍,不补造最高值。
- 利息3.66%起沿用r2年化起点,上限留空。
- 征信要求标注建议,抵押负债不看、信贷不超6笔且不超150万元保留为原文建议口径。
- 手机号码归属地不同于常住/工作城市;不能仅凭现有城市字段推定整个地区条件已满足。
## 22 徽商银行 徽享薪易贷
历史ID:52;来源:P175—P184;适用借款主体:个人。
**额度及利率:** 最高20万元;采用年化3.1%起。
**期限及还款:** 最长3年(36个月)。 等额本息、先息后本、随借随还;提前还款无违约金。
**年龄及户籍:** 22—60岁;大陆户籍。
**地区:** 北京全市;广东深圳;江苏南京;浙江宁波;安徽合肥、芜湖、蚌埠、淮南、马鞍山、铜陵、安庆、黄山、滁州、阜阳、宿州、六安、原文“毫州”、池州、宣城;四川成都、达州。
**渠道:** 线上申请、线上审批、线上放款。
**进件摘要:** 符合其一:社保客群(建议本单位满半年以上);公积金客群(缴存时间没有具体要求);优质行业客户(国企、公务员、事业单位)。社保半年是建议,不设为所有客群门槛。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求①逾期:无当前逾期,不得有3期连续逾期和连3累6;近一年逾期无2且一年内逾期次数不超6次;近两年逾期无3且两年内逾期次数不超9次②网贷笔数:非银小贷不能有4笔(含4笔)(含借呗和微粒贷)③查询:近3个月内机构查询不得超过7次准入地区北京:全市广东:深圳市江苏省:南京市浙江省:宁波市安徽省:合肥市、芜湖市、蚌埠市、淮南市、马鞍山市、铜陵市、安庆市、黄山市、滁州市、阜阳市、宿州市、六安市、毫州市、池州市、宣城市四川省:成都市、达州市
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 社保公积金或优质行业路径 | 你的{X}符合徽享薪易贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合徽享薪易贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 社保公积金或优质行业路径 | 已确认你的{X}未满足徽享薪易贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合徽享薪易贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 原文“毫州市”疑似“亳州市”,原文与转录保持,不擅自替换地区代码。
- 多城市名单全部保留,不截成当前字典的少数城市。
- 非银小贷不能有4笔(含4笔),包含借呗和微粒贷;该口径不等于所有贷款总笔数。
## 23 徽商银行 鹏城e贷
历史ID:53;来源:P185—P192;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3%。
**期限及还款:** 1—7年(12—84个月);先息后本最长3年。 等额本息、先息后本、随借随还。
**年龄及户籍:** 22—60岁;大陆户籍。
**地区:** 仅限深圳。
**进件摘要:** ①事业单位、国企、上市公司、银行券商等,且公积金满12个月、基数>6000元;②普通单位先加入白名单,且公积金满12个月、基数>8000元。每条路径内部需同时满足,不混用两档基数。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求①查询:近3个月≤6次。②当前不能有逾期。③近两年内征信逾期不要超过2个1。④信用贷用信额度用款小于100万(建议)。准入地区准入地区:深圳。
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位与公积金组合路径 | 你的{X}符合鹏城e贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合鹏城e贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位与公积金组合路径 | 已确认你的{X}未满足鹏城e贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合鹏城e贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 原文利率为年化3%单值,保留上下限同值,不改成“3%起”。
- 信用贷用信额度小于100万元属于原文建议,不单独作为硬拒绝条件。
- 基数门槛为严格大于6000/8000元,不改为大于等于。
## 24 民生银行 民易贷
历史ID:54;来源:P193—P203;适用借款主体:个人。
**额度及利率:** 最高50万元;采用年化3%起。
**期限及还款:** 最长3年(36个月);另有年龄与期限合计≤60岁约束。 循环额度、随借随还。
**年龄及户籍:** 22—60周岁,且年龄+期限≤60岁。
**地区:** 原文未明确统一城市范围。
**渠道:** 原文最高50万元:纯线上最高30万元,50万元需线下。原文“纯线上申请、秒出结果”为描述,不承诺实际时效。
**进件摘要:** 先满足受薪人士且非法人/股东等身份要求,再满足其一:白名单企业公积金连续≥6个月;非白名单企业连续≥12个月(部分≥18个月);本行按揭还款≥12个月;他行按揭≥24个月;民生代发连续≥12个月。原文另有职业、证件剩余有效期和手机号段禁入要求,须完整核对。
组合关系:任一完整路径满足;路径内部的条件同时核对。
**征信及负债原文:**
> 征信要求①查询:近2个月≤4次;②逾期:2年不超6个1,不能有2;③有房:信贷不超3笔;④无房:信贷不超2笔;⑤不能有小额字眼的贷款。⑥信用负债<100万
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 受薪身份及进件路径 | 你的{X}符合民易贷的一项完整进件路径,可按该路径进一步核实申请资料。 |
| 推荐 | 年龄与贷款期限组合 | 你的{X}符合民易贷的年龄与贷款期限组合,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 受薪身份及进件路径 | 已确认你的{X}未满足民易贷的任一进件路径,建议先核实适用客群后再考虑申请。 |
| 不推荐/注意 | 年龄与贷款期限组合 | 已确认你的{X}不符合民易贷的年龄与贷款期限组合,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 当前采用口径:产品额度上限50万元;纯线上30万元限制完整保留原文,不拆渠道,后续AI分析须读取渠道条件。
- 沿用r2:最高50万元,线上30万元限制保留,不拆产品;推荐或解释时必须说明渠道差别。
- 军人、消防、学生、无业、退休、家庭主妇等职业禁入;证件到期≤180天及完整禁入号段见原文,不压缩遗漏。
- 有房信贷不超3笔/无房不超2笔为分支;信用负债<100万元是严格小于。
- 非白名单12/18个月的适用分支原文未指明,当前资料不足以选择更宽条件。
## 25 深圳农商行 职享贷
历史ID:55;来源:P204—P215;适用借款主体:个人。
**额度及利率:** 最高100万元;采用年化3.69%起。
**期限及还款:** “最长36期限”沿用已确认按月还款口径,为最长36个月;先息后本单笔出账≤12个月。 ①按月等额还本息;②按月付息、按月等额还本;③按月付息到期还本(单笔≤12个月);④按月付息、按月定额还本1.5%,余额到期结清。
**年龄及户籍:** 24—60周岁;大陆户籍。
**地区:** 原文多项客群限定深圳总部或分支机构,但未明示全部客群的统一地区条件。
**进件摘要:** 本单位连续缴存社保满1年(含),并属于原文列明客群:公务员、事业单位公职人员;政策性银行、大型国有商业银行、全国性股份制银行、城市商业银行等的深圳总部或深圳分支机构职工;民办医院或民办学校(小学、初中、高中)的医务、教学岗位职工;国企、央企或其全资控股子公司职工;沪深主板上市企业、华为技术有限公司、腾讯科技(深圳)有限公司或前述企业全资控股子公司职工。企业控股范围按各条原文分别保留。
组合关系:并列要求同时满足。
**征信及负债原文:**
> 征信要求(建议)逾期情况:当前不能有逾期;近两年累计不能超过6次,最高逾期不超过“1”;征信查询:近半年不超过6次;负债要求(建议):①信贷余额不能超过150万;②收入负债比不超55%;
**理由草案:**
| 类型 | 关联条件 | 模板正文 |
| --- | --- | --- |
| 推荐 | 单位及连续社保要求 | 你的{X}符合职享贷的客群及缴存要求,可进一步核实申请资料。 |
| 推荐 | 年龄要求 | 你的{X}符合职享贷的年龄要求,可作为进一步核对产品适用性的依据。 |
| 不推荐/注意 | 单位及连续社保要求 | 已确认你的{X}未达到职享贷的客群或缴存要求,建议核实对应条件后再考虑申请。 |
| 不推荐/注意 | 年龄要求 | 已确认你的{X}不符合职享贷的年龄要求,建议先核实该项后再考虑申请。 |
**录入及核对备注:**
- 期限录入口径:“最长36期限”结合按月还款表述按最长36个月录入;先息后本单笔≤12个月例外仍保留原文。
- 原文征信及负债要求标注建议,信贷余额≤150万元、收入负债比≤55%不直接新增硬拒绝条件。
- 收入负债比未给具体分子分母;不能用平台当前还款负担率直接替代。
- 按月定额还本1.5%是还本金安排,不是月利率;年化利率仍按原文3.69%起。
## 来源与边界
- [原始资料](../信用贷汇总.docx)
- [首次导入回执](信用贷正式数据导入回执-20261009.json)
- [已确认口径及修订记录](../社保公积金与信用贷口径修订-20261009.md)
- [前5款本次录入回执](D:/AAdesk/202609/airz-backups/product-entry-20261014-first5/receipt.json)
原始资料SHA-256:`bb11729b5a0f5fe56608be420f06fc720c23c1c16fa3ba8c83382a6e487e17bf`。本清单不表示当前线上状态、政策有效性或产品获批承诺;没有执行导入、发布、部署、真实模型调用或代码修改。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,328 @@
# 信用贷资质与补填口径离线核查(2026-10-15)
**结论:小程序全部现有填写内容保留。** 本轮完成ID56及其余24款的逐条对照,范围为25款个人产品、82条历史条件和100条理由模板。只生成离线清单,不修改运行代码、正式产品、历史报告或线上状态,不新增模型调用。
核查基于第5轮历史合成资料、产品录入回执重建目录和本地当前源码,不是生产库的新导出,也不是银行政策确认。历史样本有深圳城市事实;当前新表单仅采集省份,两者不得混用。
## 核查结果
| 类别 | 产品/条件 | 结论 |
| --- | --- | --- |
| 已确认申请字段误入历史补填 | 56\|rule_qa_4 | 本次申请金额出现在历史missingFields;新提示仅约束今后返回,不改旧报告。 |
| 已确认历史时长提示过于笼统 | 58\|rule_qa_1、61\|rule_qa_2 | 模型请求已有公积金12至24个月区间,不能笼统要求重填;仍应指出路径、精确年限/平均基数或单位依据。 |
| 已确认历史城市事实未用于对应名单 | 75\|rule_qa_5、77\|rule_qa_5、78\|rule_qa_5 | 该轮已提供深圳城市代码仍索要城市/申请地区;现省份输入不能套用历史城市通过预期。冻结名称和提示已在前轮处理,未做新模型验收。 |
| 已确认网点核实提示不具体 | 74\|rule_qa_4 | 仅写申请地区,未清楚提示光大网点覆盖;不能由所在城市或省份推断网点。 |
| 可选路径资料混列风险 | 56\|rule_qa_3、58\|rule_qa_1、61\|rule_qa_2、68\|rule_qa_1、73\|rule_qa_1、74\|rule_qa_2、79\|rule_qa_1 | 历史缺失列表混合多个路径;这不能证明模型做了全AND,但提示可能让用户误以为须全部补齐。尤其圆梦金已有公积金基数,不应固定索要代发工资。 |
| 已采集但模型不可见 | 75\|rule_qa_2、79\|rule_qa_1、79\|rule_qa_4 | 职业、单位名称、电话按现有隐私边界保留而不外发;提示词可以约束措辞,不能让模型获知隐藏值或完成相关资格核验。 |
| 数值与银行统计口径不同 | 62\|rule_qa_2、63\|rule_qa_3、64\|rule_qa_3、67\|rule_qa_2、71\|rule_qa_3、72\|rule_qa_1 | 卡张数与发卡机构数、贷款笔数与机构数、当前与历史均值、总月供与剔除房贷后的信贷月供、缴存基数与个人缴存额不可等同。 |
| 理由独立文字条件不等于硬准入 | 62、65、67、70、76 | 快贷、融E借、信秒贷、浦闪贷、润秒贷的部分客群关系保留在独立理由条件中;不能将82条硬条件覆盖等同于所有原文已成为可判硬门槛。本轮不改配置。 |
历史82条中21条pass、61条unknown、0条fail;25款产品均unknown,主报告完整度却为100%、待完善项为空。这是两种不同口径:当前表单已提供完整,不代表银行所有条件已经核实。不能为减少unknown删除用户填写项、删银行条件,或自动认定通过。
100条模板中历史实际展示17条推荐理由、0条不推荐理由;未发现该样本将unknown直接展示成已确认不符。这里说明样本行为,不证明其他资料或未来模型输出一定正确。
unknown允许不带事实引用;因此“没有引用某个已填数值”只提示需要复核,不能单凭这一点断言模型漏用。相反,missingFields中重复出现已提供时长、误列申请字段,有直接请求与响应证据。
## 输入内容保留及使用边界
| 现有内容 | 当前使用方式 | 不能替代的银行信息 |
| --- | --- | --- |
| 身份证派生周岁(age) | 可用的白名单事实;无值或不合法时不猜测 | 只外发周岁;不外发完整证件,不推导性别、户籍或有效期。 |
| 所在省份(provinceCode) | 可用的白名单事实;无值或不合法时不猜测 | 只证明省级地区;旧任务的可信城市事实另按原冻结口径。 |
| 月收入(monthlyIncome) | 可用的白名单事实;无值或不合法时不猜测 | 普通自填月收入,不自动等于代发、纳税或银行月均认定收入。 |
| 社保连续缴纳(socialSecurity) | 可用的白名单事实;无值或不合法时不猜测 | 当前连续时长区间,不自动证明本单位、征信更新或近三年缴存次数。 |
| 公积金连续缴纳(housingFund) | 可用的白名单事实;无值或不合法时不猜测 | 同上;区间只可支持其明确包含的范围。 |
| 社保月基数(socialSecurityBase) | 可用的白名单事实;无值或不合法时不猜测 | 当前工资基数,不自动等于均值或个人缴存额。 |
| 公积金月基数(housingFundBase) | 可用的白名单事实;无值或不合法时不猜测 | 当前工资基数,不等于个人缴存额、历史平均基数。 |
| 欠款总余额(totalDebtBalance) | 可用的白名单事实;无值或不合法时不猜测 | 总余额不等于某种信用/经营贷款余额,也不是资产负债比。 |
| 贷款月还款金额(monthlyLoanPayment) | 可用的白名单事实;无值或不合法时不猜测 | 不自动包含本笔贷款、共同借款人或信用卡项,不自动剔除房贷。 |
| 信用贷款笔数(creditLoanCount) | 可用的白名单事实;无值或不合法时不猜测 | 不等于贷款机构数、非银小贷笔数或查询次数。 |
| 银行信用卡张数(creditCardCount) | 可用的白名单事实;无值或不合法时不猜测 | 不等于不同发卡机构数;范围一致时才可作机构数量上界,不能反向等同。 |
| 信用卡总额度(creditCardTotalLimit) | 可用的白名单事实;无值或不合法时不猜测 | 当前全卡总额度,不自动拆出某行或历史额度。 |
| 信用卡已使用总额度(creditCardUsedLimit) | 可用的白名单事实;无值或不合法时不猜测 | 当前值不是近三/六个月平均值。 |
| 是否有房(house) | 可用的白名单事实;无值或不合法时不猜测 | 不证明房产城市、价值、净值、年限、红本或按揭银行。 |
| 是否有车(car) | 可用的白名单事实;无值或不合法时不猜测 | 不证明行驶证满年、月供、抵押或贷款行。 |
| 职业类型(occupation) | 继续保存/回填;不因本轮审核扩大外发 | 已采集并保存/回填;当前自由文本职业不在模型事实白名单。 |
| 工作单位(company) | 继续保存/回填;不因本轮审核扩大外发 | 名称保留;不外发、不凭名称猜单位性质或银行白名单。 |
| 手机号(phone) | 继续保存/回填;不因本轮审核扩大外发 | 号码保留但不外发;模型不可见不代表用户未填。 |
| 身份证号(idCard) | 继续保存/回填;不因本轮审核扩大外发 | 号码保留;有效时仅派生周岁;证件到期日未从号码获取。 |
| 姓名/联系人(name) | 继续保存/回填;不因本轮审核扩大外发 | 身份资料保留,不为本轮模型审核扩大外发。 |
| 婚姻状况(maritalStatus) | 继续保存/回填;不因本轮审核扩大外发 | 保留原有采集和回填,本次25款条件未建立相应模型判断。 |
| 企业名称(enterpriseName) | 继续保存/回填;不因本轮审核扩大外发 | 企业表单保留;本次25款均为个人产品。 |
| 统一社会信用代码(businessCode) | 继续保存/回填;不因本轮审核扩大外发 | 企业表单保留,不作为个人产品事实外发。 |
| 企业年营收(annualRevenue) | 企业表单及原对象白名单保持 | 企业事实按现有对象白名单使用;本次不作企业产品覆盖结论。 |
| 企业经营月数(operatingMonths) | 企业表单及原对象白名单保持 | 企业表单保留;不移用于个人营业执照分支。 |
| 资料照片(documents) | 继续保存/回填;不因本轮审核扩大外发 | 上传入口/已有资料保留;未核验照片当前不参与评估,不做OCR或外发。 |
| 数据授权及同意项(authorizations) | 继续保存/回填;不因本轮审核扩大外发 | 保留选择意向;已勾选不代表已取得征信/公积金等外部数据。 |
金额仍按现有规则在小程序以万元输入、后端以元保存;本轮不做单位换算改造。所列字段只是对照用途,不新增必填,也不删减任何原字段。
## 25款总览
| ID | 产品 | 条件数 | 主要处理口径 |
| --- | --- | ---: | --- |
| 56 | 优客分期 | 4 | 保留全部收入、负债和房产信息;具体申请不列为表单漏填,完整负债公式仍待核实。 |
| 57 | 中银E贷 | 4 | 公积金独立路径可用;网点覆盖和征信仍分别核实。 |
| 58 | 随心智贷 | 3 | 继续使用已填缴存区间,追加核实应指明路径与精度,不能笼统再要缴存时长。 |
| 59 | 集团E贷 | 4 | 年龄和普通月收入可判断,优质单位与在职条件仍须独立核实。 |
| 60 | 网捷贷 | 4 | 保留公积金及社保事实,区分征信更新、缴存方式、路径适用性和经营贷余额。 |
| 61 | 乐分易 | 3 | 公积金时长和当前基数已经提供;应核实平均基数或另一条路径,而非让用户重填时长。 |
| 62 | 快贷 | 2 | 征信缺失合理;发卡机构数、负债比例不能用卡张数或余额直接代替。 |
| 63 | 建易贷 | 3 | 保留缴存、卡额度和贷款笔数,区分缴存次数、贷款机构数与贷款笔数。 |
| 64 | 分期通 | 3 | 社保时长和基数继续使用;本地工作、征信年限及历史用卡另核实。 |
| 65 | 融E借 | 2 | 当前用卡及负债可检查数值子项;银行评分不能套用平台匹配分。 |
| 66 | 闪电贷 | 3 | 公积金独立路径已满足,不能把现单位两年建议改为所有路径门槛。 |
| 67 | 信秒贷 | 3 | 保留年龄与现有信贷信息;不以当前卡使用率替代近半年平均,也不把弹性客群改硬门槛。 |
| 68 | 圆梦金 | 3 | 已填公积金基数可用于“代发或基数”分支;不能把代发固定为必填。 |
| 69 | 惠民贷 | 2 | 公积金独立路径可用;负债分类和征信不能由总额推断。 |
| 70 | 浦闪贷 | 2 | 年龄可判;征信仍待核实,列举客群的组合关系未明确,不自行改成任选。 |
| 71 | 易达金 | 3 | 现有社保、月收入、月还款保留;代发、认定收入、剔除房贷后的月供不是同一口径。 |
| 72 | 邮享贷 | 3 | 公积金基数不是个人缴存额;当前时长区间不能擅自补到满两年。 |
| 73 | 诚易贷 | 3 | 按上班族或生意族选完整路径;不能要求所有人提供营业执照和全部学历资料。 |
| 74 | 光速贷 | 4 | 保留社保、公积金;本单位关系和光大网点另核实,不能笼统要申请地区。 |
| 75 | 润薪贷 | 5 | 认定收入不等于普通月收入,省份不等于九市;既有年龄模式保持。 |
| 76 | 润秒贷 | 2 | 性别不在模型事实中;原配置强制缺性别待核实,本轮不改规则或从身份证推导。 |
| 77 | 徽享薪易贷 | 5 | 社保/公积金独立路径已满足;地区问题区分历史城市与当前省份。 |
| 78 | 鹏城e贷 | 5 | 复用公积金时长和基数,仍核实单位/白名单;广东省不能证明深圳。 |
| 79 | 民易贷 | 4 | 职业、电话和单位字段都保留;模型不可见不代表用户漏填,证件有效期则未采集。 |
| 80 | 职享贷 | 3 | 保留已有社保时长;仍需本单位连续缴存与目标单位资格。 |
## 逐条件对照
本表的pass/unknown均是历史原始结果;“审核结论”描述应遵守的口径,不是重新调用模型后的结果。完整原条件、原始产品文字、历史缺失项、引用、模板及来源段落保存在[配套JSON](信用贷资质与补填口径离线核查-20261015.json)。
### ID56 优客分期
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据;身份证号、所在省份均不能证明当前户籍。 | 历史unknown合理;不得建议重填身份证或所在省份来替代户籍。 |
| rule_qa_2 年龄要求/pass | 身份证派生周岁 | 当前冻结年龄区间无额外缺项。 | 历史pass引用年龄;原始年龄加贷款年限已由现有资质模式拆分,不能把年龄通过扩展为组合条件已核实。 |
| rule_qa_3 工薪或房产准入/unknown | 社保连续缴纳、公积金连续缴纳、社保月基数、公积金月基数、月收入、是否有房、所在省份、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 按所选完整工薪或房产路径核实单位性质、本单位年限、同单位缴存关系、纳税收入或房产位置/持有时长/银行往来。 | 历史unknown可保留;不能把所有可选路径合并成必须补齐的清单,职业与单位名称已采集但不外发。 |
| rule_qa_4 负债及偿债能力要求/unknown | 月收入、贷款月还款金额、欠款总余额、信用卡总额度、信用卡已使用总额度、是否有房 | 资产及房产价值、适用时的共同借款人和大额分期、信用卡历史均值;申请阶段负债核实依据。 | 确认历史missingFields含“本次申请金额”,与当前资质补填边界冲突。unknown保留,不能用本笔月供为零或当前卡使用额替代。 |
### ID57 中银E贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 中行网点准入/unknown | 所在省份 | 对应地区中行网点覆盖;仅省份不能证明实时网点。 | 历史unknown及“中行网点覆盖情况”指向准确。 |
| rule_qa_3 客群准入(满足任意一项)/pass | 公积金连续缴纳 | 已满足公积金分支时,无需其他银行往来或单位分支资料。 | 历史pass正确;不要再次索要代发、按揭或单位性质才能通过该条件。 |
| rule_qa_4 征信要求/unknown | 无可直接证明该项的既有白名单事实 | 当前及历史逾期、审批查询记录。 | 历史unknown合理;不能由自填负债或授权意向推断征信通过。 |
### ID58 随心智贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 进件方案(满足其中一类)/unknown | 公积金连续缴纳、公积金月基数、是否有房、月收入、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 先明确单位及进件路径;普通单位两年门槛超出当前区间可确认范围时才核实精确年限,个税/按揭路径另需相应凭据。 | 历史已有12至24个月区间,却再列“公积金连续缴存时长”,提示过于笼统;单位性质等仍未知,不能据此直接改pass。 |
| rule_qa_2 征信查询要求/unknown | 无可直接证明该项的既有白名单事实 | 对应期间的征信查询次数和统计口径。 | 历史unknown合理,不把贷款笔数当查询次数。 |
| rule_qa_3 征信逾期与分类要求/unknown | 无可直接证明该项的既有白名单事实 | 逾期历史、当前状态、贷款五级分类。 | 历史unknown合理;保留原文各时间窗口。 |
### ID59 集团E贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 客群及在职要求/unknown | 公积金连续缴纳、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 目标单位类别、正式在岗与本单位工作满年关系。 | 历史unknown合理;已填工作单位名称不等于银行认定单位类别,也不应说用户没填单位。 |
| rule_qa_3 收入要求/pass | 月收入 | 本条自填月收入门槛无额外缺项。 | 历史pass引用20000元月收入,不能借银行认定收入的其他规则否定本条普通月收入事实。 |
| rule_qa_4 征信要求/unknown | 无可直接证明该项的既有白名单事实 | 逾期、账户分类、呆账、代偿和处置记录。 | 历史unknown合理;资料完整度不证明这些条件通过。 |
### ID60 网捷贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 进件方案(满足任意一项)/unknown | 公积金连续缴纳、社保连续缴纳、是否有房、欠款总余额、职业类型〔保留不外发〕 | 依选定路径核实征信所载缴存历史/最近更新,或房贷、代发、交易流水;适用路径另核实经营贷余额。 | 历史引用缴存事实而unknown合理;自填时长不是征信更新日期,不要求所有路径齐全。 |
| rule_qa_3 公积金进件资质/unknown | 公积金连续缴纳、公积金月基数、社保连续缴纳、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 仅公积金路径核实单位及非第三方缴存关系;原文例外不能自行扩大。 | 历史已引用基数和时长,仍缺单位/缴存方式合理;本路径未被采用时不能阻断其他独立路径。 |
| rule_qa_4 征信及现有额度要求/unknown | 欠款总余额、信用贷款笔数 | 网捷贷现有额度、经营性贷款余额、逾期、担保和分类记录。 | 历史unknown合理;欠款总余额/贷款笔数不能替代贷款类型与本行额度信息。 |
### ID61 乐分易
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 当前户籍依据。 | 历史unknown合理,所在省份不证明户籍。 |
| rule_qa_2 客群及进件路径/unknown | 公积金连续缴纳、公积金月基数、是否有房、月收入、所在省份 | 公积金路径主要核实平均月缴基数;代发/个税或房产路径只核实所选路径的凭据与状态。 | 确认已有12至24个月区间仍列“公积金连续缴存时长”;平均基数并非当前基数,整条unknown不能因此直接改pass。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
### ID62 快贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 征信及负债要求/unknown | 银行信用卡张数、信用贷款笔数、欠款总余额 | 征信、半年新增贷款及开卡记录、不同发卡机构数、负债比例的分母和口径。 | 历史unknown合理;卡张数不是机构数,但在同一范围内可作数量上界,不能把二者等同或凭此通过整条;低于30%属于经验描述。 |
### ID63 建易贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 单位及公积金要求/unknown | 公积金连续缴纳、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 单位类别、入职年限、近三年具体缴存次数。 | 历史unknown合理;当前连续区间不等于近三年缴存次数。 |
| rule_qa_2 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_3 征信及负债要求/unknown | 银行信用卡张数、信用卡总额度、信用卡已使用总额度、信用贷款笔数 | 发卡及贷款机构集合、查询/逾期、小贷网贷及司法记录。 | 历史unknown合理;当前卡使用率可按现有值核对这一数值子项,但不能代替机构明细或整条征信要求。 |
### ID64 分期通
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 社保及征信记录要求/unknown | 社保连续缴纳、社保月基数、所在省份、是否有房、是否有车 | 本地工作事实、非白户及一年征信记录。 | 历史unknown只写征信记录不证明社保已被忽略;已知社保可判对应子项,有房车等辅助资料不能升级为全部必需。 |
| rule_qa_2 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_3 征信及负债要求/unknown | 银行信用卡张数、信用卡总额度、信用卡已使用总额度、信用贷款笔数、欠款总余额 | 六个月全卡及三个月建行卡历史使用口径、发卡机构、逾期与信用贷款余额。 | 历史unknown合理;当前使用额不是历史值,总欠款余额与信用贷款余额口径也不同。 |
### ID65 融E借
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录。 | 历史unknown合理。 |
| rule_qa_2 征信及负债要求/unknown | 欠款总余额、信用卡总额度、信用卡已使用总额度 | 征信查询、逾期、账户状态、分类;银行负债及评分口径。 | 历史unknown合理;不能因未引用数值事实就断言模型漏用。银行“评分大于75”不等于平台80基础分。 |
### ID66 闪电贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 招行及其他进件路径/pass | 公积金连续缴纳、是否有房、是否有车 | 已有正向公积金事实时,本条无需其他独立路径资料。 | 历史pass正确;“本单位两年”是原文建议,不能倒置为硬门槛。 |
| rule_qa_2 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录。 | 历史unknown合理。 |
| rule_qa_3 征信及负债要求/unknown | 信用卡总额度、信用卡已使用总额度、欠款总余额 | 逾期、查询、账户状态及银行认定负债情况。 | 历史unknown合理;当前使用率可作相应数值核对,“机构一般不超4家”和加分项不能升级为硬门槛。 |
### ID67 信秒贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 征信及负债要求/unknown | 信用卡总额度、信用卡已使用总额度、信用贷款笔数、欠款总余额、是否有房 | 历史卡使用均值、非住房按揭贷款笔数、信用贷款余额、小额贷款类型/机构、查询及逾期。 | 历史unknown合理;贷款笔数/余额与机构数/分类型余额不同,不能拿当前卡比例替代半年平均。 |
| rule_qa_3 非征信白户要求/unknown | 信用贷款笔数、银行信用卡张数 | 银行所认可的征信记录,不能由当前自报卡/贷数量等同核验。 | 历史unknown可保留;有自报卡贷不等于已查到有效征信历史。 |
### ID68 圆梦金
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 客群及收入证明路径/unknown | 公积金月基数、社保连续缴纳、是否有房、所在省份、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 仍须核实单位/学历/房产地点或按揭身份及本单位社保关系;只需一条完整路径。 | 历史同时索要单位性质、学历、代发工资,路径提示有堆叠风险;已有12000元公积金基数,不能把“代发工资”说成必缺。整条unknown仍合理。 |
| rule_qa_2 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录。 | 历史unknown合理。 |
| rule_qa_3 征信及负债要求/unknown | 欠款总余额、信用卡已使用总额度、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 征信查询、逾期及必要时银行认定单位/负债口径。 | 样本贷款余额加已用卡额可核对原文明示合计;缺征信仍unknown,不能凭不展示引用判定已漏算。 |
### ID69 惠民贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 交行及其他进件路径/pass | 公积金连续缴纳、是否有房、是否有车 | 当前公积金分支已完整满足,无需其他独立路径资料。 | 历史pass正确,与既有充分分支保护一致。 |
| rule_qa_2 征信及负债要求/unknown | 信用贷款笔数、欠款总余额、是否有房、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 经营贷、小贷及可剔除负债分类、单位类别、查询和逾期。 | 历史unknown合理;自填总余额不能完成剔除担保/农户贷/抵押车房后的银行负债计算。 |
### ID70 浦闪贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_2 征信及负债要求/unknown | 无可直接证明该项的既有白名单事实 | 征信查询与逾期记录。 | 历史unknown合理。 |
### ID71 易达金
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 单位及收入社保路径/unknown | 社保连续缴纳、月收入、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 适用A或B类的单位/行业、本单位社保;B类另核实代发工资及持续时长,原文替代关系保持。 | 历史unknown合理;普通月收入不能当银行代发工资,不能要求A类也填B类全部资料。 |
| rule_qa_2 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录;原文历史解释不能放宽当前逾期要求。 | 历史unknown合理。 |
| rule_qa_3 征信及负债要求/unknown | 月收入、贷款月还款金额、信用卡总额度、信用卡已使用总额度、是否有房、是否有车、信用贷款笔数 | 剔除房贷后的信贷月供、银行认定收入、信用卡10%计算基准及历史使用口径、征信。 | 历史unknown合理;贷款月还款额不可直接当信贷月供,网贷笔数/持卡机构建议不可升级为硬性补填或不符依据。 |
### ID72 邮享贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 单位及公积金要求/unknown | 公积金连续缴纳、公积金月基数、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 单位类别、单位缴存关系及满两年依据、公积金个人缴存额。 | 历史unknown合理;不因已填基数而判个人缴存额达标,不把模糊年限当已满足。 |
| rule_qa_2 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录。 | 历史unknown合理。 |
| rule_qa_3 征信及负债要求/unknown | 无可直接证明该项的既有白名单事实 | 当前及累计逾期记录。 | 历史unknown合理;不能把“不能有当前逾期”与累计次数合并丢失。 |
### ID73 诚易贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 上班族或生意族进件路径/unknown | 身份证派生周岁、社保连续缴纳、是否有房、所在省份、职业类型〔保留不外发〕 | 依适用分支核实户籍/本地房产/学历,或执照年限/经营行业;不索要另一主体分支资料。 | 历史缺失同时列户籍、学历、营业执照,有路径混列风险;unknown合理,不能将这些当所有用户必填。 |
| rule_qa_2 当前逾期要求/unknown | 无可直接证明该项的既有白名单事实 | 当前逾期记录。 | 历史unknown合理。 |
| rule_qa_3 征信及负债要求/unknown | 无可直接证明该项的既有白名单事实 | 各窗口逾期、带审批标记的查询记录。 | 历史unknown合理;原文未明确负债比和机构数门槛,不能新增。 |
### ID74 光速贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 光大往来及收入进件路径/unknown | 公积金连续缴纳、社保连续缴纳、是否有房、贷款月还款金额、工作单位〔保留不外发〕、职业类型〔保留不外发〕 | 本单位连续缴存关系,或适用的光大贷款/个税/代发/资产往来。 | 历史unknown合理,但缺失只列光大往来、个税、代发,未精确指出已提供缴存路径还缺本单位关系,有引导用户改走其他路径风险。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致;裸年龄填值已由前轮规范。 |
| rule_qa_4 地区要求/unknown | 所在省份 | 具体受理地区及光大网点覆盖依据;省份不足以证明。 | 确认历史仅写“申请地区”过于笼统;应突出网点覆盖核实,不能凭省份或历史深圳事实判网点存在。 |
### ID75 润薪贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 受薪客群及认定收入要求/unknown | 月收入、公积金月基数、社保月基数、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 目标单位/银行白名单及月均认定收入;第五类的基数条件另按所属客群判断。 | 历史“职业行业”属于已采集但不外发信息;只改提示不能让模型读取它。月收入和基数保留但不替代认定收入。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 当前资质年龄区间无额外缺项。 | 历史pass是23至60周岁,不代表原始年龄加贷款期限条件已确认。 |
| rule_qa_4 征信及负债要求/unknown | 欠款总余额、所在省份 | 银行征信评分、负债比口径、查询/逾期及具体准入城市。 | 历史unknown合理;银行600分不是平台匹配分,负债余额不是75%负债比;本条也包含地区,不能只审独立地区条。 |
| rule_qa_5 地区要求/unknown | 所在省份 | 当前仅广东省时需核实九市范围;若历史已有可信深圳城市事实,不应再索要同义地点。 | 确认历史有440300仍列“申请地区”;现已增加冻结名称提示但未经新模型验收。新省份输入保持unknown不算复发。 |
### ID76 润秒贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 分性别年龄要求/unknown | 身份证派生周岁 | 原冻结条件明示需要性别依据。 | 历史36岁虽落在男女区间交集,但原条件附缺性别保持待核实的限制,本轮不能靠提示自行删除;保留unknown,不新增外发性别。 |
### ID77 徽享薪易贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 社保公积金或优质行业路径/pass | 社保连续缴纳、公积金连续缴纳 | 已提供正向缴存事实时,无需其他优质行业分支资料。 | 历史pass正确;社保半年建议不变成全部客群硬门槛。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_4 征信及负债要求/unknown | 信用贷款笔数、所在省份 | 非银小贷种类/笔数、查询及逾期;本条原文也含城市名单。 | 历史unknown合理;信用贷款笔数不能替代非银小贷笔数,不能漏掉内嵌地区范围。 |
| rule_qa_5 地区要求/unknown | 所在省份 | 具体准入城市;不能把整省都当名单内城市。 | 确认历史已有深圳城市代码仍列“所在城市”;当前只有省份时保持unknown合理。 |
### ID78 鹏城e贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 单位与公积金组合路径/unknown | 公积金连续缴纳、公积金月基数、职业类型〔保留不外发〕、工作单位〔保留不外发〕 | 单位类别或普通单位银行白名单。 | 历史unknown合理;已有时长及12000元基数可用于两档数值子项,但不能把单位/白名单当已知。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
| rule_qa_4 征信及负债要求/unknown | 欠款总余额、所在省份 | 查询、逾期及深圳准入依据。 | 历史unknown合理;原文明示100万是建议,不能升级为硬门槛;本条内嵌地区仍保留。 |
| rule_qa_5 地区要求/unknown | 所在省份 | 当前仅省份时需核实深圳市。 | 确认历史深圳城市事实未用而再索要所在城市;不能据历史问题让新广东省输入自动pass。 |
### ID79 民易贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 受薪身份及进件路径/unknown | 公积金连续缴纳、是否有房、贷款月还款金额、职业类型〔保留不外发〕、工作单位〔保留不外发〕、手机号〔保留不外发〕、身份证号〔保留不外发〕 | 受薪且非法人/股东、适用单位白名单/缴存年限或银行往来;另核实证件剩余有效期及手机号段资格。 | 历史列“职业、手机号段”不能等同用户没填;电话和职业按隐私白名单不外发。各路径不能混为全必填。 |
| rule_qa_2 年龄要求/pass | 身份证派生周岁 | 当前资质年龄区间无额外缺项。 | 历史pass是22至60周岁,不代表原年龄加期限组合已确认。 |
| rule_qa_3 征信及负债要求/unknown | 是否有房、信用贷款笔数、欠款总余额 | 征信查询/逾期、小额贷款类型及信用负债范围。 | 当前有房和2笔信贷可核对对应笔数子项;不能用总余额替代信用负债,整条unknown仍合理。 |
| rule_qa_4 身份、职业及申请凭证要求/unknown | 职业类型〔保留不外发〕、工作单位〔保留不外发〕、手机号〔保留不外发〕、身份证号〔保留不外发〕 | 受薪/法人股东关系、职业禁入类别、证件到期日及手机号段资格。 | 职业和电话已采集但模型不可见;身份证号不含有效期,不能推算剩余天数,也不外发完整证件或电话。 |
### ID80 职享贷
| 条件/历史结果 | 可复用的现有内容 | 仍需核实或明确的范围 | 历史提示与审核结论 |
| --- | --- | --- | --- |
| rule_qa_1 户籍要求/unknown | 无可直接证明该项的既有白名单事实 | 大陆户籍依据。 | 历史unknown合理。 |
| rule_qa_2 单位及连续社保要求/unknown | 社保连续缴纳、职业类型〔保留不外发〕、工作单位〔保留不外发〕、所在省份 | 本单位连续社保满一年、具体单位/岗位/控股或深圳分支关系。 | 历史要求“本单位连续缴存社保时长”指向额外关系,区别于已填总体连续时长,不应简单删除;也不能让用户重填职业和单位名称。 |
| rule_qa_3 年龄要求/pass | 身份证派生周岁 | 无。 | 历史pass与年龄事实一致。 |
## 仅提示词方案的覆盖与剩余边界
已完成的本地提示词覆盖申请字段不作补填、保留已有事实、与/或及适用路径、当前值不冒充历史值/完整银行公式、主报告完整度边界。此前已有独立充分分支、年龄区间和地区事实提示。本轮只做清单,不继续修改提示词。
只调整提示词不能让模型知道未外发的职业、单位或电话,也不能补齐征信、证件到期日、历史均值、银行白名单或网点数据。故“模型尚无核实依据”应与“用户没填”区别理解;不通过扩大隐私白名单或增加用户必填来消除unknown。
若下一轮仍保持“只调提示词”,可在同一范围内优先补充三类措辞样例:已有时长应指出具体精度/单位关系;模型不可见不等于用户漏填;只列适用路径的缺口,不能将可选分支资料堆成必填。本次未实施这些补充,也未验证新模型遵循率。
地区方面,ID75/77/78的名单还出现在部分征信及负债复合条件内,后续不能只检查独立地区行。当前只填广东省时仍不足以确认深圳等城市;网点条件也不能借城市名称直接通过。保持省份一级选择。
ID56/75/79的原始年龄加期限文本完整保留,已有资质模式只编译年龄区间;本轮没有再次变更或删掉原文。其他产品的额度、期限和利率资料仍是产品说明,不应被当成用户待补字段。
共有12条独立理由条件、涉及6款产品:快贷、融E借、信秒贷、浦闪贷、润秒贷的10条涉及5组尚未完全明确的客群组合;另2条是惠民贷已采用征信通用口径的独立表述。它们不是82条硬条件中的新增硬门槛;缺明确关系时不擅自转换全AND/全OR,也不以未展示理由宣称整款银行准入已覆盖。
## 后续顺序
1. 本清单作为后续提示词复核依据;保留当前本地修改及全部用户填写项,线上继续不更新。
2. 如继续优化,只在用户选择的提示词范围内处理上述措辞缺口;不改产品条件、事实白名单、评分或页面。
3. 将来允许真实复测时,先定向检查申请字段、已填时长、完整独立路径、历史城市与新省份两类输入,再决定是否安排完整收费评估。当前暂缓,不运行历史run.php或execution.started入口。
## 证据与验证
- 结构化清单完整覆盖25款、82条条件、100条模板,逐项保留历史条件/版本/摘要/结果/引用/缺失项;没有截断或重写原始证据。
- 逐项与历史context、calls及report核对集合、内容和绑定;本地源材料段落及代码摘要纳入来源清单。
- 比较本轮前后255个后端运行PHP和45个相关小程序运行文件,以及9份原始材料/历史证据的SHA256,确认本轮没有变更它们。
- 仅文档与离线结构检查;不运行应用、数据库、worker、外部请求或模型,不能称为模型语义复测或真机验收。
证据目录:D:/AAdesk/202609/airz-backups/product-profile-audit-20261015。历史证据:D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r5。新提示词本地证据:D:/AAdesk/202609/airz-backups/profile-prompt-20261015。
@@ -0,0 +1,43 @@
# 公积金完整可选路径修复(本地)
2026-10-16。承接用户“继续下一步”,本轮只处理上轮真实复测确认的产品77“徽享薪易贷”路径漏判。继续先不发布、跳过真机,不新增真实模型调用。
## 问题与修正
`rule_qa_2` 名称为“社保公积金或优质行业路径”,原文:
> 符合其一:社保客群(建议本单位满半年以上);公积金客群(缴存时间没有具体要求);优质行业客户(国企、公务员、事业单位)。社保半年是建议,不设为所有客群门槛。
原备注明确只需满足一条完整路径。上轮样本已填公积金12–24个月,模型仍给出 `unknown`、空引用,因此少了一条可用推荐理由。原提取器受尾句白名单及其他分支括号保护限制,没有把本条独立公积金分支放进 `sufficientAlternatives`;不能由此推断模型内部漏判原因。
本轮沿用现有充分分支提示与严格校验,只为这一已确认表述补足请求依据。必须同时匹配完整条件名称、硬条件、`eq`、空 `valueMax`、完整三分支原文及已知不增加门槛的备注;已确认的末尾解释和备注可为空。不按产品编号特判,不放宽通用括号或任意尾句处理。
五种明确有公积金的合法时长枚举都能证明该无额外时长要求的分支。请求仍保留完整产品原文;模型必须判 `pass` 并引用 `fact:housingFund`,否则严格拒绝,不在服务端改写模型结果。无公积金、未填写或非法枚举不据此强制通过。额外时长、单位、基数、共同门槛、否定或“且”关系不进入新增分支。
## 适用范围与兼容
新个人资料参考评估通过 `capture` 显式冻结 `optionalPathPolicy=complete-optional-paths-v1`,要求个人、资料模式、参考建议、grounding及分批策略同时适用。`freeze` 新增末参默认关闭,显式重建旧目录时保留原解释;企业、需求模式及未启用相关策略的目录不附加此标记。
新策略参与目录及执行摘要,检查点随新冻结身份绑定;未知标记或不兼容策略在计划阶段拒绝,不能用全量检查点绕过。分批理由的内部中间副本同步移除 grounding 和新标记,以保留尚未调用条件的占位;真实响应及最终报告仍按原目录严格校验。
已保存报告、旧无标记快照及检查点保持原语义,上轮真实77的 `unknown` 仍可按原目录回放。本轮不回写旧报告、不改产品原文、不删表单项、不改变用户输入、零金额和地区口径。
## 验证与证据
本轮运行改动为 `ProductAiConditions.php`、`ProductMatching.php`、`ProductAiBatches.php` 三个后端文件;新增纯内存专项 `tests/product_optional_fund_paths.php`。专项先复现缺失充分分支的失败,再验证修正及反例。相关既有条件、模板、分批、年龄、地区、参考建议、零金额、匹配和短编号流程均作内存回归,不启动默认应用或读取生产数据库配置。
最终专项162项通过,14个既有内存入口全部通过;套件之间有包含关系,不累计重复断言。覆盖非法公积金枚举、冻结参数类型、原真实77条件摘要、部分/全部检查点恢复和新旧策略隔离。三个运行文件及新测试PHP语法通过,独立精准增量审查未发现实质缺陷。
保存的6批真实响应离线重放共102项检查通过:旧目录原6响应及6个持久化检查点仍可恢复,语义结果与原报告一致。按新目录身份显式重绑短ref后,不改语义的原响应在第5批77条件处被拒,前4检查点保留;另对77单条独立验证,确认拒绝来自该漏判。这是对错误输出的拒绝验证,没有自动重试或真实请求。
另构造明确标记的反事实夹具,仅将77条件改为 `pass`/公积金引用、启用对应推荐模板,并同步关联注意模板的 `conditionOutcome`(注意模板仍不适用、不新增文字)。这三个一致性改动后的6批可完整合并,并从6个新检查点零回调恢复;66条件、100模板、25参考候选完整。此结果来自人工编辑测试夹具,绝非新的真实模型通过。原响应、原正文和旧收费入口未改写。首次工具比较曾受JSON对象键序影响,改用保留值类型及列表顺序的规范摘要后重放通过,没有放宽业务校验。
本轮259个后端运行PHP中仅上述3个变化,其他256个保持;77个前端运行文件及59项既有真实实测证据文件摘要保持。未建立数据库测试环境,未复活旧隔离服务或常驻worker。
本轮证据:`D:/AAdesk/202609/airz-backups/optional-fund-path-20261016`。最终专项、原响应离线重放、源码摘要和精确差异以该目录的 `verification.json`、日志及 `implementation.diff` 为准。
## 尚未覆盖
本轮没有新真实模型调用、实际数据库任务保存回读或真机验收,也没有发布、提交或推送。离线合成的正确响应只能验证服务端流程,不等于真实模型已遵守新提示;若未来模型仍违背充分分支约束,评估仍可能被严格拒绝。生产版本保持 `20261016-report-diagnostics-r1`。
下一步可整理累计发布候选及验收范围。若继续验证新提示的实际效果,应使用独立新目录和防重入口;旧实测的收费入口及原响应不能重跑或覆盖。
@@ -0,0 +1,74 @@
# 公积金规则实测与生产基线只读核对
2026-10-19。用户同意按建议顺序继续:先验证最新公积金规则,再只读核对生产基线;继续先不发布、跳过真机。本轮没有修改前后端运行代码、固定 r1/r2 清单或原始实测证据,没有提交、推送或上传。
## 单批续跑补充验收(2026-10-19)
用户随后明确回复“允许”,授权最多新增1次真实模型调用,只补原超时第6批。已在新的本机隔离 MySQL/Redis `127.0.0.1:52378/52379` 完成;没有重跑前5批、扩大90秒超时、修改模型或运行代码。以下结论补齐前次“未形成最终报告”的缺口,前次失败记录和原始证据保留。
- 原输入、提交时间、版本编号及冻结输入/执行策略保持,原5个canonical检查点完整复用;零调用预检校验检查点绑定,并确认第6批请求与原超时请求语义内容一致:9条模型条件、12条模板、输出预算6640。
- 唯一新增调用经 `deepseek-v4-flash-0731` 在8.743秒返回;任务总9.671秒,8376输入/1106输出token,没有自动重试。前5批和本次已返回用量合计57136/8530 token,原超时调用用量仍未知,人民币账单未查。
- 实际执行原 `AnalysisTasks::run`,任务 `succeeded`/`completed`,完整保存66条模型条件、100条模板、25产品匹配及6检查点。输入版本快照中的报告/匹配与会话保存值一致;所属用户完整回读一致,其他用户读取返回404。6检查点再次零调用重放,条件及模板与保存报告一致,匹配结果按冻结规则复算一致。此为新隔离排队任务消费原检查点的验证,未修改当前重试额度,也不声称验收了生产重试接口或常驻worker。
- 产品77“徽享薪易贷”公积金条件保留第5批真实 `pass`/`fact:housingFund`;推荐模板填入“公积金”并实际保存为推荐理由,注意模板保持不适用。相较更早完整实测,66条件中仅该目标由 `unknown` 变为 `pass`,其他65条三态保持;模板自由措辞存在变化,不宣称全部正文逐字一致。
- 最终5条模型条件pass、61条unknown,17条模板适用;25款仍为80分参考候选,不能等同全部细项通过或获批。新增第6批的复合客群、户籍、精确城市及征信细项资料不足,保持unknown,没有误把已填公积金当作完整组合路径已满足;3条年龄推荐正确引用36岁。
- 16条本地年龄判断均pass;四关联金额原null及原表单/最新资料保持,状态仍为not_applicable,完整度100%、缺填及无效字段为空。六批原始主正文和最终摘要未见模型品牌/自填核验措辞,没有新增补填建议;这不保证任意未来自由正文。
新证据目录:`D:/AAdesk/202609/airz-backups/optional-fund-path-resume-20261019-r1`,仅当前Windows用户与SYSTEM可访问。`preflight.json`、`calls.json`、`context.json`、`report.json`、`saved-snapshot.json`、`report-readback.json`、`checkpoint-replay.json`、`semantic-review.json`、`verification.json`及`final-receipt.json`分别记录预检、真实调用、保存回读、恢复、语义与保全核对。新入口及逐调用排他标记保留,禁止重跑。
29张合成表清空、6触发器保留、Redis DB15为空;核对PID/创建时间/程序/命令行后,仅正常关闭本轮两服务,52378/52379释放,数据目录保留。259后端PHP、77前端UTS/UVue和原7个证据根保持;r2只读核验再次通过(3包235成员、29PHP语法、220前端别名导入)。本轮仓库仅本说明和交接文档变化,没有生产访问、发布、真机、提交或推送。
## 前次实测结论(第6批超时)
最新公积金分支的真实语义验证通过,整份评估因第六批超时未通过。两项结论必须分别记录,不能把前五批成功当作最终报告保存成功。
使用新建本机隔离 MySQL/Redis `127.0.0.1:52178/52179`、同一份合成个人资料和保存回执重建的 25 款产品目录;不是最新正式目录导出。新入口有一次执行及逐次调用排他标记,最多 6 次真实调用、失败即停、没有自动重试。模型为 `deepseek-v4-flash-0731`,经既有 TokenHub 接口调用。姓名、完整身份证、手机号未外发,职业类型与合成单位名称按已有范围发送。
| 项目 | 本次结果 |
| --- | --- |
| 目标条件 | 产品77“徽享薪易贷”/`rule_qa_2`,第5批真实返回 `pass`,引用 `fact:housingFund`,没有补填要求 |
| 对应理由 | `template_qa_1` 启用并引用公积金;`template_qa_3` 保持不适用,关联状态为 `pass` |
| 历史对照 | 已完成57条条件中,只有目标条件从历史 `unknown` 变为 `pass`;其他56条三态结果保持 |
| 已完成内容 | 前5批严格解析及持久化检查点成功:57条模型条件、88条模板 |
| 最后一批 | 第6次请求在既有90秒上限处返回 `AI_TIMEOUT`,尚缺9条模型条件及12条模板 |
| 整体状态 | `failed`,`hasReport=false`;没有发布半份报告,也未验收最终保存/所属用户回读 |
| 恢复检查 | 原5个真实检查点零调用重放通过,下一次回调准确停在第6批,没有合成第6批答案 |
| 耗时与用量 | 总147.643秒,API累计146.304秒;已返回48760输入/7424输出token;超时请求用量未知,人民币账单未查 |
前五批原始正文的建议和风险数组为空,没有索要未采集细项,没有模型品牌或自填核验类措辞;这不是尚未生成的最终报告结论。目标新充分分支随 `optionalPathPolicy=complete-optional-paths-v1` 冻结,原文、五个合法公积金枚举和实际输入的12至24个月保持。原始响应与canonical检查点分别证明模型判定及服务端接受结果,不是人工把unknown改为pass。
通用证据46项、目标及失败状态19项分别通过,有交叉内容,不累计为65项独立业务场景。现有任务失败处理会把首条assistant任务锚点日志同步标为整体失败,同时保留首批用量;不能按日志status数量推断只有4次成功。逐次外呼记录、前5个canonical检查点及离线重放共同证明实际5批成功、1批超时。
直接失败边界已定位到第六次真实外呼的90秒超时,尚不能判断供应商或网络内部原因。本轮没有扩大超时、修改模型/校验/额度,没有追加收费重试。
## 生产基线核对
通过已保存的专用面板连接进行一次只读检查:服务器内存中执行摘要/文件读取及服务状态查询,没有上传脚本、初始化业务App、读取生产数据库、创建评估、调用生产worker或修改服务。
- 当前资源版本仍为 `20261016-report-diagnostics-r1`,PHP 7.4.33。
- 上次发布137个已记录路径全部逐字节匹配;环境文件摘要保持。
- r2 后端17路径相对现场为14个既有文件有差异、3个新增路径不存在。
- `ProfileZeroBalances.php`、`ProductAgeRules.php`、`ProductAiBindings.php` 三处新增路径当前均不存在。
- 读取此前缺少旧摘要的 `AssessmentReadiness.php` 和 `admin/lang/zh-cn/user/info.php`,下载原字节并重算SHA256;已有12文件旧包也逐份匹配现场摘要。形成14文件补充旧源码包,14项PHP语法与ZIP集合/CRC/字节校验通过。
- `airz-analysis`、`php-fpm-74`、`redis` 均active,未重载/重启。面板已退出,专用浏览器关闭,临时登录与命令脚本清除。
上述现场缺口在本次时间点已补齐。补充旧源码包不是数据库、环境及服务状态的完整生产备份,也不构成发布授权;将来发布仍须重新现场预检、完整备份及核对任务状态。固定 r2 的原“待核对”字段保留冻结时含义,本轮以单独补充证据关联,不覆盖原清单。
## 证据与清理
证据目录:`D:/AAdesk/202609/airz-backups/optional-fund-path-live-20261019-r1`,仅当前Windows用户与SYSTEM可访问。主要入口及文件:
- `verification.json`:本轮汇总及证据摘要。
- `calls.json`、`context.json`、`result.json`:真实原始调用、5检查点及失败结果。
- `optional-fund-verification.json`、`checkpoint-replay-partial.json`:目标分支及零调用续点验证。
- `production-readonly/baseline-readonly.json`、`supplemental-baseline-verification.json`、`observed-backend-old-files-14.zip`:现场摘要与14文件旧源码补充材料。
- `cleanup.json`、`production-readonly/panel-cleanup.json`:本机隔离服务及专用面板清理。
本机29张合成表清空、6个原触发器保留、Redis DB15为空;核对进程创建时间/程序/命令行及所属端口后正常停止本轮MySQL/Redis,52178/52179释放,证据与数据目录保留。259个后端运行PHP及77个前端UTS/UVue保持;全仓非忽略文件核对在文档更新前前端517/后端4119均无漂移。原真实实测、离线修复、固定r1/r2六个证据根保持。本轮仓库增量仅本说明和交接文档。
同日浏览器验收独立证据为 `D:/AAdesk/202609/airz-backups/report-ui-qa-20261019/verification.json`:123主检查+8滚动补查通过,三个尺寸、合成接口、无真实模型/业务写入;不能替代本次真实评估或微信真机。
## 尚待处理
原建议的“复用5检查点,仅补第6批并验证保存回读”已按后续授权完成,见上方补充验收。两次真实入口及原始证据均保留,不能重跑或用历史响应覆盖本次结果。当前继续保留未发布的r2候选;不再需要为本次完整评估重复调用模型。
微信真机仍跳过,已发行小程序源码/上传包摘要仍未知。后续若明确授权发布,才进行新备份、整体后端发布及小程序编译发行;当前不发布。
+42
View File
@@ -0,0 +1,42 @@
# 匹配详情提示与展示口径
2026-10-16。用户要求先不发布,移除匹配详情中“用户自填、未经核验”等来源说明,检查年龄、地区已填写却仍提示完善及其他同类问题;随后确认“未参与参考筛选、无需补填”的项目不逐条展示。本轮仅修改本地展示,不删填写项,不修改已保存报告,不重新运行评估或调用真实模型。
## 已确认的问题与处理
| 问题 | 证据与处理边界 |
| --- | --- |
| 推荐理由和依据出现来源说明 | 标准规则理由带固定“根据自填资料判断”,前端又给资料附加“用户自填,未经核验”。清理已知固定展示措辞,保留条件正文及实际数值。 |
| 未参与项全部列为橙色注意事项 | 后端将部分 unknown 生成 `AI_CONDITION_NOT_ASSESSED`,产品资料未参与项生成 `PROFILE_DETAIL_NOT_ASSESSED`。详情和同源对比不再逐条展示经匹配证据确认的这两类项目;真实不符、缺填、非法值、未完成与配置异常继续显示。 |
| 已填写省份却显示“所在城市:未提供” | 截图对应的是产品适用地区未参与筛选,其证据来源为产品快照、actual 为空;旧前端把它当成用户城市字段展示。产品地区配置未知不能推断用户没填地区,省份也不能推断城市、户籍或具体网点。 |
| 年龄、职业、单位等同类提示 | 通用 unknown 文案不能证明用户没有提供资料。复合条件可以引用已填事实,但仍缺少整条条件的判断依据;这些未参与项按上述展示口径处理,不把 unknown 改成 pass 或增加匹配度。 |
| 标题和副标题重复、展开后再次重复依据 | 没有已保存逐条解释时,依据仅在“查看依据”中显示;有解释时保留解释副标题。计数按实际展示项目计算。 |
| 空态、页尾和对比暗示必须继续补填 | 使用简短参考说明,清理固定核验来源说明;保留真实条件、产品申请材料与审批结果之间的区别。 |
展示变换仅作用于前端视图,历史响应/报告 JSON、原判断依据、eligibility、score、版本、排序、冻结目录均保留。产品来源和真实要求核验依据的条件仍可查看。固定旧措辞兼容不等于过滤任意 AI 自由表达,也不按中文“未知”“核验”等词删除整条结果。
## 年龄专项诊断
已保存的另一份合成实测样本共 25 款产品、82 条条件、100 条模板,7 批请求均含年龄 36。17 条独立年龄条件中,16 条普通年龄条件通过且引用年龄;1 条分性别条件因未采集性别保持 unknown。优客分期在该样本中年龄条件为 pass,只有 4 条其他注意项;用户截图有 5 条且包含年龄,不能将两者视为同一次评估。
纯内存反事实已证明:即使年龄事实存在,现有校验仍可能接受 AI 对纯年龄区间返回 unknown,再套用“未采集或精度不足”的统一提示。另有输入边界差异:提交规范化只检查身份证字符模式,而年龄派生还检查校验位、出生日期及冻结业务日;号码能提交不等于一定能派生年龄。
本轮未取得截图对应冻结资料和模型响应,因此不能确认该次是年龄未派生、模型未正确使用,还是其他旧冻结差异。本轮修复误导展示,没有新增服务端确定性年龄判定、放宽身份证校验或重算历史结论。后续用户授权的本地确定性年龄规则已另行完成,见[年龄区间确定性判断](年龄区间确定性判断-20261016.md):新冻结策略下明确独立年龄由后端计算,旧目录/检查点保持原行为;该增量仍未发布。
## 同类检查结果
全 25 款已保存样本的 61 条 unknown 中,24 条明确引用已填事实,37 条没有引用。unknown 契约允许没有引用,不能据此直接认定漏传或漏用。职业/房产可选路径、收入与负债复合条件、连续缴存时长、征信、户籍及产品地区等均存在“整条尚未确认”与“用户漏填”混淆风险;未采集的银行细项继续按现有参考建议口径处理,不要求补填,不改银行原条件。
## 验证与发布边界
本轮验证和增量差异保存在 `D:/AAdesk/202609/airz-backups/match-detail-copy-20261016`。其中 `before/frontend` 为 4 个运行文件精确字节快照,`backend/age-fact-audit.json` 与同名 Markdown 为全 25 款只读矩阵和年龄边界证据,`integrity.json` 对照上一轮零金额修复后的源码摘要。
实际运行增量为 3 个前端文件:`services/matches.uts`、`product/product.uvue` 和 `compare/compare.uvue`,均在 `packages/finance/` 下;对比服务 `services/compare.uts` 与本轮前快照完全一致。新增 `scripts/test-match-detail-copy.cjs`。所有原始填写内容、上一轮零金额处理与已有未提交改动均保留。
- 新增 12 个展示回归场景通过:固定未参与项、证据/来源/结果冲突、真实缺填和不符、旧解释兼容、年龄/省份/职业/单位标签、原响应不变、条数和依据展开等;已保留先红后绿日志。
- 既有匹配预览 20、产品展示 19、匹配路由 33 项及零金额 15 场景分别通过,未合并累计重叠覆盖。路由首次在编译中读取暂时不存在的产物失败,原日志保留;最终编译后重跑通过。
- HBuilderX 5.26 最终微信 29 页本地编译成功。未运行微信真机或浏览器视觉测试,不能以编译和页面绑定检查代替设备验收。
- 只读 PHP 重放再次通过 25 款/82 条条件/100 条模板、冻结生日边界及“已知年龄仍接受 unknown”的反事实。入口 `backend/replay-age-audit.php` 不初始化默认应用,阻断数据库与模型;运行源码及旧样本前后摘要一致。
- 独立增量审查没有发现剩余明确缺陷。对照上一轮零金额修复后的摘要,391 个后端基线文件、74 个其他前端基线文件均保持;3 个修改运行文件的原行尾保持,UTF-8 无 BOM,差异空白检查通过。
本轮不访问生产、不调用真实模型、不重试正式评估、不发布后端或小程序,不提交或推送。上一轮零贷款/零信用卡本地修复保留;之后若配套发布,仍须后端先、小程序后。当前生产仍为 `20261016-report-diagnostics-r1`,微信真机及截图原评估的年龄事实尚未验证。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
{
"preparedAt": "2026-10-15T15:23:40.088522+08:00",
"candidateId": "rc-20261015-assessment-region-batch-r1",
"status": "complete_offline_preparation",
"documents": {
"D:\\WorkSpace\\cyun\\ai融资\\docs\\发布候选\\assessment-20261015-r1\\manifest.json": "7d088a06bd7a5538e4cd31854ed53d95823c35e89f2dc3d846e038335c607238",
"D:\\WorkSpace\\cyun\\ai融资\\docs\\发布候选\\assessment-20261015-r1\\verification.json": "38caf326e4691a539ae937203119b73010cb7517aa1a083f89ad54c46974e0dc",
"D:\\WorkSpace\\cyun\\ai融资\\docs\\评估配套发布准备-20261015.md": "75327a519c692cef34f6bd2f43e175628e311dc883083f1ad18dad68e6d2165b",
"D:\\WorkSpace\\cyun\\ai融资\\docs\\superpowers\\plans\\2026-10-15-release-preparation.md": "561e6bcdd8639c099e5825b6fcc39423855e534776d073352c1a7860066f8bca",
"D:\\AAdesk\\202609\\airz\\database\\assessment-release-preparation-20261015.md": "da7a7f574577c18c8f7f36e06948dea6c8b60e230fed57d7cf89b0a0cdcea9a1",
"D:\\WorkSpace\\cyun\\ai融资\\docs\\新会话交接.md": "0730702bc2f0db34b7de2eb6bac0b182e672f8a3b032882c869d294a54f218e4"
},
"packages": [
{
"path": "D:\\AAdesk\\202609\\airz-backups\\release-preparation-20261015\\backend-runtime.zip",
"files": 9,
"bytes": 53591,
"sha256": "c19f103cd1de4e009901e27c67a554fc18eafb842969800b6365527d1e8c2935"
},
{
"path": "D:\\AAdesk\\202609\\airz-backups\\release-preparation-20261015\\frontend-source.zip",
"files": 206,
"bytes": 14915744,
"sha256": "88b7344fbdb451b00e68c4d85f5070b5b9b26dd5bb43733a9841eaf96f8cadea"
},
{
"path": "D:\\AAdesk\\202609\\airz-backups\\release-preparation-20261015\\backend-recorded-baseline.zip",
"files": 7,
"bytes": 42558,
"sha256": "549a328fe1c55a9fc1d0b39a954be448be4991e134fdd7a448bdc1a70838e39d"
}
],
"pageRegistrationCount": 29,
"previousHandoffPreserved": true,
"runtimeSourcesUnchanged": true,
"databaseAccess": false,
"networkAccess": false,
"modelCalls": 0,
"servicesStarted": false,
"deployed": false,
"miniappReleased": false,
"committed": false,
"next": "Explicit backend deployment authorization, then fresh production preflight according to the operation sheet; user publishes miniapp separately."
}
@@ -0,0 +1,71 @@
{
"verifiedAt": "2026-10-15T15:25:15.952256+08:00",
"candidateId": "rc-20261015-assessment-region-batch-r1",
"status": "offline_candidate_verified_not_deployed",
"checks": [
{
"name": "backend sources match candidate",
"files": 9
},
{
"name": "frontend sources match candidate",
"files": 206
},
{
"name": "prior persistence runtime source binding",
"files": 255
},
{
"name": "historical receipts preserved",
"files": 8
},
{
"name": "frontend alias import dependencies included",
"imports": 220
},
{
"name": "package member paths, bytes and CRC",
"package": "backend-runtime.zip",
"files": 9
},
{
"name": "package member paths, bytes and CRC",
"package": "frontend-source.zip",
"files": 206
},
{
"name": "package member paths, bytes and CRC",
"package": "backend-recorded-baseline.zip",
"files": 7
},
{
"name": "candidate and recovered baseline PHP syntax",
"files": 16
},
{
"name": "generated province data consistency",
"summary": "34 province choices; legacy city lookup retained; consistent"
},
{
"name": "backend diff whitespace check",
"exitCode": 0
},
{
"name": "frontend diff whitespace check",
"exitCode": 0
}
],
"phpSyntaxFiles": 16,
"backendPayloadFiles": 9,
"frontendChangedFiles": 13,
"frontendSourceFiles": 206,
"rollbackBaselineFiles": 7,
"priorPersistenceRuntimeFilesUnchanged": 255,
"productionAccess": false,
"paidModelCalls": 0,
"deployed": false,
"newBuildProduced": false,
"miniappPublished": false,
"realModelSemanticsRetested": false,
"warning": "This validates local source/packages and recorded receipts; current production and actual uploaded miniapp remain unverified."
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,221 @@
{
"candidateId": "rc-20261016-assessment-fixes-r1",
"verifiedAt": "2026-10-16T17:56:04.151318+08:00",
"status": "offline_candidate_verified_not_deployed",
"manifestSha256": "2bd41f8ad4a873a99af55dfa1642daa2bea77c175bd44657a47f1ef393d90f79",
"packages": 3,
"packageMembers": 235,
"backendPayloadFiles": 17,
"frontendSourceFiles": 206,
"frontendLocalGitRuntimeDelta": 15,
"frontendVsPreviousPreparedCandidate": 9,
"rollbackRecordedFiles": 12,
"phpSyntaxFiles": 29,
"phpSyntaxChecks": [
{
"package": "backend-runtime.zip",
"path": "application/admin/lang/zh-cn/user/info.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AiReportDiagnostics.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentExecution.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentReadiness.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentRecords.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/FinancingGrade.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAgeRules.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiBatches.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiBindings.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiConditions.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiTemplates.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductMatching.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProfileZeroBalances.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ReportPdf.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/extra/financing_grade.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/extra/readiness.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AiReportDiagnostics.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentExecution.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentRecords.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/FinancingGrade.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiBatches.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiConditions.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiTemplates.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductMatching.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ReportPdf.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/extra/financing_grade.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/extra/readiness.php",
"exitCode": 0
}
],
"frontendAliasImports": 220,
"frontendMemorySuites": [
{
"suite": "test-zero-balances.cjs",
"exitCode": 0,
"output": "PASS zero counts make only empty related amounts not applicable without changing input\nPASS missing counts and positive counts never infer zero balances\nPASS all accepted zero-count spellings share the same non-applicable behavior\nPASS legacy credit-loan zero never receives the new loan scope during restoration\nPASS zero count and positive related amounts are rejected without overwriting\nPASS invalid amounts cannot grant a zero-count exemption to sibling empty amounts\nPASS entered zero money stays entered and legacy loan scope is not inferred from zero values\nPASS new submission carries all-loan scope while empty monetary fields remain null\nPASS serialization preserves entered amounts and never fabricates scope for legacy callers\nPASS edit restoration preserves old and new scope and every existing amount\nPASS archive labels retain historical credit-loan meaning and name new scope correctly\nPASS historical count requires an explicit page confirmation without clearing any values\nPASS page confirmation enables blank balances for old zero counts and remembers scope\nPASS confirmation page refuses legacy scope and displays submitted scope without reinterpreting\nPASS confirmation distinguishes exempt empty amounts from entered zero amounts\nPASS: 15 passed, 0 failed zero-balance UTS/page scenarios; modelCalls=0 businessWrites=0 networkCalls=0"
},
{
"suite": "test-city-submit.cjs",
"exitCode": 0,
"output": "PASS: 22 region submit/detail checks"
},
{
"suite": "test-archive-display.cjs",
"exitCode": 0,
"output": "PASS same-revision saved not_applicable evidence changes only missing amount display\nPASS explicit zero and positive saved amounts remain visible despite conflicting display markers\nPASS missing origin flag rather than Chinese placeholder wording controls display adaptation\nPASS partial saved markers affect only matching whitelist field keys, not counts or unrelated blanks\nPASS old loan scope and count zero do not infer a saved marker or recompute report completeness\nPASS absent or nonarray saved items never supply display evidence\nPASS v1 and unevaluated reports supply no new not-applicable display evidence\nPASS duplicate keys including conflicting or identical markers are excluded independently\nPASS different assessment or revision evidence cannot change the current archive\nPASS display adaptation copies changed amount fields and preserves original values and provided counts\nPASS archive page uses its existing report read to present markers with no additional requests\nPASS failed report read leaves raw amounts and manual retry restores only saved display markers\nPASS late earlier report response cannot replace a newer manual retry result\nPASS late failed earlier read cannot clear a newer successful display result\nPASS changing account before a delayed report resolves clears old archive data\nPASS hide before report completion prevents display evidence and personal detail from reappearing\nPASS late report cannot attach after current detail changes assessment or revision\nPASS processing or failed assessments do not fetch or infer report display markers\nPASS 18 archive display checks; 0 failures; no network/database/model/build."
},
{
"suite": "test-match-detail-copy.cjs",
"exitCode": 0,
"output": "PASS only fixed advisory codes disappear; real missing, invalid, config, pending and limits remain\nPASS old and new frozen results retain eligibility, score, versions and full raw snapshots\nPASS advisory hiding requires a consistent unknown caution with an allowed real evidence source\nPASS verified evidence and unknown or missing provenance retain their distinct source notices\nPASS standard known evidence keeps age, province, occupation and employer without provenance tails\nPASS legacy reason and saved explanation lose only known source parentheses\nPASS real verification conditions and original product sources remain visible\nPASS known legacy footer sentence becomes a reference disclaimer in saved explanations\nPASS detail attention count uses displayed reasons while retaining real failures\nPASS without saved explanation evidence is shown only through the evidence disclosure\nPASS saved explanation remains the subtitle in both detail sections\nPASS detail empty states and footer use reference language; compare labels do not request extra data\nPASS: 12 passed, 0 failed match detail copy scenarios; networkCalls=0 modelCalls=0 databaseWrites=0"
},
{
"suite": "test-province-options.cjs",
"exitCode": 0,
"output": "PASS: 54 province option UTS checks"
},
{
"suite": "test-match-preview.cjs",
"exitCode": 0,
"output": "PASS: 20 match preview UTS checks"
},
{
"suite": "test-analysis-result-route.cjs",
"exitCode": 0,
"output": "PASS: 12 analysis result route checks; no network/model access"
},
{
"suite": "test-product-presentation.cjs",
"exitCode": 0,
"output": "PASS: 19 product presentation UTS checks"
}
],
"backendRuntimeFilesBoundToLastVerification": 259,
"frontendRuntimeFilesUnchanged": 77,
"productionAccess": false,
"databaseAccess": false,
"modelCalls": 0,
"deployed": false,
"newBuildProduced": false,
"miniappPublished": false,
"committed": false,
"pushed": false,
"limitations": [
"No current production preflight.",
"Two replacement production baselines and three added-path absence checks need current server confirmation.",
"No new real-model validation of optional fund path policy.",
"WeChat physical device acceptance remains skipped.",
"Actual currently published miniapp source is unknown."
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,254 @@
{
"candidateId": "rc-20261019-assessment-fixes-r2",
"verifiedAt": "2026-10-19T10:24:14.383140+08:00",
"status": "offline_candidate_verified_not_deployed",
"manifestSha256": "f7ca780600a9f7636fa3c49000ad33b0a6ad47f8879f53e650b19938d42f5298",
"packages": 3,
"packageMembers": 235,
"backendPayloadFiles": 17,
"frontendSourceFiles": 206,
"rollbackRecordedFiles": 12,
"frontendVsParentCandidate": [
"packages/finance/report/report.uvue"
],
"phpSyntaxFiles": 29,
"phpSyntaxChecks": [
{
"package": "backend-runtime.zip",
"path": "application/admin/lang/zh-cn/user/info.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AiReportDiagnostics.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentExecution.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentReadiness.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/AssessmentRecords.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/FinancingGrade.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAgeRules.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiBatches.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiBindings.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiConditions.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductAiTemplates.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProductMatching.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ProfileZeroBalances.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/common/library/ReportPdf.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/extra/financing_grade.php",
"exitCode": 0
},
{
"package": "backend-runtime.zip",
"path": "application/extra/readiness.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AiReportDiagnostics.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentExecution.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentRecords.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/FinancingGrade.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiBatches.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiConditions.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiTemplates.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductMatching.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/common/library/ReportPdf.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/extra/financing_grade.php",
"exitCode": 0
},
{
"package": "backend-recorded-baseline.zip",
"path": "application/extra/readiness.php",
"exitCode": 0
}
],
"frontendAliasImports": 220,
"provinceDataConsistent": true,
"frontendMemorySuites": [
{
"suite": "test-zero-balances.cjs",
"exitCode": 0,
"output": "PASS: 15 passed, 0 failed zero-balance UTS/page scenarios; modelCalls=0 businessWrites=0 networkCalls=0"
},
{
"suite": "test-city-submit.cjs",
"exitCode": 0,
"output": "PASS: 22 region submit/detail checks"
},
{
"suite": "test-archive-display.cjs",
"exitCode": 0,
"output": "PASS 18 archive display checks; 0 failures; no network/database/model/build."
},
{
"suite": "test-match-detail-copy.cjs",
"exitCode": 0,
"output": "PASS: 12 passed, 0 failed match detail copy scenarios; networkCalls=0 modelCalls=0 databaseWrites=0"
},
{
"suite": "test-province-options.cjs",
"exitCode": 0,
"output": "PASS: 54 province option UTS checks"
},
{
"suite": "test-match-preview.cjs",
"exitCode": 0,
"output": "PASS: 20 match preview UTS checks"
},
{
"suite": "test-analysis-result-route.cjs",
"exitCode": 0,
"output": "PASS: 12 analysis result route checks; no network/model access"
},
{
"suite": "test-product-presentation.cjs",
"exitCode": 0,
"output": "PASS: 19 product presentation UTS checks"
},
{
"suite": "test-report-priority.cjs",
"exitCode": 0,
"output": "PASS 32 report priority checks; no network or database"
},
{
"suite": "test-report-export.cjs",
"exitCode": 0,
"output": "PASS: 38 generated MP-WEIXIN PDF export checks. No network or database access."
}
],
"backendRuntimeFilesBoundToLastVerification": 259,
"frontendSourceFilesBoundToCompile": 77,
"compileEvidence": {
"newBuildProducedThisTurn": false,
"historicalCompileDate": "2026-10-16",
"pages": 29,
"sourceFilesBound": 77,
"artifactFilesBound": 6,
"receiptPath": "D:\\AAdesk\\202609\\airz-backups\\report-copy-priority-20261016-180237\\verification.json",
"logPath": "D:\\AAdesk\\202609\\airz-backups\\report-copy-priority-20261016-180237\\weixin-compile.log",
"logSha256": "eb53433cef4abe54e831ef8a017b5baeeba3eeccb078f8f05765eba3f3a46738",
"artifacts": {
"packages/finance/report/report.js": "dac0962833112dcd5d95becb1e9beb55560e0cdf130cee82215435b9e201bce7",
"packages/finance/report/report.wxml": "4b75c8795c81b557b73c8bbe3ebc66b3b7d918128d79daf6133ca282a026410e",
"packages/finance/report/report.json": "c9eff98ede338f1518e2ac5787f10604b0452ad904850d799071e0c375455512",
"packages/finance/services/records.js": "47bceeb60d67618f9d3b04780bcaaa64de4b913b4c5cb38394453cca6e6208db",
"packages/finance/services/report-export.js": "34810196167a2d84cea61866d6645a370e45027bd7590f79066862f28f931606",
"common/vendor.js": "e981eee1d639472e5c8128ff30d8261e34f9138fefb6190b94eafac5c9c5f9c7"
},
"sourceManifestPath": "D:\\AAdesk\\202609\\airz-backups\\report-copy-priority-20261016-180237\\compiled-source-manifest.json",
"sourceManifestSha256": "04fb954857722b60e57100ab6efedca484e47ac9d662d7da49c7104eb6475078",
"allFourNewCopySentencesPresent": true
},
"productionAccess": false,
"databaseAccess": false,
"modelCalls": 0,
"deployed": false,
"newBuildProduced": false,
"miniappPublished": false,
"committed": false,
"pushed": false,
"limitations": [
"No current production preflight.",
"Two replacement production baselines and three added-path absence checks need current server confirmation.",
"No new real-model validation of optional fund path policy.",
"WeChat physical device acceptance remains skipped.",
"Actual currently published miniapp source is unknown."
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
{
"status": "PASS",
"candidateId": "rc-20261015-profile-advice-r1",
"checks": 642,
"archives": 2,
"members": 12,
"syntax": [
{
"archive": "backend-runtime.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"status": "PASS"
},
{
"archive": "backend-runtime.zip",
"path": "application/common/library/ProductAiBatches.php",
"status": "PASS"
},
{
"archive": "backend-runtime.zip",
"path": "application/common/library/ProductAiConditions.php",
"status": "PASS"
},
{
"archive": "backend-runtime.zip",
"path": "application/common/library/ProductAiTemplates.php",
"status": "PASS"
},
{
"archive": "backend-runtime.zip",
"path": "application/common/library/ProductExplanations.php",
"status": "PASS"
},
{
"archive": "backend-runtime.zip",
"path": "application/common/library/ProductMatching.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/AssessmentNeedsReport.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiBatches.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiConditions.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductAiTemplates.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductExplanations.php",
"status": "PASS"
},
{
"archive": "backend-recorded-baseline.zip",
"path": "application/common/library/ProductMatching.php",
"status": "PASS"
}
],
"recordedProductionInventory": {
"files": 135,
"exact": 128,
"changed": 6,
"routeLineEndingsOnly": 1
},
"acceptedSourceFingerprints": {
"backend": 255,
"frontend": 105
},
"evidenceBindings": 14,
"manifestSha256": "f64bd809fefdc0713d0a77f611e814bb1eebf16c9d8a691a4314de5267fc9b8f",
"serverPreflightPerformed": false,
"databaseAccess": false,
"newModelCalls": 0,
"deployed": false
}
+37
View File
@@ -0,0 +1,37 @@
# 后端校验诊断发布(2026-10-16)
用户明确要求“帮我发布一下后端”,并亲自处理证书提示及登录宝塔。本次将固定候选 `rc-20261016-report-diagnostics-r1` 发布为 `20261016-report-diagnostics-r1`;11:09:03 完成替换,11:12:47 恢复入口及 worker(Asia/Shanghai)。仅发布脱敏拒绝诊断能力,没有重试正式评估、创建新评估或新增模型调用。
## 发布范围
三个替换文件为 `DeepSeekException.php`、`ProductAiBatches.php`、`AnalysisBatchTasks.php`,新增 `AiReportDiagnostics.php`,均位于后端 `application/common/library/`。固定候选 ZIP 原包发布,SHA256 为 `bd9714ab649525ab450e6138e50f7427e8d784f5af9fbba2928d4c1af641ec68`。
现场三个旧文件与候选基线一致,新文件原先不存在;本次核验四个运行文件及 133 个保留文件,共 137 路径。前次 135 路径清单未包含 `DeepSeekException.php`,本次将其纳入并核对现场 SHA 与修改前归档,另增加新诊断类,因此范围增加两条。所有运行文件权限、归属及环境配置保持;仅数据库资源版本和 `site.php` 对应版本同步更新。
不删填写项或用户数据,不修改提示词、严格校验、三态、评分、模型配置或自动重试规则。无依赖安装、DDL 和业务数据迁移,无提交、推送或小程序发布。
## 现场验证
维护前暂存校验全部通过,活动评估、活动调用及 Redis ready/delayed/reserved 三队列均为空。维护期间临时关闭外部入口,正常停止 worker 和 PHP-FPM,备份现场源码、Nginx、site 配置与数据库,再整体替换并重启。30 张表的完整业务行摘要和结构在维护期间保持一致,数据库只更新资源版本。
PHP 7.4.33 四文件语法、137 路径摘要、25 个正式产品编译通过;现有个人目录仍为 82 条条件、100 条模板、7 批规划,规划不调用模型。只读访问 3 份历史报告、12 组匹配列表、3 份评估详情及 25 个产品,3 项跨归属访问被拒绝。服务器本机可信 HTTPS 29 项和独立公网可信 HTTPS 31 项通过。
恢复后 `airz-analysis`、`php-fpm-74`、`redis` 均 active。仅有一个正式 worker,命令及归属正确,PID 已更新,NRestarts=0;最终核验时心跳 ready、年龄 1 秒。观察期间活动任务、活动调用及三队列仍为空,worker journal 未发现错误、警告或诊断拒绝事件。零事件不代表原格式错误已经修复。
## 日志验证与边界
生产 helper 的摘要、类源文件路径、两条合成记录的字段白名单及日志故障隔离通过。合成拒绝记录只进入内存 sink,未伪造正式业务拒绝事件。
同 worker 用户、组、PHP 二进制、工作目录及 PHP ini 环境的独立 PHP 进程可通过默认 `error_log` 写出固定无个人信息标记;实际 PHP 默认通道为 stderr。真实 worker unit 的 StandardError/StandardOutput 均配置为 journal。探针运行在正式 worker 进程及 unit sandbox 之外,没有证明真实业务拒绝事件已写入 journal,也没有触发真实评估来验证这一点。
初次探针因当前 systemd 不返回空的 `EnvironmentFiles` 属性而停止,尚未进行标记写入;适配后通过完整 unit 文本确认没有 `EnvironmentFile=` 指令,再执行一次标记验证。初始脚本和修订脚本、失败和成功回执分别保留。未调整生产日志路由、权限或业务代码来绕过探针要求。
## 证据与恢复
本机发布档案:`D:/AAdesk/202609/airz-backups/production-release/airz-report-diagnostics-20261016`;服务器备份:`/root/airz-report-diagnostics-20261016`。固定开发候选仍在 `report-rejection-diagnostics-20261016/candidate`,其准备时的“未发布/未授权”字段保留为历史状态,本次授权与现场 manifest 另存。
28 份现场备份及阶段证据已下载到本机 `production-backup/`,逐份重算 SHA256 与服务器索引一致。私密档案仅当前 Windows 用户和 SYSTEM 可读,服务器发布目录为 root 私有;传输缓冲及临时登录脚本已清理,宝塔已退出并关闭本次专用浏览器。最终本机回执为发布档案中的 `release-receipt.json`。
恢复入口前已核验精确现场备份可恢复;本轮无需回滚。入口恢复后应保留后续用户资料、冻结快照、检查点、报告及用量,优先向前修复,不整库覆盖。旧单次收费诊断入口的 `model-call.started` 已确认保留,不得重跑或换目录规避。
原评估 471/版本 718 的前两次具体拒绝原因仍未知,前轮唯一一次真实诊断通过也不能反推原失败原因。本次没有恢复该正式任务。后续若用户另行授权付费恢复,应重试原记录、沿用同模型及前三批检查点,从第 4 批继续,最多剩余 4 批。小程序无需因本次后端诊断补丁重新发行。
+25
View File
@@ -0,0 +1,25 @@
# 评估、省份与分批后端发布(2026-10-15)
按用户“你帮我发布后端,然后我自己发布小程序”授权,`https://airz.hqling.cloud` 已发布 `20261015-assessment-region-batch-r1`。15:37:32完成部署,15:38:53恢复入口及分析worker(Asia/Shanghai)。候选 `rc-20261015-assessment-region-batch-r1` 的后端ZIP保持原摘要,未重建候选;小程序由用户自行重新编译发行。
本次整体发布9个后端库:替换AnalysisTasks、AssessmentNeedsReport、AssessmentRecords、MatchRecords、ProductAiConditions、ProductAiTemplates、ProductMatching,新增AnalysisBatchTasks、ProductAiBatches。累计支持默认三款读取、年龄可信事实、分批任务/用量/检查点、待补资料不限固定数量、明确公积金分支/理由填值校验及全国34省事实。province_code仅随评估版本JSON保存,city_code为空;省份不能证明具体城市、户籍、申请地或网点覆盖,历史报告不重算。
上线前现场核对133个既有文件与2个新增路径,符合20261014-product-ai-r1源码基线,两新增路径原本不存在。PHP7.4.33、实际站点/域名和worker命令确认;未改路由、管理端、依赖、.env、产品数据或模型额度,无DDL及业务数据迁移。仅同步fa_config的version值及site.php对应资源版本。正式产品现场为25款,并非旧发布时的31款;本次未增删产品。
当前正式目录只读编译通过:25款个人产品、82项条件、100个理由模板,共7批;各批不超过32项、48000描述字节及32个模板槽位。当前无企业适用产品。默认Flash、每日每用户20次额度及最大3次评估尝试保持,完整个人评估预计需要7次实际调用;实际用量与已有当日调用共用额度。只规划未调用,不能以历史约152秒样本保证当前耗时。
维护期通过以下核验:
- 135个运行/依赖路径摘要;9个候选PHP的服务器语法与归档逐项摘要。
- 30张表的完整业务行与表结构摘要一致,仅资源版本元数据更新。
- 在数据库只读事务内读取2份现有成功报告、8组匹配列表、2份资料详情、25款产品,2项跨归属读取拒绝;34省字典存在。
- 29项服务器可信HTTPS及31项独立公网可信HTTPS检查,包括公开接口、鉴权、静态资源、敏感路径和旧入口跳转。
- 精确恢复材料干跑通过,未执行回退;入口恢复后唯一分析worker已换PID、命令正确、NRestarts=0,心跳ready/idle且新鲜,三服务active。最终核对活动评估、活动模型调用及队列ready/delayed/reserved均为0,worker观察期无错误/警告。
本次没有新建正式测试账号、评估或模型调用,没有发行小程序/H5、提交或推送。上述只读生产检查不替代新省份提示的真实模型语义、微信真机和完整业务验收。ID56申请金额/本笔月供口径仍独立待核对。
执行证据与现场备份目录:服务器 `/root/airz-assessment-region-batch-20261015`;本机 `D:/AAdesk/202609/airz-backups/production-release/airz-assessment-region-batch-20261015`。`manifest.json`绑定固定候选与现场基线,阶段回执包括stage、prepare、deploy、verify、rollback-check、resume、observe、public-smoke、final-verification;最终汇总为release-receipt.json。27份现场备份与阶段证据已下载至production-backup并逐项核验摘要,服务器目录700/文件600,本机仅当前Windows用户与SYSTEM访问;.env未下载。临时传输缓冲与分片已清理,宝塔已退出并关闭专用Playwright浏览器,浏览器证书校验保持开启。
发布后已恢复正式写入。后续若发现问题,先暂停新增任务、保留全部省份资料、用量、分批检查点与报告,优先向前修复并保持兼容读取;本次恢复工具拒绝在已有resumed回执后直接回旧代码,不允许整库覆盖后续数据。旧客户端编辑新省份资料、旧微信发行包对应源码及精确前端回退尚未验收。
下一步由用户在HBuilderX从 `D:/WorkSpace/cyun/ai融资` 当前源码重新编译微信小程序,确认production API为 `https://airz.hqling.cloud/api/v1`,再上传发行。记录真实微信版本、发行时间与上传包摘要;候选归档是源码,不是微信上传包。发行后验证省份选取/回填、个人/企业确认与详情、历史报告、默认三款/查看更多/对比、处理中离开返回。新的真实收费评估须按单独授权的资料和预算执行。
@@ -0,0 +1,26 @@
# 自填资质参考建议后端发布(2026-10-15)
按用户“发布后端”明确授权,`https://airz.hqling.cloud` 已发布 `20261015-profile-advice-r1`。17:53:40完成部署,17:54:14恢复入口和分析worker(Asia/Shanghai);本次授权覆盖此前“先不更新线上”的限制。固定候选`rc-20261015-profile-advice-r1`保持原清单和ZIP摘要,小程序已由用户发行,本轮无需重新发行。
本次整体替换application/common/library下6个文件:ProductMatching、ProductAiConditions、AssessmentNeedsReport、ProductAiTemplates、ProductAiBatches、ProductExplanations。新建资质评估冻结`profileAdvicePolicy=self-reported-profile-advice-v1`:默认自填事实真实;未采集/精度不足的细项不要求补填、不阻断参考建议;unknown保留unknown、不加分,不代表银行全部准入通过。任一完整路径满足即可。职业与单位可在新个人白名单外发,姓名、完整身份证和手机号仍排除。全部表单填写项、保存/回填和用户历史资料保留。
现场135个文件全部符合上次已发布版本基线,6个候选逐成员摘要及服务器PHP7.4.33语法检查通过;其余129路径、.env、路由、产品数据、依赖和模型配置保持。没有新增或删除运行文件、DDL或业务数据迁移,仅同步fa_config.version值及site.php对应资源版本。
正式目录只读编译成功:25款个人产品、82条条件、100个模板,7批;每批不超过32项、48000描述字节和32模板槽位。新策略标记已在个人/企业冻结结果验证,当前无企业适用产品;Flash模型、每日每用户20次额度、最多3次尝试保持。此次只规划、不调用模型。
发布核验结果:
- 维护前活动评估、活动模型调用和ready/delayed/reserved三队列均为0,正常停止所属worker及PHP-FPM后备份和整体替换。
- 维护期30表完整业务行/结构摘要一致,只更新资源版本元数据;原用户资料和历史结果未改写。
- 数据库只读事务内成功读取3份历史报告、12组匹配列表、3份资料详情、25款产品;3项跨归属读取拒绝,34省字典保持。
- 29项服务器及31项独立公网HTTPS检查通过,证书正常校验,包括公开接口、鉴权、静态摘要、敏感路径和旧入口跳转。
- 唯一分析worker已换PID,命令和归属正确、NRestarts=0,心跳ready/idle且新鲜;三服务active,观察期无错误或警告,检查时活动评估/模型调用/三队列为空。
- 现场备份恢复材料干跑通过,未执行回退;27份现场备份及阶段证据已下载并逐项校验摘要。服务器目录700/文件600、本机仅当前用户和SYSTEM访问;临时传输与凭据已清理,面板已退出并关闭专用浏览器。
服务器备份:`/root/airz-profile-advice-20261015`。本机证据:`D:/AAdesk/202609/airz-backups/production-release/airz-profile-advice-20261015`,最终回执`release-receipt.json`,现场备份在production-backup。固定候选清单`docs/发布候选/profile-advice-20261015-r1/manifest.json`及历史收费证据保持。
本轮未新增正式测试账号/评估、模型调用、短信或微信真机测试,未发行小程序/H5、提交或推送。此前合成资料真实模型验收7次调用已通过,详见[实测记录](资质参考建议实测-20261015.md);本轮生产检查只确认部署、目录、读取和运行状态。4条固定产品理由仍保留后续申请“进一步核实”措辞,不生成当前补填清单。实际微信版本/发行时间/包摘要尚未补录。
新口径仅影响新建资质评估;旧任务、检查点、历史报告及旧任务重试继续原冻结口径,不会自动重算。下一步由用户在已发行小程序发起一份新评估,重点查看无须补填未采集细项、参考候选/明确不符、完整可选路径及资料回填;新评估预计7次实际调用并消耗现有额度,本轮没有代为创建。
入口已恢复正式写入。发生问题时保留全部新策略快照、检查点、报告、用户资料和用量,保持兼容读取与执行能力,优先向前修复;禁止直接旧代码或整库覆盖后续业务。本次恢复脚本在已有resumed回执后拒绝执行旧代码恢复。
@@ -0,0 +1,51 @@
# 年龄区间确定性判断
2026-10-16。用户“继续下一步”确认优先补齐年龄判断,继续先不发布、跳过真机验收。本轮仅修改本地后端及新增内存回归,不删填写项、不改用户已填内容、不访问生产或调用真实模型。
## 问题与处理
现有身份证年龄事实已按该次评估冻结的上海业务日计算,但自定义年龄条件仍交给 AI;契约允许已知年龄返回 unknown。上轮清理了误导提示,本轮将能明确比较的年龄条件移出模型判断请求,由后端直接计算通过/不符/未知。原截图对应的具体失败原因仍无该次冻结请求证据,本轮不据此追认。
新个人资料参考评估冻结 `ageRangePolicy=identity-age-range-v1`,策略进入目录及执行摘要。`capture()` 显式启用,`freeze()` 新末参默认关闭;旧冻结目录、检查点和报告没有标记时保持原行为,不动态补规则、不重算历史。企业、原需求模式和未启用参考建议的目录不启用该规则;非法标记或不兼容标记组合明确拒绝。
识别范围采用严格白名单:硬/软条件名称为“年龄、年龄要求、年龄范围”,支持整数年龄的等于、不低于、不高于、闭区间,以及完整的“18—60周岁”“22周岁到60周岁之间”等表述。边界取 0—150,倒置、负数、小数和越界不本地猜测。备注只接受空值及既有两条不增加门槛的固定说明。其他名称、未识别语法、分性别、额外共同要求或例外仍保留完整条件,进入原判断流程。
此前已确认的四类年龄与贷款期限拆分表述保留年龄区间口径;新策略给这一步加完整尾句匹配,防止把带女性上限、收入、单位或例外的复合文字截成纯年龄。产品原文和保存配置不改,旧目录采用原已冻结解释。
仅复用现有有效身份证派生的周岁:生日当天、上海零点和闰日均按冻结业务日计算,支持既有 18/15 位身份证逻辑;姓名、证件号码、手机号仍不进入模型请求。不推导性别、不增加采集项、不放宽身份证校验。缺少有效年龄时保留 unknown,不加分、不伪造符合,也不因此索要未采集细项或阻断参考建议。
普通年龄条件保留在完整冻结判断清单中,从模型请求子集移出。后端证据保存年龄、单位、区间和结果;硬条件明确不符仍阻断,软条件仅通过时按原配置加减分。理由模板从本地判断取得三态,不能用模型返回的相反结论覆盖它。全部条件均为本地年龄且没有模板时,保留一个空工作批生成主报告正文,避免报告无法生成;这仍是正常模型调用,不新增自动重试或额外诊断调用。
## 改动与验证
后端新增 `ProductAgeRules.php`,修改 `ProductMatching.php`、`ProductAiConditions.php`、`ProductAiTemplates.php`、`ProductAiBatches.php`;新增 `tests/product_age_ranges.php`。不改数据库结构、任务状态机、额度、表单、融资等级公式或前端运行文件,原有未提交修改保留。
专项先复现“已知年龄仍被请求交给 AI”的红灯,最终257项通过,验证本地比较、生日/区间边界、无效身份证、软条件加减分、复合条件保护、模板绑定、空批、34模板跨批与部分检查点恢复、实际capture作用域及额外年龄结果注入拒绝。6个PHP文件语法检查通过。原年龄55/条件68、模板89/分批36、grounding81、地区68、参考建议59、匹配151/展示20、零金额270/事实118项分别通过;包含套件不重复累计。
另用现有25款产品原始配置重新离线冻结,并将已有合成响应显式适配新请求绑定后重放:82条条件保留,其中16条普通年龄在本地计算,66条继续请求模型,分性别条仍在其中;100条模板一致性校验通过,规划6批。该结果属于离线重放,不代表新真实模型输出。原样本摘要及旧目录已知年龄可为unknown的历史行为保持。
证据目录:`D:/AAdesk/202609/airz-backups/age-ranges-20261016`,含修改前快照、红绿日志、旧年龄审计、25款重放、源码摘要及最终回执。独立只读审查未发现实质问题。
部分旧内存入口的显式加载名单不包含前序零金额类或本次新增类,直接调用会报ClassNotFound;已改用现有严格白名单内存wrapper补齐加载,未改旧测试或默认环境。一次错误退出留下的单个合成测试临时目录已复制到证据目录,自动审批拒绝删除该目录(blocked by policy),因此保留原临时目录,未尝试绕过清理限制。
本轮未发布、未提交或推送;没有真实模型调用、正式评估重试、生产访问、数据库或常驻 worker 验收。前端运行源码保持,不以本轮离线检查代替微信真机。未来配套发布仍须后端先、小程序后;本规则只适用于新建执行快照,不能用旧任务重试验收。
## 2026-10-16 新增真实模型验证(未通过,已停止)
用户明确要求“直接用真实模型验证”,本轮授权用于一份合成资料的完整评估,最多六批、不自动重试。使用既有25款产品保存配置重新冻结,82条完整条件中16条本地年龄、66条模型条件,100条模板共6批;不是新导出的正式产品目录。合成资料为身份证派生36岁、广东省、全部贷款0笔/信用卡0张、四关联金额原始null;姓名、完整身份证及手机号在发出前及保存后均检查未进入模型请求。
全新本机51778/51779隔离MySQL与Redis通过空库及进程归属审计,随后通过实际`AssessmentRecords::create → AnalysisTasks::run`执行一次,无常驻worker。首批真实响应耗时22.032秒、10620输入/3027输出token,返回12条条件及16条模板,但被`ProductAiTemplates.php:223`严格绑定校验拒绝:产品58、template_qa_4的templateHash应为64字符,模型抄成65字符,片段`...28bd99e54179...`变为`...28bd6f5c54179...`。任务failed/AI_INVALID_REPORT,0检查点、无正式报告;后5批没有发出,共1次真实调用,没有自动重试,金额未查账不能折算。
保存原始请求/响应、冻结上下文、当前合成row、用量和防重标记。禁止真实模型构造的离线重放复现同一拒绝行和请求摘要;仅在独立内存副本改回这一处hash后,首批全部校验通过,在下一批前主动停止。此反事实探针不是原模型成功结果,也没有修改原始响应、正式评估或后端校验规则。独立只读审查确认该绑定错误。
同份冻结事实的离线探针核得16条本地年龄pass、四个金额not_applicable、原始null保留;真实首批improvements及各条件missingFields均为空,未索要贷款/信用卡金额或未采集细项。因完整评估失败,不能声称25款真实模型评估、最终报告保存回读或微信真机已经通过。
另发现真实摘要仍出现“资料由用户自填,尚未核验。”,当前基础提示和示例仍明确要求这类文字;此项需要后续清理,不能把前序匹配详情展示清理当成主报告提示词已修复。本轮只验证和诊断,没有修改运行源码。后续建议先降低模型回传长hash的脆弱性(后端保留冻结绑定和严格校验),并统一摘要提示,再考虑新的完整实测;不能重跑本轮防重入口或把响应补成成功。
精确清理合成数据后29表为空、6原触发器保留、Redis DB15空;核对归属后正常关闭本轮MySQL/Redis,51778/51779释放,保留datadir及证据。258个后端application PHP、77个前端UTS/UVue摘要保持。未访问正式库、未发布、未提交/推送,仍跳过真机。证据:`D:/AAdesk/202609/airz-backups/age-ranges-live-20261016-r1`,详见`verification.json`、`failure-replay.json`及`binding-probe.json`。
## 2026-10-16 后续修复(本地完成,未发布)
针对本节真实调用发现的长hash抄错与摘要流程说明,已完成新冻结批次短ref协议及主报告提示清理。完整绑定由后端恢复并继续严格校验,旧协议/保存数据保持;后续新生成正文使用清理后的提示。编解码、流程、摘要、隔离持久化与失败续跑均有通过证据,本轮5次显式假模型、0真实调用,仍未完整真实复测、未发布。详见[短编号与摘要提示修复](短编号与摘要提示修复-20261016.md);证据为D:/AAdesk/202609/airz-backups/response-bindings-20261016。原真实失败目录、响应和防重标记保留,不重跑原收费入口。
+387
View File
@@ -1,5 +1,392 @@
# AI融资项目新会话交接
## 新会话接续摘要(2026-10-19)
- 最新模型切换:用户明确要求 Flash 用完后改用 `deepseek-v4-pro-0813`,2026-10-19 15:36:29 已切换正式后端与本地后端环境配置;补入 assessment_model、将助手/产品解释所用 model_flash 映射为 Pro,原 model_pro 保持。正式策略仍取 environment,无配置表写入;独立 App 只读回读确认三场景新请求均为 Pro,enabled/每日20次/最多3尝试/90秒及其他非目标字节保持。原唯一 worker 已支持 Pro,三服务 active、PID及重启计数保持,心跳 ready;无需重启或重发小程序。161源码/25产品与74规则保持,5历史报告/20匹配组/5详情及归属边界/34省份读取通过。旧评分档案有1会话整行摘要变化,诊断为15:19:51切换前已失败AI_BALANCE,原已发布报告保留;另4会话摘要保持,未自动重试。检查时活动评估/调用/三队列0,本轮模型调用0,Pro额度与真实推理未实测。旧发布环境摘要不回写,后续核验结合本次模型两行差异回执;原报告/任务模型字段未改。详见docs/模型切换回执-20261019.md;私密证据D:/AAdesk/202609/airz-backups/model-switch-pro-20261019-r1、服务器/root/airz-model-pro-20261019-r1。积分/协议等待办尚未推进,下方历史“Flash仍在用”的状态以本条为准。
- 最新产品加减分正式配置:用户“写入正式配置”授权后,2026-10-19 15:14:43将25款74条(49加/25减)原方案规则在单一事务中写入,版本各加1;仅物理other/update_time变化,当前正式准入、模板及其他字段保留。1356预检/1389事务检查/1020独立回读通过,161源码守卫/142发布路径、5历史报告/20匹配组/5详情与归属、25产品/34省份读取正常;三服务active且重启计数保持,唯一worker ready,检查时评估/调用/三队列0。模型仍66条件/100模板/6批,本轮调用0次;新捕获目录的评估生效,历史及已冻结任务不重算,无需重发小程序或后端。草案基于QA冻结目录,正式写入另以现场25款配置守卫并完整备份,未覆盖QA资料;本地route既有差异按已发布基线核实,未改生产源码。16份现场档案已下载核SHA、面板退出、一次性入口归档。详见docs/产品材料/产品加减分正式写入回执-20261019.md;私密档案D:/AAdesk/202609/airz-backups/product-soft-scores-production-20261019-r1。下方历史待配置状态以本条为准。
- 本轮候选核验边界补充:原r2 `candidate-tools.py --verify` 在旧开发编译产物摘要守卫处退出1,本轮不能登记完整核验通过。独立只读诊断确认当前206前端源码/资产、77前端运行源码、259后端运行源码、3固定包及39冻结证据一致;本次对照中4个dev JS产物摘要不同,修改时间均为今天约12:00:21,29页及四组报告新版文案检查通过。未修改冻结清单/开发产物、未重新编译,不能据此绑定实际微信上传包。线上31HTTPS/142代码路径及历史读取复核通过保持;诊断见下述独立档案的candidate-diagnostic.json,最终保全见completion-receipt.json。
- 最新小程序发行登记与线上复核:用户确认小程序 `1.1.0` 已于2026-10-19约12:00(Asia/Shanghai)发行,版本与时间按用户报告登记。发行后只读复核31项公网HTTPS、142代码路径、三服务active及唯一worker ready心跳通过;历史4报告/16组匹配/4详情与归属边界、25产品/34省份读取通过,检查时活动评估/调用及三队列均0。本轮未触发模型或业务写入;实际微信上传包与源码摘要绑定尚未独立核实,真机继续跳过。约12点起systemd journal未见新增worker事件,不等同全链路/实际用户验收;面板退出、浏览器关闭、临时凭据脚本清理。独立证据D:/AAdesk/202609/airz-backups/mini-program-release-1.1.0-20261019,完整说明docs/资质评估累计修复后端发布-20261019.md;本条覆盖下方客户端待发行/待登记状态,原发布与实测证据保持。
- 最新正式后端发布:用户“先发布一下,你帮我发布后端,我自己发行小程序”明确授权,后端已整体发布为20261019-assessment-fixes-r2,11:47:56(Asia/Shanghai)恢复入口及唯一新worker。固定r2后端包17文件(14替换/3新增),现场原137历史路径及2补充旧文件匹配、3新增原不存在,发布后142路径核对通过;环境保持、worker unit保持、Nginx原配置恢复。维护前评估/调用/三队列均0,完整备份后维护;30表业务数据及精确结构保持,仅资源版本值变更。生产25产品新策略只读规划为66模型条件/100模板/6批;历史4报告/16组匹配/4详情及归属边界、25产品/34省份、29服务器/31公网HTTPS及恢复前回退干跑通过。三服务active、唯一新worker ready心跳通过,观察无本轮错误/警告。无本轮收费调用、DDL、业务迁移、产品导入、模型/额度修改、正式任务重试或小程序发行。小程序由用户从D:/WorkSpace/cyun/ai融资当前源码重新编译发行,真机仍跳过;恢复写入后保留新快照/检查点/报告/用量,优先向前修复,不自动旧代码/整库回退。完整记录docs/资质评估累计修复后端发布-20261019.md,服务器私密档案/root/airz-assessment-fixes-20261019,本机D:/AAdesk/202609/airz-backups/production-release/airz-assessment-fixes-20261019。固定r1/r2和原实测证据保持,本条覆盖下方“继续不发布/待发布”状态;客户端发行状态以本页上方用户确认的1.1.0发行登记为准,实际上传包摘要绑定仍未独立核实。
- 最新单批续跑补充验收(继续不发布、跳过真机):用户“允许”明确授权最多新增1次真实调用,只补前次超时第6批。新隔离52378/52379恢复原输入/提交时间/版本及冻结策略,原5canonical检查点保持;零调用预检确认原第6请求、9条件/12模板/6640预算及原模型/90秒上限一致。实际仅新增1调用,8.743秒返回、8376/1106 token,总9.671秒;原AnalysisTasks::run成功,66模型条件/100模板/25产品及6检查点完整保存,版本快照与会话保存一致、所属用户完整回读一致、其他用户404、6检查点零调用恢复及冻结匹配复算通过。77公积金目标pass/fact:housingFund及推荐理由保留,注意模板不适用;66条件相较旧完整实测仅目标unknown→pass,其他65三态保持。最终5pass/61unknown、17适用模板;25款80分仅参考候选。16本地年龄36岁pass,四原null金额及表单/最新资料保持,not_applicable/完整度100%、无新增补填。仅验证新隔离排队任务消费检查点及保存回读,未改重试额度或验收生产worker。29合成表空、6触发器/DB15空,所属两服务正常关闭、端口释放;259后端PHP/77前端运行源码、原7证据根保持,r2只读核验通过。本轮仅更新本说明和交接,无生产访问/发布/提交/推送。最新说明docs/公积金规则实测与生产基线核对-20261019.md,证据D:/AAdesk/202609/airz-backups/optional-fund-path-resume-20261019-r1;新旧收费入口及once标记均禁止重跑。该条补齐下条“未形成报告”状态;无需重复收费复测,继续等待后续明确任务,不能自动发布。
- 最新补充验收(继续先不发布、跳过真机):用户同意按建议顺序,已用新隔离52178/52179及同份合成资料、25款保存配置实际调用6次。第5批77徽享薪易贷rule_qa_2公积金完整分支真实pass/fact:housingFund,推荐模板启用、注意模板保持不适用;前5批57模型条件/88模板严格解析及持久化检查点成功,较历史已完成条件仅目标unknown→pass。第6批在既有90秒上限处AI_TIMEOUT,任务failed、hasReport=false,未形成最终报告;总147.643秒,已返回48760/7424 token,超时用量未知,账单未查。未自动重试或追加调用,未扩大超时/改模型/改运行代码。通用证据46项及目标/失败19项分别通过,5检查点零调用重放准确续到第6批;不能登记完整评估/最终保存回读通过。最新说明docs/公积金规则实测与生产基线核对-20261019.md,证据D:/AAdesk/202609/airz-backups/optional-fund-path-live-20261019-r1。该目录evaluation-once.started及6逐调用标记保留,禁止重跑收费入口;下一步如获新增调用授权,仅补超时批,不从头重跑前5批。
- 同轮生产只读核对完成:现场仍20261016-report-diagnostics-r1/PHP7.4.33,137历史路径全匹配、环境摘要保持、三服务active。此前缺口的AssessmentReadiness及admin/lang/zh-cn/user/info两旧文件已读取并核SHA,三新增ProfileZeroBalances/ProductAgeRules/ProductAiBindings当前均不存在;14既有差异/3新增。已有12旧文件均匹配现场,另2原字节补齐为14文件补充旧源码包,语法/ZIP校验通过,证据production-readonly/supplemental-baseline-verification.json。该补充不是完整生产备份;固定r1/r2材料全部保持,原冻结清单待核对描述不回写。无生产代码/数据库/服务变更或上传/发行/提交/推送;面板退出、浏览器关闭,本机29合成表清空/6触发器保留/DB15空,所属隔离服务停止、端口释放。前端/后端运行源码及全部前序改动保持,本轮仅新增说明并追加交接;未来发布仍需新预检和完整备份,不能自动接续。
- 同日前序浏览器验收补充:r2报告页受控Chromium/合成响应下123主检查+8底部滚动检查通过,360×780、390×844、1280×900,运行错误/控制台警告/意外外部请求均0。四金额不适用、真实缺项、历史/旧报告、三栏/展开/刷新/失败重试及合成下载提示均已验证;真实PDF内容和微信真机未验收,既有H5 layout类型警告保持。证据D:/AAdesk/202609/airz-backups/report-ui-qa-20261019/verification.json,28截图;前后仓库和固定候选保持,测试服务已关闭。本条覆盖下方冻结时“浏览器视觉未验收”,不覆盖真机、完整真实评估及未发布边界。
- 最新本地候选(继续先不发布、跳过真机):用户“继续下一步”授权整理r2。两仓库与2026-10-16最后窄修复后的514前端/4119后端非忽略文件集合及字节一致,没有新增源码漂移;已固定 `rc-20261019-assessment-fixes-r2`,3ZIP/235成员仍为17后端运行文件+206完整前端源码/资产+12有生产摘要依据的旧文件。后端及旧文件ZIP与r1逐字节一致,前端相对r1仅report.uvue四句固定文案替换,脚本适配作为验证入口绑定而不入运行包。10个现有前端内存入口分别通过:零金额15场景、地区22项、档案18组、匹配文案12场景、省份54项、预览20项、完成路由12项、产品19项、报告优先级16场景/32断言、PDF导出38项。29PHP语法、3包路径/集合/字节/SHA256/CRC、220别名导入和省份同源检查通过。77前端UTS/UVue及6关键产物匹配2026-10-16最后窄修复的29页编译回执,本轮未新编译,源码包不是微信上传包。259后端运行PHP继续绑定上轮公积金修复摘要,无运行源码/测试入口新修改。清单 `docs/发布候选/assessment-fixes-20261019-r2`,操作单和验收矩阵 `docs/资质评估修复发布候选-20261019-r2.md`,3包/测试/来源核对/准备回执 `D:/AAdesk/202609/airz-backups/release-candidate-20261019-r2`;新工具 `candidate-tools.py --verify` 只读复核,禁止重复 `--build`。r1及旧实测证据保持,不覆盖原候选或收费入口。
- r2仍非发布授权:两处生产旧文件摘要(AssessmentReadiness及admin/lang/zh-cn/user/info)和三处新增路径(ProfileZeroBalances/ProductAgeRules/ProductAiBindings)须未来现场确认;12文件旧包不等于完整生产备份。最近已记录生产版本仍为 `20261016-report-diagnostics-r1`,本轮未现场核对;已发行小程序源码/上传包摘要仍未知。最新公积金规则只有原162专项/102离线反事实及既有回归,无新真实模型复测,真机与浏览器视觉未验收。无生产/数据库/真实API/模型访问,无App/数据库/worker启动,无上传/发行/提交/推送,全部前序未提交改动保留。本轮仅更新README当前入口、本交接和新候选文档/清单;后续若获发布授权,再现场完整备份、排空任务、整体后端发布并重载PHP/唯一worker,然后用户编译发行小程序;形成新数据后保留快照/检查点/报告/用量,优先向前修复。下方为2026-10-16历史状态,其中“当前源码与r1有差异未重新固定”已由本条r2覆盖。
## 新会话接续摘要(2026-10-16)
- 最新本地窄修复(仍先不发布、跳过真机):仓库内 `scripts/test-report-priority.cjs` 已补入现有 `cities.js` 依赖的隔离替身;修改前实际复现 `Unexpected dependency: ../../../services/cities.js`,修改后及本轮微信编译后原16场景/32断言均通过,原断言、输入不变检查、编译新鲜度门禁和未知依赖拒绝保持。`report.uvue` 第35/37/66行固定来源/未经核验提示及第39行同类空态说明改为中性资料/参考文案,保留未填写不等于零、机构要求、结果仅供参考和不代表授信或获批;旧版报告说明、模型品牌隐藏及内部元数据保持。除这些静态文字外,报告脚本/样式、表单、原值保存回填、评分、完整度、准入和历史快照均未改。相关源码内存回归:零金额15场景、地区提交22项、档案展示18组、匹配文案12场景、产品展示19项通过;编译后PDF导出38项通过,HBuilderX5.26微信29页本地编译成功,独立窄差异审查未发现明确缺陷。证据 `D:/AAdesk/202609/airz-backups/report-copy-priority-20261016-180237` 保存原件、红绿日志、编译日志、差异及范围核对。仅前端脚本/报告模板/本交接3个已有文件有本轮增量;前后端全部前序未提交改动保留,无后端运行文件修改,无真实API/模型/生产/数据库访问,未启动默认App/数据库/worker,未提交/推送/上传/发行,真机及浏览器视觉未验收。
- 候选差异提醒:`rc-20261016-assessment-fixes-r1` 的3个ZIP、固定清单及原证据保持,**未包含本轮主报告新文案**,未重新固定候选。当前源码已与r1前端源码包发生差异,不能沿用r1回执证明当前源码已封包或可直接发行;如后续重新固定必须使用新标识,禁止用 `--build` 覆盖r1。生产仍仅有既往记录的 `20261016-report-diagnostics-r1`(本轮未现场核对),最新公积金规则仍只有原162专项/102离线反事实及既有回归,无新增真实模型复测;旧收费入口和原实测证据保持。下条为本轮之前的r1准备记录。
- 最新候选准备(仍先不发布、跳过真机):用户“继续下一步”,已固定rc-20261016-assessment-fixes-r1,3ZIP/235成员=17后端运行文件+206完整前端源码/资产+12精确生产摘要旧文件。以最后已发布20261016-report-diagnostics-r1的137路径回执核对,124当前字节一致、12业务替换、1route仅换行排除;另2替换AssessmentReadiness及admin/lang/zh-cn/user/info缺生产旧摘要,3新增ProfileZeroBalances/ProductAgeRules/ProductAiBindings须现场核absence,不能将本地before当完整生产回退包。17/12候选和旧文件共29PHP语法、220前端别名导入、省份同源及3包路径/字节/CRC通过;本轮8前端内存入口通过。前端15本地Git运行差异仅作本地集合,较20261015历史206源码9差异;实际已发行源码/上传包摘要未知。76业务源码匹配最后29页编译快照,额外main.uts与历史完整源码一致,6关键产物与回执一致,继承历史编译而非本轮新编译;完整源码ZIP不是微信上传包。259后端PHP及77前端UTS/UVue集合/字节保持,与最新修复证据绑定。主报告既有静态免责声明和内部元数据保留,不能称全应用来源文案清空。最新公积金策略仍只有162专项/102离线反事实和既有回归,没有新真实模型复测。清单docs/发布候选/assessment-fixes-20261016-r1,操作单docs/资质评估修复发布候选-20261016.md,证据及3包D:/AAdesk/202609/airz-backups/release-candidate-20261016,candidate-tools.py --verify可本机复核,禁止重复--build覆盖固定包。无生产/数据库/模型/服务启动/发布/新编译/提交/推送。实际发布仍需用户明确要求,之后先现场核对备份14旧文件及3新路径、排空任务、整体发布后端并重载PHP/唯一worker,再由用户编译发行小程序;新写入后保留快照/检查点/报告/用量,优先向前修复,不整库回退。下条为候选前修复记录。
- 最新本地修复(继续先不发布、跳过真机):用户“继续下一步”,已定向补足77徽享薪易贷“社保公积金或优质行业路径”的独立公积金充分分支。新个人资料参考capture冻结optionalPathPolicy=complete-optional-paths-v1,完整标题/hard/eq/空valueMax/整句及安全备注白名单同时匹配才附sufficientAlternatives;已有公积金必须pass并引用fact:housingFund,否则严格拒绝,不自动改写模型结果。原产品文案/用户填写不改,额外时长/单位/基数/共同门槛反例不放宽;无标记旧报告和检查点保留原解释。3后端运行文件修改、其他256PHP及77前端运行文件保持。162专项、14既有内存入口及4PHP语法通过,独立精准增量审查无实质问题。102项纯离线重放验证旧6真实响应/6原检查点保持;新目录显式重绑ref后原77 unknown在第5批被拒并保留前4检查点;仅77条件及其2关联模板作明确标记的反事实一致性改动后,6批66条件/100模板/25候选完整合并和6检查点恢复通过,不能视为新真实模型验证。59原实测证据SHA256保持,0新真实调用/0App或数据库访问/0生产访问,无发布/提交/推送,生产仍为20261016-report-diagnostics-r1。说明docs/公积金可选路径修复-20261016.md,证据D:/AAdesk/202609/airz-backups/optional-fund-path-20261016。下一步可整理累计发布候选及验收边界;新提示实际效果尚未真实复测,不得重跑旧收费入口或默认接续发布。下条为本修复前真实复测结果。
- 最新真实完整复测(继续先不发布、跳过真机):用户在短ref/摘要本地修复后“继续下一步”,全新51978/51979隔离环境以同份合成资料和25款保存配置实际新建评估,6批真实调用全部严格解析、合并、保存及所属用户回读通过;66模型条件/100模板/25产品,6canonical检查点零调用恢复通过,16模板适用。总65.883秒、API63.831秒、56920输入/8279输出token,无自动重试或额外诊断;账单金额未查。新batch-item-reference-v1的wire只用短ref,保存恢复完整编号/64位摘要;原11份失败证据及旧收费入口保持。实际6批模型正文及最终摘要未出现用户自填/核验状态/品牌,0贷款0信用卡四关联原null保持且not_applicable,完整度100%、无补填,16本地年龄36岁均pass,省份未误报缺填。4AI条件pass/62unknown;25款为参考候选且80分,不等于所有细项通过。仍有1处明确语义漏判:77徽享薪易贷rule_qa_2的“公积金客群(缴存时间没有具体要求)”完整可选路径,已填12–24月却unknown且refs空,少一条可用推荐理由但不阻断参考结果;现sufficientAlternatives被尾句白名单及其他分支括号保护挡住。69公积金路径pass,年龄理由正确指实际36岁。本轮不改模型结果或运行代码、不追加收费重试。63项离线核对及独立语义/协议审查有证据;259后端PHP/77前端运行文件摘要保持。29合成表空/6触发器/DB15空,所属服务正常关闭、51978/51979释放。证据D:/AAdesk/202609/airz-backups/response-bindings-live-20261016-r1,详见docs/短编号真实模型复测-20261016.md。下一步建议只针对明确任选公积金路径做本地修正及反例回归,不能把本轮称为全部语义通过或直接发布;新语义尚未实施/实测。无生产/常驻worker/发布/小程序发行/提交/推送,生产仍为20261016-report-diagnostics-r1。新evaluation-once.started及6逐call标记保留,禁止重跑本收费入口。下条“尚未完整实测”为修复阶段历史状态。
- 最新本地修复(继续先不发布、跳过真机):已处理前条真实首批失败的长hash回传脆弱性及摘要提示。新capture冻结aiResponseBindingPolicy=batch-item-reference-v1,模型仅回传同批短ref,后端按冻结目录恢复完整绑定后继续严格校验;错ref/跨批/重复/缺项/额外字段/对象冒充数组仍拒绝,不按返回位置猜测。checkpoint与aggregate仍用canonical完整绑定,未知策略即使全量检查点恢复也拒绝;无标记旧目录保持旧协议,已保存报告不改。主报告提示/示例删除要求输出用户自填、核验状态等流程说明,影响后续新生成正文(含旧任务未来重试),不宣称旧messages字节不变。6后端运行文件修改+新增ProductAiBindings,252其他后端PHP及77前端运行文件摘要保持。126编解码/188流程(另含257年龄)/70摘要/819实际隔离任务断言分别通过,11PHP语法及相关既有内存回归通过;独立复核无剩余实质缺陷。5次显式假模型、0真实调用,验证保存回读与第二批失败后只续剩余批,原null金额、0笔/0张、all_loans及省份保持。原真实首批业务字段显式离线投影到新ref后通过12条件/16模板,旧原响应仍被拒绝、8份原证据摘要保持,不等于新模型或完整评估通过。29合成表空/6触发器/DB15空,51878/51879所属服务正常停止、端口释放;任务前259源码摘要保持。证据D:/AAdesk/202609/airz-backups/response-bindings-20261016,详见docs/短编号与摘要提示修复-20261016.md。无生产/常驻worker/发布/提交/推送;生产仍为20261016-report-diagnostics-r1。下一步建议用新目录、防重入口和新capture进行完整真实模型复测,失败即停,不能重跑上轮收费入口;本轮未新增付费调用,不能宣称25款语义或未来摘要已实际通过。下条为原失败记录。
- 最新真实模型验证(继续先不发布、跳过真机):用户明确“直接用真实模型验证”。全新本机51778/51779隔离环境执行1份合成资料/25款保存配置的新冻结评估(16本地年龄+66模型条件/100模板,最多6批、不自动重试)。首批实际1调用即AI_INVALID_REPORT,10620输入/3027输出token、22.032秒,0检查点、无报告,后5批未调用。已定位产品58/template_qa_4的templateHash被模型从64字符抄成65字符,ProductAiTemplates.php:223拒绝;离线原响应重放复现,独立内存副本只改这一处hash即首批通过,不代表原模型或全报告通过,原响应/防重标记保持。冻结离线探针16年龄pass、四金额not_applicable且原null保留;真实首批无补填请求,身份信息未外发。另见摘要仍写“用户自填、尚未核验”,基础提示及示例确有此要求,列入后续清理。本轮不改运行源码,258后端/77前端摘要保持,29合成表清空/6触发器/DB15空,本轮两服务正常关闭、端口释放。证据D:/AAdesk/202609/airz-backups/age-ranges-live-20261016-r1,说明docs/年龄区间确定性判断-20261016.md新增实测节。无正式库/发布/常驻worker/提交/推送。下一步应先解决长hash回传脆弱性及摘要提示;不能重跑本轮收费入口,不能以本次验证宣称完整评估通过。
- 最新本地修复(继续先不发布、用户跳过真机):已补齐明确独立年龄区间的后端确定性比较,新个人资料参考评估冻结ageRangePolicy=identity-age-range-v1;纯年龄保留完整判断清单但不再交AI决定,按原身份证派生的冻结周岁直接比较。缺年龄仍unknown、不加分、不因此补填/阻断;明确不符仍拦,软条件按原分值。严格名称/语法/备注白名单及新ageOnly尾句保护保留性别/收入/单位/例外等完整复合条件;既有四类年龄与期限拆分口径保留,产品原文及用户内容不改。无标记旧目录/报告/检查点不升级,capture显式启用而freeze新末参默认关;模板沿用本地三态,全年龄无模板保留单个正文批。4后端运行文件修改+新增ProductAgeRules,新增257项专项通过,覆盖34模板跨批/部分检查点恢复及真实capture门控;6PHP语法及相关既有内存回归通过。25款原始配置离线冻结重放:82条件中16年龄本地/66继续AI、100模板/6批,1分性别条保留AI,原样本及旧语义保持;非新真实模型输出。独立源码/测试审查未见实质问题,前端运行源码保持,无生产/数据库/真实模型/常驻worker/发布/提交/推送。说明docs/年龄区间确定性判断-20261016.md,证据D:/AAdesk/202609/airz-backups/age-ranges-20261016。旧测试加载缺依赖已用安全wrapper验证;一次错误退出的合成临时目录已归档副本,删除被自动审批以blocked by policy拒绝,原临时目录保留。下一步可补新规则的隔离任务链路验证或整理累计发布候选,不能自动接续发布、付费调用或真机。
- 最新本地修复(继续先不发布):已统一档案详情零金额的“不适用”展示,覆盖下条验收发现的展示差异。复用现有同版报告完整度请求,仅取evaluated v2报告中四个金额fieldKey唯一且明确not_applicable的保存状态;同assessmentId/revisionId时,只在展示副本将原null金额显示“不适用”。明确填写的0/正金额、原详情/编辑回填、完整度和provided计数保持;旧报告、无报告、读取失败及缺标记不凭当前笔数推导,重复/冲突标记不隐藏缺项。档案行使用专用fieldKey/isMissing元数据,原通用报告字段类型保持;没有新增HTTP请求。账户/离页/请求序号保护保留并加当前详情id/revision核验。2个前端运行文件增量+1个新回归脚本,18组新场景、原零金额15/地区提交22通过,HBuilderX5.26微信29页编译通过;独立审查无实质问题,74个其他前端运行文件及257个后端application PHP摘要保持。原用户填写内容/前序未提交修改保留,无后端/生产访问/模型调用/发布/提交。证据D:/AAdesk/202609/airz-backups/archive-display-20261016(红绿日志、编译日志、before、implementation.diff、verification.json);详见docs/零贷款零信用卡处理-20261016.md“档案展示统一”。微信真机与浏览器视觉未验收,真实模型/常驻worker仍沿用前条限制。下一步可在用户微信真机上验收累计前端改动,当前先不发布继续有效。
- 最新本地验收(继续先不发布):完成零贷款/零信用卡的隔离持久化、实际任务入口、真实本机HTTP与小程序UTS回放。新增2个PHP集成测试;持久化735项(26次显式假模型)、HTTP605项/119请求(5次显式假模型)、业务回包8组及最终HTTP回包5场景/31组通过,各套件不合并计数。个人/企业适用字段的空金额与显式零计算等价,原null/已填金额、all_loans、省份和原文本保持;新旧版本隔离、检查点续跑、明确不符、鉴权/越权和5份实际PDF下载通过。6份PDF共37页已渲染总览并核对重点零金额页,未见明显重叠/截断/乱码,保留原版式尾页留白;PDF中文文本提取不完整,未以该工具证明正文全文。257个后端运行PHP及76个前端UTS/UVue摘要保持,无运行源码修改/新编译;新增测试省略隔离bootstrap均在应用初始化前拒绝。29张合成表清空、6个原触发器保留、Redis DB15空,所属51678/51679服务正常关闭,HTTP进程/模式文件清理。证据D:/AAdesk/202609/airz-backups/zero-balances-e2e-20261016,详细verification.json与docs/零贷款零信用卡处理-20261016.md追加验收节。仍有展示差异:档案详情原始空金额显示“未填写”,资产区“已记录”,同版报告为“不适用”且不扣分;本轮未改此展示。若下一步统一字样,应仅按同revision已保存报告的not_applicable映射,不按当前表单倒推历史。真实模型语义、微信真机及常驻worker未验收;零真实模型/生产访问/发布/提交/推送,生产仍为20261016-report-diagnostics-r1。当前先不发布继续有效。
- 最新本地小改动(先不发布):用户要求小程序不显示 DeepSeek 等模型/供应商信息。已移除报告“报告信息”中“分析来源:用户自填资料 · modelCode”整行,这是前端唯一主动展示具体模型的绑定;状态、匹配、助手元信息与 PDF 未发现同类型号字段展示。仅1个报告模板文件有运行增量,后台模型调用/日志/历史快照和 API 字段保留;不修改用户填写内容。微信29页编译通过,未真机/浏览器视觉验证、生产访问、模型调用、发布/提交。证据 D:/AAdesk/202609/airz-backups/model-info-display-20261016。静态字段审计不保证任意历史/未来 AI 自由正文不提品牌;本轮未做全局正文替换。上一轮匹配详情和零金额修复继续保留。
- 最新本地修复(先不发布):用户要求清理匹配详情“用户自填、未经核验”等提示,并确认“未参与参考筛选、无需补填”的项目不逐条展示。已修改3个前端运行文件,清理详情及同源对比展示,保留原始资料/历史结果、真实不符和缺填/异常,不改分数或把 unknown 改成 pass。地区提示已确认为产品地区未知被误写成用户城市未提供;年龄截图具体冻结事实尚未取得,不能断言该次漏传年龄。另份25款样本的16条普通年龄判断均引用年龄通过,纯内存已复现已知年龄仍允许 AI 返回 unknown 的保护缺口,本轮不增加年龄判定策略。新增12场景、既有20/19/33项与15零金额场景、微信29页编译及独立审查通过;391后端/74其他前端基线文件摘要保持,未真机/浏览器视觉验收,无生产/数据库/模型调用/发布/提交。说明 docs/匹配详情提示清理-20261016.md,证据 D:/AAdesk/202609/airz-backups/match-detail-copy-20261016;上一轮零金额修复保留,后端生产仍为20261016-report-diagnostics-r1。用户当前暂缓发布覆盖下条“下一步发布”的先前建议。
- 最新本地修复:用户反馈贷款/信用卡填 0 仍被要求完善,已明确授权修改,并选择贷款统一统计全部当前未结清贷款。本地新增冻结零金额规则,0 笔/0 张的关联空金额不扣完整度、不因此暂不评级;已填金额完整保留,矛盾值提示核对。历史贷款口径显式确认,旧报告/执行/检查点不重算。总贷款正数不冒充信用贷款分项,参考建议不因此补填或阻断;后台列名同步说明历史口径。后端270+118新增内存检查及相关既有回归、前端15场景等检查、微信29页编译通过;尚未真机、真实模型或新持久化/worker验收。本轮未发布/收费/提交/推送,生产仍为20261016-report-diagnostics-r1。详见docs/零贷款零信用卡处理-20261016.md,证据D:/AAdesk/202609/airz-backups/zero-balances-20261016。下一步应先配套发布后端,再由用户发行小程序;新口径须新建评估,不能用旧任务重试验收。本条覆盖下方“无需重发小程序”的历史状态。
- 最新发布:用户明确“帮我发布一下后端”,四文件脱敏诊断候选已发布为20261016-report-diagnostics-r1,2026-10-16 11:12:47(Asia/Shanghai)恢复入口及唯一新worker。137路径、30表维护期业务/结构保持、29服务器/31公网HTTPS及历史报告只读核验通过。PHP默认error_log为stderr,unit配置journal;同身份独立PHP探针的sink/标记通过,未触发真实worker拒绝事件。未新增模型调用或正式评估重试,小程序无需重发。现场档案D:/AAdesk/202609/airz-backups/production-release/airz-report-diagnostics-20261016,详见docs/后端校验诊断发布-20261016.md。下方“未发布/需发布授权”为先前本地阶段状态。
- 最新本地工作:用户反馈两次格式校验失败;评估471/版本718首次和重试均停第4批,前三批有效。前轮已授权的唯一1次真实诊断第4批通过(9780输入/2985输出token,约21秒),未复现原错误,不等于修复或正式报告完成。正式任务仍failed/3检查点/5次历史调用;本轮没有新增真实调用。
- 本轮按“继续下一步”完成脱敏拒绝诊断本地补丁,3修改(DeepSeekException/ProductAiBatches/AnalysisBatchTasks)+1新增AiReportDiagnostics。固定规则分类、文件/行/源码摘要、冻结批号及请求/响应摘要/用量;无正文/用户值/任意键,日志故障不影响任务,不放宽校验或自动收费。新增43纯内存+10任务诊断检查、原25分批任务及相关规则回归通过;29合成表清空、6触发器保留、DB15空,所属51578/51579服务停止。四文件候选与证据D:/AAdesk/202609/airz-backups/report-rejection-diagnostics-20261016;未发布/提交/推送,77个小程序UTS/UVue摘要保持。详见docs/AI输出校验诊断补充-20261016.md。
- 当前下一步:诊断补丁已发布;原失败根因仍未知。需要恢复时应另获付费授权后重试原记录,同模型沿用3检查点,最多继续4批,不新建重跑7批。线上为20261016-report-diagnostics-r1,服务器单次诊断model-call.started已确认保留,禁止重跑唯一收费入口。下方2026-10-15的新建评估建议为历史状态。
- 本轮发布收尾:28份现场备份及阶段证据已下载逐份核SHA,服务器/本机传输临时文件已清理,宝塔已退出并关闭专用浏览器;最终回执为上述发布档案/release-receipt.json。77个小程序UTS/UVue摘要保持,无提交或推送。
- 用户最终要求:地区只选一级省份,如广东省;不要恢复省/市两级选择。全国34省级单选已本地完成。
- 当前状态:后端已发布20261015-profile-advice-r1,2026-10-15 17:54:14(Asia/Shanghai)恢复入口/worker,6文件整体替换;用户已发行小程序,本轮无需重发。现场135路径、维护期30表业务/结构一致、25产品新策略冻结/7批、3历史报告/12组匹配/3详情/25产品及29服务器/31公网HTTPS通过。唯一worker ready,观察无错误/警告;本轮零模型调用、无正式新评估、未提交/推送。记录docs/后端资质参考建议发布-20261015.md。
- 路径:前端 D:/WorkSpace/cyun/ai融资;后端 D:/AAdesk/202609/airz。先读紧接的最新工作和 docs/superpowers/plans/2026-10-15-city-options.md,再按需查看源码。
- 部署前已验证:690项PHP、108项UTS、18项受控H5检查、微信29页编译;该开发验证轮没有新模型调用或正式库访问。证据 D:/AAdesk/202609/airz-backups/city-options-20261015/verification.json,截图/日志/增量差异同目录。
- 本轮新增验证:436项省份真实持久化、58项版本、514项默认城市HTTP/158请求、533项省份HTTP/162请求、68项地区事实检查通过;各套件单列,不合并重复覆盖。证据D:/AAdesk/202609/airz-backups/province-persistence-20261015/verification.json。29张合成表清空、6个原有触发器保留、Redis DB15无键;仅本轮隔离MySQL/Redis关闭,51478/51479端口释放。255个后端运行PHP摘要与执行前一致。
- 发布候选:rc-20261015-profile-advice-r1已实际部署,6替换/0新增/0删除,固定manifest与原ZIP摘要保持。现场与最终回执D:/AAdesk/202609/airz-backups/production-release/airz-profile-advice-20261015,27份备份及阶段证据下载核验,临时传输已清理,面板退出并关闭;历史候选/收费证据保持。
- 建议下一步:在用户已发行小程序中新建一份评估,查看未采集细项不补填/不阻断、完整可选路径、明确不符保护与资料回填;新评估预计7次调用消耗现有额度,本轮未代为创建。旧任务重试/旧报告保持旧冻结口径。真机暂按用户此前安排未复测,微信版本/发行时间/包摘要未知;4条固定理由的后续申请核实措辞按需另议。保留新策略资料/检查点/用量/报告和兼容能力,优先向前修复,不直接旧代码/整库回退。
- 语义边界:未知细项保留unknown,仅未参与参考筛选,不改pass、不加分,不表示银行全部准入通过。省份不猜城市,产品地区待确认不改全国;明确不符、当前表单缺填/格式异常、配置异常和分析未完成仍体现。此前合成真实模型样本21pass/61unknown,25参考候选各基础80分;本轮仅部署及只读验收,没有生产真实模型/真机测试。
- ID56当前处理:新策略不再索要申请金额/本笔月供或相关银行细项,不因此阻断参考建议;原负债条件和粒度保留,无法确认的完整条件仍unknown。第5次真实评估证据位于product-ai-live-20261015-r5,其execution.started保留,禁止重跑原入口重复收费。
## 最新工作:2026-10-16 校验拒绝脱敏诊断已发布
用户明确授权后,固定候选原包部署3替换/1新增,不改严格校验、提示词、评分或自动重试。11:09:03部署、11:12:47恢复入口与worker;137路径及新增归属核验,30表维护前后完整业务/结构一致,只同步资源版本。服务器29/公网31可信HTTPS、3历史报告/12匹配组/3详情/25产品及3越权拒绝通过;3服务active、唯一新worker ready、观察未见错误/警告,任务和三队列空。本次不重试评估、不新增模型调用,保留收费入口防重标记。日志实际为PHP stderr→unit配置journal,同身份独立PHP合成sink与默认输出标记通过,尚无真实业务拒绝事件闭环。原根因仍未知,小程序无需重发。详细记录docs/后端校验诊断发布-20261016.md。
## 历史工作:2026-10-16 校验拒绝脱敏诊断本地完成(发布前)
用户“继续下一步”承接本地补齐具体拒绝位置及离线验证,本轮无生产访问或模型调用。新增43项纯内存与10项实际任务诊断检查,原25分批任务及模板89/批量36/参考建议59/条件68/grounding81/年龄55/地区68通过;包含套件不重复累计。生产失败响应未保存,前轮仅1次授权诊断第4批全部通过,无法确定原两条失败具体分支;不宣称已修复。
三修改/一新增运行文件,仅增强AI_INVALID_REPORT的内部观测;不删填写项/用户数据,不改提示、严格校验、三态或收费规则。PHP error_log记录两次白名单后的批号、固定规则分类、文件/实际拒绝行/源码摘要、请求/响应摘要和用量;日志异常不影响原错误及检查点。全新隔离MySQL/Redis实际任务入口通过且已清理关停,77个小程序源码摘要保持。四文件本地候选和本地修改前快照在D:/AAdesk/202609/airz-backups/report-rejection-diagnostics-20261016;完整说明docs/AI输出校验诊断补充-20261016.md。未发布/提交/推送;生产日志落点、补丁上线和原评估继续执行尚未验收或授权。
## 历史工作:2026-10-15 自填资质参考建议后端已发布
用户明确“发布后端”,覆盖此前暂不更新线上限制。候选rc-20261015-profile-advice-r1已原包发布为20261015-profile-advice-r1,17:53:40部署、17:54:14恢复入口及唯一worker(Asia/Shanghai)。6替换文件和其他129路径摘要、PHP7.4.33与25正式产品/82条件/100模板/7批新策略冻结编译通过。维护窗口备份、排空、正常停止worker/PHP、整体替换后重载;30表完整业务行/结构保持,只同步资源版本。
只读读取3份历史报告、12组匹配、3份详情和25产品,3项跨归属拒绝;29服务器/31公网可信HTTPS通过。唯一worker命令/归属/新PID/NRestarts=0及ready心跳核验,观察期日志无错误/警告,活动评估/模型调用/三队列为空。27份现场备份及阶段证据下载并逐项重算摘要;临时凭据/传输已清理,面板退出并关闭。最终回执D:/AAdesk/202609/airz-backups/production-release/airz-profile-advice-20261015/release-receipt.json,服务器备份/root/airz-profile-advice-20261015。完整记录docs/后端资质参考建议发布-20261015.md。
默认自填真实、未采集细项不补填/不阻断、unknown保持未知不加分、职业/单位白名单及完整路径口径已上线,未删填写项/用户数据。小程序已由用户发行,本轮无需重发;未新增付费调用/正式评估/真机复测,未提交/推送。下一步新建评估查看新口径,旧任务及旧报告不重算。正式写入已恢复,保留新策略快照/检查点/报告/用量,优先向前修复,不旧代码或整库覆盖。下方“未发布/需授权”为发布前历史状态。
## 历史工作:2026-10-15 自填资质参考建议发布准备完成(发布前)
用户“继续下一步”承接上一轮“准备6个后端文件发布”的建议,本轮固定候选rc-20261015-profile-advice-r1、拟发布版本20261015-profile-advice-r1;保留此前“先不更新线上”的明确要求,没有生产访问/发布或新增模型调用。
与上次已发布20261015-assessment-region-batch-r1的135文件回执对照:128字节一致、6业务变化、1路由纯换行差异,路由不替换。六替换文件为ProductMatching、ProductAiConditions、AssessmentNeedsReport、ProductAiTemplates、ProductAiBatches、ProductExplanations;无新增/删除运行文件。六份旧文件字节精确匹配回执,5份来自上次已核runtime.zip,ProductExplanations来自实施前快照。当前255后端PHP及105小程序源码/配置与本轮前真实模型验收摘要一致,不重打前端包、不删填写项或资料。
两ZIP共12成员的路径/CRC/摘要/12份PHP语法及证据绑定等642项检查通过;backend-runtime.zip为42511字节,backend-recorded-baseline.zip为37770字节,摘要及新旧逐文件清单已固定。无DDL、业务迁移、产品编辑、依赖安装或模型配置变化;4条固定理由的后续申请阶段核实措辞保留。
操作单docs/资质参考建议发布准备-20261015.md,固定清单docs/发布候选/profile-advice-20261015-r1/manifest.json,验证同目录verification.json;证据D:/AAdesk/202609/airz-backups/profile-advice-release-preparation-20261015-r1,含两归档、package-receipt.json、release.diff、verify-candidate.py及before-docs。后端入口database/profile-advice-release-preparation-20261015.md。
下一步实际发布须明确覆盖“先不更新线上”的限制后,重新核对现场目标/版本/135文件及最新目录,备份实际6旧文件,暂停新增写入并排空活动调用与三队列,维护窗口整体替换、重载PHP及唯一worker,无付费只读核验后恢复并记录回执。未恢复写入且未产生新策略快照时才能按现场备份恢复;恢复后保留所有新资料/检查点/用量/报告和兼容能力,优先向前修复,不直接旧代码/整库回退。当前尚无本轮现场预检或最新生产状态证据,不用旧服务PID/队列状态冒充当前。
## 历史工作:2026-10-15 自填资质参考建议真实模型验收通过(未发布)
用户在建议用合成资料进行一次真实完整评估后回复“执行吧”,本轮按新建任务、新冻结profileAdvicePolicy执行,7批真实deepseek-v4-flash-0731经TokenHub调用全部成功,无付费重试,未发布。目录为历史录入回执重建的25款个人产品,不是最新生产导出;82条条件/100条模板完整覆盖,7检查点、最终报告、匹配项及所属用户回读一致。任务138.103秒,API累计136.422秒,输入68165/输出19580 token,人民币账单未查询。
核心口径通过:21pass/61unknown/0fail,所有missingFields为空,主报告improvements为空;61条未知保持未知、不阻断参考筛选、不加分,25款为参考候选且各基础80分。公积金完整可选路径正确,年龄与收入理由正常;职业/单位7批外发且8条相关条件实际引用,姓名/完整身份证/手机号排除。广东省不猜具体城市,不补造单位连续关系、平均基数、个人缴存额、性别或征信。表单分析前后内容和回填保持,完整度100%,分级公式未改。
928项结构/用量/隐私/持久化/清理检查、161项定向语义检查通过。另214项纯离线检查验证本次真实7检查点零调用恢复报告/匹配一致,以及25款实际匹配项的追加解释解析契约;解释文本是合成夹具,没有额外付费解释调用。报告比较显式排除生成时间和持久化层追加的revisionId/revisionNo,正文与业务判断完整比较。
保留文案观察:主报告仍说明尚未外部核验;4条固定理由有“进一步核实申请额度/申请资料”的后续申请阶段措辞,没有当前补填或筛选阻断。本轮不改产品原文;若用户希望完全消除该措辞,可另行润色。单份样本无明确fail,不代替既有不符保护回归、企业/正式目录/生产worker/微信真机验收。
证据D:/AAdesk/202609/airz-backups/profile-advice-live-20261015-r1。早期三个预检脚本问题均在首次实际模型调用前修正,两次零调用启动证据已保留,不计入7次;evaluation-once.started必须保留,不得重跑run-evaluation-ready.php。6张合成业务表清空、6个原触发器保留、Redis DB15为空,本轮所属MySQL/Redis停止,51378/51379释放。255后端运行PHP及105小程序源码/配置摘要与执行前一致,第5轮26原证据摘要保持;无正式库访问、生产发布、提交或推送。
详见docs/资质参考建议实测-20261015.md。下一步可按用户安排准备6文件配套发布,小程序无需因本轮后端变化重新修改;新增付费复测和正式发布继续需要另行明确授权。本节覆盖下方“未做真实模型验收”的旧状态,不覆盖仍未发布的边界。
## 历史工作:2026-10-15 自填资质参考建议本地实施完成(未发布)
用户最新确认:默认已填内容真实;未采集或粒度不足的银行细项不补填、不阻断建议;允许外发职业类型和单位名称,姓名、完整身份证、手机号仍排除;允许必要后端配套调整。小程序所有填写项与用户数据保留,继续先不更新线上、不新增付费调用/真机复测、不提交/推送。此约定覆盖下方“仅提示词、缺细项仍阻断”的历史边界。
新capture为资质模式冻结profileAdvicePolicy=self-reported-profile-advice-v1。新策略AI判断仍pass/fail/unknown,所有missingFields为空;已完成unknown保留证据,标记未参与参考筛选,不阻断、不加分,不套旧补填模板。明确不符、待执行/超范围和配置异常保留。标准规则先验证原配置,再以self_reported参考副本比较;原银行配置和事实来源保持。未采集标准细项缺失不阻断,当前表单真实缺填或格式异常仍体现。
补齐25款共有的“适用地区尚未核实”拦截:仅该编译器固定提示且regionMode=unverified、regions=null时不阻断参考筛选;不标全国、不猜城市,其他配置问题仍阻断。职业occupation与单位employer只在新个人白名单外发,长度100/50、拒绝不安全文本,明确为不可信用户数据;企业外发边界不扩大。主报告待完善项只接受真实缺填/无效/一致性依据,unknown模板不展示,单产品解释对未参与细项使用服务端固定说明。
本轮仅修改6个既有后端运行文件:ProductMatching.php、ProductAiConditions.php、AssessmentNeedsReport.php、ProductAiTemplates.php、ProductAiBatches.php、ProductExplanations.php;新增tests/product_profile_advice.php。未改产品文本/配置、完整度/融资等级公式、表单、持久化或数据库结构。冻结标记计入目录/执行/检查点绑定;旧报告、旧任务及重试继续原口径,不动态补入职业/单位或新策略。
新59项检查及既有条件68、模板89、分批36、年龄55、grounding81、地区68、分级122、匹配151、展示20、发布编译122及投影11项均通过;包含套件不重复累计。7个PHP语法检查通过。两个旧直接入口的既有自动加载缺失用仓库外隔离加载器解决,旧测试未改,数据库与模型访问阻断。未启动隔离DB或重跑真实API/worker链路。
242项离线回放通过:第5轮原7批用户payload、82判断、100模板规范结果和匹配统计保持。新口径适配夹具采用广东省输入,仅清空61条unknown缺失清单,保留21pass/61unknown;未移除引用或新增理由适用结论。25款成为参考候选、各仍基础80分,完整度/融资等级保持,7检查点零调用恢复。这是后端逻辑和兼容证据,不是新模型结果或银行资格通过。
证据D:/AAdesk/202609/airz-backups/profile-advice-20261015:before、before-hashes.json、implementation.diff、verification.json、replay.php、replay-result.json、适配检查点/报告、source-integrity.json。255个后端运行PHP中仅上述6文件变化;105个小程序源码/配置摘要保持。第5轮证据及execution.started摘要保持,无默认.env读取、数据库/Redis/生产访问、新模型调用、部署或提交。实施说明docs/资质参考建议调整-20261015.md,计划docs/superpowers/plans/2026-10-15-profile-advice.md。后续复测/发布继续等用户明确安排,不能只发布提示词或套旧任务重试验证新口径。
## 历史工作:2026-10-15 全25款资质与补填口径离线核查完成
用户要求“先不更新线上”,并授权继续核查其余24款同类问题。本轮连同ID56完成25款/82条历史硬条件/100条理由的对照,新增docs/产品材料/信用贷资质与补填口径离线核查-20261015.md及配套JSON。逐条列出现有可复用字段、保留但不外发字段、核实范围、历史原结果和审核意见,完整保留来源段落、原产品条件、原始手输配置、版本/摘要/引用/缺失项及模板。来源是第5轮合成资料及录入回执重建目录,不是正式库新导出,也不是银行现行政策核验。
确认历史问题:ID56负债条件索要本次申请金额;ID58进件/ID61客群路径已获公积金12至24个月区间仍笼统索要连续缴存时长;ID75/77/78已有深圳城市代码仍索要同义地点;ID74网点条件只写申请地区,提示不够具体。不能把已填区间当精确年限、当前基数当平均或个人缴存额;后两者缺证据仍应unknown,不直接改pass。当前新表单只选省份,不能套用历史深圳的通过预期。
其他提示风险:部分unknown混列多条可选路径资料;圆梦金已有公积金基数,不能固定要求代发作为唯一缺口。职业/单位/电话已采集、保存和回填但按隐私白名单不外发,模型不可见不代表用户漏填;仅提示词不能补齐这些事实。卡张数/发卡机构数、贷款笔数/贷款机构数、当前值/历史均值、总月供/剔除房贷后的信贷月供等继续区分。12条独立理由条件涉及6款,其中5款10条保留客群关系歧义,惠民贷另2条是已采用征信通用口径,不能把它们当新增硬条件或已完整覆盖全部银行门槛。
历史样本当前表单完整度100%、待完善项为空,但25款均待核实;21条pass、61条unknown、0fail,17条推荐理由展示、无不推荐断言展示。这说明表单已提供与银行所有条件已核实不能混为一谈,不为减少unknown删用户字段、改机构门槛或自动通过。unknown未引用某事实也不足以单独证明漏用。
本轮31项离线结构/绑定核对通过:82条条件及100模板与7次历史请求、原始响应、最终报告逐项一致,25份完整产品原文及未规范化年龄配置保持;25个文档章节/82个表格条件行完整。255后端运行PHP+45相关小程序运行文件共300摘要不变,9份原始材料/证据含execution.started摘要保持。证据D:/AAdesk/202609/airz-backups/product-profile-audit-20261015,生成及验证入口只读本地文件,不初始化应用。
无新增运行代码或提示词改动、数据库/生产访问、模型调用、部署、发行、提交或推送。上一轮2文件本地提示词改动保留、尚未发布;所有小程序填写项与既有数据保持。本轮仅形成审核清单,后续如继续优化仍限提示词,可优先补三类措辞样例;真实模型/真机和线上更新继续暂缓,不重跑历史收费入口。
## 最新工作:2026-10-15 仅调整资质评估AI提示词(本地完成,未发布)
用户明确选择“只调整AI提示词”,并要求保留小程序全部现有填写内容。本轮仅修改后端ProductAiConditions::instructions的说明文字及AssessmentNeedsReport::messages的system提示词;小程序填写项/提交/保存/回填、事实白名单、产品原文、输出结构、校验、评分、分批和用量策略均保持。本轮前后255个后端运行PHP中仅这两个文件变化;45个相关小程序运行文件摘要保持。
profileOnly=true的条件/模板请求增加提示:已有资质按原含义继续使用;本次申请金额、融资期限/贷款年限、资金用途、本笔新增贷款月供不列为当前补填项或表单不完整依据。产品约束仍保留,不能将未知当零或把部分满足当整条通过;按原有与/或关系判断,明确不符仍fail,无法确认完整条件仍unknown。unknown允许用简短资料名称或核实事项说明,例如“申请阶段负债核实依据”;共同借款人、信用卡历史均值、房产价值只在适用条件实际需要且缺依据时核实,不能用现有月还款、当前卡使用额或有房替代。主报告待完善项仍按既有完整度/异常/矛盾依据生成,固定理由正文若索要已取消需求字段或断言未确认条件通过则提示不展示。
这是提示词约束,没有新增服务端缺失项过滤或语义强制校验;模型仍可能不遵守。现有unknown至少一项及“资料不足,需补充”的展示框架保持,因此不能承诺已彻底区分表单缺填与申请阶段核实。已保存历史报告不重算;部署后重建的profileOnly请求及重试使用新提示,已成功批次按既有检查点复用,本轮未触发任何任务。
本地验证:89项模板、36项分批规划、68项自定义条件、55项年龄、81项事实语义保护、68项地区、122项分级回归通过,各套件单列。另15项改动前后请求兼容核对通过:用户消息载荷逐字节一致,完整负债原文/事实/摘要/引用保留,隐私仍排除,profile标记控制附加提示、模板上下文同样覆盖;同一合成unknown输出通过原校验与报告解析,前后保存内容一致、不进入候选。2文件PHP语法及增量差异检查通过。以上不代表新的真实模型语义验收。
证据D:/AAdesk/202609/airz-backups/profile-prompt-20261015,含修改前文件、请求对照、兼容验证入口/结果、增量差异与最终摘要。本轮无数据库/生产访问、模型调用、部署、发行、提交或推送;用户已反馈发行小程序,但未提供版本材料且暂缓校验。后续仅需发布这两个相对上一已发布基线的后端变化,仍应先核对现场基线和任务状态;本轮不需要小程序改动或重新发行。
## 最新工作:2026-10-15 评估/省份/分批后端已发布,小程序用户发行
用户明确要求“你帮我发布后端,然后我自己发布小程序”,本轮按固定候选rc-20261015-assessment-region-batch-r1发布9个后端库,版本20261015-assessment-region-batch-r1,15:37:32完成替换、15:38:53恢复入口与唯一worker(Asia/Shanghai)。此前发布准备的未授权状态由本轮授权和实际回执覆盖,小程序未由代理发行,无提交/推送/PR。
现场133既有文件与2新增路径符合基线、.env保持;PHP7.4.33,站点/www/wwwroot/xctai.cn/public及https://airz.hqling.cloud确认。7文件替换、2类新增,默认三款/年龄/分批执行与检查点/缺失项不限固定数量/可选公积金路径与理由填值/34省事实已整体上线。无DDL、业务迁移、产品修改、依赖安装或模型额度变更,province_code仅在版本JSON保存,历史结果不重算。
当前正式25款个人产品只读编译为7批,82条件/100模板完整覆盖,各批均在32项/48000字节/32模板槽位限制内;没有企业适用产品。Flash、20次/用户/日与最大3次评估尝试保持,完整个人评估需要7次实际模型调用,和当日其他调用共用额度;没有新真实模型请求或当前耗时实测。原历史152秒样本仍只作参考。
本轮生产核验:135路径摘要,维护期30表业务行与结构一致;只读事务读取2现有报告/8匹配分组/2详情/25产品及2归属拒绝,34省字典存在;29服务器与31独立公网可信HTTPS通过;精确恢复材料干跑通过未回退。新worker PID1726835,唯一进程、正确命令、NRestarts0,15:39:47最终心跳ready/idle且0秒新鲜,三服务active,活动评估/调用/三队列均0,观察期worker日志无错误/警告。PID是本次证据,不代表后续永久状态。
执行和备份证据D:/AAdesk/202609/airz-backups/production-release/airz-assessment-region-batch-20261015,服务器/root/airz-assessment-region-batch-20261015;27份现场备份与阶段证据已下载逐份核摘要;服务器目录700/文件600,本机仅当前用户与SYSTEM访问,临时传输及宝塔会话已清理,Playwright证书校验保持开启。最终release-receipt.json、backup-download.json、server-cleanup.json和panel-cleanup.json记录摘要核验及收尾。后续入口已经允许正式写入,恢复工具拒绝resumed后直接回旧代码,须保留新资料/批次/检查点/用量/报告和兼容读取,优先向前修复,不能整库覆盖。
发布说明docs/后端评估配套发布-20261015.md和后端database/assessment-region-batch-release-20261015.md。下一步用户重新编译发行微信,记录版本/时间/上传包摘要,并验证省份选取/回填、确认/详情、历史报告、默认三款/查看更多/对比及处理中离开返回。微信真机、新省份真实模型语义、旧客户端编辑新省份资料未验收;ID56申请金额/本笔月供仍独立待核对。未重新编译或上传微信,本轮没有发行H5。
## 最新工作:2026-10-15 累计改动配套发布准备完成(未部署)
用户授权继续上一轮建议的本地发布准备。本轮固定候选rc-20261015-assessment-region-batch-r1,新增操作单docs/评估配套发布准备-20261015.md、docs/发布候选/assessment-20261015-r1/manifest.json及verification.json,后端说明database/assessment-release-preparation-20261015.md。没有修改前后端运行代码或测试,不访问服务器、数据库、模型,不启动worker、不新编译/发行/提交/推送,已有未提交改动保留。
以20261014-product-ai-r1历史发布回执核133文件:125字节一致、7个业务变化、application/route.php仅换行差异(统一CRLF后匹配回执),路由不替换。另新增AnalysisBatchTasks/ProductAiBatches两类;后端完整候选9文件覆盖默认三款、身份证年龄、分批执行与检查点、待补资料无固定数量上限、明确公积金路径/填值校验及省份事实,不能只发布最后省份文件。前端13累计运行文件纳入完整206文件源码归档,保留依赖与资产,不含旧编译产物、文档、测试或私密配置。
仓库外归档D:/AAdesk/202609/airz-backups/release-preparation-20261015:backend-runtime.zip、frontend-source.zip、backend-recorded-baseline.zip及package-receipt.json。恢复包7旧文件均逐字节匹配上次生产回执;5个从摘要核对的历史runtime.zip提取,另外2个从Git对象重建换行后匹配回执,不直接把HEAD视为线上。两新增路径是否当前不存在仍须现场确认,历史旧基线包不替代本次正式备份。
本轮本机验证3ZIP/222成员的安全路径、成员集合、逐文件字节与CRC;9候选+7恢复PHP语法、220处前端@/导入依赖、34省同源生成检查及两仓库差异检查通过。255后端运行PHP与前轮省份持久化证据完全一致,8份历史证据摘要保持;没有重复累计业务检查或声称本轮新跑了数据库/真实模型/微信编译。构建脚本首次将回执release字段误按目录名比对而拒绝,修正为releaseId后生成;无业务源码或历史回执改写。
兼容/顺序/回滚已在操作单落实:旧城市请求可由新后端承接;新省份字段和all分组不兼容旧后端,必须先后端再小程序。正式新增省份/分批快照前可在维护及无调用条件下精确恢复旧文件;形成新资料、检查点或报告后保留数据及兼容读取,默认向前修复,不直接代码/整库回退。小程序当前实际发行版本、时间及旧包摘要未知,不能承诺精确前端恢复。
下一步需明确后端发布授权后再按操作单核目标https://airz.hqling.cloud/根/www/wwwroot/xctai.cn、133记录+2新增路径、当前正式目录与预算、任务/调用/队列,备份后排空并仅停止所属worker,再整体替换/加载/无付费冒烟。上述地址及服务名来自旧回执,不是本轮在线检查。分批增加实际请求次数与耗时,历史25款/82条件/100模板样本为7批,当前正式目录须只读复核;不自动新增付费评估。用户再编译发行微信,真机和最新省份提示真实模型语义仍待验收;ID56口径保留独立待办。
## 前轮工作:2026-10-15 省份资料隔离持久化与HTTP验收完成(未部署)
用户授权“继续下一步”,本轮落实上一节建议的隔离保存与回读验收。新增后端tests/province_persistence.php,复用现有输入、服务和假模型;tests/api_assessments.php增加可选--province-fixture模式,默认城市用例保留。未修改前后端业务运行源码;255个后端application PHP文件执行前后SHA256完全一致,所有既有未提交改动保留。
436项新增持久化检查:全国34省级地区分别通过个人/企业创建、版本快照、只读及编辑详情回读、最新资料和原键重放;省份代码仅在版本form中保存,真实user_info无province_code列,city_code为空。个人/企业广东→江苏编辑及假模型报告保存、地区事实、重复消费、旧报告与旧输入不改写、原键重放不回退最新资料、处理中/过期基版本冲突、跨账户拒绝、非法名称代码配对、旧深圳自动城市代码/旧南京未知代码及转省份均通过。两个独立PHP连接先READY再统一放行,实际竞争用户锁:同键新建/编辑只生成一次,同键不同省份REQUEST_CONFLICT,不同键同基版本编辑只有一个成功,失败方REVISION_CONFLICT。
HTTP使用真实本机API和合成用户鉴权:默认城市514项/158请求通过;省份533项/162请求通过,覆盖创建/详情/编辑/历史读取、个人广东→江苏和企业广东→香港、错误配对与同键改省拒绝无写入,并保留原有鉴权、并发、事务故障回滚和报告重试覆盖。另58项原版本集成、68项地区纯内存、两测试文件PHP语法及无bootstrap拒绝门禁通过。检查数按套件分别记录,不把默认/省份重叠覆盖或失败/重复运行累计为总量。
全新本机MySQL5.7/Redis,独立目录和数据库、仅127.0.0.1:51478/51479、Redis DB15;不读取正式.env。框架初始化前替换DeepSeekClient为构造即拒绝的类,各PHP进程退出复核替换保持;分析仅显式传入进程内假模型,未启动真实worker。持久化专项4次假调用,HTTP两模式各5次假调用;不是新真实模型语义结果。首次版本回归因本轮生成的隔离测试密钥用了十六进制而非项目要求的32字节Base64失败,修正隔离配置后全部通过,失败日志保留且不累计;隔离ROOT的PDF依赖从仓库既有锁定引擎/字体复制并核332文件摘要,无安装。
每组测试结束独立核验全部29张表为空、6个原有触发器保留、Redis DB15键0。最终核对PID创建时间、exe、完整命令行和loopback监听归属后正常关闭仅本轮MySQL/Redis,两PID退出且51478/51479端口释放;数据目录保留。证据D:/AAdesk/202609/airz-backups/province-persistence-20261015,含verification.json、checks.json、cleanup.json、各套件日志、执行前源码摘要、before-docs、测试原件及隔离准备/关闭脚本;私密配置不整包分享。
本轮未部署、发行、访问正式库、改正式产品、调用付费模型、提交或推送。省份持久化专项使用空合成产品目录,HTTP/版本回归使用既有演示产品,不能冒充最新正式目录验收;微信真机、生产worker和新提示的真实模型语义仍待相应条件。下一步可本地整理累计改动配套发布清单、兼容与回滚方案;实际发布和新付费调用另需明确授权。ID56申请金额/本笔月供口径仍独立待核对。
## 前轮工作:2026-10-15 全国省份一级选择与冻结地区事实(本地完成,未部署)
用户最终明确选择“只选省份(如广东省)”,覆盖本轮先前全国省/市两级方案。填写页改为“所在省份”,全国34个省级地区在单层可滚动列表中展示,点选即完成;空表单不默认选第一项,取消不改变选择,重开定位已选省份。已知历史城市回填为所属省份;未识别的旧手填地区需用户重新选择,历史原报告和原版本不改写。确认页、档案详情和匹配证据均支持省份中文标签。
新请求沿用form.city保存显示名称,另带provinceCode,cityCode=null;后端严格校验省份名称/代码配对并拒绝同时携带具体城市。province_code仅保存在评估版本快照,写user_info前移除,不新增数据列;详情可回传provinceCode。旧请求未携带新字段时规范化形状与五城自动推码保持,原幂等重放不变。原CITIES没有扩充为全国城市;省份事实与城市事实分别表示,不能把广东省当成深圳市。
新冻结目录增加regionFactsPolicy=region-label-v1、cityNames及provinceNames并绑定摘要。实际已知城市外发代码与冻结中文displayValue,省份须与版本输入名称一致才外发provinceCode事实。未知策略/非法映射拒绝,旧目录保持原事实及显示标签,不从当前字典补新事实。提示明确省份不能证明具体城市、户籍、工作城市、申请地区或银行网点;城市名单所需资料不足仍unknown,本轮没有把模型结论自动改pass。上轮三款漏用深圳事实只完成同类本地输入/边界回归,未重新调用真实模型,不能声称新提示实际语义已通过。
地区来源为版本化2023-06-30内地数据和项目已有省级字典,不宣称为2026最新区划。历史城市数据只用于省份回填,港澳台采用省级选项,不猜具体城市代码。前后端由同一JSON生成并有一致性检查。
验证:690项PHP纯内存检查(新增地区68+原共享622)、108项UTS行为/展示检查、18项受控H5交互检查、5文件PHP语法及本轮差异检查通过;HBuilderX5.26微信29页编译成功。Browser插件不可用,使用已有Playwright/Chrome,仓库外副本development配置、拦截全部外部请求,零真实外呼和业务提交。H5仍有既有layout.uts窗口resize类型警告。独立审查发现详情标签赋值位置问题,已修正并新增省份/旧城市详情回归;不累计失败和重复运行。未执行数据库持久化/并发、微信真机、生产worker或新模型真实评估;创建至重放的完整持久化链路仍建议后续隔离验收。
运行改动:后端AssessmentRecords/ProductMatching/ProductAiConditions/AssessmentNeedsReport;前端填写页/确认页/records/matches、新增省份选择组件及同源地区服务。未部署、修改正式产品、提交或推送;已有未提交修改全部保留。证据D:/AAdesk/202609/airz-backups/city-options-20261015,含before、implementation.diff、源码摘要、回归日志、微信编译日志和省份选择截图。下一步可先对省份输入完成隔离保存/回读验收,再在获得具体授权后安排配套上线或新一轮真实评估。
## 最新工作:2026-10-15 第5次真实完整评估通过流程与目标修正,城市判断仍有待办(未部署)
用户按上一轮“重新跑一次真实完整评估”的建议授权“继续下一步”。本轮当前本地新提示与校验+同一合成资料+25款录入回执重建目录,执行7批真实deepseek-v4-flash-0731经TokenHub调用;不是正式库新导出。82条件/100模板/25款全部合并、保存和所属用户回读通过,7个成功检查点,无重复、漏项、AI_CONDITION_LIMIT或AI_TEMPLATE_LIMIT。任务152.027秒、API累计150.174秒,输入62274/输出20402 token;人民币账单未查。含前四轮累计17次,输入152462/输出51043 token,无自动付费重试。
上轮目标修正本次实际通过:ID69惠民贷明确公积金路径原始输出pass、引用fact:housingFund、缺失为空,相应推荐理由展示;ID59收入原始填20000元;ID64/67/74/75/80年龄理由均为“你的年龄(36岁)符合……”。仅ID74/75原始填“36岁”由服务端规范为“年龄(36岁)”,其余已按新提示直接生成;保留原始响应,不把规范化值冒充模型原文。ID56/75/79仅年龄区间pass、不索要贷款年限;ID76分性别条件缺性别保持unknown。ID56 rule_qa_4本次实际返回5项资料并完整保存,真实验证不受4项上限阻断。
本轮21条件pass、61unknown、0fail;25款仍全部待核实、80基础分,17条推荐理由展示,83条模板关闭,不支持的不推荐断言未启用。相比第4轮23pass,惠民贷新增1条pass,但ID75润薪贷/77徽享薪易贷/78鹏城e贷的rule_qa_5地区条件由pass变unknown,分别索要“申请地区/所在城市/所在城市”,尽管已提供cityCode=440300且准入名单包含深圳。当前请求仅有代码,未同时携带可信代码名称映射,模型本轮没有使用已有城市;不能把流程通过说成全部语义通过,也不直接断言全部原因。建议下一步补齐冻结城市代码名称映射、区分所在城市/户籍/申请地区/网点,先对这3条做定向回归。ID74网点覆盖条件仍应保留未知,不能凭城市就判覆盖;其“申请地区”缺失提示也不够准确。
ID56负债原条件与资质评估口径仍是独立待办,本次5项资料包含“本次申请金额”,原文仍含本笔贷款月供。本轮不改产品条件、不删缺失项、不以完整保留等同资料全部适用。征信未知、公积金基数与个人缴存额的区分继续保持;7批未外发姓名、完整身份证、电话、单位名或新增性别。
9个关键运行文件执行前后SHA256一致,无前后端运行代码修改、正式库访问、线上产品修改、部署、提交或推送。6张合成业务表、Redis DB15清空;仅关闭归属明确的隔离MySQL/Redis,51378/51379端口释放,数据目录及6个schema触发器保留。未验收最新正式目录、生产worker或微信真机,尚不能认为线上已生效。
证据D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r5,含评估复测结果.md、calls.json、result.json、context.json、report.json、matches.json、usage-logs.json、verification.json、verify-results.py、semantic-review.json、per-product-audit.json、cleanup.json及before-docs。execution.started保留,不重跑本入口重复收费。原第4轮及更早目录保持只读;下一步是城市事实与提示口径的本地定向修正,不需立即再次整轮付费重跑,本轮尚未实施该新行为。
## 最新工作:2026-10-15 可选分支漏判拦截与理由填值修正(本地完成,未部署)
用户在第4次完整评估后授权“继续下一步”,本轮按上一节建议实施本地修正。新冻结目录增加aiGroundingPolicy=product-ai-grounding-v1并绑定目录摘要;旧冻结目录保持原判断与填值,不重算历史报告。仅对明示任选、完整且无附加门槛的公积金分支提取sufficientAlternatives;已有正向公积金事实命中时,模型必须pass并引用fact:housingFund,矛盾的unknown/fail或无关引用会被拒绝,不自动改写模型结论。未命中不等于不符合,也不代表整款产品通过。
提取范围保持保守:备注与尾句仅允许空或已核对的固定路径说明;分号只拆顶层,圈号分支须同级;未支持的编号、嵌套括号、共同要求、时长及组合歧义退回模型判断。独立审查发现的备注约束、分支内顿号、尾句共同要求和其他编号反例均补回归;实际ID69惠民贷原路径仍能提取。年龄主语也直接比对实际fact:age,不能借条件上下限冒充申请人年龄。
理由仅规范可确定的填值,不改固定模板:月收入无固定单位且裸数字等于所引实际月收入时补“元”;“你的{X}符合……年龄要求”中的实际年龄36/36岁规范为“年龄(36岁)”。原有单位换算、引用、版本/摘要及三态校验保持。分批内部unknown占位只在未发布的中间证据副本停用新语义策略,模型响应、检查点和最终报告仍严格按原冻结目录验证,模型不能借占位文字绕过校验。
622项不重复内存检查通过:条件68、身份证年龄55、模板89、分批36、新定向81、匹配151、发布编译122、展示20;6个PHP文件语法和本轮差异检查通过。使用第4轮未改写的7批原始响应零外呼回放:旧目录7批用户payload和报告判断/填值保持一致(系统提示已变化,不声称完整消息摘要不变);新目录第4批原惠民贷漏判被拒绝。另用明确标注的对照夹具仅改ID69一条条件及两条绑定模板,通过7批合并、7个检查点恢复和25款结果,6处文案规范化、17条推荐理由,整体仍25款待核实。夹具不是新的真实模型结果,也未发布。
本轮修改4个后端运行文件、1个既有测试并新增1个定向测试,保留全部既有改动;未访问数据库/Redis、未新增模型调用、未改正式产品、未部署、提交或推送。证据D:/AAdesk/202609/airz-backups/product-ai-grounding-20261015,含修正与验证说明.md、before、before-docs、implementation.diff、verification.json、review-regression.json、replay-result.json和对照夹具。原第4轮目录及execution.started保持只读。
下一步是按新提示重新进行真实模型完整评估,验证模型能正确返回明确分支;当前证据证明拦截与兼容,不证明新提示已实际解决模型语义问题。正式配置、生产worker和微信真机仍待配套验收;部署与新一轮付费调用本轮均未执行。ID56“本笔贷款月供”与资质评估口径仍是独立待办,本轮未改其条件。
## 最新工作:2026-10-15 第4次真实完整评估流程通过,语义仍有待办(未部署)
用户授权“再重新跑一次完整评估”。本轮当前本地代码+同一合成资料+25款回执重建目录,完整执行82条件+100模板/7批;不是正式库的新导出。7次真实deepseek-v4-flash-0731经TokenHub调用均返回并通过结构校验,任务succeeded,报告保存及所属用户读取回验通过。逐项对照请求、原始响应、7个成功检查点与最终报告完全一致,25款、82条件、100模板无重复/遗漏,无AI_CONDITION_LIMIT或AI_TEMPLATE_LIMIT。任务156.204秒,API累计154.939秒;输入61326/输出20371 token;未查人民币账单。含前三轮首批失败,累计10次、输入90188/输出30641 token;没有自动付费重试。
最终23条条件pass、59条unknown、0条fail,25款均为待核实;16条推荐理由适用,其余84条未展示,所有不推荐理由均未启用。25款仍沿用80分基础分,不代表全部符合或获批概率;自填完整度100%也不等于产品资料齐全。本次模型实际待补资料每条1—4项,没有超量阻断;模型未返回5项以上,超4项保留能力沿用前轮离线回放与回归证据,不伪称本次实测到。
年龄按36岁判断;ID56/75/79均仅核年龄区间并pass,不索要贷款年限;ID76分性别条件缺性别依据保持unknown。ID57/66/77可选公积金路径均pass,ID72没有混淆个人缴存额与缴存基数,征信未知未编造成符合或不符。7批白名单事实无姓名、完整身份证、电话、单位名或额外性别事实。
语义质量尚未全面通过:①ID69惠民贷rule_qa_1明示满足一条且有公积金缴纳分支,已提供公积金12—24个月却被模型判unknown,仍要求交行信用卡、代发、按揭信息,相应推荐理由关闭;应按冻结明确分支判pass,整体仍另受征信未知约束。②ID59集团E贷理由“月收入为20000”缺元单位。③ID64/67/74/75/80五款理由为“你的36符合……”,数字槽位与句式不匹配。④ID56仍要求本笔贷款月供,与资质评估取消本次申请需求采集的口径需独立核对。本轮保留原始输出,未修源码、模板或正式产品,也未把格式通过等同判断正确。
9个关键后端文件摘要未变,无正式库访问、部署、提交或推送。6张合成业务表、Redis DB15已清空;归属明确的隔离MySQL/Redis已关闭,51378/51379端口释放,datadir及6个schema触发器保留。未覆盖最新正式配置、生产worker或微信真机验收,前轮本地改动仍未配套上线。
证据D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r4,含评估复测结果.md、calls.json、result.json、context.json、report.json、matches.json、usage-logs.json、verification.json、semantic-review.json、per-product-audit.json、verify-results.py、cleanup.json及before-docs。建议下一步先修复可选路径漏判与填值文案,并用已保存案例定向回归;当前只获评估授权,尚未实施新修正或获部署授权。execution.started须保留,不重跑收费入口。本节覆盖下方“后6批未调用/无完整报告”的最新现状,历史失败记录不改写。
## 最新工作:2026-10-15 取消待补资料条数上限(本地完成,未部署)
用户明确要求“不限制待补资料数量”。本轮取消 ProductAiConditions 对 missingFields 的4项固定上限,模型提示同步为只列相关且不重复的缺失资料、不设固定条数;合法列表全部保留,不截断或归并。unknown仍至少1项,pass/fail仍必须为空;每项保持非空字符串、最多60字及控制字符/HTML检查,重复项、非法列表结构继续拒绝。条件绑定、版本/摘要、证据和三态一致性保持。此约定覆盖下方“最多4项”和“5项以上拒绝”的旧记录。
419项不重复内存检查通过:自定义条件68、身份证年龄55、AI模板89、分批36、匹配151、展示20;另2个PHP语法及差异检查通过。5项、6项和64项均可完整经过校验、报告解析与匹配理由输出,非法列表及空/非字符串/过长/HTML/控制字符名称仍拒绝。回归先复现旧4项限制再完成修正;首次预期失败触发框架临时日志及空目录清理警告,日志已归档、该测试临时目录已定向清理,成功检查不累计失败或重复执行。
对第3次真实评估未改写的原始首批响应离线回放:冻结请求payload完全一致,15条件+16模板可解析,ID56两条条件的5项、6项资料完整保留,判断和模板填值与原响应一致。关闭理由仍不展示,原公积金引用保留。本轮没有新模型调用或数据库访问,不能将此回放当作完整7批真实评估;后6批仍未调用,未生成或发布完整报告。
本轮仅修改1个后端运行文件、1个测试及本交接/规则契约,保留既有改动;未部署、提交或推送。证据D:/AAdesk/202609/airz-backups/missing-fields-unlimited-20261015,含before、before-hashes.json、implementation.diff、tests.diff、replay-response.php、replay-result.json、verification.json。原第3次真实评估目录和execution.started记录保持;本节只覆盖新的本地校验口径,不改写历史失败状态。原始列表中“本次申请金额”等已取消采集项的适用性核对仍是独立待办,本轮不借取消条数上限改写该内容。
## 最新工作:2026-10-15 第3次真实评估未通过(待补资料返回超量)
用户授权“重新跑一次完整评估”。本轮用相同合成资料和25款回执重建目录运行当前本地代码,计划82条件+100模板/7批;不是正式库新导出。第1批15条件+16模板返回后AI_INVALID_REPORT,成功检查点0,后6批未调用,无完整或部分报告发布,没有自动付费重试。真实deepseek-v4-flash-0731经TokenHub调用1次,API25.150秒/任务25.530秒,输入9671/输出3276 token;三轮累计3次、输入28862/输出10270 token,账单金额未查。
原始响应的唯一结构阻塞是ID56优客分期两条unknown条件的missingFields分别返回5项(rule_qa_3)和6项(rule_qa_4),超过已经明确提示和校验的最多4项。完整请求可离线精确重建;逐项13/15条件、16/16模板通过。仅在内存诊断夹具截为4项后整批可解析,夹具未发布且原始输出未改写,不能将此视为原始评估成功。
上一轮引用例外本次已通过;ID57不展示理由可保留空引用,条件本身公积金证据仍在。首批年龄按36岁判pass、任一客群分支按公积金12—24个月判pass均正确;征信缺失保持unknown、不支持的不符理由未启用,ID59收入本次填“20000元”。ID75/79及后6批未调用,不声称完整语义验收通过。另待核对ID56待补资料包含已取消采集的“本次申请金额”;本轮未修正源码或条件。
建议下一步将最多4项作为可归并的资料提示限制:合法待补资料去重、过滤已取消采集项后,归并或限制到4项,保留原始输出追溯,不改变三态和证据校验。本轮只执行评估/诊断,尚未实施该行为变更;不得直接重跑execution.started入口重复收费。
9个关键运行文件摘要未变,无正式库访问、部署或提交/推送。6张合成业务表、Redis DB15已清空;自建隔离MySQL/Redis已关闭,51378/51379端口释放,datadir与6个schema触发器保留。初次隔离服务连接参数不兼容已在模型调用前修正,正式服务未改。证据D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r3,含评估复测结果.md、calls.json、offline-diagnostics.json、result.json、usage-logs.json、verification.json、cleanup.json及before-docs。
## 最新工作:2026-10-15 适度放宽不展示理由的重复引用校验(本地完成,未部署)
用户要求“适当放宽校验”。本轮仅调整 ProductAiTemplates 的一处引用要求并同步模型提示:理由已绑定有效条件、结论与该条件一致且 applicable=false 时,允许 evidenceRefs=[];values 仍必须为空。关联条件保留已校验的原始证据,既不补造引用,也不改写模型结论。实际展示的 pass/fail 理由、未绑定条件的 pass/fail 理由仍须引用事实;所有提供的引用仍检查白名单与去重,版本/模板摘要/三态一致性、槽数与金额单位校验保持。
新增16项回归先复现 AI_INVALID_REPORT,再随最小修正通过。403项不重复内存检查通过:AI模板89、分批36、自定义条件52、身份证年龄55、产品匹配151、产品展示20;另2个PHP文件语法与本轮差异检查通过。覆盖已绑定但不展示的pass/fail、保持准入/分数/排名、不显示关闭理由,以及缺失关联条件、非法/重复引用、非空填值、版本/摘要错误、实际展示无引用和自由理由无引用仍拒绝。
对第2次真实评估的原始首批响应进行了零模型调用离线回放:冻结请求payload完全一致,15条件+16模板均可解析,判断与填值均未改写;ID57 template_qa_3继续不展示且引用保持空数组,rule_qa_3的公积金证据仍保留。此结果仅修复首批解析阻塞;后6批仍未调用,不能据此声称完整真实评估已通过,没有发布完整或部分报告。
本轮只修改1个后端运行文件、1个测试及本交接/规则契约文档,保留全部既有改动;无数据库访问、新增模型调用、部署、提交或推送。证据:D:/AAdesk/202609/airz-backups/template-validation-20261015,含before、before-hashes.json、implementation.diff、replay-response.php、replay-result.json、verification.json。下一步可重新进行完整7批真实评估,确认全部产品的判断与结果展示后再安排配套发布;本轮尚未执行该付费评估或获得部署授权。
## 最新工作:2026-10-15 第2次真实模型评估仍未完成(引用约定阻塞)
用户按“重新跑一次完整评估”的建议授权继续。本轮同一合成资料、25款回执重建目录再次发起真实7批任务,82条件+100模板;不是正式库的新导出。仅第1批15条件+16模板返回后AI_INVALID_REPORT,后6批未调用;成功检查点0,无报告或部分结果发布,没有自动付费重试。模型deepseek-v4-flash-0731,经TokenHub,API26.789秒/任务27.203秒,输入9652/输出3478 token。含上次累计2次、输入19191/输出6994 token,人民币账单未查。
本批15个条件均通过校验。待补资料4项已正常接受;中银E贷任一项准入正确以公积金12—24个月判pass并引用fact:housingFund;优客分期年龄18—65岁仅按36岁判pass,不再索要贷款年限。ID75/79尚未实际调用,不能宣称全目录语义通过。征信仍unknown,不支持的不符理由未展示;ID59收入模板仍填纯数字20000,正文缺单位问题保留。
唯一离线复现的阻塞是ID57 template_qa_3:模型正确将不推荐理由设为conditionOutcome=pass、applicable=false、values=[],但evidenceRefs=[]。当前提示及ProductAiTemplates::validate要求所有pass/fail理由至少一条引用,即使理由不展示,因此整批失败。16理由独立校验仅此项失败;仅为该项补fact:housingFund的诊断夹具可让整批解析通过,但夹具未发布、原响应未改写。建议下一步调整已绑定且不展示理由的证据复用约定,展示理由仍严格校验;本轮没有实施该代码修正,也没有获得新的付费重试或部署授权。
本轮不修改运行源码,关键文件摘要保持;没有正式数据库访问、结构变更、部署或提交/推送。6张合成业务表和Redis DB15已清空;归属明确的独立MySQL/Redis已关闭,51378/51379端口释放,datadir及6个原约束触发器保留。证据D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r2,含评估复测结果.md、calls.json、offline-diagnostics.json、result.json、usage-logs.json、cleanup.json、verification.json及before-docs。不要重跑execution.started标记的脚本造成重复收费。
## 最新工作:2026-10-15 待补资料上限4项、只判断年龄(本地完成,未部署)
用户明确要求“最多4项”“只判断年龄,不用管贷款年限”,并询问任一分支误判是否为DeepSeek问题。已将自定义条件unknown的missingFields由1—3项放宽到1—4项,提示与校验同步;5项、重复项、pass/fail携带缺失项仍拒绝。原始首批响应现在可以通过解析,4项全部保留,没有改写原输出或将语义误判当成通过。
个人新资质评估冻结目录时,ProductAiConditions::ageOnly将明确年龄区间+贷款年限/期限组合编译为仅年龄between条件;原产品行、原文和已冻结编译条件保留。当前25款涉及ID56优客分期18—65岁、ID75润薪贷23—60岁、ID79民易贷22—60岁;ID75/79关联AI理由的组合措辞同步规范为“年龄要求”。分性别年龄要求不合并,旧需求模式不应用该变换。按本次新口径,36岁无需贷款年限即可满足ID56年龄项;下方将该pass列为问题的记录仅针对之前的组合口径。
任一分支误判是上次DeepSeek实际输出中的错误,服务端按约定消费其三态;已强化模型指令,明确完整分支之间为或、分支内部必需条件为与,并给出公积金12—24个月满足半年以上分支的例子。本轮没有重新调用模型,不能宣称真实语义问题已消除,也没有补造全量成功报告。
509项不重复内存检查通过:自定义AI条件52、身份证年龄55、AI模板73、分批规划36、匹配151、发布编译122、展示20。新增4项上限、5项拒绝、重复项、报告解析保留4项、年龄区间/关联理由/真实请求、旧冻结和分性别兼容回归;先观察预期失败再修正。原始首批15条件+16模板零模型调用回放解析通过,重建25款仍为82条件+100模板/7批。原OR误判保持在历史响应,未伪造为语义通过。
本轮只改2个后端运行文件及2个测试,保留前轮未提交改动;无正式数据库访问、结构变更、模型调用、部署、提交或推送。证据:D:/AAdesk/202609/airz-backups/assessment-policy-20261015,含before、before-hashes.json、replay-result.json及verification.json。先复测真实模型,再安排配套发布;前端匹配页面、身份证年龄和自动分批等本地改动仍尚未配套上线。
## 最新工作:2026-10-15 真实模型完整评估尝试未通过(未部署)
用户授权“帮我跑一次完整评估”。本轮在显式独立MySQL/Redis中,以一份合成个人资料和已保存回执重建的25款目录发起真实任务,计划7批覆盖82条件+100模板;不是正式库的新导出,地区未捕获项继续未核实。模型为deepseek-v4-flash-0731,实际仅第1批调用:15条件+16模板、4款产品,API约27.434秒,输入9539/输出3516 token。整批校验失败AI_INVALID_REPORT,后6批没有调用;成功检查点0,无报告或部分结果发布,无自动付费重试。未查询人民币账单。
已用原始响应零调用离线复现,重建请求与实际请求完全一致。直接阻塞为ID58 rule_qa_1的missingFields返回4项,超过最多3项;15条件逐项校验仅此项格式失败,16模板独立格式与关联校验通过。另发现语义错误:ID57任意一项准入已有公积金12—24个月却判unknown;ID56“年龄+贷款年限≤65”只凭36岁判pass,模型输入缺少贷款年限或相关上限依据。ID59月收入填20000无单位也通过现有数值校验,正文应补金额单位。格式通过不能代替语义正确,不能据此宣称25款已完成真实验收。
有效身份证正确在本地计算36岁并只外发age,姓名/完整身份证/电话/单位名不外发。未提供征信保持unknown,本批不支持的不符合理由未启用。没有修改运行代码或放宽规则来让结果通过;关键9个后端文件摘要不变,没有访问正式业务库、部署、提交或推送。六张合成业务表与Redis DB15均清空;本轮归属的独立MySQL/Redis已关闭,51378/51379端口释放,原6个schema约束触发器和数据目录保留。
下一步先针对原始响应修正格式稳定性及“任一分支”/组合条件的语义边界,补回归后再进行一次完整真实模型评估,通过后再部署和微信真机验收;本轮只授权评估,没有实施运行代码修正或获得部署授权。不要直接重跑run.php收费调用,execution.started标记与本次用量应保留。
证据:D:/AAdesk/202609/airz-backups/product-ai-live-20261015,含评估验收结果.md、calls.json、context.json、result.json、usage-logs.json、offline-diagnostics.json、source-verification.json、cleanup.json及before-docs。下方自动分批“本地通过”仅指假模型/隔离测试,不代表本次真实模型验收通过;匹配页面与年龄等本地改动仍未配套上线。
## 最新工作:2026-10-15 AI总量取消固定上限、内部自动分批(本地完成,未部署)
用户明确批准“总量不设固定上限,内部自动分批”。后端在仓库外隔离worktree实现并验证后,已定向回填 D:/AAdesk/202609/airz。新执行目录冻结 aiBatchPolicy=product-ai-batches-v1,移除新任务全目录32项AI判断及100款/2MB的固定总量截断;单次调用仍限制32项、48KB描述、32个填值槽。优先整款打包,单款超限继续拆分;单条本身过大明确失败,不能静默忽略。没有该策略的历史冻结任务保留原口径,已发布报告不重算。
全部批次共用同份提交资料、冻结日期和产品目录。跨批引用的自定义条件沿用已校验结果,同产品同名自由理由保持相同三态;缺资料仍为unknown,理由不改硬准入、分数或排序。批内只复算本批理由关联产品的证据,完成后再对完整目录统一校验、计算并原子保存。analysis_context顶层 aiBatchProgress 绑定输入/执行/内容摘要、版本、模型和批次计划,逐批保存已校验前缀;中断重试复用有效前缀,不改冻结摘要。重试更换模型时对同份冻结输入/目录从头判断,不混合模型结果。
每次实际调用分别记录用量并检查追加调用额度、续租和worker心跳;超时、格式错误、额度不足、租约过期或任务被替换均不能推进无效检查点或发布半报告。所有批次完成后若最终保存失败,重试可零模型调用重新校验并发布。保留原每日调用保护及任务重试次数;提交/重试仍预留一次任务额度,零调用的发布重试也沿用这项既有预留语义,用量不虚构、不重复累计。
本轮642项不重复检查通过:AI模板73、分批规划/校验36、自定义条件48、身份证年龄22、自定义规则29、三款预览11、产品匹配151、产品展示20、分批任务25、报告持久化75、版本58、任务调度36、准备度/报告HTTP58。分批任务覆盖中途超时仅重试剩余批次、无半报告、用量、篡改检查点、过期/替换任务、每日额度、模型切换、全部批次完成后的发布回滚与零调用恢复。另按已保存回执重建25款,82条件+100理由在pass/fail/unknown三个合成情景均分7批完整覆盖,不再出现AI_CONDITION_LIMIT/AI_TEMPLATE_LIMIT;这不是新导出的正式数据,也不验证真实机构政策与模型语义。
独立只读代码审查无确认的阻塞缺陷。纯内存100/200款样本的20/40批处理约0.463/1.044秒,峰值30/48MB;仅说明本地假模型执行性能,不能外推真实API耗时。后端7个运行文件与2个测试回填前核对原7个源码摘要未变,保留此前年龄/匹配预览等既有未提交改动;无正式库访问或结构变更、无新增迁移、无真实模型调用、无提交/推送/部署。独立MySQL/Redis仅使用随机合成记录,最终六张业务表与故障注入触发器、Redis键均清空,保留原schema的六个约束触发器;本轮隔离服务关闭情况见cleanup.json。
证据:D:/AAdesk/202609/airz-backups/product-ai-batching-20261015,含implementation-plan.md、before/before-copyback、worktree、verification.json、task-checks.json、catalog-batch-qa.json、逐项测试日志、review.json及copyback.json。此前匹配页面/身份证年龄改动仍未配套发布。下一步是部署授权后备份并发布后端、重启正式worker,再进行真实模型和微信真机验收;本轮未获部署或付费调用授权,不把本地完成写成线上生效。
## 最新工作:2026-10-15 25款目录与匹配流程离线检查(发现容量阻塞)
用户要求“继续下一步”,本轮按前次建议完成配置、模拟匹配和本地页面检查,没有新增产品或修改运行代码。依据2026-10-14保存的录入回执重建ID56—80共25款;保留真实产品编号、参数与文案,规则/模板编号和版本使用测试值,不是新导出的正式冻结快照。回执未捕获的地区保持未核实;额外“全国地区”实验仅用于隔离容量因素,不代表政策变更。
优先问题:25款包含82个自定义硬条件和100条AI理由模板,现有ProductAiConditions::plan全目录最多请求32项,ProductAiTemplates::plan共享额度且硬条件优先。实际只请求32个条件,另外50个标记AI_CONDITION_LIMIT;100条模板请求数为0,全部AI_TEMPLATE_LIMIT。按编号排序,ID56—65前10款硬条件均进入请求,ID66—80后15款没有硬条件进入请求。最后一款职享贷单独冻结时3条件+4模板均可进入请求,确认是全目录共享容量问题。详情页面已复现“该理由本次超出分析数量、文字或占位符范围,尚未判断”。
按回执重建的配置没有软加减分项、基础分为80,因此pass/fail/unknown模拟均为80%;条件不符仍保留不符状态,不能据80%解释为获批概率,也未擅自改评分。全国控制实验中pass为10符合+15未知,fail为10不符+15未知,unknown为25未知;地区及机构现行政策尚未独立核实。
最终336项逻辑与接口检查通过(前端115、模板73、自定义条件48、身份证年龄22、三款预览11、25款目录52、实际MatchRecords分页/详情15),重复运行不累计。实际分页三组各9页读取25款,无重复/漏项、保留冻结状态。另19项本地页面检查通过:手机390×844/桌面1280×900,报告默认3款,查看更多进入列表并从3款追加到6款;条件不符及资料缺失保留真实状态,年龄36岁可展开查看;模拟processing到succeeded后自动进入报告tab=matches。15%/5%/0%仅用于前端显示实验,不是本批配置的实际引擎分数。页面无错误覆盖层、相关控制台错误或横向溢出;未验收微信真机和真实模型语义质量。
下一步优先设计/实施按整款产品分批判断并统一汇总:本轮仅做只读规划实验,沿用每批32项数量、文字与占位符约束,25款可分成7批,完整覆盖82条件+100模板。尚未实施批处理;以后必须使用同一次冻结目录、绑定版本/摘要、保存批次重试与用量,完整验证后原子保存统一结果,历史报告不重算。请求成本和耗时会增加,完整提示词/响应token及真实模型质量尚未测量,不能仅提高全局上限即认定解决。
证据:D:/AAdesk/202609/airz-backups/product-catalog-qa-20261015,含input-manifest.json、catalog-audit.json、list-audit.json、api-fixtures.json、ui-qa.json、手机/桌面截图、candidate-batches.json、verification.json及before-docs。运行源码前后摘要一致,前端24个运行文件与此前隔离H5副本一致。测试API仅GET且全部指向本机127.0.0.1:18580,无正式数据库、真实模型或业务写入;当前产品可用性为unknown是因为模拟DB边界主动拒绝访问,不能据此推断线上下架。
本地H5首次启动缺少HX_APP_ROOT和随后仅IPv6监听问题已解决;个别浏览器定位超时经刷新状态与支持的AX操作恢复。编译有既有services/layout.uts的onResize/窗口回调类型声明警告,未扩展修复。本轮只更新交接与仓库外检查证据,不部署、发行、提交或推送;测试服务及浏览器清理以cleanup.json为准。前轮匹配页面与身份证年龄改动仍尚未配套发布。
## 最新工作:2026-10-14 后20款已录入并上架
用户明确授权“录入吧,每款附2条推荐、2条不推荐理由,上架”。已通过正式后台新增《信用贷汇总.docx》第6—25项20款,实际ID61—80,全部上架;每款2条推荐、2条不推荐,共80条。当前后台完整列表25款均已上架,原ID56—60五款可见字段、状态及更新时间保持。历史ID36—55未用于本轮更新,不按旧记录重复恢复。
逐款保存后回读,最终405项数据核验通过,涵盖参数、准入条件、适用对象、四条理由、完整原文、来源和其他设置;邮享贷两项个人必需资料已填写。沿用r2口径,建议不升级硬门槛,组合不明处保留待核实;原文保持,结构化比较符仅作等价全角替换。光速贷ID74原文误入字符已在现有记录修正;其他设置展开回读无须再保存。
详见[录入上架回执](产品材料/信用贷后20款录入上架回执-20261014.md)及[配套JSON](产品材料/信用贷后20款录入上架回执-20261014.json)。证据:D:/AAdesk/202609/airz-backups/product-entry-20261014-rest20,含逐款回读、最终25条列表、截图、verification.json及before-docs。下方“尚未新增/未入库/待授权”均为本轮前历史阶段,相关录入上架待办已完成。
本轮没有改运行代码、部署、正式迁移、真实模型调用、提交或推送。前两轮匹配页面和身份证年龄改动仍为本地完成、尚未配套发布,不能把本轮产品上架视为代码上线。
## 最新工作:2026-10-14 后20款当前后台核对(只读,尚未新增)
用户要求“继续下一步”。后台原会话过期,用户重新登录后,在无关键词、全部状态、全部类型下确认当前完整列表仅5条:ID56—60前5款,均已上架;后20款同名产品和历史ID36—55均未出现在当前列表。历史曾导入不代表当前存在,不据此推断删除原因,也不直接恢复旧表或更新旧ID。
已形成[后台核对结果及新增草稿方案](产品材料/信用贷后20款后台核对结果-20261014.md)和[配套JSON](产品材料/信用贷后20款后台核对结果-20261014.json)。拟按上一轮资料新增20款草稿,每款2推荐/2不推荐,不上架;正式写入这20款的明确授权尚未收到。保存前应再次查重,沿用r2口径,原文分支不明保留备注而不造硬准入。
证据:D:/AAdesk/202609/airz-backups/credit-products-current-audit-20261014,含current-list.json、current-list.jpg及before-docs。本轮只读后台并整理本地文档,业务写入0、模型调用0,没有改代码或部署。下方“未读取当前正式记录”仅描述上一轮资料整理阶段;匹配与年龄改动仍待配套发布。
## 最新工作:2026-10-14 信用贷后20款本地资料整理(未入库)
用户暂缓发布和验收,已同意先整理剩余信用贷产品资料。本轮覆盖《信用贷汇总.docx》第6—25项,共20款;已形成[待复核补充清单](产品材料/信用贷后20款整理清单-20261014.md)及[配套JSON](产品材料/信用贷后20款整理清单-20261014.json),包含参数、进件分支、征信原文、每款2条推荐/2条不推荐理由草案,共80条。JSON保留P43—P215完整逐段原文和来源引用;理由全部未录入,不是可直接执行的规则或导入配置。
历史回执显示全部25款曾于2026-10-09导入为草稿并完成r2修订,后20款历史ID为36—55;2026-10-14前5款另有录入记录ID56—60。本轮没有检查当前线上记录,后续应先按机构、产品名、来源和历史ID查重,再补充现有记录,不直接重复新增。月息乘12、分期期数按月、版别/渠道不拆产品及冲突通用口径沿用已确认r2,不重新要求补造范围、有效期或用途。分支关系不明确处保留待核对,建议项不升级硬门槛,资料不足不生成不符合理由。
源文件摘要、20款覆盖、历史ID、数值及80条模板已核对;机构现行政策尚未独立核实。证据目录:D:/AAdesk/202609/airz-backups/credit-products-preparation-20261014,含curation.json、prepare.py、verification.json及before-docs。本轮仅新增两份本地资料并更新交接,没有修改运行代码、正式产品、部署、模型调用或提交推送;下方两轮匹配/年龄改动仍处于本地完成、待配套发布状态。
## 最新工作:2026-10-14 匹配度显示、结果页精简与自动进入(本地完成,尚未部署)
用户要求解释“待确认”无匹配度、精简查看更多下方内容,并在提交生成结果后默认展示匹配结果。已将“待确认”统一为“待核实”;符合、待核实和条件不符三态均展示后端已保存的有效匹配度(含0%),状态单独保留,旧null分数不造分。报告、独立列表、详情和对比口径一致;评分、准入和排序未改,资料不足仍显示“资料不足”。本节覆盖旧阶段“unknown/ineligible不显示百分比”的口径。
报告匹配标签下方移除重复状态统计、长排序说明、咨询AI/下一步行动及报告元信息,仅保留“修改资料,重新评估”和一句匹配度说明;完整行动与报告信息仍在评估概况/改善建议。分析页只在succeeded、hasReport且publishedRevisionId等于本次revisionId时自动redirect到本次报告tab=matches;保留离页、请求代次和账号守卫,重评处理中/失败不打开旧报告。无tab的历史报告默认overview及显式栏目路由保持。失败无服务端消息时显示未完成及恢复提示,不再误写“分析已完成”。
本轮最终115项前端纯检查(匹配预览19、分析跳转12、产品展示19、分包路由33、报告优先级32)、33项Cua合成页面检查通过;390×844、360×780、1280×900无横向溢出/错误覆盖层或浏览器运行警告。HBuilderX5.26于16:47:46(Asia/Shanghai)微信29页编译成功。H5仅运行仓库外副本与本机GET合成API;编译沿用既有layout窗口回调类型警告。编译产物检查曾与编译并行导致文件暂缺,待编译完成后已顺序通过,失败及重复验证不累计。真机和真实模型质量仍未验收。
证据根:D:/AAdesk/202609/airz-backups/match-result-streamline-20261014;before/before-docs、ui-qa.json、fixture-requests.json、verification.json及截图,清理见cleanup.json。本轮修改5个前端运行文件、扩展匹配预览测试并新增分析跳转测试;后端本轮未改、不连接正式库、不发业务POST、不调用真实模型、不部署/提交/推送。上一轮默认三款及身份证年龄的4个后端库仍待配套部署,小程序仍需用户重新发行;不能将本轮编译成功视为上线。
## 最新工作:2026-10-14 匹配页默认三款及身份证年龄(本地完成,尚未部署)
用户已反馈小程序发行(未提供版本与时间),本轮明确批准修改两个匹配入口默认展示3款并从身份证计算年龄。报告匹配标签/独立列表已按原型03改为浅蓝摘要和紧凑机构卡片;后端新增all展示分组,先符合、再待核实、最后不符,同组按分数/ID排序,真实状态、分数和冻结rank保持。0款符合、5款待核实首屏也显示3款;实际不足3款不造产品。查看更多、筛选、对比及登录回跳已配套。
个人新评估按冻结的上海业务日期从有效身份证计算周岁,18位校验码/生日检查并兼容15位;只提供age事实给条件判断、理由填值和报告,身份证不传模型。冻结目录新增identityFactsPolicy=identity-age-v1,旧目录无此策略时不新增age,旧报告不重算;有效身份证的新评估才会生效。无表单新字段或数据库迁移。
最终269项本地纯检查(后端174、前端95)、44项Cua浏览器合成场景检查及6个PHP文件语法通过;HBuilderX5.26微信29页编译成功。H5使用仓库外副本与127.0.0.1:18580合成API,在390×844、360×780、1280×900检查页面/交互,运行控制台无错误,编译保留既有layout窗口回调类型警告。旧product_presentation测试直接入口缺少AI类依赖,已用仓库外隔离bootstrap补载后20项通过;失败及重复运行不累计。真机和真实模型质量未验收。
证据根:D:/AAdesk/202609/airz-backups/match-preview-age-20261014;before-frontend/before-backend/before-docs、ui-qa.json、fixture-requests.json、verification.json及手机/桌面截图。本轮不访问正式数据库、不调用真实模型、不改正式产品、不部署、不提交推送;五条已录入产品及每条2推荐/2不推荐模板沿用上一任务结果。源码前端位于D:/WorkSpace/cyun/ai融资,后端位于D:/AAdesk/202609/airz。
下一步若用户要求上线,应配套发布ProductMatching.php、ProductAiConditions.php、AssessmentNeedsReport.php、MatchRecords.php并安全重启worker,小程序由用户重新发行。不能只覆盖页面或沿用旧部署脚本;本轮没有发布授权。下方20261014-product-ai-r1仍是上次线上版本,本节优先于旧展示/年龄限制。
## 新会话先读:2026-10-14 进度已整理
本轮按用户要求记录进度,未继续开发、重新部署、调用模型或执行验收。开发进度2.36/1.46、技术文档v2.04/1.17已同步。先读取本节和下方最新发布记录,再结合用户的新需求接续;下方旧记录中的“未部署/未迁移”不作为当前待办。
+35
View File
@@ -0,0 +1,35 @@
# 正式模型切换回执(2026-10-19)
用户明确要求将已用完的 `deepseek-v4-flash-0731` 切换为 `deepseek-v4-pro-0813`。正式后端于 **2026-10-19 15:36:29(Asia/Shanghai)** 完成配置切换,15:36:39 独立回读通过,15:40:52 完成补充历史读取核对。本轮仅处理模型切换,尚未推进积分、协议等功能。
| 新请求场景 | 原模型 | 当前模型 |
| --- | --- | --- |
| 资质评估 | deepseek-v4-flash-0731 | deepseek-v4-pro-0813 |
| AI 助手回复 | deepseek-v4-flash-0731 | deepseek-v4-pro-0813 |
| 单产品追加解释 | deepseek-v4-flash-0731 | deepseek-v4-pro-0813 |
## 配置与生效方式
- 正式 `ai_runtime_policy` 配置行仍不存在,运行策略来源为环境配置;未创建策略行或写入业务数据库。
- 正式后端 `.env` 补入 `assessment_model = "deepseek-v4-pro-0813"`,将现有 `model_flash` 映射为 `deepseek-v4-pro-0813`;原 `model_pro` 已为目标值。`model_flash` 是现有助手及产品解释代码使用的配置键,实际模型以其值为准。
- 本地后端 `D:/AAdesk/202609/airz/.env` 同步以上两项,保留本地其他配置。未在本机启动默认 App、数据库或 worker。
- 正式应用重新加载配置后,实际 `AiRuntimePolicy::effective()` 独立回读确认三场景均为 Pro;开关为 true,每人每日 20 次、最多 3 次尝试、90 秒超时、连接超时及 token 上限保持。API 密钥、地址及所有非目标环境字节保持。
- 原 worker 已配置并允许 Pro;消费时使用提交日志中冻结的模型代码。无需重启服务,原 worker PID 和三个服务重启计数保持,唯一 worker 心跳 ready。已有报告和任务的模型字段未改写。
- 未发布后端源码或小程序,此项配置变更无需重发小程序。代码中的默认模型回退值未改动,后续部署须保留本次显式环境配置。
## 核对结果与边界
- 正式环境原子替换及独立回读通过:文件属主、组及 `0640` 权限保持;只变更目标模型行,其他字节保持。切换前完整环境备份保存在服务器私密目录,未将正文或密钥输出至回执。
- 161 个正式源码文件匹配既有生产守卫;25 款产品整行摘要与上一轮加减分事务结果一致,74 条评分规则保持。
- 5 份已发布历史报告、20 组匹配读取、5 组归属边界及详情、25 款产品、34 个省份读取通过;所有检查使用只读事务并拦截模型客户端。
- 旧评分验收中的 5 个会话整行摘要有 4 个保持。另 1 个会话在 **15:19:51** 已变为 failed,当前输入保存错误码 `AI_BALANCE`,早于本次模型切换;原已发布报告仍在并已成功读取。本轮未自动重试。早期两个补充校验回执记录了旧整行摘要守卫退出,诊断回执及修正后按已发布报告指针读取的结果均保留,未修改旧证据。
- 配置预检和独立回读时,活动评估、活动调用和 Redis 三队列均为 0;三个服务 active、原 PID 保持。
- **本轮模型调用 0 次**,未实际测试 Pro 的供应商额度或响应,不据配置回读宣称真实推理已验收。未迁移数据、改评分、重算历史或调整额度。
## 证据与恢复边界
本机私密证据:`D:/AAdesk/202609/airz-backups/model-switch-pro-20261019-r1`。服务器私密目录:`/root/airz-model-pro-20261019-r1`。主要回执为 `switch-plan.json`、`applied.json`、`verification.json`、`historical-hash-diagnostic.json`、`preservation-verification-r3.json`、`local-applied.json`,最终清理与本地保全回执另存于该本机目录。
正式 `.env` 切换前 SHA256:`07e35d8a0a9594c87ac26b8c861c55bf690172dba31b18292b08158155cad705`;切换后 SHA256:`4404c3868ce96a7e038abdedfb0db6c0f1e9c9df952ca8d38c1a0b024c73d2d6`。排除两项目标行后的 SHA256 前后相同。原后端发布清单记录的是切换前环境,今后核验应结合本次两行变更回执,不得覆盖旧发布清单或将合法模型配置变化当作源码漂移。
如后续需恢复,先核对当前环境与本次切换后摘要完全一致,并检查运行中的任务及后续配置变更;仅定向恢复原 `model_flash` 行并移除本次新增的 `assessment_model` 行。恢复材料已保留,本轮没有执行恢复或整文件回滚。
@@ -0,0 +1,74 @@
# 模型响应短编号与摘要提示修复
2026-10-16。承接真实模型首批因长 hash 抄写错误而失败的问题,用户“继续下一步”后完成本地修复。继续先不发布、跳过真机;本轮没有新增真实模型调用、正式评估重试、生产访问、提交或推送。
## 问题与结果
上轮真实评估中,模型将产品58/`template_qa_4`的64字符模板摘要抄成65字符,触发严格绑定校验,首批未保存检查点,后五批未执行。原始响应和防重标记保留,不能把修补响应当成真实模型通过。
新评估改为让模型回传同批短编号 `ref`,后端根据冻结批次恢复产品、版本、条件/模板编号和完整摘要,再执行原有校验。模型不再需要抄写长 hash;短编号仍有严格的格式、数量及归属校验,不能保证未来模型输出一定通过。
主报告摘要提示及示例同时清理:要求简短概括已提供的资料,不再要求输出“用户自填、尚未核验、未经核验、未执行产品匹配”等流程说明。不改写用户输入或历史报告,不改变评分和匹配业务规则。
## 协议与兼容
- 新 `capture()` 冻结 `aiResponseBindingPolicy=batch-item-reference-v1`,标记进入目录和执行摘要;`freeze()` 的新增末参默认关闭,只有显式启用且采用自动分批时才保存标记。
- 每个编号由条件/模板类型、冻结目录与批次的范围摘要、从1开始的批内序号组成。条件和模板各自编号,不能跨批或跨目录复用,也不按返回顺序猜绑定。
- 请求保留产品分组和完整业务文字,移除需要模型复述的版本、条件/模板编号及完整摘要,替换为 `ref`;模板关联条件转换为同产品、同批条件的 `ref`。
- 条件响应只接受 `ref/outcome/evidenceRefs/missingFields`;模板响应只接受 `ref/conditionOutcome/applicable/values/evidenceRefs`。未知、重复、缺项、跨批编号及额外完整绑定字段均拒绝。
- 仅处理真实响应时解码编号。已保存的检查点及最终合并结果仍使用完整绑定,沿用既有校验;不把检查点再次当作短编号响应解析。未知策略即使已有全部检查点也拒绝。
- 保留 JSON 对象与数组形状,禁止把空对象当空数组放行;金额依据、事实引用、三态、模板适用性等原有约束保持。
- 无新标记的旧冻结目录仍用旧绑定协议,历史报告与检查点不升级、不重算。主报告提示清理适用于后续新生成的正文,包括旧任务未来重试;不能宣称旧请求消息字节不变。
- 新协议拒绝写入固定脱敏诊断分类 `RESPONSE_BINDING`,不记录原始响应正文、用户值或任意字段名。
## 改动范围
后端新增 `application/common/library/ProductAiBindings.php`,修改 `ProductMatching.php`、`ProductAiConditions.php`、`ProductAiTemplates.php`、`AssessmentNeedsReport.php`、`ProductAiBatches.php`、`AiReportDiagnostics.php`,共6修改+1新增。新增4个专项测试入口。
相对本轮开始时的精确快照,252个其他后端 `application` PHP 文件及77个小程序 UTS/UVue文件摘要保持。前序未提交改动全部保留,不改数据库结构、产品条件、表单、任务状态机或收费重试策略。
## 验证证据
各套件有包含与重叠,不把下列数量相加作为独立断言总数。
| 验证 | 结果 |
| --- | --- |
| 短编号编解码 | 126项通过,覆盖精确字段、归属、关联、重复/缺项、形状及旧协议 |
| 冻结至批次解析流程 | 188项通过;入口另含257项年龄断言 |
| 摘要提示 | 70项通过,包含上轮保存资料经实际 prepare/messages 的检查 |
| 实际隔离任务链路 | 819项通过,5次显式假模型调用,0次真实调用 |
| PHP语法 | 7个运行文件及4个新测试,共11文件通过 |
| 原有内存回归 | 条件68、模板89、分批36、grounding81、身份证年龄55、地区68、参考建议59、零金额270、零事实118、匹配151、展示20、脱敏诊断43项分别通过 |
任务链路使用合成产品与资料,通过实际创建、冻结、任务执行、检查点保存、失败续跑、报告持久化及所属用户回读。包含18条本地年龄、18条AI条件、36条模板、2批;完整成功用2次假调用,另一任务在第二批模拟超时用2次假调用,续跑只调用剩余1批。完整绑定稳定,原始空金额、0笔/0张、全部贷款口径及省份保持。
新增任务入口省略精确 `--bootstrap=` 参数即拒绝。默认后端环境指向正式库,不能直接启动应用、任务或worker。一次误选的旧 `product_explanations.php` 入口被隔离守卫拒绝,未执行;展示回归使用正确的 `product_presentation.php`,不把被拒绝的入口列为验收通过。
独立源码审查提出的“完整检查点恢复遗漏未知策略检查”和“列表形状拒绝的诊断分类遗漏”均已修复,并补红绿回归;最终复核未见剩余实质缺陷。
编解码与摘要专项先通过,随后仅补齐计划阶段的未知策略保护;对应编解码和提示代码没有再变动。最终188项流程、819项任务、相关回归及11文件语法验证覆盖了最后的运行源码,不宣称每个早期专项均在最后一处增量后重跑。
### 原真实响应的离线适配
使用上轮保存的25款配置重新冻结,16条年龄本地处理、66条AI条件、100条模板规划6批。将原首批响应的业务字段显式投影为新短编号协议后,12条条件和16条模板通过,在下一批前主动停止。完整绑定来自新冻结目录。
旧协议仍拒绝原坏 hash 响应,新协议也拒绝直接传入原完整绑定格式。原8份证据文件摘要前后保持;探针保留原摘要文字以单独验证协议,不用它证明摘要文案已改善。此项是离线协议适配,**不是新的模型输出,不代表完整评估通过**。
### 隔离环境清理
全新本机 MySQL/Redis 仅监听 `127.0.0.1:51878/51879`,独立数据目录、29张合成表和Redis DB15;应用初始化前替换真实模型客户端,显式注入假模型,不启动常驻worker。测试结束29表全空、6个原触发器保留、DB15键数0;任务前后259个运行PHP摘要一致。
核对进程PID、创建时间、程序及完整命令行归属后,正常关闭本轮两个服务,进程退出、端口释放,datadir保留。生产进程未改。准备隔离环境时发生最后一处策略保护修正,任务执行前另存最终源码基线 `runtime-sources-before-tasks.json`;初始准备基线仍保留,审计明确使用任务前基线。
## 当前状态与下一步
本地协议、提示和隔离任务验证已完成,完整的新真实模型评估尚未复测,不能宣称25款语义或未来摘要文案已实际通过。生产仍为 `20261016-report-diagnostics-r1`,本轮未发布,小程序也未重新发行,微信真机及生产worker未验收。
下一步建议对同一合成资料和保存配置进行一轮新的完整真实模型验证:使用新的证据目录、防重入口和 `capture()` 快照,最多按冻结批数调用,失败即停,不自动重试;不能重跑上轮 `age-ranges-live-20261016-r1` 的收费入口或更改其原始响应。实际付费执行与发布按当轮明确授权范围处理,本轮不执行。
证据目录:`D:/AAdesk/202609/airz-backups/response-bindings-20261016`,含修改前原件、红绿日志、隔离任务结果、清理审计、离线适配、`implementation.diff`及`verification.json`。原真实失败证据仍位于 `D:/AAdesk/202609/airz-backups/age-ranges-live-20261016-r1`。
## 2026-10-16 后续完整真实模型复测
上节“尚未完整复测”为本地修复阶段状态。用户随后“继续下一步”,已在新的隔离环境完成6批真实调用,66条模型条件、100条模板、25款匹配保存回读及6检查点零调用恢复通过,实际摘要与零金额/年龄目标通过。仍发现77徽享薪易贷明确公积金路径漏判unknown,不能声称全部语义通过;未追加收费重试或修改原响应,继续未发布。完整结果、限制及下一步见[真实模型复测记录](短编号真实模型复测-20261016.md),证据D:/AAdesk/202609/airz-backups/response-bindings-live-20261016-r1。
@@ -0,0 +1,60 @@
# 短编号与摘要提示的真实模型复测
2026-10-16。用户在本地修复后“继续下一步”,承接上一轮明确的“完整真实模型复测”。本轮执行一次新建合成评估,最多6批、失败即停、不自动重试;继续不发布、跳过真机。
后续本地修正见[公积金完整可选路径修复](公积金可选路径修复-20261016.md)。下文保留当次真实调用结论及原漏判证据,不把后续离线修正视为新的真实模型通过。
## 结果
6批真实调用全部通过原有严格解析、合并及持久化校验,任务状态 `succeeded`。66条模型条件、100条模板、25款匹配结果均完整保存并由所属合成用户回读;6个检查点用零新增调用完整恢复。新短编号协议的流程验收通过,仍有一条明确的路径语义漏判,不能称为全部语义通过。
| 项目 | 实际结果 |
| --- | --- |
| 真实模型 | `deepseek-v4-flash-0731`,经既有TokenHub接口 |
| 调用数 | 6次,无重试、无额外诊断调用 |
| 总任务耗时 | 65.883秒 |
| API累计耗时 | 63.831秒 |
| 输入/输出token | 56920/8279 |
| 条件 | 16条本地年龄+66条模型条件;模型4条pass、62条unknown、0条fail |
| 模板 | 100条完整保存,16条适用 |
| 匹配 | 25款参考候选、0款已知条件不符,分数均80;这是当前参考筛选口径 |
供应商只返回token用量,没有查询人民币账单,不折算金额。本轮使用同一份合成申请人资料及25款既有保存配置重建目录,不是最新正式产品目录导出,不代表任意资料组合或未来模型输出均通过。
## 修复目标的实测
新执行快照包含 `aiResponseBindingPolicy=batch-item-reference-v1`。逐批原始请求和响应只用短 `ref` 绑定条件/模板,不要求模型抄写完整hash。后端保存的报告和检查点均恢复为产品、版本、条件/模板编号与64字符摘要,最终合并及零调用检查点恢复仍严格校验,不以返回顺序猜绑定。
6批原始摘要及三个正文数组、最终保存的对应正文均未发现“用户自填、未经核验、尚未核验、未执行产品匹配”或模型品牌。最终摘要为“现有资料可供整理参考。”;3条strengths概括职业、收入和社保/公积金资料,improvements和riskWarnings为空。此结论针对模型生成正文,不把仍保留的来源、readiness、融资等级说明等固定元数据当成模型新输出,也没有全局删除或替换这些元数据。
合成资料填写全部贷款0笔、信用卡0张,四关联金额原始null。保存及回读后,四金额均为 `not_applicable`,完整度100%,missingFields与invalidFields均为空;逐批模型条件missingFields为空,没有要求补贷款/信用卡金额或未采集细项。原null没有被覆盖为0,全部贷款口径、省份、0笔/0张与整份详情表单保持。
16条可确定年龄条件由后端按冻结的身份证派生36岁全部判pass,适用年龄模板正确描述实际36岁;不存在把实际年龄冒充准入上下限的情况。本次省份为广东省,不提供具体城市;模型没有把已填省份说成未填。具体城市、户籍、申请地区或网点覆盖不足时保持unknown,没有索要补填,不能凭广东省推断深圳市。
惠民贷等明确公积金可选路径可正确pass并引用 `fact:housingFund`;集团E贷收入模板填“20000元”。这不代表其他复合条件均已满足。
## 剩余语义问题
第5批产品77“徽享薪易贷”/`rule_qa_2`原文为:
> 符合其一:社保客群(建议本单位满半年以上);公积金客群(缴存时间没有具体要求);优质行业客户(国企、公务员、事业单位)。社保半年是建议,不设为所有客群门槛。
原备注明确只需满足一条完整路径。样本已提供公积金缴纳12–24个月,公积金分支没有额外门槛;原始模型仍返回 `unknown`、`evidenceRefs=[]`,相应路径推荐模板没有启用。这是已知完整路径漏判,不能当成一般的“未采集资料”。当前参考建议口径未因此阻断整款结果,但少展示一条可用理由。
该请求没有 `sufficientAlternatives` 保护。现有保守提取器只接受严格尾句白名单,并对其他分支括号保持保守,不能覆盖本条完整表述;不能仅据本次输出推断模型内部为何漏判。本轮只记录证据,不修改产品原文、不手工把unknown补成pass、不追加收费重试。
下一步建议对“明确任选且公积金路径无附加门槛”的表述做本地定向修正,保留其他分支中的时长、单位、共同要求与例外约束,并补产品77反例及旧协议兼容回归。修正后的新语义尚未验证,不自动启动下一轮真实调用或发布。
## 隔离与证据
本轮仅在新建本机MySQL/Redis `127.0.0.1:51978/51979` 执行实际创建、任务、保存及回读,无常驻worker。应用根目录与环境前缀独立,仅读取既有模型配置段到内存,不加载正式库配置;逐次外呼前检查短编号、冻结策略、源码摘要及敏感字段。姓名、完整身份证、手机号均未外发,职业类型与单位名称按既有授权发送。
预检独立审查发现测试入口提前读取尚未生成的执行快照,已在零调用阶段将检查移到任务冻结后、首次外呼前;不是业务源码缺陷。`--plan` 模式关闭模型调用,付费执行使用排他全局标记及6份逐次标记,旧收费入口与11份原证据文件摘要保持。
独立离线核对63项通过,验证原始短编号响应、完整canonical保存、真实用量记录、检查点恢复、用户表单和零金额状态等。259个后端运行PHP、77个小程序UTS/UVue摘要前后保持,没有本轮运行源码修改。
实际任务结束精确清理合成数据后,29表全空、6个原触发器保留、Redis DB15键数0;核对PID、创建时间、程序与完整命令行归属后正常停止两个本轮服务,端口释放、datadir保留,生产进程未改。
证据:`D:/AAdesk/202609/airz-backups/response-bindings-live-20261016-r1`,含原始calls、context、report、matches、usage-logs、6检查点、零金额与年龄证据、回读、独立语义审查、清理审计和verification。`evaluation-once.started`与6份`model-call-*.started`保留,禁止重跑本收费入口。上一轮原始失败证据保持只读。
本轮未发布、发行小程序、访问正式库、重试正式评估、提交或推送;生产仍为 `20261016-report-diagnostics-r1`。微信真机、生产worker及最新正式目录仍未验收。
+89
View File
@@ -0,0 +1,89 @@
# 评估配套发布准备(2026-10-15)
状态:**后端已于2026-10-15按用户明确授权部署,微信小程序待用户自行发行。** 线上版本 `20261015-assessment-region-batch-r1`,15:38:53(Asia/Shanghai)恢复入口与worker;固定候选 `rc-20261015-assessment-region-batch-r1`未重建。详见[实际发布记录](后端评估配套发布-20261015.md)。下方保留发布准备时的边界及操作依据,现场预检/备份/恢复/无付费验收均已完成,未调用新模型。
固定[源码与基线清单](发布候选/assessment-20261015-r1/manifest.json)和[本轮核验结果](发布候选/assessment-20261015-r1/verification.json)。三个归档位于 `D:/AAdesk/202609/airz-backups/release-preparation-20261015/`:
| 归档 | 内容 | 用途 |
| --- | --- | --- |
| backend-runtime.zip | 9个后端运行文件 | 经现场预检后整体替换,含2个新增类 |
| frontend-source.zip | 206个完整前端源码/资产文件 | 在独立目录解压后用HBuilderX重新编译;不是微信上传包 |
| backend-recorded-baseline.zip | 7个旧后端文件 | 与上次生产发布回执摘要完全一致的恢复材料,使用条件见回滚章节 |
归档大小、SHA256、逐文件清单及旧文件来源已固定在manifest.json,归档回执另存同证据目录package-receipt.json。任何源码、包、基线或依赖变化均应重新生成候选,不沿用本回执。
## 基线与累计范围
最近已有后端发布为 `20261014-product-ai-r1`。其回执记录目标 `https://airz.hqling.cloud`、站点根 `/www/wwwroot/xctai.cn`,运行目录 `public`。这只是历史记录,执行发布前必须现场核对目标、站点和实际版本,不能把旧PID/队列状态当作当前状态。
离线核对上次133文件记录:125字节一致、7个业务变更,`application/route.php`仅混合换行与统一CRLF不同,统一CRLF后的SHA256与回执完全一致。路由不纳入替换,线上仍须按原记录摘要核对。另2个新增类在旧清单中未登记,发布前必须确认目标路径不存在;若已存在,应先核对归属与内容,不能直接覆盖。7个旧文件优先从已核历史runtime.zip提取,另外2个由Git对象重建换行后逐字节匹配生产回执;不是把HEAD直接视为线上版本。
后端目录统一为 `application/common/library/`,以下9个文件必须配套:
| 文件 | 动作 | 累计行为 |
| --- | --- | --- |
| AnalysisTasks.php | 替换 | 单次/分批执行切换、内部额度与租约共用接口 |
| AnalysisBatchTasks.php | 新增 | 逐批调用日志、额度、租约、用量、绑定与检查点 |
| ProductAiBatches.php | 新增 | 按同一冻结目录规划批次、恢复与最终完整合并 |
| AssessmentNeedsReport.php | 替换 | 分批请求/报告解析、省份标签与年龄等事实展示 |
| AssessmentRecords.php | 替换 | 名称/省份代码配对、版本保存、详情回读,写最新资料前移除省份代码 |
| MatchRecords.php | 替换 | all分组和首屏三款读取排序,保留真实资格/分数/冻结rank |
| ProductAiConditions.php | 替换 | 年龄与地区可信事实、可选分支校验、待补资料不限固定条数 |
| ProductAiTemplates.php | 替换 | 分批模板校验、填值单位与年龄主语规范化 |
| ProductMatching.php | 替换 | 新目录冻结分批/年龄/地区/明确分支策略与摘要 |
无需DDL、数据迁移、产品导入或产品条件修改;产品继续原16字段,province_code仅随评估版本JSON保存,不新增user_info列。无新依赖安装、配置密钥变更或模型/额度参数调整。管理端JS/模板不在本轮替换范围,上次部署的管理端版本是本候选的依赖。
前端累计13个运行文件如下,完整206文件归档同时保留其公共组件、服务、图标和项目构建入口:
- `packages/finance/`内analyzing、assessment、confirm、report、matches、product、compare七页同名.uvue:省份填写/确认、完成后进入匹配结果、默认三款、匹配度/理由和产品可选信息展示。
- `packages/finance/services/matches.uts`、`records.uts`:all分组、地区显示、版本详情与提交。
- `packages/finance/components/AssessmentProvinceSelect.uvue`、`MatchProductCard.uvue`:34省级单选及紧凑产品卡。
- `services/cities.uts`、`services/match-route.uts`:省份字典/历史城市回填及all路由白名单。
完整源码的根文件为App.uvue、main.uts、manifest.json、pages.json、uni.scss、index.html、platformConfig.json,目录为components、packages、pages、services、static。已排除.env、数据、私密配置、IDE设置、缓存、文档、测试及unpackage旧编译产物。当前源配置为production、`https://airz.hqling.cloud/api/v1`、演示关闭,未改配置。
## 版本兼容与验证边界
| 组合 | 当前依据与要求 |
| --- | --- |
| 旧城市请求→新后端 | 旧字段形状与五城自动推码保持;原版本及默认城市HTTP回归通过 |
| 新省份请求→新后端 | 34省×个人/企业持久化、编辑回读、重放/冲突及省份HTTP已通过 |
| 新客户端→旧后端 | 不兼容:旧提交白名单没有provinceCode,旧匹配分组不支持all;必须先部署后端 |
| 旧冻结报告→新后端 | 按原目录与原版本读取,不补新事实、不重算;已有版本/历史回归通过 |
| 旧客户端读取/编辑新省份资料 | 尚无实际发行包与真机证据;不得据旧请求回归声称完整互换已通过 |
| 新分批/省份快照→旧后端 | 未验收旧代码读取与继续任务;旧代码也有原数量/缺失项限制,不允许直接代码回退 |
分批会把一次完整评估拆成多次实际模型请求,逐批消耗调用额度并增加耗时。历史回执重建的25款/82条件/100模板样本为7批,第5次真实评估约152秒;不是当前正式目录容量或当前省份提示语义的实测。发布前应只读获取最新正式目录并离线编译规划,核对数量、单批32项/48000字节描述/模板槽位限制、调用预算及等待体验;不擅自提高正式额度或触发收费评估。
已完成的业务证据包括690项PHP纯检查、108项UTS、18项受控H5、微信29页编译,以及436项省份持久化、58项版本、默认城市HTTP514项/158请求、省份HTTP533项/162请求、地区事实68项。各套件按原证据记录,重叠不合计。本轮只核候选归档、摘要、依赖与语法;没有重跑同类数据库测试、新编译上传包、服务器验收或真实模型调用。持久化专项使用空合成目录,HTTP/版本使用演示产品。
本轮验证3个ZIP共222成员的路径、成员集合、逐文件字节及CRC;9个候选和7个恢复PHP文件语法通过;完整前端220处@/导入均在归档内;34省份同源生成检查通过。255个后端运行PHP字节与前轮隔离持久化验收一致,8份历史证据摘要保持。完整检查记录见verification.json。
## 获得发布授权后的操作顺序
1. **绑定现场与备份。** 核对上述域名、真实站点路径、线上资源版本、PHP7.4与依赖。复核133个记录文件以及2个新增路径;差异须解释并形成新候选。私密记录.env/服务/队列/模型参数及文件权限摘要,不输出密钥。备份当前实际7个被替换文件、两新增路径是否原本存在、资源版本与维护配置;保留正式库灾难备份,不把数据库或私密配置放入候选ZIP。历史恢复包不替代现场备份。
2. **先阻止新增写入,再排空任务。** 覆盖评估、重试、助手和产品解释等新调用入口;让已有任务完成,核对活动评估、实际模型调用和三类队列均无待办后,再正常停止所属airz-analysis。若有未完成调用,暂停发布并等待/分析,不强杀、删队列或重跑收费任务。记录维护起点与业务行/结构摘要。
3. **维护窗口整体替换。** 先将候选9文件放入独立暂存目录并逐项核SHA256/PHP语法,确认全部齐备后整体应用;Web PHP和worker均不能在混合版本下接收业务。记录7文件替换、2文件新增及真实权限;不覆盖.env、数据库、上传目录或依赖,不重跑历史初始化/迁移脚本。
4. **加载与无付费核验。** 按实际PHP-FPM服务重载代码/OPcache,启动一个归属明确的airz-analysis,核对新进程命令、ready/心跳/无重启循环及错误日志。只读核对最新正式目录可冻结/分批规划、旧报告/产品/字典和身份边界、HTTPS与敏感路径、业务数据及结构保持;不通过创建正式评估来做默认冒烟。没有新产品/模型配置操作,不要求重启Redis。
5. **恢复与观察。** 明确允许接收新版本资料前记录恢复时间;恢复入口后再次核实际服务状态、队列和日志。记录本次已部署摘要、备份、前后数据一致性和无付费冒烟结果,不能沿用2026-10-14旧服务回执。若发生异常,按下节对应阶段处理。
6. **后发行小程序。** 后端通过后,从固定完整源码在干净目录重新编译微信29页,记录HBuilderX版本、全部源码清单摘要、编译日志和上传包摘要;核对request合法域名。小程序由用户发行,版本号/发行时间由实际回执登记,不能使用本候选标识充当微信版本。H5发布不包含在本操作单中。
7. **真机验证。** 验证34省单选/取消/回填、个人/企业确认和版本详情、旧报告、默认3款/查看更多/对比、处理中离开返回。新的真实评估会收费,须按另行授权的固定资料、调用范围和预算执行并保留原始响应;省份不能证明具体城市、户籍或网点覆盖。ID56申请金额/本笔月供口径仍单独待核对,不借发布修改产品条件。
## 回滚条件
**尚未恢复新写入且未形成新版本资料/目录/检查点/报告时:** 保持维护,确认没有活动调用,停止所属worker。优先恢复本次现场备份的7个旧文件,并核其摘要与部署前一致;只有本次备份证明新增两路径原本不存在、当前内容仍精确等于本候选且没有其他代码依赖时,才可移除本轮新增文件。资源版本和维护配置只按本次备份还原,重新加载PHP/worker并只读复核。归档中的7文件旧基线仅适用于现场仍精确匹配20261014-product-ai-r1的情形。
**已经恢复写入或产生任何新省份/分批数据时:** 再次暂停新增写入,保留所有新资料、调用用量、检查点和报告,默认保留兼容读取/继续任务所需代码,优先向前修复。切回旧代码、回退客户端或恢复数据库都须重新评估实际新增数据及兼容读取范围,不能用“无DDL”推断可直接回退,也不能整库覆盖后续业务。小程序旧发行包/对应源码摘要目前未知,尚不能承诺前端精确恢复。
## 复核入口与下一步
本机只读复核命令:
```powershell
& 'D:/Develop/Python311/python.exe' 'D:/AAdesk/202609/airz-backups/release-preparation-20261015/verify-candidate.py'
```
它只核当前源码/归档/历史证据、PHP语法和字典一致性,不连接服务器或数据库、不启动worker或调用模型。出现漂移须更新候选,不直接重新运行已完成的build-candidate.py覆盖原产物。
用户随后已明确授权并完成本操作单范围的后端发布:135路径核对、正式25款产品冻结及7批规划、现场备份、维护期30表一致性、29服务器/31公网HTTPS和唯一worker均已验证。下一步由用户重新编译发行微信,提供实际版本/发行时间/上传包摘要;微信真机及新省份提示真实模型语义仍待验收。正式写入已恢复,后续遵守上节保留新格式数据并优先向前修复的限制。
@@ -0,0 +1,70 @@
# 自填资质参考建议发布准备(2026-10-15)
状态:用户随后明确授权“发布后端”,固定候选`rc-20261015-profile-advice-r1`已部署为`20261015-profile-advice-r1`,2026-10-15 17:54:14(Asia/Shanghai)恢复入口/worker。无本轮模型调用,无小程序重新发行、提交或推送。现场备份及核验见[实际发布记录](后端资质参考建议发布-20261015.md),固定清单不改写;下方保留发布前准备说明。
## 已固定材料
完整[候选清单](发布候选/profile-advice-20261015-r1/manifest.json)记录每个文件的新旧SHA256、135文件历史基线核对、依赖、验收证据和发布边界;[候选验证结果](发布候选/profile-advice-20261015-r1/verification.json)记录本轮检查。归档目录为`D:/AAdesk/202609/airz-backups/profile-advice-release-preparation-20261015-r1`。
| 归档 | 内容 | 字节数 | 用途 |
| --- | --- | --- | --- |
| backend-runtime.zip | 6个当前后端运行文件 | 42511 | 经现场预检和备份后整体替换 |
| backend-recorded-baseline.zip | 6份精确匹配上次发布回执的旧文件 | 37770 | 辅助核对发布前恢复材料,不替代新现场备份 |
包摘要、成员摘要及manifest摘要均在清单和package-receipt.json中固定。本轮未打包.env、密钥、数据库、上传文件、依赖、测试、小程序源码或旧模型请求。原始线上备份、已完成的收费入口及其标记不变。
## 范围与基线
上次已发布后端为`20261015-assessment-region-batch-r1`。历史回执目标为`https://airz.hqling.cloud`,站点根`/www/wwwroot/xctai.cn`,运行目录`public`。这些是历史记录,实际发布前须重新核对站点、版本、PHP和文件摘要,不能视为当前服务器状态。
与上次135文件记录对照:128个字节一致、6个业务变化、application/route.php仅换行格式不同且统一CRLF后与原回执完全一致。路由不替换。5份旧文件来自已核摘要的上次runtime.zip;ProductExplanations旧文件来自实施前快照,其字节精确匹配上次生产回执。
以下文件均位于`application/common/library/`,必须整体发布:
| 文件 | 行为变化 |
| --- | --- |
| ProductMatching.php | 新资质评估冻结新策略,自填事实参与参考比较;未采集细项和特定未核实地区提示不阻断参考筛选 |
| ProductAiConditions.php | 默认已填内容真实;职业/单位受白名单限制外发;unknown保留但missingFields为空、不加分 |
| AssessmentNeedsReport.php | 主报告采用相同口径,只按实际缺填/无效/矛盾生成待完善项;说明参考筛选范围 |
| ProductAiTemplates.php | 新策略unknown模板不展示、不填值,原固定正文不改写 |
| ProductAiBatches.php | 新策略中间占位与空缺失项口径一致,保持分批、检查点和最终完整校验 |
| ProductExplanations.php | 追加解释采用同一口径,未参与判断细项使用固定说明,不能重新索要资料 |
没有新增或删除运行文件;无需DDL、业务迁移、产品编辑、依赖安装、模型/额度/密钥配置变更。6个运行文件之外,只在获准发布时按实际部署流程记录资源版本元数据;若使用fa_config.version及生成site.php,只更新本次版本并核对其他配置保持,不把完整配置装入候选。
本轮没有改动小程序,所有填写项、提交、保存、回填和历史数据保留;此次后端变化无需重新发行小程序。上次小程序由用户发行,实际版本和包摘要尚未补录,不能据此声称已做真机验收。
## 验收与兼容
候选与上一轮授权真实模型验收时的255个后端PHP及105个小程序源码/配置摘要一致。该轮7次实际调用覆盖25款/82条件/100模板:21pass、61unknown,所有补填清单为空,25款参考候选各基础80分,表单回填保持。详见[真实模型验收](资质参考建议实测-20261015.md)。单份合成个人资料和回执重建目录不替代当前正式产品目录核查。
本轮642项检查通过:两个ZIP/12成员的路径、成员集合、CRC、字节摘要、12份PHP语法、新旧基线、360份源码指纹及14份证据绑定均通过。真实调用原证据与收费标记保持,没有重跑收费入口或相同数据库套件。
新策略只对新建资质评估冻结生效。旧任务/旧检查点/旧报告沿原冻结策略,不补入职业/单位、不重算,也不能通过重试旧任务验证新口径。未知不等于满足银行要求;明确不符、真实表单缺填/格式错误、配置异常及任务未完成仍体现。产品适用地区未核实不改成全国,不从省份猜城市。
仍有4条固定理由带“进一步核实申请额度/申请资料”的后续申请阶段措辞,不生成当前补填清单或阻断参考筛选;本候选保持产品原文。主报告仍显示未经外部核验的范围说明,不要求证明自填真实性。本次无新的明确fail、企业产品、追加解释模型或真机实测。
## 获准发布后的执行顺序
1. 核对当前域名、站点根、public运行目录、线上版本和PHP7.4;对照manifest中的135个文件摘要及6个替换路径。差异先解释并形成新的审阅结果,不能按旧回执直接覆盖。只读核对实际25款目录和调用策略;当前数量以现场为准,不调用模型、不提高额度、不输出凭据。
2. 对实际6个旧文件、资源版本及维护配置做本次现场备份,记录摘要、所有者、权限和恢复位置;保留数据库灾难恢复备份,但本次无需数据迁移,不能把历史整库备份用于回滚后续业务。
3. 暂停评估/重试/助手/产品解释等新增写入和调用入口,允许已有任务完成。核对活动评估、实际模型请求、队列ready/reserved/delayed均空后正常停止所属worker。若存在未完成调用,等待或诊断;不得删队列、强杀调用或重跑任务。
4. 在维护窗口把6个候选文件暂存并逐项核摘要和PHP语法,齐备后整体替换,保留实际权限。Web PHP和worker不在混合版本下接业务;不覆盖.env、上传文件、依赖或产品配置。
5. 按现场服务重载PHP-FPM/OPcache,启动且仅启动一个所属worker。核对命令、版本、ready心跳、重启次数及日志。只读验证旧报告/匹配分组/产品详情/资料回读和归属边界、最新目录的新策略冻结及分批规划、可信HTTPS与敏感路径,不创建正式评估、不触发付费冒烟。
6. 核对维护前后业务数据/结构和非版本配置保持,记录已部署摘要、备份摘要及无付费核验结果;恢复入口并记录时间,再观察worker/队列/日志。形成实际发布回执,更新交接和本候选的部署关联;候选清单及历史证据本身保持不可变。
## 恢复边界
尚未恢复写入、且没有任何新策略执行快照/检查点/报告时,可保持维护并恢复本次现场备份的6文件和对应版本元数据,重载PHP/worker后只读核验。候选附带的旧基线ZIP仅在现场精确匹配上次回执时可辅助使用,不能替代现场备份。
一旦恢复写入或产生新策略快照/检查点/报告,就保留全部用户资料、调用用量和结果,保留兼容读取/继续执行所需代码,优先向前修复。不能因“没有DDL”直接回旧代码,也不能整库覆盖后续业务或重试旧收费入口。
## 本机只读复核
```powershell
& 'D:/Develop/Python311/python.exe' -X utf8 'D:/AAdesk/202609/airz-backups/profile-advice-release-preparation-20261015-r1/verify-candidate.py'
```
该入口只核归档、源码、历史证据及PHP语法,不初始化应用、不读默认.env、不连接数据库或服务器、不启动worker、不调用模型。源码或归档出现漂移时,应另建新候选,不覆盖这份已固定材料。
用户已明确授权并完成上述实际发布;后续按实际发布记录的恢复边界处理。
+42
View File
@@ -0,0 +1,42 @@
# 自填资质参考建议真实模型验收(2026-10-15,未发布)
用户在建议进行一次真实完整评估后回复“执行吧”,本轮据此仅授权真实模型验收。使用新建的合成个人资料及新冻结策略,最多7次模型调用,不自动付费重试、不发布线上、不修改小程序、不提交或推送。
## 结果
完整任务成功,25款产品、82条条件、100条理由模板经7批实际调用处理,7个成功检查点和完整报告均已保存并回读。总耗时138.103秒,API累计136.422秒;模型为经TokenHub调用的deepseek-v4-flash-0731。输入68165、输出19580 token,共87745 token;没有查询人民币账单,不能据token数量声称具体费用。
| 项目 | 本次实际结果 |
| --- | --- |
| 条件判断 | 21条pass、61条unknown、0条fail |
| 补填清单 | 82条条件的missingFields均为空,主报告improvements为空 |
| 参考列表 | 25款可供参考;61条未知保持原判断,不阻断参考筛选、不加分 |
| 分数与完整度 | 25款仍各为基础80分;表单完整度100%,原分级公式保持 |
| 理由模板 | 17条展示,未知条件模板均不展示、不填值 |
| 完整路径 | 中银E贷、闪电贷、惠民贷、徽享薪易贷公积金路径正确满足 |
| 职业/单位 | 7批均外发企业职员/合成测试单位,8条相关条件实际引用职业或单位 |
| 隐私 | 姓名、完整身份证、手机号均未外发;不从身份证推导性别 |
| 地区与粒度 | 只提供广东省,不猜深圳等城市;不把当前基数当平均基数或个人缴存额 |
| 保存与回填 | 分析前后全部表单字段相同,非隐私输入逐项回读一致 |
25款进入参考列表不代表全部银行准入通过。未判断的户籍、征信、具体城市、单位白名单等细项仍保留unknown证据,展示未参与参考筛选的说明。用户全部填写项、已填内容、原产品条件和固定正文均未删减。
## 验证证据
本轮928项结构、绑定、覆盖、引用、用量、隐私、持久化及清理检查通过;逐项语义复核覆盖全部82条条件和100条模板,161项定向判断检查通过。数字代表不同检查入口,不能当作独立业务场景数量相加。
另外214项纯离线检查通过:7个本次真实检查点零模型调用恢复完整报告,除生成时间和持久化层追加的revisionId/revisionNo外正文与保存结果一致;重算匹配项与保存结果完全一致。使用25款实际保存匹配项及明确标记的合成解释文本验证追加解释解析契约,未再调用单产品解释模型。
255个后端运行PHP及105个小程序源码/配置摘要与本轮执行前一致,第5轮26个原证据文件及收费标记保持。6张合成业务表已清空、6个原schema触发器保留;Redis DB15为空,仅关闭本轮所属隔离MySQL/Redis,51378/51379端口已释放。没有访问正式库或生产服务,没有部署、提交、推送。
证据目录:`D:/AAdesk/202609/airz-backups/profile-advice-live-20261015-r1`。主要文件为authorization.json、calls.json、context.json、report.json、report-readback.json、matches.json、detail-before.json、detail-after.json、usage-logs.json、result.json、verification.json、semantic-review.json、per-product-audit.json、replay-verification.json、cleanup.json。
本轮收费入口为run-evaluation-ready.php,evaluation-once.started须保留,不得重跑。早期预检的三级配置读取、合成用户名长度及执行快照检查时机问题均已在首次实际模型请求前修正;两次零调用启动的证据保留在preflight-zero-call及preflight-zero-call-2,不计入7次付费调用。离线比较最初包含持久化层追加元数据,定位后显式排除两项版本标识,仅正文与业务结果参与重建比较;未修改原始报告。
## 文案观察与边界
主报告仍显示“尚未核验”等范围说明,没有要求证明用户自填真实性。4条已展示的固定理由仍有“进一步核实申请额度/申请资料”的申请阶段措辞;它们没有生成当前补填项或阻断参考建议,也未在本轮擅自改写。若希望页面完全不出现这类措辞,可单独润色固定产品理由。
这是单份合成个人资料、历史录入回执重建目录的验收,不是最新正式目录或微信真机验收。本样本没有明确不符的fail分支,该保护仍以此前离线回归为依据;企业产品及追加解释模型未作新的付费实测。
核心业务口径已通过本次真实模型验收。后续可在用户明确安排发布后,核对线上基线并配套发布6个后端文件;不能只发布提示词。本轮不需要重新修改或发行小程序,线上仍为20261015-assessment-region-batch-r1。新增付费复测与生产发布均不能复用本次授权。
+54
View File
@@ -0,0 +1,54 @@
# 自填资质参考建议调整(2026-10-15,本地未发布)
最新状态:用户随后授权一次真实模型完整评估,已完成7批/25款/82条件/100模板,核心口径通过;21pass/61unknown,未索要补填,未知不阻断参考建议。线上仍未发布。详见[真实模型验收](资质参考建议实测-20261015.md);下文“不做真实复测”描述保留实施时边界,由此次单次授权覆盖,不能据此新增收费调用或发布。
## 用户确认的口径
默认用户已填内容真实,用于测试和参考建议,不要求证明真实性。当前表单未采集、模型不可见或粒度不足的银行细项,不要求补填,也不因此阻断建议。可选路径按“任一完整路径满足即可”,同一路径内部的必需条件仍须同时满足。
允许向AI发送个人职业类型、工作单位名称;姓名、完整身份证、手机号继续排除。允许必要的本地后端配套修改,覆盖此前“仅提示词”的限制。小程序全部现有填写项、已填数据、提交、保存和回填继续保留。先不更新线上、不做真实模型或真机复测、不新增付费调用;不提交、不推送、不创建PR。
## 实际行为
新资质评估运行入口冻结 `profileAdvicePolicy=self-reported-profile-advice-v1`,标记纳入目录、执行和检查点绑定摘要。无该标记的历史目录、旧任务重试、已成功检查点和报告继续原语义;需求模式不启用。`freeze`末位参数显式控制该策略,默认关闭以保留历史重建夹具;正式`capture`入口为新资质模式开启。
条件仍保留pass/fail/unknown,不新增“银行全部通过”结论。新策略中所有条件的missingFields固定为空数组;已完成的unknown记录为“未参与参考筛选”,不阻断参考候选、不贡献加减分,也不套用要求补填或断言不符的旧模板。未执行、超范围、配置异常和已有明确不符继续体现,分析未完成的产品不会提前作为已完成建议。当前表单本身缺填或格式异常仍由原完整度和输入问题机制体现。
标准规则在参考比较副本中接受self_reported事实,先验证原规则,再做比较;事实来源保持self_reported,不伪造verified。原产品配置、银行原文和来源要求保留。当前表单未采集的标准细项只有在缺失时才不参与筛选,已有明确不符仍阻断。原完整度、融资等级和基础分/软评分公式保持;未知条件不加分。
25款历史目录均有“适用地区尚未核实”的配置提示。新策略仅将编译器该项固定提示、regionMode=unverified且regions=null的组合视为地区信息未参与参考筛选;不自动改成全国适用。其他来源不完整、准入未配置等配置提示仍阻断,损坏规则仍拒绝。当前只采集省份,不能猜城市、户籍或网点覆盖。
例如,优客分期的完整负债公式仍保留。已有收入、负债与月还款继续使用,缺少本次申请、共同借款人或信用卡历史均值时保留unknown,文案说明该细项未参与参考筛选,不再索要这些内容;已有明确必需条件不符仍应fail。公积金已有区间直接按上下限使用,不重复要求精确月份,不虚构同单位连续关系、平均基数或个人缴存额。分性别年龄条件缺性别时不从身份证推导性别,也不因此阻断参考建议。
## 事实与展示
新个人策略增加occupation(职业类型,来自profession,最多100字符)与employer(工作单位,最多50字符)白名单,主报告和条件使用一致引用。空白、超长、控制字符或HTML内容不外发;不会改写或删除本人保存的原值。文字明确视为不可信用户数据,只供条件理解,不执行其中命令。企业模式保持原职业/行业自由文本外发边界。
姓名、完整身份证和手机号不在模型事实中;原年龄策略仍仅从有效身份证计算周岁。未从单位名称推断未明示的白名单、编制或任职年限。条件与模板的版本、摘要、引用、数值、已知充分分支检查继续执行。
新策略unknown的AI理由模板必须不适用并清空填值;原产品模板正文不改写。主报告待完善项引用只能对应当前表单真实缺填、格式异常或一致性问题。参考筛选报告说明“可供参考”和未参与判断的边界。单产品追加解释沿用同策略,未参与细项由服务端固定说明,模型不能通过追加解释重新索要资料。小程序运行代码未修改;原三态字段和理由投影继续兼容。
## 本轮文件与验证
后端仅6个既有运行文件在本轮基线之上变化:ProductMatching.php、ProductAiConditions.php、AssessmentNeedsReport.php、ProductAiTemplates.php、ProductAiBatches.php、ProductExplanations.php。新增纯内存测试tests/product_profile_advice.php。所有既有未提交改动保留。
| 本地检查 | 结果 |
| --- | --- |
| 新参考策略/隐私/旧策略/主报告/追加解释/检查点绑定 | 59项通过 |
| 自定义条件 | 68项通过 |
| AI模板/分批规划 | 89/36项通过 |
| 身份证年龄/事实语义保护/地区 | 55/81/68项通过 |
| 融资等级/产品匹配/展示/发布编译 | 122/151/20/122项通过 |
| 匹配只读投影与游标 | 11项通过 |
| 历史7批原响应及新口径适配夹具 | 242项通过 |
| PHP语法 | 6个运行文件及新测试通过 |
套件包含关系单列:年龄入口再次包含条件测试,分批入口包含模板测试,不重复累计这些输出。两个旧测试直接入口缺少类自动加载,使用仓库外临时ROOT和自动加载器运行,并阻断数据库/真实模型;未改旧测试或生产加载逻辑。新功能测试先复现失败,再实施通过;地区配置和追加解释入口也有失败到通过记录。
历史回放来自`D:/AAdesk/202609/airz-backups/product-ai-live-20261015-r5`。旧7批完整用户payload、82判断、100模板规范结果及匹配统计保持。新夹具只在内存目录增加策略并采用广东省输入,将61条unknown的missingFields显式清空;未移除任何不可用引用,也没有改变21条pass/61条unknown或新增模板适用结论。新结果25款进入参考候选、基础分仍各80分,完整度和融资等级保持。7个新夹具检查点可零调用恢复,不能把此结果当新模型判断或银行准入通过。
证据目录:`D:/AAdesk/202609/airz-backups/profile-advice-20261015`。含6文件基线备份、360个基线摘要、implementation.diff、verification.json、replay.php、replay-result.json、适配检查点/报告、source-integrity.json及隔离加载器。255个后端运行PHP中仅上述6文件变化,105个小程序源码/配置摘要保持;原第5轮证据及execution.started保持。没有读取项目默认.env、连接数据库/Redis、运行worker、调用真实模型或访问生产。
## 后续边界
本地实施和离线验证完成,线上仍是20261015-assessment-region-batch-r1。真实模型对新提示的遵循情况、实际持久化/API和真机效果尚未重新验证;本轮未启动隔离数据库,也不把内存检查当完整端到端验证。若后续允许真实模型复测,应使用新的受控入口,不能重跑带execution.started的历史收费脚本。发布须配套上述6个当前文件,不能只发布提示词;本轮没有安排发布。
@@ -0,0 +1,72 @@
# 资质评估累计修复发布候选
2026-10-16。候选 `rc-20261016-assessment-fixes-r1` 仅在本机准备,**尚未发布**。继续遵守用户“先不发布、跳过真机”的要求;本轮没有生产访问、数据库访问、真实模型调用、提交或推送。
固定清单与核验结果位于 [manifest.json](发布候选/assessment-fixes-20261016-r1/manifest.json) 和 [verification.json](发布候选/assessment-fixes-20261016-r1/verification.json)。本机证据及压缩包目录为 `D:/AAdesk/202609/airz-backups/release-candidate-20261016`。
## 累计行为
- 统一按全部贷款统计“贷款笔数”;明确0笔/0张时,关联空金额在适用的新冻结规则下按“不适用”处理,不扣完整度,也不要求补填。原null和用户明确填写的金额保留,不把旧资料自动升级为新口径。
- 匹配详情不再将固定“用户自填/未经核验”或“未参与参考筛选”的说明逐条列作不推荐/需注意;保留原判断、实际不符、分数、排序和可展开依据。已填省份不冒充具体城市或网点覆盖。
- 明确独立年龄区间由后端按冻结的身份证派生周岁比较;复杂年龄与其他要求组合仍保留原条件。只发送派生年龄,不发送姓名、完整身份证或手机号;职业与单位按既有授权使用。
- 模型传输改用同批短引用,后端恢复完整绑定再严格校验;旧目录及检查点保留原协议。模型正文提示去除资料来源、核验状态和品牌说明,主报告的模型品牌绑定已移除。
- 产品77明确独立公积金路径补足充分分支依据,并拒绝相反结果;新增严格整句白名单,不放宽附加时长、单位、基数或共同门槛。
- 档案详情只依据同一评估/版本已保存报告中的唯一“不适用”标记显示空金额,不改编辑回填、原值、统计或旧报告,也不增加HTTP请求。
文案范围需要区分:主报告既有静态免责声明和后端来源/模型等内部元数据仍保留;本候选没有宣称全应用所有来源文字均被删除,也不通过搜索替换过滤任意模型自由文本。
## 包与基线
| 归档 | 内容 | 用途与限制 |
| --- | --- | --- |
| `backend-runtime.zip` | 17个后端运行文件 | 12个生产记录内替换、2个生产旧摘要尚未证实的替换、3个新增依赖;必须整体核对后应用 |
| `frontend-source.zip` | 206个完整小程序源码/资产文件 | 本地Git运行差异15项;相对20261015的完整源码候选有9项变化。实际已发行源码未知;此包不是微信上传包 |
| `backend-recorded-baseline.zip` | 12个旧后端文件 | 字节均与最后已发布记录的摘要一致;不覆盖另外2个替换文件,不能代替现场完整备份 |
后端以已发布 `20261016-report-diagnostics-r1` 的本地发布回执为历史基线,未连接服务器。137个已记录路径中124个当前字节一致、12个有业务差异、1个 `application/route.php` 仅换行不同;路由不入补丁。不能把Git未提交列表直接当作待发布集合。
以下两个文件没有出现在该生产文件摘要清单中:
- `application/common/library/AssessmentReadiness.php`
- `application/admin/lang/zh-cn/user/info.php`
它们有本地修改前原件,但不能据此证明当前生产旧字节。发布前须核对现场文件、确认本次差异并取得实际备份;不能用本地副本填造生产回退材料。
新增 `ProfileZeroBalances.php`、`ProductAgeRules.php`、`ProductAiBindings.php` 均位于 `application/common/library/`。旧清单没有列出它们不等于服务器一定不存在;发布前仍须逐一检查。若路径已存在或任一记录文件与历史摘要不符,应先解释现场差异并重新固定候选。
其余12个后端替换文件及全部逐文件SHA256见固定清单。无需DDL、业务数据迁移、产品导入、依赖安装、模型参数或额度调整。ZIP不含`.env`、凭据、业务数据、文档、测试、IDE配置、缓存或旧编译产物。完整前端使用既有production API配置、演示关闭。
## 已验证范围
本轮重新执行8个当前源码内存入口,分别覆盖零金额15场景、地区提交22项、档案展示18组、匹配详情12场景、省份字典54项、匹配预览20项、分析完成路由12项、产品展示19项,均通过。测试使用内存响应替身,不访问真实API。
当前76个业务UTS/UVue与最后一次微信29页编译时的源码快照一致;额外第77个 `main.uts` 与历史完整源码一致。6处关键现有编译产物与对应回执匹配,因此可继承历史本地编译依据。本轮没有重新编译,历史dev目录不放入源码包;正式发行仍需用户用HBuilderX重新编译。
后端259个运行PHP与上一轮公积金修正验证后的摘要完全一致。本轮关联原162项专项、102项离线重放、相关内存回归及独立审查证据,没有为计数重复数据库或模型验证。17个候选及12个旧基线的PHP语法、3包成员路径/集合/字节/CRC、前端别名导入与省份同源数据由本轮候选核验工具检查。
历史完整真实模型复测为25款保存配置、6批调用,严格解析与保存回读通过,但有77路径漏判。之后的修正只做了内存及离线反事实验证,**最新公积金策略尚无新的真实模型实测**。不得把人工修改的反事实夹具当作新模型输出,也不能重跑已有防重标记的收费入口。当前正式产品目录、队列状态和服务状态均未现场核对。
## 后续发布顺序
只有用户另行明确要求发布后,才执行外部变更。记录中的目标为 `https://airz.hqling.cloud`、站点根 `/www/wwwroot/xctai.cn`,仅供现场绑定核对,不能将本机候选视为已上线。
1. 核对现场版本、站点、PHP与依赖、137条历史路径、两个未证实旧文件及三个新增路径;读取当前产品目录并离线编译规划,不创建评估或调用模型。暂存17文件,逐项核SHA256及语法;完整备份14个被替换文件、3个新增路径的原状态、资源版本和必要维护配置。
2. 阻止新增业务写入及模型请求,等现有任务、实际调用和队列排空,再正常停止所属worker和相关PHP进程。若仍有活动调用,等待或诊断,不强杀、清队列或重跑收费任务。
3. 在维护窗口整体应用补丁,核对17文件及保留依赖,按实际发布版本同步资源标识。重新加载PHP与唯一worker;只读核对旧报告/匹配/详情、鉴权归属、服务、心跳和队列,并核维护期业务数据及结构保持。无授权不触发新的真实评估。
4. 后端确认通过后,用户从固定完整源码重新编译并自行发行小程序,记录实际微信版本、时间和上传包/源码对应关系。当前新增客户端字段不应先接入尚未配套的旧后端。旧客户端请求和无新标记旧快照走兼容分支,不把旧0笔资料推成已确认全部贷款口径。
真机仍按用户要求跳过,不登记为已验收。若要核实最新提示的实际效果,应使用独立新目录、防重入口及明确调用范围;本候选准备没有执行该付费步骤。
## 恢复边界
尚未恢复写入、且没有形成新快照/检查点/报告时,可保持维护,按本次现场完整备份恢复。12文件记录基线包只可在现场旧摘要精确匹配时辅助恢复;两个缺生产摘要文件仍须依赖现场备份。新增文件只有在本次备份证实此前不存在、当前字节仍等于候选、无其他代码依赖时,才具备移除条件。
一旦恢复写入或产生新规则数据,应保留用户资料、快照、报告、检查点和用量,优先向前修复;不可依据“无需DDL”直接切回旧代码,更不可整库覆盖。当前小程序已发行包摘要未知,不能承诺前端精确回退。
## 本机复核
```powershell
& 'D:/Develop/Python311/python.exe' 'D:/AAdesk/202609/airz-backups/release-candidate-20261016/candidate-tools.py' --verify
```
该命令只核固定包、源码、历史证据、PHP语法与省份字典,不连接服务器或数据库,不启动应用/worker,不调用模型。源码或证据漂移时应建立新候选,不能覆盖本候选ZIP或重复执行 `--build`。
@@ -0,0 +1,56 @@
# 资质评估修复本地候选 r2
2026-10-19。`rc-20261019-assessment-fixes-r2` 已在本机固定并完成离线核验,**尚未发布**。继续遵守“先不发布、跳过真机”;没有生产访问、数据库访问、应用/数据库/worker启动、真实模型调用、上传、提交或推送。
固定清单为 [manifest.json](发布候选/assessment-fixes-20261019-r2/manifest.json),核验结果为 [verification.json](发布候选/assessment-fixes-20261019-r2/verification.json)。清单 SHA256 为 `f7ca780600a9f7636fa3c49000ad33b0a6ad47f8879f53e650b19938d42f5298`。三个包及本轮证据位于 `D:/AAdesk/202609/airz-backups/release-candidate-20261019-r2`。
## 相对 r1 的变化
前后端分别514/4119个非忽略文件与2026-10-16窄修复后的快照完全一致,没有新源码漂移。本轮只整理候选和文档,没有修改运行源码或回归脚本。
- 前端完整源码相对 `rc-20261016-assessment-fixes-r1` 仅 `packages/finance/report/report.uvue` 有变化,逐字节确认仅四句固定文案替换:准备状态、提交资料说明、风险空态和页尾参考说明。保留未填写不等于零、机构要求、结果仅供参考和不代表授信或获批;模板条件、脚本、样式、原值、回填、评分、完整度、准入和历史快照保持。
- `scripts/test-report-priority.cjs` 已包含上轮增加的 `cities.js` 隔离替身,原16场景/32断言、新鲜度检查和未知依赖拒绝保持。脚本作为本轮验证证据绑定,按原封包规则不进入运行包。
- 17个后端运行文件和12个有生产摘要依据的旧文件完全沿用 r1 字节,两个对应ZIP与 r1 的 SHA256 相同。所有原候选材料保持,未重新执行 r1 的 `--build`。
- 模型品牌展示继续隐藏,后端来源/modelCode等内部元数据保持;未过滤历史或未来模型自由正文。
累计零金额、省份、档案展示、匹配提示、年龄、短引用和公积金修复的业务范围及恢复边界,参见 [r1 操作单](资质评估修复发布候选-20261016.md)。其中关于 r1 静态文案和旧编译来源的描述属于原候选状态,r2 以本单及新清单为准。
## 固定包
| 包 | 文件数 | 本轮处理 |
| --- | ---: | --- |
| `backend-runtime.zip` | 17 | 与r1整包字节一致,14替换/3新增待现场核对 |
| `frontend-source.zip` | 206 | 新固定完整源码/资产,包含四句新文案;不是微信上传包 |
| `backend-recorded-baseline.zip` | 12 | 与r1整包字节一致,仅覆盖有生产摘要依据的12个旧文件 |
三包合计235成员。逐包摘要和逐文件路径/字节/SHA256见固定清单。前端本地Git运行差异仍为15项,相对20261015完整源码候选仍有9项变化;这两个集合均不能代替实际已发行小程序的源码基线。ZIP排除 `.env`、凭据、数据、测试、文档、IDE配置、缓存和 `unpackage`。既有production API配置及演示关闭保持,验证没有执行该真实API。
## 验收矩阵
| 项目 | 本轮证据与状态 | 边界 |
| --- | --- | --- |
| 前端内存回归 | 10入口通过:零金额15场景、地区22项、档案18组、匹配文案12场景、省份54项、预览20项、完成路由12项、产品19项、报告优先级16场景/32断言、PDF导出38项 | 使用内存响应替身,无真实API/账户/模型访问;不同套件不合并计数 |
| 包与依赖 | 3包路径/集合/字节/SHA256/CRC通过;220处前端别名导入、省份同源检查通过 | 只证明本地固定包及依赖完整性 |
| 后端语法及来源 | 包内17当前+12旧文件共29项PHP语法通过;259个运行PHP摘要与上轮公积金修复一致 | PHP仅语法检查,无App初始化/数据库/worker执行 |
| 微信编译关联 | 当前77个UTS/UVue、6个关键产物及29页记录与2026-10-16最后窄修复编译回执完全一致;编译后的报告含四句新文案且没有模型品牌绑定 | 本轮没有重新编译;开发产物不封入源码包;正式发行需另行编译并记录上传包 |
| 最新公积金策略 | 绑定原162项专项/102项离线反事实及既有回归 | 本轮未重跑收费入口,无新增真实模型复测;不能以反事实夹具宣称新模型通过 |
| 真机及浏览器视觉 | 本轮未验收,真机继续按要求跳过 | 不登记为通过 |
| 生产基线及回退材料 | 保留既往 `20261016-report-diagnostics-r1` 回执及137路径比较:124一致/12替换/1路由仅换行排除 | 本轮未现场核对;两处旧摘要和三处新增路径仍待确认,12文件旧包不是完整生产备份 |
编译来源为 `D:/AAdesk/202609/airz-backups/report-copy-priority-20261016-180237`。本轮源码摘要、测试脚本及日志、编译来源和历史证据均与 r2 清单绑定。测试及封包之后的文档变动单独记录在本轮 `preparation-receipt.json`,不混入运行包。
## 后续仍待确认
两处未证实生产旧摘要:`application/common/library/AssessmentReadiness.php`、`application/admin/lang/zh-cn/user/info.php`。三处新增路径:`application/common/library/ProfileZeroBalances.php`、`ProductAgeRules.php`、`ProductAiBindings.php`,必须现场核对是否原本不存在。不得把本地before原件当作生产精确备份。
实际发布须用户另行明确要求,之后按r1操作单先现场核对并完整备份14替换文件和3新路径状态、排空任务,再整体应用17文件并重载PHP/唯一worker,最后由用户编译发行小程序。当前最新生产版本只沿用历史记录,本轮未现场确认;实际已发行小程序源码和上传包摘要仍未知。
一旦形成新快照、检查点、报告或用量,应保留新数据并优先向前修复,不自动恢复旧代码或覆盖数据库。无需DDL不代表可随意回退。没有新增真实模型或真机授权,不自动接续这些验收。
## 本机只读复核
```powershell
& 'D:/Develop/Python311/python.exe' -B 'D:/AAdesk/202609/airz-backups/release-candidate-20261019-r2/candidate-tools.py' --verify
```
复核命令读取固定包、当前源码和已绑定证据,执行PHP语法及省份同源检查,不修改固定清单/回执,不连接服务器或数据库,不启动应用,也不调用模型。源码或证据漂移应建立新的候选标识,不覆盖r1或r2,不重复 `--build`。
@@ -0,0 +1,46 @@
# 资质评估累计修复后端发布
2026-10-19,按用户“先发布一下,你帮我发布后端,我自己发行小程序”授权,后端已发布为 `20261019-assessment-fixes-r2`。11:47:56(Asia/Shanghai)恢复原访问入口与唯一新worker;用户随后确认小程序1.1.0已于同日约12:00发行(时间为约数),真机仍按既有要求跳过;发行登记及线上复核见下文。
## 发布内容
使用固定候选 `rc-20261019-assessment-fixes-r2` 的 `backend-runtime.zip`,SHA256 `19437d0b99ca97d71940851c79a1e7c71cad8b3d0cb02a5b519cf6db474fe4d6`,17文件整体发布:14替换、3新增。新增依赖为 `ProfileZeroBalances.php`、`ProductAgeRules.php`、`ProductAiBindings.php`。包括零贷款/零信用卡空金额适用性、独立年龄范围本地判断、模型同批短引用和严格绑定、公积金完整可选路径、报告提示及配套等级/PDF兼容;前端档案及文案改动已包含在供用户发行的当前源码中,实际上传包与源码绑定的核实边界见下文。
没有DDL、业务迁移、产品导入、依赖安装、模型/额度调整、正式评估重试或本轮收费调用。仅数据库资源版本值及 `application/extra/site.php` 对应版本字段按发布流程更新;用户资料、原报告、检查点和用量保持。固定r1/r2包与原始实测证据保留,本发布记录独立覆盖冻结时“尚未发布”的状态,不回写冻结清单。
## 现场核对与维护验证
- 当前原版本为 `20261016-report-diagnostics-r1`,PHP7.4.33。历史137路径及同日补充的两个旧文件均匹配,3新增路径现场确实不存在;候选17文件与125保留路径共142路径核对通过。
- 生产25款上架产品按新策略完成只读编译规划;个人评估为66条模型条件、100模板、6批,零金额/年龄/短引用/可选公积金策略均已冻结。只规划,不生成评估或调用模型。
- 活动评估、实际调用和Redis即刻/延迟/保留队列均为0后,以临时Nginx维护门禁阻止外部写入,正常停止所属worker及PHP。完整备份14旧文件、3新增路径原不存在状态、环境文件、站点配置、worker unit、Nginx配置及全库SQL/表结构指纹;私密备份存放站点之外。
- 整体应用17文件,并同步资源版本;重新启动PHP。维护期30张表业务数据及精确结构指纹一致,只允许资源版本值变化,没有整库恢复或资料改写。
- 29项服务器本机HTTPS检查通过;4份历史报告、16组匹配列表、4份资料详情及4组归属边界、25产品与34省份只读检查通过,模型构造被显式阻断。恢复前精确回退干跑通过。
- 11:47:56恢复唯一新worker和原Nginx入口。31项公网HTTPS检查通过,TLS验证开启;三服务active、唯一worker命令/新PID核对及ready心跳通过,发布后观察未发现本轮worker错误/警告。环境、worker配置和Nginx原配置保持。
最初整包终端传输没有收到确认;只读检查证明服务器暂存目录不存在、维护未开启、三服务active。随后改用11段排他传输,每段及最终五份发布输入分别核SHA256,才开始维护。未在不明状态下重复发布或覆盖文件。
## 小程序1.1.0发行登记与线上复核
用户确认小程序 `1.1.0` 已于2026-10-19约12:00(Asia/Shanghai)发行。版本和时间来自用户报告,时间为约数;本次没有独立取得实际微信上传包,未验证上传包与当前源码摘要的绑定。无需为登记版本修改本地 `manifest.json`。
- 31项公网HTTPS检查通过,TLS证书验证开启,资源版本为 `20261019-assessment-fixes-r2`;7份后台JS摘要匹配。
- 142代码路径、环境与站点配置、原Nginx配置、worker unit及文件归属核对通过;三服务active,唯一worker PID3020785保持,心跳ready/1秒,三服务重启计数相较发布后基线不变。检查时活动评估/调用为0,三队列均0;这只是检查时的运行状态。
- 只读事务内完成4份历史报告、16组匹配列表、4份资料详情与4组归属边界,以及25上架产品/34省份读取。模型构造被显式阻断,检查未创建或重试生产评估,未调用收费模型。
约12:00至本次检查的 `airz-analysis` systemd journal 窗口返回0条事件,未见该来源的新增错误/警告;不据此推断全链路日志或实际用户评估均已验收。面板已退出、专用浏览器已关闭、临时凭据脚本不存在。真机继续按既有决定跳过,正式用户关键流程签收未在本轮执行。
补核固定r2候选时,原 `candidate-tools.py --verify` 在历史编译产物摘要守卫处退出1,不能记为本轮完整核验通过。独立只读诊断确认当前206份前端源码/资产、77份前端运行源码、259份后端运行源码、3个固定包及39份冻结证据一致;差异仅在本次对照的4个开发产物:`report.js`、`records.js`、`report-export.js`、`common/vendor.js`,修改时间均为2026-10-19约12:00:21(Asia/Shanghai)。开发产物仍为29页,报告四组新版文案检查通过。上述结果不能绑定实际微信上传包,也不能确定产物由哪次操作生成;没有改写冻结摘要、覆盖开发产物或重新编译。详见 `candidate-diagnostic.json`。
独立私密档案:`D:/AAdesk/202609/airz-backups/mini-program-release-1.1.0-20261019`。核心证据为 `release-user-report.json`、`public-smoke.json`、`online-verification.json`、`panel-cleanup.json`、`verification-receipt.json`、`candidate-diagnostic.json` 与 `completion-receipt.json`;原发布档案及固定候选保持,不覆盖原回执。
## 备份、证据和后续边界
服务器私密档案:`/root/airz-assessment-fixes-20261019`;本机档案:`D:/AAdesk/202609/airz-backups/production-release/airz-assessment-fixes-20261019`。本机目录仅当前Windows用户及SYSTEM可访问,服务器档案仅root可读。
关键证据:`authorization.json`、`manifest.json`、`readonly.json`、`stage.json`、`prepare.json`、`deploy.json`、`verify.json`、`rollback-check.json`、`resume.json`、`observe.json`、`final-verification.json`、`public-smoke.json`。备份下载、逐文件摘要及清理另见 `backup-download.json`、`backup-download-index.json`、`server-cleanup.json`、`panel-cleanup.json`,最终本机保全汇总见 `release-receipt.json`。
恢复写入后可能产生新规则快照、检查点、报告和实际用量;之后保留新数据并优先向前修复,不自动回退旧代码或覆盖数据库。维护期回退干跑通过不代表恢复写入后仍可直接回退。
小程序从 `D:/WorkSpace/cyun/ai融资` 的当前完整源码重新编译并由用户发行;源码/资产与r2的206文件包一致,不使用旧dev产物代替上传包。用户已报告1.1.0约12:00发行,登记见上文。实际已发行上传包尚未独立核实,不宣称由代理发布客户端或完成真机验收。
本轮没有新增真实模型调用;最新公积金真实判定、完整保存及回读已在此前独立隔离验收完成,见[实测与续跑记录](公积金规则实测与生产基线核对-20261019.md)。25款仅为参考候选,61条未采集细项保持待核实,不代表所有条件通过或授信获批。
@@ -0,0 +1,63 @@
# 零贷款、零信用卡填写与计算口径
2026-10-16,本地实施完成,尚未发布后端或小程序。本轮用户明确要求修改,并确认贷款统一按全部贷款统计;不删除填写项、不更改用户已经填写的金额。本次没有访问生产、调用真实模型、重试正式评估或提交/推送代码。
## 用户可见行为
- 新表单将“信用贷款笔数”改称“贷款笔数”,统计信用贷款、房贷、车贷等全部当前未结清贷款。
- 明确填写贷款 0 笔时,空的欠款总余额、贷款月还款按无贷款计算;明确填写信用卡 0 张时,空的信用卡总额度、已用额度按无卡计算。对应空金额不扣完整度,不再因此要求补填或暂不评级;收入等其他实际缺失/异常仍按原规则处理。
- 金额输入继续保留,已填金额原样保存。数量为 0 却有正金额时,前端和新请求服务端均提示核对,不覆盖或清空金额。空数量、正数量、非法金额不会授权补零。
- 确认页对符合条件的空金额显示“不适用(无贷款/无信用卡)”。历史编辑缺少口径标记时保留原笔数和金额,用户检查全部贷款后点击确认口径;不会静默把旧信用贷款笔数当成全部贷款。
- 后台最新资料表列名改为“贷款笔数(旧资料可能仅含信用贷款)”,避免把新总数显示成信用贷款分项。精确口径仍以对应评估版本档案为准。
## 数据与兼容边界
API 继续使用 inputVersion=3 和 creditLoanCount 字段;新增可选 form.loanCountScope,唯一合法值为 all_loans。服务端只在请求明确携带时增加 loan_count_scope 到评估版本 JSON,原缺省请求形状及幂等重放保持;口径不写入没有对应列的最新资料表,不做数据库迁移。只读详情回显原始空值,不把计算结果写回填写资料,姓名/身份证/手机号的原脱敏规则不变。
新完整度、评级及产品目录显式冻结 zeroBalancePolicy=all-loans-zero-balances-v1。ProfileZeroBalances 仅生成计算副本:严格整数 0/驱动字符串 0、贷款 all_loans 标记及组内金额一致时,才将该组空金额推导为 0.00。已有金额不改;任一非法值或正金额均阻止该组推导。原完整度四个派生金额记为 not_applicable,不计入分母;评级与明确填写四个 0.00 等价,原阈值、权重不变。
主报告、产品规则、AI 条件事实和 PDF 共用这一口径。派生金额保留 self_reported 来源并携带固定 derivedFrom;新贷款笔数事实携带 loanCountScope。企业报告也展示已填的对应贷款笔数,以说明派生依据。模型可看到有效金额及来源,不把这些金额当作漏填;姓名、完整证件及手机号继续不外发。
总贷款笔数为正不能推断信用贷款分项。标准硬/软条件比较只在副本中将此分项保持未知;参考建议模式下,该未单独采集分项不阻断、不加减分、不要求补填。AI 和报告仍保留用户总笔数及其 all_loans 标记。总贷款为 0 可确定当前信用贷款也为 0。未修改银行原条件或伪造条件通过。
旧报告直接读原保存内容;旧执行、目录和检查点没有新标记时继续原计算,不动态加入零金额、口径或新提示。现有失败任务不应通过重试来验收本轮新规则,应在配套发布后使用新建评估。
## 验证与证据
- 新增后端 270 项零金额边界/完整度/评级检查、118 项规范化/回显/报告/产品事实/PDF及贷款分项回归,均为合成内存数据;包含真实红灯到绿灯证据。详情测试使用内存替身,仅证明转换及脱敏,不代替数据库持久化或鉴权验收。
- 既有融资等级 122、参考建议 59、AI 模板 89/分批 36、条件 68、grounding 81、年龄 55、地区 68、匹配 151、产品展示 20 项通过。包含套件不重复累计。数据库/真实模型在相关内存入口被阻断。
- 前端 15 个零金额场景、22 个地区提交检查、26 个资料服务场景、54 个省份检查通过。微信 29 页本地编译成功;编译后报告优先级 16 场景/32 断言通过。原报告优先级测试的既有城市依赖白名单不足,仅在私密证据副本补空 stub,仓库原测试未改。
- 首轮实施时尚未验证新规则的真实数据库/任务链路;下节追加了本机隔离持久化和实际任务入口验收。真实模型语义、常驻worker和微信真机仍未覆盖。
证据:D:/AAdesk/202609/airz-backups/zero-balances-20261016,含实施前 465 文件快照、增量差异、源码摘要、后端回归与前端编译回执。新增 1 个运行 helper,替换 11 个后端运行/配置/语言文件,修改 4 个小程序运行文件,新增 2 个 PHP 测试和 1 个前端测试;无删除文件或依赖安装。
## 追加验收:保存、任务、接口与小程序回包
用户“继续下一步”承接本地验收,仍先不发布。本轮新增后端测试 `tests/profile_zero_persistence.php` 与 `tests/profile_zero_http.php`,运行源码保持;所有资料、账户和产品均为合成数据。显式独立bootstrap在应用初始化前替换真实模型类,默认端点为不可用本机地址;每次实际任务入口另传假模型。未访问默认.env、正式数据库或真实模型。
- 持久化735项通过,26次显式假模型调用。覆盖个人/企业原值保存与编辑回读、12款合成产品多批任务、派生零事实/完整度/评级/匹配/PDF同源、重复提交和消费无副作用、矛盾值拒绝。第2批失败后首批检查点复用,当前配置变化不改冻结新口径;人工构造无新标记的旧冻结任务重试后继续原缺失和评级语义。
- HTTP605项、119次本机请求、5次显式假模型调用通过。覆盖个人/企业空金额与显式零四组、新编辑版本、匿名401/跨账户404、报告未完成409、矛盾金额422、旧版本回读与版本冲突。真实匹配回包使用eligibility=all、limit=3,含1款通过与1款明确不符,不把明确不符隐藏或改成符合。5份实际PDF的类型、私有缓存、签名、长度及摘要核对通过。
- 真实UTS/API/页面脚本离线回放:业务入口回包8组、最终HTTP回包5场景31组通过。金额null与显式0、原表单字段、all_loans、省份、幂等键和新旧版本保持;报告完整度与匹配版本一致。实际5份PDF字节通过小程序服务的校验/打开/临时文件清理替身,不触发额外HTTP或解释请求。
- 使用已有Poppler渲染业务PDF14页与5份HTTP PDF共23页,检查总览和重点金额页,未见明显重叠、截断或乱码,原版式的少量尾页留白保留。PDF中文文本提取不完整,未用该工具声称全文提取验证;内容同源由报告/HTML断言及渲染图核对。没有安装依赖。
- 257个后端application PHP和76个前端UTS/UVue摘要与本轮前/前轮编译收尾一致,本轮不重编译。两个新增测试无隔离bootstrap均先拒绝;PHP语法、增量空白检查通过。测试自身的早期夹具/分页/版本预期修正日志保留,失败和重复轮不累计。
本次验收发现的展示差异:档案详情忠实显示原始空金额“未填写”,资产区仍为“已记录”;同revision报告按“不适用”处理且不扣完整度。该验收轮只记录差异;后续用户授权的展示修复见下节,原值和旧报告始终保留。
所有测试完成后独立核对29张合成表全空、6个schema原触发器保留、Redis DB15无键;核验exe/完整命令行/创建时间/监听PID后正常关闭仅本轮MySQL和Redis,51678/51679端口释放,HTTP进程与模式文件已清理。证据和数据目录保留在D:/AAdesk/202609/airz-backups/zero-balances-e2e-20261016,包含最终日志、真实合成信封、PDF/渲染图、各套件回执和cleanup.json。各套件分开计数,不重复累计。
本轮未验证真实模型输出质量、微信真机与常驻队列worker;假模型实际任务入口不能代替这些验收。没有发布、生产访问、付费调用、正式任务重试、提交或推送。
## 档案展示统一(本地未发布)
用户“继续下一步”同意处理上述展示差异。修改前端records.uts和archive.uvue,复用档案页已有的完整度请求,不增加报告请求,也不改后端/用户填写内容。
- 仅从同assessmentId/revisionId已保存的evaluated v2报告读取completeness.items。四个固定金额键totalDebtBalance、monthlyLoanPayment、creditCardTotalLimit、creditCardUsedLimit各自只有唯一条目且state为not_applicable时,才作为展示依据。
- 档案行增加专用fieldKey和isMissing元数据;isMissing只由原金额为null判断。档案页用独立展示副本将对应空金额显示“不适用”,明确填写的0和正金额保持,原详情、编辑回填、provided计数与完整度均不变。其他空字段仍显示“未填写”。
- 缺items、非数组、重复/冲突键、其他状态、旧v1、未评定、无报告、读取失败均不补推“不适用”。不按表单笔数/口径重算历史,不借用另一版本的最新成功报告。读取失败保留原详情与手动重试入口。
- 原账户、页面离开和报告请求序号保护保留,成功或失败回包还需匹配当前详情id/revision;清页和每次读取时清理展示映射,迟到回包不能恢复旧账户或旧版资料。
新增scripts/test-archive-display.cjs,18组实际UTS/页面脚本内存回归通过,含修复前预期红灯及修复后绿灯;原零金额15场景、地区提交/详情22项保持通过。HBuilderX5.26微信29页本地编译通过。运行增量仅2个前端文件,74个其他前端运行文件与257个后端application PHP摘要保持;独立增量及测试审查未发现实质问题。没有安装依赖、生产访问、数据库或模型调用、发布、提交或推送。
证据:D:/AAdesk/202609/airz-backups/archive-display-20261016,包含before、红绿日志、微信编译日志、增量diff及verification.json。回归使用JavaScript环境模拟UTS和页面生命周期,不代替微信真机/浏览器视觉验收;本轮未重跑上节数据库/模型链路,前轮证据保留。
后续需配套发布后端并由用户重新编译发行小程序,新客户端会发送新口径字段,不能先接到不支持它的旧后端。后端应先发布、重载 PHP 与唯一 worker,再发行小程序;后端的旧客户端兼容分支保留。当前生产仍为上一轮 20261016-report-diagnostics-r1,本轮没有发布授权或生产变更。
+13
View File
@@ -1,5 +1,18 @@
# 融资需求与评估结果数据契约
## 2026-10-14 补充:结果默认进入匹配栏目(本地待发布)
提交后的分析页仅在status=succeeded、hasReport=true且publishedRevisionId等于本次revisionId时,自动redirect至报告tab=matches并携带该revisionId。processing继续轮询,failed保留恢复入口;离页、过期请求或切换账号的回包不能触发跳转,旧成功版本不能冒充本次结果。无tab的历史报告仍默认overview,显式栏目及冻结版本兼容保持。
所有实际匹配三态均展示已有有效score(含0%),保留真实状态;待确认文案改为待核实,资料不足不混同,旧null分数不补造。报告/列表/详情/对比统一此口径,不改准入、分数、排序或rank。匹配栏目底部只保留修改资料与一句结果说明,其他栏目保留完整报告行动及元信息。本节覆盖下方旧展示限制;本轮前端115项纯检查、33项合成页面检查及微信29页编译通过,未部署/调用真实模型或真机验收。
## 2026-10-14 匹配摘要与派生年龄增补(本地待发布)
报告匹配标签与独立列表默认展示前3款实际产品,优先符合条件,再待核实、条件不符;不足3款时按实际数量显示。保留真实三态,unknown/ineligible不进入候选排名,不冒充推荐或获批概率。符合条件项保留真实分数(包括0%),旧结果无分数时不造分;查看更多使用冻结结果的all分组游标。
新个人评估从有效身份证计算评估当天的周岁,冻结策略为identity-age-v1。只向模型/报告提供age事实(integer、unit=age、source=self_reported、state=provided),供 `fact:age` 证据引用;证件号码不进入模型或公开响应。身份证无效时不猜算,企业评估不自动使用个人年龄;旧冻结任务和旧报告保持。没有新增采集项/数据库列,具体校验与排序见[匹配规则契约最新增补](产品匹配规则开发契约.md)。
## 2026-10-09 万元输入与缴纳资料增补(当前)
小程序所有业务金额含socialSecurityBase/housingFundBase以万元输入和展示,最多六位小数;提交前以整数分换算为yuan元两位小数字符串。0.8万元=8000.00元、0.000001万元=0.01元;回填、确认、档案和在线报告对称转换。后端元金额及历史快照原值不改,基数仅随评估版本JSON保存,不增fa_user_info列;未知留空、未缴纳清空、企业拒绝有效个人基数。
+13 -4
View File
@@ -8,7 +8,7 @@
<view v-if="processing" class="analysis-pulse"><view class="pulse-dot" :style="pulseDotStyle()"></view><text class="stage-label">{{ stageLabel }}</text><text class="pulse-dots" :style="pulseDotsStyle()">···</text></view>
<text v-if="error.length > 0" class="status-message">{{ error }}</text>
<text v-if="canRetry" class="view-now" @click="retry">{{ retrying ? '正在提交…' : '重新分析' }}</text>
<text v-if="succeeded" class="view-now" @click="report">查看分析报告</text>
<text v-if="succeeded" class="view-now" @click="report">查看匹配结果</text>
<text class="view-now" @click="refreshStatus">{{ retrying ? '正在提交重试…' : '刷新状态' }}</text>
<text class="view-now" @click="archive">查看已提交资料</text>
</view>
@@ -18,9 +18,11 @@
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { fetchAssessmentStatus, retryAssessment } from '@/packages/finance/services/records.uts'
import { reportRoute } from '@/services/report-route.uts'
const { statusStyle, pageStyle } = usePageLayout()
const title = ref('正在读取评估状态')
const message = ref(''); const error = ref(''); const assessmentId = ref('')
const publishedRevisionId = ref('')
const canRetry = ref(false); const succeeded = ref(false); const retrying = ref(false); const processing = ref(false); const stageLabel = ref('正在准备分析')
let retryKey = ''; let retryTask = ''; let retryOwner = ''
let timer = 0; let motionTimer = 0; let visible = false; let generation = 0
@@ -57,7 +59,14 @@
stageLabel.value = result.stage == 'queued' ? '正在排队,分析任务即将开始' : '正在整理资料,生成分析报告'
if (retryTask != result.taskId || retryOwner != owner) { retryKey = ''; retryTask = result.taskId; retryOwner = owner }
title.value = result.statusLabel
message.value = result.message.length > 0 ? result.message : (result.status == 'processing' ? (result.stage == 'queued' ? '资料已保存,等待分析。您可以稍后从档案查看。' : '正在分析自填资料,您可以稍后从档案查看。') : '分析已完成,可查看本次报告。')
message.value = result.message.length > 0 ? result.message : (result.status == 'processing' ? (result.stage == 'queued' ? '资料已保存,等待分析。您可以稍后从档案查看。' : '正在分析自填资料,您可以稍后从档案查看。') : (result.status == 'failed' ? '分析未完成,可重新分析或刷新状态。已提交的资料无需重新填写。' : '分析已完成,可查看本次报告。'))
if (result.status == 'succeeded') {
// 只打开本次已发布版本;重评处理中或旧报告回包不会触发跳转。
const target = result.hasReport && result.publishedRevisionId == result.revisionId ? reportRoute(assessmentId.value, 'matches', result.publishedRevisionId) : ''
succeeded.value = target.length > 0
if (target.length > 0) { publishedRevisionId.value = result.publishedRevisionId; uni.redirectTo({ url: target }); return }
message.value = '本次报告尚未就绪,请刷新状态。'
}
// 只轮询服务端仍在处理的任务,不用计时器模拟完成。
if (result.status == 'processing') timer = setTimeout(() => { loadStatus() }, 5000)
} catch (err) {
@@ -86,11 +95,11 @@
} catch (err) { if (visible && request == generation && getSession()?.getString('token') == owner) error.value = err instanceof Error ? err.message : '重新分析失败,请重试' }
finally { retrying.value = false }
}
const report = () => { uni.navigateTo({ url: '/packages/finance/report/report?assessmentId=' + encodeURIComponent(assessmentId.value) }) }
const report = () => { const target = reportRoute(assessmentId.value, 'matches', publishedRevisionId.value); if (succeeded.value && target.length > 0) uni.redirectTo({ url: target }) }
const archive = () => { uni.reLaunch({ url: '/packages/finance/archive/archive?assessmentId=' + encodeURIComponent(assessmentId.value) }) }
onLoad((options) => { if (options != null) assessmentId.value = options['assessmentId'] ?? '' })
onShow(() => { visible = true; loadStatus() })
onHide(() => { visible = false; generation++; clearTimeout(timer); stopMotion(); canRetry.value = false; succeeded.value = false; processing.value = false; message.value = ''; error.value = '' })
onHide(() => { visible = false; generation++; clearTimeout(timer); stopMotion(); canRetry.value = false; succeeded.value = false; processing.value = false; publishedRevisionId.value = ''; message.value = ''; error.value = '' })
onUnload(() => { visible = false; generation++; clearTimeout(timer); stopMotion() })
</script>
+12 -7
View File
@@ -72,16 +72,17 @@
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { clearAssessmentFlow } from '@/services/assessment-flow.uts'
import { AssessmentRecord, AssessmentDetail, ArchiveSection, fetchAssessments, fetchAssessmentDetail, fetchArchiveCompleteness, fetchAssessmentRevisions } from '@/packages/finance/services/records.uts'
import { AssessmentRecord, AssessmentDetail, ArchiveSection, ArchiveCompleteness, archiveSections, fetchAssessments, fetchAssessmentDetail, fetchArchiveCompleteness, fetchAssessmentRevisions } from '@/packages/finance/services/records.uts'
const { pageStyle, contentStyle } = usePageLayout()
const type = ref('personal'); const selectedId = ref(''); const records = ref<AssessmentRecord[]>([])
const revisionId = ref(''); const versions = ref<AssessmentRecord[]>([]); const versionsMore = ref(false); const versionsError = ref(''); const versionsLoading = ref(false); let versionsCursor = ''
const detail = ref<AssessmentDetail | null>(null); const expanded = ref(''); const historyOpen = ref(false)
const loading = ref(false); const error = ref(''); const listError = ref(''); const listLoading = ref(false); const hasMore = ref(false)
const completeness = ref<number | null>(null); const completenessFailed = ref(false)
const archiveCompleteness = ref<ArchiveCompleteness | null>(null)
const completenessMessage = ref('提交评估后,查看对应报告的资料完整度。')
let cursor = ''; let generation = 0; let reportRequest = 0; let visible = false; let routeError = ''; let routeId = ''; let owner = ''; let accountTimer = 0
const sections = computed((): ArchiveSection[] => detail.value != null ? detail.value!.sections : [
const sections = computed((): ArchiveSection[] => detail.value != null ? archiveSections(detail.value!, archiveCompleteness.value) : [
{ id: 'basic', icon: 'file-text', title: '基本信息', note: type.value == 'enterprise' ? '企业与联系人信息' : '身份与联系方式', fields: [], provided: 0 },
{ id: 'income', icon: 'income', title: type.value == 'enterprise' ? '经营信息' : '职业与收入', note: type.value == 'enterprise' ? '经营行业、月数与营收' : '职业、收入与缴纳情况', fields: [], provided: 0 },
{ id: 'assets', icon: 'asset', title: '资产与负债', note: '贷款、信用卡额度与房车情况', fields: [], provided: 0 },
@@ -94,7 +95,7 @@
}
const clearView = () => {
detail.value = null; records.value = []; versions.value = []; versionsMore.value = false; versionsError.value = ''; versionsCursor = ''; versionsLoading.value = false; cursor = ''; hasMore.value = false; expanded.value = ''
completeness.value = null; completenessFailed.value = false; reportRequest++
completeness.value = null; archiveCompleteness.value = null; completenessFailed.value = false; reportRequest++
completenessMessage.value = '提交评估后,查看对应报告的资料完整度。'
error.value = ''; listError.value = ''; listLoading.value = false
}
@@ -120,13 +121,17 @@
return false
}
const readCompleteness = async (id: string, request: number, token: string) => {
if (detail.value == null || detail.value!.record.id != id) return
const expectedRevision = detail.value!.record.revisionId
const reportSequence = ++reportRequest
completeness.value = null; completenessFailed.value = false; completenessMessage.value = '正在读取本次报告的资料完整度…'
completeness.value = null; archiveCompleteness.value = null; completenessFailed.value = false; completenessMessage.value = '正在读取本次报告的资料完整度…'
try {
const result = await fetchArchiveCompleteness(id, detail.value?.record.revisionId ?? revisionId.value)
if (active(request, token) && reportSequence == reportRequest) { completeness.value = result.percent; completenessMessage.value = result.description }
const result = await fetchArchiveCompleteness(id, expectedRevision)
if (active(request, token) && reportSequence == reportRequest && detail.value?.record.id == id && detail.value?.record.revisionId == expectedRevision) {
archiveCompleteness.value = result; completeness.value = result.percent; completenessMessage.value = result.description
}
} catch (err) {
if (active(request, token) && reportSequence == reportRequest) {
if (active(request, token) && reportSequence == reportRequest && detail.value?.record.id == id && detail.value?.record.revisionId == expectedRevision) {
completenessFailed.value = true; completenessMessage.value = '完整度暂时读取失败,已提交资料仍可查看。'
}
}
+14 -12
View File
@@ -13,8 +13,7 @@
<view class="form-row"><text class="field-label">{{ assessmentType == 'enterprise' ? '联系人' : '姓名' }}</text><input v-model="form.name" class="field-input" placeholder="请输入姓名" /></view>
<view v-if="assessmentType == 'personal'" class="form-row"><text class="field-label">身份证号</text><input v-model="form.idCard" class="field-input" maxlength="18" placeholder="请输入身份证号" /></view>
<view class="form-row"><text class="field-label">手机号</text><input v-model="form.phone" class="field-input" type="number" maxlength="11" placeholder="请输入手机号" /></view>
<AssessmentSelect label="所在城市" :modelValue="form.city" @update:modelValue="cityChanged" :options="['深圳市', '广州市', '北京市', '上海市', '杭州市', '其他城市']" />
<view v-if="form.city == '其他城市' || customCity.length > 0" class="form-row"><text class="field-label">城市名称</text><input v-model="customCity" class="field-input" placeholder="请输入所在城市" /></view>
<AssessmentProvinceSelect v-model="form.city" />
<AssessmentSelect v-if="assessmentType == 'personal'" label="婚姻状况" v-model="form.maritalStatus" :options="['未婚', '已婚', '离异', '丧偶']" />
</view>
<view v-else-if="step == 1" class="form-list">
@@ -37,11 +36,14 @@
</view>
<view v-else-if="step == 2" class="form-list">
<text class="asset-section-title">贷款信息</text>
<view class="form-row"><text class="field-label">信用贷款笔数</text><input v-model="form.creditLoanCount" class="field-input" type="number" maxlength="4" placeholder="无则填 0" /><text class="field-unit">笔</text></view>
<view class="form-row"><text class="field-label">贷款笔数</text><input v-model="form.creditLoanCount" class="field-input" type="number" maxlength="4" placeholder="无则填 0" /><text class="field-unit">笔</text></view>
<text class="local-note">统计信用贷款、房贷、车贷等全部当前未结清贷款。无贷款填 0,下面两项金额可留空;已填写的金额会保留。</text>
<view v-if="form.loanCountScope != 'all_loans' && form.creditLoanCount.length > 0"><text class="local-note">旧资料仅统计信用贷款,请按上述全部贷款口径检查并确认笔数;原笔数和金额均已保留。</text><text class="consent-link" @click="confirmLoanCountScope">已按全部贷款口径确认笔数</text></view>
<view class="form-row"><text class="field-label">欠款总余额</text><input v-model="form.totalDebtBalance" class="field-input" type="digit" maxlength="15" placeholder="请输入金额" /><text class="field-unit">万元</text></view>
<view class="form-row"><text class="field-label">贷款月还款金额</text><input v-model="form.monthlyLoanPayment" class="field-input" type="digit" maxlength="15" placeholder="请输入金额" /><text class="field-unit">万元</text></view>
<text class="asset-section-title">信用卡信息</text>
<view class="form-row"><text class="field-label">银行信用卡张数</text><input v-model="form.creditCardCount" class="field-input" type="number" maxlength="4" placeholder="无则填 0" /><text class="field-unit">张</text></view>
<text class="local-note">无信用卡填 0,总额度和已使用总额度可留空;已填写的金额会保留。</text>
<view class="form-row"><text class="field-label">总额度</text><input v-model="form.creditCardTotalLimit" class="field-input" type="digit" maxlength="15" placeholder="请输入金额" /><text class="field-unit">万元</text></view>
<view class="form-row"><text class="field-label">已使用总额度</text><input v-model="form.creditCardUsedLimit" class="field-input" type="digit" maxlength="15" placeholder="请输入金额" /><text class="field-unit">万元</text></view>
<text class="asset-section-title">房车信息</text>
@@ -74,6 +76,8 @@
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
import AssessmentSelect from '@/packages/finance/components/AssessmentSelect.uvue'
import AssessmentProvinceSelect from '@/packages/finance/components/AssessmentProvinceSelect.uvue'
import { provinceForCity, provinceCodeForName } from '@/services/cities.uts'
import { AssessmentForm, SelectedDocument, DataAuthorization, emptyAssessmentForm, activeAssessmentType, getAssessmentInput, formFromInput, documentsFromInput, setAssessmentInput, showAgreementNotice, assetValidationMessage, contributionValidationMessage, clearAssessmentFlow } from '@/services/assessment.uts'
import { contributionOptions as contributionChoices, contributionBaseVisible as baseVisible } from '@/services/contribution.uts'
import { uploadDocument, downloadDocument, deleteDocument, releaseDocumentTemp, documentCategory, fetchOwnedDocuments } from '@/services/documents.uts'
@@ -88,8 +92,6 @@
const form = ref<AssessmentForm>(emptyAssessmentForm())
let ownerToken = ''; let goingToConfirm = false; let goingToAgreement = false; let visible = false; let accountTimer = 0
const sameOwner = (): boolean => { const session = getSession(); return session != null && session['token'] == ownerToken }
const customCity = ref('')
const cityChanged = (city: string) => { form.value.city = city; if (city != '其他城市') customCity.value = '' }
const documentConsent = ref(false)
const dataConsent = ref(false)
const documents = ref<SelectedDocument[]>([])
@@ -110,7 +112,7 @@
const session = getSession()
ownerToken = session?.getString('token') ?? ''
editGeneration++; invalidateDocuments(); documentConsentId = ''; consentKey = ''; pendingAgreement = emptyAgreement(); documentMessage.value = ''; initialized = false; stopAccountTimer(); clearAssessmentFlow()
form.value = emptyAssessmentForm(); documents.value = []; customCity.value = ''; step.value = 0
form.value = emptyAssessmentForm(); documents.value = []; step.value = 0
documentConsent.value = false; dataConsent.value = false; authorizations.value.forEach((item) => { item.selected = false })
baseRevisionId = ''; editLoading.value = false; editError.value = '账户已变化,请返回重新填写。'
if (session == null) uni.redirectTo({ url: '/packages/account/login/login' })
@@ -126,13 +128,13 @@
const rememberInput = () => {
if (!initialized || !sameOwner()) return
const source = form.value
const currentForm: AssessmentForm = { name: source.name, idCard: source.idCard, phone: source.phone, city: source.city, maritalStatus: source.maritalStatus, occupation: source.occupation, company: source.company, monthlyIncome: source.monthlyIncome, socialSecurity: source.socialSecurity, housingFund: source.housingFund, socialSecurityBase: source.socialSecurityBase, housingFundBase: source.housingFundBase, house: source.house, car: source.car, savings: source.savings, creditCard: source.creditCard, otherAssets: source.otherAssets, creditLoanCount: source.creditLoanCount, totalDebtBalance: source.totalDebtBalance, monthlyLoanPayment: source.monthlyLoanPayment, creditCardCount: source.creditCardCount, creditCardTotalLimit: source.creditCardTotalLimit, creditCardUsedLimit: source.creditCardUsedLimit, enterpriseName: source.enterpriseName, businessCode: source.businessCode, annualRevenue: source.annualRevenue, operatingYears: source.operatingYears, operatingMonths: source.operatingMonths }
if (currentForm.city == '其他城市' && customCity.value.trim().length > 0) currentForm.city = customCity.value.trim()
const currentForm: AssessmentForm = { name: source.name, idCard: source.idCard, phone: source.phone, city: source.city, maritalStatus: source.maritalStatus, occupation: source.occupation, company: source.company, monthlyIncome: source.monthlyIncome, socialSecurity: source.socialSecurity, housingFund: source.housingFund, socialSecurityBase: source.socialSecurityBase, housingFundBase: source.housingFundBase, house: source.house, car: source.car, savings: source.savings, creditCard: source.creditCard, otherAssets: source.otherAssets, creditLoanCount: source.creditLoanCount, loanCountScope: source.loanCountScope, totalDebtBalance: source.totalDebtBalance, monthlyLoanPayment: source.monthlyLoanPayment, creditCardCount: source.creditCardCount, creditCardTotalLimit: source.creditCardTotalLimit, creditCardUsedLimit: source.creditCardUsedLimit, enterpriseName: source.enterpriseName, businessCode: source.businessCode, annualRevenue: source.annualRevenue, operatingYears: source.operatingYears, operatingMonths: source.operatingMonths }
setAssessmentInput(assessmentType.value, step.value, currentForm, documents.value, authorizations.value, documentConsent.value, dataConsent.value, editId.value, baseRevisionId)
}
const confirmLoanCountScope = () => { if (!ensureOwner()) return; form.value.loanCountScope = 'all_loans'; rememberInput() }
const showAgreement = (title: string) => { if (!ensureOwner()) return; rememberInput(); goingToAgreement = true; showAgreementNotice(title) }
const restore = (type: string) => {
assessmentType.value = type; step.value = 0; form.value = emptyAssessmentForm(); customCity.value = ''; documents.value = []; documentConsent.value = false; dataConsent.value = false
assessmentType.value = type; step.value = 0; form.value = emptyAssessmentForm(); documents.value = []; documentConsent.value = false; dataConsent.value = false
authorizations.value.forEach((item) => { item.selected = false })
const saved = getAssessmentInput(type)
if (saved != null) {
@@ -141,7 +143,7 @@
documentConsent.value = saved['documentConsent'] == true; dataConsent.value = saved['dataConsent'] == true
const grants = saved['authorizations'] as string[] | null
if (grants != null) authorizations.value.forEach((item) => { item.selected = grants.includes(item.key) })
if (form.value.city.length > 0 && !['深圳市', '广州市', '北京市', '上海市', '杭州市', '其他城市'].includes(form.value.city)) { customCity.value = form.value.city; form.value.city = '其他城市' }
if (form.value.city.length > 0) form.value.city = provinceForCity(form.value.city)
}
initialized = true
rememberInput()
@@ -221,7 +223,7 @@
editGeneration++; if (editId.value.length > 0) editError.value = '已离开修改流程,请返回档案重新进入。'
// 离页清空资料但不切换步骤,避免卸载时又挂载另一组原生输入组件。
clearAssessmentFlow(); form.value = emptyAssessmentForm(); documents.value = []
customCity.value = ''; documentConsent.value = false; dataConsent.value = false
documentConsent.value = false; dataConsent.value = false
authorizations.value.forEach((item) => { item.selected = false })
}
})
@@ -358,7 +360,7 @@
else if (form.value.name.trim().length < 2) message = '请输入正确姓名'
else if (assessmentType.value == 'personal' && !/(^[0-9]{15}$)|(^[0-9]{17}[0-9Xx]$)/.test(form.value.idCard.trim())) message = '请输入正确身份证号'
else if (!/^1[3-9][0-9]{9}$/.test(form.value.phone)) message = '请输入正确手机号'
else if (form.value.city.length == 0 || (form.value.city == '其他城市' && customCity.value.trim().length == 0)) message = '请选择或填写所在城市'
else if (provinceCodeForName(form.value.city) == null) message = '请选择所在省份'
}
if (step.value == 1) {
const amount = assessmentType.value == 'enterprise' ? form.value.annualRevenue : form.value.monthlyIncome
+4 -4
View File
@@ -97,7 +97,7 @@
{ id: 'score', label: '匹配度', height: 120 },
{ id: 'amount', label: '额度范围', height: 120 }, { id: 'term', label: '期限范围', height: 120 }, { id: 'rate', label: '年化利率', height: 120 },
{ id: 'objects', label: '适用对象', height: 120 }, { id: 'regions', label: '适用地区', height: 140 },
{ id: 'positive', label: '推荐与参考理由', height: 360 }, { id: 'blocking', label: '不推荐原因', height: 300 }, { id: 'cautions', label: '注意与补充资料', height: 300 },
{ id: 'positive', label: '推荐与参考理由', height: 360 }, { id: 'blocking', label: '不推荐原因', height: 300 }, { id: 'cautions', label: '注意事项', height: 300 },
{ id: 'conditions', label: '准入条件', height: 300 }, { id: 'repayment', label: '还款方式', height: 120 },
{ id: 'materials', label: '准备材料', height: 260 }, { id: 'channel', label: '办理渠道', height: 120 }, { id: 'source', label: '资料来源', height: 260 }
]
@@ -125,8 +125,8 @@
if (key == 'score') return matchPercentage(m.state, m.score) || '—'
if (key == 'availability') return m.availabilityLabel + ';不改变历史结论'
if (key == 'positive') return compareReasonText(m.positive, '暂无已确认满足的条件')
if (key == 'blocking') return compareReasonText(m.blocking, '未发现已知不符条件;仍以核验为准')
if (key == 'cautions') return compareReasonText(m.cautions, '暂无待核实提示;不代表已获审批')
if (key == 'blocking') return compareReasonText(m.blocking, '未发现已知不符条件;结果仅供参考')
if (key == 'cautions') return compareReasonText(m.cautions, '暂无需要特别注意的已知条件;不代表审批结果')
if (key == 'conditions') return p.conditions.length > 0 ? p.conditions.join('\n\n') : '未提供'
if (key == 'amount') return hasProductValue(p.amount) ? p.amount : '—'
if (key == 'term') return hasProductValue(p.term) ? p.term : '—'
@@ -142,7 +142,7 @@
const rowHasValue = (row: CompareRow): boolean => columns.value.some((column: CompareColumn): boolean => !usable(column) || hasProductValue(cell(column, row.id)))
const overviewRows = computed((): CompareRow[] => overviewDefinitions.filter((row: CompareRow): boolean => !['score', 'amount', 'term', 'rate'].includes(row.id) || rowHasValue(row)))
const rows = computed((): CompareRow[] => detailRows.filter((row: CompareRow): boolean => !['score', 'amount', 'term', 'rate', 'repayment', 'channel', 'source'].includes(row.id) || rowHasValue(row)))
const orderingNote = computed((): string => columns.value.some((column: CompareColumn): boolean => usable(column) && column.data!.orderingVersion == 'product-score-descending-v1') ? '匹配列表按匹配度排序,此处按所选顺序对比。匹配度不代表获批概率。' : '按所选顺序对比,具体条件以机构核验为准。')
const orderingNote = computed((): string => columns.value.some((column: CompareColumn): boolean => usable(column) && column.data!.orderingVersion == 'product-score-descending-v1') ? '同组产品按匹配度排序,此处按所选顺序对比。匹配度不代表获批概率。' : '按所选顺序对比,具体条件以机构核验为准。')
const loadColumn = async (id: string) => {
if (!current(generation, owner)) return
const column = columns.value.find((c: CompareColumn): boolean => c.id == id)
@@ -0,0 +1,44 @@
<template>
<view class="province-row" @click="open"><text class="province-label">所在省份</text><text :class="isSelected ? 'province-value' : 'province-value province-placeholder'">{{ isSelected ? modelValue : '请选择省份' }}</text><AppIcon name="chevron-right" :size="28" /></view>
<view v-if="opened" class="province-mask" @click="cancel">
<view class="province-sheet" @click.stop="noop">
<view class="province-toolbar"><text class="province-title">选择所在省份</text><text class="province-cancel" @click="cancel">取消</text></view>
<scroll-view scroll-y class="province-list" :scroll-top="selectedTop">
<view v-for="item in provinceOptions" :key="item.code" :id="'province-' + item.code" :class="item.name == modelValue ? 'province-option province-active' : 'province-option'" @click="choose(item.name)"><text class="province-option-name">{{ item.name }}</text><text v-if="item.name == modelValue" class="province-check">✓</text></view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="uts">
import AppIcon from '@/components/AppIcon.uvue'
import { provinceOptions, provinceCodeForName } from '@/services/cities.uts'
const props = defineProps({ modelValue: { type: String, default: '' } })
const emit = defineEmits(['update:modelValue'])
const opened = ref(false)
const selectedTop = ref(0)
const isSelected = computed((): boolean => provinceCodeForName(props.modelValue) != null)
const noop = () => {}
const open = () => {
uni.hideKeyboard(); selectedTop.value = 0; opened.value = true
const index = provinceOptions.findIndex((item): boolean => item.name == props.modelValue)
nextTick(() => { selectedTop.value = Math.max(0, index - 2) * 94 * uni.getWindowInfo().windowWidth / 750 })
}
const cancel = () => { opened.value = false }
const choose = (name: string) => { emit('update:modelValue', name); opened.value = false }
</script>
<style>
.province-row { min-height: 106rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.province-label { font-size: 28rpx; color: #29374e; margin-right: 16rpx; flex-shrink: 0; }
.province-value { flex: 1; min-width: 0; margin-right: 12rpx; text-align: right; color: #22324a; font-size: 27rpx; line-height: 38rpx; }
.province-placeholder { color: #8995a7; }
.province-mask { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; background-color: rgba(20, 32, 52, 0.4); justify-content: flex-end; }
.province-sheet { background-color: #ffffff; border-top-left-radius: 24rpx; border-top-right-radius: 24rpx; padding-bottom: 48rpx; }
.province-toolbar { height: 112rpx; flex-direction: row; align-items: center; justify-content: space-between; padding-left: 32rpx; padding-right: 32rpx; }
.province-title { font-size: 30rpx; color: #22324a; font-weight: 600; }
.province-cancel { font-size: 28rpx; color: #6e7e95; padding: 16rpx; }
.province-list { height: 650rpx; }
.province-option { height: 94rpx; padding-left: 40rpx; padding-right: 40rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.province-option-name { color: #22324a; font-size: 28rpx; }
.province-active { background-color: #eef4ff; }
.province-check { color: #2468ec; font-size: 30rpx; }
</style>
@@ -0,0 +1,41 @@
<template>
<view class="match-card" @click="emit('open', item.id)">
<view class="match-head">
<view :class="markClass(item.product.bank)"><image v-if="item.product.logo.length > 0 && !logoFailed" class="institution-logo" :src="item.product.logo" mode="aspectFit" @error="logoFailed = true" /><text v-else class="institution-letter">{{ item.product.bank.length > 0 ? item.product.bank.slice(0, 1) : item.product.name.slice(0, 1) }}</text></view>
<view class="identity"><text v-if="item.product.bank.length > 0" class="institution-name">{{ item.product.bank }}</text><text :class="item.product.bank.length > 0 ? 'product-name' : 'institution-name'">{{ item.product.name }}</text></view>
<view class="match-status"><text v-if="matchPercentage(item.state, item.score).length > 0" :class="item.state == 'eligible' ? 'percentage' : 'percentage reference-percentage'">匹配度 {{ matchPercentage(item.state, item.score) }}</text><text :class="item.state == 'eligible' ? 'state eligible' : (item.state == 'ineligible' ? 'state ineligible' : 'state unknown')">{{ item.stateLabel }}</text></view><text class="arrow">›</text>
</view>
<view class="product-tags"><text v-if="hasProductValue(item.product.rate)" class="product-tag">年化 {{ item.product.rate }}</text><text v-if="hasProductValue(item.product.term)" class="product-tag">{{ item.product.term }}</text><text v-if="hasProductValue(item.product.channel)" class="product-tag">{{ item.product.channel }}</text><text v-if="!hasProductValue(item.product.rate) && !hasProductValue(item.product.term) && !hasProductValue(item.product.channel)" class="product-tag">{{ item.product.objects }}</text></view>
<text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }}</text>
<view v-if="selectable" class="card-actions"><text class="detail-label">查看匹配依据</text><view class="compare-control" @click.stop="emit('select', item.id)"><text :class="selected ? 'selection selected-box' : 'selection'">{{ selected ? '✓' : '+' }}</text><text class="compare-label">{{ selected ? '已选对比' : '加入对比' }}</text></view></view>
</view>
</template>
<script setup lang="uts">
import { MatchView, hasProductValue, matchPercentage } from '@/packages/finance/services/matches.uts'
const props = defineProps({ item: { type: Object as PropType<MatchView>, required: true }, selectable: { type: Boolean, default: false }, selected: { type: Boolean, default: false } })
const emit = defineEmits(['open', 'select'])
const logoFailed = ref(false)
watch((): string => props.item.product.logo, () => { logoFailed.value = false })
const markClass = (bank: string): string => bank.includes('工商') || bank.includes('招商') || bank.includes('中国银行') ? 'institution-mark red' : (bank.includes('农业') || bank.includes('邮储') ? 'institution-mark green' : 'institution-mark blue')
</script>
<style>
.match-card { padding: 28rpx 6rpx 24rpx; border-bottom: 1rpx solid #edf1f6; background-color: #fff; }
.match-head { flex-direction: row; align-items: center; }
.institution-mark { width: 72rpx; height: 72rpx; border-radius: 36rpx; align-items: center; justify-content: center; margin-right: 18rpx; flex-shrink: 0; }
.blue { background-color: #edf4ff; }.red { background-color: #fff0f1; }.green { background-color: #eaf8f2; }
.institution-logo { width: 58rpx; height: 58rpx; }.institution-letter { font-size: 31rpx; color: #1763b9; font-weight: 700; }
.red .institution-letter { color: #bd2e3d; }.green .institution-letter { color: #138a63; }
.identity { flex: 1; min-width: 0; }.institution-name { font-size: 28rpx; line-height: 40rpx; font-weight: 600; color: #24354b; }
.product-name { font-size: 24rpx; line-height: 36rpx; color: #77869a; margin-top: 3rpx; }
.match-status { align-items: flex-end; margin-left: 12rpx; flex-shrink: 0; }.percentage { color: #15946c; font-size: 23rpx; line-height: 36rpx; font-weight: 600; }
.reference-percentage { color: #547aa8; }
.state { font-size: 20rpx; line-height: 30rpx; padding: 2rpx 10rpx; border-radius: 5rpx; margin-top: 3rpx; }
.eligible { color: #19815d; background-color: #eaf8f2; }.ineligible { color: #b56849; background-color: #fff3ed; }.unknown { color: #947125; background-color: #fff8e8; }
.arrow { color: #97a4b5; font-size: 36rpx; margin-left: 14rpx; }
.product-tags { flex-direction: row; flex-wrap: wrap; margin-left: 90rpx; margin-top: 15rpx; }
.product-tag { background-color: #f0f5fc; color: #547aa8; font-size: 20rpx; line-height: 30rpx; padding: 3rpx 9rpx; border-radius: 4rpx; margin-right: 8rpx; margin-bottom: 5rpx; }
.availability { font-size: 21rpx; line-height: 34rpx; color: #9c783a; margin-top: 10rpx; }
.card-actions { flex-direction: row; justify-content: space-between; align-items: center; margin-left: 90rpx; margin-top: 10rpx; }
.detail-label { font-size: 20rpx; color: #8b9aae; }.compare-control { flex-direction: row; align-items: center; padding: 4rpx 0 4rpx 12rpx; }
.selection { width: 28rpx; height: 28rpx; border: 1rpx solid #c3d4e9; border-radius: 6rpx; color: #527ba9; font-size: 22rpx; text-align: center; line-height: 26rpx; }.selected-box { background-color: #0868f7; border-color: #0868f7; color: #fff; }.compare-label { color: #527ba9; font-size: 21rpx; margin-left: 8rpx; }
</style>
+10 -4
View File
@@ -22,7 +22,7 @@
import { usePageLayout } from '@/services/layout.uts'
import AppHeader from '@/components/AppHeader.uvue'
import { submitAssessment } from '@/packages/finance/services/api.uts'
import { activeAssessmentType, getAssessmentInput, draftText, documentsFromInput, ownershipFromInput, formFromInput, assetValidationMessage, contributionValidationMessage, clearAssessmentFlow } from '@/services/assessment.uts'
import { activeAssessmentType, getAssessmentInput, draftText, documentsFromInput, ownershipFromInput, formFromInput, assetValidationMessage, assetNotApplicableFields, contributionValidationMessage, clearAssessmentFlow } from '@/services/assessment.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
type InfoRow = { label: string, value: string }
type InfoSection = { title: string, step: number, rows: InfoRow[], expanded: boolean }
@@ -45,7 +45,12 @@
const mask = (value: string, left: number, right: number): string => { if (value.length <= left + right) return value; return value.substring(0, left) + '****' + value.substring(value.length - right) }
const displayValue = (saved: UTSJSONObject, key: string): string => { const value = draftText(saved, key); return value.length > 0 ? value : '未填写' }
const appendRow = (rows: InfoRow[], label: string, value: string) => { const row: InfoRow = { label, value }; rows.push(row) }
const valueWithUnit = (saved: UTSJSONObject, key: string, unit: string): string => { const value = draftText(saved, key); return value.length > 0 ? value + ' ' + unit : '未填写' }
const valueWithUnit = (saved: UTSJSONObject, key: string, unit: string): string => {
const value = draftText(saved, key)
if (value.length > 0) return value + ' ' + unit
if (assetNotApplicableFields(formFromInput(saved)).includes(key)) return ['totalDebtBalance', 'monthlyLoanPayment'].includes(key) ? '不适用(无贷款)' : '不适用(无信用卡)'
return '未填写'
}
const loadInput = () => {
assessmentType.value = activeAssessmentType()
const saved = getAssessmentInput(assessmentType.value)
@@ -59,7 +64,7 @@
}
appendRow(basicRows, enterprise ? '联系人' : '姓名', displayValue(saved, 'name'))
appendRow(basicRows, '手机号', mask(displayValue(saved, 'phone'), 3, 4))
appendRow(basicRows, '所在城市', displayValue(saved, 'city'))
appendRow(basicRows, '所在省份', displayValue(saved, 'city'))
if (!enterprise) {
appendRow(basicRows, '身份证号', mask(displayValue(saved, 'idCard'), 4, 4))
appendRow(basicRows, '婚姻状况', displayValue(saved, 'maritalStatus'))
@@ -78,7 +83,8 @@
{ title: '基本信息', step: 0, rows: basicRows, expanded: false },
{ title: enterprise ? '经营信息' : '职业与收入', step: 1, rows: incomeRows, expanded: false },
{ title: '资产与负债', step: 2, rows: [
{ label: '信用贷款笔数', value: valueWithUnit(saved, 'creditLoanCount', '笔') },
{ label: draftText(saved, 'loanCountScope') == 'all_loans' ? '贷款笔数' : '信用贷款笔数(旧口径)', value: valueWithUnit(saved, 'creditLoanCount', '笔') },
{ label: '贷款统计口径', value: draftText(saved, 'loanCountScope') == 'all_loans' ? '信用贷款、房贷、车贷等全部当前未结清贷款' : '旧资料仅统计信用贷款,请返回资产页重新确认' },
{ label: '欠款总余额', value: valueWithUnit(saved, 'totalDebtBalance', '万元') },
{ label: '贷款月还款金额', value: valueWithUnit(saved, 'monthlyLoanPayment', '万元') },
{ label: '银行信用卡张数', value: valueWithUnit(saved, 'creditCardCount', '张') },
+12 -16
View File
@@ -12,16 +12,11 @@
<text v-if="loading && matches.length == 0" class="state-copy">正在读取本次匹配结果…</text>
<view v-if="error.length > 0" class="state-panel"><text class="state-title">暂时无法读取</text><text class="muted">{{ error }}</text><button class="secondary" @click="retry">重新加载</button><text class="link" @click="history">返回融资档案</text></view>
<view v-if="pageError.length > 0" class="state-panel"><text class="muted">{{ pageError }}</text><button class="secondary" :disabled="loading" @click="loadMore">重试加载更多</button></view>
<view v-if="!loading && error.length == 0 && matches.length == 0" class="state-panel"><text class="state-title">{{ status == 'unavailable' ? '本次未执行匹配' : (group == 'eligible' ? '暂无符合已知条件的产品' : (group == 'ineligible' ? '暂无条件不符的产品' : '暂无资料不足或待确认的产品')) }}</text><text class="muted">{{ status == 'unavailable' ? '历史结果不会自动补算。可从融资档案发起新的评估。' : '可切换分组查看其他结果,或返回报告了解资料准备情况。' }}</text><text class="link" @click="report">查看本次报告</text></view>
<view v-for="item in matches" :key="item.id" class="card" @click="openProduct(item.id)">
<view class="card-head"><view :class="bankMarkClass(item.product.bank)"><image v-if="item.product.logo.length > 0" class="mark-logo" :src="item.product.logo" mode="aspectFit" @error="item.product.logo = ''" /><text v-else class="mark-text">{{ item.product.bank.length > 0 ? item.product.bank.slice(0, 1) : item.product.name.slice(0, 1) }}</text></view><view class="identity"><text v-if="item.product.bank.length > 0" class="bank-name">{{ item.product.bank }}</text><text class="product-name">{{ item.product.name }}</text></view><view class="card-status"><text :class="item.state == 'eligible' ? 'match-state good' : (item.state == 'ineligible' ? 'match-state blocked' : 'match-state pending')">{{ item.stateLabel }}</text><text v-if="matchPercentage(item.state, item.score).length > 0" class="match-score">匹配度 {{ matchPercentage(item.state, item.score) }}</text></view><text class="card-arrow">›</text></view>
<view class="tags"><text v-if="hasProductValue(item.product.rate)" class="tag">年化 {{ item.product.rate }}</text><text v-if="hasProductValue(item.product.term)" class="tag">{{ item.product.term }}</text><text class="tag">{{ item.product.objects }}</text></view>
<text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }};历史结果仍可查看。</text>
<view class="card-foot"><text class="detail-link">查看匹配依据</text><view class="choose-control" @click.stop="toggleCompare(item.id)"><text :class="selectedIds.includes(item.id) ? 'selection-box selected-box' : 'selection-box'">{{ selectedIds.includes(item.id) ? '✓' : '+' }}</text><text class="choose-label">{{ selectedIds.includes(item.id) ? '已选对比' : '加入对比' }}</text></view></view>
</view>
<view v-if="!loading && error.length == 0 && matches.length == 0" class="state-panel"><text class="state-title">{{ status == 'unavailable' ? '本次未执行匹配' : (group == 'all' ? '本次暂无可展示的产品' : (group == 'eligible' ? '暂无符合已知条件的产品' : (group == 'ineligible' ? '暂无条件不符的产品' : '暂无资料不足或待确认的产品'))) }}</text><text class="muted">{{ status == 'unavailable' ? '历史结果不会自动补算。可从融资档案发起新的评估。' : '可切换分组查看其他结果,或返回报告了解资料准备情况。' }}</text><text class="link" @click="report">查看本次报告</text></view>
<MatchProductCard v-for="item in matches" :key="item.id" :item="item" :selectable="true" :selected="selectedIds.includes(item.id)" @open="openProduct" @select="toggleCompare" />
<button v-if="hasMore && pageError.length == 0" class="more-button" :disabled="loading" @click="loadMore">{{ loading ? '正在加载…' : '查看更多产品(共' + count(group) + '项)' }}</button>
<view v-if="error.length == 0 && matches.length > 0" class="compare-bar"><text class="compare-caption">已选 {{ selectedIds.length }} / 3 项</text><button class="compare-button" :disabled="selectedIds.length < 2" @click="openCompare">对比所选产品</button></view>
<text v-if="matches.length > 0 && !hasMore" class="end-note">已显示本组全部结果</text>
<text v-if="matches.length > 0 && !hasMore" class="end-note">{{ group == 'all' ? '已显示全部匹配产品' : '已显示本组全部结果' }}</text>
<text v-if="message.length > 0" class="muted notice">{{ message }}</text>
<text class="link" @click="report">返回本次报告</text>
</template>
@@ -31,29 +26,30 @@
</template>
<script setup lang="uts">
import AppHeader from '@/components/AppHeader.uvue'
import MatchProductCard from '@/packages/finance/components/MatchProductCard.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { MatchView, MatchPage, fetchMatchPage, validRecordId, hasProductValue, matchPercentage } from '@/packages/finance/services/matches.uts'
import { MatchView, MatchPage, fetchMatchPage, validRecordId } from '@/packages/finance/services/matches.uts'
import { parseCompareIds, compareSelection, saveCompareSelection, compareQuery, matchReturnPath } from '@/packages/finance/services/compare.uts'
const { pageStyle, contentStyle } = usePageLayout()
type MatchGroup = { id: string, label: string }
const groups: MatchGroup[] = [{ id: 'eligible', label: '符合条件' }, { id: 'ineligible', label: '条件不符' }, { id: 'unknown', label: '待确认' }]
const revisionId = ref(''); const assessmentId = ref(''); const group = ref('eligible'); const matches = ref<MatchView[]>([])
const groups: MatchGroup[] = [{ id: 'all', label: '全部' }, { id: 'eligible', label: '符合条件' }, { id: 'unknown', label: '待核实' }, { id: 'ineligible', label: '条件不符' }]
const revisionId = ref(''); const assessmentId = ref(''); const group = ref('all'); const matches = ref<MatchView[]>([])
const selectedIds = ref<string[]>([])
const pageActive = ref(false)
const loading = ref(false); const error = ref(''); const pageError = ref(''); const message = ref(''); const status = ref('')
const eligible = ref('—'); const ineligible = ref('—'); const unknown = ref('—'); const hasMore = ref(false)
let cursor = ''; let generation = 0; let visible = false; let routeError = ''; let owner = ''
let firstPage: MatchPage | null = null; let accountTimer = 0
const count = (id: string): string => id == 'eligible' ? eligible.value : (id == 'ineligible' ? ineligible.value : unknown.value)
const count = (id: string): string => id == 'all' ? (eligible.value == '—' ? '—' : (Number(eligible.value) + Number(unknown.value) + Number(ineligible.value)).toString()) : (id == 'eligible' ? eligible.value : (id == 'ineligible' ? ineligible.value : unknown.value))
const summaryTitle = computed((): string => {
if (assessmentId.value.length == 0) return '选择评估,查看融资匹配'
if (error.value.length > 0) return '匹配结果暂不可读取'
if (status.value == 'unavailable') return '这份报告尚未执行产品匹配'
if (eligible.value == '—') return '正在为你读取匹配结果'
return eligible.value == '0' ? '暂未筛选出符合已知条件的产品' : '为你筛选出' + eligible.value + '项符合已知条件的产品'
if (group.value == 'all') return matches.value.length > 0 ? '为你展示' + matches.value.length.toString() + '款融资产品' : '本次产品匹配结果'
return '本次' + count(group.value) + '款' + (group.value == 'eligible' ? '符合条件' : (group.value == 'unknown' ? '待核实' : '条件不符')) + '产品'
})
const bankMarkClass = (bank: string): string => bank.includes('工商') || bank.includes('招商') || bank.includes('中国银行') ? 'mark mark-red' : (bank.includes('农业') || bank.includes('邮储') ? 'mark mark-green' : 'mark mark-blue')
const clear = () => { matches.value = []; message.value = ''; status.value = ''; pageError.value = ''; hasMore.value = false; cursor = ''; firstPage = null; eligible.value = '—'; ineligible.value = '—'; unknown.value = '—' }
const stopAccountTimer = () => { if (accountTimer != 0) clearInterval(accountTimer); accountTimer = 0 }
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeMatch=1&matchPage=matches' + compareQuery(assessmentId.value, selectedIds.value, revisionId.value).replace('?', '&') + '&group=' + group.value }) }
@@ -116,8 +112,8 @@
revisionId.value = options?.['revisionId'] ?? '';
if (revisionId.value.length > 0 && !validRecordId(revisionId.value)) routeError = '版本编号无效,请返回重新选择。'
assessmentId.value = options?.['assessmentId'] ?? ''
const selected = options?.['group'] ?? 'eligible'
if (['eligible','ineligible','unknown'].includes(selected)) group.value = selected
const selected = options?.['group'] ?? 'all'
if (['all','eligible','ineligible','unknown'].includes(selected)) group.value = selected
else routeError = '匹配分组参数无效,请从报告重新进入。'
if (assessmentId.value.length > 0 && !validRecordId(assessmentId.value)) routeError = '评估编号无效,请从融资档案重新选择。'
if (options?.['productIds'] != null) {
+9 -9
View File
@@ -9,7 +9,7 @@
<view class="bank-header">
<view class="bank-mark"><image class="bank-icon" :src="product!.logo.length > 0 ? product!.logo : '/static/mobile/icons-generated/building.png'" @error="product!.logo = ''" mode="aspectFit" /></view>
<view class="bank-info">
<view class="bank-title-row"><text v-if="product!.bank.length > 0" class="bank-name">{{ product!.bank }}</text><text v-if="match != null" :class="match!.state == 'eligible' ? 'badge good' : (match!.state == 'ineligible' ? 'badge blocked' : 'badge pending')">{{ match!.state == 'eligible' ? (match!.score != null ? '匹配度 ' + match!.score!.toString() + '%' : '可供参考') : (match!.state == 'ineligible' ? '暂不推荐' : match!.stateLabel) }}</text></view>
<view class="bank-title-row"><text v-if="product!.bank.length > 0" class="bank-name">{{ product!.bank }}</text><text v-if="match != null" :class="match!.state == 'eligible' ? 'badge good' : (match!.state == 'ineligible' ? 'badge blocked' : 'badge pending')">{{ matchPercentage(match!.state, match!.score).length > 0 ? '匹配度 ' + matchPercentage(match!.state, match!.score) : (match!.state == 'eligible' ? '可供参考' : (match!.state == 'ineligible' ? '暂不推荐' : match!.stateLabel)) }}</text></view>
<text v-if="match != null" :class="match!.state == 'eligible' ? 'state good-text' : 'state attention-text'">{{ match!.stateLabel }}</text>
<text class="product-name">{{ product!.name }}</text>
</view>
@@ -23,19 +23,19 @@
<template v-if="tab == 'explanation' && match != null">
<view class="reason-section">
<text class="heading good-text">{{ match!.state == 'eligible' ? '推荐理由(' + match!.positive.length + '条)' : '参考理由(' + match!.positive.length + '条)' }}</text>
<text v-if="match!.positive.length == 0" class="empty-note">暂无已确认满足的条件,不补充未经验证的推荐理由。</text>
<text v-if="match!.positive.length == 0" class="empty-note">暂无可展示的推荐理由。</text>
<view v-for="(reason, index) in positiveVisible" :key="reason.id" class="reason-row">
<text class="number green-number">{{ index + 1 }}</text>
<view class="reason-content"><text class="reason-title">{{ reasonTitle(reason) }}</text><text class="reason-detail">{{ reason.explanation.length > 0 ? reason.explanation : reason.evidence.join(';') }}</text><text class="evidence-toggle" @click="openReason = openReason == reason.id ? '' : reason.id">{{ openReason == reason.id ? '收起依据' : '查看依据' }}</text><view v-if="openReason == reason.id" class="evidence-box"><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view></view>
<view class="reason-content"><text class="reason-title">{{ reasonTitle(reason) }}</text><text v-if="reason.explanation.length > 0" class="reason-detail">{{ reason.explanation }}</text><text class="evidence-toggle" @click="openReason = openReason == reason.id ? '' : reason.id">{{ openReason == reason.id ? '收起依据' : '查看依据' }}</text><view v-if="openReason == reason.id" class="evidence-box"><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view></view>
</view>
<text v-if="match!.positive.length > 3" class="expand" @click="expanded = !expanded">{{ expanded ? '收起' : '展开其余' + (match!.positive.length - 3) + '条依据' }}</text>
</view>
<view class="reason-section attention-section">
<text class="heading attention-text">不推荐/需注意的点({{ attention.length }}条)</text>
<text v-if="attention.length == 0" class="empty-note">已知规则中暂无不符或待核实项,仍需核验资料,不代表审批结果。</text>
<text v-if="attention.length == 0" class="empty-note">当前没有需要特别注意的已知条件。结果仅供参考,不代表审批结果。</text>
<view v-for="(reason, index) in attentionVisible" :key="reason.id" class="reason-row">
<text class="number orange-number">{{ index + 1 }}</text>
<view class="reason-content"><text class="reason-title">{{ reasonTitle(reason) }}</text><text class="reason-detail">{{ reason.explanation.length > 0 ? reason.explanation : reason.evidence.join(';') }}</text><text class="evidence-toggle" @click="openReason = openReason == reason.id ? '' : reason.id">{{ openReason == reason.id ? '收起依据' : '查看依据' }}</text><view v-if="openReason == reason.id" class="evidence-box"><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view></view>
<view class="reason-content"><text class="reason-title">{{ reasonTitle(reason) }}</text><text v-if="reason.explanation.length > 0" class="reason-detail">{{ reason.explanation }}</text><text class="evidence-toggle" @click="openReason = openReason == reason.id ? '' : reason.id">{{ openReason == reason.id ? '收起依据' : '查看依据' }}</text><view v-if="openReason == reason.id" class="evidence-box"><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view></view>
</view>
<text v-if="attention.length > 3" class="expand" @click="attentionExpanded = !attentionExpanded">{{ attentionExpanded ? '收起' : '展开其余' + (attention.length - 3) + '条注意事项' }}</text>
</view>
@@ -69,7 +69,7 @@
<view class="details-section"><text class="heading">产品准入条件</text><text class="muted">公开条件不代表个人已符合,缺少核验依据时仍需核实。</text><text v-for="(condition, index) in product!.conditions" :key="index" class="condition">{{ condition }}</text></view>
<view class="details-section"><text class="heading">资料准备说明</text><view v-for="(material, index) in product!.materialList" :key="index" class="material-item"><text class="condition">{{ material.name }} · {{ material.required ? '必需' : '补充' }} · {{ material.objects }}</text><text v-if="material.note.length > 0" class="evidence">{{ material.note }}</text></view><text v-if="product!.materialList.length == 0 || product!.materials != '未提供'" class="body">{{ product!.materials }}</text><text class="muted">仅提供材料引导,不向机构提交申请。实际条件和材料须向机构核实。</text></view>
</template>
<view class="footnotes"><text class="record-note">{{ assessmentId.length > 0 ? '本次评估 #' + assessmentId : '公共产品资料' }} · 产品版本 v{{ product!.version }}</text><text v-if="match != null" class="record-note">{{ match!.availabilityLabel }},历史参数与结论保留。</text><text v-if="match != null && !canPrepare" class="record-note">{{ match!.state != 'eligible' ? '请先核对不符条件或待核实项。' : '当前产品不可用或状态未知,暂不提供准备入口。' }}</text><text v-if="match != null && match!.score != null" class="record-note">匹配度表示条件匹配程度,不代表获批概率。</text><text class="record-note">自填信息未经核验;本页面不承诺额度、放款或获批。</text><text v-if="assessmentId.length > 0" class="link" @click="openCompare">查看同类产品对比</text><text v-if="assessmentId.length > 0" class="link" @click="toMatches">返回本次匹配</text><text v-else class="link" @click="history">选择评估查看匹配结果</text></view>
<view class="footnotes"><text class="record-note">{{ assessmentId.length > 0 ? '本次评估 #' + assessmentId : '公共产品资料' }} · 产品版本 v{{ product!.version }}</text><text v-if="match != null" class="record-note">{{ match!.availabilityLabel }},历史参数与结论保留。</text><text v-if="match != null && !canPrepare" class="record-note">{{ match!.state != 'eligible' ? '请先核对不符条件或待核实项。' : '当前产品不可用或状态未知,暂不提供准备入口。' }}</text><text v-if="match != null && match!.score != null" class="record-note">匹配度表示条件匹配程度,不代表获批概率。</text><text class="record-note">结果仅供参考,不代表审批结果;本页面不承诺额度、放款或获批。</text><text v-if="assessmentId.length > 0" class="link" @click="openCompare">查看同类产品对比</text><text v-if="assessmentId.length > 0" class="link" @click="toMatches">返回本次匹配</text><text v-else class="link" @click="history">选择评估查看匹配结果</text></view>
</template>
</view>
</scroll-view>
@@ -84,7 +84,7 @@
import AppHeader from '@/components/AppHeader.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { MatchView, MatchReason, MatchDetail, ProductView, fetchMatchDetailRecord, fetchPublicProduct, validRecordId, requestProductExplanation, hasProductValue } from '@/packages/finance/services/matches.uts'
import { MatchView, MatchReason, MatchDetail, ProductView, fetchMatchDetailRecord, fetchPublicProduct, validRecordId, requestProductExplanation, hasProductValue, matchPercentage } from '@/packages/finance/services/matches.uts'
import { parseCompareIds, compareSelection, saveCompareSelection, compareQuery, sameMatchSnapshot, matchReturnPath } from '@/packages/finance/services/compare.uts'
import { assistantRoute } from '@/packages/finance/services/assistant.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
@@ -102,8 +102,8 @@
const attentionVisible = computed((): MatchReason[] => attentionExpanded.value ? attention.value : attention.value.slice(0, 3))
const aiPending = computed((): boolean => match.value != null && ['queued','running'].includes(match.value!.aiStatus))
const canPrepare = computed((): boolean => match.value != null && match.value!.state == 'eligible' && match.value!.availability == 'available')
// 缩短重复的规则模板标题;核验限制仍保留在逐条依据和页尾,不改判定含义。
const reasonTitle = (reason: MatchReason): string => reason.text.replace('(资料尚需核验)。', '').replace('本产品已配置条件', '产品条件')
// 缩短规则模板标题,不改判定含义。
const reasonTitle = (reason: MatchReason): string => reason.text.replace('本产品已配置条件', '产品条件')
function stopPoll(): void { if (timer != 0) clearTimeout(timer); timer = 0 }
const stopAccountTimer = () => { if (accountTimer != 0) clearInterval(accountTimer); accountTimer = 0 }
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeMatch=1&matchPage=product&assessmentId=' + assessmentId.value + '&revisionId=' + revisionId.value + '&productIds=' + selectedIds.value.join(',') + '&productId=' + productId.value + '&tab=' + tab.value + '&from=' + from }) }
+25 -17
View File
@@ -7,7 +7,7 @@
<view v-if="report != null" class="header-export" :style="navigationActionStyle(96, 1)"><ReportExport :assessmentId="assessmentId" :revisionId="revisionId" :toolbar="true" @authrequired="login" @sessionchanged="loadReport" @statuschange="exportStatus" /></view>
</view>
<view class="tabs"><view v-for="item in tabs" :key="item.id" :class="tab == item.id ? 'tab active' : 'tab'" @click="switchTab(item.id)"><text :class="tab == item.id ? 'tab-label selected' : 'tab-label'">{{ item.label }}</text></view></view>
<scroll-view scroll-y :style="contentStyle(report == null ? 178 : 314, false)">
<scroll-view scroll-y :style="contentStyle(report == null || tab == 'matches' ? 178 : 314, false)">
<view class="report-content" :style="bottomStyle(24)">
<!-- 离页清空报告时只切换可见性,避免在页面销毁过程中重新挂载文本节点。 -->
<text v-show="report == null" class="eyebrow">评估记录 #{{ assessmentId.length > 0 ? assessmentId : '待选择' }}</text>
@@ -32,25 +32,22 @@
<view class="panel"><view class="panel-heading"><text class="section-title">资料完整度</text><text class="percent">{{ report!.completenessPercent == null ? '—' : report!.completenessPercent!.toString() + '%' }}</text></view><view v-if="report!.completenessPercent != null" class="progress-track"><view class="progress-fill" :style="'width:' + report!.completenessPercent!.toString() + '%;'"></view></view><text class="report-note">{{ report!.completenessText }}</text><text class="report-note">完整度不代表信用评分、资料真实性或获批概率。</text></view>
<view v-if="report!.demand.length > 0" class="panel"><text class="section-title">本次资金需求</text><view class="fact-row" v-for="fact in report!.demand" :key="fact.label"><text class="fact-label">{{ fact.label }}</text><text class="fact-value">{{ fact.value }}</text></view></view>
<view class="panel"><text class="section-title">综合评分依据</text><template v-if="report!.financingGrade.status == 'evaluated'"><text class="body-text">融资能力 {{ report!.financingGrade.capacityScore }} / 100 · 资料完整度 {{ report!.financingGrade.completenessPercent }}%</text><text class="report-note">加权原始分 {{ report!.financingGrade.weightedScore }} · 最终分 {{ report!.financingGrade.score }}</text><view class="grade-dimension" v-for="item in report!.financingGrade.dimensions" :key="item.key"><view class="panel-heading"><text class="body-text">{{ item.label }}</text><text class="dimension-score">{{ item.score }} / {{ item.maximum }}</text></view><text class="report-note">{{ item.basis }}</text></view></template><text v-for="item in report!.financingGrade.limitations" :key="item" class="body-text">{{ item }}</text><text class="report-note">{{ report!.financingGrade.notice }}</text></view>
<view v-if="report!.version == 2" class="panel"><text class="section-title">资料准备状态:{{ report!.readinessLabel }}</text><text v-if="report!.readinessBasis.length == 0" class="report-note">本次没有可展示的准备状态依据。</text><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text><text class="report-note">资料未经核验;完整度达到100%仍需核对机构要求。</text></view>
<view v-if="report!.version == 2" class="panel"><text class="section-title">资料准备状态:{{ report!.readinessLabel }}</text><text v-if="report!.readinessBasis.length == 0" class="report-note">本次没有可展示的准备状态依据。</text><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text><text class="report-note">资料完整度反映填写情况,达到100%仍需核对机构要求。</text></view>
<view class="panel"><text class="section-title">{{ report!.version == 2 ? '主要优势' : '资料解读' }}</text><text v-if="report!.observations.length == 0" class="report-note">暂无有充分依据的优势说明。</text><text class="body-text" v-for="(item, index) in report!.observations" :key="index">{{ item }}</text></view>
<view class="panel"><view class="panel-heading" @click="factsOpen = !factsOpen"><text class="section-title">本次提交的事实</text><text class="toggle">{{ factsOpen ? '收起' : '展开' }}</text></view><text class="report-note">来源:用户自填,未经外部核验;未填写不等于零。</text><template v-if="factsOpen"><view class="fact-row" v-for="fact in report!.facts" :key="fact.label"><text class="fact-label">{{ fact.label }}</text><text class="fact-value">{{ fact.value }}</text></view><text v-if="report!.facts.length == 0" class="report-note">暂无可展示的事实。</text></template></view>
<view class="panel"><view class="panel-heading" @click="factsOpen = !factsOpen"><text class="section-title">本次提交的事实</text><text class="toggle">{{ factsOpen ? '收起' : '展开' }}</text></view><text class="report-note">以下为本次提交的资料;未填写不等于零。</text><template v-if="factsOpen"><view class="fact-row" v-for="fact in report!.facts" :key="fact.label"><text class="fact-label">{{ fact.label }}</text><text class="fact-value">{{ fact.value }}</text></view><text v-if="report!.facts.length == 0" class="report-note">暂无可展示的事实。</text></template></view>
<view v-if="report!.version == 2" class="panel"><view class="panel-heading" @click="completenessOpen = !completenessOpen"><text class="section-title">逐项填写情况</text><text class="toggle">{{ completenessOpen ? '收起' : '展开' }}</text></view><template v-if="completenessOpen"><view class="fact-row" v-for="item in report!.completenessItems" :key="item.label"><text class="fact-label">{{ item.label }}</text><text class="fact-value">{{ item.value }}</text></view><text v-if="report!.completenessItems.length == 0" class="report-note">本次未评定逐项完整度。</text></template></view>
<view class="panel"><text class="section-title">{{ report!.version == 2 ? '风险与核验边界' : '待补充信息' }}</text><text class="body-text" v-for="(item, index) in report!.warnings" :key="'system-' + index">{{ item }}</text><text class="body-text" v-for="(item, index) in report!.dataGaps" :key="index">{{ item }}</text><text v-if="report!.warnings.length == 0 && report!.dataGaps.length == 0" class="report-note">本次暂无额外提示,不代表资料已核验或无风险。</text></view>
<view class="panel"><text class="section-title">{{ report!.version == 2 ? '风险与核验边界' : '待补充信息' }}</text><text class="body-text" v-for="(item, index) in report!.warnings" :key="'system-' + index">{{ item }}</text><text class="body-text" v-for="(item, index) in report!.dataGaps" :key="index">{{ item }}</text><text v-if="report!.warnings.length == 0 && report!.dataGaps.length == 0" class="report-note">本次暂无额外提示,仍需关注实际风险和机构要求。</text></view>
</template>
</template>
<template v-else-if="tab == 'matches'">
<text class="headline">本次需求的可选方向</text><text class="intro">以下为评估时的条件判断。展示顺序不代表优劣,也不代表机构审批。</text>
<view v-if="report!.matching.status == 'completed'" class="stats"><view class="stat" @click="allMatches('eligible')"><text class="stat-number">{{ report!.matching.eligible }}</text><text class="stat-label">符合已知条件</text></view><view class="stat" @click="allMatches('ineligible')"><text class="stat-number">{{ report!.matching.ineligible }}</text><text class="stat-label">条件不符</text></view><view class="stat" @click="allMatches('unknown')"><text class="stat-number">{{ report!.matching.unknown }}</text><text class="stat-label">待核实</text></view></view>
<text class="report-note">{{ report!.matchingMessage }}</text>
<view class="matching-summary"><text class="matching-title">{{ candidates.length > 0 ? '为你展示' + candidates.length.toString() + '款融资产品' : '本次产品匹配结果' }}</text><text class="matching-caption">根据本次资料匹配,进入产品查看具体条件与依据。</text></view>
<view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">本次未执行产品匹配</text><text class="body-text">这与“已匹配但没有候选”不同。历史报告不会自动补算;需要采用新资料与当前产品条件时,请重新评估。</text></view>
<template v-else>
<text v-if="candidateLoading" class="body-text">正在读取历史候选…</text>
<view v-if="candidateError.length > 0" class="panel"><text class="section-title">候选读取失败</text><text class="body-text">{{ candidateError }}</text><button class="secondary" @click="loadCandidates">重试候选</button></view>
<view v-if="!candidateLoading && candidateError.length == 0 && candidates.length == 0" class="panel"><text class="section-title">暂无符合已知条件的产品</text><text class="body-text">本次已执行匹配,可查看条件不符或待核实原因。没有候选不代表永久无法融资。</text></view>
<view v-for="item in candidates" :key="item.id" class="product-card" @click="openProduct(item.id)"><view class="panel-heading"><text class="product-name">{{ item.product.name }}</text><text class="version">v{{ item.product.version }}</text></view><text class="report-note">{{ item.product.bank }} · {{ item.stateLabel }}</text><text class="amount">{{ item.product.amount }}</text><view class="metrics"><text class="metric">年化 {{ item.product.rate }}</text><text class="metric">{{ item.product.term }}</text></view><text class="reason" v-for="reason in item.positive.slice(0, 2)" :key="reason.id">{{ reason.text }}</text><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }};仍保留历史参数。</text><text class="action">查看匹配依据与详情 ›</text></view>
<text v-if="candidates.length > 0" class="report-note">摘要最多展示3项符合已知条件的产品,全部分组见完整匹配列表。</text>
<button class="primary" @click="allMatches('eligible')">查看全部匹配结果</button><view class="links"><text class="action" @click="allMatches('ineligible')">查看不符原因</text><text class="action" @click="allMatches('unknown')">查看待核实项</text></view>
<text v-if="candidateLoading" class="body-text">正在读取匹配产品…</text>
<view v-if="candidateError.length > 0" class="panel"><text class="section-title">匹配产品读取失败</text><text class="body-text">{{ candidateError }}</text><button class="secondary" @click="loadCandidates">重新加载</button></view>
<view v-if="!candidateLoading && candidateError.length == 0 && candidates.length == 0" class="panel"><text class="section-title">本次暂无可展示的产品</text><text class="body-text">可重新评估,采用最新资料与产品条件进行匹配。</text></view>
<MatchProductCard v-for="item in candidates" :key="item.id" :item="item" @open="openProduct" />
<button v-if="candidates.length > 0" class="matching-more" @click="allMatches('all')">查看更多产品(共{{ matchingTotal }}款)</button>
</template>
</template>
<template v-else>
@@ -61,19 +58,23 @@
<view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">产品条件提示</text><text class="report-note">本次未执行产品匹配,没有可关联的产品改善依据。</text></view>
<template v-else><view v-for="group in adviceGroups" :key="group.id" class="panel"><text class="section-title">{{ group.label }}</text><text class="report-note">仅展示本组前3项产品的已有依据,可进入完整列表查看更多。</text><text v-if="group.loading" class="body-text">正在读取产品依据…</text><template v-if="group.error.length > 0"><text class="body-text">{{ group.error }}</text><button class="secondary" @click="loadAdvice(group.id)">重试{{ group.label }}</button></template><text v-if="!group.loading && group.error.length == 0 && group.items.length == 0" class="report-note">本组暂无产品依据。</text><view v-for="item in group.items" :key="item.id" class="advice-product"><text class="product-name" @click="openProduct(item.id)">{{ item.product.name }} ›</text><text class="source-note">{{ item.stateLabel }} · 评估时版本 v{{ item.product.version }}</text><view v-for="reason in adviceReasons(item)" :key="reason.id"><text class="body-text">{{ reason.text }}</text><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }}</text></view><text class="action" @click="allMatches(group.id)">查看全部{{ group.label }} ›</text></view></template>
</template>
<view v-if="tab == 'matches'" class="matching-footer"><text class="report-link" @click="editAssessment">修改资料,重新评估 ›</text><text class="matching-disclaimer">匹配度仅供参考,不代表获批概率。</text></view>
<template v-else>
<view class="report-links"><text class="report-link" @click="nextStep">下一步行动 ›</text><text class="report-link" @click="consult">咨询AI ›</text></view>
<view class="details-toggle" @click="metadataOpen = !metadataOpen"><text class="metadata-toggle">报告信息</text><text class="details-action">{{ metadataOpen ? '收起' : '展开' }}</text></view>
<view v-if="metadataOpen" class="metadata"><text class="report-note">评估 #{{ assessmentId }} · 第 {{ report!.revisionNo }} 版 · {{ report!.generatedAt }}</text><text class="report-note">分析来源:用户自填资料 · {{ report!.modelCode }}</text><text class="report-note">报告已保存至融资档案。导出不含身份信息,请妥善保管下载文件。</text><text class="disclaimer">{{ report!.disclaimer }}</text><view class="report-links"><text class="report-link" @click="start">发起新评估</text><text class="report-link" @click="history">历史评估</text></view></view>
<text v-show="!metadataOpen" class="disclaimer">自填资料未经核验,结果仅供参考,不代表授信或获批。</text>
<view v-if="metadataOpen" class="metadata"><text class="report-note">评估 #{{ assessmentId }} · 第 {{ report!.revisionNo }} 版 · {{ report!.generatedAt }}</text><text class="report-note">报告已保存至融资档案。导出不含身份信息,请妥善保管下载文件。</text><text class="disclaimer">{{ report!.disclaimer }}</text><view class="report-links"><text class="report-link" @click="start">发起新评估</text><text class="report-link" @click="history">历史评估</text></view></view>
<text v-show="!metadataOpen" class="disclaimer">本报告依据本次资料生成,结果仅供参考,不代表授信或获批。</text>
</template>
</template>
</view>
</scroll-view>
<view v-if="report != null" class="report-footer" :style="bottomStyle(24)"><button class="reevaluate-button" @click="editAssessment">重新评估</button></view>
<view v-if="report != null && tab != 'matches'" class="report-footer" :style="bottomStyle(24)"><button class="reevaluate-button" @click="editAssessment">重新评估</button></view>
</view>
</template>
<script setup lang="uts">
import AppIcon from '@/components/AppIcon.uvue'
import ReportExport from '@/packages/finance/components/ReportExport.uvue'
import MatchProductCard from '@/packages/finance/components/MatchProductCard.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { assistantRoute } from '@/packages/finance/services/assistant.uts'
@@ -100,6 +101,7 @@
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeReport=1&assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&tab=' + tab.value }) }
const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充','需核实'].includes(item.value)))
const priorityItems = computed((): string[] => report.value == null ? [] : report.value!.priorityItems)
const matchingTotal = computed((): string => report.value == null || report.value!.matching.status != 'completed' ? '0' : (Number(report.value!.matching.eligible) + Number(report.value!.matching.unknown) + Number(report.value!.matching.ineligible)).toString())
const adviceReasons = (item: MatchView): MatchReason[] => item.state == 'ineligible' ? item.blocking.concat(item.cautions) : item.cautions
const reset = () => { report.value = null; candidates.value = []; adviceGroups.value = []; candidateLoading.value = false; candidateError.value = ''; factsOpen.value = false; completenessOpen.value = false; metadataOpen.value = false; detailsOpen.value = false; downloadStatus.value = '' }
const current = (request: number, token: string): boolean => visible && request == generation && getSession()?.getString('token') == token
@@ -120,7 +122,7 @@
const request = generation; const token = getSession()?.getString('token') ?? ''
candidateLoading.value = true; candidateError.value = ''; candidates.value = []
try {
const result = await fetchMatchPage(assessmentId.value, 'eligible', '', revisionId.value)
const result = await fetchMatchPage(assessmentId.value, 'all', '', revisionId.value)
if (!current(request, token)) { sessionChanged(request, token); return }
verifyMatches(result); candidates.value = result.items
} catch (err) { if (!sessionChanged(request, token)) candidateError.value = err instanceof Error ? err.message : '候选读取失败' }
@@ -205,6 +207,12 @@
.revision-notice { font-size: 24rpx; line-height: 38rpx; color: #886934; padding: 18rpx 22rpx; background-color: #fff8eb; border-radius: 12rpx; margin-bottom: 12rpx; }
.download-status { font-size: 23rpx; line-height: 36rpx; color: #547292; padding-bottom: 18rpx; }
.report-content { padding: 28rpx 30rpx 24rpx; }
.matching-summary { background-color: #edf5ff; padding: 25rpx 26rpx; border-radius: 10rpx; margin-bottom: 8rpx; }
.matching-title { color: #244f85; font-size: 30rpx; line-height: 44rpx; font-weight: 600; }
.matching-caption { color: #7188a4; font-size: 22rpx; line-height: 35rpx; margin-top: 8rpx; }
.matching-more { background-color: #0868f7; color: #fff; height: 76rpx; line-height: 76rpx; font-size: 25rpx; border-radius: 10rpx; margin: 26rpx 0 0; }
.matching-footer { align-items: center; padding-top: 22rpx; }
.matching-disclaimer { font-size: 21rpx; line-height: 34rpx; color: #8c98a8; margin-top: 20rpx; text-align: center; }
.eyebrow { color: #778ba3; font-size: 22rpx; }
.headline { color: #1a3859; font-size: 34rpx; font-weight: 700; margin-top: 22rpx; }
.intro { color: #73869d; font-size: 24rpx; line-height: 40rpx; margin-top: 14rpx; }
+36 -14
View File
@@ -2,6 +2,7 @@ import { revisionParams } from './records.uts'
import { requestJson } from '@/services/api.uts'
import { API_BASE_URL } from '@/services/config.uts'
import { yuanToWan } from '@/services/money.uts'
import { provinceOptions } from '@/services/cities.uts'
export type ProductMaterial = { name: string, note: string, required: boolean, objects: string }
export type ProductView = { logo: string, materialList: ProductMaterial[], consultPhone: string, repayment: string, channel: string, approval: string, id: string, version: number, name: string, bank: string, type: string, demo: boolean, amount: string, rate: string, term: string, objects: string, regions: string, valid: string, materials: string, source: string, sourceRef: string, checkedOn: string, conditions: string[] }
@@ -12,16 +13,23 @@ export type MatchPage = { assessmentId: string, revisionId: string, resultVersio
function text(data: UTSJSONObject, key: string): string { return data.getString(key) ?? '' }
export function validRecordId(value: string): boolean { return /^[1-9][0-9]{0,9}$/.test(value) }
export function eligibilityLabel(value: string): string { return value == 'eligible' ? '符合已知条件' : (value == 'ineligible' ? '条件不符' : '待确认') }
export function eligibilityLabel(value: string): string { return value == 'eligible' ? '符合已知条件' : (value == 'ineligible' ? '条件不符' : '待核实') }
export function hasProductValue(value: string): boolean { return value.trim().length > 0 && !['未提供', '待核实', '—'].includes(value.trim()) }
export function matchPercentage(state: string, score: number | null): string { return state == 'eligible' && score != null ? score.toString() + '%' : '' }
const fieldNames: UTSJSONObject = { requestedAmountYuan: '融资金额', requestedTermMonths: '融资期限', applicantType: '申请主体', purposeCode: '资金用途', cityCode: '所在城市', monthlyIncome: '月收入', annualRevenue: '年营收', operatingMonths: '经营月数', totalDebtBalance: '贷款余额', monthlyLoanPayment: '每月还款额', creditLoanCount: '贷款笔数', creditCardCount: '信用卡数量', creditCardTotalLimit: '信用卡总额度', creditCardUsedLimit: '信用卡已用额度', socialSecurity: '社保情况', housingFund: '公积金情况', house: '房产情况', car: '车辆情况', establishedMonths: '企业成立月数', annualInvoiceAmount: '年开票金额', taxPaymentMonths: '连续纳税月数', taxpayerGrade: '纳税等级', enterpriseCreditStatus: '企业信用状态', annualRevenueGrowthPercent: '年营收增长率', debtRatioPercent: '负债率', cashFlowStability: '经营流水稳定性', industryCategory: '行业类别' }
export function matchPercentage(state: string, score: number | null): string { return ['eligible','unknown','ineligible'].includes(state) && score != null ? score.toString() + '%' : '' }
const fieldNames: UTSJSONObject = { age: '年龄', occupation: '职业类型', employer: '单位名称', requestedAmountYuan: '融资金额', requestedTermMonths: '融资期限', applicantType: '申请主体', purposeCode: '资金用途', cityCode: '所在城市', provinceCode: '所在省份', monthlyIncome: '月收入', annualRevenue: '年营收', operatingMonths: '经营月数', totalDebtBalance: '贷款余额', monthlyLoanPayment: '每月还款额', creditLoanCount: '贷款笔数', creditCardCount: '信用卡数量', creditCardTotalLimit: '信用卡总额度', creditCardUsedLimit: '信用卡已用额度', socialSecurity: '社保情况', housingFund: '公积金情况', house: '房产情况', car: '车辆情况', establishedMonths: '企业成立月数', annualInvoiceAmount: '年开票金额', taxPaymentMonths: '连续纳税月数', taxpayerGrade: '纳税等级', enterpriseCreditStatus: '企业信用状态', annualRevenueGrowthPercent: '年营收增长率', debtRatioPercent: '负债率', cashFlowStability: '经营流水稳定性', industryCategory: '行业类别' }
const labels: UTSJSONObject = { personal: '个人', enterprise: '企业', daily_consumption: '日常消费', education: '教育进修', home_improvement: '家居装修', personal_business: '个体经营周转', working_capital: '经营周转', inventory_purchase: '采购备货', equipment_purchase: '设备购置', business_expansion: '经营扩展', other: '其他用途', none: '无', under_six_months: '不足六个月', six_months_plus: '六个月及以上', yes: '有', no: '无', normal: '正常', abnormal: '异常', stable: '稳定', unstable: '不稳定', manufacturing: '制造业', trade: '贸易', services: '服务业', construction: '建筑业', agriculture: '农业', finance: '金融业', real_estate: '房地产业', '440300': '深圳市', '440100': '广州市', '110100': '北京市', '310100': '上海市', '330100': '杭州市' }
const moneyFields = ['requestedAmountYuan','monthlyIncome','annualRevenue','totalDebtBalance','monthlyLoanPayment','creditCardTotalLimit','creditCardUsedLimit','annualInvoiceAmount']
const advisoryReasonCodes = ['AI_CONDITION_NOT_ASSESSED', 'PROFILE_DETAIL_NOT_ASSESSED']
function displayReasonText(value: string): string {
return value.replace(/((?:根据自填资料判断|资料尚需核验|用户自填[,、]未经核验|自填信息未经核验|产品快照))/g, '').replace(/自填信息未经核验;本页面不承诺额度、放款或获批。/g, '结果仅供参考,不代表审批结果。').trim()
}
function valueText(value: any | null, field: string): string {
if (value == null) return '未提供'
const raw = value.toString()
if (field == 'provinceCode') return provinceOptions.find((item): boolean => item.code == raw)?.name ?? '待核实'
if (field == 'age') return raw + ' 岁'
if (moneyFields.includes(field)) return yuanToWan(raw) + ' 万元'
if (field == 'requestedTermMonths' || field == 'operatingMonths' || field == 'establishedMonths' || field == 'taxPaymentMonths') return raw + ' 个月'
if (field == 'debtRatioPercent' || field == 'annualRevenueGrowthPercent') return raw + '%'
@@ -81,24 +89,37 @@ function matchView(data: UTSJSONObject): MatchView {
const evidence = data['evidence'] as UTSJSONObject[]
const ai = data['aiExplanation'] as UTSJSONObject
const explanations = (ai['reasonExplanations'] as UTSJSONObject[] | null) ?? []
const reasons = (key: string): MatchReason[] => (data[key] as UTSJSONObject[]).map((r: UTSJSONObject): MatchReason => {
const hiddenAdvisory = (reason: UTSJSONObject): boolean => {
const code = text(reason, 'code')
if (!advisoryReasonCodes.includes(code)) return false
const refs = (reason['evidenceRefs'] as string[] | null) ?? []
return refs.length > 0 && refs.every((ref: string): boolean => {
const item = evidence.find((entry: UTSJSONObject): boolean => text(entry, 'evidenceId') == ref)
if (item == null || text(item, 'reasonCode') != code || text(item, 'outcome') != 'unknown') return false
const source = text(item, 'source')
return code == 'AI_CONDITION_NOT_ASSESSED' ? source == 'ai_self_reported' : ['self_reported', 'product_snapshot'].includes(source)
})
}
// 仅隐藏证据一致的未参与判断提示;异常记录与原始快照保持可追溯。
const reasons = (key: string): MatchReason[] => (data[key] as UTSJSONObject[]).filter((r: UTSJSONObject): boolean => key != 'cautions' || !hiddenAdvisory(r)).map((r: UTSJSONObject): MatchReason => {
const reasonFields: string[] = []
const reasonText = ['AI_CONDITION_PASS', 'AI_CONDITION_FAIL'].includes(text(r, 'code')) ? text(r, 'text').replace('(根据自填资料判断)', '') : text(r, 'text')
const lines = (r['evidenceRefs'] as string[]).map((ref: string): string => {
const reasonText = displayReasonText(text(r, 'text'))
const lines = ((r['evidenceRefs'] as string[] | null) ?? []).map((ref: string): string => {
const e = evidence.find((item: UTSJSONObject): boolean => text(item, 'evidenceId') == ref)
if (e == null) return '证据暂不可读取'
const fields = e['fieldRefs'] as string[]; const field = fields.length > 0 ? fields[0] : ''
fields.forEach((name: string) => { if (!reasonFields.includes(name)) reasonFields.push(name) })
if (text(e, 'source') == 'ai_self_reported') {
if (e['actual'] != null && fieldNames.getString(field) != null) return fieldNames.getString(field)! + ':' + valueText(e['actual'], field)
const names = fields.map((name: string): string => fieldNames.getString(name) ?? '').filter((name: string): boolean => name.length > 0)
if (e['actual'] != null && (field == 'age' || fieldNames.getString(field) != null)) return (field == 'age' ? '年龄' : fieldNames.getString(field)!) + ':' + valueText(e['actual'], field)
const names = fields.map((name: string): string => name == 'age' ? '年龄' : (fieldNames.getString(name) ?? '')).filter((name: string): boolean => name.length > 0)
return names.length > 0 ? '关联资料:' + names.join('、') : reasonText
}
const source = text(e, 'source') == 'verified' ? '外部核验' : (text(e, 'source') == 'product_snapshot' ? '产品快照' : (text(e, 'source') == 'self_reported' ? '用户自填,未经核验' : '来源未知'))
return (fieldNames.getString(field) ?? field) + ':' + valueText(e['actual'], field) + ' · ' + source
const source = text(e, 'source')
const sourceNote = ['self_reported', 'product_snapshot'].includes(source) ? '' : ' · ' + (source == 'verified' ? '外部核验' : '来源未知')
return (fieldNames.getString(field) ?? field) + ':' + valueText(e['actual'], field) + sourceNote
})
const explanation = explanations.find((item: UTSJSONObject): boolean => text(item, 'reasonId') == text(r, 'reasonId'))
return { id: text(r, 'reasonId'), text: reasonText, evidence: lines, fields: reasonFields, explanation: text(ai, 'status') == 'completed' && explanation != null ? text(explanation, 'text') : '' }
return { id: text(r, 'reasonId'), text: reasonText, evidence: lines, fields: reasonFields, explanation: text(ai, 'status') == 'completed' && explanation != null ? displayReasonText(text(explanation, 'text')) : '' }
})
const availability = text(data, 'currentAvailability')
const score = data.getNumber('score')
@@ -108,7 +129,7 @@ function matchView(data: UTSJSONObject): MatchView {
return { score, id: text(data, 'productId'), state: text(data, 'eligibility'), stateLabel: missingData ? '资料不足' : eligibilityLabel(text(data, 'eligibility')), availability,
availabilityLabel: availability == 'available' ? '当前产品可查看' : (availability == 'unavailable' ? '当前产品已下架' : '当前产品状态暂无法确认'),
product: productView(data['productSnapshot'] as UTSJSONObject, false), positive: reasons('positiveReasons'), blocking: reasons('blockingReasons'), cautions: reasons('cautions'),
aiText: text(ai, 'status') == 'completed' ? text(ai, 'text') : (text(ai, 'status') == 'failed' ? 'AI解释暂不可用,已保留全部规则依据。' : '当前展示规则依据,尚未生成单产品AI解释。'),
aiText: text(ai, 'status') == 'completed' ? displayReasonText(text(ai, 'text')) : (text(ai, 'status') == 'failed' ? 'AI解释暂不可用,已保留全部规则依据。' : '当前展示规则依据,尚未生成单产品AI解释。'),
aiStatus: text(ai, 'status'), aiCanRetry: ai.getBoolean('canRetry') ?? false, aiCanRequest: data.getBoolean('canRequestExplanation') ?? false, aiGeneratedAt: text(ai, 'generatedAt') }
}
export async function requestProductExplanation(id: string, productId: string, requestId: string, revisionId: string = ''): Promise<MatchView> {
@@ -116,6 +137,7 @@ export async function requestProductExplanation(id: string, productId: string, r
return detailRecord(data, id, productId, revisionId).item
}
export async function fetchMatchPage(id: string, group: string, cursor: string, revisionId: string = ''): Promise<MatchPage> {
if (!['all','eligible','unknown','ineligible'].includes(group)) throw new Error('匹配分组无效,请重新选择。')
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches', 'GET', { ...revisionParams(revisionId), eligibility: group, limit: '3', cursor })
const matching = data['matching'] as UTSJSONObject; const status = text(matching, 'status')
const resolvedRevision = text(data, 'revisionId'); const version = data.getNumber('resultVersion') ?? 0
@@ -123,7 +145,7 @@ export async function fetchMatchPage(id: string, group: string, cursor: string,
const items = (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): MatchView => matchView(v))
const hasMore = data.getBoolean('hasMore') ?? false; const nextCursor = text(data, 'nextCursor')
const ids: string[] = []
items.forEach((item: MatchView) => { if (item.state != group || ids.includes(item.id)) throw new Error('匹配分组或产品编号不一致,请重新加载。'); ids.push(item.id) })
items.forEach((item: MatchView) => { if ((group != 'all' && item.state != group) || ids.includes(item.id)) throw new Error('匹配分组或产品编号不一致,请重新加载。'); ids.push(item.id) })
if (!['completed','unavailable'].includes(status) || items.length > 3 || (hasMore && (items.length == 0 || nextCursor.length == 0)) || (!hasMore && nextCursor.length > 0) || (status == 'unavailable' && (items.length > 0 || hasMore))) throw new Error('匹配分页状态不完整,请重新加载。')
if (status == 'completed') {
if (text(matching, 'catalogSnapshotId').length == 0 || text(matching, 'ruleVersion').length == 0 || text(matching, 'orderingPolicyVersion').length == 0) throw new Error('匹配快照版本不完整,请重新加载。')
@@ -132,7 +154,7 @@ export async function fetchMatchPage(id: string, group: string, cursor: string,
}
const reason = text(matching, 'reasonCode')
const count = (key: string): string => { const v = matching.getNumber(key); return v == null ? '—' : v.toString() }
const message = status == 'completed' ? (text(matching, 'orderingPolicyVersion') == 'product-score-descending-v1' ? '按本次条件匹配度排序,匹配度不代表获批概率。' : '按本次评估时的产品条件判断,展示顺序不代表优劣。') : (reason == 'LEGACY_INPUT' ? '旧版报告未执行产品匹配,可重新评估获取新的结果。' : (reason == 'MATCH_RULES_UNPUBLISHED' ? '本次评估尚未启用产品匹配规则。' : '本次评估没有可用产品目录,未执行产品匹配。'))
const message = status == 'completed' ? (group == 'all' ? '优先展示符合条件的产品,其次为待核实、条件不符的产品。' : (text(matching, 'orderingPolicyVersion') == 'product-score-descending-v1' ? '按本次条件匹配度排序,匹配度不代表获批概率。' : '按本次评估时的产品条件判断,展示顺序不代表优劣。')) : (reason == 'LEGACY_INPUT' ? '旧版报告未执行产品匹配,可重新评估获取新的结果。' : (reason == 'MATCH_RULES_UNPUBLISHED' ? '本次评估尚未启用产品匹配规则。' : '本次评估没有可用产品目录,未执行产品匹配。'))
return { assessmentId: id, revisionId: resolvedRevision, resultVersion: version, snapshotId: text(matching, 'catalogSnapshotId'), ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), status, message, eligible: count('candidateCount'), ineligible: count('ineligibleCount'), unknown: count('unknownCount'), items, hasMore, cursor: nextCursor }
}
export async function fetchMatchDetail(id: string, productId: string, revisionId: string = ''): Promise<MatchView> {
+42 -11
View File
@@ -3,27 +3,51 @@ import { assessmentApiPayload, draftText } from '@/services/assessment.uts'
import { assessmentRequestKey } from '@/services/assessment-flow.uts'
import { contributionLabels, contributionCodes, contributionLabel } from '@/services/contribution.uts'
import { yuanToWan } from '@/services/money.uts'
import { provinceCodeForName } from '@/services/cities.uts'
import { FinancingGradeView, financingGradeFrom } from '@/packages/finance/services/financing-grade.uts'
export type AssessmentRecord = { id: string, type: string, status: string, statusLabel: string, message: string, city: string, submittedAt: string, taskId: string, stage: string, canRetry: boolean, revisionId: string, revisionNo: number, hasReport: boolean, publishedRevisionId: string }
export type AssessmentPage = { items: AssessmentRecord[], hasMore: boolean, nextCursor: string }
export type AssessmentField = { label: string, value: string }
export type ArchiveSection = { id: string, icon: string, title: string, note: string, fields: AssessmentField[], provided: number }
export type AssessmentDetail = { record: AssessmentRecord, fields: AssessmentField[], sections: ArchiveSection[] }
export type ArchiveCompleteness = { percent: number | null, description: string }
export type ArchiveField = { fieldKey: string, label: string, value: string, isMissing: boolean }
export type ArchiveSection = { id: string, icon: string, title: string, note: string, fields: ArchiveField[], provided: number }
export type AssessmentDetail = { record: AssessmentRecord, fields: ArchiveField[], sections: ArchiveSection[] }
export type ArchiveCompleteness = { assessmentId: string, revisionId: string, percent: number | null, description: string, notApplicableFields: string[] }
const archiveAmountKeys = ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']
// 档案只消费报告中已保存的完整度,不用当前表单规则重算旧评估,也不把缺失当作0%。
export async function fetchArchiveCompleteness(id: string, revisionId: string = ''): Promise<ArchiveCompleteness> {
const report = await requestJson('/assessments/' + encodeURIComponent(id) + '/report', 'GET', revisionParams(revisionId))
if (report.getString('assessmentId') != id) throw new Error('报告与当前档案不一致')
if (revisionId.length > 0 && report.getString('revisionId') != revisionId) throw new Error('报告版本不一致,请重新选择。')
if (report.getNumber('resultVersion') == 1) return { percent: null, description: '这份旧版报告未计算完整度,已提交资料仍可查看。' }
const result: ArchiveCompleteness = { assessmentId: id, revisionId: draftText(report, 'revisionId'), percent: null, description: '这份报告尚未评定资料完整度。', notApplicableFields: [] }
if (report.getNumber('resultVersion') == 1) { result.description = '这份旧版报告未计算完整度,已提交资料仍可查看。'; return result }
if (report.getNumber('resultVersion') != 2) throw new Error('报告格式暂不支持')
const completeness = report['completeness'] as UTSJSONObject | null
if (completeness == null || completeness.getString('status') != 'evaluated') return { percent: null, description: '这份报告尚未评定资料完整度。' }
if (completeness == null || completeness.getString('status') != 'evaluated') return result
const percent = completeness.getNumber('percent')
if (percent == null || !Number.isInteger(percent) || percent < 0 || percent > 100) throw new Error('完整度数据暂不可读取')
return { percent, description: '反映本次评估时的资料填写情况,不代表信用评分或审批结果。' }
result.percent = percent; result.description = '反映本次评估时的资料填写情况,不代表信用评分或审批结果。'
const items = completeness['items'] as UTSJSONObject[] | null
if (items != null && Array.isArray(items)) archiveAmountKeys.forEach((key: string) => {
const matching = items.filter((item: UTSJSONObject): boolean => item != null && item['fieldKey'] == key)
if (matching.length == 1 && matching[0]['state'] == 'not_applicable') result.notApplicableFields.push(key)
})
return result
}
// 只投影同版报告已经保存的不适用项,原档案与编辑回填继续保留原值。
export function archiveSections(detail: AssessmentDetail, completeness: ArchiveCompleteness | null): ArchiveSection[] {
if (completeness == null || completeness.assessmentId != detail.record.id
|| completeness.revisionId.length == 0 || completeness.revisionId != detail.record.revisionId) return detail.sections
return detail.sections.map((section: ArchiveSection): ArchiveSection => ({
id: section.id, icon: section.icon, title: section.title, note: section.note, provided: section.provided,
fields: section.fields.map((field: ArchiveField): ArchiveField => {
if (!field.isMissing || !archiveAmountKeys.includes(field.fieldKey) || !completeness!.notApplicableFields.includes(field.fieldKey)) return field
return { fieldKey: field.fieldKey, label: field.label, value: '不适用', isMissing: field.isMissing }
})
}))
}
function validAssessmentId(value: string): boolean { return /^[1-9][0-9]{0,9}$/.test(value) }
@@ -112,6 +136,7 @@ export async function fetchAnalysisReport(id: string, revisionId: string = ''):
let value = raw == null ? '未填写' : (typeof raw == 'number' ? (raw as number).toString() : draftText(fact, 'value'))
if (fact['value'] != null && fact['unit'] == 'yuan') value = yuanToWan(value) + ' 万元'
if (fact['value'] != null && fact['unit'] == 'month') value += ' 个月'
if (fact['value'] != null && fact['unit'] == 'age') value += ' 岁'
if (fact['state'] == 'invalid') value = '格式需核实'
const enumLabels: UTSJSONObject = { six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '无' }
if (['socialSecurity', 'housingFund'].includes(draftText(fact, 'fieldKey'))) value = contributionLabel(value)
@@ -194,15 +219,18 @@ export async function createAssessment(payload: UTSJSONObject): Promise<string>
if (value.length > 0 && !/^[0-9]{1,4}$/.test(value)) throw new Error('数量与经营月数须为0至9999的整数')
form[key] = value.length == 0 ? null : parseInt(value)
})
if (draftText(values, 'loanCountScope') == 'all_loans') form['loanCountScope'] = 'all_loans'
form['maritalStatus'] = enumValue(draftText(values, 'maritalStatus'), ['已婚', '未婚', '离异', '丧偶'], ['married', 'single', 'divorced', 'widowed'])
const socialKeys = ['socialSecurity', 'housingFund']
socialKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), contributionLabels, contributionCodes) })
const ownershipKeys = ['house', 'car']
ownershipKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), ['有', '无'], ['yes', 'no']) })
const provinceCode = provinceCodeForName(draftText(values, 'city').trim())
const cityNames = ['深圳市', '广州市', '北京市', '上海市', '杭州市']
const cityCodes = ['440300', '440100', '110100', '310100', '330100']
const cityIndex = cityNames.indexOf(draftText(values, 'city'))
form['cityCode'] = cityIndex < 0 ? null : cityCodes[cityIndex]
form['cityCode'] = provinceCode != null || cityIndex < 0 ? null : cityCodes[cityIndex]
if (provinceCode != null) form['provinceCode'] = provinceCode
// 卡片资料单独保存,确认页明示暂不绑定评估;只发送自填信息,绝不把本机路径或附件送入分析。
const body: UTSJSONObject = { inputVersion: 3, type: values['type'], moneyUnit: 'yuan', form, documents: [] as UTSJSONObject[], consentIds: [] as string[] }
if (editId.length > 0) body['baseRevisionId'] = baseRevision
@@ -252,7 +280,7 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
const record = recordFrom(result, id, revisionId)
const form = result['form'] as UTSJSONObject | null
if (form == null) throw new Error('评估详情不完整')
const fields: AssessmentField[] = []
const fields: ArchiveField[] = []
const enterprise = result['type'] == 'enterprise'
const sections: ArchiveSection[] = [
{ id: 'basic', icon: 'file-text', title: '基本信息', note: enterprise ? '企业与联系人信息' : '身份与联系方式', fields: [], provided: 0 },
@@ -260,14 +288,16 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
{ id: 'assets', icon: 'asset', title: '资产与负债', note: '贷款、信用卡额度与房车情况', fields: [], provided: 0 },
{ id: 'authorization', icon: 'authorization', title: '资料与授权', note: '私有资料与外部查询授权', fields: [], provided: 0 }
]
const addField = (section: number, label: string, value: string, provided: boolean) => {
const field: AssessmentField = { label, value }
const addField = (section: number, fieldKey: string, label: string, value: string, provided: boolean, isMissing: boolean) => {
const field: ArchiveField = { fieldKey, label, value, isMissing }
fields.push(field); sections[section].fields.push(field)
if (provided) sections[section].provided++
}
const keys = ['name', 'phone', 'city', 'idCard', 'enterpriseName', 'businessCode', 'occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'maritalStatus', 'socialSecurity', 'housingFund', 'house', 'car', 'socialSecurityBase', 'housingFundBase']
const labels = ['姓名 / 联系人', '手机号', '城市', '身份证号', '企业名称', '信用代码', '职业 / 行业', '工作单位', '经营月数', '月收入', '年营收', '信用贷款笔数', '欠款总余额', '贷款月还款金额', '信用卡张数', '信用卡总额度', '信用卡已用额度', '婚姻状况', '社保缴纳', '公积金缴纳', '是否有房', '是否有车', '社保月缴存基数', '公积金月缴存基数']
labels[11] = draftText(form, 'loanCountScope') == 'all_loans' ? '贷款笔数' : '信用贷款笔数(旧口径)'
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
if (form.getString('provinceCode') != null) labels[2] = '所在省份'
const enumLabels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '未缴纳' }
keys.forEach((key: string, index: number) => {
const raw = form[key]
@@ -282,7 +312,7 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
const incomeKeys = ['occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'socialSecurity', 'housingFund', 'socialSecurityBase', 'housingFundBase']
const assetKeys = ['creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'house', 'car']
// “无”及数值0都是已填写,不能用真值判断遗漏零负债、零经营月数。
addField(incomeKeys.includes(key) ? 1 : (assetKeys.includes(key) ? 2 : 0), labels[index], value, raw != null && value.length > 0)
addField(incomeKeys.includes(key) ? 1 : (assetKeys.includes(key) ? 2 : 0), key, labels[index], value, raw != null && value.length > 0, raw == null)
})
return { record, fields, sections }
}
@@ -300,6 +330,7 @@ export async function fetchAssessmentEdit(id: string): Promise<UTSJSONObject> {
const form = result['form'] as UTSJSONObject | null
if (form == null || Array.isArray(form) || typeof form != 'object' || result.getString('moneyUnit') != 'yuan') throw new Error('修改资料不完整,请返回档案重新读取。')
const payload: UTSJSONObject = { type: result['type'], editAssessmentId: id, baseRevisionId: result['revisionId'], step: 0 }
payload['loanCountScope'] = draftText(form, 'loanCountScope') == 'all_loans' ? 'all_loans' : ''
const money = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
const labels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳 6 个月以上', under_six_months: '缴纳不足 6 个月', none: '未缴纳' }
const enums = ['maritalStatus', 'house', 'car', 'socialSecurity', 'housingFund']
+73
View File
@@ -0,0 +1,73 @@
"""从版本化选择字典生成前后端城市数据;不下载、不修改生产配置。"""
import argparse
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser()
parser.add_argument('--backend', type=Path, required=True)
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
data = json.loads((ROOT / 'docs/data/city-options.json').read_text(encoding='utf-8'))
regions = data['regions']
names = [city['name'] for region in regions for city in region['cities']]
codes = {city['code']: city['name'] for region in regions for city in region['cities'] if city['code']}
assert len(names) == len(set(names)), '城市名称不能歧义'
assert len(codes) == sum(bool(city['code']) for region in regions for city in region['cities']), '代码重复'
assert all(re.fullmatch(r'\d{6}', code) for code in codes)
uts = "// 由 scripts/generate-city-options.py 生成;来源和覆盖口径见 docs/data/city-options.json。\n"
uts += "export type CityOption = { name: string, code: string }\nexport type CityRegion = { name: string, cities: CityOption[] }\n"
uts += "export const cityRegions: CityRegion[] = [\n"
uts += ',\n'.join(' ' + json.dumps(region, ensure_ascii=False) for region in regions) + '\n]\n'
provinces = {str(index['code']) + '0000': index['name'] for index in data['provinces']}
uts += 'export const provinceOptions: CityOption[] = ' + json.dumps([{'name':name,'code':code} for code,name in provinces.items()],ensure_ascii=False) + '\n'
uts += '''
export const citySelection = (name: string): number[] => {
for (let province = 0; province < cityRegions.length; province++) {
const cities = cityRegions[province].cities
for (let city = 0; city < cities.length; city++) {
if (cities[city].name == name) return [province, city]
}
}
return [-1, -1]
}
export const cityCodeForName = (name: string): string | null => {
const selected = citySelection(name)
if (selected[0] < 0) return null
const code = cityRegions[selected[0]].cities[selected[1]].code
return code.length > 0 ? code : null
}
export const cityDisplayName = (name: string): string => {
const selected = citySelection(name)
if (selected[0] < 0) return name
const province = cityRegions[selected[0]].name
return province == name ? name : province + ' / ' + name
}
export const provinceCodeForName = (name: string): string | null => {
const index = provinceOptions.findIndex((item: CityOption): boolean => item.name == name)
return index < 0 ? null : provinceOptions[index].code
}
// 仅供历史城市资料回填为省份;未知名称不猜测。
export const provinceForCity = (name: string): string => {
if (provinceCodeForName(name) != null) return name
const selected = citySelection(name)
return selected[0] < 0 ? '' : cityRegions[selected[0]].name
}
'''
constant = ' const PROVINCES = [\n' + '\n'.join(" '%s' => '%s'," % (code, name) for code,name in provinces.items()) + '\n ];\n'
backend = args.backend / 'application/common/library/AssessmentRecords.php'
original = backend.read_text(encoding='utf-8')
php, replacements = re.subn(r' const PROVINCES = .*?;\n', lambda _: constant, original, count=1, flags=re.S)
if not replacements:
php = original.replace(' private static function only(', constant + '\n private static function only(',1)
for path, content in [(ROOT / 'services/cities.uts', uts), (backend, php)]:
if args.check:
assert path.read_text(encoding='utf-8') == content, f'生成文件不同步:{path}'
else:
path.write_text(content, encoding='utf-8', newline='\n')
print(f'{len(provinces)} province choices; legacy city lookup retained; ' + ('consistent' if args.check else 'generated'))
+40
View File
@@ -0,0 +1,40 @@
// Execute the analysis page's actual lifecycle with synthetic status responses; no network/model access.
const fs=require('node:fs'),path=require('node:path'),vm=require('node:vm'),assert=require('node:assert/strict');
const ts=require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
const repo=path.resolve(__dirname,'..');
const compile=source=>ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2020}}).outputText;
const routeExports={};vm.runInNewContext(compile(fs.readFileSync(path.join(repo,'services/report-route.uts'),'utf8')),{exports:routeExports});
const script=fs.readFileSync(path.join(repo,'packages/finance/analyzing/analyzing.uvue'),'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
function status(overrides={}){return {id:'42',revisionId:'9',publishedRevisionId:'9',status:'succeeded',statusLabel:'分析完成',message:'',hasReport:true,stage:'completed',canRetry:false,taskId:'task9',...overrides};}
function page(reply){
let token='synthetic-owner',response=reply,onLoad,onShow,onHide,onUnload;const navigations=[],timers=[];
const session=()=>token==null?null:{token,getString:key=>key==='token'?token:''};
const context={exports:{},Error,Date,Math,Promise,ref:value=>({value}),
onLoad:fn=>{onLoad=fn},onShow:fn=>{onShow=fn},onHide:fn=>{onHide=fn},onUnload:fn=>{onUnload=fn},
setTimeout:fn=>{timers.push(fn);return timers.length},clearTimeout:()=>{},setInterval:()=>1,clearInterval:()=>{},
uni:{redirectTo:({url})=>{navigations.push({kind:'redirect',url})},navigateTo:({url})=>{navigations.push({kind:'navigate',url})}},
require:module=>module.endsWith('layout.uts')?{usePageLayout:()=>({})}:module.endsWith('api.uts')?{getSession:session}:module.endsWith('records.uts')?{fetchAssessmentStatus:async()=>typeof response==='function'?response():response}:module.endsWith('report-route.uts')?routeExports:(()=>{throw Error('Unexpected dependency '+module)})()};
vm.runInNewContext(compile(script+'\nexports.refreshStatus=refreshStatus; exports.report=report; exports.statusMessage=()=>message.value;'),context);
onLoad({assessmentId:'42'});
return {navigations,timers,show:()=>onShow(),hide:()=>onHide(),setReply:r=>{response=r},setToken:t=>{token=t},refresh:()=>context.exports.refreshStatus(),manualReport:()=>context.exports.report(),message:()=>context.exports.statusMessage()};
}
const flush=async()=>{await Promise.resolve();await Promise.resolve();await Promise.resolve()};
let checks=0;const check=(ok,label)=>{assert.ok(ok,label);checks++};
(async()=>{
let p=page(status());p.show();await flush();
check(p.navigations.length===1,'successful analysis automatically opens its result');
assert.deepEqual(p.navigations[0],{kind:'redirect',url:'/packages/finance/report/report?assessmentId=42&tab=matches&revisionId=9'});checks++;
check(p.timers.length===0,'successful result does not keep polling');
p=page(status({status:'processing',stage:'queued',revisionId:'10',publishedRevisionId:'9'}));p.show();await flush();
check(p.navigations.length===0&&p.timers.length===1,'processing revision never opens the older published report');
p.setReply(status({revisionId:'10',publishedRevisionId:'10'}));p.timers[0]();await flush();
check(p.navigations.length===1&&p.navigations[0].url.endsWith('tab=matches&revisionId=10'),'poll completion opens the newly published revision');
p=page(status({status:'failed',canRetry:true}));p.show();await flush();check(p.navigations.length===0,'failed assessment stays on recovery page');
check(p.message().includes('分析未完成')&&!p.message().includes('可查看本次报告'),'failed analysis without server message does not announce completed report');
p=page(status({hasReport:false,publishedRevisionId:''}));p.show();await flush();check(p.navigations.length===0,'missing report never navigates to an unfrozen result');
p=page(status({publishedRevisionId:'8'}));p.show();await flush();check(p.navigations.length===0,'inconsistent published revision is not opened as the new result');
let resolve;const pending=new Promise(r=>{resolve=r});p=page(()=>pending);p.show();p.hide();resolve(status());await flush();check(p.navigations.length===0,'late response after leaving cannot navigate');
let switched;const delayed=new Promise(r=>{switched=r});p=page(()=>delayed);p.show();p.setToken('other-account');switched(status());await flush();check(p.navigations.length===0,'account switch rejects former account result');
p=page(status());p.setToken(null);p.show();await flush();check(p.navigations[0].url==='/packages/account/login/login','expired session requires login');
console.log('PASS: '+checks+' analysis result route checks; no network/model access');
})().catch(e=>{console.error(e);process.exitCode=1});
+271
View File
@@ -0,0 +1,271 @@
// Exercise real archive adapters/page scripts with synthetic memory responses; no network, DB or build.
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const ts = require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
const root = path.resolve(__dirname, '..');
const amountKeys = ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit'];
class Json {
constructor(values = {}) { for (const [key, value] of Object.entries(values)) this[key] = wrap(value); }
getString(key) { return typeof this[key] === 'string' ? this[key] : null; }
getNumber(key) { return typeof this[key] === 'number' ? this[key] : null; }
getBoolean(key) { return typeof this[key] === 'boolean' ? this[key] : null; }
getArray(key) { return Array.isArray(this[key]) ? this[key] : null; }
}
function wrap(value) { return Array.isArray(value) ? value.map(wrap) : value && typeof value === 'object' && !(value instanceof Json) ? new Json(value) : value; }
const plain = value => JSON.parse(JSON.stringify(value));
function harness() {
const modules = {}; const queue = []; const calls = []; const hooks = {}; const navigation = [];
let session = new Json({ token: 'synthetic-archive-owner' });
const api = {
getSession: () => session,
async requestJson(route, method, params) {
calls.push({ route, method, params: plain(params) });
const response = queue.shift(); assert.ok(response, 'response queued for ' + route);
assert.equal(route, response.route); assert.equal(method, 'GET');
if (response.params !== undefined) assert.deepEqual(plain(params), response.params);
if (response.error) throw response.error;
return wrap(response.promise ? await response.promise : response.data);
}
};
function compile(source, filename, extras = {}) {
const exports = {};
const prepared = source.replace(/const (labels|fieldNames): UTSJSONObject = (.*)/g, 'const $1: UTSJSONObject = new UTSJSONObject($2)');
const compiled = ts.transpileModule(prepared, { reportDiagnostics: true, compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } });
assert.equal((compiled.diagnostics || []).filter(item => item.category === ts.DiagnosticCategory.Error).length, 0, filename + ' transpiles');
vm.runInNewContext(compiled.outputText, { exports, UTSJSONObject: Json, Error, Number, Date, Math, Promise, Array,
require(name) {
if (name.endsWith('.uvue')) return {};
if (name.endsWith('/api.uts') || name === './api.uts') return api;
if (name.endsWith('/layout.uts')) return { usePageLayout: () => ({ pageStyle: () => '', contentStyle: () => '' }) };
return load(name.startsWith('@/') ? name.slice(2) : path.posix.normalize(path.posix.dirname(filename) + '/' + name));
}, setInterval: () => 1, clearInterval: () => {}, ...extras
}, { filename });
return exports;
}
function load(filename) {
if (!modules[filename]) modules[filename] = compile(fs.readFileSync(path.join(root, filename), 'utf8'), filename);
return modules[filename];
}
function page() {
const filename = 'packages/finance/archive/archive.uvue';
const source = fs.readFileSync(path.join(root, filename), 'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
const exposure = '{detail,sections,completeness,completenessFailed,error,readCompleteness,retryCompleteness,refresh,clearView,archiveCompleteness:typeof archiveCompleteness==="undefined"?null:archiveCompleteness}';
return compile(source + '\nexports.page=' + exposure, filename, {
ref: value => ({ value }), computed: callback => ({ get value() { return callback(); } }),
onLoad: callback => { hooks.load = callback; }, onShow: callback => { hooks.show = callback; },
onHide: callback => { hooks.hide = callback; }, onUnload: callback => { hooks.unload = callback; },
uni: { redirectTo: options => navigation.push(options.url), navigateTo: options => navigation.push(options.url) }
}).page;
}
function enqueue(route, data, params) { queue.push({ route, data, params }); }
function defer(route, params) {
let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
queue.push({ route, promise, params }); return { resolve, reject };
}
return { load, page, hooks, calls, queue, navigation, enqueue, defer, fail: (route, error = new Error('synthetic read failed')) => queue.push({ route, error }), setSession: token => { session = token == null ? null : new Json({ token }); } };
}
function detailResponse(overrides = {}) {
const form = { name: '合成资料', phone: '199****0009', city: '广东省', provinceCode: '440000', cityCode: null,
occupation: '合成职业', monthlyIncome: '20000.00', loanCountScope: 'all_loans', creditLoanCount: 0, creditCardCount: 0,
house: 'no', car: 'no', totalDebtBalance: null, monthlyLoanPayment: null, creditCardTotalLimit: null, creditCardUsedLimit: null, ...overrides.form };
return { assessmentId: '1', revisionId: '2', revisionNo: 1, type: 'personal', status: 'succeeded', statusLabel: '分析完成',
message: '', taskId: 'synthetic-task', stage: 'completed', canRetry: false, hasReport: true, publishedRevisionId: '2',
submittedAt: '2026-01-01T00:00:00Z', inputVersion: 3, moneyUnit: 'yuan', ...overrides, form };
}
function reportResponse(overrides = {}) {
return { assessmentId: '1', revisionId: '2', resultVersion: 2,
completeness: { status: 'evaluated', percent: 100, items: amountKeys.map(fieldKey => ({ fieldKey, state: 'not_applicable' })), ...overrides.completeness },
...Object.fromEntries(Object.entries(overrides).filter(([key]) => key !== 'completeness')) };
}
async function fetchDetail(h, response = detailResponse()) {
h.enqueue('/assessments/' + response.assessmentId, response, { revisionId: response.revisionId });
return h.load('packages/finance/services/records.uts').fetchAssessmentDetail(response.assessmentId, response.revisionId);
}
async function fetchCompleteness(h, response = reportResponse()) {
h.enqueue('/assessments/1/report', response, { revisionId: '2' });
return h.load('packages/finance/services/records.uts').fetchArchiveCompleteness('1', '2');
}
const settle = async () => { for (let i = 0; i < 20; i++) await Promise.resolve(); };
function queuePageReads(h, detail = detailResponse()) {
const id = detail.assessmentId; const revision = detail.revisionId;
h.enqueue('/assessments/' + id, detail, { revisionId: revision });
h.enqueue('/assessments', { items: [detail], hasMore: false, nextCursor: '' }, { limit: '20', cursor: '', type: detail.type });
h.enqueue('/assessments/' + id + '/revisions', { items: [detail], hasMore: false, nextCursor: '' }, { cursor: '' });
}
async function openPage(h, report = reportResponse(), options = {}) {
const detail = options.detail || detailResponse(); const view = h.page();
h.hooks.load({ assessmentId: detail.assessmentId, revisionId: detail.revisionId });
queuePageReads(h, detail);
let pending = null;
if (detail.status === 'succeeded') {
if (options.deferred) pending = h.defer('/assessments/' + detail.assessmentId + '/report', { revisionId: detail.revisionId });
else if (options.error) h.fail('/assessments/' + detail.assessmentId + '/report');
else h.enqueue('/assessments/' + detail.assessmentId + '/report', report, { revisionId: detail.revisionId });
}
h.hooks.show(); await settle();
return { view, pending };
}
function amountValues(sections) {
return plain(sections.find(item => item.id === 'assets').fields.filter(item => amountKeys.includes(item.fieldKey)).map(item => item.value));
}
let passed = 0, failed = 0;
async function test(name, callback) {
try { await callback(); passed++; console.log('PASS ' + name); }
catch (error) { failed++; console.error('FAIL ' + name + '\n' + error.stack); }
}
(async () => {
await test('same-revision saved not_applicable evidence changes only missing amount display', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts');
const detail = await fetchDetail(h); const before = JSON.stringify(detail);
const completeness = await fetchCompleteness(h);
assert.ok(Array.isArray(completeness.notApplicableFields), 'saved report must expose applicable archive display evidence');
assert.deepEqual(plain(completeness.notApplicableFields), amountKeys);
const sections = records.archiveSections(detail, completeness);
const fields = sections.find(item => item.id === 'assets').fields;
assert.deepEqual(plain(fields.filter(item => amountKeys.includes(item.fieldKey)).map(item => item.value)), ['不适用', '不适用', '不适用', '不适用']);
assert.equal(sections.find(item => item.id === 'assets').provided, 4);
assert.equal(JSON.stringify(detail), before); assert.equal(h.calls.length, 2);
assert.ok(detail.fields.filter(item => amountKeys.includes(item.fieldKey)).every(item => item.isMissing && item.value === '未填写'));
});
await test('explicit zero and positive saved amounts remain visible despite conflicting display markers', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts');
const detail = await fetchDetail(h, detailResponse({ form: { totalDebtBalance: '0.00', monthlyLoanPayment: '12345.67', creditCardTotalLimit: '50000.00', creditCardUsedLimit: null } }));
const before = JSON.stringify(detail); const completeness = await fetchCompleteness(h);
assert.deepEqual(amountValues(records.archiveSections(detail, completeness)), ['0 万元', '1.234567 万元', '5 万元', '不适用']);
assert.equal(JSON.stringify(detail), before);
assert.equal(detail.sections.find(item => item.id === 'assets').provided, 7);
assert.ok(detail.fields.filter(item => ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit'].includes(item.fieldKey)).every(item => item.isMissing === false));
});
await test('missing origin flag rather than Chinese placeholder wording controls display adaptation', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts'); const detail = await fetchDetail(h);
const fields = detail.sections.find(item => item.id === 'assets').fields;
fields.find(item => item.fieldKey === 'totalDebtBalance').value = '尚未提供';
const completeness = await fetchCompleteness(h);
assert.equal(records.archiveSections(detail, completeness).find(item => item.id === 'assets').fields.find(item => item.fieldKey === 'totalDebtBalance').value, '不适用');
assert.equal(fields.find(item => item.fieldKey === 'totalDebtBalance').value, '尚未提供');
});
await test('partial saved markers affect only matching whitelist field keys, not counts or unrelated blanks', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts'); const detail = await fetchDetail(h);
const completeness = await fetchCompleteness(h, reportResponse({ completeness: { items: [
{ fieldKey: 'totalDebtBalance', state: 'not_applicable' }, { fieldKey: 'creditLoanCount', state: 'not_applicable' },
{ fieldKey: 'company', state: 'not_applicable' }, { fieldKey: 'monthlyLoanPayment', state: 'missing' }
] } }));
assert.deepEqual(plain(completeness.notApplicableFields), ['totalDebtBalance']);
assert.deepEqual(amountValues(records.archiveSections(detail, completeness)), ['不适用', '未填写', '未填写', '未填写']);
assert.equal(records.archiveSections(detail, completeness).find(item => item.id === 'assets').fields.find(item => item.fieldKey === 'creditLoanCount').value, '0');
});
await test('old loan scope and count zero do not infer a saved marker or recompute report completeness', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts');
const detail = await fetchDetail(h, detailResponse({ form: { loanCountScope: undefined } }));
const completeness = await fetchCompleteness(h, reportResponse({ completeness: { percent: 60, items: [] } }));
assert.equal(completeness.percent, 60); assert.deepEqual(plain(completeness.notApplicableFields), []);
assert.deepEqual(amountValues(records.archiveSections(detail, completeness)), ['未填写', '未填写', '未填写', '未填写']);
});
await test('absent or nonarray saved items never supply display evidence', async () => {
for (const items of [undefined, null, {}, 'not_applicable']) {
const h = harness(); const result = await fetchCompleteness(h, reportResponse({ completeness: { items } }));
assert.deepEqual(plain(result.notApplicableFields), []); assert.equal(result.percent, 100);
}
});
await test('v1 and unevaluated reports supply no new not-applicable display evidence', async () => {
for (const response of [reportResponse({ resultVersion: 1 }), reportResponse({ completeness: { status: 'not_evaluated' } })]) {
const h = harness(); const result = await fetchCompleteness(h, response);
assert.equal(result.percent, null); assert.deepEqual(plain(result.notApplicableFields), []);
assert.equal(result.assessmentId, '1'); assert.equal(result.revisionId, '2');
}
});
await test('duplicate keys including conflicting or identical markers are excluded independently', async () => {
const h = harness(); const result = await fetchCompleteness(h, reportResponse({ completeness: { items: [
{ fieldKey: 'totalDebtBalance', state: 'not_applicable' }, { fieldKey: 'totalDebtBalance', state: 'provided' },
{ fieldKey: 'monthlyLoanPayment', state: 'not_applicable' }, { fieldKey: 'monthlyLoanPayment', state: 'not_applicable' },
{ fieldKey: 'creditCardTotalLimit', state: 'not_applicable' }, { fieldKey: 'creditCardUsedLimit', state: 'invalid' },
null, 0, 'unknown', { label: '欠款总余额', state: 'not_applicable' }
] } }));
assert.deepEqual(plain(result.notApplicableFields), ['creditCardTotalLimit']);
});
await test('different assessment or revision evidence cannot change the current archive', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts'); const detail = await fetchDetail(h);
const result = await fetchCompleteness(h);
for (const evidence of [null, { ...result, assessmentId: '3' }, { ...result, revisionId: '4' }]) {
assert.deepEqual(amountValues(records.archiveSections(detail, evidence)), ['未填写', '未填写', '未填写', '未填写']);
}
assert.equal(h.calls.length, 2);
h.enqueue('/assessments/1/report', reportResponse({ revisionId: '4' }), { revisionId: '2' });
await assert.rejects(records.fetchArchiveCompleteness('1', '2'), /报告版本不一致/);
});
await test('display adaptation copies changed amount fields and preserves original values and provided counts', async () => {
const h = harness(); const records = h.load('packages/finance/services/records.uts'); const detail = await fetchDetail(h);
const result = await fetchCompleteness(h); const before = JSON.stringify(detail);
const display = records.archiveSections(detail, result);
assert.notEqual(display, detail.sections); assert.notEqual(display[0], detail.sections[0]);
const changed = display.find(item => item.id === 'assets').fields.find(item => item.fieldKey === 'totalDebtBalance');
const originalField = detail.sections.find(item => item.id === 'assets').fields.find(item => item.fieldKey === 'totalDebtBalance');
assert.notEqual(display[0].fields, detail.sections[0].fields); assert.notEqual(changed, originalField);
assert.deepEqual(plain(display.map(item => item.provided)), plain(detail.sections.map(item => item.provided)));
changed.value = 'changed-display-only'; assert.equal(JSON.stringify(detail), before);
});
await test('archive page uses its existing report read to present markers with no additional requests', async () => {
const h = harness(); const { view } = await openPage(h);
assert.equal(view.error.value, ''); assert.equal(view.completeness.value, 100);
assert.deepEqual(amountValues(view.sections.value), ['不适用', '不适用', '不适用', '不适用']);
assert.deepEqual(amountValues(view.detail.value.sections), ['未填写', '未填写', '未填写', '未填写']);
assert.equal(h.calls.length, 4); assert.equal(h.calls.filter(item => item.route.endsWith('/report')).length, 1);
assert.equal(h.queue.length, 0); h.hooks.hide(); assert.equal(view.archiveCompleteness.value, null);
});
await test('failed report read leaves raw amounts and manual retry restores only saved display markers', async () => {
const h = harness(); const { view } = await openPage(h, null, { error: true });
assert.equal(view.completenessFailed.value, true); assert.equal(view.archiveCompleteness.value, null);
assert.deepEqual(amountValues(view.sections.value), ['未填写', '未填写', '未填写', '未填写']);
h.enqueue('/assessments/1/report', reportResponse(), { revisionId: '2' }); view.retryCompleteness(); await settle();
assert.equal(view.completenessFailed.value, false); assert.deepEqual(amountValues(view.sections.value), ['不适用', '不适用', '不适用', '不适用']);
h.fail('/assessments/1/report'); view.retryCompleteness(); await settle();
assert.equal(view.completenessFailed.value, true); assert.equal(view.archiveCompleteness.value, null);
assert.deepEqual(amountValues(view.sections.value), ['未填写', '未填写', '未填写', '未填写']); assert.equal(h.calls.length, 6);
h.hooks.hide();
});
await test('late earlier report response cannot replace a newer manual retry result', async () => {
const h = harness(); const { view, pending } = await openPage(h, null, { deferred: true });
const newer = h.defer('/assessments/1/report', { revisionId: '2' }); view.retryCompleteness();
newer.resolve(reportResponse({ completeness: { items: [{ fieldKey: 'monthlyLoanPayment', state: 'not_applicable' }] } })); await settle();
assert.deepEqual(amountValues(view.sections.value), ['未填写', '不适用', '未填写', '未填写']);
pending.resolve(reportResponse()); await settle();
assert.deepEqual(amountValues(view.sections.value), ['未填写', '不适用', '未填写', '未填写']); h.hooks.hide();
});
await test('late failed earlier read cannot clear a newer successful display result', async () => {
const h = harness(); const { view, pending } = await openPage(h, null, { deferred: true });
h.enqueue('/assessments/1/report', reportResponse(), { revisionId: '2' }); view.retryCompleteness(); await settle();
pending.reject(new Error('synthetic old read failure')); await settle();
assert.equal(view.completenessFailed.value, false); assert.deepEqual(amountValues(view.sections.value), ['不适用', '不适用', '不适用', '不适用']); h.hooks.hide();
});
await test('changing account before a delayed report resolves clears old archive data', async () => {
const h = harness(); const { view, pending } = await openPage(h, null, { deferred: true });
h.setSession('synthetic-other-owner'); pending.resolve(reportResponse()); await settle();
assert.equal(view.detail.value, null); assert.equal(view.archiveCompleteness.value, null);
assert.ok(view.sections.value.every(item => item.fields.length === 0)); h.hooks.hide();
});
await test('hide before report completion prevents display evidence and personal detail from reappearing', async () => {
const h = harness(); const { view, pending } = await openPage(h, null, { deferred: true });
h.hooks.hide(); pending.resolve(reportResponse()); await settle();
assert.equal(view.detail.value, null); assert.equal(view.archiveCompleteness.value, null);
assert.equal(view.completeness.value, null);
});
await test('late report cannot attach after current detail changes assessment or revision', async () => {
for (const response of [detailResponse({ assessmentId: '3' }), detailResponse({ revisionId: '4', publishedRevisionId: '4' })]) {
const h = harness(); const { view, pending } = await openPage(h, null, { deferred: true });
view.detail.value = await fetchDetail(h, response); pending.resolve(reportResponse()); await settle();
assert.equal(view.archiveCompleteness.value, null);
assert.deepEqual(amountValues(view.sections.value), ['未填写', '未填写', '未填写', '未填写']); h.hooks.hide();
}
});
await test('processing or failed assessments do not fetch or infer report display markers', async () => {
for (const status of ['processing', 'failed']) {
const h = harness(); const { view } = await openPage(h, null, { detail: detailResponse({ status, hasReport: false, publishedRevisionId: '' }) });
assert.equal(view.archiveCompleteness.value, null); assert.equal(h.calls.length, 3);
assert.deepEqual(amountValues(view.sections.value), ['未填写', '未填写', '未填写', '未填写']); h.hooks.hide();
}
});
console.log(`${failed ? 'FAIL' : 'PASS'} ${passed} archive display checks; ${failed} failures; no network/database/model/build.`);
process.exitCode = failed ? 1 : 0;
})();
+22
View File
@@ -0,0 +1,22 @@
const fs=require('node:fs'),vm=require('node:vm'),assert=require('node:assert/strict');
const ts=require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
class Json {constructor(values={}){Object.assign(this,values)} getString(k){return this[k]??null} getNumber(k){return this[k]??null} getBoolean(k){return this[k]??null} getJSON(k){return this[k]??null} getArray(k){return this[k]??null}}
let sent, reply;
function load(file){const out={};const source=fs.readFileSync(__dirname+'/../'+file,'utf8');vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2020}}).outputText,{exports:out,UTSJSONObject:Json,require:p=>{
if(p.endsWith('api.uts'))return {getSession:()=>({token:'synthetic'}),requestJson:async(path,method,body)=>{sent=body;return reply ?? new Json({assessmentId:'1',revisionId:'2',submissionStatus:'accepted'})}};
if(p.endsWith('/assessment.uts'))return {assessmentApiPayload:p=>p,draftText:(p,k)=>typeof p[k]==='string'?p[k]:''};
if(p.endsWith('assessment-flow.uts'))return {assessmentRequestKey:()=> 'synthetic-key'};
if(p.endsWith('cities.uts'))return load('services/cities.uts');
if(p.endsWith('contribution.uts'))return {contributionLabel:v=>v};
return {};
}});return out;}
(async()=>{const service=load('packages/finance/services/records.uts');let checks=0;
for(const [city,provinceCode,cityCode]of[['广东省','440000',null],['北京市','110000',null],['台湾省','710000',null],['深圳市',undefined,'440300'],['南京市',undefined,null],['旧手填地区',undefined,null]]){
await service.createAssessment(new Json({type:'personal',city,documents:[],authorizations:[]}));
assert.equal(sent.form.city,city);assert.equal(sent.form.cityCode,cityCode);assert.equal(sent.form.provinceCode,provinceCode);checks+=3;
}
for(const [city,provinceCode,label]of[['广东省','440000','所在省份'],['深圳市',undefined,'城市']]){
reply=new Json({assessmentId:'1',revisionId:'2',revisionNo:1,type:'personal',status:'succeeded',form:new Json({city,provinceCode})});
const detail=await service.fetchAssessmentDetail('1','2');assert.equal(detail.fields[2].label,label);assert.equal(detail.fields[2].value,city);checks+=2;
}
console.log(`PASS: ${checks} region submit/detail checks`);})().catch(e=>{console.error(e);process.exitCode=1});
+214
View File
@@ -0,0 +1,214 @@
// Exercise actual UTS adapters and detail-page bindings with frozen responses; no network or storage.
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const ts = require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
const root = path.resolve(__dirname, '..');
class Json {
constructor(value = {}) { for (const [key, item] of Object.entries(value)) this[key] = wrap(item); }
getString(key) { return typeof this[key] === 'string' ? this[key] : null; }
getNumber(key) { return typeof this[key] === 'number' ? this[key] : null; }
getBoolean(key) { return typeof this[key] === 'boolean' ? this[key] : null; }
}
function wrap(value) { return Array.isArray(value) ? value.map(wrap) : value && typeof value === 'object' ? new Json(value) : value; }
let response;
const modules = {};
function compile(source, filename, extras = {}) {
const exports = {};
const compiled = ts.transpileModule(source, { reportDiagnostics: true, compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } });
assert.equal((compiled.diagnostics || []).filter(item => item.category === ts.DiagnosticCategory.Error).length, 0, filename + ' transpiles');
vm.runInNewContext(compiled.outputText, {
exports, UTSJSONObject: Json, Number, Math, Error, setTimeout, clearTimeout,
require: name => name.endsWith('api.uts') ? { requestJson: async () => new Json(response), getSession: () => null } :
name.endsWith('config.uts') ? { API_BASE_URL: 'https://synthetic.invalid/api/v1' } :
name.endsWith('records.uts') ? { revisionParams: value => ({ revisionId: value }) } :
name.endsWith('layout.uts') ? { usePageLayout: () => ({}) } :
name.endsWith('.uvue') || name.endsWith('assistant.uts') ? {} :
load(name.startsWith('@/') ? name.slice(2) : path.posix.normalize(path.posix.dirname(filename) + '/' + name)),
...extras
}, { filename });
return exports;
}
function load(filename) {
if (modules[filename]) return modules[filename];
const source = fs.readFileSync(path.join(root, filename), 'utf8').replace(/const (fieldNames|labels): UTSJSONObject = (.*)/g, 'const $1: UTSJSONObject = new UTSJSONObject($2)');
return modules[filename] = compile(source, filename);
}
function page(kind, exposure) {
const filename = `packages/finance/${kind}/${kind}.uvue`;
const source = fs.readFileSync(path.join(root, filename), 'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
return compile(source + '\nexports.page=' + exposure, filename, {
ref: value => ({ value }), computed: callback => ({ get value() { return callback(); } }),
onLoad: () => {}, onShow: () => {}, onHide: () => {}, onUnload: () => {}
}).page;
}
function fixture(state = 'eligible') {
return {
assessmentId: '9', revisionId: '11', resultVersion: 2,
matching: { status: 'completed', catalogSnapshotId: 'frozen-original', ruleVersion: 'product-matching-v2', orderingPolicyVersion: 'product-score-descending-v1' },
item: {
productId: '1', productVersion: 3, eligibility: state, score: 78, scorePolicyVersion: 'product-soft-score-v1', rank: 2,
currentAvailability: 'available', positiveReasons: [], blockingReasons: [], cautions: [], evidence: [], aiExplanation: { status: 'not_requested' },
productSnapshot: { productId: '1', productVersion: 3, productName: '合成产品', bankName: '合成机构', applicantTypes: ['personal'], regions: [],
source: { publisher: '合成机构', title: '原产品资料', reference: 'synthetic-reference' },
rules: [{ field: 'monthlyIncome', operator: 'gte', value: '8000.00', requiredSource: 'verified' }] }
}
};
}
function addReason(target, group, id, code, text, field = 'age', actual = null, source, outcome = 'unknown') {
const origin = source === undefined ? (code === 'AI_CONDITION_NOT_ASSESSED' ? 'ai_self_reported' : code === 'PROFILE_DETAIL_NOT_ASSESSED' ? 'product_snapshot' : 'self_reported') : source;
target.item.evidence.push({ evidenceId: id, fieldRefs: [field], actual, source: origin, kind: 'hard', outcome, reasonCode: code });
target.item[group].push({ reasonId: id, code, text, evidenceRefs: [id] });
}
const service = load('packages/finance/services/matches.uts');
async function detail(value) { response = value; return service.fetchMatchDetailRecord('9', '1', '11'); }
let checks = 0, failures = 0;
async function test(name, callback) {
try { await callback(); checks++; console.log('PASS ' + name); }
catch (error) { failures++; console.error('FAIL ' + name + '\n' + error.stack); }
}
const productText = fs.readFileSync(path.join(root, 'packages/finance/product/product.uvue'), 'utf8');
function subtitles(reason) {
const tags = Array.from(productText.matchAll(/<text\b((?:"[^"]*"|'[^']*'|[^'">])*)>([\s\S]*?)<\/text>/g)).filter(item => /\bclass="reason-detail"/.test(item[1]));
assert.equal(tags.length, 2, 'both reason sections have a subtitle binding');
return tags.map(([, attributes, body]) => {
const condition = attributes.match(/v-if="([^"]+)"/);
if (condition && !vm.runInNewContext(condition[1], { reason })) return '';
return body.replace(/{{([\s\S]*?)}}/g, (_, expression) => vm.runInNewContext(expression, { reason }));
});
}
(async () => {
await test('only fixed advisory codes disappear; real missing, invalid, config, pending and limits remain', async () => {
const value = fixture();
const hidden = ['AI_CONDITION_NOT_ASSESSED', 'PROFILE_DETAIL_NOT_ASSESSED'];
const retained = ['AI_CONDITION_FAIL', 'FACT_MISSING', 'FACT_INVALID', 'SOURCE_INSUFFICIENT', 'PRODUCT_CONFIGURATION_UNVERIFIED', 'AI_CONDITION_PENDING', 'AI_CONDITION_LIMIT', 'AI_CONDITION_UNKNOWN'];
for (const code of hidden.concat(retained)) addReason(value, 'cautions', code, code, '未参与参考筛选;同样文字不能决定隐藏');
addReason(value, 'blockingReasons', 'real-fail', 'RULE_FAILED', '不满足已知条件', 'monthlyIncome', '1000.00', 'self_reported', 'fail');
const frozen = JSON.stringify(value);
const result = await detail(value);
assert.deepEqual(Array.from(result.item.cautions, item => item.id), retained);
assert.equal(result.item.blocking.length, 1);
assert.equal(JSON.stringify(value), frozen, 'original saved reasons and evidence remain intact');
});
await test('old and new frozen results retain eligibility, score, versions and full raw snapshots', async () => {
for (const state of ['eligible', 'unknown', 'ineligible']) {
const value = fixture(state);
addReason(value, 'cautions', 'unused-age', 'AI_CONDITION_NOT_ASSESSED', '年龄细项未参与参考筛选');
const frozen = JSON.stringify(value);
const result = await detail(value);
assert.equal(result.item.state, state); assert.equal(result.item.score, 78);
assert.equal(result.revisionId, '11'); assert.equal(result.snapshotId, 'frozen-original');
assert.equal(result.item.cautions.length, 0); assert.equal(JSON.stringify(value), frozen);
}
});
await test('advisory hiding requires a consistent unknown caution with an allowed real evidence source', async () => {
const value = fixture();
addReason(value, 'cautions', 'valid-ai', 'AI_CONDITION_NOT_ASSESSED', '未参与参考筛选');
addReason(value, 'cautions', 'valid-profile-user', 'PROFILE_DETAIL_NOT_ASSESSED', '未参与参考筛选', 'age', null, 'self_reported');
addReason(value, 'cautions', 'valid-profile-product', 'PROFILE_DETAIL_NOT_ASSESSED', '未参与参考筛选');
for (const [id, source, outcome, evidenceCode] of [
['conflicting-fail', 'ai_self_reported', 'fail', 'AI_CONDITION_NOT_ASSESSED'],
['wrong-code', 'ai_self_reported', 'unknown', 'FACT_MISSING'],
['wrong-source', 'product_snapshot', 'unknown', 'AI_CONDITION_NOT_ASSESSED'],
['unknown-source', 'unsupported', 'unknown', 'AI_CONDITION_NOT_ASSESSED']
]) {
addReason(value, 'cautions', id, 'AI_CONDITION_NOT_ASSESSED', '异常记录仍须显示', 'age', null, source, outcome);
value.item.evidence.at(-1).reasonCode = evidenceCode;
}
for (const [id, refs] of [['empty-refs', []], ['missing-refs', undefined], ['missing-evidence', ['absent']]])
value.item.cautions.push({ reasonId: id, code: 'AI_CONDITION_NOT_ASSESSED', text: '缺失证据仍须显示', evidenceRefs: refs });
addReason(value, 'cautions', 'mixed-evidence', 'AI_CONDITION_NOT_ASSESSED', '混合证据仍须显示');
value.item.cautions.at(-1).evidenceRefs.push('conflicting-fail');
addReason(value, 'blockingReasons', 'blocking-same-code', 'AI_CONDITION_NOT_ASSESSED', '异常阻断不能静默隐藏');
addReason(value, 'positiveReasons', 'positive-same-code', 'AI_CONDITION_NOT_ASSESSED', '异常正向不能静默隐藏');
const frozen = JSON.stringify(value);
const result = await detail(value);
assert.deepEqual(Array.from(result.item.cautions, item => item.id), ['conflicting-fail', 'wrong-code', 'wrong-source', 'unknown-source', 'empty-refs', 'missing-refs', 'missing-evidence', 'mixed-evidence']);
assert.equal(result.item.blocking[0].id, 'blocking-same-code');
assert.equal(result.item.positive[0].id, 'positive-same-code');
assert.equal(result.item.cautions.find(item => item.id === 'missing-evidence').evidence[0], '证据暂不可读取');
assert.equal(JSON.stringify(value), frozen);
});
await test('verified evidence and unknown or missing provenance retain their distinct source notices', async () => {
const value = fixture();
for (const [id, source] of [['verified', 'verified'], ['unknown', 'unsupported'], ['missing', null]])
addReason(value, 'cautions', id, 'SOURCE_INSUFFICIENT', '请查看来源状态', 'monthlyIncome', '12000.00', source);
const result = await detail(value);
assert.deepEqual(Array.from(result.item.cautions, item => item.evidence[0]), ['月收入:1.2 万元 · 外部核验', '月收入:1.2 万元 · 来源未知', '月收入:1.2 万元 · 来源未知']);
});
await test('standard known evidence keeps age, province, occupation and employer without provenance tails', async () => {
const value = fixture();
for (const [field, actual] of [['age', 36], ['provinceCode', '440000'], ['occupation', '企业职员'], ['employer', '合成单位']])
addReason(value, 'positiveReasons', field, 'RULE_PASSED', '满足对应条件', field, actual, 'self_reported', 'pass');
addReason(value, 'positiveReasons', 'product-evidence', 'RULE_PASSED', '满足产品条件', 'monthlyIncome', '12000.00', 'product_snapshot', 'pass');
const result = await detail(value);
assert.deepEqual(Array.from(result.item.positive, item => item.evidence[0]), ['年龄:36 岁', '所在省份:广东省', '职业类型:企业职员', '单位名称:合成单位', '月收入:1.2 万元']);
});
await test('legacy reason and saved explanation lose only known source parentheses', async () => {
const value = fixture();
const suffixes = ['(根据自填资料判断)', '(资料尚需核验)', '(用户自填,未经核验)', '(用户自填、未经核验)', '(自填信息未经核验)', '(产品快照)'];
value.item.aiExplanation = { status: 'completed', text: '已保存的原总解释', reasonExplanations: [] };
suffixes.forEach((suffix, index) => {
const id = 'reason-' + index;
addReason(value, 'positiveReasons', id, 'RULE_PASSED', '符合条件' + suffix + '。', 'age', 36, 'self_reported', 'pass');
value.item.aiExplanation.reasonExplanations.push({ reasonId: id, text: '满足该项要求' + suffix + '。', evidenceRefs: [id] });
});
const frozen = JSON.stringify(value);
const result = await detail(value);
for (const reason of result.item.positive) { assert.equal(reason.text, '符合条件。'); assert.equal(reason.explanation, '满足该项要求。'); }
assert.equal(JSON.stringify(value), frozen);
});
await test('real verification conditions and original product sources remain visible', async () => {
const value = fixture();
addReason(value, 'blockingReasons', 'source-required', 'SOURCE_INSUFFICIENT', '此条件需核验收入依据', 'monthlyIncome', '12000.00');
value.item.aiExplanation = { status: 'completed', reasonExplanations: [{ reasonId: 'source-required', text: '该产品条件要求提供可核验依据。', evidenceRefs: ['source-required'] }] };
const result = await detail(value);
assert.equal(result.item.blocking[0].text, '此条件需核验收入依据');
assert.equal(result.item.blocking[0].explanation, '该产品条件要求提供可核验依据。');
assert.equal(result.item.product.conditions[0], '月收入:不少于 0.8 万元(需核验依据)');
assert.equal(result.item.product.source, '合成机构 · 原产品资料');
assert.equal(result.item.product.sourceRef, 'synthetic-reference');
});
await test('known legacy footer sentence becomes a reference disclaimer in saved explanations', async () => {
const value = fixture();
const saved = '满足对应条件。自填信息未经核验;本页面不承诺额度、放款或获批。';
addReason(value, 'positiveReasons', 'legacy-copy', 'RULE_PASSED', '满足对应条件', 'age', 36, 'self_reported', 'pass');
value.item.aiExplanation = { status: 'completed', text: saved, reasonExplanations: [{ reasonId: 'legacy-copy', text: saved, evidenceRefs: ['legacy-copy'] }] };
const frozen = JSON.stringify(value);
const result = await detail(value);
assert.equal(result.item.positive[0].explanation, '满足对应条件。结果仅供参考,不代表审批结果。');
assert.equal(result.item.aiText, '满足对应条件。结果仅供参考,不代表审批结果。');
assert.equal(JSON.stringify(value), frozen);
});
await test('detail attention count uses displayed reasons while retaining real failures', async () => {
const value = fixture();
addReason(value, 'cautions', 'age', 'AI_CONDITION_NOT_ASSESSED', '年龄细项未参与参考筛选');
addReason(value, 'cautions', 'region', 'PROFILE_DETAIL_NOT_ASSESSED', '产品适用地区未参与参考筛选', 'cityCode', null, 'product_snapshot');
addReason(value, 'cautions', 'missing', 'FACT_MISSING', '请补充已采集的月收入', 'monthlyIncome');
addReason(value, 'blockingReasons', 'failed', 'RULE_FAILED', '已知条件不符');
const result = await detail(value);
const current = page('product', '{match,attention,attentionVisible}'); current.match.value = result.item;
assert.deepEqual(Array.from(current.attention.value, item => item.id), ['failed', 'missing']);
assert.equal(current.attentionVisible.value.length, 2);
});
await test('without saved explanation evidence is shown only through the evidence disclosure', () => {
const reason = { explanation: '', evidence: ['年龄:36 岁'] };
assert.deepEqual(subtitles(reason), ['', '']);
assert.equal((productText.match(/v-for="line in reason.evidence"/g) || []).length, 2, 'both disclosure lists retain the original evidence');
});
await test('saved explanation remains the subtitle in both detail sections', () => {
assert.deepEqual(subtitles({ explanation: '满足该项要求。', evidence: ['年龄:36 岁'] }), ['满足该项要求。', '满足该项要求。']);
});
await test('detail empty states and footer use reference language; compare labels do not request extra data', () => {
const empty = Array.from(productText.matchAll(/<text[^>]*class="empty-note"[^>]*>([\s\S]*?)<\/text>/g), item => item[1]);
assert.equal(empty.length, 2);
empty.forEach(value => assert.doesNotMatch(value, /核验|自填/));
const footer = productText.match(/<view class="footnotes">([\s\S]*?)<\/view>/)[1];
assert.doesNotMatch(footer, /自填|未经核验/); assert.match(footer, /仅供参考/); assert.match(footer, /不代表审批/);
const comparison = page('compare', '{detailRows}');
assert.equal(comparison.detailRows.find(row => row.id === 'cautions').label, '注意事项');
});
console.log(`PASS: ${checks} passed, ${failures} failed match detail copy scenarios; networkCalls=0 modelCalls=0 databaseWrites=0`);
if (failures) process.exitCode = 1;
})().catch(error => { console.error(error); process.exitCode = 1; });
+26
View File
@@ -0,0 +1,26 @@
// Exercise the actual UTS adapter with frozen synthetic responses; no network/model access.
const fs=require('node:fs'),path=require('node:path'),vm=require('node:vm'),assert=require('node:assert/strict');
const ts=require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
class Json{constructor(v={}){Object.entries(v).forEach(([k,x])=>this[k]=wrap(x));}getString(k){return typeof this[k]==='string'?this[k]:null;}getNumber(k){return typeof this[k]==='number'?this[k]:null;}getBoolean(k){return typeof this[k]==='boolean'?this[k]:null;}}
function wrap(v){return Array.isArray(v)?v.map(wrap):v&&typeof v==='object'?new Json(v):v;}
let reply,query;
function load(file){const exports={};const source=fs.readFileSync(path.join(__dirname,'..',file),'utf8').replace(/const (fieldNames|labels): UTSJSONObject = (.*)/g,'const $1: UTSJSONObject = new UTSJSONObject($2)');vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2020}}).outputText,{exports,UTSJSONObject:Json,Error,Number,Math,require:p=>p.endsWith('api.uts')?{requestJson:async(_url,_method,params)=>{query=params;return new Json(reply);}}:p.endsWith('config.uts')?{API_BASE_URL:'http://synthetic.invalid/api/v1'}:p.endsWith('records.uts')?{revisionParams:v=>({revisionId:v})}:p.endsWith('cities.uts')?load('services/cities.uts'):load('services/money.uts')},{filename:file});return exports;}
function item(id,state,score){return {productId:id,productVersion:1,eligibility:state,score,scorePolicyVersion:'product-soft-score-v1',currentAvailability:'available',positiveReasons:[],blockingReasons:[],cautions:[],evidence:[],aiExplanation:{status:'not_requested'},productSnapshot:{productId:id,productVersion:1,productName:'合成产品'+id,bankName:'合成机构',applicantTypes:['personal'],regions:[],source:{},rules:[]}};}
function page(items){return {assessmentId:'9',revisionId:'11',resultVersion:2,matching:{status:'completed',ruleVersion:'product-matching-v2',orderingPolicyVersion:'product-score-descending-v1',catalogSnapshotId:'frozen',candidateCount:0,ineligibleCount:0,unknownCount:5},items,hasMore:true,nextCursor:'synthetic-opaque'};}
(async()=>{const service=load('packages/finance/services/matches.uts');let checks=0;
for(const [state,score,want] of [['eligible',92,'92%'],['eligible',0,'0%'],['unknown',80,'80%'],['ineligible',20,'20%'],['unknown',null,''],['ineligible',null,''],['eligible',null,'']]){assert.equal(service.matchPercentage(state,score),want);checks++;}
assert.equal(service.eligibilityLabel('unknown'),'待核实');checks++;
reply=page([item('2','unknown',30),item('5','unknown',30),item('1','unknown',20)]);
let result=await service.fetchMatchPage('9','all','','11');assert.equal(result.items.length,3);assert.equal(query.eligibility,'all');checks+=2;
assert.deepEqual(Array.from(result.items,x=>x.state),['unknown','unknown','unknown']);checks++;
reply=page([item('1','eligible',0),item('2','unknown',50),item('3','ineligible',10)]);reply.matching.candidateCount=1;reply.matching.ineligibleCount=1;reply.matching.unknownCount=1;reply.hasMore=false;reply.nextCursor=null;
result=await service.fetchMatchPage('9','all','','11');assert.equal(result.items[0].score,0);assert.equal(service.matchPercentage('eligible',0),'0%');checks+=2;
await assert.rejects(service.fetchMatchPage('9','eligible','','11'));checks++;
reply=page([item('2','unknown',30),item('2','unknown',30)]);await assert.rejects(service.fetchMatchPage('9','all','','11'));checks++;
reply=page([item('1','unrecognized',20)]);await assert.rejects(service.fetchMatchPage('9','all','','11'));checks++;
reply=page([item('1','unknown',20)]);reply.revisionId='12';await assert.rejects(service.fetchMatchPage('9','all','','11'));checks++;
reply=page([item('1','unknown',20)]);reply.hasMore=false;reply.nextCursor=null;assert.equal((await service.fetchMatchPage('9','all','','11')).items.length,1);checks++;
const age=item('1','eligible',30);age.evidence=[{evidenceId:'age',fieldRefs:['age'],actual:36,source:'ai_self_reported'}];age.positiveReasons=[{reasonId:'age-reason',code:'AI_CONDITION_PASS',text:'符合年龄要求',evidenceRefs:['age']}];
reply={assessmentId:'9',revisionId:'11',resultVersion:2,matching:page([]).matching,item:age};result=await service.fetchMatchDetailRecord('9','1','11');assert.equal(result.item.positive[0].evidence[0],'年龄:36 岁');checks++;
const province=item('1','eligible',80);province.evidence=[{evidenceId:'province',fieldRefs:['provinceCode'],actual:'440000',source:'ai_self_reported'}];province.positiveReasons=[{reasonId:'region-reason',code:'AI_CONDITION_PASS',text:'符合省份要求',evidenceRefs:['province']}];reply={assessmentId:'9',revisionId:'11',resultVersion:2,matching:page([]).matching,item:province};result=await service.fetchMatchDetailRecord('9','1','11');assert.equal(result.item.positive[0].evidence[0],'所在省份:广东省');checks++;
console.log('PASS: '+checks+' match preview UTS checks');})().catch(e=>{console.error(e);process.exitCode=1;});
+6
View File
@@ -87,7 +87,13 @@ test('account login restores match context using the shared route module', () =>
assert.equal(restoredPath, '/packages/finance/product/product?assessmentId=42&productIds=3,7&revisionId=9&productId=7&tab=details&from=compare');
});
test('account login restores the all-products preview group', () => {
onLoad({ resumeMatch: '1', matchPage: 'matches', assessmentId: '42', productIds: '', revisionId: '9', group: 'all' });
assert.equal(restoredPath, '/packages/finance/matches/matches?assessmentId=42&productIds=&revisionId=9&group=all');
});
const cases = [
['all matches preview with revision', ['matches', '42', '', '9', '', 'all'], '/packages/finance/matches/matches?assessmentId=42&productIds=&revisionId=9&group=all'],
['matches with selected products and revision', ['matches', '42', '3,7', '9'], '/packages/finance/matches/matches?assessmentId=42&productIds=3,7&revisionId=9&group=eligible'],
['matches without a selection', ['matches', '42', '', '', '', 'unknown'], '/packages/finance/matches/matches?assessmentId=42&productIds=&group=unknown'],
['compare selection order', ['compare', '42', '7,3,8', '9'], '/packages/finance/compare/compare?assessmentId=42&productIds=7,3,8&revisionId=9'],
+10
View File
@@ -0,0 +1,10 @@
const fs=require('node:fs'),vm=require('node:vm'),assert=require('node:assert/strict');
const ts=require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
const out={};vm.runInNewContext(ts.transpileModule(fs.readFileSync(__dirname+'/../services/cities.uts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2020}}).outputText,{exports:out});
const {provinceOptions,provinceCodeForName,provinceForCity}=out;let checks=0;
const eq=(a,b,label)=>{assert.deepEqual(JSON.parse(JSON.stringify(a)),b,label);checks++};
eq(provinceOptions.length,34,'34 province-level choices');eq(new Set(provinceOptions.map(p=>p.code)).size,34,'codes unique');
for(const[name,code]of[['广东省','440000'],['江苏省','320000'],['北京市','110000'],['重庆市','500000'],['台湾省','710000'],['香港特别行政区','810000'],['澳门特别行政区','820000'],['',null],['深圳市',null],['未知省',null]])eq(provinceCodeForName(name),code,name);
for(const[name,province]of[['深圳市','广东省'],['南京市','江苏省'],['重庆市','重庆市'],['仙桃市','湖北省'],['白杨市','新疆维吾尔自治区'],['台北市','台湾省'],['广东省','广东省'],['旧手填地区','']])eq(provinceForCity(name),province,'restore '+name);
for(const p of provinceOptions)eq(provinceCodeForName(p.name),p.code,'province name/code exact');
console.log(`PASS: ${checks} province option UTS checks`);
+1 -1
View File
@@ -30,7 +30,7 @@ vm.runInNewContext(fs.readFileSync(built, 'utf8'), {
if (module.endsWith('/services/assessment.js')) return { draftText: (item, key) => typeof item[key] === 'string' ? item[key] : '' };
if (module.endsWith('/services/api.js')) return { requestJson: async () => response };
if (module.endsWith('/financing-grade.js')) return { financingGradeFrom: () => ({}) };
if (['/services/assessment-flow.js', '/services/contribution.js', '/services/money.js'].some(suffix => module.endsWith(suffix))) return {};
if (['/services/assessment-flow.js', '/services/contribution.js', '/services/money.js', '/services/cities.js'].some(suffix => module.endsWith(suffix))) return {};
throw Error('Unexpected dependency: ' + module);
}
}, { filename: built });
+211
View File
@@ -0,0 +1,211 @@
// Execute the actual UTS services and page scripts in memory. No app build, network or storage.
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const ts = require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
class Json {
constructor(values = {}) { Object.assign(this, values); }
getString(k) { return typeof this[k] === 'string' ? this[k] : null; }
getNumber(k) { return typeof this[k] === 'number' ? this[k] : null; }
getBoolean(k) { return typeof this[k] === 'boolean' ? this[k] : null; }
getArray(k) { return Array.isArray(this[k]) ? this[k] : null; }
}
const root = path.join(__dirname, '..');
const modules = {};
const calls = [];
const activePages = [];
let response = new Json({ assessmentId: '1', revisionId: '2', submissionStatus: 'accepted' });
const session = new Json({ token: 'synthetic-zero-balance-owner' });
const api = {
getSession: () => session,
requestJson: async (...args) => { calls.push(args); return response; }
};
function compile(source, filename, extras = {}) {
const exports = {};
const compiled = ts.transpileModule(source, { reportDiagnostics: true, compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } });
assert.equal((compiled.diagnostics || []).filter(d => d.category === ts.DiagnosticCategory.Error).length, 0, 'UTS transpiles: ' + filename);
vm.runInNewContext(compiled.outputText, {
exports, UTSJSONObject: Json, Error, Number, Date, Math, Promise,
require: p => p.endsWith('.uvue') ? {} : p === '@/services/api.uts' || p === './api.uts' ? api :
p === '@/services/layout.uts' ? { usePageLayout: () => ({ pageStyle: () => '', contentStyle: () => '', bottomStyle: () => '' }) } :
p === '@/services/documents.uts' ? { fetchOwnedDocuments: async () => [], releaseDocumentTemp: () => {} } :
load(p.startsWith('@/') ? p.slice(2) : path.posix.normalize(path.posix.dirname(filename) + '/' + p)),
...extras
}, { filename });
return exports;
}
function load(filename) {
if (modules[filename]) return modules[filename];
const result = compile(fs.readFileSync(path.join(root, filename), 'utf8'), filename);
modules[filename] = result;
return result;
}
const service = load('services/assessment.uts');
const records = load('packages/finance/services/records.uts');
const flow = load('services/assessment-flow.uts');
function form(overrides = {}) {
return Object.assign(service.emptyAssessmentForm(), {
name: '合成用户', idCard: '110101199001010000', phone: '19900000009', city: '广东省', maritalStatus: '未婚',
occupation: '企业职员', company: '合成单位', monthlyIncome: '1', socialSecurity: '未缴纳', housingFund: '未缴纳',
house: '无', car: '无', creditLoanCount: '0', creditCardCount: '0', loanCountScope: 'all_loans'
}, overrides);
}
function save(value) { service.setAssessmentInput('personal', 2, value, [], [], false, false); }
function snapshot(value) { return JSON.stringify(value); }
function detail(formValues) {
return new Json({ assessmentId: '1', revisionId: '2', revisionNo: 1, type: 'personal', status: 'succeeded', moneyUnit: 'yuan', form: new Json(formValues) });
}
function page(kind) {
const filename = `packages/finance/${kind}/${kind}.uvue`;
const hooks = {};
const events = { toasts: [], navigation: [] };
const exposure = kind === 'assessment' ? '{form,step,restore,next,validateStep,confirmLoanCountScope:typeof confirmLoanCountScope === "undefined" ? null : confirmLoanCountScope}' : '{payload,sections,submit,loadInput}';
const source = fs.readFileSync(path.join(root, filename), 'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
const result = compile(source + '\nexports.page=' + exposure, filename, {
ref: value => ({ value }), computed: fn => ({ get value() { return fn(); } }),
onLoad: fn => { hooks.load = fn; }, onShow: fn => { hooks.show = fn; },
onHide: fn => { hooks.hide = fn; }, onUnload: fn => { hooks.unload = fn; }, onBackPress: () => {},
getCurrentPages: () => [], setInterval: () => 1, clearInterval: () => {},
uni: { showToast: value => events.toasts.push(value.title), navigateTo: value => events.navigation.push(value.url),
redirectTo: value => events.navigation.push(value.url), reLaunch: value => events.navigation.push(value.url) }
});
if (kind === 'assessment') { hooks.load({}); hooks.show(); }
else hooks.show();
activePages.push(hooks);
return { ...result.page, events };
}
let checks = 0, failures = 0;
async function test(name, callback) {
activePages.splice(0).forEach(hooks => { hooks.hide?.(); });
flow.clearAssessmentFlow(); calls.length = 0;
try { await callback(); checks++; console.log('PASS ' + name); }
catch (error) { failures++; console.error('FAIL ' + name + '\n' + error.stack); }
}
(async () => {
await test('zero counts make only empty related amounts not applicable without changing input', () => {
const value = form(); const before = snapshot(value); save(value);
const progress = service.assessmentProgress('personal');
assert.equal(progress.assetsDone, true, 'empty balances for explicitly absent loans/cards are complete');
assert.equal(progress.percent, 100);
assert.equal(snapshot(value), before);
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit'])
assert.equal(service.getAssessmentInput('personal')[key], '');
});
await test('missing counts and positive counts never infer zero balances', () => {
for (const value of [form({ creditLoanCount: '', creditCardCount: '' }), form({ creditLoanCount: '1', creditCardCount: '1' })]) {
save(value); const progress = service.assessmentProgress('personal');
assert.equal(progress.assetsDone, false); assert.ok(progress.percent < 100);
}
});
await test('all accepted zero-count spellings share the same non-applicable behavior', () => {
for (const count of ['0', '00', '000', '0000']) {
const value = form({ creditLoanCount: count, creditCardCount: count }); save(value);
assert.equal(service.assessmentProgress('personal').assetsDone, true);
assert.equal(service.assessmentProgress('personal').percent, 100);
assert.equal(service.getAssessmentInput('personal').creditLoanCount, count);
}
});
await test('legacy credit-loan zero never receives the new loan scope during restoration', () => {
const saved = new Json({ ...form(), loanCountScope: '' });
const restored = service.formFromInput(saved); save(restored);
assert.equal(restored.creditLoanCount, '0'); assert.equal(restored.loanCountScope, '');
assert.equal(service.assessmentProgress('personal').assetsDone, false);
assert.ok(service.assetValidationMessage(restored).includes('重新确认'));
});
await test('zero count and positive related amounts are rejected without overwriting', () => {
for (const [key, expected] of [['totalDebtBalance', '贷款笔数为 0 时,欠款总余额和贷款月还款金额不能大于 0'],
['monthlyLoanPayment', '贷款笔数为 0 时,欠款总余额和贷款月还款金额不能大于 0'],
['creditCardTotalLimit', '信用卡张数为 0 时,总额度和已使用总额度不能大于 0'],
['creditCardUsedLimit', '信用卡张数为 0 时,总额度和已使用总额度不能大于 0']]) {
const value = form({ [key]: '0.000001' }); const before = snapshot(value);
assert.equal(service.assetValidationMessage(value), expected); save(value);
assert.equal(service.assessmentProgress('personal').assetsDone, false);
assert.equal(snapshot(value), before); assert.equal(service.getAssessmentInput('personal')[key], '0.000001');
}
});
await test('invalid amounts cannot grant a zero-count exemption to sibling empty amounts', () => {
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']) {
const value = form({ [key]: 'bad' }); save(value);
assert.ok(service.assetValidationMessage(value).length > 0);
assert.equal(service.assessmentProgress('personal').assetsDone, false);
assert.equal(service.getAssessmentInput('personal')[key], 'bad');
}
});
await test('entered zero money stays entered and legacy loan scope is not inferred from zero values', () => {
const value = form({ totalDebtBalance: '0.000000', monthlyLoanPayment: '0.00', creditCardTotalLimit: '0', creditCardUsedLimit: '0.000' });
const before = snapshot(value); save(value); assert.equal(service.assessmentProgress('personal').assetsDone, true);
assert.equal(service.assetValidationMessage(value), ''); assert.equal(snapshot(value), before);
const old = service.formFromInput(new Json({ ...value, loanCountScope: '' }));
assert.equal(old.loanCountScope, ''); assert.ok(service.assetValidationMessage(old).length > 0);
});
await test('new submission carries all-loan scope while empty monetary fields remain null', async () => {
const value = new Json({ ...form(), type: 'personal', documents: [], authorizations: [] });
const before = snapshot(value); await records.createAssessment(value);
const body = calls.at(-1)[2]; assert.equal(body.form.loanCountScope, 'all_loans');
assert.equal(body.form.creditLoanCount, 0); assert.equal(body.form.creditCardCount, 0);
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']) assert.equal(body.form[key], null);
assert.equal(snapshot(value), before);
});
await test('serialization preserves entered amounts and never fabricates scope for legacy callers', async () => {
const value = new Json({ ...form({ loanCountScope: '', creditLoanCount: '2', creditCardCount: '1', totalDebtBalance: '1.234567', monthlyLoanPayment: '0', creditCardTotalLimit: '2', creditCardUsedLimit: '0.123456' }), type: 'personal' });
const before = snapshot(value); await records.createAssessment(value);
const body = calls.at(-1)[2]; assert.equal(body.form.loanCountScope, undefined);
assert.equal(body.form.totalDebtBalance, '12345.67'); assert.equal(body.form.monthlyLoanPayment, '0.00');
assert.equal(body.form.creditCardTotalLimit, '20000.00'); assert.equal(body.form.creditCardUsedLimit, '1234.56');
assert.equal(snapshot(value), before);
});
await test('edit restoration preserves old and new scope and every existing amount', async () => {
for (const scope of [undefined, 'all_loans']) {
response = detail({ creditLoanCount: 0, loanCountScope: scope, creditCardCount: 0, totalDebtBalance: '12000.00', monthlyLoanPayment: null, creditCardTotalLimit: '0.00', creditCardUsedLimit: null });
const restored = await records.fetchAssessmentEdit('1');
assert.equal(restored.loanCountScope || '', scope || ''); assert.equal(restored.creditLoanCount, '0');
assert.equal(restored.totalDebtBalance, '1.2'); assert.equal(restored.monthlyLoanPayment, '');
assert.equal(restored.creditCardTotalLimit, '0'); assert.equal(restored.creditCardUsedLimit, '');
}
});
await test('archive labels retain historical credit-loan meaning and name new scope correctly', async () => {
for (const [scope, label] of [[undefined, '信用贷款笔数(旧口径)'], ['all_loans', '贷款笔数']]) {
response = detail({ creditLoanCount: 0, loanCountScope: scope });
const archived = await records.fetchAssessmentDetail('1', '2');
assert.ok(archived.fields.some(item => item.label === label && item.value === '0'));
}
});
await test('historical count requires an explicit page confirmation without clearing any values', () => {
const current = page('assessment');
const old = form({ loanCountScope: '', totalDebtBalance: '1.2', monthlyLoanPayment: '0.03' });
save(old); current.restore('personal'); current.step.value = 2;
const before = snapshot(current.form.value); current.next();
assert.equal(current.step.value, 2); assert.ok(current.events.toasts.at(-1).includes('重新确认'));
assert.equal(typeof current.confirmLoanCountScope, 'function'); current.confirmLoanCountScope();
const after = JSON.parse(snapshot(current.form.value)); after.loanCountScope = '';
assert.equal(JSON.stringify(after), before); assert.equal(current.form.value.loanCountScope, 'all_loans');
current.next(); assert.equal(current.step.value, 2, 'positive money conflict remains after confirming zero loans');
});
await test('page confirmation enables blank balances for old zero counts and remembers scope', () => {
const current = page('assessment'); save(form({ loanCountScope: '' })); current.restore('personal'); current.step.value = 2;
current.next(); assert.equal(current.step.value, 2);
current.confirmLoanCountScope(); current.next(); assert.equal(current.step.value, 3);
assert.equal(service.getAssessmentInput('personal').loanCountScope, 'all_loans');
assert.equal(service.getAssessmentInput('personal').totalDebtBalance, '');
});
await test('confirmation page refuses legacy scope and displays submitted scope without reinterpreting', async () => {
save(form({ loanCountScope: '' })); let current = page('confirm');
const before = calls.length; await current.submit(); assert.equal(calls.length, before);
assert.ok(current.events.toasts.at(-1).includes('重新确认'));
assert.equal(service.getAssessmentInput('personal').step, 2);
save(form()); current = page('confirm');
assert.ok(current.sections.value[2].rows.some(row => row.label === '贷款笔数'));
assert.ok(current.sections.value[2].rows.some(row => row.value.includes('房贷') && row.value.includes('车贷')));
});
await test('confirmation distinguishes exempt empty amounts from entered zero amounts', () => {
save(form()); let current = page('confirm'); let rows = current.sections.value[2].rows;
assert.equal(rows.find(row => row.label === '欠款总余额').value, '不适用(无贷款)');
assert.equal(rows.find(row => row.label === '总额度').value, '不适用(无信用卡)');
save(form({ totalDebtBalance: '0.000000', creditCardTotalLimit: '0.00' })); current = page('confirm'); rows = current.sections.value[2].rows;
assert.equal(rows.find(row => row.label === '欠款总余额').value, '0.000000 万元');
assert.equal(rows.find(row => row.label === '总额度').value, '0.00 万元');
});
console.log((failures ? 'FAIL' : 'PASS') + ': ' + checks + ' passed, ' + failures + ' failed zero-balance UTS/page scenarios; modelCalls=0 businessWrites=0 networkCalls=0');
if (failures) process.exitCode = 1;
})().catch(error => { console.error(error.stack); process.exitCode = 1; });
+31 -7
View File
@@ -9,7 +9,7 @@ export type AssessmentForm = {
name: string, idCard: string, phone: string, city: string, maritalStatus: string,
occupation: string, company: string, monthlyIncome: string, socialSecurity: string,
housingFund: string, socialSecurityBase: string, housingFundBase: string, house: string, car: string, savings: string, creditCard: string,
creditLoanCount: string, totalDebtBalance: string, monthlyLoanPayment: string,
creditLoanCount: string, loanCountScope: string, totalDebtBalance: string, monthlyLoanPayment: string,
creditCardCount: string, creditCardTotalLimit: string, creditCardUsedLimit: string,
otherAssets: string, enterpriseName: string, businessCode: string, annualRevenue: string, operatingYears: string, operatingMonths: string
}
@@ -17,7 +17,7 @@ export type SelectedDocument = { name: string, path: string, documentId: string,
export type DataAuthorization = { key: string, name: string, selected: boolean }
export function emptyAssessmentForm(): AssessmentForm {
return { name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', socialSecurityBase: '', housingFundBase: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '', creditLoanCount: '', totalDebtBalance: '', monthlyLoanPayment: '', creditCardCount: '', creditCardTotalLimit: '', creditCardUsedLimit: '', enterpriseName: '', businessCode: '', annualRevenue: '', operatingYears: '', operatingMonths: '' }
return { name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', socialSecurityBase: '', housingFundBase: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '', creditLoanCount: '', loanCountScope: 'all_loans', totalDebtBalance: '', monthlyLoanPayment: '', creditCardCount: '', creditCardTotalLimit: '', creditCardUsedLimit: '', enterpriseName: '', businessCode: '', annualRevenue: '', operatingYears: '', operatingMonths: '' }
}
export function draftText(saved: UTSJSONObject, key: string): string {
const value = saved[key]
@@ -49,7 +49,7 @@ export function formFromInput(saved: UTSJSONObject): AssessmentForm {
name: draftText(saved, 'name'), idCard: draftText(saved, 'idCard'), phone: draftText(saved, 'phone'), city: draftText(saved, 'city'), maritalStatus: draftText(saved, 'maritalStatus'),
occupation: draftText(saved, 'occupation'), company: draftText(saved, 'company'), monthlyIncome: draftText(saved, 'monthlyIncome'), socialSecurity: draftText(saved, 'socialSecurity'), housingFund: draftText(saved, 'housingFund'), socialSecurityBase: draftText(saved, 'socialSecurityBase'), housingFundBase: draftText(saved, 'housingFundBase'),
house: ownershipFromInput(saved, 'house'), car: ownershipFromInput(saved, 'car'), savings: draftText(saved, 'savings'), creditCard: draftText(saved, 'creditCard'), otherAssets: draftText(saved, 'otherAssets'),
creditLoanCount: draftText(saved, 'creditLoanCount'), totalDebtBalance: draftText(saved, 'totalDebtBalance'), monthlyLoanPayment: draftText(saved, 'monthlyLoanPayment'),
creditLoanCount: draftText(saved, 'creditLoanCount'), loanCountScope: draftText(saved, 'loanCountScope') == 'all_loans' ? 'all_loans' : '', totalDebtBalance: draftText(saved, 'totalDebtBalance'), monthlyLoanPayment: draftText(saved, 'monthlyLoanPayment'),
creditCardCount: draftText(saved, 'creditCardCount'), creditCardTotalLimit: draftText(saved, 'creditCardTotalLimit'), creditCardUsedLimit: draftText(saved, 'creditCardUsedLimit'),
enterpriseName: draftText(saved, 'enterpriseName'), businessCode: draftText(saved, 'businessCode'), annualRevenue: draftText(saved, 'annualRevenue'), operatingYears: draftText(saved, 'operatingYears'), operatingMonths: draftText(saved, 'operatingMonths')
}
@@ -74,7 +74,7 @@ export function contributionValidationMessage(form: AssessmentForm): string {
}
export function assetValidationMessage(form: AssessmentForm): string {
const counts = [form.creditLoanCount, form.creditCardCount]
const countLabels = ['信用贷款笔数', '银行信用卡张数']
const countLabels = ['贷款笔数', '银行信用卡张数']
for (let i = 0; i < counts.length; i++) {
if (counts[i].length > 0 && !/^[0-9]{1,4}$/.test(counts[i])) return countLabels[i] + '请输入非负整数'
}
@@ -83,9 +83,31 @@ export function assetValidationMessage(form: AssessmentForm): string {
for (let i = 0; i < amounts.length; i++) {
if (amounts[i].length > 0 && !wanAmountValid(amounts[i])) return amountLabels[i] + '范围为 0–99999.999999 万元,最多六位小数'
}
if (form.creditLoanCount.length > 0 && form.loanCountScope != 'all_loans') return '历史贷款笔数仅统计信用贷款,请按全部未结清贷款重新确认'
if (form.loanCountScope == 'all_loans' && /^0{1,4}$/.test(form.creditLoanCount)
&& [form.totalDebtBalance, form.monthlyLoanPayment].some((value) => value.length > 0 && wanToCents(value) > 0)) return '贷款笔数为 0 时,欠款总余额和贷款月还款金额不能大于 0'
if (/^0{1,4}$/.test(form.creditCardCount)
&& [form.creditCardTotalLimit, form.creditCardUsedLimit].some((value) => value.length > 0 && wanToCents(value) > 0)) return '信用卡张数为 0 时,总额度和已使用总额度不能大于 0'
if (form.creditCardTotalLimit.length > 0 && form.creditCardUsedLimit.length > 0 && wanToCents(form.creditCardUsedLimit) > wanToCents(form.creditCardTotalLimit)) return '信用卡已使用额度不能大于总额度'
return ''
}
// 仅计算空金额是否适用;不补零、不更改任何已填内容,矛盾或无效金额不豁免同组字段。
export function assetNotApplicableFields(form: AssessmentForm): string[] {
const fields: string[] = []
const loanAmounts = [form.totalDebtBalance, form.monthlyLoanPayment]
if (form.loanCountScope == 'all_loans' && /^0{1,4}$/.test(form.creditLoanCount)
&& loanAmounts.every((value) => value.length == 0 || (wanAmountValid(value) && wanToCents(value) == 0))) {
if (form.totalDebtBalance.length == 0) fields.push('totalDebtBalance')
if (form.monthlyLoanPayment.length == 0) fields.push('monthlyLoanPayment')
}
const cardAmounts = [form.creditCardTotalLimit, form.creditCardUsedLimit]
if (/^0{1,4}$/.test(form.creditCardCount)
&& cardAmounts.every((value) => value.length == 0 || (wanAmountValid(value) && wanToCents(value) == 0))) {
if (form.creditCardTotalLimit.length == 0) fields.push('creditCardTotalLimit')
if (form.creditCardUsedLimit.length == 0) fields.push('creditCardUsedLimit')
}
return fields
}
export function documentsFromInput(saved: UTSJSONObject): SelectedDocument[] {
const results: SelectedDocument[] = []
const items = saved['documents'] as UTSJSONObject[] | null
@@ -106,7 +128,7 @@ export function setAssessmentInput(type: string, step: number, form: AssessmentF
type, step, editAssessmentId, baseRevisionId, moneyUnit: 'wan', name: form.name, idCard: form.idCard, phone: form.phone, city: form.city, maritalStatus: form.maritalStatus,
occupation: form.occupation, company: form.company, monthlyIncome: form.monthlyIncome, socialSecurity: form.socialSecurity, housingFund: form.housingFund, socialSecurityBase: form.socialSecurityBase, housingFundBase: form.housingFundBase,
house: form.house, car: form.car, savings: form.savings, creditCard: form.creditCard, otherAssets: form.otherAssets,
creditLoanCount: form.creditLoanCount, totalDebtBalance: form.totalDebtBalance, monthlyLoanPayment: form.monthlyLoanPayment,
creditLoanCount: form.creditLoanCount, loanCountScope: form.loanCountScope, totalDebtBalance: form.totalDebtBalance, monthlyLoanPayment: form.monthlyLoanPayment,
creditCardCount: form.creditCardCount, creditCardTotalLimit: form.creditCardTotalLimit, creditCardUsedLimit: form.creditCardUsedLimit,
enterpriseName: form.enterpriseName, businessCode: form.businessCode, annualRevenue: form.annualRevenue, operatingYears: form.operatingYears, operatingMonths: form.operatingMonths,
documents: files, authorizations: grants, documentConsent, dataConsent
@@ -123,9 +145,11 @@ export function assessmentProgress(type: string): AssessmentProgress {
if (saved == null) return { percent: 0, basicDone: false, incomeDone: false, assetsDone: false, authorizationSelected: false }
const basic = type == 'enterprise' ? ['enterpriseName', 'businessCode', 'name', 'phone', 'city'] : ['name', 'idCard', 'phone', 'city', 'maritalStatus']
const income = type == 'enterprise' ? ['occupation', 'operatingMonths', 'annualRevenue'] : ['occupation', 'company', 'monthlyIncome', 'socialSecurity', 'housingFund']
const assets = ['house', 'car', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit']
const form = formFromInput(saved)
const notApplicable = assetNotApplicableFields(form)
const assets = ['house', 'car', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit'].filter((key) => !notApplicable.includes(key))
const keys = basic.concat(income).concat(assets)
const filled = keys.filter((key) => draftText(saved, key).trim().length > 0).length
const grants = saved['authorizations'] as string[] | null
return { percent: Math.round(filled / keys.length * 100), basicDone: basic.every((key) => draftText(saved, key).length > 0), incomeDone: income.every((key) => draftText(saved, key).length > 0), assetsDone: assets.every((key) => draftText(saved, key).trim().length > 0) && assetValidationMessage(formFromInput(saved)).length == 0, authorizationSelected: documentsFromInput(saved).length > 0 || (grants != null && grants.length > 0) }
return { percent: Math.round(filled / keys.length * 100), basicDone: basic.every((key) => draftText(saved, key).length > 0), incomeDone: income.every((key) => draftText(saved, key).length > 0), assetsDone: assets.every((key) => draftText(saved, key).trim().length > 0) && assetValidationMessage(form).length == 0, authorizationSelected: documentsFromInput(saved).length > 0 || (grants != null && grants.length > 0) }
}
+76
View File
@@ -0,0 +1,76 @@
// 由 scripts/generate-city-options.py 生成;来源和覆盖口径见 docs/data/city-options.json。
export type CityOption = { name: string, code: string }
export type CityRegion = { name: string, cities: CityOption[] }
export const cityRegions: CityRegion[] = [
{"name": "北京市", "cities": [{"name": "北京市", "code": "110100"}]},
{"name": "天津市", "cities": [{"name": "天津市", "code": "120100"}]},
{"name": "河北省", "cities": [{"name": "石家庄市", "code": "130100"}, {"name": "唐山市", "code": "130200"}, {"name": "秦皇岛市", "code": "130300"}, {"name": "邯郸市", "code": "130400"}, {"name": "邢台市", "code": "130500"}, {"name": "保定市", "code": "130600"}, {"name": "张家口市", "code": "130700"}, {"name": "承德市", "code": "130800"}, {"name": "沧州市", "code": "130900"}, {"name": "廊坊市", "code": "131000"}, {"name": "衡水市", "code": "131100"}]},
{"name": "山西省", "cities": [{"name": "太原市", "code": "140100"}, {"name": "大同市", "code": "140200"}, {"name": "阳泉市", "code": "140300"}, {"name": "长治市", "code": "140400"}, {"name": "晋城市", "code": "140500"}, {"name": "朔州市", "code": "140600"}, {"name": "晋中市", "code": "140700"}, {"name": "运城市", "code": "140800"}, {"name": "忻州市", "code": "140900"}, {"name": "临汾市", "code": "141000"}, {"name": "吕梁市", "code": "141100"}]},
{"name": "内蒙古自治区", "cities": [{"name": "呼和浩特市", "code": "150100"}, {"name": "包头市", "code": "150200"}, {"name": "乌海市", "code": "150300"}, {"name": "赤峰市", "code": "150400"}, {"name": "通辽市", "code": "150500"}, {"name": "鄂尔多斯市", "code": "150600"}, {"name": "呼伦贝尔市", "code": "150700"}, {"name": "巴彦淖尔市", "code": "150800"}, {"name": "乌兰察布市", "code": "150900"}, {"name": "兴安盟", "code": "152200"}, {"name": "锡林郭勒盟", "code": "152500"}, {"name": "阿拉善盟", "code": "152900"}]},
{"name": "辽宁省", "cities": [{"name": "沈阳市", "code": "210100"}, {"name": "大连市", "code": "210200"}, {"name": "鞍山市", "code": "210300"}, {"name": "抚顺市", "code": "210400"}, {"name": "本溪市", "code": "210500"}, {"name": "丹东市", "code": "210600"}, {"name": "锦州市", "code": "210700"}, {"name": "营口市", "code": "210800"}, {"name": "阜新市", "code": "210900"}, {"name": "辽阳市", "code": "211000"}, {"name": "盘锦市", "code": "211100"}, {"name": "铁岭市", "code": "211200"}, {"name": "朝阳市", "code": "211300"}, {"name": "葫芦岛市", "code": "211400"}]},
{"name": "吉林省", "cities": [{"name": "长春市", "code": "220100"}, {"name": "吉林市", "code": "220200"}, {"name": "四平市", "code": "220300"}, {"name": "辽源市", "code": "220400"}, {"name": "通化市", "code": "220500"}, {"name": "白山市", "code": "220600"}, {"name": "松原市", "code": "220700"}, {"name": "白城市", "code": "220800"}, {"name": "延边朝鲜族自治州", "code": "222400"}]},
{"name": "黑龙江省", "cities": [{"name": "哈尔滨市", "code": "230100"}, {"name": "齐齐哈尔市", "code": "230200"}, {"name": "鸡西市", "code": "230300"}, {"name": "鹤岗市", "code": "230400"}, {"name": "双鸭山市", "code": "230500"}, {"name": "大庆市", "code": "230600"}, {"name": "伊春市", "code": "230700"}, {"name": "佳木斯市", "code": "230800"}, {"name": "七台河市", "code": "230900"}, {"name": "牡丹江市", "code": "231000"}, {"name": "黑河市", "code": "231100"}, {"name": "绥化市", "code": "231200"}, {"name": "大兴安岭地区", "code": "232700"}]},
{"name": "上海市", "cities": [{"name": "上海市", "code": "310100"}]},
{"name": "江苏省", "cities": [{"name": "南京市", "code": "320100"}, {"name": "无锡市", "code": "320200"}, {"name": "徐州市", "code": "320300"}, {"name": "常州市", "code": "320400"}, {"name": "苏州市", "code": "320500"}, {"name": "南通市", "code": "320600"}, {"name": "连云港市", "code": "320700"}, {"name": "淮安市", "code": "320800"}, {"name": "盐城市", "code": "320900"}, {"name": "扬州市", "code": "321000"}, {"name": "镇江市", "code": "321100"}, {"name": "泰州市", "code": "321200"}, {"name": "宿迁市", "code": "321300"}]},
{"name": "浙江省", "cities": [{"name": "杭州市", "code": "330100"}, {"name": "宁波市", "code": "330200"}, {"name": "温州市", "code": "330300"}, {"name": "嘉兴市", "code": "330400"}, {"name": "湖州市", "code": "330500"}, {"name": "绍兴市", "code": "330600"}, {"name": "金华市", "code": "330700"}, {"name": "衢州市", "code": "330800"}, {"name": "舟山市", "code": "330900"}, {"name": "台州市", "code": "331000"}, {"name": "丽水市", "code": "331100"}]},
{"name": "安徽省", "cities": [{"name": "合肥市", "code": "340100"}, {"name": "芜湖市", "code": "340200"}, {"name": "蚌埠市", "code": "340300"}, {"name": "淮南市", "code": "340400"}, {"name": "马鞍山市", "code": "340500"}, {"name": "淮北市", "code": "340600"}, {"name": "铜陵市", "code": "340700"}, {"name": "安庆市", "code": "340800"}, {"name": "黄山市", "code": "341000"}, {"name": "滁州市", "code": "341100"}, {"name": "阜阳市", "code": "341200"}, {"name": "宿州市", "code": "341300"}, {"name": "六安市", "code": "341500"}, {"name": "亳州市", "code": "341600"}, {"name": "池州市", "code": "341700"}, {"name": "宣城市", "code": "341800"}]},
{"name": "福建省", "cities": [{"name": "福州市", "code": "350100"}, {"name": "厦门市", "code": "350200"}, {"name": "莆田市", "code": "350300"}, {"name": "三明市", "code": "350400"}, {"name": "泉州市", "code": "350500"}, {"name": "漳州市", "code": "350600"}, {"name": "南平市", "code": "350700"}, {"name": "龙岩市", "code": "350800"}, {"name": "宁德市", "code": "350900"}]},
{"name": "江西省", "cities": [{"name": "南昌市", "code": "360100"}, {"name": "景德镇市", "code": "360200"}, {"name": "萍乡市", "code": "360300"}, {"name": "九江市", "code": "360400"}, {"name": "新余市", "code": "360500"}, {"name": "鹰潭市", "code": "360600"}, {"name": "赣州市", "code": "360700"}, {"name": "吉安市", "code": "360800"}, {"name": "宜春市", "code": "360900"}, {"name": "抚州市", "code": "361000"}, {"name": "上饶市", "code": "361100"}]},
{"name": "山东省", "cities": [{"name": "济南市", "code": "370100"}, {"name": "青岛市", "code": "370200"}, {"name": "淄博市", "code": "370300"}, {"name": "枣庄市", "code": "370400"}, {"name": "东营市", "code": "370500"}, {"name": "烟台市", "code": "370600"}, {"name": "潍坊市", "code": "370700"}, {"name": "济宁市", "code": "370800"}, {"name": "泰安市", "code": "370900"}, {"name": "威海市", "code": "371000"}, {"name": "日照市", "code": "371100"}, {"name": "临沂市", "code": "371300"}, {"name": "德州市", "code": "371400"}, {"name": "聊城市", "code": "371500"}, {"name": "滨州市", "code": "371600"}, {"name": "菏泽市", "code": "371700"}]},
{"name": "河南省", "cities": [{"name": "郑州市", "code": "410100"}, {"name": "开封市", "code": "410200"}, {"name": "洛阳市", "code": "410300"}, {"name": "平顶山市", "code": "410400"}, {"name": "安阳市", "code": "410500"}, {"name": "鹤壁市", "code": "410600"}, {"name": "新乡市", "code": "410700"}, {"name": "焦作市", "code": "410800"}, {"name": "濮阳市", "code": "410900"}, {"name": "许昌市", "code": "411000"}, {"name": "漯河市", "code": "411100"}, {"name": "三门峡市", "code": "411200"}, {"name": "南阳市", "code": "411300"}, {"name": "商丘市", "code": "411400"}, {"name": "信阳市", "code": "411500"}, {"name": "周口市", "code": "411600"}, {"name": "驻马店市", "code": "411700"}, {"name": "济源市", "code": "419001"}]},
{"name": "湖北省", "cities": [{"name": "武汉市", "code": "420100"}, {"name": "黄石市", "code": "420200"}, {"name": "十堰市", "code": "420300"}, {"name": "宜昌市", "code": "420500"}, {"name": "襄阳市", "code": "420600"}, {"name": "鄂州市", "code": "420700"}, {"name": "荆门市", "code": "420800"}, {"name": "孝感市", "code": "420900"}, {"name": "荆州市", "code": "421000"}, {"name": "黄冈市", "code": "421100"}, {"name": "咸宁市", "code": "421200"}, {"name": "随州市", "code": "421300"}, {"name": "恩施土家族苗族自治州", "code": "422800"}, {"name": "仙桃市", "code": "429004"}, {"name": "潜江市", "code": "429005"}, {"name": "天门市", "code": "429006"}, {"name": "神农架林区", "code": "429021"}]},
{"name": "湖南省", "cities": [{"name": "长沙市", "code": "430100"}, {"name": "株洲市", "code": "430200"}, {"name": "湘潭市", "code": "430300"}, {"name": "衡阳市", "code": "430400"}, {"name": "邵阳市", "code": "430500"}, {"name": "岳阳市", "code": "430600"}, {"name": "常德市", "code": "430700"}, {"name": "张家界市", "code": "430800"}, {"name": "益阳市", "code": "430900"}, {"name": "郴州市", "code": "431000"}, {"name": "永州市", "code": "431100"}, {"name": "怀化市", "code": "431200"}, {"name": "娄底市", "code": "431300"}, {"name": "湘西土家族苗族自治州", "code": "433100"}]},
{"name": "广东省", "cities": [{"name": "广州市", "code": "440100"}, {"name": "韶关市", "code": "440200"}, {"name": "深圳市", "code": "440300"}, {"name": "珠海市", "code": "440400"}, {"name": "汕头市", "code": "440500"}, {"name": "佛山市", "code": "440600"}, {"name": "江门市", "code": "440700"}, {"name": "湛江市", "code": "440800"}, {"name": "茂名市", "code": "440900"}, {"name": "肇庆市", "code": "441200"}, {"name": "惠州市", "code": "441300"}, {"name": "梅州市", "code": "441400"}, {"name": "汕尾市", "code": "441500"}, {"name": "河源市", "code": "441600"}, {"name": "阳江市", "code": "441700"}, {"name": "清远市", "code": "441800"}, {"name": "东莞市", "code": "441900"}, {"name": "中山市", "code": "442000"}, {"name": "潮州市", "code": "445100"}, {"name": "揭阳市", "code": "445200"}, {"name": "云浮市", "code": "445300"}]},
{"name": "广西壮族自治区", "cities": [{"name": "南宁市", "code": "450100"}, {"name": "柳州市", "code": "450200"}, {"name": "桂林市", "code": "450300"}, {"name": "梧州市", "code": "450400"}, {"name": "北海市", "code": "450500"}, {"name": "防城港市", "code": "450600"}, {"name": "钦州市", "code": "450700"}, {"name": "贵港市", "code": "450800"}, {"name": "玉林市", "code": "450900"}, {"name": "百色市", "code": "451000"}, {"name": "贺州市", "code": "451100"}, {"name": "河池市", "code": "451200"}, {"name": "来宾市", "code": "451300"}, {"name": "崇左市", "code": "451400"}]},
{"name": "海南省", "cities": [{"name": "海口市", "code": "460100"}, {"name": "三亚市", "code": "460200"}, {"name": "三沙市", "code": "460300"}, {"name": "儋州市", "code": "460400"}, {"name": "五指山市", "code": "469001"}, {"name": "琼海市", "code": "469002"}, {"name": "文昌市", "code": "469005"}, {"name": "万宁市", "code": "469006"}, {"name": "东方市", "code": "469007"}, {"name": "定安县", "code": "469021"}, {"name": "屯昌县", "code": "469022"}, {"name": "澄迈县", "code": "469023"}, {"name": "临高县", "code": "469024"}, {"name": "白沙黎族自治县", "code": "469025"}, {"name": "昌江黎族自治县", "code": "469026"}, {"name": "乐东黎族自治县", "code": "469027"}, {"name": "陵水黎族自治县", "code": "469028"}, {"name": "保亭黎族苗族自治县", "code": "469029"}, {"name": "琼中黎族苗族自治县", "code": "469030"}]},
{"name": "重庆市", "cities": [{"name": "重庆市", "code": "500100"}]},
{"name": "四川省", "cities": [{"name": "成都市", "code": "510100"}, {"name": "自贡市", "code": "510300"}, {"name": "攀枝花市", "code": "510400"}, {"name": "泸州市", "code": "510500"}, {"name": "德阳市", "code": "510600"}, {"name": "绵阳市", "code": "510700"}, {"name": "广元市", "code": "510800"}, {"name": "遂宁市", "code": "510900"}, {"name": "内江市", "code": "511000"}, {"name": "乐山市", "code": "511100"}, {"name": "南充市", "code": "511300"}, {"name": "眉山市", "code": "511400"}, {"name": "宜宾市", "code": "511500"}, {"name": "广安市", "code": "511600"}, {"name": "达州市", "code": "511700"}, {"name": "雅安市", "code": "511800"}, {"name": "巴中市", "code": "511900"}, {"name": "资阳市", "code": "512000"}, {"name": "阿坝藏族羌族自治州", "code": "513200"}, {"name": "甘孜藏族自治州", "code": "513300"}, {"name": "凉山彝族自治州", "code": "513400"}]},
{"name": "贵州省", "cities": [{"name": "贵阳市", "code": "520100"}, {"name": "六盘水市", "code": "520200"}, {"name": "遵义市", "code": "520300"}, {"name": "安顺市", "code": "520400"}, {"name": "毕节市", "code": "520500"}, {"name": "铜仁市", "code": "520600"}, {"name": "黔西南布依族苗族自治州", "code": "522300"}, {"name": "黔东南苗族侗族自治州", "code": "522600"}, {"name": "黔南布依族苗族自治州", "code": "522700"}]},
{"name": "云南省", "cities": [{"name": "昆明市", "code": "530100"}, {"name": "曲靖市", "code": "530300"}, {"name": "玉溪市", "code": "530400"}, {"name": "保山市", "code": "530500"}, {"name": "昭通市", "code": "530600"}, {"name": "丽江市", "code": "530700"}, {"name": "普洱市", "code": "530800"}, {"name": "临沧市", "code": "530900"}, {"name": "楚雄彝族自治州", "code": "532300"}, {"name": "红河哈尼族彝族自治州", "code": "532500"}, {"name": "文山壮族苗族自治州", "code": "532600"}, {"name": "西双版纳傣族自治州", "code": "532800"}, {"name": "大理白族自治州", "code": "532900"}, {"name": "德宏傣族景颇族自治州", "code": "533100"}, {"name": "怒江傈僳族自治州", "code": "533300"}, {"name": "迪庆藏族自治州", "code": "533400"}]},
{"name": "西藏自治区", "cities": [{"name": "拉萨市", "code": "540100"}, {"name": "日喀则市", "code": "540200"}, {"name": "昌都市", "code": "540300"}, {"name": "林芝市", "code": "540400"}, {"name": "山南市", "code": "540500"}, {"name": "那曲市", "code": "540600"}, {"name": "阿里地区", "code": "542500"}]},
{"name": "陕西省", "cities": [{"name": "西安市", "code": "610100"}, {"name": "铜川市", "code": "610200"}, {"name": "宝鸡市", "code": "610300"}, {"name": "咸阳市", "code": "610400"}, {"name": "渭南市", "code": "610500"}, {"name": "延安市", "code": "610600"}, {"name": "汉中市", "code": "610700"}, {"name": "榆林市", "code": "610800"}, {"name": "安康市", "code": "610900"}, {"name": "商洛市", "code": "611000"}]},
{"name": "甘肃省", "cities": [{"name": "兰州市", "code": "620100"}, {"name": "嘉峪关市", "code": "620200"}, {"name": "金昌市", "code": "620300"}, {"name": "白银市", "code": "620400"}, {"name": "天水市", "code": "620500"}, {"name": "武威市", "code": "620600"}, {"name": "张掖市", "code": "620700"}, {"name": "平凉市", "code": "620800"}, {"name": "酒泉市", "code": "620900"}, {"name": "庆阳市", "code": "621000"}, {"name": "定西市", "code": "621100"}, {"name": "陇南市", "code": "621200"}, {"name": "临夏回族自治州", "code": "622900"}, {"name": "甘南藏族自治州", "code": "623000"}]},
{"name": "青海省", "cities": [{"name": "西宁市", "code": "630100"}, {"name": "海东市", "code": "630200"}, {"name": "海北藏族自治州", "code": "632200"}, {"name": "黄南藏族自治州", "code": "632300"}, {"name": "海南藏族自治州", "code": "632500"}, {"name": "果洛藏族自治州", "code": "632600"}, {"name": "玉树藏族自治州", "code": "632700"}, {"name": "海西蒙古族藏族自治州", "code": "632800"}]},
{"name": "宁夏回族自治区", "cities": [{"name": "银川市", "code": "640100"}, {"name": "石嘴山市", "code": "640200"}, {"name": "吴忠市", "code": "640300"}, {"name": "固原市", "code": "640400"}, {"name": "中卫市", "code": "640500"}]},
{"name": "新疆维吾尔自治区", "cities": [{"name": "乌鲁木齐市", "code": "650100"}, {"name": "克拉玛依市", "code": "650200"}, {"name": "吐鲁番市", "code": "650400"}, {"name": "哈密市", "code": "650500"}, {"name": "昌吉回族自治州", "code": "652300"}, {"name": "博尔塔拉蒙古自治州", "code": "652700"}, {"name": "巴音郭楞蒙古自治州", "code": "652800"}, {"name": "阿克苏地区", "code": "652900"}, {"name": "克孜勒苏柯尔克孜自治州", "code": "653000"}, {"name": "喀什地区", "code": "653100"}, {"name": "和田地区", "code": "653200"}, {"name": "伊犁哈萨克自治州", "code": "654000"}, {"name": "塔城地区", "code": "654200"}, {"name": "阿勒泰地区", "code": "654300"}, {"name": "石河子市", "code": "659001"}, {"name": "阿拉尔市", "code": "659002"}, {"name": "图木舒克市", "code": "659003"}, {"name": "五家渠市", "code": "659004"}, {"name": "北屯市", "code": "659005"}, {"name": "铁门关市", "code": "659006"}, {"name": "双河市", "code": "659007"}, {"name": "可克达拉市", "code": "659008"}, {"name": "昆玉市", "code": "659009"}, {"name": "胡杨河市", "code": "659010"}, {"name": "新星市", "code": "659011"}, {"name": "白杨市", "code": "659012"}]},
{"name": "台湾省", "cities": [{"name": "台北市", "code": ""}, {"name": "新北市", "code": ""}, {"name": "桃园市", "code": ""}, {"name": "台中市", "code": ""}, {"name": "台南市", "code": ""}, {"name": "高雄市", "code": ""}, {"name": "基隆市", "code": ""}, {"name": "新竹市", "code": ""}, {"name": "嘉义市", "code": ""}, {"name": "新竹县", "code": ""}, {"name": "苗栗县", "code": ""}, {"name": "彰化县", "code": ""}, {"name": "南投县", "code": ""}, {"name": "云林县", "code": ""}, {"name": "嘉义县", "code": ""}, {"name": "屏东县", "code": ""}, {"name": "宜兰县", "code": ""}, {"name": "花莲县", "code": ""}, {"name": "台东县", "code": ""}, {"name": "澎湖县", "code": ""}, {"name": "金门县", "code": ""}, {"name": "连江县", "code": ""}]},
{"name": "香港特别行政区", "cities": [{"name": "香港特别行政区", "code": ""}]},
{"name": "澳门特别行政区", "cities": [{"name": "澳门特别行政区", "code": ""}]}
]
export const provinceOptions: CityOption[] = [{"name": "北京市", "code": "110000"}, {"name": "天津市", "code": "120000"}, {"name": "河北省", "code": "130000"}, {"name": "山西省", "code": "140000"}, {"name": "内蒙古自治区", "code": "150000"}, {"name": "辽宁省", "code": "210000"}, {"name": "吉林省", "code": "220000"}, {"name": "黑龙江省", "code": "230000"}, {"name": "上海市", "code": "310000"}, {"name": "江苏省", "code": "320000"}, {"name": "浙江省", "code": "330000"}, {"name": "安徽省", "code": "340000"}, {"name": "福建省", "code": "350000"}, {"name": "江西省", "code": "360000"}, {"name": "山东省", "code": "370000"}, {"name": "河南省", "code": "410000"}, {"name": "湖北省", "code": "420000"}, {"name": "湖南省", "code": "430000"}, {"name": "广东省", "code": "440000"}, {"name": "广西壮族自治区", "code": "450000"}, {"name": "海南省", "code": "460000"}, {"name": "重庆市", "code": "500000"}, {"name": "四川省", "code": "510000"}, {"name": "贵州省", "code": "520000"}, {"name": "云南省", "code": "530000"}, {"name": "西藏自治区", "code": "540000"}, {"name": "陕西省", "code": "610000"}, {"name": "甘肃省", "code": "620000"}, {"name": "青海省", "code": "630000"}, {"name": "宁夏回族自治区", "code": "640000"}, {"name": "新疆维吾尔自治区", "code": "650000"}, {"name": "台湾省", "code": "710000"}, {"name": "香港特别行政区", "code": "810000"}, {"name": "澳门特别行政区", "code": "820000"}]
export const citySelection = (name: string): number[] => {
for (let province = 0; province < cityRegions.length; province++) {
const cities = cityRegions[province].cities
for (let city = 0; city < cities.length; city++) {
if (cities[city].name == name) return [province, city]
}
}
return [-1, -1]
}
export const cityCodeForName = (name: string): string | null => {
const selected = citySelection(name)
if (selected[0] < 0) return null
const code = cityRegions[selected[0]].cities[selected[1]].code
return code.length > 0 ? code : null
}
export const cityDisplayName = (name: string): string => {
const selected = citySelection(name)
if (selected[0] < 0) return name
const province = cityRegions[selected[0]].name
return province == name ? name : province + ' / ' + name
}
export const provinceCodeForName = (name: string): string | null => {
const index = provinceOptions.findIndex((item: CityOption): boolean => item.name == name)
return index < 0 ? null : provinceOptions[index].code
}
// 仅供历史城市资料回填为省份;未知名称不猜测。
export const provinceForCity = (name: string): string => {
if (provinceCodeForName(name) != null) return name
const selected = citySelection(name)
return selected[0] < 0 ? '' : cityRegions[selected[0]].name
}
+1 -1
View File
@@ -23,7 +23,7 @@ export function matchReturnPath(page: string, assessmentId: string, rawIds: stri
let ids: string[] = []
try { ids = parseCompareIds(rawIds) } catch (err) { return '' }
if (assessmentId.length == 0 && (revisionId.length > 0 || ids.length > 0)) return ''
if (page == 'matches' && !['eligible','ineligible','unknown'].includes(group)) return ''
if (page == 'matches' && !['all','eligible','ineligible','unknown'].includes(group)) return ''
if (page == 'product' && (!validRecordId(productId) || !['explanation','details','conditions'].includes(tab) || (assessmentId.length == 0 && tab == 'explanation') || !['','matches','product','compare','report','next-step','archive'].includes(from))) return ''
const query = assessmentId.length > 0 ? compareQuery(assessmentId, ids, revisionId) : '?'
if (page == 'matches') return '/packages/finance/matches/matches' + query + '&group=' + group