Skip to content

fix(agents): keep pasted data exact or warn about every change - #12196

Merged
MarkusNeusinger merged 7 commits into
mainfrom
fix/agent-data-fidelity
Oct 15, 2026
Merged

MarkusNeusinger merged 7 commits into
mainfrom
fix/agent-data-fidelity

Conversation

@MarkusNeusinger

@MarkusNeusinger MarkusNeusinger commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR audits how faithfully the agent chat keeps pasted data and fixes every silent precision loss it found. It follows the owner rule of 2026-10-15: what the user pastes is what the data holds, or a warning or change line says what changed and how often. Silent precision loss is treated like silent corruption.

Audit

Path What can be lost Before After
code/loader.py: run-form pd.read_csv (exported plot.py, sandbox render) float digits No float_precision. On pandas 3.0, about 23 % of realistic 17-digit floats were read 1 ulp off, so the plot did not draw the values in data.csv. float_precision="round_trip" whenever a number column exists. Test: 2,000 random floats of every magnitude plus extremes read back bit-exactly.
parse.py _as_numbers: CSV number cells digits past float64 (0.1234567890123456789, 9007199254740993 beside decimals, subnormals, 1e-400) Silently rounded. Stored as the nearest float (repr), with a warning that counts the cells: "N values with more significant digits than a number holds (15 to 17) are stored rounded".
parse.py _decode_literal: JSON and Python-literal floats digits json.loads and literal_eval rounded them before typing, with no warning. The written text is kept (_SourceFloat), so the same count applies. JSON 1e400 is now text instead of vanishing as missing.
parse.py integers past int64 last digits of IDs and counts Became float (1.2345678901234568e+22). The column stays text with a warning. set_type number can still convert it, and its change line counts the rounding.
parse.py profile min/max of integer columns digits past 2^53 float Exact int (ColumnProfile.min/max: int | float | Cell).
parse.py trailing zeros, +, exponent, -0 spelling 1.50 becomes 1.5, 1e3 becomes 1000.0, -0 becomes 0 Unchanged by design: the value is identical (shortest round-trip form). See the findings below.
parse.py thousands and decimal-comma normalization, % and currency value Already warned (notes and unit-moved warnings). Unchanged, now covered by an exact-digits test.
parse.py text cells: surrounding whitespace characters Silently stripped. In text columns, one warning counts the cells. Space-aligned tables (layout padding) and number or date columns get no warning.
parse.py text cells: None, -, null, … the whole cell (a possible category) Silently became missing. In text columns, one warning counts the cells and names the tokens. Number and date columns are unchanged (the usual missing markers there).
parse.py NFC code points Silent. One warning counts the changed lines.
parse.py zero-width and bidi characters, header canonicalization, 200-character cell cap Already warned or refused. Unchanged.
parse.py thinning of list fields over 20,000 rows rows Already warned with counts. Unchanged.
prepare.py set_type number whole numbers past 2^53, many-digit decimals Read through float (9007199254740993 became …992), silently. Whole numbers are read exactly through Decimal (Int64). The change line counts rounded cells.
prepare.py aggregate sum of Int64 the whole value pandas wrapped around silently (2 × 9.2e18 gave -1616). Summed in Python ints. Past int64 the result is float, and the change line counts the rounded values.
prepare.py derive cumsum of Int64 digits past 2^53 Computed in float64, silently. Computed in Python ints and stays whole. Past int64 the change line counts the rounded values.
prepare.py derive sum and difference past int64 digits Silent fallback to float. The change line counts the rounded values.
prepare.py pivot_longer of Int64 beside float64 digits past 2^53 Silent cast. The change line counts the rounded whole numbers.
prepare.py derive ratio, share, percent digits Full float, written with repr. Unchanged, now covered by a test.
prepare.py _cell_text: date-times sub-seconds timespec="seconds" dropped them silently. moment_text: microseconds or nanoseconds when present. The offset is kept.
prepare.py set_type datetime without a format month and day order 2024-06-01T13:00:00Z was read as 6 January (dayfirst=True). Year-first columns are read year, month, day.
prepare.py set_type datetime with mixed UTC offsets Uncaught ValueError, so the tool crashed. type_mismatch refusal. The times are not converted.
prepare.py _read and _write, render/harness.py frame_csv _read already used round_trip. to_csv writes repr (verified bit-exact on 20,000 random doubles). Unchanged.
parse.py dates, _iso, loader parse_dates seconds and range Seconds are kept. Years 1 to 9999 read as M8[us] on pandas 3. Unchanged. Fractional seconds and offsets at parse time belong to the sibling PR (feat/agent-file-drop-binary).
Page preview (DataPanel PreviewTable) display Shows the canonical cells verbatim, with no number formatting. Cells over 40 characters are cut with …, and whitespace runs are collapsed for display. Unchanged. See the findings below.

Owner decisions (2026-10-15), applied

  1. Trailing zeros and spelling. The value is enough (1.50 becomes 1.5), but one warning now counts the numbers whose trailing zeros, exponent or plus sign went, and names their columns. Spelling the canonical form only adds (2 as 2.0, .5 as 0.5, 1e-7 as 1e-07) is not counted.
  2. Different UTC offsets are converted to UTC with a change line that names the offsets, consistent with the parser since feat(agents): read dropped Excel, ODS, Parquet, Feather, Arrow and NumPy files #12194. This now also applies to set_type datetime. Times with and without an offset in one column are refused.
  3. Preview. The server keeps each preview cell's whitespace (parse.preview_cell). The data panel draws a line break as ↵ and each space of a run as ·, muted and with screen-reader text. The model-facing sample and top values stay one line.
  4. Profile mean and median at six significant digits are kept, for the model only; this is documented.
  5. Integers past 2^53 in the browser are kept as is, documented. This only affects the binding hint.
  6. The CSV-delimiter space exemption stays: there is no warning for 1, alpha.

Merge with #12194

origin/main is merged in. #12194's _cell_text (value.isoformat(), which writes every fraction digit and the offset) replaces this PR's moment_text, and both test classes are kept. The stress case duplicate-headers now expects 4 warnings, including the spelling line; it was regenerated with make_stress. The parse-only fixture test ignores the spelling line.

Frontend

The change is app/src/sections/agent-chat/previewCell.ts plus its use in DataPanel, with Vitest coverage in previewCell.test.ts and DataPanel.test.tsx. App gates on Node 24 pass: lint, Prettier, type-check, 1,114 tests and the build. I did not drive the page in a browser in this session.

Test plan

  • TestFidelity in test_parse.py with generalized fixtures: random decimals of 1, 7 and 15 digits in CSV, JSON, written JSON and Python literals keep their exact value with no warning; 19-digit decimals plus edge cases are counted exactly; int64 limits are exact and larger integers become text; NFC, whitespace and missing-token warnings; no warning for aligned padding or number columns.
  • TestFidelity in test_prepare.py: 500 random floats of every magnitude plus integers past 2^53 survive operations exactly; set_type, aggregate sum, cumsum and sum overflow, the pivot_longer count, sub-seconds and offsets, the year-first fix and the mixed-offset refusal.
  • test_loader.py: bit-exact read-back of 2,000 random floats plus extremes through the run form.
  • uv run pytest tests/unit -n 8: 11,318 passed. ruff check and ruff format --check on the repo, mypy api core agents and the changelog check all pass.
  • No app/ change, so no frontend gate applies.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M

Owner rule of 2026-10-15: precision is never lost silently.

- loader: read data.csv with float_precision="round_trip" (pandas 3's
  default reader misread ~23 % of 17-digit floats by one ulp)
- parse: count numbers stored rounded (more digits than a float64), keep
  JSON/literal float texts, whole numbers past int64 stay text, warn on
  NFC, trimmed text cells and missing tokens in text columns, exact
  integer min/max in the profile
- prepare: exact whole numbers in set_type number, aggregate sum (no
  Int64 wraparound), cumsum; change lines count rounded values; keep
  sub-seconds and offsets; year-first ISO not read day-first; mixed
  UTC offsets refused instead of crashing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Copilot AI balanced review requested due to automatic review settings October 15, 2026 13:47
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.96133% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agents/anyplot/data/parse.py 94.55% 8 Missing ⚠️
agents/anyplot/data/prepare.py 98.42% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Parsing performance, incorrect running-total grouping, and incomplete loss warnings remain unresolved.

5 open findings
What changed in this PR

Improves data fidelity across agent-chat parsing, preparation, and exported plots by preserving precision or reporting changes.

Changes:

  • Adds exact float loading and counted normalization warnings.
  • Preserves integer calculations and timestamp precision.
  • Expands regression coverage and documents fidelity guarantees.
File Description
tests/​unit/​agents/​evals/​test_stress.py Checks round-trip loader generation.
tests/​unit/​agents/​data/​test_prepare.py Tests operation precision and timestamps.
tests/​unit/​agents/​data/​test_parse.py Tests numeric fidelity and normalization warnings.
tests/​unit/​agents/​code/​test_loader.py Tests exact float read-back.
docs/​concepts/​agent-network.md Documents fidelity behavior.
changelog.d/​agent-data-fidelity.md Records user-facing fixes.
agents/​evals/​stress.py Aligns evaluation loading with production.
agents/​anyplot/​schemas.py Allows exact integer profile bounds.
agents/​anyplot/​data/​prepare.py Preserves calculation precision and reports rounding.
agents/​anyplot/​data/​parse.py Retains numeric source text and counts changes.
agents/​anyplot/​code/​loader.py Enables round-trip float parsing.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agents/anyplot/data/parse.py Resolved
Comment thread agents/anyplot/data/prepare.py Resolved
Comment thread agents/anyplot/data/parse.py Resolved
Comment thread agents/anyplot/data/parse.py Resolved
Comment thread agents/anyplot/data/prepare.py Resolved
- literal float texts sliced from lines split once (get_source_segment
  per float was quadratic); digit separators dropped from the kept text
  so every digit reaches the rounding count
- exact cumsum groups native key values (-0.0 with 0.0, as pandas)
- a rounding notice is never cut by the change-line cap: the
  descriptive part is shortened instead

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread agents/anyplot/data/prepare.py Resolved
Comment thread agents/anyplot/data/prepare.py Resolved
MarkusNeusinger and others added 4 commits October 15, 2026 16:09
- set_type datetime chooses day-first per cell, so one cell of another
  shape no longer turns the year-first ones around
- an exponent past the decimal module's range takes the float path
  instead of raising InvalidOperation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Merge #12194 (ISO timestamps with fractions and offsets in parse.py):
its _cell_text (value.isoformat()) replaces moment_text; both test
classes kept.

Owner decisions of 2026-10-15:
- one warning counts numbers whose spelling went (trailing zeros,
  exponent, plus sign), naming their columns
- set_type datetime reads different UTC offsets in UTC with a change
  line naming them, as the parser does; naive beside zoned is refused
- the preview keeps whitespace; the data panel draws line breaks as ↵
  and space runs as ·, muted and named for screen readers
- profile mean/median at six digits (model only) and integers past 2^53
  in the browser kept and documented

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Conflict in docs/concepts/agent-network.md (Parse paragraph): main's text
kept, this PR's fidelity, loader and prepare sentences re-applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
- conditions on a column with a UTC offset compare moments; a value with
  the wrong kind of zone is refused instead of crashing with TypeError
- conditions on whole-number columns compare exact integers
- nanosecond times beside a date outside 1677..2262 stay text with a
  warning (the loader had turned them text silently)
- JSON NaN/Infinity/-Infinity counted as missing
- whole numbers past int64 with dot groups stay text with BIG_INTEGERS
- results past 2^53 written as floats are counted, also exact ones
  (2^63); float results count their Int64 inputs past 2^53
- read and loader use date_format="ISO8601" (no inference warning)
- set_type datetime wording says year first where it is; missing-token
  note counts further tokens; preview marks a tab as →

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
@MarkusNeusinger
MarkusNeusinger merged commit 2a00233 into main Oct 15, 2026
15 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/agent-data-fidelity branch October 15, 2026 16:47
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.

2 participants