Skip to content

Locale-aware catalog (Milestone 1): local relevance#2

Open
realtobyfu wants to merge 6 commits into
locationfrom
claude/sweet-bassi-b542bf
Open

Locale-aware catalog (Milestone 1): local relevance#2
realtobyfu wants to merge 6 commits into
locationfrom
claude/sweet-bassi-b542bf

Conversation

@realtobyfu

Copy link
Copy Markdown
Owner

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: optional gbifTaxonKey / inaturalistTaxonID / monthlyAffinity + scientific-name join helper.
  • INaturalistService: keyless species_counts client (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 → explainable LocalCatalogItems) + visual-dominant identification reranker.
  • PlantIDAPIService: returns top-N candidates; single-result identify() delegates to it.

PR2 — UI layer (4475cee)

  • AppStore wiring (localityProfile, localCatalogItems, catalogFreshnessDate, refreshLocalCatalog(), region override that never touches an observation's stored region).
  • LocalDiscoveryPrompt pre-permission explainer + updated location usage string.
  • Ecology-led Explore: Near You NowReported This Month → existing sections, per-card "Why this plant?" copy ("reported nearby", no abundance claims), RegionPickerSheet, freshness pill.
  • Capture review surfaces reranked alternatives ("Other possibilities").

Fixes / tests / previews

  • 33aeb38 Restored the CoreML offline fallback that PR2 had inadvertently dropped in the capture flow.
  • f8fbe4f FieldnoteUnitTests (Swift Testing): 21 cases over ranking, the count→catalog join, and locality coarsening — all green.
  • bf5bd94 SwiftUI previews for the new UI (LocalCatalogSection/Card, LocalDiscoveryPrompt, RegionPickerSheet, and an extracted AlternativeCandidatesCard) so the new surfaces render in Xcode canvas.

Verification

  • xcodebuild build (iPhone 17 / iOS 26 sim): BUILD SUCCEEDED
  • xcodebuild test -only-testing:FieldnoteUnitTests: TEST SUCCEEDED

Review notes

  • Preview the new components in Xcode canvas (open each component file): LocalCatalogSection.swift, LocalDiscoveryPrompt.swift, RegionPickerSheet.swift, AlternativeCandidatesCard.swift.
  • Test target pbxproj/scheme entries were created in your Xcode session and applied to this branch; confirm the pbxproj merge is clean on your side.
  • Commit 34ce28d ("Add profile badges and progress") appears in this PR only because remote origin/location is one commit behind your local location; it is not part of this feature.

Deliberately deferred (M1.1 polish, not bugs)

  • Region picker uses a preset city list (vs. live geocoding search).
  • Capture "alternatives" = all reranked runners-up (vs. a score-closeness threshold).

🤖 Generated with Claude Code

realtobyfu and others added 6 commits June 24, 2026 14:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant