-
Notifications
You must be signed in to change notification settings - Fork 0
Add WhereData module: persistence, GPS, evidence, simulated-year tests #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
1eb60d4
Add WhereData module: persistence, GPS, evidence, simulated-year tests
kyleve 0749852
Add swift testing and swift data skills
kyleve ba1c9ee
WhereData: address PR review findings
kyleve 6f17916
WhereCore/WhereData: address PR review questions
kyleve f72d05e
AGENTS: don't act on PR comments or poll CI without being asked
kyleve d9cf441
WhereCore: bundle US Census state polygons for CA/NY attribution
kyleve df1ecc1
AGENTS: prefix anything posted on the user's behalf
kyleve c598c00
WhereData: drop data snapshot tests in favor of #expect assertions
kyleve 13424bc
swiftformat: wrap multi-arg calls/decls at 120 columns
kyleve 7dd8c74
swiftformat: --allow-partial-wrapping false for one-per-line params
kyleve f2a3dd9
swiftformat: tighten --max-width from 120 to 100
kyleve 56b19f0
AGENTS: gitignore Plans/, remove committed plan
kyleve 4ed1872
AGENTS: drop the Plans section
kyleve 32296a0
WhereData: add NY-heavy year tests as mirror of SimulatedYear
kyleve 84eb9b5
WhereData: add bare-majority CA/NY tests (1-day margin)
kyleve bf22f75
WhereData: add back-and-forth ~8-week alternation tests
kyleve d09b463
Where: merge WhereData into WhereCore
kyleve a83cde4
WhereCore: address remaining PR feedback (groups A–H)
kyleve 154e91b
AGENTS+ide: --no-open, working-on-plans rule
kyleve 92f8223
WhereCore: drop InMemoryStore, use SwiftDataStore(.inMemory) in tests
kyleve 933ef88
WhereCoreTests: drop redundant SwiftDataStoreTests, keep upsert asser…
kyleve 9c71a25
SwiftDataStore: defer perform counter, rename addSample, add Storage.…
kyleve 75d8c8b
RegionAttributor: rename to .shared, extract GeoJSON, assert on empty…
kyleve 0897300
Region.localizedName: switch with literal String(localized:) per case
kyleve 9adf2b6
Evidence: drop manual Codable, require contentType, add .other label
kyleve f6869f1
SwiftDataStore: per-perform peer ModelContext for transactional writes
kyleve File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,22 @@ | ||
| { | ||
| "swiftui-pro": { | ||
| "repo": "twostraws/swiftui-agent-skill", | ||
| "path": "swiftui-pro", | ||
| "ref": "61b74001b64b292da8397355464d7c8a4c2c7d89" | ||
| "repo": "twostraws/swiftui-agent-skill", | ||
| "path": "swiftui-pro", | ||
| "ref": "61b74001b64b292da8397355464d7c8a4c2c7d89" | ||
| }, | ||
| "swift-concurrency-pro": { | ||
| "repo": "twostraws/Swift-Concurrency-Agent-Skill", | ||
| "path": "swift-concurrency-pro", | ||
| "ref": "e710f8d577ccbff1ac047aac1594a5683000cb0b" | ||
| "repo": "twostraws/Swift-Concurrency-Agent-Skill", | ||
| "path": "swift-concurrency-pro", | ||
| "ref": "e710f8d577ccbff1ac047aac1594a5683000cb0b" | ||
| }, | ||
| "swift-testing-pro": { | ||
| "repo": "twostraws/Swift-Testing-Agent-Skill", | ||
| "path": "swift-testing-pro", | ||
| "ref": "2d6bba14a3c8bf3694f218b92fffe617c41ae43e" | ||
| }, | ||
| "swiftdata-pro": { | ||
| "repo": "twostraws/SwiftData-Agent-Skill", | ||
| "path": "swiftdata-pro", | ||
| "ref": "922d989473a9914210b41529a1ac5636aff4b8c1" | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| # External skills — fetched via ./sync-agents --install | ||
| /swift-concurrency-pro/ | ||
| /swift-testing-pro/ | ||
| /swiftdata-pro/ | ||
| /swiftui-pro/ |
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
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Empty file.
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import Foundation | ||
|
|
||
| /// A WGS84 latitude/longitude pair. Kept as a plain value type so the | ||
| /// pure model layer never has to link CoreLocation — the `Location/` | ||
| /// adapter converts from `CLLocation` at the boundary. | ||
| public struct Coordinate: Hashable, Codable, Sendable { | ||
| public let latitude: Double | ||
| public let longitude: Double | ||
|
|
||
| public init(latitude: Double, longitude: Double) { | ||
| self.latitude = latitude | ||
| self.longitude = longitude | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import Foundation | ||
|
|
||
| /// Pure rules for turning `LocationSample`s and manual day entries into | ||
| /// `DayPresence` values and `YearReport`s. No I/O. | ||
| /// | ||
| /// Rule: a day "counts" for a region if **any** sample in that calendar day | ||
| /// fell inside the region. A single day can therefore belong to multiple | ||
| /// regions (e.g. a CA→NY same-day flight). Manual day entries union with | ||
| /// GPS-derived attributions for the same day. | ||
| public struct DayAggregator: Sendable { | ||
| public let calendar: Calendar | ||
| public let timeZone: TimeZone | ||
|
|
||
| public init( | ||
| calendar: Calendar = Calendar(identifier: .gregorian), | ||
| timeZone: TimeZone = .current, | ||
| ) { | ||
| var cal = calendar | ||
| cal.timeZone = timeZone | ||
| self.calendar = cal | ||
| self.timeZone = timeZone | ||
| } | ||
|
|
||
| public func aggregate( | ||
| samples: [LocationSample], | ||
| attributor: RegionAttributor, | ||
| ) -> [DayPresence] { | ||
| var dayRegions: [Date: Set<Region>] = [:] | ||
| for sample in samples { | ||
| let dayStart = calendar.startOfDay(for: sample.timestamp) | ||
| let region = attributor.region(at: sample.coordinate) | ||
| dayRegions[dayStart, default: []].insert(region) | ||
| } | ||
| return dayRegions | ||
| .map { DayPresence(date: $0.key, regions: $0.value) } | ||
| .sorted { $0.date < $1.date } | ||
| } | ||
|
|
||
| public func report( | ||
| for year: Int, | ||
| samples: [LocationSample], | ||
| manualDays: [DayPresence] = [], | ||
| attributor: RegionAttributor, | ||
| ) -> YearReport { | ||
| var dayRegions: [Date: Set<Region>] = [:] | ||
| for day in aggregate(samples: samples, attributor: attributor) { | ||
| dayRegions[day.date, default: []].formUnion(day.regions) | ||
| } | ||
| for day in manualDays { | ||
| let dayStart = calendar.startOfDay(for: day.date) | ||
| dayRegions[dayStart, default: []].formUnion(day.regions) | ||
| } | ||
|
|
||
| let yearDays = dayRegions | ||
| .filter { calendar.component(.year, from: $0.key) == year } | ||
| .map { DayPresence(date: $0.key, regions: $0.value) } | ||
| .sorted { $0.date < $1.date } | ||
|
|
||
| var totals: [Region: Int] = [:] | ||
| for day in yearDays { | ||
| for region in day.regions { | ||
| totals[region, default: 0] += 1 | ||
| } | ||
| } | ||
|
|
||
| return YearReport(year: year, days: yearDays, totals: totals) | ||
| } | ||
|
|
||
| /// Half-open `DateInterval` spanning the requested calendar year in this | ||
| /// aggregator's calendar and timezone: `start` is the first instant of | ||
| /// `year`, `end` is the first instant of `year + 1`. `WhereStore` | ||
| /// implementations must therefore filter as `timestamp >= start && | ||
| /// timestamp < end` so the first instant of the next year is excluded | ||
| /// (and not double-counted by the next year's report). | ||
| public func yearInterval(year: Int) -> DateInterval { | ||
| var startComponents = DateComponents() | ||
| startComponents.year = year | ||
| startComponents.month = 1 | ||
| startComponents.day = 1 | ||
| let start = calendar.date(from: startComponents) ?? Date(timeIntervalSince1970: 0) | ||
| var endComponents = DateComponents() | ||
| endComponents.year = year + 1 | ||
| endComponents.month = 1 | ||
| endComponents.day = 1 | ||
| let end = calendar.date(from: endComponents) ?? start | ||
| return DateInterval(start: start, end: end) | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.