History: import flights from CSV (Southwest PNR format)

Adds a "Import CSV…" entry to the History tab's + menu, opening a
file picker → preview → save flow.

- CSVFlightImporter: RFC-4180-ish quote-aware parser + format
  detection. Today only recognizes the Southwest PNR export schema
  (columns Flt No / ORG / DST / Dep Date / OPNG Flt). Returns
  ParsedFlight values with carrier, flight number, route, and
  scheduled departure.
- ImportCSVView: SwiftUI .fileImporter picks a CSV from Files (iCloud
  Drive / On My iPhone / etc.), parses on a Task, dedupes against
  the existing log via FlightHistoryStore.exists(...), shows a
  preview with "N new · M dupes" counts, imports on confirm.
- LoggedFlights created from import store the PNR in notes
  ("PNR: ABC123") and source "csv-import".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-05-29 10:27:20 -05:00
parent 9e1dbfbf90
commit d639cdef15
4 changed files with 431 additions and 0 deletions
+8
View File
@@ -76,6 +76,8 @@
HX0E000EEEE000EEEE000001 /* HistoryRouteMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX0E000EEEE000EEEE000002 /* HistoryRouteMapView.swift */; };
HX0F000FFFF000FFFF000001 /* YearInReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX0F000FFFF000FFFF000002 /* YearInReviewView.swift */; };
HX1000001000000010000001 /* AirframeMetadataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1000001000000010000002 /* AirframeMetadataService.swift */; };
HX1100001100000011000001 /* CSVFlightImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1100001100000011000002 /* CSVFlightImporter.swift */; };
HX1200001200000012000001 /* ImportCSVView.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1200001200000012000002 /* ImportCSVView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -161,6 +163,8 @@
HX0E000EEEE000EEEE000002 /* HistoryRouteMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryRouteMapView.swift; sourceTree = "<group>"; };
HX0F000FFFF000FFFF000002 /* YearInReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YearInReviewView.swift; sourceTree = "<group>"; };
HX1000001000000010000002 /* AirframeMetadataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirframeMetadataService.swift; sourceTree = "<group>"; };
HX1100001100000011000002 /* CSVFlightImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSVFlightImporter.swift; sourceTree = "<group>"; };
HX1200001200000012000002 /* ImportCSVView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportCSVView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -207,6 +211,7 @@
HX0D000DDDD000DDDD000002 /* LifetimeStatsView.swift */,
HX0E000EEEE000EEEE000002 /* HistoryRouteMapView.swift */,
HX0F000FFFF000FFFF000002 /* YearInReviewView.swift */,
HX1200001200000012000002 /* ImportCSVView.swift */,
AA5555555555555555555555 /* Styles */,
AA6666666666666666666666 /* Components */,
);
@@ -296,6 +301,7 @@
HX0600006666000066660002 /* CalendarFlightImporter.swift */,
HX0700007777000077770002 /* WalletPassObserver.swift */,
HX1000001000000010000002 /* AirframeMetadataService.swift */,
HX1100001100000011000002 /* CSVFlightImporter.swift */,
);
path = Services;
sourceTree = "<group>";
@@ -487,6 +493,8 @@
HX0E000EEEE000EEEE000001 /* HistoryRouteMapView.swift in Sources */,
HX0F000FFFF000FFFF000001 /* YearInReviewView.swift in Sources */,
HX1000001000000010000001 /* AirframeMetadataService.swift in Sources */,
HX1100001100000011000001 /* CSVFlightImporter.swift in Sources */,
HX1200001200000012000001 /* ImportCSVView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};