Skip to content

Commit 1eb752a

Browse files
authored
Merge pull request #84 from subtype-space/dev
Raise "was" anchor gate to 15 min (v1.18.2)
2 parents 900b9a9 + e458053 commit 1eb752a

4 files changed

Lines changed: 54 additions & 39 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "subspace-api",
3-
"version": "1.18.1",
3+
"version": "1.18.2",
44
"description": "API service for subtype.space",
55
"repository": {
66
"type": "git",

scripts/renderFlightPreview.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ const baseFlight: FlightDisplayData = {
3535
}
3636

3737
// Each scenario exercises a different schedule/data state: the on-time verdict (15-min rule),
38-
// the "was" anchors (any deviation >2 min), and the no-telemetry fallback (departs-in / arrives-in).
38+
// the "was" anchors (notable deviations >15 min only), and the no-telemetry fallback (departs-in / arrives-in).
3939
// eta/depTime are the actual times; schedEta/schedDep are the originals. adherence is derived below.
4040
type Scenario = { title: string; flight: FlightDisplayData }
4141
const scenarios: Scenario[] = [
4242
{ title: 'On time (0)', flight: { ...baseFlight, delayMin: 0, schedEta: '14:36' } },
4343
{
44-
title: 'Minor delay, still "On time" — 12 min (anchors show, <15-min threshold)',
44+
title: 'Minor delay — 12 min (On time, no anchor: under the 15-min gate)',
4545
flight: { ...baseFlight, depDelayMin: 12, schedDep: '08:00', delayMin: 12, schedEta: '14:24' },
4646
},
4747
{
@@ -57,8 +57,12 @@ const scenarios: Scenario[] = [
5757
flight: { ...baseFlight, delayMin: -22, schedEta: '14:58' },
5858
},
5959
{
60-
title: 'Late departure, early arrival (was 07:58 / 14:58)',
61-
flight: { ...baseFlight, depDelayMin: 14, schedDep: '07:58', delayMin: -22, schedEta: '14:58' },
60+
title: 'Late departure (20), early arrival (22) — both notable, both anchor',
61+
flight: { ...baseFlight, depDelayMin: 20, schedDep: '07:52', delayMin: -22, schedEta: '14:58' },
62+
},
63+
{
64+
title: 'Departed 26 late, arriving 10 early — origin anchors, arrival under the gate (On time)',
65+
flight: { ...baseFlight, depDelayMin: 26, schedDep: '07:46', depTime: '08:12', delayMin: -10, schedEta: '14:46', eta: '14:36' },
6266
},
6367
{
6468
title: 'Descending into SFO (near arrival)',

src/integrations/aerodatabox/renderer.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ import type { MarkupVariant } from '../../types/trmnl/types.js'
33
import { escapeHtml } from '../../utils/html.js'
44
import { buildArcSvg, formatDuration, planeSvg, AIRLINE_NAMES } from './formatters.js'
55

6-
// If a departure/arrival deviates more than +-2 minutes from schedule, show the original
7-
// scheduled time as a "was HH:MM" anchor next to the actual time. Returns '' otherwise.
6+
// Only surface the scheduled "was HH:MM" anchor for *notable* deviations. The actual time is
7+
// already shown, so a plane landing 2-14 min off schedule isn't worth an extra line — that's
8+
// detail, not glance. This 15-min bar matches the On-time/Delayed/Early adherence threshold, so
9+
// an anchor appears exactly when the flight is off-schedule enough to earn a verdict. Departure
10+
// and arrival are gated independently (a late pushback can anchor without the on-time arrival).
11+
const ANCHOR_MIN_DEVIATION_MIN = 15
12+
813
function wasAnchor(delayMin: number | null, schedTime: string): string {
9-
return delayMin != null && Math.abs(delayMin) > 2 && schedTime !== '--' ? `was ${escapeHtml(schedTime)}` : ''
14+
return delayMin != null && Math.abs(delayMin) > ANCHOR_MIN_DEVIATION_MIN && schedTime !== '--'
15+
? `was ${escapeHtml(schedTime)}`
16+
: ''
1017
}
1118

1219
// No-telemetry countdown: before wheels-up we count down to departure, after to arrival.

test/integrations/aerodatabox/renderers.test.ts

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ describe('renderMarkup', () => {
6161
})
6262

6363
it('full variant tiles carry only live telemetry (no delay tile); delay lives in the arc anchor', () => {
64-
const out = renderMarkup({ ...sampleFlight, delayMin: 14, schedEta: '14:22' }, 'full', 0, 'https://example.com')
64+
const out = renderMarkup({ ...sampleFlight, delayMin: 22, schedEta: '14:14' }, 'full', 0, 'https://example.com')
6565
expect(out).toContain('>ALT<')
6666
expect(out).toContain('>SPD<')
6767
expect(out).toContain('>HDG<')
6868
expect(out).not.toContain('>ARR<') // no delay tile
69-
expect(out).not.toContain('14m late') // no worded delta
70-
expect(out).toContain('was 14:22') // scheduled anchor on the arc
69+
expect(out).not.toContain('22m late') // no worded delta
70+
expect(out).toContain('was 14:14') // scheduled anchor on the arc (notable, >15 min)
7171
})
7272

7373
it('falls back to derived time-left + trip tiles when there is no live telemetry (no row of --)', () => {
@@ -123,47 +123,51 @@ describe('renderMarkup', () => {
123123
expect(half).toContain('plane-icon') // inline SVG on the flat route line
124124
})
125125

126-
it('shows the scheduled "was" anchor (two absolute clocks) only when the flight deviates', () => {
126+
it('shows the scheduled "was" anchor only for notable (>15 min) deviations, not minor ones', () => {
127127
// assert on the rendered anchor text, not the (always-present) .route-sched CSS rule
128-
// late: actual 14:36, scheduled 14:22
129-
const late = renderMarkup({ ...sampleFlight, delayMin: 14, schedEta: '14:22' }, 'half_vertical', 0, 'https://example.com')
130-
expect(late).toContain('>was 14:22<')
131-
expect(late).not.toContain('14m late') // no delta / no math
132-
133-
// early: actual 14:36, scheduled 14:48 — still an absolute clock, no negative delta / word
134-
const early = renderMarkup({ ...sampleFlight, delayMin: -12, schedEta: '14:48' }, 'half_vertical', 0, 'https://example.com')
135-
expect(early).toContain('>was 14:48<')
128+
// notably late (22 min): anchor shows the original scheduled clock
129+
const late = renderMarkup({ ...sampleFlight, delayMin: 22, schedEta: '14:14' }, 'half_vertical', 0, 'https://example.com')
130+
expect(late).toContain('>was 14:14<')
131+
expect(late).not.toContain('22m late') // no delta / no math
132+
133+
// notably early (20 min): still an absolute clock, no negative delta / word
134+
const early = renderMarkup({ ...sampleFlight, delayMin: -20, schedEta: '14:56' }, 'half_vertical', 0, 'https://example.com')
135+
expect(early).toContain('>was 14:56<')
136136
expect(early).not.toContain('early')
137137

138-
// on-time (within window): no anchor even though schedEta is present
139-
const onTime = renderMarkup({ ...sampleFlight, delayMin: 1, schedEta: '14:35' }, 'half_vertical', 0, 'https://example.com')
140-
expect(onTime).not.toContain('>was ')
138+
// minor deviation within the 15-min window: no anchor (the actual time already carries it) —
139+
// a plane landing 10 min off isn't worth an extra line on an at-a-glance display
140+
const minorLate = renderMarkup({ ...sampleFlight, delayMin: 10, schedEta: '14:26' }, 'half_vertical', 0, 'https://example.com')
141+
expect(minorLate).not.toContain('>was ')
142+
const minorEarly = renderMarkup({ ...sampleFlight, delayMin: -12, schedEta: '14:48' }, 'half_vertical', 0, 'https://example.com')
143+
expect(minorEarly).not.toContain('>was ')
144+
141145
// unknown schedule: no anchor
142146
const unknown = renderMarkup({ ...sampleFlight, delayMin: null, schedEta: '--' }, 'half_vertical', 0, 'https://example.com')
143147
expect(unknown).not.toContain('>was ')
144148
})
145149

146-
it('anchors departure and arrival independently (a late pushback shows "was" under the origin)', () => {
147-
// Departed 14 late, arrived on time (made up the time en route): only the origin gets an anchor.
148-
const depLate = { ...sampleFlight, depDelayMin: 14, schedDep: '07:58', delayMin: 0, schedEta: '14:36' }
150+
it('anchors departure and arrival independently (each gated on its own >15 min deviation)', () => {
151+
// Departed 22 late, arrived on time (made up the time en route): only the origin gets an anchor.
152+
const depLate = { ...sampleFlight, depDelayMin: 22, schedDep: '07:50', delayMin: 0, schedEta: '14:36' }
149153
const full = renderMarkup(depLate, 'full', 0, 'https://example.com')
150-
expect(full).toContain('was 07:58') // origin anchor
154+
expect(full).toContain('was 07:50') // origin anchor
151155
expect(full).not.toContain('was 14:36') // arrival on time -> no anchor
152156
const half = renderMarkup(depLate, 'half_vertical', 0, 'https://example.com')
153-
expect(half).toContain('>was 07:58<')
157+
expect(half).toContain('>was 07:50<')
154158

155-
// Both deviate -> both anchors render.
156-
const both = { ...sampleFlight, depDelayMin: 14, schedDep: '07:58', delayMin: 14, schedEta: '14:22' }
159+
// Both deviate notably -> both anchors render.
160+
const both = { ...sampleFlight, depDelayMin: 22, schedDep: '07:50', delayMin: 22, schedEta: '14:14' }
157161
const bothOut = renderMarkup(both, 'half_vertical', 0, 'https://example.com')
158-
expect(bothOut).toContain('>was 07:58<')
159-
expect(bothOut).toContain('>was 14:22<')
160-
161-
// Opposite directions (late pushback, early arrival) both anchor — the "was" is an absolute
162-
// clock, so the sign of each delay is irrelevant to the display.
163-
const mixed = { ...sampleFlight, depDelayMin: 14, schedDep: '07:58', delayMin: -10, schedEta: '14:46' }
164-
const mixedOut = renderMarkup(mixed, 'full', 0, 'https://example.com')
165-
expect(mixedOut).toContain('was 07:58') // late departure
166-
expect(mixedOut).toContain('was 14:46') // early arrival
162+
expect(bothOut).toContain('>was 07:50<')
163+
expect(bothOut).toContain('>was 14:14<')
164+
165+
// The screenshot case: pushed back 26 late but arrives only 10 early -> origin anchors, the
166+
// in-window arrival does not (10 < 15), even though the departure deviation is notable.
167+
const departedLateArrivedClose = { ...sampleFlight, depDelayMin: 26, schedDep: '07:46', delayMin: -10, schedEta: '14:46' }
168+
const mixedOut = renderMarkup(departedLateArrivedClose, 'full', 0, 'https://example.com')
169+
expect(mixedOut).toContain('was 07:46') // notable late departure
170+
expect(mixedOut).not.toContain('was 14:46') // minor early arrival -> gated out
167171
})
168172

169173
it('shows the on-time verdict as the 4th full-card tile (label-less), and hides it when unknown', () => {

0 commit comments

Comments
 (0)