Locale-aware catalog (Milestone 1): local relevance#2
Open
realtobyfu wants to merge 6 commits into
Open
Conversation
Milestone 1 data layer for locale-aware discovery (client-side, no backend): - CatalogPlant: optional gbifTaxonKey / inaturalistTaxonID / monthlyAffinity plus a scientific-name join helper for matching external taxa. - INaturalistService: keyless species_counts client returning nearby + monthly taxa with default-photo license metadata. - LocalityProfile: coarse grid cell (privacy-preserving) + month/hemisphere. - LocalCatalogCache: per cell+month cache of species counts with freshness. - LocalRankingService: pure Stage-1 ecological ranking (log-scaled occurrence + seasonal affinity) producing explainable LocalCatalogItems, plus a visual-dominant identification reranker. - PlantIDAPIService: returns top-N candidates (PlantIdentificationCandidate) with optional GBIF key; single-result identify() delegates to it. See Docs/LocaleAwareCatalogImplementationPlan.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the PR1 data layer into the UI (Workstream B, items B1-B4).
B1 - AppStore wiring:
- Observable state: localityProfile, localCatalogItems, catalogFreshnessDate,
isRefreshingLocalCatalog, and a selectedRegionOverride (ExploreRegion) that is
a view preference and never touches an observation's stored region.
- refreshLocalCatalog(): resolves a coarse coordinate (current location or a
chosen city), builds a LocalityProfile, reuses a fresh LocalCatalogCache entry
or fetches via INaturalistService + stores it, then ranks the bundled catalog
with LocalRankingService. Resilient: falls back to stale cache and otherwise
leaves existing state untouched on any failure. Called from refresh().
- Derived helpers: nearYouNowItems, reportedThisMonthItems, catalogFreshnessLabel.
B2 - Location-value prompt:
- LocalDiscoveryPrompt explainer ("See plants reported near you and what is
active this season") with Use Approximate Location / Choose a City.
- NSLocationWhenInUseUsageDescription (both build configs) now mentions local
discovery, not just observation tagging.
B3 - Explore sections:
- Ecology-led order when a locality exists: Near You Now -> Reported This Month
-> existing Recently Encountered / custom / full catalog fallback.
- LocalCatalogSection/LocalCatalogCard render localCatalogItems with a
per-card "Why this plant?" line from explanationCodes.first.label
("reported nearby" wording, no abundance claims).
- RegionPickerSheet (current location vs. one of a few preset cities) plus a
freshness pill ("Updated N days ago"). Falls back to today's behavior when no
locality is set, so nothing regresses.
B4 - Capture review alternatives:
- CaptureMode.mlIdentification gains an alternatives: [RankedCandidate] payload.
- CaptureViewModel uses identifyCandidates + LocalRankingService.rerankCandidates
(locality items + month passed in from AppStore via CaptureView), surfacing the
reranked top result plus close runners-up. Manual-entry / error fallbacks intact.
- CaptureReviewSheet shows an "Other possibilities" card; tapping an alternative
fills the form and re-matches the catalog.
Build: ** BUILD SUCCEEDED ** (iPhone 17, iOS 26 simulator).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR2's candidate flow called Pl@ntNet directly via identifyCandidates, which dropped the on-device CoreML fallback the hybrid service provided — offline captures fell straight through to manual entry. Now a candidate-path failure falls back to HybridPlantIdentificationService.identify (API-first, CoreML offline) before manual entry, and the injected identificationService is no longer dead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds FieldnoteUnitTests (Swift Testing) covering the PR1 data layer: - LocalRankingService: count→catalog join (by iNat ID and name key), log-scaled occurrence ordering, bounded 0...1 scores, explanation-code selection, and visual-dominant identification reranking. - CatalogPlant: scientificNameKey normalization and match() precedence. - LocalityProfile: coarse-grid coarsening, hemisphere, and cache key. Also brings the FieldnoteUnitTests / FieldnoteUITests target definitions (synchronized groups) and scheme test entries onto this branch so the suite runs via `xcodebuild test -only-testing:FieldnoteUnitTests`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
So the new Explore + capture surfaces are reviewable in Xcode canvas: - LocalCatalogSection / LocalCatalogCard: previews via DEBUG-only LocaleCatalogPreviewData (Near You Now + Reported This Month, discovered and undiscovered states). - LocalDiscoveryPrompt: pre-permission explainer preview. - RegionPickerSheet: region list preview. - Extracted the capture "Other possibilities" UI from CaptureReviewSheet into a standalone AlternativeCandidatesCard view with its own preview (the sheet now composes it, passing applyCandidate). Build + FieldnoteUnitTests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Implements Milestone 1 of the locale-aware catalog (client-side, no backend) — Explore and identification now reflect where + when the user is. See
Fieldnote/Docs/LocaleAwareCatalogImplementationPlan.md.What's in here
PR1 — data layer (
74fceb5)CatalogPlant: optionalgbifTaxonKey/inaturalistTaxonID/monthlyAffinity+ scientific-name join helper.INaturalistService: keylessspecies_countsclient (nearby + monthly taxa, with default-photo license metadata).LocalityProfile: privacy-preserving coarse grid cell + month/hemisphere (precise coords never leave the device).LocalCatalogCache: per cell+month cache with freshness.LocalRankingService: pure Stage-1 ecological ranking (log-scaled occurrence + seasonal affinity → explainableLocalCatalogItems) + visual-dominant identification reranker.PlantIDAPIService: returns top-N candidates; single-resultidentify()delegates to it.PR2 — UI layer (
4475cee)localityProfile,localCatalogItems,catalogFreshnessDate,refreshLocalCatalog(), region override that never touches an observation's stored region).LocalDiscoveryPromptpre-permission explainer + updated location usage string.RegionPickerSheet, freshness pill.Fixes / tests / previews
33aeb38Restored the CoreML offline fallback that PR2 had inadvertently dropped in the capture flow.f8fbe4fFieldnoteUnitTests(Swift Testing): 21 cases over ranking, the count→catalog join, and locality coarsening — all green.bf5bd94SwiftUI previews for the new UI (LocalCatalogSection/Card, LocalDiscoveryPrompt, RegionPickerSheet, and an extractedAlternativeCandidatesCard) so the new surfaces render in Xcode canvas.Verification
xcodebuild build(iPhone 17 / iOS 26 sim): BUILD SUCCEEDEDxcodebuild test -only-testing:FieldnoteUnitTests: TEST SUCCEEDEDReview notes
LocalCatalogSection.swift,LocalDiscoveryPrompt.swift,RegionPickerSheet.swift,AlternativeCandidatesCard.swift.34ce28d("Add profile badges and progress") appears in this PR only because remoteorigin/locationis one commit behind your locallocation; it is not part of this feature.Deliberately deferred (M1.1 polish, not bugs)
🤖 Generated with Claude Code