feat(runner): bound auto-summarize to a recency window
In automatic mode the scheduler now only summarizes videos published within TAPIR_AUTO_SUMMARIZE_WINDOW (default ~7d). Older videos are still discovered and listed — they keep the manual "Summarize" affordance — but are not auto-processed, so a large back-catalogue (the maintainer's ~256-deep queue) stops self-inflicting 429s against the per-IP caption gate each cycle (UX review B1, recency design). - runner.WithAutoWindow + Stats.SkippedTooOld; tooOld() treats a zero window as disabled and an undated video as never-aged-out (processed, not stranded). - An explicit manual request bypasses the bound even in auto mode (requested videos are loaded in auto mode when a window is active). - Wired through cmdRun, the scheduler's per-user runner, sumStats, and pass logging. config: TAPIR_AUTO_SUMMARIZE_WINDOW (default 168h), .env.example. - Account copy (A7) updated to match: "Automatic summarizes new videos from about the last week; older videos stay browsable — summarize on demand." The rate gate is untouched; the manual path still serialises through it. This bounds auto LOAD, it does not fetch harder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,14 @@ type Config struct {
|
||||
// the web click-path serialise through it. Zero = unlimited (dev/tests).
|
||||
FetchRate time.Duration
|
||||
|
||||
// AutoSummarizeWindow bounds auto-summarization to recent videos: in automatic
|
||||
// mode the scheduler only summarizes videos published within this window of now.
|
||||
// Older videos are still discovered and listed, but wait for an explicit manual
|
||||
// "Summarize" — so a large back-catalogue does not self-inflict 429s against the
|
||||
// caption rate gate. Zero disables the bound (summarize every unseen video, the
|
||||
// pre-recency behaviour). Default ~7 days.
|
||||
AutoSummarizeWindow time.Duration
|
||||
|
||||
// DiscoveryInterval, when > 0, makes `serve` run in-process scheduled discovery
|
||||
// for ALL users on that cadence (ADR-018). Zero/unset = disabled, so dev and
|
||||
// tests never auto-fetch. Single-replica assumption — see cmdServe.
|
||||
@@ -110,6 +118,7 @@ const (
|
||||
defaultFetchBackoff = time.Hour
|
||||
defaultFetchRate = 2 * time.Second
|
||||
defaultPublicURL = "https://tapir.d-ma.be"
|
||||
defaultAutoSummarizeWindow = 7 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// Load reads the environment into a Config, applying defaults. It does not
|
||||
@@ -168,6 +177,12 @@ func Load() (Config, error) {
|
||||
}
|
||||
c.DiscoveryInterval = discovery
|
||||
|
||||
autoWindow, err := durationOr("TAPIR_AUTO_SUMMARIZE_WINDOW", defaultAutoSummarizeWindow)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
c.AutoSummarizeWindow = autoWindow
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -48,18 +48,22 @@ func TestLoad_AppliesDefaults(t *testing.T) {
|
||||
if c.FetchBackoff != defaultFetchBackoff {
|
||||
t.Errorf("FetchBackoff = %v, want default %v", c.FetchBackoff, defaultFetchBackoff)
|
||||
}
|
||||
if c.AutoSummarizeWindow != defaultAutoSummarizeWindow {
|
||||
t.Errorf("AutoSummarizeWindow = %v, want default %v", c.AutoSummarizeWindow, defaultAutoSummarizeWindow)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ParsesValues(t *testing.T) {
|
||||
setEnv(t, map[string]string{
|
||||
"TAPIR_USER_ID": "11111111-1111-1111-1111-111111111111",
|
||||
"TAPIR_GATEWAY_URL": "http://example/v1",
|
||||
"TAPIR_GATEWAY_KEY": "sk-test",
|
||||
"TAPIR_SUMMARIZER_MODEL": "iguana/deepseek-r1-14b",
|
||||
"TAPIR_SUMMARIZER_TIMEOUT": "90s",
|
||||
"TAPIR_DB_DSN": "postgres://x",
|
||||
"TAPIR_POLL_INTERVAL": "10m",
|
||||
"TAPIR_FETCH_BACKOFF": "30m",
|
||||
"TAPIR_USER_ID": "11111111-1111-1111-1111-111111111111",
|
||||
"TAPIR_GATEWAY_URL": "http://example/v1",
|
||||
"TAPIR_GATEWAY_KEY": "sk-test",
|
||||
"TAPIR_SUMMARIZER_MODEL": "iguana/deepseek-r1-14b",
|
||||
"TAPIR_SUMMARIZER_TIMEOUT": "90s",
|
||||
"TAPIR_DB_DSN": "postgres://x",
|
||||
"TAPIR_POLL_INTERVAL": "10m",
|
||||
"TAPIR_FETCH_BACKOFF": "30m",
|
||||
"TAPIR_AUTO_SUMMARIZE_WINDOW": "48h",
|
||||
})
|
||||
|
||||
c, err := Load()
|
||||
@@ -84,6 +88,9 @@ func TestLoad_ParsesValues(t *testing.T) {
|
||||
if c.FetchBackoff != 30*time.Minute {
|
||||
t.Errorf("FetchBackoff = %v, want 30m", c.FetchBackoff)
|
||||
}
|
||||
if c.AutoSummarizeWindow != 48*time.Hour {
|
||||
t.Errorf("AutoSummarizeWindow = %v, want 48h", c.AutoSummarizeWindow)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_RejectsBadDuration(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user