Skip to content

v0.18.2-0042: fix #1015 daily slot rollover dedup (maintainer-integrated) - #1020

Merged
MotWakorb merged 5 commits into
devfrom
integration/pr-1016-clean
Sep 20, 2026
Merged

MotWakorb merged 5 commits into
devfrom
integration/pr-1016-clean

Conversation

@MotWakorb

Copy link
Copy Markdown
Owner

Summary

This replacement PR supersedes #1016 only because maintainer edits are disabled on the contributor branch. It preserves and credits the original work by @lukeeexd from #1016 at exact head 622b1363e78293687e5da903928adf4d8787d819, merged without rebasing onto current dev at b3169ca9421bed7d6cc72a8449fc0b017dc117f0.

Closes #1015 for the daily-rollover bug. Dry-run pending-merge visibility remains explicitly deferred to #1018, matching the maintainer disposition recorded on #1016; this PR does not claim preview/live parity.

Conflict resolution

Verification

Original PR #1016 is intentionally left open and unchanged.

lukeeexd and others added 5 commits September 20, 2026 18:10
…lot rollover

Event providers reuse a fixed pool of slot names and roll the fixture and the
airing over every day, so yesterday's channel for a slot and today's stream
for the same slot share every template word. token_set_ratio scored the pair
at 0.86-0.99 — above the default 0.80 — queued the stream as a pending merge,
and deferred the channel creation. When the whole pool rolled over, the run
created nothing and the group stayed on yesterday's fixtures until orphan
cleanup happened to delete their candidate channels.

- dedup_matcher: a pair whose two stated airings (the date/time run in the
  name) disagree is dropped before scoring. Stripping the run instead would
  leave two character-identical names scoring 1.00, so the run is treated as
  a signal, not noise. A name that states no airing scores byte-for-byte as
  before, and the 0.60 floor and operator threshold are untouched.
- executor: the pending-merge candidate list no longer includes the channel
  the stream is already attached to (the pair scores 1.00 by construction).
- executor/engine/tasks: a deferred create names its blocking pending_merges
  row, the run summary counts the deferred streams and lists the rows, and
  the follow-on assign_epg reports the deferral instead of the bare
  "No channel context for assign_epg" that operators read as an EPG fault.

The queue-lifecycle asks (auto-expiring a pending row whose candidate channel
is gone, sticky per-pair dismissal) are deliberately out of scope here:
ADR-008 §D4, migration 0014 and a recorded PO decision all specify today's
behaviour, so changing them is an ADR amendment for the PO. The addendum says
so explicitly.

Tests: airing-gate unit cases (rollover does not queue; identical names still
do), candidate-scope cases, the rollover integration scenario through
_execute_create_channel, deferral-reason surfacing, merge-id exposure on both
the insert and the §D5 collision branch, and the run-summary contract for
deferred streams. Verified by reverting the gate: the rollover tests fail
with the reported 0.86-0.99 queueing behaviour and pass with it.
…rting (#1015)

1. airing_key accepts only valid airing tokens: a real month name or
   abbreviation, a day in 1-31, a real clock (12-hour with a meridiem,
   24-hour otherwise), complete tokens (a day cannot start inside a longer
   number; a clock cannot continue into more digits). The name is scanned
   for the FIRST VALID run, so a false prefix cannot hide a real trailing
   airing. "Sports 24 Hour 09:30", "Channel 250 News 09:30" and
   "Sky Sports F1 Live 20:00" state no airing and score as before.
2. Zones are supported literal labels (GMT, UTC, IST, BST, CET, EST, ...),
   bare or with a numeric offset, compared as written; GMT vs IST differ.
   A non-zone suffix after the clock (HD2, UHD, RAW) is not a zone and
   leaves the key unchanged.
3. The single "Completed with Warnings" notification for an unattended
   failed-action run carries the deferral diagnosis and the blocking row
   ids (task_engine message branch for auto_creation + metadata extras),
   built from the same phrase as the engine log and the task summary.
4. Units are distinct: pending_merges_added counts deferred create
   actions, pending_merge_stream_count counts distinct streams, and
   pending_merge_ids lists distinct rows (deduplicated in the executor and
   in the engine aggregation). describe_pending_merge_deferral renders all
   three on every surface.
5. A deferral explanation is scoped to one create attempt: the next create
   attempt for the same stream clears it, so a later failure in another
   group is not misattributed to the earlier pending merge.
6. The regex has no unanchored whitespace scan (bounded \s{1,3} gaps, a
   lookbehind day boundary and a lookahead clock boundary); the source
   comment states the actual runtime property.
7. Group/name eligibility is applied before stream membership is inspected,
   so non-candidate channels' memberships are never traversed.

Tests: airing-key validation table and the reviewer's counterexamples
through find_candidate; bare and offset zones; non-zone suffixes; false
prefix with a real trailing airing; regex structure plus a bounded timing
check on padded input; executor membership-traversal proof, attached
exclusion after the reorder, distinct stream/action/row counts on a §D5
collision, attribution superseded by a later failed create; engine
aggregation through _process_streams (distinct rows/streams, JSON-safe
result); task details carry the note and stream count; the task-engine
warning message and metadata carry the deferral and rows; a run without
deferrals keeps the generic warning. test_same_airing_still_scores_fuzzily
now uses genuinely different normalized names and asserts a non-exact
accepted score. ADR-008 addendum and changelog aligned with the corrected
grammar, zone policy, counter units, notification surface and regex
runtime; dry-run visibility is called out as a maintainer decision.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGuX7qVpTp99bzYZXgLJMm
…1016 review

Strict-delta review on PR #1016 asked for retained regressions that cross the
real boundaries rather than mocked ones, plus an explicit disposition of the
dry-run visibility ask.

- New tests/integration/test_gh1015_deferral_end_to_end.py drives a real
  pending_merges table, the real ActionExecutor + BD-F hook, the real
  ChannelPipelineEngine.run_pipeline, the real ChannelPipelineTask
  post-refresh path and the real TaskEngine completion notification, with
  only Dispatcharr and the notification sink doubled. It asserts the emitted
  "Completed with Warnings" payload names the deferral and the persisted
  PendingMerge row ids on the fresh-insert refresh and on the next refresh
  (every row a D5 collision), that two same-named streams from two providers
  share one row while the counters keep actions (2) / streams (2) / rows (1)
  apart in the engine result, task details and warning metadata, that no
  channel is created for a deferred stream, and that its assign_epg failure
  in the persisted execution log names the deferral and the row. A control
  run with nothing deferred carries no deferral wording or ids.
- test_channel_pipeline_executor_dedup_hook.py gains the false-prefix /
  trailing-airing rollover through enqueue and execution: the next-day
  fixture creates and queues nothing; the same trailing airing with a
  different fixture still queues, against the persisted row.
- ADR-008 addendum and CHANGELOG record the maintainer decision: dry-run
  pending-merge visibility is owned by #1018 and this change does not claim
  preview/live parity.
- Rebased onto origin/dev (#1006, #1008, #1017 landed); build stays 0038.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ror warning (#1015)

Review round 3 on PR #1016: a capped run with failed actions emits the
task's own "Capped, with errors" notification and suppresses the task
engine's generic warning, so a capped, failed, deferred run named neither
Pending Merges nor its blocking rows in the one warning it sent.

- tasks/channel_pipeline.py computes describe_pending_merge_deferral before
  the cap block; the cap/error message appends the same bounded phrase plus
  the Pending Merges pointer, and the notification metadata carries
  pending_merges_added, pending_merge_stream_count and pending_merge_ids.
- tests/tasks/test_gh1015_deferred_merge_visibility.py: a capped + failed +
  deferred result through _run_post_refresh_pipeline emits exactly one
  warning, titled "Capped, with errors", carrying the phrase, the rows and
  the metadata, with the engine warning suppressed; a capped run with no
  deferrals keeps the plain cap warning and no metadata.
- tests/integration/test_gh1015_deferral_end_to_end.py: a real refresh with
  max_auto_created_channels_per_run=1, one deferred stream and two creatable
  ones ends capped with one create, persists one PendingMerge row, and the
  single emitted warning names that row id in its message and metadata.
- CHANGELOG and ADR-008 addendum name the combined cap/error surface.
- RED check: dropping the append fails both new tests on the phrase.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
from __future__ import annotations

import json
from datetime import datetime
@MotWakorb
MotWakorb merged commit ebae7a4 into dev Sep 20, 2026
30 of 31 checks passed
@MotWakorb
MotWakorb deleted the integration/pr-1016-clean branch September 20, 2026 22:11
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.

3 participants