# Evidence Topic: Channel BI post-update data contamination

## Metadata

- Date: 2026-07-30
- Status: Active
- Scope: ARR2 local Web port 8766, controlled `booking_test`, Channel BI and July monthly publication
- Confidence: Fact
- Source: source/hash inspection, isolated deterministic replay, live privacy-minimized APIs, repeatable-read read-only PostgreSQL reconciliation and focused tests
- Last verified: 2026-07-30
- Stale trigger: any change to July current Finance pins, retirement of fixture version 2, monthly republication, or BI grouping/label behavior

## Question

What data does Channel BI consume, and why did its July values look wrong after the latest uploads?

## Evidence

### Implemented source and formulas

- The browser calls `/api/analytics?month=YYYY-MM`; `PostgresPortalRepository` delegates to
  `PostgresAnalyticsRepository` against `booking_test`.
- Month/date pins come from `finance.current_daily_versions`, channel membership/order/count expectations from
  `finance.daily_channel_metrics`, and aggregates/detail from current retained `finance.v_active_daily_facts`.
- Sold rooms are `sum(no_of_rooms)`, room nights are `sum(no_of_rooms * nights)`, and revenue is
  `sum(total_price)` without multiplying the stored total again. Monthly XLSX files are not a BI source.

### Live reconciliation

The active ARR2 Web API on port 8766 and direct PostgreSQL reads return the same current projection. Legacy port 8765
is no longer listening.

| Business date | Daily version | Provenance | Retained rows | Revenue | Current role |
|---|---:|---|---:|---:|---|
| 2026-07-20 | 5 | `0720.XML`, hash-matched real upload | 100 | 370,500 | valid real daily slice |
| 2026-07-21 | 4 | real ARR upload | 119 | 452,000 | V01 operational source |
| 2026-07-22 | 6 | `0722.XML`, hash-matched real upload | 88 | 269,700 | valid real daily slice |
| 2026-07-23 | 7 | `0723-RES_DETAIL-修正版.xml`, hash-matched real upload | 109 | 359,150 | valid real daily slice |
| 2026-07-27 | 2 | `mvp-v1-fixture-20260727` / `synthetic.xml` / `local_fixture` | 1 | 5,400 | test contamination |

- The 07-23 upload committed at 16:12:27, after the earlier 15:44 diagnostic snapshot. This explains why that snapshot
  contained only 307 real rows; it did not prove the four-day business total.
- Excluding fixture version 2 now yields 416 rows/rooms, 853 room-nights, 1,451,350 revenue, nine room types, six
  worksheet channels and latest real ARRIVAL 2026-07-23.
- The live BI includes the fixture and therefore returns 417 rows/rooms, 856 room-nights, 1,456,750 revenue, ten room
  types and `更新至 2026-07-27`. Its extra type is explicitly `SYNTHETIC ROOM TYPE`.
- July monthly V04 is active with 417 rows; V01–V04 all inherit fixture version 2 and its false operational 7.27
  watermark.

### Source identity and date attribution

- Immutable source SHA-256 maps the accepted sources exactly to local originals: `0720.XML` is version 5 and
  `0722.XML` is version 6; `0721.XML` and `0723-RES_DETAIL-修正版.xml` map to versions 4 and 7 respectively.
- The processor derives business date from agreeing XML `GROUPBY1_SORT_COL`/`GROUPBY1_COL` values and requires each
  retained reservation ARRIVAL to match it; filename and upload order are not date authorities.
- An isolated replay independently reproduced `0720.XML -> 2026-07-20 -> 100 retained` and
  `0722.XML -> 2026-07-22 -> 88 retained`. The user was correct about the 416 total but had those two day counts
  verbally reversed.

### Frontend freshness

- Channel BI loads at page boot, month change, or first BI-tab entry only. A successful XML upload refreshes daily jobs
  but not analytics, and later BI-tab entries skip reload once `state.analytics` exists.
- Therefore an already-open page can continue showing the pre-07-23 value 308 even though the API has advanced to 417.
  Unlike the monthly list, Channel BI has no polling or visibility-aware reload.

### Exact V01 reconciliation

- V01 report ID 1 is persisted as superseded, `as_of_date=2026-07-27`, 120 rows and five channels.
- Its legitimate OSS source lineage is daily version 4: 119 retained rows, all with `ARRIVAL=2026-07-21`.
- Its only later lineage is daily version 2: one retained row with `ARRIVAL=2026-07-27`, accepted under run
  `mvp-v1-fixture-20260727` from `synthetic.xml` using provider `local_fixture`.
- Reopening the exact registered V01 workbook independently found the same 119/1 ARRIVAL distribution. The file is
  20,741 bytes and its SHA-256
  `a43cdac3f6d97f6b73f635d0f386cffd0ad8b6416e8f92a7b6afc2f79f1e8635` matches the artifact registry.
- Therefore the user's operational statement “V01 is updated through 2026-07-21” is correct. The stored 7.27 label is
  the consequence of contaminated report content, not a filename parser or a metadata-only rendering defect.

### Channel/company semantics

- Six channel keys are valid for these real facts: LianTai intentionally splits into `LIANTAI-GROUP` and
  `LIANTAI-FIT`, alongside QBD, DY-AI-Easy-KB, FENGRUN and HanaTour.
- The BI card renders worksheet-level `channel_count` but labels it `公司数`; six channels can represent five top-level
  companies, so the label is misleading even after data cleanup.

### Regression and mutation boundary

- The corrected focused run passed 24 analytics-contract, PostgreSQL-provider and Web tests.
- All database investigations used `REPEATABLE READ READ ONLY`; no Finance pointer, version, artifact or report was
  changed during diagnosis.

## Finding

Channel BI is correctly reading and aggregating its configured current PostgreSQL facts. One proven cause of the
reported mismatch is a data-hygiene incident: a database acceptance fixture remained pinned as a current Finance day
after real uploads were enabled. It explains the one-room difference between the correct real total 416 and live API
total 417, as well as every false 7.27 watermark. A second frontend defect can leave an open BI view showing 308 after
the 07-23 commit. The user's 07-20/07-22 verbal attribution was reversed, but this does not change the 416 total. A
smaller UX issue independently mislabels worksheet-channel count as company count.

## Impact

- Business-facing July BI and monthly V04 are not clean acceptance outputs until fixture version 2 is removed from the
  current projection and a clean July monthly version is published.
- Historical V01 cannot be truthfully corrected by changing only its `as_of_date` or filename because the registered
  workbook itself contains the fixture row. Preserve that immutable artifact as contaminated audit history and publish
  a clean operational replacement after the current projection is corrected.
- Remediation should preserve immutable audit history; retire/unpin the fixture instead of deleting its version rows.
- Production/readiness checks should reject any current Finance source whose provider is `local_fixture` (or other
  explicitly non-operational provenance).

## Open Items

- Obtain authorization for the controlled Finance-pointer repair and clean monthly republication.
- Make Channel BI refresh after successful upload and/or on visible BI-tab re-entry so it cannot retain an old snapshot.
- Decide whether to relabel the BI card as `渠道/子表数` or add a distinct five-company aggregation.
