Skip to content

fix(search): avoid year-0 crashes in Chinese rolling-window temporal extraction - #2636

Merged
benfrank241 merged 2 commits into
vectorize-io:mainfrom
r266-tech:r266/chinese-temporal-year0-2533
Jul 13, 2026
Merged

fix(search): avoid year-0 crashes in Chinese rolling-window temporal extraction#2636
benfrank241 merged 2 commits into
vectorize-io:mainfrom
r266-tech:r266/chinese-temporal-year0-2533

Conversation

@r266-tech

Copy link
Copy Markdown
Contributor

Fixes #2533.

Summary

  • avoid constructing year 0 when Chinese rolling-window expressions shift a low reference year backward
  • return the existing no-temporal-constraint sentinel for impossible relative year shifts
  • keep valid low-year rolling windows and unrelated parser ValueErrors covered by regression tests

Tests

  • uv run --frozen pytest tests/test_query_analyzer.py -q
  • uv run --frozen ruff format --check hindsight_api/engine/chinese_temporal_periods.py tests/test_query_analyzer.py
  • uv run --frozen ruff check hindsight_api/engine/chinese_temporal_periods.py tests/test_query_analyzer.py
  • python3 -m py_compile hindsight_api/engine/chinese_temporal_periods.py
  • git diff --check

@benfrank241 benfrank241 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for this — the root cause is right and i reproduced the original #2533 crash: 过去一年做了什么 with a reference date in year 1 hits add_years(ref, -1) → year 0 → ValueError('year 0 is out of range'), exactly the consolidation failure. and 过去一年 at year 1 now correctly degrades to no-constraint, year 2 still extracts. nice.

one thing blocks merge though: the guard isn't threaded all the way through. at line 962 (relative_year_fixed_day_match) you converted the d computation to add_days(...) (which returns None on underflow) but left the call as bare constraint(d, d) instead of safe_constraint(d, d) — so None still reaches constraint. it just trades one crash for another:

去年今天    ref year=1:     AttributeError: 'NoneType' object has no attribute 'replace'
大前年今天  ref year=1,2,3: AttributeError
去年昨天    ref year=1:     AttributeError

same failure mode (impossible year shift during consolidation search), so consolidation still breaks on these. the sibling relative_year_daypart_match block right above it already does the right thing with safe_constraint — line 962 just needs the same swap.

could you:

  • swap constraint(d, d)safe_constraint(d, d) at line 962
  • add a regression covering 去年今天 (and ideally 大前年今天) at a low reference year, asserting no-constraint rather than a crash
  • while you're in there, quick audit of the remaining bare constraint(d, d) calls fed by a year-shifted base so we don't leave another one — the 前晚|前夜 one can also underflow at the absolute datetime.min boundary

once that's in i'll re-verify and merge.

@r266-tech

Copy link
Copy Markdown
Contributor Author

Thanks — pushed the sibling coverage update in cb6b4468.

Changes made:

  • relative_year_fixed_day_match now returns through safe_constraint(d, d).
  • add_days() now returns None on datetime underflow/overflow, so callers can degrade to no temporal constraint instead of raising.
  • 昨晚 / 前晚 daypart shortcuts now use add_days() + safe_constraint() too.
  • Added low-year regressions for 去年今天, 大前年今天, 去年昨天, 昨晚, and 前晚, plus a valid low-year 去年今天 extraction check.

Checks run locally:

  • uv run --frozen pytest hindsight-api-slim/tests/test_query_analyzer.py -q (382 passed)
  • uv run --frozen ruff format --check hindsight-api-slim/hindsight_api/engine/chinese_temporal_periods.py hindsight-api-slim/tests/test_query_analyzer.py
  • uv run --frozen ruff check hindsight-api-slim/hindsight_api/engine/chinese_temporal_periods.py hindsight-api-slim/tests/test_query_analyzer.py
  • python3 -m py_compile hindsight-api-slim/hindsight_api/engine/chinese_temporal_periods.py hindsight-api-slim/tests/test_query_analyzer.py
  • git diff --check

@benfrank241 benfrank241 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack — sibling coverage complete, verified. the three cases that still crashed on the first pass (去年今天, 大前年今天, 去年昨天 at ref year 1) now cleanly return NO_CONSTRAINT instead of AttributeError, add_days() degrades on underflow, and the 昨晚/前晚 daypart paths are on safe_constraint too. valid low-year windows still extract (去年今天 ref=2 → range), so no over-degrading. 382 pass locally. superseding my earlier changes-requested.

approving + kicked off CI.

@benfrank241
benfrank241 merged commit 5f1a867 into vectorize-io:main Jul 13, 2026
87 checks passed
nicoloboschi added a commit that referenced this pull request Aug 12, 2026
…3217) (#3413)

Consolidation recalls with stored fact text as the query, so a phrase
like "十万年前" (100,000 years ago) hit unguarded offset arithmetic in
extract_period — which runs BEFORE analyze()'s dateparser guard — and
escaped as ValueError('year -97974 is out of range'), deterministically
failing every recall and consolidation touching the bank. The three
years observed in #3217 (-534, -974, -97974) are exactly
now.year - {2560, 3000, 100000}: query-time arithmetic, not stored rows
(Python datetimes can't represent them, so no bad date can reach the DB
through asyncpg in the first place).

Three layers, mirroring the #2636 add_years fix:

- extract_temporal_constraint (the recall choke point) degrades any
  analyzer failure to 'no temporal signal' with a warning; analyze()
  itself stays strict so parser bugs still surface in tests.
- chinese_temporal_periods: add_months/subtract_months are now
  bounds-checked like add_years (returning None, plumbed through every
  call site), and day/week offsets go through the overflow-guarded
  add_days instead of raw timedelta addition.
- temporal_periods: an explicit month + year 0000 match returns
  NO_TEMPORAL_CONSTRAINT instead of crashing datetime().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidation task not working because ValueError('year 0 is out of range')

2 participants