Repair legacy timestamps in `NativeDB` merges
Keep incoming provider frames strict while dropping non-positive rows from persisted history before merge validation. This lets known-bad MNQ history repair itself on the next valid IB frame without losing valid old bars or publishing noncanonical indexes. Prompt-IO: ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))backfiller_deep_fixes
parent
ed85721cfd
commit
1f30917047
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
provider: openai
|
||||||
|
service: opencode
|
||||||
|
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
|
||||||
|
timestamp: 2026-07-28T23:27:18Z
|
||||||
|
git_ref: ed85721c
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260728T232718Z_ed85721c_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
Diagnose the live `pikerd` failure where backfill publication raised
|
||||||
|
`ValueError: OHLCV timestamps must be finite and positive` while merging the
|
||||||
|
known gappy MNQ NativeDB baseline with fresh IB history. Determine whether to
|
||||||
|
commit the pending audit stack or iterate first.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Traced the primary failure through `publish_latest_frame()` to
|
||||||
|
`NativeStorageClient.update_ohlcv()`. The incoming frame was valid, but the
|
||||||
|
canonicalized persisted baseline retained its epoch-zero row and caused the
|
||||||
|
merged frame to fail validation. Added persisted-row repair before merge and
|
||||||
|
a regression using the observed legacy schema characteristics.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `piker/storage/nativedb.py` - drop non-positive persisted rows during merge
|
||||||
|
- `tests/test_storage_nativedb.py` - live MNQ failure-shape regression
|
||||||
|
- `ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.raw.md`
|
||||||
|
- unedited response record
|
||||||
|
- `ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.md`
|
||||||
|
- provenance metadata and response summary
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
None - generated changes have not been edited by the human.
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
provider: openai
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-07-28T23:27:18Z
|
||||||
|
git_ref: ed85721c
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- piker/storage/nativedb.py tests/test_storage_nativedb.py`
|
||||||
|
|
||||||
|
Fixed incremental NativeDB updates against legacy persisted history with
|
||||||
|
non-positive timestamps. Incoming provider frames remain strictly validated,
|
||||||
|
while merge repair discards invalid persisted rows before concat, dedupe,
|
||||||
|
ordering, canonical-index rewrite, and atomic publication.
|
||||||
|
|
||||||
|
The regression reproduces the observed MNQ baseline shape: one epoch-zero
|
||||||
|
row, noncanonical absolute indexes, extra derived columns, and a valid fresh
|
||||||
|
IB frame. It proves the invalid row is removed while all valid old and new
|
||||||
|
bars survive with canonical indexes.
|
||||||
|
|
||||||
|
Verification generated with the patch:
|
||||||
|
|
||||||
|
- NativeDB regressions: 18 passed
|
||||||
|
- history backfill regressions: 6 passed
|
||||||
|
- focused Ruff: passed
|
||||||
|
- `git diff --check`: passed
|
||||||
|
|
@ -514,6 +514,14 @@ class NativeStorageClient:
|
||||||
stored: pl.DataFrame = self._canonicalize_ohlcv(
|
stored: pl.DataFrame = self._canonicalize_ohlcv(
|
||||||
pl.read_parquet(path)
|
pl.read_parquet(path)
|
||||||
)
|
)
|
||||||
|
stored_len: int = stored.height
|
||||||
|
stored = stored.filter(pl.col('time') > 0)
|
||||||
|
dropped: int = stored_len - stored.height
|
||||||
|
if dropped:
|
||||||
|
log.warning(
|
||||||
|
f'Dropping {dropped} persisted OHLCV row(s) with '
|
||||||
|
f'non-positive timestamps during merge repair'
|
||||||
|
)
|
||||||
merged: pl.DataFrame = pl.concat(
|
merged: pl.DataFrame = pl.concat(
|
||||||
[stored, incoming],
|
[stored, incoming],
|
||||||
how='diagonal_relaxed',
|
how='diagonal_relaxed',
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,47 @@ def test_update_preserves_history_and_resolves_conflicts(
|
||||||
assert stored['index'].to_list() == [0, 1, 2, 3]
|
assert stored['index'].to_list() == [0, 1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_repairs_nonpositive_persisted_timestamps(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Valid incoming history must repair a legacy epoch-zero row.
|
||||||
|
|
||||||
|
The known MNQ baseline contains one zero timestamp plus extra derived
|
||||||
|
columns and noncanonical absolute indexes. ``update_ohlcv()`` used to
|
||||||
|
canonicalize those bytes but retain the zero row, then reject the
|
||||||
|
whole merged frame before fresh IB bars could publish. Write that
|
||||||
|
legacy shape directly, append a valid provider frame, and prove only
|
||||||
|
the invalid persisted row disappears while old and new valid bars
|
||||||
|
survive with canonical indexes.
|
||||||
|
|
||||||
|
'''
|
||||||
|
client = NativeStorageClient(tmp_path)
|
||||||
|
legacy = (
|
||||||
|
tsp.np2pl(mk_ohlcv(
|
||||||
|
(0, 60, 120),
|
||||||
|
(0, 1, 2),
|
||||||
|
))
|
||||||
|
.with_columns(
|
||||||
|
pl.Series('index', [3137800, 3137801, 3137802]),
|
||||||
|
pl.Series('time_prev', [None, 0, 60]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
path: Path = client.mk_path('x.test', 60)
|
||||||
|
legacy.write_parquet(path)
|
||||||
|
|
||||||
|
run(client.update_ohlcv(
|
||||||
|
'x.test',
|
||||||
|
mk_ohlcv((180,), (3,)),
|
||||||
|
60,
|
||||||
|
))
|
||||||
|
stored: pl.DataFrame = pl.read_parquet(path)
|
||||||
|
|
||||||
|
assert stored['time'].to_list() == [60, 120, 180]
|
||||||
|
assert stored['close'].to_list() == [1, 2, 3]
|
||||||
|
assert stored['index'].to_list() == [0, 1, 2]
|
||||||
|
|
||||||
|
|
||||||
def test_write_ohlcv_remains_an_explicit_replacement(
|
def test_write_ohlcv_remains_an_explicit_replacement(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue