diff --git a/.dbg/device-id-missing.env b/.dbg/device-id-missing.env new file mode 100644 index 0000000..557c688 --- /dev/null +++ b/.dbg/device-id-missing.env @@ -0,0 +1,2 @@ +DEBUG_SERVER_URL=http://127.0.0.1:7777/event +DEBUG_SESSION_ID=device-id-missing diff --git a/debug-device-id-missing.md b/debug-device-id-missing.md new file mode 100644 index 0000000..beb7952 --- /dev/null +++ b/debug-device-id-missing.md @@ -0,0 +1,28 @@ +# Debug Session: device-id-missing +- **Status**: [OPEN] +- **Issue**: 数据记录中的设备 ID 当前全部未显示,界面只看到“机器ID:”后没有具体值。 +- **Debug Server**: http://127.0.0.1:7777/event +- **Log File**: .dbg/trae-debug-log-device-id-missing.ndjson + +## Reproduction Steps +1. 使用售票机完成一次售票。 +2. 使用闸机完成一次进站或出站。 +3. 打开后台或票据时间线页面查看“机器ID”字段。 +4. 观察“机器ID:”后方是否为空。 + +## Hypotheses & Verification +| ID | Hypothesis | Likelihood | Effort | Evidence | +|----|------------|------------|--------|----------| +| A | 后端接口返回的事件对象里根本没有 `device` / `detail.device` 字段 | High | Low | Pending | +| B | 前端格式化函数读到了值,但后续字符串拼接或编码问题把值丢掉了 | High | Low | Pending | +| C | 售票与检票两类事件的数据结构不同,当前前端只兼容了其中一类 | Med | Low | Pending | +| D | 设备端 Lua 确实上报了 ID,但服务端在写库或对外返回时被覆盖成空串 | Med | Med | Pending | +| E | 页面实际运行的不是当前工作区这份前端代码,导致本地修改未生效 | Med | Med | Pending | + +## Log Evidence +- 2026-07-10: 已启动 Debug Server `http://127.0.0.1:7777/event` +- 2026-07-10: 已在 `[public.js]` 的票据时间线组装处添加 A/C 埋点 +- 2026-07-10: 已在 `[index.js]` 与 `[ticket-board.html]` 的设备 ID 格式化处添加 B 埋点 + +## Verification Conclusion +[Pending] diff --git a/server/routes/public.js b/server/routes/public.js index e19a59a..ce44831 100644 --- a/server/routes/public.js +++ b/server/routes/public.js @@ -694,8 +694,39 @@ router.get('/tickets/:id', async (req, res, next) => { }; const formattedEvents = events.map(e => { + // #region debug-point A:public-ticket-event-raw + (() => { + const fs = require('fs'); + let u = 'http://127.0.0.1:7777/event'; + let s = 'device-id-missing'; + try { + const env = fs.readFileSync('.dbg/device-id-missing.env', 'utf8'); + u = env.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u; + s = env.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s; + } catch {} + fetch(u, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: s, + runId: 'pre-fix', + hypothesisId: 'A', + location: 'server/routes/public.js:696', + msg: '[DEBUG] public ticket event raw', + data: { + type: e?.type || '', + action: e?.action || '', + ticket_id: e?.ticket_id || '', + device: e?.device ?? null, + detail_device: e?.detail?.device ?? null + }, + ts: Date.now() + }) + }).catch(() => {}); + })(); + // #endregion if (e.type === 'sale') { - return { + const mapped = { type: 'sale', ts: e.ts, station_name: e.station_name || nameFor(e.station_code), @@ -708,6 +739,36 @@ router.get('/tickets/:id', async (req, res, next) => { trips_total: e.trips_total ?? 0, amount: e.cost ?? 0 }; + // #region debug-point A:public-ticket-event-sale-mapped + (() => { + const fs = require('fs'); + let u = 'http://127.0.0.1:7777/event'; + let s = 'device-id-missing'; + try { + const env = fs.readFileSync('.dbg/device-id-missing.env', 'utf8'); + u = env.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u; + s = env.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s; + } catch {} + fetch(u, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: s, + runId: 'pre-fix', + hypothesisId: 'A', + location: 'server/routes/public.js:701', + msg: '[DEBUG] public sale event mapped', + data: { + ticket_id: mapped.ticket_id || '', + device: mapped.device ?? null, + station_code: mapped.station_code || '' + }, + ts: Date.now() + }) + }).catch(() => {}); + })(); + // #endregion + return mapped; } else if (e.type === 'status') { return { type: 'status', @@ -720,11 +781,42 @@ router.get('/tickets/:id', async (req, res, next) => { trips_remaining: e.trips_remaining ?? e.rides_remaining ?? null }; } else if (e.type === 'check') { - return { + const mapped = { ...e, station_name: e.station_name || nameFor(e.station_code), station_en: e.station_code ? enNameFor(e.station_code) : (e.station_en || '') }; + // #region debug-point C:public-ticket-event-check-mapped + (() => { + const fs = require('fs'); + let u = 'http://127.0.0.1:7777/event'; + let s = 'device-id-missing'; + try { + const env = fs.readFileSync('.dbg/device-id-missing.env', 'utf8'); + u = env.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u; + s = env.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s; + } catch {} + fetch(u, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: s, + runId: 'pre-fix', + hypothesisId: 'C', + location: 'server/routes/public.js:718', + msg: '[DEBUG] public check event mapped', + data: { + ticket_id: mapped.ticket_id || '', + action: mapped.action || '', + device: mapped.device ?? null, + station_code: mapped.station_code || '' + }, + ts: Date.now() + }) + }).catch(() => {}); + })(); + // #endregion + return mapped; } return e; }); diff --git a/web/index.js b/web/index.js index 0b86d9f..5a4f41f 100644 --- a/web/index.js +++ b/web/index.js @@ -350,6 +350,29 @@ createApp({ const action = getTicketEventAction(ev); let deviceId = String(ev?.device || ev?.device_id || ev?.detail?.device || ev?.detail?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); const dStr = deviceId.toLowerCase(); + + // #region debug-point B:index-ticket-device-before-map + fetch('http://127.0.0.1:7777/event', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: 'device-id-missing', + runId: 'pre-fix', + hypothesisId: 'B', + location: 'web/index.js:351', + msg: '[DEBUG] admin ticket formatter raw device', + data: { + type, + action, + raw_device: ev?.device ?? null, + raw_device_id: ev?.device_id ?? null, + raw_detail_device: ev?.detail?.device ?? null, + resolved_before_map: deviceId + }, + ts: Date.now() + }) + }).catch(() => {}); + // #endregion if (dStr === 'gate') { deviceId = '闸机'; @@ -364,6 +387,26 @@ createApp({ deviceId = '售票机'; } } + + // #region debug-point B:index-ticket-device-after-map + fetch('http://127.0.0.1:7777/event', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: 'device-id-missing', + runId: 'pre-fix', + hypothesisId: 'B', + location: 'web/index.js:373', + msg: '[DEBUG] admin ticket formatter mapped device', + data: { + type, + action, + mapped_device: deviceId + }, + ts: Date.now() + }) + }).catch(() => {}); + // #endregion const timeStr = formatTime(ev?.ts || ev?.["时间戳"]); if (type === 'sale' || type === '售票') { diff --git a/web/ticket-board.html b/web/ticket-board.html index 86c81f2..1d610a4 100644 --- a/web/ticket-board.html +++ b/web/ticket-board.html @@ -322,6 +322,29 @@ const stationEn = event.station_en || event['站点英文'] || ''; let deviceId = String(event.device || event.device_id || event.detail?.device || event.detail?.device_id || event['设备编号'] || event['机器ID'] || '').trim(); const dStr = deviceId.toLowerCase(); + + // #region debug-point B:ticket-board-device-before-map + fetch('http://127.0.0.1:7777/event', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: 'device-id-missing', + runId: 'pre-fix', + hypothesisId: 'B', + location: 'web/ticket-board.html:323', + msg: '[DEBUG] public ticket formatter raw device', + data: { + type, + action, + raw_device: event.device ?? null, + raw_device_id: event.device_id ?? null, + raw_detail_device: event.detail?.device ?? null, + resolved_before_map: deviceId + }, + ts: Date.now() + }) + }).catch(() => {}); + // #endregion if (dStr === 'gate') { deviceId = '闸机'; @@ -338,6 +361,26 @@ deviceId = '售票机'; } } + + // #region debug-point B:ticket-board-device-after-map + fetch('http://127.0.0.1:7777/event', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sessionId: 'device-id-missing', + runId: 'pre-fix', + hypothesisId: 'B', + location: 'web/ticket-board.html:345', + msg: '[DEBUG] public ticket formatter mapped device', + data: { + type, + action, + mapped_device: deviceId + }, + ts: Date.now() + }) + }).catch(() => {}); + // #endregion if (stationEn && deviceId) return `${stationEn} (${deviceId})`; if (deviceId) return `设备:${deviceId}`;