Add flight-day detector + richer "Fix this day" screen#90
Open
kyleve wants to merge 13 commits into
Open
Conversation
Plan step: distance-helper. Adds a haversine great-circle distance in meters on Coordinate (RegionKit), needed by the flight-day detector to turn consecutive fixes into a ground speed across continent-spanning gaps where GeoPolygon's planar edge math would be wrong. Pure groundwork (no behavior change) plus unit tests.
Plan step: input-samples. Extend DataIssueInput with per-day GPS samples (gpsVisit/significantChange only, sorted by timestamp) so a speed-based detector can walk consecutive fixes; add ReportReader.samples(inYear:) and have DataIssueScanner group them by day. Existing detectors and the aggregated inputs are unchanged. Groundwork (no behavior change).
Plan step: flight-detector. Flags a day whose region set was inflated by cruise-speed GPS fixes crossing untracked geography (a flight): a fix is a fly-over point when the legs on both sides clear ~300 km/h over >=80 km, and a region is spurious when all its fixes are fly-over points. Requires at least two fly-over fixes so a lone teleport glitch doesn't read as a plane, and only fires when >=1 region is removed and >=1 survives. Adds IssueResolution.correctFlightDay, DataIssueCategory.flightDay, DataIssueID.flightDay, FlightDayIssue, and registers the detector in DataIssueScanner's default list. Thresholds injectable via @_spi(Testing).
Plan step: flight-detector-tests. Covers the July 14 NYC->SF profile
(flags, keeps {NY, CA}, drops .other, peak >300 km/h), and confirms it
ignores fast driving below threshold, a lone teleport glitch, a
mid-flight-only day, and a day with no samples. Adds a gpsSample fixture
helper to DataIssueDetectorTestSupport.
Plan step: extract-map. Pulls the map + pin de-duplication + GPS uncertainty circles out of RegionDaysView into a reusable RecordedPointsMap that tints pins per region (so a multi-region day like a flight reads clearly). RegionDaysView now feeds it single-region points; the flight detail view and 'Fix this day' screen reuse it next.
Plan step: day-points-read. Adds DayAggregator.pointsByRegion(onDay:), ReportReader.locations(onDay:), and YearReportModel.locations(onDay:), returning a day's recorded points grouped by attributed region — so the flight detail view and 'Fix this day' screen can map every point of a multi-region day at once. Includes DayAggregator tests.
Plan step: flight-detail-view. FlightDayDetailView shows the day's points map (fly-over pins tinted apart from endpoints), an inference explanation, a one-tap 'Apply — keep <regions>' authoritative overrideDay, a 'Not what you expected?' escape hatch into the regular DayRelabelView, and a 'These are all correct' dismiss. ResolutionView routes .correctFlightDay to it, with an airplane section icon and flight row copy.
Plan step: enrich-fix-screen. DayRelabelView now shows RecordedPointsMap of the day's recorded points at the top and an optional DayRelabelReason banner explaining why the day was flagged. The reason is threaded from each entry point: border drift (ResolutionView), travel day (AbruptChangeDetailView), flight (FlightDayDetailView's 'Not what you expected?'), and none for plain Elsewhere browsing (RegionDaysView).
Plan step: strings-previews-docs. Adds WhereUI Strings for the flight section/row, the FlightDayDetailView copy, and the DayRelabelReason banners; seeds a FlightDayIssue in PreviewSupport (and thus the flight detail/relabel previews); adds an end-to-end ResolveModel flight test; and documents the detector + DataIssueInput.daySamples in WhereCore's README/AGENTS.
Addresses code-review findings 1-4 (missing tests): 1. applyingFlightFixClearsTheIssue — idempotence: the one-tap authoritative overrideDay clears the issue on rescan. 2. flightDetectionIgnoresManualAndEvidenceSamples — the coast-to-coast pattern recorded as manual/evidence fixes produces no flight issue (gpsSamplesByDay excludes non-GPS sources from the speed math). 3. keepsRegionWithALegitimateLayover — a genuine .other dwell keeps the region grounded, so a flight with fly-over .other points isn't stripped. 4. flightDayDetailViewHosts — hosts FlightDayDetailView without crashing. Also refactors ResolveModelTests to share a flight-seeding helper.
…ector # Conflicts: # Where/WhereCore/AGENTS.md # Where/WhereCore/README.md # Where/WhereCore/Sources/DataResolution/DataIssue.swift # Where/WhereCore/Sources/DataResolution/DataIssueDetector.swift # Where/WhereCore/Tests/DataIssueDetectorTestSupport.swift # Where/WhereUI/Sources/Preview/PreviewSupport.swift # Where/WhereUI/Sources/Resolution/ResolutionView.swift # Where/WhereUI/Sources/Secondary/RegionDaysView.swift
Second main merge brought PR #87, which turned RegionAttributor into the 'any RegionAttributing' protocol across the detection/aggregation APIs. Point the two flight-feature params added by this branch (DayAggregator.pointsByRegion, FlightDayDetector.flightIssue) at the protocol so they accept the store-backed attributor callers now pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds a speed-based flight-day classifier to the Resolve tab. When you fly, passive GPS drops cruise-speed fixes across regions you only passed over — e.g. a NYC→SF flight adds a spurious
.other(the fly-over states collapse to Elsewhere since theRegionenum only models CA/NY/Canada/EU). This detects those days and offers a one-tap fix.It also enriches the shared "Fix this day" screen (
DayRelabelView) with a map of the day's recorded points and a classifier-driven "why" banner, so every resolution flow — not just flights — shows where the GPS points actually were and why the day was flagged.Validated against a real July 14 backup: cruise legs of 590–898 km/h (a 1,346 km leg in 90 min) vs. ground/noise blips topping out at ~116 km/h. A ~300 km/h threshold + ~80 km min-leg-distance separates flight from driving and from GPS teleport glitches with a wide margin.
How it works
overrideDaykeeping only the endpoints (reversible — raw samples untouched).RecordedPointsMapwas extracted fromRegionDaysViewand is now shared by the Elsewhere drill-in, the flight detail view, andDayRelabelView.Design decisions
DayRelabelViewseeded from the day's actual regions (so a wrong guess isn't baked in).Testing
FlightDayDetectorunit tests: flags the NYC→SF profile (keeps{NY, CA}, drops.other, peak >300); ignores fast driving, a lone teleport glitch, a mid-flight-only day, and a legitimate layover.DayAggregator.pointsByRegiontests;Coordinate.distancegreat-circle tests.ResolveModeltests: a seeded flight surfaces through the real scanner; applying the fix clears the issue (idempotence); manual/evidence samples are excluded from the speed math.FlightDayDetailViewhosting test + previews../swiftformat --lintclean;RegionKitTests,WhereCoreTests,WhereUITests, and the fullStuff-iOS-Testsscheme all pass locally.Notes / limitations
driftThresholdMeters, a near-border fly-over fix could make both the border-drift and flight detectors fire for the same day (two rows); resolving either clears both. Minor UX, self-resolving.Stringsuse the codebase's inlineString(localized:defaultValue:)pattern; catalog translations populate via Xcode extraction.