Skip to content

feat(ask): bind relative-time filters to event time (v2.13.0) - #581

Open
seonghobae wants to merge 10 commits into
mainfrom
feat/ask-event-time-filter-v2130
Open

feat(ask): bind relative-time filters to event time (v2.13.0)#581
seonghobae wants to merge 10 commits into
mainfrom
feat/ask-event-time-filter-v2130

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #569. Global Ask relative-time questions (어제, 그저께, 지난주) now bind to the source-system event instant, not record ingestion time (ADR 0168).

Bulk imports cluster created_at near import time, so 어제 무슨 일이 있었나요? used to miss yesterday's events. Ask now compares coalesce(event_occurred_at, created_at) on the Asia/Seoul calendar. Missing event time falls back to created_at and is named as that fallback — never invented.

After make seed, leftover closest/farthest pairs still sit above the member list; a click still opens that post. Ask 어제 무슨 일이 있었나요? on bulk-imported fixtures that share one ingest day keeps the post whose event fell yesterday, drops last week's event, and names Time axis on the cited evidence. Open that cited post to read which clock matched.

Decision (ADR 0168)

  • Persist nullable source_post.event_occurred_at (Allen 1983; W3C Time; PROV-O prov:generatedAtTime stays on created_at).
  • Event time wins. Ingestion time is the documented fallback.
  • Cited sources disclose time axis: event occurred at or time axis: record created at.
  • Importers may map --event-occurred-at-column. Unmapped imports leave the column null.
  • Seed copies fixture occurred_at onto both clocks so reconstruct and leftover pairs stay on the designed January timeline.
  • Analysis-run knowledge cutoff and period-report week membership keep created_at.

Tests

  • uv run --python 3.12 --extra dev --with asyncpg pytest tests/test_ask_time_axis.py tests/test_global_ask_sources.py tests/test_post_chat.py tests/test_import_postgresql_posts.py tests/test_temporal_expressions.py — 90 passed, 2 skipped.
  • pnpm exec vitest run src/components/AskEvidenceLayerPopup.test.tsx — 9 passed.

Synthetic fixtures only. Independent APPROVE required. Do not self-approve. Squash-merge only with independent APPROVE + exact-head required success.


Open in Devin Review

Global Ask 어제/그저께/지난주 now compares coalesce(event_occurred_at,
created_at) on the Seoul calendar. Clustered bulk imports keep
yesterday's events retrievable. Cited evidence names Time axis so a
click still opens that post (ADR 0168).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Contributor Author

Cycle note 2026-08-24T12:24Z.

Head ad845b94bb5685bd5f57b32b68c56247212b7b21 implements ADR 0168 / #569 on feat/ask-event-time-filter-v2130.

Independent APPROVE is required. I will not self-approve or squash-merge this PR. Squash-merge only with an independent APPROVE on this exact head plus required checks success.

Copilot review requested. seonghobae is currently the only collaborator, so a human independent APPROVE still has to come from outside this loop.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 39 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 01f449c8-8fb7-45a1-b95e-41e176dcca1a

📥 Commits

Reviewing files that changed from the base of the PR and between 98e37fe and c4f41dc.

📒 Files selected for processing (26)
  • AGENTS.md
  • ARCHITECTURE.md
  • CHANGELOG.d/2.13.0-ask-event-time-filter.md
  • CHANGELOG.md
  • CLAUDE.md
  • backend/app/post_chat_ingestion.py
  • backend/tests/test_api.py
  • docs/adr/0150-korean-relative-time-retrieval.md
  • docs/adr/0202-ask-event-time-filter.md
  • docs/adr/README.md
  • frontend/src/components/AskEvidenceLayerPopup.stories.test.ts
  • frontend/src/components/AskEvidenceLayerPopup.stories.tsx
  • frontend/src/components/AskEvidenceLayerPopup.test.tsx
  • frontend/src/evidenceKindLabels.ts
  • frontend/src/i18n.ts
  • lineageweave/ask_time_axis.py
  • lineageweave/post_chat.py
  • lineageweave/temporal_expressions.py
  • migrations/0183_source_post_event_occurred_at.sql
  • migrations/rollback/0183_source_post_event_occurred_at.sql
  • scripts/import_postgresql_posts.py
  • scripts/seed_demo_data.py
  • tests/test_ask_time_axis.py
  • tests/test_global_ask_sources.py
  • tests/test_import_postgresql_posts.py
  • tests/test_post_chat.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copilot/human independent review requested. Do not treat this comment as APPROVE.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Open in Devin Review

@devin-ai-integration devin-ai-integration Bot Aug 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Event-time binding does not reach candidate discovery

The keyword candidate subqueries still order by created_at desc limit 32 per term; the event-time window is applied only in the final rows query. When a bulk import clusters created_at and a term has more than 32 matches, a post whose event fell yesterday can be dropped at the discovery limit before the event-time filter runs. Pre-existing, but it undercuts the PR goal for large clustered corpora.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread frontend/src/components/AskEvidenceLayerPopup.stories.tsx
Comment on lines +567 to +571
visible_rows = [
row
for row in rows
if can_see_post(row) and row_matches_time_range(row, resolved_time_range)
][:limit]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Time filter applied twice

gather_global_chat_sources bounds coalesce(event_occurred_at, created_at) in SQL and re-checks the same window in Python via row_matches_time_range. The Python pass is redundant in production but is what actually enforces the window for the test FakeConnections, which ignore SQL WHERE clauses. row_matches_time_range keeps rows whose clock is absent, which cannot occur in production where created_at is NOT NULL.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions
github-actions Bot disabled auto-merge August 24, 2026 15:15
…ilter-v2130

# Conflicts:
#	CHANGELOG.md
#	CLAUDE.md
#	frontend/package.json
#	pyproject.toml
#	uv.lock
The post-merge seeded_db fixture created source_post rows with
event_occurred_at (scripts/seed_demo_data.py::insert_fixture_source_posts)
but never applied migrations/0168_source_post_event_occurred_at.sql,
so six backend/tests/test_api.py tests failed with UndefinedColumn.
Wire the migration in alongside the other seeded_db migrations.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +567 to +571
visible_rows = [
row
for row in rows
if can_see_post(row) and row_matches_time_range(row, resolved_time_range)
][:limit]

@devin-ai-integration devin-ai-integration Bot Aug 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Time filter drops lineage-context neighbors

The top keyword match is expanded through its post_lineage_edge neighbors for before/after context (post_chat_ingestion.py), then those neighbors pass through the same event-time filter (post_chat_ingestion.py). A narrow window like "어제" drops any neighbor whose event fell on another day, largely defeating the expansion. This matches ADR 0168's narrowing but conflicts with the expansion's stated purpose.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae enabled auto-merge August 24, 2026 16:13
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 16:51
@seonghobae
seonghobae enabled auto-merge August 24, 2026 17:03
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 17:59
…ilter-v2130

# Conflicts:
#	CHANGELOG.md
#	backend/app/post_chat_ingestion.py
#	backend/tests/test_api.py
#	frontend/package.json
#	pyproject.toml
#	uv.lock
@seonghobae
seonghobae enabled auto-merge August 24, 2026 18:56

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines 572 to 574
resolved_time_range[0] if resolved_time_range else None,
resolved_time_range[1] if resolved_time_range else None,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Time-only questions return the whole window

When a temporal question has no non-stopword keywords, candidate_ids is empty and the final query (post_chat_ingestion.py) uses it only for ordering, not filtering. Every visible post inside the resolved window is then returned up to limit, ordered by event time. Pre-existing behavior, unchanged here.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread migrations/0183_source_post_event_occurred_at.sql
@github-actions
github-actions Bot disabled auto-merge August 24, 2026 19:05
…ilter-v2130

# Conflicts:
#	backend/tests/test_api.py
#	frontend/package.json
#	pyproject.toml
#	uv.lock

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Event-time filter narrows candidates, does not discover by date

The relative-time window bounds the keyword-ranked candidate set; it does not surface posts by date alone. '어제 무슨 일이 있었나요?' still needs the yesterday-event post to match a residual generic term (무슨/일이/있었나요) to enter candidacy. The test_global_sources_bind_relative_time_to_event_clock_not_ingest_cluster FakeConnection returns rows for any term, so real-DB recall is not exercised. Consistent with ADR 0150's stated design.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +70 to +82
def row_matches_time_range(
row: Any,
time_range: tuple[date, date] | None,
) -> bool:
"""Keep rows inside the Seoul window, or keep them when clocks are absent."""
if time_range is None:
return True
instant = ask_filter_instant(row)
day = seoul_calendar_date(instant)
if day is None:
return True
start, end = time_range
return start <= day <= end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Overflow window excludes all rows and adds no axis fact

resolve_korean_relative_time returns _EMPTY_DATE_RANGE (date.max, date.min) on overflow (temporal_expressions.py). That is non-None, so time_filter_active becomes True and both the SQL bound and row_matches_time_range drop every row, yielding no sources and no time-axis fact. 언젠가 still returns None. Pre-existing, unaffected here.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae enabled auto-merge August 24, 2026 22:46
@github-actions
github-actions Bot disabled auto-merge August 24, 2026 23:06
Main's 0183 slot is taken by the GNB four-Korean-chrome ADR (#587), so the
incoming ask-event-time-filter ADR moves to the next free number (0202).
All references across AGENTS.md/CLAUDE.md/ARCHITECTURE.md, docstrings,
ADR 0150 related links, seed comments, and changelog fragments follow.
CHANGELOG.md keeps both the 2.13.0 and 2.15.0 entries in descending order.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread AGENTS.md
`report_leftover_map_coverage` and captions the pair list with how
many scored posts entered the map.

Global Ask relative-time filters (ADR 0150 / 0183) bind to

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Event-time feature cites unrelated ADR number

The added rule cites ADR 0150 / 0183, but 0183 is the GNB-chrome ADR (docs/adr/0183-gnb-four-korean-chrome.md); the event-time decision is ADR 0202. 0183_source_post_event_occurred_at.sql repeats the same wrong citation. Readers are sent to an unrelated ADR.

Suggested change
Global Ask relative-time filters (ADR 0150 / 0183) bind to
Global Ask relative-time filters (ADR 0150 / 0202) bind to
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

[Data] Temporal Ask filters should bind to event time, not record ingestion time

1 participant