Skip to content

docs, test: gap-fill, LOCF and interpolation recipes, held to a reference (#1296 phase 1) - #1311

Merged
jdatcmd merged 4 commits into
mainfrom
test/1296-timeseries-recipes
Oct 13, 2026
Merged

jdatcmd merged 4 commits into
mainfrom
test/1296-timeseries-recipes

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Phase 1 of #1296. Phase 0's numbers and the decision are on the issue: recipes and existing extensions cover all three, and nothing is built for 1.0. Phase 2, the hll and tdigest compatibility arms, comes as its own PR.

What users get

A new how-to section, "Fill gaps in a time series", with three plain-SQL recipes over readings (ts, v) in one-hour buckets:

  • Fill empty buckets: a series LEFT JOIN the bucketed aggregate.
  • Carry the last value forward: a running count of non-null values, then first_value in each group.
  • Interpolate between known values: running counts in both directions find both neighbours, then a straight line between them.

The buckets come from date_bin, with the series start as origin, not date_trunc. date_trunc truncates in the session's time zone. In an Asia/Kolkata session (+5:30), its buckets start at :30 UTC and match nothing in the series, so with date_trunc in the document, every bucket came back empty. The docs say why.

Tests: timeseries_recipes.sh and an independent test_timeseries_recipes.py, 10 checks each

  • The recipes are read from docs/how-to.md: the first sql block under each heading, run as written. An edited recipe is the recipe tested.
  • The references are computed differently in each twin: a plpgsql loop over the raw rows in the shell suite, and Python over the inserted rows in the twin. Neither shares a window function or a join with the recipes.
  • The fixture:
    • an empty first hour, a run of empty hours, an hour with some NULLs, an hour of only NULLs, and two empty last hours;
    • values that grow as the square of the hour, so a line through the wrong neighbours gives the wrong value;
    • every value a multiple of 1/16, so comparisons are exact.
  • The checks:
    • each recipe matches the reference for all 24 hours;
    • the gap fill gives the same buckets in a session at a half-hour offset;
    • with enable_group_vectorization on, its aggregate runs as the vectorized grouped node.

Red first. On main, the shell suite (PG17) and the twin (PG16) find no recipes: the premises read 0 and the comparisons get nothing.

Mutations, each run on 15–19 and merged into the ledger one log at a time:

mutation of the recipes check that turned red
date_trunc buckets the half-hour zone check (all 24 buckets NULL)
JOIN instead of LEFT JOIN the fill, LOCF and interpolation checks, and the zone check
LOCF by lag() the LOCF check (03 carried, 04 NULL)
interpolation toward prev_v the interpolation check (a flat fill)
avg(v ORDER BY v) the vectorized-node check
the heading renamed the gap-fill premise and its three checks

Ledger: 10 rows, 6 dated, and the 4 still never are premises. Census 1693 → 1697, rows 1943 → 1953, suites_not_covered stays 247. compare_to_bash grades the pair 10 of 10. docs_style is green.

Registries: TESTS.md section 93, cluster_tests 500 → 502 by collection. #1307 also takes section 93; whichever merges second renumbers.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, this is ready for review. It's rebased onto d7c9951 (#1312), with head 9e27071.

  • The rebase: only the budget file conflicted. The merged ledger is main's 1945 rows plus this PR's 10, verified row by row. Census 1694 → 1698, rows 1945 → 1955. The budget text says the mutation runs were of this PR on its first base, 3deb6af.
  • Re-run on the rebased tree: timeseries_recipes is 10/10 on each of 15–19.
  • Counts: cluster_tests collected at 502, unchanged; the ledger guards give 119 passed.

What to attack: whether the plpgsql and Python references really are independent of the recipes, and whether the half-hour zone check could pass for the wrong reason. It was red with date_trunc in the document.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The recipes, the references and the mutation table are solid. The date_bin-not-date_trunc point with the Kolkata measurement is exactly the trap a user hits. One #1236 point blocks, and it's the same one as #1313:

A dated premise. The ledger dates premise: the how-to gives the gap-fill recipe with the heading-renamed mutation. Editing the document is a repository change, so this check is an arm. Its two siblings, premise: the how-to gives the LOCF recipe and premise: the how-to gives the interpolation recipe, read their own headings the same way. Their mutation wasn't run, but renaming either heading would redden them just the same. So I'd drop the premise: prefix from all three in both twins and re-run, with the logs carrying the new names.

The other two premises, the readings table being columnar and the reference's 24 hours, are fixture facts no repository change touches. They're right as premises.

Everything else checked out here: 10 ledger rows, 6 dated, every dated row matching your table, and the arithmetic 1693 → 1697.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Taken: you're right, as on #1313. Head is now f349004, rebased onto 7e6fd48 (#1318).

The three checks are arms now. the how-to gives the gap-fill / LOCF / interpolation recipe dropped the premise: prefix in both twins, with a comment saying why.

Fresh runs, because the old logs carry the old names. A clean run plus eight mutations, each on all five majors:

mutation check that turned red
the six from before as in the PR description
'Fill empty buckets' heading renamed gap-fill recipe found, and its three dependent checks
'Carry the last value forward' renamed (new) the how-to gives the LOCF recipe, and the LOCF comparison
'Interpolate between known values' renamed (new) the how-to gives the interpolation recipe, and the interpolation comparison

@jdatcmd
jdatcmd force-pushed the test/1296-timeseries-recipes branch from 9e27071 to f349004 Compare October 13, 2026 01:12
jdatcmd added a commit that referenced this pull request Oct 13, 2026
), red

Gap fill, LOCF and linear interpolation are recipes, not features (phase 0
on #1296). This suite reads each recipe from docs/how-to.md, as the first
sql block under its heading, runs it against the table the document names,
and compares every bucket with a plpgsql reference that walks the raw rows
and shares no window function or join with the recipes. The 24-hour fixture
holds a leading gap, a run of gaps, a bucket with some NULLs, a bucket of
only NULLs and a trailing gap; values grow as the square of the hour.

The three 'the how-to gives the ... recipe' checks read the document, so
they are arms, not premises (#1236; OffgridwithJD's review of #1311).

Red on main: the how-to has no such headings, so those three read 0 and the
comparisons get nothing. The reference and columnar premises pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at f349004. I re-checked it row by row against 7e6fd48:

  • 1984 → 1994 rows, all 10 new.
  • The three the how-to gives the ... recipe checks are arms now. Each is dated by its own heading-rename mutation, as your table says.
  • The 2 never rows are the fixture premises (a columnar table, and the reference's 24 hours). No premise is dated.
  • The census is 1707, matching the budget line.

🤖 Generated with Claude Code

jdatcmd added a commit that referenced this pull request Oct 13, 2026
), red

Gap fill, LOCF and linear interpolation are recipes, not features (phase 0
on #1296). This suite reads each recipe from docs/how-to.md, as the first
sql block under its heading, runs it against the table the document names,
and compares every bucket with a plpgsql reference that walks the raw rows
and shares no window function or join with the recipes. The 24-hour fixture
holds a leading gap, a run of gaps, a bucket with some NULLs, a bucket of
only NULLs and a trailing gap; values grow as the square of the hour.

The three 'the how-to gives the ... recipe' checks read the document, so
they are arms, not premises (#1236; OffgridwithJD's review of #1311).

Red on main: the how-to has no such headings, so those three read 0 and the
comparisons get nothing. The reference and columnar premises pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd
jdatcmd force-pushed the test/1296-timeseries-recipes branch from f349004 to 1d5f0f9 Compare October 13, 2026 01:28
@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the approval at f349004. #1314 and #1313 merged first, so I've rebased onto 953c192. Head is now 1d5f0f9; please re-approve.

No other file's patch changed.

jdatcmd added a commit that referenced this pull request Oct 13, 2026
), red

Gap fill, LOCF and linear interpolation are recipes, not features (phase 0
on #1296). This suite reads each recipe from docs/how-to.md, as the first
sql block under its heading, runs it against the table the document names,
and compares every bucket with a plpgsql reference that walks the raw rows
and shares no window function or join with the recipes. The 24-hour fixture
holds a leading gap, a run of gaps, a bucket with some NULLs, a bucket of
only NULLs and a trailing gap; values grow as the square of the hour.

The three 'the how-to gives the ... recipe' checks read the document, so
they are arms, not premises (#1236; OffgridwithJD's review of #1311).

Red on main: the how-to has no such headings, so those three read 0 and the
comparisons get nothing. The reference and columnar premises pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

#1320 merged before your re-approval came in, so I've rebased once more, onto c0ee868. Head is now 74d2c94, which replaces 1d5f0f9. check_ledger.tsv +/- lines are identical to the 10 you approved; the budget is restated as 1683 → 1685, rows 1994, dated 301 + 8 = 309; test_mutation_ledger.py 39 passed. Nothing else changed.

@jdatcmd
jdatcmd force-pushed the test/1296-timeseries-recipes branch from 1d5f0f9 to 74d2c94 Compare October 13, 2026 01:44

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approved at 74d2c94, after checking the tree myself:

  • Rebased onto c0ee868 (4 commits). The file set equals f349004's, and every file's +/- lines equal the ones I approved, compared sorted, apart from the budget and expected_tests restatements.
  • Census, computed from the ledger: 1994 rows, 1685 never, against main's 1984/1683. The budget says 1685.
  • cluster_tests: 506 collected over the cluster files on this tree, and the file says 506.
  • test_mutation_ledger.py 39 passed; docs_style PASSED.

@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the approval at 74d2c94. #1307 merged first, so I've rebased onto b6656ac. Head is now 39d4b9c; please re-approve.

No other file's patch changed.

jdatcmd added a commit that referenced this pull request Oct 13, 2026
), red

Gap fill, LOCF and linear interpolation are recipes, not features (phase 0
on #1296). This suite reads each recipe from docs/how-to.md, as the first
sql block under its heading, runs it against the table the document names,
and compares every bucket with a plpgsql reference that walks the raw rows
and shares no window function or join with the recipes. The 24-hour fixture
holds a leading gap, a run of gaps, a bucket with some NULLs, a bucket of
only NULLs and a trailing gap; values grow as the square of the hour.

The three 'the how-to gives the ... recipe' checks read the document, so
they are arms, not premises (#1236; OffgridwithJD's review of #1311).

Red on main: the how-to has no such headings, so those three read 0 and the
comparisons get nothing. The reference and columnar premises pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd
jdatcmd force-pushed the test/1296-timeseries-recipes branch from 74d2c94 to 39d4b9c Compare October 13, 2026 01:59
jdatcmd and others added 4 commits October 13, 2026 02:39
), red

Gap fill, LOCF and linear interpolation are recipes, not features (phase 0
on #1296). This suite reads each recipe from docs/how-to.md, as the first
sql block under its heading, runs it against the table the document names,
and compares every bucket with a plpgsql reference that walks the raw rows
and shares no window function or join with the recipes. The 24-hour fixture
holds a leading gap, a run of gaps, a bucket with some NULLs, a bucket of
only NULLs and a trailing gap; values grow as the square of the hour.

The three 'the how-to gives the ... recipe' checks read the document, so
they are arms, not premises (#1236; OffgridwithJD's review of #1311).

Red on main: the how-to has no such headings, so those three read 0 and the
comparisons get nothing. The reference and columnar premises pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A how-to section with three recipes over readings (ts, v) in one-hour
buckets: a series LEFT JOIN the bucketed aggregate; LOCF by a running count
of non-null values and first_value; linear interpolation by running counts
in both directions. The buckets come from date_bin with the series start as
origin, not date_trunc, which truncates in the session's zone: with
date_trunc in the recipe, an Asia/Kolkata session read all 24 buckets NULL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1296)

Independent of the shell suite: rows generated in Python and a reference
computed in Python from them. compare_to_bash grades the pair 10 of 10.
TESTS.md section 96; cluster_tests 503 -> 505 by collection. CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…utations (#1296)

10 rows from a clean run (10/10 on each of 15-19) and eight mutations of
the recipes, each on all five majors, merged one log at a time with
--mutation and --target: date_trunc buckets, an inner join, LOCF by lag,
interpolation toward the wrong neighbour, an ordered aggregate, and each of
the three recipe headings renamed. Eight dated; the two still never are
fixture premises. Census 1705 -> 1707, rows 1984 -> 1994.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd

jdatcmd commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

#1319 merged before you got to 39d4b9c, so I've rebased again, onto 024e737. Head is now 2fbc40c, which replaces 39d4b9c.

No other file's patch changed.

@jdatcmd
jdatcmd force-pushed the test/1296-timeseries-recipes branch from 39d4b9c to 2fbc40c Compare October 13, 2026 02:40

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approved at 2fbc40c, after checking the tree myself:

  • Rebased onto 024e737. Every file's +/- lines equal the ones I approved at 74d2c94, compared sorted, apart from three restatements: the budget, expected_tests, and TESTS.md. TESTS.md is identical except for the section number, now 98.
  • Census, computed from the ledger: 2069 rows and 1700 never, which matches the budget.
  • cluster_tests: 513 collected on this tree, and the file says 513.
  • test_mutation_ledger.py 39 passed; docs_style PASSED.

Note that #1322 also takes TESTS.md section 98; whichever merges second renumbers.

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