summaryrefslogtreecommitdiff
path: root/docs/recovery-01-results.md
blob: 0e57ab7df64cac67c3cc007583b74e2646460686 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# Recovery 01 results — real listed-ETF ingestion and failure semantics

Task: docs/recovery-task-01.md. Date: 2026-09-17. Scope: this repo. No
deployment, no production image replacement, no commits, no user DB access,
no restarts. The user's failed dataset was not refetched or mutated.

## Skills actually loaded (native skill tool calls, this session)

1. systematic-debugging — loaded before any fix attempt.
2. test-driven-development — loaded; RED→GREEN cycles recorded below.
3. writing-plans — loaded; plan written to docs/recovery-01-plan.md.
4. verification-before-completion — loaded before this handoff.
No fallback to manual SKILL.md reads was needed. Discovery succeeded.

## Root cause (with real upstream evidence)

- Symptom reproduced live first attempt: `ak.fund_etf_hist_em(symbol="159399",
  start 20251231, end 20260917)` →
  `ConnectionError: RemoteDisconnected('Remote end closed connection without response')`.
- Direct replay of the exact endpoint akshare calls
  (`https://push2his.eastmoney.com/api/qt/stock/kline/get`, secid `0.159399`)
  with no UA and with a browser UA both failed identically → the eastmoney
  kline host is genuinely refusing/silently dropping connections from this
  environment. Not a UA/timeout bug, not a code bug in the worker.
- Verified against installed akshare 1.18.94: there is no tencent listed-ETF
  daily adapter (`fund_etf_hist_em`, `fund_etf_hist_min_em`,
  `fund_etf_hist_sina` are the only ETF end-of-day ETF adapters). The
  pre-existing `source_unavailable` message at worker/data.py:124 is accurate.
- Live-verified alternative provider: `ak.fund_etf_hist_sina(symbol="sz159399")`
  → 381 rows (2025-02-27..2026-09-16), columns
  `date,open,high,low,close,volume,amount,postVol,postAmt`, unadjusted, no date
  parameters (full history; must be sliced locally), volume unit 股 (shares).
- Units cross-verified live on the same session with stock sz000001 2026-09-16:
  tencent volume 949,626 (lots, 手) vs sina 94,962,632 (shares, 股) — exact ×100,
  identical turnover (1,106,652,900 vs 1,106,652,940). Hence honest units
  warnings on sina output rather than silent rescaling.

## Change (smallest correct; worker + trivial frontend label)

- `worker/data.py`:
  - new `_fetch_sina`: etf-only, daily-only, adjustment none-only (explicit
    `unsupported_*` rejections otherwise), sina symbol `sz159399`, full-history
    fetch sliced locally to the requested window, canonical identity
    `SZ#159399` preserved, verbatim provider numbers, source_warnings with
    slicing/units/unadjusted disclosure.
  - `SUPPORTED_SOURCES` now `{eastmoney, tencent, sina, auto}`; auto chain for
    ETF: eastmoney → sina (honestly labeled `provider_fallback` warning naming
    both providers); stock/index auto chains unchanged (eastmoney → tencent).
  - explicit `source=tencent` + etf still raises the unchanged, accurate
    "tencent source has no listed-ETF daily adapter".
- `worker/main.py`: `ALLOWED_SOURCES` += `sina`.
- `frontend/src/lib/format.ts`: `sina` → `新浪` source label (display only,
  no UI redesign). Backend (server/) unchanged: it never passes or validates
  a source field (datasets.rs validates frequency/adjustment/asset_type only).

## TDD evidence

Tests added in `tests/worker/test_data.py` (synthetic fixtures are clearly
labeled synthetic; live-captured provider shapes are quoted in comments):

- test_sina_etf_serves_159399_with_honest_provenance
- test_sina_rejects_adjustment_and_range_semantics
- test_sina_rejects_stock_and_index
- test_auto_etf_falls_back_from_eastmoney_to_sina_honestly
- test_auto_etf_fails_when_no_provider_is_viable
- test_tencent_etf_rejection_message_unchanged
- test_sina_registered_in_explicit_source_surface

RED: 6 of the above failed against the unmodified worker (the tencent
rejection test passed as pre-existing behavior). GREEN after implementation.

## Verification commands and fresh results

- `python -m pytest tests/worker -q` → **56 passed** (0.79s), final run.
- `python -m pytest tests -q` → **56 passed** (0.72s), final run.
- `npm run check` (frontend) → **0 errors, 0 warnings**.
- `npm test` (frontend) → **13 files / 45 tests passed**.
- Live exact-request fetch (real upstreams, single bounded attempts,
  SIGALRM 180s wall clock):
  `python -m worker.main fetch --request artifacts/recovery-01/req159399.json
  --output artifacts/recovery-01/etf_out` → **status: ready**, ~0.9s.

## Exact reported dataset facts (159399, requested 2025-12-31..2026-09-17)

- provider **sina**, endpoint `fund_etf_hist_sina`, params `{"symbol": "sz159399"}`
- row_count **173**, actual_start **2025-12-31**, actual_end **2026-09-16**
- columns: `date,open,high,low,close,volume,amount,symbol` (amount requested)
- adjustment **none (unadjusted)**; prices CNY decimals; **volume in 股
  (shares)** — 100× the lot convention of eastmoney/tencent (verified ×100 on
  the same session, not silently converted)
- 2026-09-17 has **no bar**: today's session end-of-day not yet published; the
  warning "actual coverage 2025-12-31..2026-09-16 differs from request; gaps
  kept" is on the manifest. All calendar gaps present-truthful (holidays etc.).
- identity preserved all the way through: normalized symbol `159399` CSV and
  canonical `SZ#159399` contract in adapter tests.
- fetch → persisted raw JSON (`objects/raw_159399_*.json`) + normalized CSV
  (`objects/159399.csv`) + preview contract in `result.json` (columns,
  row_count, coverage, last-20 rows), exercised outside production in
  `artifacts/recovery-01/etf_out/`.

## Comparison cases (truthful, live attempts)

- stock 600000 auto (unchanged path): attempt 1 failed (eastmoney down +
  tencent proxy.finance.qq.com connect timeout); attempt 2 → **ready**
  provider **tencent**, endpoint stock_zh_a_hist_tx, 173 rows,
  2025-12-31..2026-09-16.
- index 000300 auto (unchanged path): attempts 1–2 failed — attempt 1 tencent
  proxy connect timeout; attempt 2 eastmoney RemoteDisconnected + tencent
  returned an empty payload to akshare ("Length mismatch"). Environmental,
  pre-existing flakiness; not caused by this change and out of this task's
  repair scope.
- 159399 with source=tencent: explicitly, correctly rejected
  (source_unavailable). 159399 with qfq: with eastmoney down auto now fails
  honestly because sina has no adjustment factors (tested).

## Remaining blockers

1. eastmoney push2his connectivity from this environment is down (affects
   stock/index first choice too); recovery is environmental, workarounds live
   in fallback chains.
2. tencent proxy.finance.qq.com flaky/intermittent under this environment;
   index fetch unverified live in this session.
3. Full release gate (docs/skill-assisted-development.md) is a separate
   acceptance workflow; this task only fixes worker ETF ingestion and its
   failure semantics. Parent independent verification still required.

## Changed files

- worker/data.py
- worker/main.py
- tests/worker/test_data.py (new tests only)
- frontend/src/lib/format.ts (one-line source label)
- docs/recovery-01-plan.md (new)
- docs/recovery-01-results.md (this file, new)
- artifacts/recovery-01/ (isolated live-fetch artifacts: 3 request files,
  etf_out/, stock_out/, index_out/)