Rebase seeded time-series data to now, so the demo stops going quiet
ShiftRecord is generated against now (shifts.go); everything else stayed on the fixed calendar in seed.js while the calendar moved on. Twenty-three days after that file was written the activity agent truthfully reported zero events in the last seven days, and applications were sixteen days stale. Nothing was broken — the data had simply aged out of every window the product reports over, and it gets worse every day nobody reseeds. RebaseToNow moves a set of records so the newest sits at now, keeping every gap exactly as authored. The SHAPE is what every reader of this data is looking at: three hires on one day, a screening the day after, a quiet fortnight before it. Shifting the whole set by one delta preserves all of it. Scaling into a window or scattering events across recent days would invent a rhythm nobody wrote. Applied per entity — UserActivity, JobApplication, AIInterview — because each anchors on its own newest record. One shared anchor would drag the quieter entities by another entity's delta and invent relationships between them. Reference data is untouched: a course's date is a fact about the course, not a position in a window. Rebasing rather than generating, so seed.json stays the single authored source, still deterministic and still comparable byte-for-byte by the drift check. The alternative — excluding these from the fixture the way ShiftRecord is — means a second generator to keep in step with the frontend's copy. The existing TestSeedPreservesSourceValues caught a real bug in the first attempt, and its comment is why: "Applications carry updated_date in the source, and the gap from created_date is what buildHires reads as time-to-hire." I had shifted created_date alone, which turned five-day hires into three-week ones. EVERY timestamp on a record now moves by the same delta, and there is a test on that specifically. That test now asserts the GAP rather than the absolute dates, because for a rebased entity the dates are deliberately different — which is the one reason it is supposed to allow. Its real subject was always the interval. Verified against the database: before, activity was 23 days old with 0 events in the last 7; after, all three entities are current, with 24 applications spread across the last 30 days and 15 activity events inside 30. Not fixed here: the Hiring activity chart on Control Center still renders empty, and it was equally empty before this change. Its bucketing is correct — replaying it in the browser against live data matched all 24 applications into the right days — so the fault is further down in that component, not in the data. Naming it rather than leaving it implied by a chart that still looks wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
This commit is contained in:
@@ -3,6 +3,7 @@ package seeder_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -153,11 +154,23 @@ func TestSeedPreservesSourceValues(t *testing.T) {
|
||||
if status != want["status"] {
|
||||
t.Errorf("%s status = %q, want %q", legacy, status, want["status"])
|
||||
}
|
||||
if created != want["created_date"] {
|
||||
t.Errorf("%s created_date = %q, want %q", legacy, created, want["created_date"])
|
||||
// Applications are REBASED (see RebaseToNow), so their absolute dates
|
||||
// deliberately differ from the fixture — otherwise the demo ages out of
|
||||
// every window the product reports over. What must survive is the GAP,
|
||||
// because buildHires subtracts these two to get time-to-hire. Asserting
|
||||
// the gap keeps this test's real subject and stops it failing for the
|
||||
// one reason it is supposed to allow.
|
||||
wantGap, err := fixtureGap(want["created_date"], want["updated_date"])
|
||||
if err != nil {
|
||||
t.Fatalf("%s: fixture dates: %v", legacy, err)
|
||||
}
|
||||
if updated != want["updated_date"] {
|
||||
t.Errorf("%s updated_date = %q, want %q", legacy, updated, want["updated_date"])
|
||||
gotGap, err := fixtureGap(created, updated)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: seeded dates: %v", legacy, err)
|
||||
}
|
||||
if gotGap != wantGap {
|
||||
t.Errorf("%s time-to-hire = %s, want %s (created %s, updated %s)",
|
||||
legacy, gotGap, wantGap, created, updated)
|
||||
}
|
||||
if v, ok := want["ai_score"].(float64); ok && score != int(v) {
|
||||
t.Errorf("%s ai_score = %d, want %d", legacy, score, int(v))
|
||||
@@ -414,3 +427,17 @@ func TestFixtureIsGeneratedNotHandWritten(t *testing.T) {
|
||||
t.Errorf("`_generated` does not name its source: %q", head.Generated)
|
||||
}
|
||||
}
|
||||
|
||||
// fixtureGap is the interval between two fixture timestamps.
|
||||
func fixtureGap(from, to any) (time.Duration, error) {
|
||||
const layout = "2006-01-02T15:04:05.000Z"
|
||||
a, err := time.Parse(layout, fmt.Sprint(from))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse %v: %w", from, err)
|
||||
}
|
||||
b, err := time.Parse(layout, fmt.Sprint(to))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse %v: %w", to, err)
|
||||
}
|
||||
return b.Sub(a), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user