Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow - Add Core Data persistence for plants, care schedules, and cached API data - Create collection view with grid/list layouts and filtering - Build plant detail views with care information display - Integrate Trefle botanical API for plant care data - Add local image storage for captured plant photos - Implement dependency injection container for testability - Include accessibility support throughout the app Bug fixes in this commit: - Fix Trefle API decoding by removing duplicate CodingKeys - Fix LocalCachedImage to load from correct PlantImages directory - Set dateAdded when saving plants for proper collection sorting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
327
Docs/IMPLEMENTATION_PLAN.md
Normal file
327
Docs/IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Botanica - Plant Identification iOS App
|
||||
|
||||
## Overview
|
||||
Custom iOS 17+ app using SwiftUI that identifies plants via camera with care schedules.
|
||||
|
||||
**Stack:**
|
||||
- On-device ML: PlantNet-300K model converted to Core ML
|
||||
- Online API: Pl@ntNet (my.plantnet.org) for higher accuracy
|
||||
- Care data: Trefle API (open source botanical database)
|
||||
- Architecture: Clean Architecture + MVVM
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Foundation (Week 1-2)
|
||||
|
||||
**Goal:** Core infrastructure with camera capture
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 1.1 | Create Xcode project (iOS 17+, SwiftUI) |
|
||||
| 1.2 | Set up folder structure (App/, Core/, Domain/, Data/, ML/, Presentation/) |
|
||||
| 1.3 | Implement `DIContainer.swift` for dependency injection |
|
||||
| 1.4 | Create domain entities: `Plant`, `PlantIdentification`, `PlantCareSchedule`, `CareTask` |
|
||||
| 1.5 | Define repository protocols in `Domain/RepositoryInterfaces/` |
|
||||
| 1.6 | Build `NetworkService.swift` with async/await and multipart upload |
|
||||
| 1.7 | Implement `CameraView` + `CameraViewModel` with AVFoundation |
|
||||
| 1.8 | Set up Core Data stack for persistence |
|
||||
| 1.9 | Create tab navigation (Camera, Collection, Care, Settings) |
|
||||
|
||||
**Deliverable:** Working camera capture with photo preview
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: On-Device ML (Week 3-4)
|
||||
|
||||
**Goal:** Offline plant identification with Core ML
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 2.1 | Download PlantNet-300K pre-trained ResNet weights |
|
||||
| 2.2 | Convert to Core ML using `coremltools` (Python script) |
|
||||
| 2.3 | Add `PlantNet300K.mlpackage` to Xcode |
|
||||
| 2.4 | Create `PlantLabels.json` with 1,081 species names |
|
||||
| 2.5 | Implement `PlantClassificationService.swift` using Vision framework |
|
||||
| 2.6 | Create `ImagePreprocessor.swift` for model input normalization |
|
||||
| 2.7 | Build `IdentifyPlantOnDeviceUseCase.swift` |
|
||||
| 2.8 | Create `IdentificationView` showing results with confidence scores |
|
||||
| 2.9 | Build `SpeciesMatchCard` and `ConfidenceIndicator` components |
|
||||
| 2.10 | Performance test on device (target: <500ms inference) |
|
||||
|
||||
**Deliverable:** End-to-end offline identification flow
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: PlantNet API Integration (Week 5-6)
|
||||
|
||||
**Goal:** Hybrid identification with API fallback
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 3.1 | Register at my.plantnet.org for API key |
|
||||
| 3.2 | Create `PlantNetEndpoints.swift` (POST /v2/identify/{project}) |
|
||||
| 3.3 | Implement `PlantNetAPIService.swift` with multipart image upload |
|
||||
| 3.4 | Create DTOs: `PlantNetIdentifyResponseDTO`, `PlantNetSpeciesDTO` |
|
||||
| 3.5 | Build `PlantNetMapper.swift` (DTO → Domain entity) |
|
||||
| 3.6 | Implement `IdentifyPlantOnlineUseCase.swift` |
|
||||
| 3.7 | Create `HybridIdentificationUseCase.swift` (on-device first, API for confirmation) |
|
||||
| 3.8 | Add network reachability monitoring |
|
||||
| 3.9 | Handle rate limiting (500 free requests/day) |
|
||||
| 3.10 | Implement `IdentificationCache.swift` for previous results |
|
||||
|
||||
**Deliverable:** Hybrid identification combining on-device + API
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Trefle API & Plant Care (Week 7-8)
|
||||
|
||||
**Goal:** Complete care information and scheduling
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 4.1 | Register at trefle.io for API token |
|
||||
| 4.2 | Create `TrefleEndpoints.swift` (GET /plants/search, GET /species/{slug}) |
|
||||
| 4.3 | Implement `TrefleAPIService.swift` |
|
||||
| 4.4 | Create DTOs: `TrefleSpeciesDTO`, `GrowthDataDTO` |
|
||||
| 4.5 | Build `TrefleMapper.swift` mapping growth data to care schedules |
|
||||
| 4.6 | Implement `FetchPlantCareUseCase.swift` |
|
||||
| 4.7 | Create `CreateCareScheduleUseCase.swift` |
|
||||
| 4.8 | Build `PlantDetailView` with `CareInformationSection` |
|
||||
| 4.9 | Implement `CareScheduleView` with upcoming tasks |
|
||||
| 4.10 | Add local notifications for care reminders |
|
||||
|
||||
**Deliverable:** Full plant care data with watering/fertilizer schedules
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Plant Collection & Persistence (Week 9-10)
|
||||
|
||||
**Goal:** Saved plants with full offline support
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 5.1 | Define Core Data models (PlantMO, CareScheduleMO, IdentificationMO) |
|
||||
| 5.2 | Implement `CoreDataPlantStorage.swift` |
|
||||
| 5.3 | Build `PlantCollectionRepository.swift` |
|
||||
| 5.4 | Create use cases: `SavePlantUseCase`, `FetchCollectionUseCase` |
|
||||
| 5.5 | Build `CollectionView` with grid layout |
|
||||
| 5.6 | Implement `ImageCache.swift` for offline images |
|
||||
| 5.7 | Add search/filter in collection |
|
||||
|
||||
**Deliverable:** Full plant collection management with offline support
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Release (Week 11-12)
|
||||
|
||||
**Goal:** Production-ready application
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| 6.1 | Build `SettingsView` (offline mode toggle, API status, cache clear) |
|
||||
| 6.2 | Add comprehensive error handling with `ErrorView` |
|
||||
| 6.3 | Implement loading states with shimmer effects |
|
||||
| 6.4 | Add accessibility labels and Dynamic Type support |
|
||||
| 6.5 | Performance optimization pass |
|
||||
| 6.6 | Write unit tests for use cases and services |
|
||||
| 6.7 | Write UI tests for critical flows |
|
||||
| 6.8 | Final QA and bug fixes |
|
||||
|
||||
**Deliverable:** App Store ready application
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
Botanica/
|
||||
├── App/
|
||||
│ ├── BotanicaApp.swift
|
||||
│ └── Configuration/
|
||||
│ ├── AppConfiguration.swift
|
||||
│ └── APIKeys.swift
|
||||
├── Core/
|
||||
│ ├── DI/DIContainer.swift
|
||||
│ ├── Extensions/
|
||||
│ └── Utilities/
|
||||
├── Domain/
|
||||
│ ├── Entities/
|
||||
│ │ ├── Plant.swift
|
||||
│ │ ├── PlantIdentification.swift
|
||||
│ │ ├── PlantCareSchedule.swift
|
||||
│ │ └── CareTask.swift
|
||||
│ ├── UseCases/
|
||||
│ │ ├── Identification/
|
||||
│ │ │ ├── IdentifyPlantOnDeviceUseCase.swift
|
||||
│ │ │ ├── IdentifyPlantOnlineUseCase.swift
|
||||
│ │ │ └── HybridIdentificationUseCase.swift
|
||||
│ │ ├── PlantCare/
|
||||
│ │ │ ├── FetchPlantCareUseCase.swift
|
||||
│ │ │ └── CreateCareScheduleUseCase.swift
|
||||
│ │ └── Collection/
|
||||
│ └── RepositoryInterfaces/
|
||||
├── Data/
|
||||
│ ├── Repositories/
|
||||
│ ├── DataSources/
|
||||
│ │ ├── Remote/
|
||||
│ │ │ ├── PlantNetAPI/
|
||||
│ │ │ │ ├── PlantNetAPIService.swift
|
||||
│ │ │ │ └── DTOs/
|
||||
│ │ │ ├── TrefleAPI/
|
||||
│ │ │ │ ├── TrefleAPIService.swift
|
||||
│ │ │ │ └── DTOs/
|
||||
│ │ │ └── NetworkService/
|
||||
│ │ └── Local/
|
||||
│ │ ├── CoreData/
|
||||
│ │ └── Cache/
|
||||
│ └── Mappers/
|
||||
├── ML/
|
||||
│ ├── Models/
|
||||
│ │ └── PlantNet300K.mlpackage
|
||||
│ ├── Services/
|
||||
│ │ └── PlantClassificationService.swift
|
||||
│ └── Preprocessing/
|
||||
├── Presentation/
|
||||
│ ├── Scenes/
|
||||
│ │ ├── Camera/
|
||||
│ │ ├── Identification/
|
||||
│ │ ├── PlantDetail/
|
||||
│ │ ├── Collection/
|
||||
│ │ ├── CareSchedule/
|
||||
│ │ └── Settings/
|
||||
│ ├── Common/Components/
|
||||
│ └── Navigation/
|
||||
└── Resources/
|
||||
├── PlantLabels.json
|
||||
└── Assets.xcassets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Details
|
||||
|
||||
### Pl@ntNet API
|
||||
- **Base URL:** `https://my-api.plantnet.org`
|
||||
- **Endpoint:** `POST /v2/identify/{project}`
|
||||
- **Free tier:** 500 requests/day
|
||||
- **Coverage:** 77,565 species
|
||||
- **Documentation:** [my.plantnet.org/doc](https://my.plantnet.org/doc)
|
||||
|
||||
### Trefle API
|
||||
- **Base URL:** `https://trefle.io/api/v1`
|
||||
- **Endpoints:**
|
||||
- `GET /plants/search?q={name}`
|
||||
- `GET /species/{slug}`
|
||||
- **Data:** Light requirements, watering, soil, temperature, fertilizer, growth info
|
||||
- **Documentation:** [docs.trefle.io](https://docs.trefle.io)
|
||||
|
||||
---
|
||||
|
||||
## Core ML Conversion
|
||||
|
||||
### Prerequisites
|
||||
```bash
|
||||
pip install torch torchvision coremltools pillow numpy
|
||||
```
|
||||
|
||||
### Conversion Script
|
||||
```python
|
||||
# scripts/convert_plantnet_to_coreml.py
|
||||
import torch
|
||||
import torchvision.models as models
|
||||
import coremltools as ct
|
||||
|
||||
# Load PlantNet-300K pre-trained ResNet
|
||||
model = models.resnet50(weights=None)
|
||||
model.fc = torch.nn.Linear(model.fc.in_features, 1081)
|
||||
model.load_state_dict(torch.load("resnet50_weights_best_acc.tar", map_location='cpu')['state_dict'])
|
||||
model.eval()
|
||||
|
||||
# Trace for conversion
|
||||
traced = torch.jit.trace(model, torch.rand(1, 3, 224, 224))
|
||||
|
||||
# Convert to Core ML
|
||||
image_input = ct.ImageType(
|
||||
name="image",
|
||||
shape=(1, 3, 224, 224),
|
||||
scale=1/255.0,
|
||||
bias=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
|
||||
color_layout=ct.colorlayout.RGB
|
||||
)
|
||||
|
||||
mlmodel = ct.convert(
|
||||
traced,
|
||||
inputs=[image_input],
|
||||
convert_to="mlprogram",
|
||||
minimum_deployment_target=ct.target.iOS17,
|
||||
compute_precision=ct.precision.FLOAT16,
|
||||
)
|
||||
|
||||
mlmodel.save("PlantNet300K.mlpackage")
|
||||
```
|
||||
|
||||
### Download Weights
|
||||
```bash
|
||||
# From Zenodo (PlantNet-300K official)
|
||||
wget https://zenodo.org/records/4726653/files/resnet50_weights_best_acc.tar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Data Models
|
||||
|
||||
### Plant Entity
|
||||
```swift
|
||||
struct Plant: Identifiable, Sendable {
|
||||
let id: UUID
|
||||
let scientificName: String
|
||||
let commonNames: [String]
|
||||
let family: String
|
||||
let genus: String
|
||||
let imageURLs: [URL]
|
||||
let dateIdentified: Date
|
||||
let identificationSource: IdentificationSource
|
||||
|
||||
enum IdentificationSource: String {
|
||||
case onDevice, plantNetAPI, hybrid
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PlantCareSchedule Entity
|
||||
```swift
|
||||
struct PlantCareSchedule: Identifiable, Sendable {
|
||||
let id: UUID
|
||||
let plantID: UUID
|
||||
let lightRequirement: LightRequirement
|
||||
let wateringSchedule: WateringSchedule
|
||||
let temperatureRange: TemperatureRange
|
||||
let fertilizerSchedule: FertilizerSchedule?
|
||||
let tasks: [CareTask]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
| Test | Expected Result |
|
||||
|------|-----------------|
|
||||
| Camera capture | Take photo → preview displays |
|
||||
| On-device ML | Photo → top 10 species with confidence scores (<500ms) |
|
||||
| PlantNet API | Photo → API results match/exceed on-device accuracy |
|
||||
| Trefle API | Scientific name → care data (watering, light, fertilizer) |
|
||||
| Save plant | Save to collection → persists after app restart |
|
||||
| Offline mode | Disable network → on-device identification still works |
|
||||
| Care reminders | Create schedule → notification fires at scheduled time |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [PlantNet-300K GitHub](https://github.com/plantnet/PlantNet-300K)
|
||||
- [PlantNet-300K Dataset (Zenodo)](https://zenodo.org/records/4726653)
|
||||
- [Pl@ntNet API Docs](https://my.plantnet.org/doc)
|
||||
- [Trefle API Docs](https://docs.trefle.io)
|
||||
- [Apple Core ML Tools](https://github.com/apple/coremltools)
|
||||
- [Vision Framework](https://developer.apple.com/documentation/vision)
|
||||
Reference in New Issue
Block a user