feat: 新增高铁游与车宿徒步游技能及相关基础设施

This commit is contained in:
yangxiangyuan
2026-09-30 13:47:51 +08:00
parent bbc7ca6523
commit ac475b6659
66 changed files with 14572 additions and 0 deletions
@@ -0,0 +1,112 @@
/* ============================================================
* debug_rail_replay_wuyuan.js —— 用真实失败案例回放,验证修复有效
*
* 背景:2026-09-30 生成「婺源」时返回 payload_invalid,唯一 error 是
* 「第一天缺「夜逛」环节(night_stroll)」——但 AI 其实排了 19:00 的夜逛。
* 根因是校验器写死了 day.label 含「第一天」,而 AI 标的是「周六/周日」。
*
* 本脚本把那次失败响应的 payload 原样取出来,走一遍「修复 → 校验」,
* 用来证明修复后不再误报(同时也顺带检查其余 warning 是否已消除)。
*
* 用法:
* node dev_test_scripts/debug/debug_rail_replay_wuyuan.js 只回放校验,不写任何文件
* node dev_test_scripts/debug/debug_rail_replay_wuyuan.js --save 额外把这份结果写回
* 技能私有 data/last_result.json,
* 这样用户可在页面上点「载入最近一次生成」
* 直接看到并导出这份婺源行程(不必重烧 token)
*
* 为什么需要 --save:服务端只在「校验 0 errors」时才落盘,那次因误判被丢弃,
* 用户 4 分钟的生成白费了。这个开关是给「已经拿到完整 payload、只想补落盘」的补救场景用的。
* ============================================================ */
const fs = require('fs')
const path = require('path')
const os = require('os')
const SRC = path.join(
os.homedir(), 'AppData', 'Roaming', 'Trae CN', 'User', 'workspaceStorage',
'540d38674b58f7802a478abf6956abc7', 'long-text', '6ab1e0ff40ac82e5fecf1de9',
'munlta58-u92r', '当前状态__失败__....txt'
)
const SKILL = path.join(process.cwd(), 'src', 'server', 'thought_lab', 'labs', 'travel_citytour', 'skills', 'sh_2d_rail_hike')
const skillIndex = require(path.join(SKILL, 'index.js'))
const { validatePayload } = require(path.join(SKILL, 'validate.js'))
const { repairPayload } = require(path.join(SKILL, 'repair.js'))
if (!fs.existsSync(SRC)) {
console.log('找不到失败响应文件(可能已被清理):' + SRC)
process.exit(0)
}
const raw = fs.readFileSync(SRC, 'utf-8')
// 响应体从「返回结果」后的第一个 '{' 开始,到「错误信息」之前结束
const startMark = raw.indexOf('返回结果')
const start = raw.indexOf('{', startMark)
const endMark = raw.indexOf('错误信息', start)
const text = raw.slice(start, endMark > 0 ? endMark : undefined).trim()
let envelope = null
try {
envelope = JSON.parse(text)
} catch (e) {
// 兜底:按大括号配平截取
let depth = 0, endIdx = -1
for (let i = start; i < raw.length; i++) {
if (raw[i] === '{') depth++
else if (raw[i] === '}') { depth--; if (depth === 0) { endIdx = i; break } }
}
envelope = JSON.parse(raw.slice(start, endIdx + 1))
}
const payload = envelope.payload
const bundle = skillIndex.loadBundle()
console.log('===== 婺源失败案例回放 =====')
console.log('原文 error:' + JSON.stringify(envelope.check && envelope.check.errors))
console.log('day.label 实际写法:' + ((payload.timeline && payload.timeline.days) || []).map((d) => JSON.stringify(d.label)).join(' / '))
console.log('AI 是否排了夜逛行:' + ((payload.timeline && payload.timeline.days) || [])
.reduce((a, d) => a.concat(d.rows || []), [])
.some((r) => r.anchor_id === 'night_stroll'))
console.log('')
const repaired = repairPayload(JSON.parse(JSON.stringify(payload)), bundle)
const chk = validatePayload(repaired.payload, bundle)
console.log('— 修复说明 —')
repaired.fixes.forEach((f) => console.log(' · ' + f))
console.log('')
console.log('— 修复后校验结果 —')
console.log('errors : ' + chk.errors.length + (chk.errors.length ? '\n ✗ ' + chk.errors.join('\n ✗ ') : ''))
console.log('warnings: ' + chk.warnings.length + (chk.warnings.length ? '\n ! ' + chk.warnings.join('\n ! ') : ''))
console.log('')
console.log(chk.errors.length === 0
? '✓ 回放通过:这份真实失败的 payload 修复后 0 errors,不再被误判'
: '✗ 仍有 error,需继续排查')
// --save:把修复后的结果补落盘,供页面「载入最近一次生成」使用
if (process.argv.indexOf('--save') >= 0) {
if (chk.errors.length) {
console.log('\n⚠ 仍有 error,拒绝落盘(避免把不合格结果写进 last_result)')
process.exit(1)
}
const outEnvelope = {
ok: true,
payload: repaired.payload,
report_spec: bundle.report_spec || {},
destination_override: null,
check: chk,
meta: Object.assign({}, envelope.meta, {
repairs: repaired.fixes,
recovered_from: 'payload_invalid 误判(校验器已修复)'
})
}
const dataDir = path.join(SKILL, 'data')
fs.mkdirSync(dataDir, { recursive: true })
const out = path.join(dataDir, 'last_result.json')
fs.writeFileSync(out, JSON.stringify(outEnvelope), 'utf-8')
console.log('\n✓ 已补落盘:' + out)
console.log(' 下一步:打开本技能页面 → 点「载入最近一次生成」即可查看/导出这份婺源行程')
}
process.exit(chk.errors.length === 0 ? 0 : 1)