Scripts/retrieval/time_adapter.py compiles semantic windows (for example, yesterday, past_week) into source-specific physical predicates that can be executed consistently by both Gold (Qdrant) and Silver (DuckDB).
This prevents temporal mismatches caused by mixed data cadences (event-level, daily, monthly).
[Semantic TimeWindow + Anchor Date]
-> compile_predicate(source_key)
-> SourceTimeSpec lookup (granularity, keys, units, min lookback)
-> widening / business-day policy
-> TimePredicate(start/end date + epoch + reason)
-> compile_all()
-> one predicate per registered source
-> predicates_from_serialised()
-> round-trip deserialization from state["time_range"]["source_predicates"]
-> used by rescue paths and Checker re-audit
-> union_epoch_range(predicates)
-> merges min(start) / max(end) across a predicate list
-> consumed by qdrant_retriever.py for mixed-source epoch filters
-> serialized payload consumed by MasterRetriever / Gold / Silver
flowchart TD
A[Input TimeWindow + Anchor] --> B[Lookup SourceTimeSpec]
B --> C[Resolve base_days via shared time policy]
C --> D{Granularity}
D -->|DAILY| E[Business-day alignment and min lookback policy]
D -->|MONTHLY| F[Month-safe widening policy]
D -->|EVENT| G[Event min lookback policy]
E --> H[Build start/end epoch bounds]
F --> H
G --> H
H --> I[Emit TimePredicate]
I --> J[serialize]
J --> K[MasterRetriever time_range.source_predicates]
Policy strategy:
- Daily sources support business-day semantics for
todayandyesterday. - Monthly sources widen short windows to preserve latest monthly observation viability.
- Event sources enforce minimum lookback to avoid empty windows around weekends.
- Predicates are compiled once and reused, preventing cross-retry drift.
Two-stage widening logic inside compile_predicate:
Stage A — DAILY sources with a non-business anchor date: end is snapped to previous_business_day(anchor) before start is computed. This prevents weekend gaps from producing a zero-length window.
Stage B — min_lookback_days floor: if the computed window is shorter than the source's minimum lookback, the window is widened:
- MONTHLY:
startsnaps to first of the anchor month. - DAILY (non-
yesterdaylabels):startfloors tomin_lookback_dayscalendar days back fromend. - EVENT:
startfloors tomin_lookback_dayscalendar days back fromend.
When widening is applied, widened=True and widen_reason carries a deterministic reason string for audit.
| Schema | Type | Description | Path |
|---|---|---|---|
SourceTimeKey |
Enum | Stable key namespace (gold.news, silver.options, etc.) |
Scripts/retrieval/time_adapter.py |
SourceTimeSpec |
Dataclass | Physical contract (granularity, keys, units, lookback floor) | Scripts/retrieval/time_adapter.py |
SOURCE_SPECS |
Dict | Authoritative registry for all retrieval sources | Scripts/retrieval/time_adapter.py |
SOURCE_SPECS registry — authoritative per-source physical contracts:
| Source key | Granularity | Example time keys | min_lookback_days |
|---|---|---|---|
gold.news |
EVENT | unified_timestamp, publish_timestamp |
1 |
gold.sec |
EVENT | filed_at_epoch_s, transaction_date_epoch_s, filed_at (ISO), transaction_date (ISO) |
3 |
gold.gpr |
MONTHLY | unified_timestamp, publish_timestamp |
35 |
silver.options |
DAILY | snapshot_date |
3 |
silver.macro |
DAILY | observation_date, retrieval_date |
3 |
silver.gpr |
MONTHLY | date, month (ts_ns) |
35 |
Gold sources use epoch-second keys for Qdrant range filters; Silver sources use date-style keys for DuckDB SQL BETWEEN clauses. The key_units field in TimePredicate distinguishes epoch_s (Gold) from date (Silver) so each consumer can bind the correct predicate field.
| Field | Type | Description | Path |
|---|---|---|---|
source |
SourceTimeKey |
Logical source binding | Scripts/retrieval/time_adapter.py |
label |
str |
Human-readable window label | Scripts/retrieval/time_adapter.py |
granularity |
TimeGranularity |
Event/Daily/Monthly physical cadence | Scripts/retrieval/time_adapter.py |
start_date, end_date |
date |
Date bounds for SQL-style consumers | Scripts/retrieval/time_adapter.py |
start_epoch_s, end_epoch_s |
int |
Epoch bounds for Qdrant numeric range filters | Scripts/retrieval/time_adapter.py |
window_days |
int |
Effective compiled lookback days | Scripts/retrieval/time_adapter.py |
widened |
bool |
Whether widening was applied | Scripts/retrieval/time_adapter.py |
widen_reason |
str | None |
Deterministic widening reason string | Scripts/retrieval/time_adapter.py |
time_keys, key_units |
Tuple[str, ...] |
Physical keys and units for consumers | Scripts/retrieval/time_adapter.py |
| Artifact | Description | Path |
|---|---|---|
time_range.source_predicates |
Serialized predicate map persisted in retrieval payload | Scripts/retrieval/master_retriever.py |
python -c "from datetime import date; from Scripts.retrieval.time_adapter import compile_all; from Scripts.retrieval.schema import TimeWindow; print({k.value:v.to_dict() for k,v in compile_all(TimeWindow.PAST_WEEK, date.today()).items()})"
python Scripts/tests/test_router_e2e.pyValidation focus:
- predicates differ appropriately across
gold.gpr(monthly) andsilver.options(daily), yesterdaysemantics are business-day safe,- serialized predicates can be round-tripped via
predicates_from_serialised().
Scripts/retrieval/time_adapter.pyScripts/retrieval/schema.pyScripts/retrieval/master_retriever.pyScripts/retrieval/qdrant_retriever.pyScripts/retrieval/sql_tools.py