fix: make daily task log header-only

This commit is contained in:
Wyndham ARR
2026-08-11 17:23:33 +08:00
parent c947230471
commit f2c1265a1c
16 changed files with 251 additions and 34 deletions

View File

@@ -20,6 +20,7 @@
- 测试必须证明月报触发发生在数据库成功提交之后,且失败提交不会触发。
- 测试必须覆盖合并重复价格键、候选价、整数格式、显式 `0`(冻结清单规范化为 `0.00`、未完成禁止确认、revision 冲突、重复最终化、技术重试、取消留痕、认证/CSRF和隐私字段隔离。
- `待人工处理` 状态与复核进度必须作为同一个操作入口,点击或键盘激活后定位并聚焦人工价格面板,不能误开任务日志;复核面板、动态消息、确认弹窗和安全 API 错误必须完整支持中文、英文和泰文。
- Daily Report 具体行必须是非操作型表格内容不得通过鼠标、Enter 或 Space 打开任务日志;只有页头右上角的 `任务日志` 控件可以打开 `#task-log-dialog`,下载和人工复核仍由各自明确控件触发。
- 人工确认请求在途时,上传卡片必须显示本地化的“日报生成中”;请求结束后按成功、仍在生成或可重试复核状态收敛,不能继续显示过期的“等待人工定价”。
- 最终人工成功必须证明日报行序/数值可重放、Finance 只有一个 active 版本、`manual_review` 行数和冻结清单 SHA-256 可追踪、月报事件仅一次。
- 重新打开生成的 XLSX确认 `TOTAL PRICE` 为公式单元格并逐行引用同一行的 `REAL PRICE``NIGHTS``NO_OF_ROOMS`
@@ -28,4 +29,4 @@
## Last Reviewed
2026-08-06
2026-08-11

View File

@@ -19,6 +19,17 @@ preserves its no-formula contract. Both publishers upload new workbook and `resu
immutable OSS adapter, while download routing retains controlled-local compatibility for historical records and keeps
`.web-jobs` queue state local.
## Daily Interaction Correction On 2026-08-11
- The local source now treats Daily Report rows as informational table rows rather than hidden log controls. It removes
row click and Enter/Space activation, `role=button`, `tabindex`, selected-row decoration and pointer styling. The
explicit needs-review button still focuses the manual-price panel, report downloads remain links, and only the
top-right `任务日志` link opens the trace dialog.
- JavaScript syntax, all 81 `test_arr_web*.py` regressions and an isolated browser interaction check pass. The browser
proved an ordinary `0805.XML` row leaves the dialog closed, the `待人工处理` control focuses a populated review panel,
and the header utility opens the dialog; there were no console errors. This source change has not been pushed or
deployed to `8.138.234.141:8765`, so the reported production behavior remains until a controlled release.
## Implemented Locally On 2026-08-11
- Implemented Booking parser 2.1.0 exact-header compatibility for the user-confirmed July legacy and August bilingual
@@ -31,6 +42,34 @@ immutable OSS adapter, while download routing retains controlled-local compatibi
draft, Booking batch, Finance fact or runtime process was changed. Deployment and a controlled post-restart
read-only replay remain pending; QBD pending items are existing room-type review work, not a header failure.
## Diagnosed On 2026-08-11
- The reported company-channel Booking Excel parse failure occurs before object storage and draft persistence; a
read-only database snapshot shows no new draft/artifact/batch and accepted batch 7 `july-test.xlsx` remains current.
Exact parser replay isolates two cases: generated Daily/channel report workbooks are correctly rejected as the wrong
input type, while current Lian Tai/QBD raw workbooks expose a parser compatibility defect because bilingual
`Tour Code / 主团号` and hotel-detail headers do not equal the narrow exact alias allowlist. Replacing only those two
headers in memory makes both raw workbooks parse, with original files and all business state unchanged. At diagnosis,
the controlled alias expansion plus regression fixtures remained unimplemented. The user has now explicitly confirmed that both
supplied formats must be supported. An exact three-alias process-memory probe recognizes only the intended sheet in
each file: Lian Tai yields 67 rows/100 items/16 pending, while QBD 2026-08-06 yields 104 rows/218 items/212 pending;
Daily/channel output workbooks remain rejected. This parser-only plan needs no migration or upload-page redesign, but
the existing one-file/full-replacement activation model still cannot append two separately uploaded files.
- The exact desktop `0806.XML` deterministically fails processor 4.0.0 before any row or price processing with
`XML_PARSE_ERROR` at line 1872, column 0. A `TRACE_TEXT` value contains one CESU-8 surrogate pair representing
`U+1F389` even though the document declares UTF-8; the file is structurally closed and not truncated. This error is
outside the pure-`PRICE_UNMATCHED` review boundary, so a formal failure with no manual-price panel is correct. The
public deployment health endpoint remains 200. The source must be re-exported as valid UTF-8 and uploaded as a new
task; no source, runtime, database, Finance, object-store or event state was changed during diagnosis.
## Artifact Conversion On 2026-08-11
- The user-supplied `8.6 修正.XML` was an XLSX package mislabeled with an XML extension. Its 181-row first sheet
contains the flattened Opera fields needed by the ARR parser. A separate UTF-8 `RES_DETAIL` XML was reconstructed
from those rows and passed strict XML/UTF-8 checks plus processor 4.0.0: 115 retained rows, 37 rate-code exclusions,
29 duplicates and zero price-unmatched/review rows. The original workbook and malformed XML remain unchanged; no
upload, Finance, database, object-store or event state was changed.
## Runtime Recovery On 2026-08-06
- Restored the local ARR login page after a reported opening failure. No Web process was listening and the retained
@@ -315,8 +354,9 @@ immutable OSS adapter, while download routing retains controlled-local compatibi
- Updated the daily XML upload surface so submitting ARR.XML stays on the daily page instead of opening the task-log
dialog automatically. The upload card now shows a compact stage-based estimate for upload, fixed processing,
validation and database commit, finalizing at 100% on success and retaining an inline error state on failure. The
header task-log button and daily-history row actions still open the existing trace dialog on demand. JavaScript
syntax and 15 focused Web/static/trace tests pass; no API or processing behavior changed.
header task-log button remains the sole on-demand trace entry; the later 2026-08-11 interaction correction removed
daily-history row log activation. JavaScript syntax and the original 15 focused Web/static/trace tests passed; no API
or processing behavior changed.
- Simplified the desktop monthly page into a single six-column download table. The redundant standard-monthly heading,
`VERSION HISTORY`/version-record copy, visible monthly `版本` column and C/O-period footer note are removed; the
navigation label is now `月报`; the list is identified by a compact `月报处理` heading. The visible `更新至` cell reads the explicit `max_arrival_date` API field, which is
@@ -401,10 +441,10 @@ immutable OSS adapter, while download routing retains controlled-local compatibi
- The desktop and H5 headers use the plain `ARR Report` identity with no decorative icon. The daily content heading is
`Daily Report`; the duplicated daily/monthly explanatory copy has been removed without changing automatic publishing.
- The desktop header utility formerly labeled `手机看板` is now `任务日志` and opens the existing black task console in
a native modal. The console no longer occupies the daily-processing layout; upload completion and daily-history row
activation open the selected job directly. `/h5` remains available by direct URL. Both history and trace SQL are
explicitly limited to `pipeline_type = 'opera_daily'`, so this is a one-daily-job processing trace rather than a
global server, monthly-run or company-report log.
a native modal. The console no longer occupies the daily-processing layout, and the 2026-08-11 interaction correction
makes this header utility its only opener; upload completion and Daily rows do not open it. `/h5` remains available
by direct URL. Both history and trace SQL are explicitly limited to `pipeline_type = 'opera_daily'`, so this is a
one-daily-job processing trace rather than a global server, monthly-run or company-report log.
- Refined the daily desktop surface after visual review. `ARR Report` remains the dominant workspace title while
`Daily Report` is smaller; `任务日志` is a compact outlined button at the adjacent status-label size; the upload
station is centered and responsive; decorative `01 / UPLOAD` and `本月留痕` labels are gone; and `开始处理` is a

View File

@@ -5,7 +5,12 @@
| Date | Task | Outcome | Docs Updated |
|---|---|---|---|
| 2026-08-11 | Implement Lian Tai/QBD bilingual Booking-header compatibility | Bumped the bounded parser to 2.1.0, converted readable approved header labels into the existing exact normalized allowlist, preserved legacy aliases and fail-closed ambiguity, and added Chinese/English/Thai header-error mapping. Synthetic parser/coordinator tests, all four supplied workbook replays and wrong-report negatives pass; no API, migration, upload, draft, source activation or runtime deployment occurred | README, architecture/data-flow/business rules, current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale deployment item |
| 2026-08-06 | Repair manual-review navigation/progress and add English/Thai | Merged the status chip and progress into one review action that focuses the operation panel while row clicks still own logs; removed the frozen-case sentence; set upload status to `日报生成中` before finalization; localized the complete review UI and review API errors. Live browser QA covered zh/en/th and dialog copy without submitting a business action. Full suite: 393 passed, 3 intentional skips | Current state, success criteria, module map, [UI evidence](../50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md), scoped planning record |
| 2026-08-11 | Make the top-right control the sole Daily task-log entry | Removed concrete-row click/Enter/Space log activation plus the row's button semantics, pointer, focus and selected styling. Explicit download and manual-review controls remain; isolated browser QA proved ordinary row → no dialog, review control → focused operation panel, header `任务日志` → dialog. JavaScript syntax and all 81 Web tests pass. The source is local and production deployment is still pending | Success criteria, current state, [interaction evidence](../50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md), evidence index, stale deployment item, scoped planning record |
| 2026-08-11 | Plan Lian Tai/QBD bilingual Booking-header compatibility | Verified the exact supplied header cells and rendered header-only ranges. Current parser 2.0.0 rejects both; an in-memory exact three-alias expansion parses only the intended sheets (Lian Tai 67/100/16 pending; QBD 104/218/212 pending) while two report-output negatives remain rejected. Recommended parser 2.1.0, synthetic parser/coordinator regressions, neutral error copy and docs; no migration/UI redesign. One-file/full-replacement activation remains a separate operational boundary. No code, workbook, database or runtime changed | Current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale item, scoped planning record |
| 2026-08-11 | Convert user-supplied `8.6 修正.XML` into an uploadable XML | Identified the input as an XLSX package mislabeled `.XML`; reconstructed a UTF-8 `RES_DETAIL` XML from its 181 flattened rows and validated it with `xmllint`, strict UTF-8 decoding and processor 4.0.0. Result: 115 retained rows, 37 exclusions, 29 duplicates, zero price-unmatched/review rows. Original workbook and malformed XML were left unchanged; no business/database write occurred | Current state, [conversion evidence](../50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md), evidence index |
| 2026-08-11 | Diagnose “公司渠道明细” Excel parse failure | Read-only database evidence proves the request stopped before draft/artifact/source persistence and left accepted batch 7 current. Exact production-parser replay shows generated report workbooks are correctly rejected, while current Lian Tai/QBD raw workbooks fail because their bilingual Tour Code and hotel-detail headers do not equal the exact parser aliases; changing only both headers in memory makes both parse. No code, workbook, database or runtime state changed | Current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale item |
| 2026-08-11 | Diagnose failed `0806.XML` | Public health remained 200, while an isolated processor-4.0.0 replay of the exact desktop file failed before parsing any row. Line 1872 inside `TRACE_TEXT` contains one CESU-8 surrogate pair for `U+1F389` although the file declares UTF-8, producing `XML_PARSE_ERROR`; therefore the run correctly cannot enter price review. No source, upload, runtime or business state changed | Current state, [invalid-UTF-8 evidence](../50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md), evidence index |
| 2026-08-06 | Repair manual-review navigation/progress and add English/Thai | Merged the status chip and progress into one review action that focuses the operation panel; at that time ordinary row clicks still owned logs, which the 2026-08-11 entry-boundary correction supersedes. Removed the frozen-case sentence, set upload status to `日报生成中` before finalization and localized the complete review UI/API errors. Live browser QA covered zh/en/th without a business mutation. Full suite: 393 passed, 3 intentional skips | Current state, success criteria, module map, [UI evidence](../50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md), scoped planning record |
| 2026-08-06 | Use integer review prices and repair `请求未完成` finalization | Changed review display/PATCH input to non-negative integer text while retaining canonical `.00` storage and replay. Bound the visible failure to runs 66/67, repaired a 21-placeholder/20-value Finance INSERT and the missing `manual_override_json` artifact kind through additive migration 018, then proved the full PostgreSQL finalization path in an outer rollback. Web PID 26286 is healthy; latest run 67 remains explicitly retryable with frozen `200/0` and no Finance/outbox mutation. Full suite: 387 passed, 3 intentional skips | ADR/domain/architecture/current state, migration/deployment ledgers, [repair evidence](../50-evidence/topics/2026-08-06-integer-review-finalize-repair.md), scoped planning record |
| 2026-08-06 | Deploy migration 017 and restore live `0805.XML` manual review | Reconciled already-live 016 semantics, corrected a legacy run-constraint drift found by rollback probe, formally applied 017 with unchanged business counts, closed runs 6163 as infrastructure failures, then fixed a 16-value/15-placeholder delivery INSERT exposed by the first acceptance upload. Restarted Web and proved run 65 opens the exact two-key review panel at revision 0 with no prices, daily XLSX, Finance version or daily-commit/monthly event. Historical version 28 remains rejected | Migration ledger, current state, [live rollout evidence](../50-evidence/topics/2026-08-06-live-daily-price-review-rollout.md), SHA-protected runtime checkpoints, scoped planning record |
| 2026-08-06 | Diagnose fresh `0805.XML` v4 upload not entering review | Proved the exact file correctly yields independently valid v4 `review_required` with two keys, but live `booking_test` lacks migration 017 tables/states. Three new runs remain `running` after source registration because review commit and best-effort closure both depend on the missing table; no delivery, Finance or outbox fact exists. Live 016 effects are present despite the checked-in ledger saying pending. No database/runtime/business mutation was performed; 017 deployment and explicit shell cleanup require operator authorization | Current state, [live mismatch evidence](../50-evidence/topics/2026-08-06-live-0805-review-migration-mismatch.md), evidence index, stale item, scoped planning record |

View File

@@ -4,7 +4,10 @@ Use this index for searchable, traceable evidence records.
| Date | Topic | Status | Source | Detail |
|---|---|---|---|---|
| 2026-08-11 | Daily row / task-log entry boundary | Implemented locally; production deployment pending | [Evidence topic](topics/2026-08-11-daily-row-task-log-entry-boundary.md) | Daily rows no longer have log-opening click/keyboard handlers, button semantics or interactive styling. Manual-review and download controls remain explicit, and the header `任务日志` link is the sole dialog opener. JavaScript syntax, 81 Web tests and isolated browser interaction QA pass with no console errors; no production or business state changed. |
| 2026-08-11 | Convert `8.6 修正.XML` workbook to uploadable XML | Converted and independently accepted | [Evidence topic](topics/2026-08-11-0806-workbook-to-res-detail-xml.md) | The supplied `.XML` is actually an XLSX with 181 rows and flattened `RES_DETAIL` fields. A new UTF-8 `RES_DETAIL` XML was reconstructed without changing the workbook; strict XML/UTF-8 checks and processor 4.0.0 pass with 115 retained rows, zero price-unmatched rows and no business/database write. |
| 2026-08-11 | Company-channel Booking Excel parse failure | Parser 2.1.0 implemented locally; deployment pending | [Evidence topic](topics/2026-08-11-company-channel-booking-header-parse-failure.md) | The user confirmed both supplied formats must be accepted. Parser 2.1.0 preserves exact whole-cell matching while accepting the three approved bilingual aliases; synthetic/coordinator tests, four supplied workbook replays and wrong-report negatives pass. Separate-file append remains outside the singleton full-source model. |
| 2026-08-11 | `0806.XML` invalid UTF-8 | Diagnosed read-only; source re-export required | [Evidence topic](topics/2026-08-11-0806-xml-invalid-utf8.md) | Processor 4.0.0 fails before row parsing with `XML_PARSE_ERROR` at line 1872, column 0. A `TRACE_TEXT` value contains one CESU-8 surrogate pair for `U+1F389` despite the UTF-8 declaration. Because this is not pure `PRICE_UNMATCHED`, failure rather than manual review is correct. The public service remains healthy and no business state changed. |
| 2026-08-06 | Daily review navigation, progress and three-language UI | Implemented and runtime-active | [Evidence topic](topics/2026-08-06-daily-review-ui-navigation-i18n.md) | The whole needs-review status now opens/focuses the operation panel without opening logs; the unwanted retry sentence is removed; finalization sets `日报生成中` before its request; review UI/API errors are localized in Chinese, English and Thai. Live read-only browser QA and 393 tests pass; no price/finalize/cancel/upload mutation was invoked. |
| 2026-08-06 | Integer review prices and finalization repair | Deployed repair; run-67 checkpoint is historical | [Evidence topic](topics/2026-08-06-integer-review-finalize-repair.md) | Review amounts now display/accept integers and normalize to canonical `.00`. Migration 018 fixes the omitted manual-manifest artifact kind and the Finance INSERT now has 20 binds; rollback probes, live readiness and the then-current 387-test suite passed. Run 67 was still frozen at `200/0` when that repair ended; later browser-visible lifecycle state is recorded in the UI follow-up topic. |
| 2026-08-06 | Live Daily price-review rollout and `0805.XML` acceptance | Historical initial-review snapshot; superseded by repair topic | [Evidence topic](topics/2026-08-06-live-daily-price-review-rollout.md) | Reconciled live 016 semantics without rerunning it, corrected and applied 017 after an exact up/down rollback probe, closed runs 6163, fixed a delivery-placeholder runtime bug, and proved run 65 reached the two-key live panel with no Finance/report/outbox effect. Later runs and current retry instructions are in the integer/finalization repair topic. |
@@ -51,7 +54,7 @@ Use this index for searchable, traceable evidence records.
| 2026-07-30 | Daily page visual polish | Active | [Evidence topic](topics/2026-07-30-daily-page-visual-polish.md) | The daily surface now preserves workspace-title hierarchy, exposes task log as a real button and centers a simplified upload station with a compact action. Thirty-three focused tests and four responsive live widths passed without overflow or browser issues. |
| 2026-07-30 | Web login runtime mismatch | Resolved; authenticated runtime active | [Evidence topic](topics/2026-07-30-web-login-runtime-mismatch.md) | Keychain-backed login is active under detached Screen on `*:8766`. A 2026-07-31 Keychain-only rotation and controlled restart reject the prior pair, accept the new pair and pass session/readiness/logout plus loopback/LAN checks without business writes. DHCP has the current LAN entry at `.48`; the password is still not distinct from the username, and reboot-persistent supervision remains open. |
| 2026-07-30 | Daily upload filename provenance | Implemented/migrated; post-restart write acceptance pending | [Evidence topic](topics/2026-07-30-daily-upload-filename-provenance.md) | Migration 013 separates `processing_runs.uploaded_filename` from canonical artifact `source.xml`; history/trace use only the upload provenance and old rows render unknown. The authenticated runtime is active; one controlled no-PII upload remains to verify the live write path. |
| 2026-07-30 | Task-log relocation and daily-only scope | Historical before-state; superseded by the 2026-07-31 upload interaction update | [Evidence topic](topics/2026-07-30-task-log-relocation-and-scope.md) | The desktop header opens the black task console as a native modal, and history selection still reveals the matching job. The earlier upload-triggered opening is superseded; query inspection still proves both history and trace roots are limited to `pipeline_type = 'opera_daily'`. |
| 2026-07-30 | Task-log relocation and daily-only scope | Historical before-state; interaction superseded through 2026-08-11 | [Evidence topic](topics/2026-07-30-task-log-relocation-and-scope.md) | The modal relocation and daily-only query scope remain factual. Upload-triggered opening was removed on 2026-07-31, and Daily-row selection was removed on 2026-08-11; the header `任务日志` link is now the sole dialog opener. |
| 2026-07-30 | Company-report path and July month-end readiness | Historical pre-change readiness snapshot; current generation rule superseded | [Evidence topic](topics/2026-07-30-company-report-path-and-month-end-readiness.md) | Missing/unmatched Group Codes leave Booking Room blank and current July projection previews valid 5/5. The topic records the prior July completion boundary and the remaining acceptance work; current-month incomplete periods are now also generatable, with rerun after completion when needed. |
| 2026-07-30 | Channel BI post-update data contamination | Active; repair not yet authorized | [Evidence topic](topics/2026-07-30-channel-bi-post-update-data-contamination.md) | Four hash-matched real files total 416 rooms; live API/monthly V04 show 417 because active Finance version 2 is a one-room local fixture. An open BI view can also stay at pre-07-23 value 308 because analytics does not refresh after upload. |
| 2026-07-30 | Monthly publication live acceptance | Implemented; current data caveat active | [Evidence topic](topics/2026-07-30-monthly-publication-live-acceptance.md) | Publication mechanics remain verified. Later runs reached V04 active with 417 rows and five published events; 416 are real and one is the known fixture. Browser/access logs previously proved automatic list discovery without refresh. |

View File

@@ -3,7 +3,7 @@
## Metadata
- Date: 2026-07-30
- Status: Historical before-state; superseded by the 2026-07-31 upload interaction update
- Status: Historical before-state; opening interactions superseded through 2026-08-11
- Scope: Desktop task-log information architecture, interaction and backend query boundary
- Confidence: Fact
- Source: `arr_web/static/`, `arr_web/repository.py`, focused tests and live local browser verification
@@ -15,7 +15,8 @@
- The desktop header position formerly occupied by `手机看板` now contains a `任务日志` button.
- The sole existing black task console moved out of the 日报处理 panel into a native modal dialog; no duplicate console or alternate trace implementation was introduced.
- Before the 2026-07-31 upload interaction update, starting an upload opened the dialog. Mouse or keyboard activation
of a daily-history row still opens the selected job's trace.
of a daily-history row also opened the selected job's trace until the 2026-08-11 entry-boundary correction. Current
behavior allows only the header `任务日志` link to open the dialog.
- Closing the dialog returns focus to the opener. Trace polling runs only while the dialog is open and the selected job remains active.
- The `/h5` route and assets remain intact, but the desktop header no longer links to them.

View File

@@ -4,6 +4,10 @@ Date: 2026-08-06
Status: implemented and runtime-active
Scope: authenticated desktop Daily Report UI only
Interaction note (2026-08-11): the row-owned task-log behavior described as the surrounding before-state below is
superseded in local source by the Daily row / task-log entry-boundary correction. After that change is deployed, only
the top-right `任务日志` link opens the log dialog.
## Reported Problems
- Clicking `待人工处理` opened the task log instead of the manual-price operation panel.

View File

@@ -0,0 +1,32 @@
# `8.6 修正.XML` workbook-to-XML conversion
## Metadata
- Date: 2026-08-11
- Status: Converted and independently accepted by the active processor
- Scope: user-supplied `8.6 修正.XML` workbook and generated upload artifact
- Confidence: High for the generated artifact; no original workbook bytes were changed
## Input classification
- The supplied file is an XLSX ZIP package despite its `.XML` filename (`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`).
- It contains three sheets, with 181 data rows on `Sheet1`; every row has the same `20260806` / `06-08-26` business-date pair.
- The first sheet exposes the flattened Opera `RES_DETAIL` fields needed by the ARR parser, plus separate flattened comment and trace columns.
## Conversion and validation
- Generated a new UTF-8 `RES_DETAIL` XML with the fixed path
`LIST_G_GROUP_BY1/G_GROUP_BY1/LIST_G_RESERVATION/G_RESERVATION`.
- Mapped the workbook's required reservation fields without changing business values; flattened comment and trace values
were placed in the parser's existing nested `RES_COMMENT` and `TRACE_TEXT` paths. The workbook remained read-only.
- Output artifact: `8.6 修正-可上传.XML`, SHA-256
`27b41f4338967ba2b5ae03161fa7eb7b63929a9708c0106e01a740e6c2cc1c91`.
- `xmllint`, strict UTF-8 decoding and ARR processor 4.0.0 all pass. Processor result is `success` for business date
`2026-08-06`: 181 source rows, 37 rate-code exclusions, 29 duplicates, 115 retained rows, zero price-unmatched
rows and zero review items. No daily/Finance/database write was performed.
## Boundary
This artifact is a deterministic reconstruction from the supplied workbook, not a byte-for-byte restoration of the
malformed original Opera export. Upload the generated XML as a new task; keep the source workbook and the original
malformed XML as historical inputs.

View File

@@ -0,0 +1,41 @@
# `0806.XML` invalid UTF-8 diagnosis
## Metadata
- Date: 2026-08-11
- Status: Read-only input diagnosis; source re-export required
- Scope: exact desktop `0806.XML`, processor 4.0.0 isolated replay, public deployment health probe
- Confidence: High for the inspected file; the authenticated remote artifact hash was not read
## Observed behavior
- The public ARR endpoint at `8.138.234.141:8765` remained healthy: anonymous `/healthz` returned HTTP 200 while
authenticated detail endpoints remained protected.
- The inspected `0806.XML` is 694,286 bytes with SHA-256
`b41c103bfad7f9f0abb25af159a0c5920f0f9b89e156d31919521d057b62639f`.
- An isolated active-processor replay exited 3 with `status=failed`, `business_date=null`, zero parsed source rows and
exactly one `XML_PARSE_ERROR`: `not well-formed (invalid token): line 1872, column 0`.
- The structured result is processor 4.0.0 / Schema 4.0 with `activation_eligible=false`; every outcome count and
review count is zero. No price matching was reached.
## Root cause
- The XML declaration says UTF-8, but line 1872 inside a `TRACE_TEXT` value begins with bytes
`ED A0 BC ED BE 89`.
- Those six bytes are the CESU-8 representation of UTF-16 surrogate pair `U+D83C U+DF89`, whose intended Unicode
scalar is `U+1F389`. UTF-8 must encode that scalar as one four-byte sequence; XML 1.0 also forbids surrogate code
points as PCDATA.
- The file contains exactly one such surrogate pair. Its closing XML structure is present, so this is an encoding
defect rather than a truncated export.
- Manual price review is intentionally unavailable: only a non-empty error set consisting entirely of
`PRICE_UNMATCHED` can enter `review_required`; `XML_PARSE_ERROR` is a formal input failure before pricing.
## Safe recovery boundary
Re-export the 2026-08-06 Opera XML as standards-compliant UTF-8, preferably after the upstream trace text no longer
contains the mis-encoded character, then upload the new export as a fresh task. Do not edit the registered source
artifact or convert the failed task into a review case. A fresh replay is still required to discover any later price
or business-rule issues after parsing succeeds.
No original XML byte, upload, review item, database fact, Finance version, object-store object, event or runtime process
was changed during this diagnosis.

View File

@@ -0,0 +1,47 @@
# Daily Row / Task-Log Entry Boundary
Date: 2026-08-11
Status: implemented locally; production deployment pending
Scope: authenticated desktop Daily Report interaction only
## Reported Problem
On the production Daily page, clicking a concrete history row opened the technical task-log dialog. The intended
information architecture is stricter: logs are an on-demand utility available only from the top-right `任务日志` link;
ordinary users should not reach them while inspecting business rows.
## Root Cause
The row behavior was explicit rather than accidental bubbling. `renderJobs()` emitted every row with `role=button`,
`tabindex=0`, a selected state and a log-specific accessible label. Delegated click and Enter/Space handlers then called
`selectJob(..., { showLog: true })`; CSS reinforced the action with a pointer cursor, focus ring and selected-row tint.
## Change
- Render Daily history as ordinary table rows with no row action attributes.
- Remove the row click and Enter/Space log activation paths and the `showLog` option from internal trace selection.
- Remove the row pointer/focus/selected styling so appearance and accessibility semantics match the inert behavior.
- Preserve the explicit `待人工处理` button, report download link and top-right `任务日志` dialog trigger.
No API, trace query, processor, Finance, database, upload or monthly-publication behavior changed.
## Verification
- `node --check arr_web/static/app.js`: passed.
- All `test_arr_web*.py` tests: 81 passed in 1.105 seconds.
- The focused static contract proves `openTaskLog();` has exactly one caller, tied to `#task-log-trigger`, and rejects
any row key handler, log-selection call, interactive semantics or `.job-row` styling.
- An isolated local page served the real edited HTML/CSS/JavaScript with two mock Daily records. Browser checks proved:
- clicking ordinary `0805.XML` kept `#task-log-dialog.open === false` and left the hash at `#daily`;
- clicking `待人工处理` focused the visible review panel with one issue and kept the log closed;
- clicking the top-right `任务日志` link opened the dialog;
- the ordinary row had no role/tabindex, computed cursor was `auto`, and the console had zero errors.
The local browser used only mock GET responses. It did not upload, save a price, finalize/cancel a review, download an
artifact, write Finance/database state or contact a production mutation endpoint.
## Deployment Boundary
An unauthenticated production static-asset read correctly redirected to login, and no credentials or deployment path
were inferred. The edited source has not been committed, pushed, restarted or deployed to `8.138.234.141:8765`; the
production symptom therefore remains until the controlled release step is authorized and completed.

View File

@@ -0,0 +1,39 @@
# Reflection: Technical Surfaces Need Explicit Entry Controls
## Trigger
The user corrected the production Daily interaction: clicking a concrete business row unexpectedly opened the full
technical task log, even though a named `任务日志` utility already existed in the header.
## Expected Behavior
Inspecting business data should be inert unless a cell exposes a clear business action. Dense diagnostic material
should appear only after an explicit, accurately named user action.
## Actual Behavior
Every Daily row was visually and semantically a hidden log button. Mouse click, Enter and Space opened the dialog, so
the same technical surface had both a deliberate header entry and an implicit whole-row entry.
## Root Cause
- A convenient job-selection interaction was conflated with permission to reveal diagnostic detail.
- Handler removal alone had not been treated as enough; row role, focusability, cursor and selected styling also encoded
the obsolete action.
- Earlier acceptance covered the special needs-review button but did not assert that all ordinary rows were inert.
## Lesson
When a product has a dedicated diagnostic control, make it the sole opener unless row-level diagnostics are an explicit
requirement. A behavior change is complete only when event handlers, keyboard paths, accessibility semantics, visual
affordances and regression tests all agree.
## Action
- Keep Daily rows informational and retain only explicit download/review controls inside them.
- Assert that the named header utility is the only task-log opener.
- Verify ordinary row, business-action and diagnostic-entry clicks separately in browser acceptance.
## Promotion
Promoted to project success criteria, current state, focused static UI contracts and the 2026-08-11 interaction evidence.

View File

@@ -4,6 +4,7 @@ Use this index for second-order workflow lessons.
| Date | Reflection | Trigger | Action | Detail |
|---|---|---|---|---|
| 2026-08-11 | Technical surfaces need explicit entry controls | The user corrected the Daily table because inspecting a business row unexpectedly opened a dense technical log | Keep ordinary rows informational, place technical utilities behind named controls, and remove interaction styling/semantics with the handler | [Case](cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md) |
| 2026-07-31 | Formatting is not business state | A yellow final Booking row was initially at risk of being interpreted as cancellation | Make cancellation text-based, preserve formatting as non-authoritative and keep a real-style regression | [Case](cases/2026-07-31-formatting-is-not-business-state.md) |
| 2026-07-30 | Coordinate hot static assets with the long-lived backend | Login static files activated before backend restart; later launchd ownership also hit macOS Desktop privacy | Feature-detect mixed versions, gate restart on all secrets, use a secret-free launcher and a desktop-authorized supervisor until deployment leaves Desktop | [Case](cases/2026-07-30-coordinate-hot-static-and-long-lived-backend.md) |
| 2026-07-30 | Distinguish a database snapshot from batch completeness | A 15:44 three-file snapshot was incorrectly declared the clean total before the 16:12 07-23 commit | Timestamp snapshots, enumerate expected dates and reconcile recent/in-flight runs before declaring completeness | [Case](cases/2026-07-30-distinguish-snapshot-from-batch-completeness.md) |

View File

@@ -4,8 +4,9 @@
| Date | Document / Module | Issue | Next Action |
|---|---|---|---|
| 2026-08-06 | Daily `PRICE_UNMATCHED` final Finance acceptance / business date 2026-08-05 | Integer UI/API and both finalization defects are repaired and deployed: migration 018 admits the immutable manifest artifact, the Finance INSERT bind count is exact, and a real PostgreSQL rollback slice passes. Latest run 67 is still revision-2 `generation_failed` with frozen GRP1/LBLT `200/0`; it has no Finance version or run outbox. Run 66 separately retains `2300/0`, run 65 remains unset, and rejected version 28 remains immutable | Refresh/login, open latest run 67, verify `200/0` are the intended approved values, then explicitly retry generation once. Do not retry run 66 by mistake. After success, prove 153 retained rows with 2 `manual_review` rows, one active Finance version, matching frozen-manifest SHA and exactly one new final daily-commit/monthly event; do not modify version 28 |
| 2026-08-11 | Daily row / task-log production deployment | Local source and tests now make the header `任务日志` link the sole dialog opener, but the user-reported production site `8.138.234.141:8765` still serves the old row-opening behavior. This task did not push, restart or deploy the production service | Release the scoped frontend/tests through the controlled production workflow, then perform authenticated read-only QA: ordinary row leaves the dialog closed, review control focuses the operation panel, header control opens the dialog, and download remains independent |
| 2026-08-11 | `booking_ingestion.excel` header compatibility deployment | Parser 2.1.0 now supports the approved legacy Thai and bilingual Lian Tai/QBD full headers through exact normalized aliases; synthetic/coordinator tests, four exact workbook replays and wrong-report rejections pass. The UI remains one-file and activation replaces the singleton full source rather than appending separate files | Deploy/restart Web, verify authenticated readiness, then run the four supplied files through a read-only parser replay before any separately authorized upload/review/activation. Treat multi-file append as a separate product decision; QBD's pending room-type items still require the existing manual review workflow |
| 2026-08-06 | Daily `PRICE_UNMATCHED` final Finance acceptance / business date 2026-08-05 | Integer UI/API and both finalization defects are repaired and deployed: migration 018 admits the immutable manifest artifact, the Finance INSERT bind count is exact, and a real PostgreSQL rollback slice passes. Latest run 67 is still revision-2 `generation_failed` with frozen GRP1/LBLT `200/0`; it has no Finance version or run outbox. Run 66 separately retains `2300/0`, run 65 remains unset, and rejected version 28 remains immutable | Refresh/login, open latest run 67, verify `200/0` are the intended approved values, then explicitly retry generation once. Do not retry run 66 by mistake. After success, prove 153 retained rows with 2 `manual_review` rows, one active Finance version, matching frozen-manifest SHA and exactly one new final daily-commit/monthly event; do not modify version 28 |
| 2026-07-31 | Booking raw Excel parser/review business acceptance | Parser 2.1, migrations 014/015, atomic draft repository, routes, editor, HTTP transport and current runtime are ready. Supplied-workbook replay, expanded tests, live 5/5 processing and a real-PostgreSQL create/edit/activate/rollback slice pass. No authorized real workbook has replaced the current source | Run one operator-authorized complete-workbook upload/review/activation, then one released five-company job and verify all downloads |
| 2026-07-31 | Booking review audit depth | Migration 015 stores current confirmed/pending/deleted state but no reviewer, reason, explicit review event, old/new values or immutable revision history. Repository logic serializes and gates activation, but declarative DB guards are limited and same-artifact re-extraction deletes earlier unactivated edits | Confirm whether single-operator latest-state editing is sufficient. If review must be auditable, add actor/reason/event or revision tables, optimistic concurrency and DB transition guards before migration acceptance |
| 2026-07-31 | Booking Excel current-source runtime acceptance | Current port 8766 loads the review composition with all readiness flags true; migrations 014/015 are recorded and `booking.current_source_batch` still selects Markdown batch 1. A 14:14 +08 read-only recheck found no reviewing draft; the prior draft is no longer an active task-creation blocker and did not switch the current source. The Markdown five-workbook baseline has been frozen with exact hashes | Run one Markdown-backed five-company Web job after the July period completes if a final-period snapshot is desired, then verify downloads. Keep any later complete-workbook activation operator-authorized, then compare its five overlapping Group Codes with the frozen baseline |

View File

@@ -410,7 +410,7 @@
: chip(job.status, jobStatus);
const canDownload = state.health.download_ready && job.status === "succeeded" && job.daily_report_sha256;
return `
<tr class="job-row${job.job_id === state.selectedJobId ? " is-selected" : ""}" data-job-id="${escapeHtml(job.job_id)}" tabindex="0" role="button" aria-selected="${job.job_id === state.selectedJobId}" aria-label="${escapeHtml(I18N?.t("task.view_job", { jobId: job.job_id }) || `查看任务 ${job.job_id} 的全流程日志`)}">
<tr>
<td><span class="filename" title="${escapeHtml(job.filename || "—")}">${escapeHtml(job.filename || "—")}</span></td>
<td>${statusMarkup}${job.failure_code ? `<code class="job-failure-code" title="${escapeHtml(job.failure_code)}">${escapeHtml(job.failure_code)}</code>` : ""}</td>
<td>${escapeHtml(job.arrival_date ? formatDate(job.arrival_date) : "—")}</td>
@@ -1009,14 +1009,13 @@
}
}
async function selectJob(jobId, { showLog = false } = {}) {
async function selectJob(jobId) {
if (!jobId) return;
clearTracePoll();
const changed = state.selectedJobId !== jobId;
state.selectedJobId = jobId;
if (changed) state.jobTrace = null;
renderJobs(state.jobs);
if (showLog) openTaskLog();
await loadJobTrace(true, !changed);
}
@@ -2611,19 +2610,7 @@
if (reviewTrigger) {
event.preventDefault();
openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true);
return;
}
if (event.target.closest("a, button")) return;
const row = event.target.closest("[data-job-id]");
if (row) selectJob(row.dataset.jobId, { showLog: true });
});
$("#jobs-body").addEventListener("keydown", (event) => {
if (!["Enter", " "].includes(event.key)) return;
if (event.target.closest("a, button")) return;
const row = event.target.closest("[data-job-id]");
if (!row) return;
event.preventDefault();
selectJob(row.dataset.jobId, { showLog: true });
});
$("#daily-price-review-body").addEventListener("click", (event) => {
const save = event.target.closest("[data-daily-price-review-save]");

View File

@@ -295,10 +295,6 @@ th { color: var(--muted); background: #fbfcfe; font-size: 10px; font-weight: 750
td { font-size: 13px; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: #fafcff; }
.job-row { cursor: pointer; }
.job-row.is-selected td { background: #f1f6ff; }
.job-row.is-selected td:first-child { box-shadow: inset 3px 0 var(--blue); }
.job-row:focus-visible { outline: 3px solid rgba(37, 99, 235, .28); outline-offset: -3px; }
.job-failure-code { max-width: 180px; margin-top: 4px; overflow: hidden; display: block; color: #b42318; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 8px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.filename { max-width: 260px; overflow: hidden; display: block; font-weight: 620; text-overflow: ellipsis; }
.cell-subtle { color: var(--muted); }

View File

@@ -32,7 +32,9 @@ class DailyPriceReviewUiContractTests(unittest.TestCase):
handler_end = self.script.index('$("#daily-price-review-body")', handler_start)
handlers = self.script[handler_start:handler_end]
self.assertIn("openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true)", handlers)
self.assertGreaterEqual(handlers.count('event.target.closest("a, button")'), 2)
self.assertNotIn('$("#jobs-body").addEventListener("keydown"', handlers)
self.assertNotIn('event.target.closest("a, button")', handlers)
self.assertNotIn('closest("[data-job-id]")', handlers)
self.assertIn('id="daily-price-review-panel" aria-labelledby="daily-price-review-title" tabindex="-1"', self.html)
self.assertIn("panel.focus({ preventScroll: true });", self.script)

View File

@@ -46,13 +46,30 @@ class TaskLogStaticContractTests(unittest.TestCase):
def test_dialog_open_close_and_polling_contract(self) -> None:
self.assertIn('$("#task-log-trigger").addEventListener("click", (event) => {', self.script)
self.assertIn("event.preventDefault();", self.script)
self.assertIn("openTaskLog();", self.script)
self.assertEqual(self.script.count("openTaskLog();"), 1)
self.assertIn('$("#close-task-log").addEventListener("click", closeTaskLog)', self.script)
self.assertIn('$("#task-log-dialog").addEventListener("close", clearTracePoll)', self.script)
self.assertIn("|| !taskLogIsOpen()", self.script)
self.assertIn("{ showLog: true }", self.script)
self.assertNotIn("showLog", self.script)
self.assertNotIn('{ scroll: true }', self.script)
def test_daily_rows_are_not_task_log_controls(self) -> None:
render_start = self.script.index("function renderJobs(jobs)")
render_end = self.script.index("function reviewPrice(value)", render_start)
renderer = self.script[render_start:render_end]
self.assertNotIn('class="job-row', renderer)
self.assertNotIn('data-job-id=', renderer)
self.assertNotIn('tabindex="0" role="button"', renderer)
self.assertNotIn('aria-selected=', renderer)
handler_start = self.script.index('$("#jobs-body").addEventListener("click"')
handler_end = self.script.index('$("#daily-price-review-body")', handler_start)
handlers = self.script[handler_start:handler_end]
self.assertNotIn('$("#jobs-body").addEventListener("keydown"', handlers)
self.assertNotIn('closest("[data-job-id]")', handlers)
self.assertNotIn("selectJob(", handlers)
self.assertNotIn(".job-row", self.styles)
def test_upload_keeps_progress_inline_without_opening_log(self) -> None:
self.assertIn('id="upload-progress"', self.html)
self.assertIn('id="upload-progress-track"', self.html)