0.103.0: story grouping on Heroes (Stage E, first channel)
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 1m49s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m29s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-04 18:53:24 +00:00
co-authored by Claude Opus 5.5
parent c638cf0977
commit c3f2a282bf
6 changed files with 49 additions and 8 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 151
versionName = "0.102.0-vortex-idents"
versionCode = 152
versionName = "0.103.0-heroes-stories"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -199,7 +199,10 @@ internal val SmartChannels = listOf(
suggestionRules = SmartChannelRules[it.number].orEmpty(), dayParts = SmartChannelParts[it.number].orEmpty(), shuffleTitles = SmartChannelMixed[it.number].orEmpty(),
channelFilms = if (it.number == 401) PpvSunday else emptyList(), tierWeights = SmartChannelTiers[it.number], adaptiveFilmRest = it.number in AdaptiveFilmRestChannels,
catalogueScaleHours = if (it.number in CatalogueScaledChannels) CATALOGUE_SCALE_HOURS else null,
catalogueScaleFloor = CatalogueScaleFloors[it.number] ?: MIN_CATALOGUE_SCALE) }
catalogueScaleFloor = CatalogueScaleFloors[it.number] ?: MIN_CATALOGUE_SCALE, storyGrouping = it.number in StoryGroupingChannels) }
/** Living Network Stage E: story grouping beyond Paradox, one channel at a time. Heroes first (4 November 2026): 119 story parts a week, none booked together. */
private val StoryGroupingChannels get() = setOf(602)
/**
* Where a short series must keep at least half its tier's weight, so the hybrid trims its weekly repeats without cutting its
@@ -66,7 +66,7 @@ internal class HttpsEmbyTransport(internal val deviceId: String = UUID.randomUUI
connection.connectTimeout = 10_000
connection.readTimeout = request.readTimeoutMs ?: if (request.path == "/Sessions/Logout") 5_000 else 15_000
connection.setRequestProperty("Accept", "application/json")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.102.0\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.103.0\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -33,7 +33,7 @@ internal class SecureMediaDataSource(private val session: EmbySampleSession) : B
next.instanceFollowRedirects = false
next.connectTimeout = 10_000
next.readTimeout = 20_000
next.setRequestProperty("User-Agent", "OrbitTV/0.102.0")
next.setRequestProperty("User-Agent", "OrbitTV/0.103.0")
next.setRequestProperty("Accept-Encoding", "identity")
session.mediaHeaders(address).forEach { (key, value) -> next.setRequestProperty(key, value) }
if (dataSpec.position != 0L || dataSpec.length != C.LENGTH_UNSET.toLong()) {
@@ -128,8 +128,9 @@ class StoryGroupingTest {
assertEquals(run(pool = unmarked, stories = false), run(pool = unmarked, stories = true))
}
@Test fun onlyParadoxGroupsStories() {
assertEquals(listOf(106), SmartChannels.filter { it.storyGrouping }.map { it.number })
assertTrue(SmartChannels.first { it.number == 106 }.tierWeights != null)
@Test fun onlyApprovedChannelsGroupStories() {
// Paradox (Stage A), then Heroes (Stage E, one channel at a time). Grouping works through weighted rotation.
assertEquals(listOf(106, 602), SmartChannels.filter { it.storyGrouping }.map { it.number }.sorted())
assertTrue(SmartChannels.filter { it.storyGrouping }.all { it.tierWeights != null })
}
}
+37
View File
@@ -0,0 +1,37 @@
# 0.103.0: story grouping on Heroes (Living Network Stage E, first channel)
Step 3 of Paul's agreed order: story grouping on more channels, one at a time.
**Why Heroes first:** of the weighted channels, Heroes had the most broken stories. Its committed week has 119 story parts and none of them sit next to their other parts.
| Weighted channel | Stories in its pool | Story parts this week | Parts aired together |
|---|---|---|---|
| Heroes 602 | 84 | 119 | 0 |
| Orbit X 603 | 158 | 46 | 0 |
| Vortex 601 | 169 | 36 | 0 |
| Orbit 1 101 | 144 | 21 | 0 |
| Evidence 107 | 190 | 11 | 0 |
**The change:** `storyGrouping = true` for 602, through `StoryGroupingChannels`. The rule is the same one Paradox uses since 0.99.13, and is described in StoryGroups.kt.
## Checks
- 467 unit tests run, 0 failures; lint clean.
- **Four-week replay** from the box's plans and pools at 18:27 today. Every fingerprint is identical to 0.102.0 except Heroes, and Heroes' committed bookings are unchanged (`kept=true`).
- **Heroes over the four weeks** (1,807 new programmes):
| | 0.102.0 | 0.103.0 |
|---|---|---|
| Story openers followed by part 2 | 0 | 157 |
| Openers left hanging | 160 | 6 |
| Later parts aired alone | 194 | 5 |
| Same episode again within 7 days | 91 | 88 |
The show mix barely moves (Justice League 150 → 152, Teen Titans 151 → 149).
**The 11 parts left apart are the library's own numbering, not the rule:**
- **Batman: The Animated Series:** The Cat and the Claw is numbered E1 and E8, and Robin's Reckoning E51 and E53. They aren't consecutive, so the rule correctly won't treat them as stories.
- **Spider-Man:** Six Forgotten Warriors (1) has no part 2 next to it in the library.
- **Justice League:** one part 2 continues a part 1 that was already booked before the replay.
APK: `outputs/revision-0.103.0`, signed. Not installed.