Skip to content

Commit 0650830

Browse files
BirdInTheTreeclaude
andcommitted
Add span requirements for ABC rank
A requires ≥75% span, B ≥50%, C ≥25%. Prevents short arcs (like Vogler 5/22 = 23%) from getting B rank. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 23224b7 commit 0650830

2 files changed

Lines changed: 29 additions & 10 deletions

File tree

src/tvplotlines/postprocess.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,25 +157,35 @@ def compute_ranks(
157157
plotline.computed_rank = "B"
158158
fixed_ids.add(plotline.id)
159159

160-
# Phase 2: remaining plotlines sorted by event count
160+
# Phase 2: remaining plotlines sorted by event count, filtered by span
161+
n_episodes = len(episodes)
161162
remaining = [p for p in plotlines if p.id not in fixed_ids]
162163
remaining.sort(key=lambda p: (-event_counts.get(p.id, 0), p.id))
163164

164165
logger.info(
165166
"Rank assignment order: %s",
166-
[(p.id, event_counts.get(p.id, 0)) for p in remaining],
167+
[(p.id, event_counts.get(p.id, 0), len(p.span)) for p in remaining],
167168
)
168169

170+
# Span requirements: A ≥ 75%, B ≥ 50%, C ≥ 25%
169171
for i, plotline in enumerate(remaining):
170-
if i == 0 and not is_a_taken:
172+
span_frac = len(plotline.span) / n_episodes if n_episodes else 0
173+
if i == 0 and not is_a_taken and span_frac >= 0.75:
171174
plotline.computed_rank = "A"
172175
is_a_taken = True
173-
elif i <= 1:
174-
# Second plotline (or first when A is taken by case_of_the_week)
175-
plotline.computed_rank = "B"
176-
else:
176+
elif i <= 1 and span_frac >= 0.50:
177+
plotline.computed_rank = "B" if is_a_taken else "A"
178+
if plotline.computed_rank == "A":
179+
is_a_taken = True
180+
elif span_frac >= 0.25:
177181
plotline.computed_rank = "C"
178-
logger.info(" %s → %s (events=%d)", plotline.id, plotline.computed_rank, event_counts.get(plotline.id, 0))
182+
else:
183+
plotline.computed_rank = "C" # low span but still serialized, not runner
184+
logger.info(
185+
" %s → %s (events=%d, span=%d/%d=%.0f%%)",
186+
plotline.id, plotline.computed_rank, event_counts.get(plotline.id, 0),
187+
len(plotline.span), n_episodes, span_frac * 100,
188+
)
179189

180190

181191
def validate_ranks(

tests/test_postprocess.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,17 @@ def _make_plotline_no_rank(id: str, ptype: str = "serialized") -> Plotline:
147147

148148
class TestComputeRanks:
149149
def test_serial_assigns_a_b_c(self):
150-
"""Most events → A, second → B, rest → C."""
150+
"""Most events → A, second → B, rest → C. Span must meet thresholds."""
151151
ctx = SeriesContext(format="serial", story_engine="x", genre="drama")
152152
lines = [_make_plotline_no_rank("big"), _make_plotline_no_rank("mid"), _make_plotline_no_rank("small")]
153+
# 4 episodes so span fractions are: big=4/4=100%, mid=3/4=75%, small=2/4=50%
153154
episodes = [
154-
_make_episode("S01E01", ["big", "big", "big", "mid", "mid", "small"]),
155+
_make_episode("S01E01", ["big", "big", "mid", "small"]),
156+
_make_episode("S01E02", ["big", "big", "mid", "small"]),
157+
_make_episode("S01E03", ["big", "mid"]),
158+
_make_episode("S01E04", ["big"]),
155159
]
160+
compute_span(lines, episodes)
156161
compute_ranks(lines, episodes, ctx)
157162
assert lines[0].computed_rank == "A"
158163
assert lines[1].computed_rank == "B"
@@ -163,6 +168,7 @@ def test_runner_gets_no_rank(self):
163168
runner = _make_plotline_no_rank("bg", ptype="runner")
164169
main = _make_plotline_no_rank("main")
165170
episodes = [_make_episode("S01E01", ["main", "main"])]
171+
compute_span([runner, main], episodes)
166172
compute_ranks([runner, main], episodes, ctx)
167173
assert runner.computed_rank is None
168174
assert main.computed_rank == "A"
@@ -173,6 +179,7 @@ def test_procedural_case_of_week_gets_a(self):
173179
arc = _make_plotline_no_rank("arc")
174180
# arc has more events, but cotw is fixed at A for procedural
175181
episodes = [_make_episode("S01E01", ["arc", "arc", "arc", "case"])]
182+
compute_span([cotw, arc], episodes)
176183
compute_ranks([cotw, arc], episodes, ctx)
177184
assert cotw.computed_rank == "A"
178185
assert arc.computed_rank == "B"
@@ -182,6 +189,7 @@ def test_hybrid_case_of_week_gets_b(self):
182189
cotw = _make_plotline_no_rank("case", ptype="case_of_the_week")
183190
arc = _make_plotline_no_rank("arc")
184191
episodes = [_make_episode("S01E01", ["arc", "case", "case"])]
192+
compute_span([cotw, arc], episodes)
185193
compute_ranks([cotw, arc], episodes, ctx)
186194
assert cotw.computed_rank == "B"
187195
assert arc.computed_rank == "A"
@@ -203,6 +211,7 @@ def test_also_affects_counted_equally(self):
203211
],
204212
theme="t",
205213
)
214+
compute_span([main, side], [ep])
206215
compute_ranks([main, side], [ep], ctx)
207216
# main: 2 primary + 0 AA = 2. side: 1 primary + 2 AA = 3. side wins.
208217
assert side.computed_rank == "A"

0 commit comments

Comments
 (0)