Repository navigation
feat(agent): date_number turns a date column into numbers - #12558
Merged
Merged
Conversation
The owner's test of 2026-11-04: a customers table on scatter-basic had Index on x, nothing for y but its subscription dates, and "convert subscription date to integer" had no operation (set_type to number reads text cells, derive takes number columns, make_date goes the other way). - prepare.py: `date_number` (column, unit year|days|timestamp|decimal_year, name) adds a number column beside the date column. Days count calendar dates from the earliest (0); the timestamp is Unix seconds in UTC, a date at its midnight UTC, a time without an offset read as UTC, an offset converted, fractions kept (with a count of values a float64 rounds); year, days and decimal year keep the column's clock. Missing dates stay empty and are counted; a non-date column is a type_mismatch with a set_type hint. Without `name` the column is named after its source and unit in the session language (`apply_operations(language=)`). The new column goes into `Prepared.converted`, so it fills an open number role; undo works as for every version. - conversions.py: an open required role that takes numbers only, which no conversion or row number fills, gets an offer line for a free date column (`DateNumbers`, never certain). `suffixed_name` moved to prepare.py for both. - root.md: the op and the offer bullet (one statement, never a question or an invitation; a request without a unit takes days). - session/query tools pass the session language; telemetry PrepOp, eval describe, three prep fixtures (en, de, a summary that only offers). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
root.md: an offer waits for the user's request, unlike a certain line, which the root makes without asking (the earlier "never make it unasked" could read as covering every conversion). agents/README.md: the three date_number prep cases and their live runs on Claude Haiku 5.5 (eu). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The one conflict was the prep fixture count in test_prep.py: 71 cases on the old main, plus the four plot-type cases of #12557 and the three date_number cases of this branch, is 78. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…vitations Flash on 4f1166d (no correctness, regression or safety finding) named three test gaps, each now with a test and a mutant it catches: - days and decimal_year on 2025-01-01T00:30+02:00 keep the column's clock (break: `_wall` converting to UTC first); - a date_number on data whose x and y are bound leaves the bindings as they are (break: `_new_numbers` taking a bound role); - an all-missing date column is no offer (break: the `missing < rows` clause dropped). root.md: the offer bullet ends the offer at the statement, with the imperative closers named as invitations; the conversions line says "never as a question or a request to choose". 2 of 5 earlier summary replies had closed with "Choose the y column in the data panel, or name the unit you want."; 0 of 6 on the final wording. prep eval: `closes_with_invitation` (a narrow list of choose/tell verbs at the start of the last sentence or clause, never the .confirm() closer) makes a reply miss on the same terms as a closing question, and an open role the block offered a date column for (`offered`) excuses neither. It flags the three live invitations and none of the 37 other replies of this session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Two active nit findings remain: the summary-fixture inventory is outdated, and timestamp rounding detection can report exact far-future whole-second values as rounded.
2 open issues
What changed in this PR
This pull request adds date_number conversion for turning date columns into years, elapsed days, Unix timestamps, or decimal years while preserving the original column. It also adds localized naming, binding and undo support, date-number offers for open numeric roles, and expanded evaluation coverage.
Changes:
- Adds and validates date-to-number preparation and query operations, including timezone handling, missing values, precision reporting, and localized names.
- Adds date conversion offers, binding carryover, undo support, telemetry, prompts, and tool documentation.
- Extends unit, integration, evaluation, fixture, documentation, and changelog coverage, including closing-invitation detection.
Review status: This pull request requires changes.
| File | Summary | Review notes |
|---|---|---|
tests/unit/agents/runtime/test_service_flow.py |
Covers the German end-to-end offer, conversion, binding, and undo flow. | — |
tests/unit/agents/runtime/test_plugins.py |
Validates date_number tool argument safety. |
— |
tests/unit/agents/runtime/test_dataset_versions.py |
Verifies converted bindings and version undo behavior. | — |
tests/unit/agents/runtime/test_data_questions.py |
Verifies localized generated-column names in queries. | — |
tests/unit/agents/evals/test_prep.py |
Tests invitation detection, reply scoring, and offer-aware evaluation. | — |
tests/unit/agents/data/test_prepare.py |
Covers date-number units, timezone behavior, precision, validation, naming, and edge cases. | — |
tests/unit/agents/data/test_conversions.py |
Covers date-number offers, precedence, role compatibility, and fallback behavior. | — |
docs/concepts/agent-network.md |
Documents date-number conversion and offer semantics. | — |
changelog.d/agent-date-number.md |
Records the new feature and evaluation changes. | — |
agents/README.md |
Documents preparation evaluation behavior and date-number fixtures. | Nit: Update the summary-fixture inventory to include the new ninth summary fixture and reflect the 13-case set. |
agents/evals/prep.py |
Detects closing invitations and accounts for date-number offers in reply scoring. | — |
agents/evals/fixtures/prep/en-summary-date-offer.json |
Adds the English summary-only date-number offer fixture. | — |
agents/evals/fixtures/prep/en-date-as-integer.json |
Adds the English date-to-integer fixture. | — |
agents/evals/fixtures/prep/de-datum-als-ganzzahl.json |
Adds the German date-to-integer fixture. | — |
agents/anyplot/tools/session.py |
Exposes date_number and propagates session language. |
— |
agents/anyplot/telemetry.py |
Registers the new preparation operation. | — |
agents/anyplot/prompts/root.md |
Documents the operation, default unit, and offer behavior. | — |
agents/anyplot/data/query.py |
Propagates session language for query-generated names. | — |
agents/anyplot/data/prepare.py |
Implements date-number conversion, date arithmetic, timestamps, naming, and precision reporting. | Nit: Count only timestamp values whose exact microsecond values are actually rounded by float64, rather than all values beyond the magnitude threshold when any fractional timestamp exists. |
agents/anyplot/data/conversions.py |
Adds date-number offers for open numeric roles and integrates them into conversion selection. | Moderate: Restrict date-number offers to roles whose accepted kinds are exactly numeric; roles accepting both text and numeric values must not receive this offer. |
agents/anyplot/agent.py |
Includes date-number offers in the session conversion context. | — |
Review effort: Lite
…ventory Copilot on #12558, two low findings, both applied: - prepare.py: the rounding count of a timestamp column with fractions went by magnitude (2**33 seconds) and so counted a far whole second, which a float64 holds exactly (2300-01-01 is 10413792000.0). It now compares the text data.csv writes (the float's shortest repr) with the exact microseconds. New test for the far whole second; both directions have a mutant that turns a test red. - agents/README.md: "the eight summary cases" had gone stale (13 now); it names the first eight and points to the paragraphs that add the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Codecov on #12558: the empty-reply branch of `closes_with_invitation` was the one line of the patch without a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
MarkusNeusinger
added a commit
that referenced
this pull request
Nov 5, 2026
Conflicts resolved: prepare.py's _Carried keeps main's `language` beside the branch's kept cells and row flag; prep.py's case runner lists the conversions once (main's _listed_conversions) and owes their certain lines on every summary turn, the note's included; the reply-miss docs and the gate's text name the invitation, the long reply and the missing row count; test_prep.py counts 84 cases and keeps both new test groups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
date_numberin the data step.prepare_datatakes{"op": "date_number", "column", "unit": "year" | "days" | "timestamp" | "decimal_year", "name"?}. It adds a number column, appended last, beside a date column, which stays as it is. In the owner's test of 2026-11-04, a customers table onscatter-basichadIndexon x and no column for y butSubscription Date, and "convert subscription date to integer" had no operation:set_typeto number reads text cells,derivetakes number columns, andmake_dategoes the other way.dayscounts calendar dates from the column's earliest date (0).timestampis Unix seconds in UTC. A date counts from its midnight UTC, a time without an offset is read as UTC, and an offset is converted; the change line says which. Whole seconds stay whole numbers for every year from 1 to 9999. A fraction of a second is kept as decimals, and the change line counts the values a float64 rounds (more than 272 years from 1970).year,daysanddecimal_yearfollow the column's own clock, as theaggregatebuckets do.type_mismatchthat tells the root toset_typeit first.name, the column is named after its source and the unit in the session language (apply_operations(language=...), passed byprepare_dataandquery_data).Prepared.converted, so it fills an open role that takes numbers, and.undo()takes it back.conversions.DateNumbers, nevercertain). The root states it in one sentence and makes it only on request; a request without a unit takesdays. An offer never takes the place of a text-column conversion or of the row-number fallback.closes_with_invitationflags a reply whose last sentence or clause asks the user to choose ("Choose …", "Pick one …", "tell me which …", "Wähle …"). Such a reply is a miss on the same terms as a closing question, and an open role that the block offered a date column for no longer excuses either. Three fixtures mirror the owner's case: English, German, and a summary that only offers.Plan
N/A. The design is the owner's decision of 2026-11-04; the docs are in
docs/concepts/agent-network.md("Built since 2026-11-04 (dates as numbers)").Texts to read
Change lines, one per unit
Example table:
Index,Subscription Datewith2021-04-01,2020-02-29and one empty cell.year: new column Subscription Date (year): 2 dates of Subscription Date read as calendar years, 1 cell without a date is empty; Subscription Date stays as it isdays: new column Subscription Date (days): 2 dates of Subscription Date read as whole days since 2020-02-29, the earliest date, which is 0, 1 cell without a date is empty; Subscription Date stays as it istimestamp: new column Subscription Date (Unix time): 2 dates of Subscription Date read as Unix time in seconds (UTC), 1 cell without a date is empty; Subscription Date stays as it is, each date counted from its midnight UTCdecimal_year: new column Subscription Date (decimal year): 2 dates of Subscription Date read as decimal years (the year plus the part of it gone by), 1 cell without a date is empty; Subscription Date stays as it isVariants of the change lines
dayson a column at UTC+02:00: new column t (days): 2 dates of t read as whole days since 2025-01-01, the earliest date, which is 0, by the clock of UTC+02:00; t stays as it istimestampon a column with an offset: new column t (Unix time): 2 dates of t read as Unix time in seconds (UTC); t stays as it is, the moments converted to UTC from UTC+02:00timestampon a column the parser wrote in UTC: new column t (Unix time): 2 dates of t read as Unix time in seconds (UTC); t stays as it is, the moments as stored, in UTCtimestampon times without an offset: new column t (Unix time): 2 dates of t read as Unix time in seconds (UTC); t stays as it is, times without a UTC offset read as UTCtimestampwith fractions of a second: new column t (Unix time): 2 dates of t read as Unix time in seconds (UTC); t stays as it is, times without a UTC offset read as UTC; fractions of a second kept as decimals, 1 value more than 272 years from 1970 rounded to the float's digitsRefusals
Default column names
The server uses these when the root leaves
nameout (prepare.DATE_UNIT_WORDS; any other language takes English).yeardaystimestampdecimal_yearSubscription Date (year)Subscription Date (days)Subscription Date (Unix time)Subscription Date (decimal year)Subscription Date (Jahr)Subscription Date (Tage)Subscription Date (Unix-Zeit)Subscription Date (Dezimaljahr)(année)(jours)(temps Unix)(année décimale)(año)(días)(tiempo Unix)(año decimal)(anno)(giorni)(tempo Unix)(anno decimale)(ano)(dias)(tempo Unix)(ano decimal)(jaar)(dagen)(Unix-tijd)(decimaal jaar)(rok)(dni)(czas uniksowy)(rok dziesiętny)root.md, the op line (in the list under "Step 1: the data")root.md, the offer bullet (after "Conversions for open roles")Session block: the conversions heading and the offer line
The
prepare_datatool description (its docstring, which the model reads) gains one clause:Test plan
uv run --extra dev ruff check .andruff format --check .: clean.uv run --extra typecheck mypy api core --pretty: clean.uv run pytest tests/unit tests/integration -n auto: 26,853 passed, 209 skipped (Docker-only).uv run python -m tools.changelog check --base origin/main: passes.4f1166d91: no correctness, regression or safety finding. Its three test gaps are added ind87f8b1bb: days and decimal year on an offset timestamp, complete bindings left untouched, and no offer for an all-missing column.days, seconds fortimestamp); offsets converted fortimestampand kept as the clock for the other units;YYYYMMcolumn after itsset_typein the same call; a column with no date left refused;column).carry_bindingsfills the openy, and complete bindings stay as they are;push_versionandundo_versionrestore the version before, withyopen again;Subscription Date (Tage), binds y, and undo takes it back;query_datanames the column in the session language.convertedbinding and the session-language name;_wallin UTC, a bound role reassigned, an all-missing column offered;closes_with_invitationover the 40 replies captured in the live runs: it flags exactly the 3 known invitations and none of the other 37, which span 15 cases in English and German.eu, in process with ADC:30f3d5615c83b581): the summary offer matched in 6 of 6 runs, each closing with a statement such as "Subscription Date can become numbers for y: the days since the first date, the year, the Unix time or a decimal year." Both message cases matched withdaysand noname; the outage of one run (guard_unavailable) was re-run. About $0.024..create_plot()beside the message box..undo()brings back the earlier data." German: "Ich habe das Abodatum in eine ganze Zahl umgewandelt: die Tage seit dem frühesten Datum (25.03.2020 = 0), für alle 8 Zeilen. Die neue Spalte heißt „Abodatum (Tage)" und ist jetzt die y-Achse; das Originaldatum bleibt erhalten. Prüfe die Vorschau im Datenbereich und drücke dann.create_plot()."Known issues not fixed here
de-summary-zeilennummerit wrote "ohne deinen Auftrag … Das war mein Fehler", including onorigin/main's prompt (2 of 3 runs, one of them a miss). It predates this change and needs its own fix inroot.md.Nrcolumn of 1 to n is not a row-number name (bindings.ROW_NUMBER_NAMES). It sits on x, and the row-number fallback then moves it to y and puts new row numbers on x, which draws a diagonal.https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8