Repository navigation
feat(wiki): extract_claims reads a claim's stated event time (#717, slice 3) - #923
Conversation
…lice 3)
ClaimEvent.event_time (slice 1) is persisted (slice 2) but extract_claims
never set it, so every extracted claim stayed unknown even when its sentence
states a date. core/claim_event_time.stated_event_time reads the date the
sentence writes, at the precision written: "March 2026" is a month, "12 March
2026" or "March 12, 2026" a day, ISO 8601 extended dates and times a month,
day, minute or second, with their stated offset (naive when none is stated).
shared/temporal_normalize.normalize_date_to_iso is not reused: it fills the
components a source omits ("March 2026" -> 2026-03-06, "2019" -> 2019-11-06,
measured 2026-11-06), the invented precision TimeBound refuses.
No date, a bare year, a relative date, an impossible date or offset,
fractional seconds, or two different dates in one sentence leave the event
time unknown; it is never derived from extracted_at.
Refs #717
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYu4w7Hd9Zs3AWTyLgR
|
Claude finished @cdeust's task in 2m 44s —— View job PR #923 review (1 of 3)
No Blockers. The review lists 3 Notes that need no action before merge:
I traced these by hand; I couldn't run Python in this environment. Verdict: approve |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdfe08eb13
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| digits = raw[1:].replace(":", "") | ||
| delta = timedelta(hours=int(digits[:2]), minutes=int(digits[2:])) | ||
| return timezone(-delta if raw[0] == "-" else delta) |
There was a problem hiding this comment.
Validate offset minutes before constructing the timedelta
When a claim contains an invalid offset such as 2026-03-12T10:30+01:99, the regex accepts it and timedelta normalizes the offset to +02:39; timezone does not reject it because the total remains below 24 hours. Consequently, extraction records a definite event time with an invented offset instead of leaving the impossible timestamp unknown. Validate the minute component before constructing the duration, and raise ValueError for out-of-range values so the existing fallback handles them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Corrigé dans 7efde80. _offset rejette maintenant des minutes au-delà de 59 (_MAX_OFFSET_MINUTE, ISO 8601-1:2019 §5.3.4). Le ValueError fait tomber l'heure à unknown. Test ajouté : +05:60 donne unknown, +05:59 reste un décalage valide.
Generated by Claude Code
| _ISO_RE = re.compile( | ||
| r"(?<![\w-])(\d{4})-(\d{2})(?:-(\d{2})" | ||
| r"(?:T(\d{2}):(\d{2})(?::(\d{2}))?(Z|[+-]\d{2}:?\d{2})?)?)?" | ||
| r"(?![\w:+-]|\.\d)" |
There was a problem hiding this comment.
Allow a colon delimiter after a complete calendar date
For dated entries such as 2026-03-12: We decided to drop Redis. or Measured on 2026-03-12: latency was 10 ms., the trailing negative lookahead rejects the colon and the regex finds no date. Both entries are classified by extract_claims, but their event times incorrectly remain unknown despite stating a supported, unambiguous ISO day. Distinguish punctuation following a complete calendar date from a colon continuing a time component, rather than excluding colons after every supported form.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Corrigé dans 7efde80. Le lookahead n'exclut plus qu'un deux-points suivi d'un chiffre ([:.,]\d), c'est-à-dire la suite d'une heure. 2026-03-12: we decided… et Measured on 2026-03: … donnent maintenant un point, et 10:30:15.1 et 10:30:15,5 restent unknown.
Generated by Claude Code
| entity_ids=entity_ids or [], | ||
| evidence_refs=refs, | ||
| confidence=confidence, | ||
| event_time=stated_event_time(sent), |
There was a problem hiding this comment.
Keep dates in neighboring sentences out of the claim's event time
When a paragraph is shorter than 120 characters, _split_sentences deliberately returns the whole paragraph without splitting its sentences; headings and list paragraphs also bypass sentence splitting. For example, We decided to drop Redis. On March 12, 2026 we measured throughput. produces a decision claim, and this call assigns the measurement's date to that decision even though its own sentence is undated. Ensure temporal extraction operates on the sentence supplying the classified claim, or leave the time unknown when the date belongs to another sentence in an atomic paragraph.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur cdfe08e : la claim decision recevait la date de la mesure. Corrigé dans 7efde80. Quand une unité contient plusieurs phrases (paragraphe court, titre ou item de liste gardés entiers), son event_time reste unknown : la date peut appartenir à l'autre phrase. La frontière de phrase n'est définie qu'à un endroit (claim_event_time.SENTENCE_END), et l'extracteur découpe sur cette même définition. Test : test_a_date_in_a_neighbouring_sentence_does_not_date_the_claim.
Generated by Claude Code
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Review 1 of 3 — PR #923 (
|
…717, slice 3) Codex review on cdfe08e: a short paragraph, heading or list item reaches stated_event_time whole, so "We decided to drop Redis. On March 12, 2026 we measured throughput." dated the decision with the measurement's date. A unit holding several sentences now stays unknown; the sentence boundary is defined once (claim_event_time.SENTENCE_END) and the extractor splits on it. Codex review: "2026-03-12: we decided ..." was refused because every colon after a date was read as a time continuation. Only a colon followed by a digit is one now. Claude review 1, Notes 1 and 3: an offset with minutes above 59 ("+05:99") was accepted as +06:39, and a comma decimal ("10:30:15,5") matched at second precision; both now leave the event time unknown, as the docstring states. Note 2 (a year range such as "2010-11" reads as a month) is documented. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_018kyZYu4w7Hd9Zs3AWTyLgR
|
Claude finished @cdeust's task in 2m 43s —— View job PR #923 review (2 of 3)
No Blockers. The fixup does what it says for each review comment. The review lists 3 Notes that need no action before merge:
I traced these by hand; I couldn't run Python in this environment. Verdict: approve |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7efde809a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| distinct = {(b.value, b.value.utcoffset(), b.precision): b for b in bounds} | ||
| if len(distinct) != 1: | ||
| return EventTime() | ||
| return EventTime(kind="point", start=next(iter(distinct.values()))) |
There was a problem hiding this comment.
Leave abbreviated date ranges unknown
For a claim such as We measured throughput from 10 to 12 March 2026., the named-date matcher recognizes only 12 March 2026, so this return assigns a definite point on March 12 even though the measurement explicitly spans multiple days. The same happens with between 10 and 12 March 2026. Checking the number of complete date matches does not detect ranges whose endpoints share a month and year; reject these range constructions before promoting the matched endpoint to a point, keeping them unknown until interval extraction is supported.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur 7efde80, corrigé dans 741cdd1. Une phrase qui énonce un intervalle à côté d'une date (from 10 to 12 March 2026, between 10 and 12 March 2026, 10-12 March 2026, 10–12 March 2026, since 2026-01, until March 2026) reste unknown (_RANGE_RE), en attendant l'extraction des intervalles. We switched from Redis on 2026-03-12. reste un point, parce que from n'y précède pas une date.
Generated by Claude Code
| for m in _ISO_RE.finditer(sentence): | ||
| parts = [int(g) for g in m.groups()[:6] if g is not None] | ||
| bounds.append(_bound(parts, _offset(m.group(7)))) |
There was a problem hiding this comment.
Exclude reference targets from event-date matching
When a claim contains a dated URL or file path, scanning the raw sentence treats the reference's date as the claim's event time. For example, extract_claims('We decided to use https://example.org/2026-03-12/redis.') produces a decision dated March 12 even though no decision date is stated; We decided to use ./2026-03-12/report.md as the source. behaves the same way. _extract_evidence already recognizes these spans as URL/file references, but they are not excluded from temporal matching. Mask reference targets before matching dates so an undated claim does not acquire an unrelated reference date.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur 7efde80, corrigé dans 741cdd1. Une date ISO ne compte maintenant que si elle forme un mot à elle seule, à la ponctuation près (_is_own_word). Une date prise dans une URL, un chemin ou un nom de fichier (https://example.org/2026-03-12/redis, ./2026-03-12/report.md, 2026-03-12.md) est une référence : elle n'est pas lue. Une vraie date ailleurs dans la phrase reste lue (See ./2026-01-05/log for the 2026-03-12 run. donne le 12 mars).
Generated by Claude Code
Review 2 of 3: PR #923,
|
…717, slice 3) Codex review on 7efde80: a date inside a reference dated an undated claim ("We decided to use ./2026-03-12/report.md"); an ISO date now counts only when it is its own word, punctuation aside. Interval wording next to a date ("from 10 to 12 March 2026", "between ...", "10-12 March", "since 2026-01", "until March 2026") was promoted to a point on one endpoint; it now stays unknown until intervals are extracted. Claude review 2, Note 1: a unit the extractor keeps whole but that spans lines ("# Release 2026-03\nWe decided ...") lent its date to the claim, which the docstring said it did not; a unit holding a newline now stays unknown. Note 2 ("2026-03-12,2026-03-13" read as one point) is closed by the own-word rule; Note 3 adds the positive cases followed by "." and ",". Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_018kyZYu4w7Hd9Zs3AWTyLgR
|
Claude finished @cdeust's task in 2m 47s —— View job PR #923 review (3 of 3)
No Blockers. The whole PR is accepted (
Verdict: approve |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 741cdd1048
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| rf"(?i:\b(?:from|between|since|until|till|through))\s+(?:\d|(?:{_MONTH_GROUP})\b)" | ||
| rf"|\b\d{{1,2}}\s*(?:-|\u2013|\u2014|to|and)\s*\d{{1,2}}\s+(?:{_MONTH_GROUP})\b" |
There was a problem hiding this comment.
Restrict interval detection to temporal ranges
For On March 12, 2026 we improved throughput from 10 to 12 requests per second., extraction returns unknown because this pattern matches from 1, even though the range describes throughput rather than dates. Likewise, On 2026-03-12 we switched from 3 replicas to 5 replicas. loses its explicit event date. Require the interval wording to connect to a date expression instead of rejecting any sentence containing a range introduced by a number.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur 741cdd1. Je reporte ce point à une PR de suite, sans le pousser ici : la revue 3/3 a accepté la PR et le budget de trois revues (ADR-1119) est épuisé. Le détail et la correction prévue sont dans #923 (comment).
Generated by Claude Code
| before = re.split(r"\s", sentence[: m.start()])[-1] | ||
| after = re.split(r"\s", sentence[m.end() :])[0] | ||
| return not before.strip(_OPENERS) and not after.strip(_CLOSERS) |
There was a problem hiding this comment.
Preserve ISO dates wrapped in Markdown formatting
For Markdown claims such as We decided to drop Redis on `2026-03-12`. or We decided to drop Redis on **2026-03-12**., _is_own_word rejects the date because backticks and emphasis markers are not permitted wrappers. Both claims therefore remain unknown, although the identical unformatted date produces a point; named-month dates do not have this restriction. Recognize Markdown wrappers without treating them as parts of a reference target.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur 741cdd1. Je reporte ce point à une PR de suite, sans le pousser ici : la revue 3/3 a accepté la PR et le budget de trois revues (ADR-1119) est épuisé. Le détail et la correction prévue sont dans #923 (comment).
Generated by Claude Code
| rf"\b(?:(?P<day>\d{{1,2}})\s+(?P<month>{_MONTH_GROUP})" | ||
| rf"|(?P<month_first>{_MONTH_GROUP})\s+(?P<day_second>\d{{1,2}}),?" | ||
| rf"|(?P<month_only>{_MONTH_GROUP}))\s+(?P<year>\d{{4}})\b" |
There was a problem hiding this comment.
Avoid matching only the month inside an ordinal day date
For We decided to drop Redis on 12th March 2026., the day-first alternative fails on 12th, but the month-only alternative then matches March 2026, producing month precision instead of the stated day. More seriously, We decided to drop Redis on 31st February 2026. becomes a definite February point rather than remaining unknown as an impossible date. Recognize and validate ordinal day components, or prevent the month-only fallback from consuming a suffix of an unsupported day-date expression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Reproduit sur 741cdd1. Je reporte ce point à une PR de suite, sans le pousser ici : la revue 3/3 a accepté la PR et le budget de trois revues (ADR-1119) est épuisé. Le détail et la correction prévue sont dans #923 (comment).
Generated by Claude Code
Review 3 of 3: PR #923,
|
|
Points reportés à une PR de suite, pas à celle-ci. La revue 3/3 a accepté la PR entière, sans bloquant. Le budget de trois revues (ADR-1119) est épuisé. Je n'ai donc rien poussé après J'ai reproduit chaque point sur Dates perdues. Le résultat est
Précision ou forme mal lues.
La PR de suite les traitera ensemble. L'extraction des intervalles, prévue en tranche 4 de #717, remplacera de toute façon Generated by Claude Code |
Summary
Slice 3 of #717.
ClaimEvent.event_timeexists (slice 1, #875) and is persisted (slice 2, #894), butextract_claimsnever set it: every extracted claim stayedunknown, even when its sentence states a date. The newcore/claim_event_time.stated_event_timereads the date written in the claim's own sentence, at the precision written, andextract_claimspasses it to eachClaimEvent.Refs #717 (not
Closes: slice 4, carrying the field through synthesis and retrieval, remains).Root cause and design. Nothing turned a stated date into an
EventTime.shared/temporal_normalize.normalize_date_to_isois not reused: it fills the components a source leaves out. Measured 2026-11-06 (CPython 3.11, dateutil fromuv.lock):"March 2026"gives2026-03-06and"2019"gives2019-11-06. That is the invented precisionTimeBoundrefuses. What gets read:March 2026gives month precision.12 March 2026,March 12, 2026andMarch 12 2026give day precision. Month names must be capitalised, so the modal "may" is never read as a month.YYYY-MM,YYYY-MM-DD,…THH:MMand…THH:MM:SSgive month, day, minute and second precision. A stated offset (Z,±HH:MM,±HHMM) is kept. With no stated zone, the value stays naive.unknown, because reading them would mean inventing something: no date at all, a bare year (it cannot be told apart from a citation like "Smith 1992"), a relative date, an impossible date or offset, fractional seconds (finer thanTemporalPrecision), and a sentence with two different dates. The same date written twice counts as one point.Type of change
Test plan
pytest tests_py/core/test_claim_event_time.py: 35 passed (new).tests_py/shared/test_wiki_ir_event_time.py,tests_py/infrastructure/test_claim_event_time_persistence.pyand every test file that mentionsclaim/wiki_extract): 1321 passed, 25 skipped (PostgreSQL is not available here).GITHUB_BASE_REF=main python scripts/ci_mutation_diff.py:2 file(s), 0 failed, 0 untested. A first version left 7 survivors: dead"year"/"hour"entries and a redundant overlap check. The code was simplified so they cannot occur (an exact precision table, a single alternation regex). No equivalents were registered.python scripts/check_craftsmanship.py: OK.ruff checkandruff format --check(0.17.3, pinned inuv.lock) pass on the changed files. The 8E501errors and 7 unformatted files reported across the repo are the same onmain.extracted_at(test_a_historical_event_keeps_its_date_apart_from_extraction).Not verified here: the full suite and pyright (torch cannot be downloaded through this environment's proxy), and the PostgreSQL variants. CI is the reference.
Audit notes
event_timethrough synthesis and retrieval, with corrections and contradictory dates keeping their provenance and uncertainty. Interval extraction ("from X to Y") staysunknownin this slice and belongs with it.Coding-standards compliance
core/claim_event_time.pyimports onlyshared/and the stdlib.Breaking changes
None. Claims without a stated date keep
{"kind": "unknown"}.Reviewer checklist
Generated by Claude Code