docs: add API ML service migration spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
c3e7453390
commit
b69b32f5b3
@@ -0,0 +1,101 @@
|
||||
# API ML Service Migration Design
|
||||
|
||||
## Summary
|
||||
|
||||
Migrate `apps/api` image classification from in-process TensorFlow.js inference to the separate FastAPI ML service at `apps/ml-service`. The public API behavior and database writes stay the same; only the model inference boundary changes.
|
||||
|
||||
## Goals
|
||||
|
||||
- Remove TensorFlow.js model loading and image decoding from the Bun/Elysia API runtime.
|
||||
- Keep existing route behavior for image classifications and diagnoses.
|
||||
- Use `apps/ml-service` as the single runtime owner for Keras/TensorFlow inference.
|
||||
- Preserve the internal `classifyImage(file)` API shape used by existing routes.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not change frontend behavior.
|
||||
- Do not change database schema.
|
||||
- Do not add a fallback to TFJS inference.
|
||||
- Do not migrate upload storage or diagnosis workflows beyond the inference call.
|
||||
|
||||
## Architecture
|
||||
|
||||
`apps/api/src/lib/image-model.ts` remains the API-side classification boundary. Its implementation changes from local TFJS inference to an HTTP client for the ML service.
|
||||
|
||||
`classifyImage(file)` will:
|
||||
|
||||
1. Build multipart `FormData` with field name `file`.
|
||||
2. POST it to `${env.mlServiceUrl}/predict`.
|
||||
3. Validate the ML service response.
|
||||
4. Map returned Indonesian labels to API disease slugs.
|
||||
5. Return the existing `ClassificationResult` shape:
|
||||
- `predictedDiseaseSlug`
|
||||
- `confidence`
|
||||
- `probabilities`
|
||||
|
||||
Existing route files continue to call `classifyImage(file)`, so route and database logic remain stable.
|
||||
|
||||
## Runtime configuration
|
||||
|
||||
Add `mlServiceUrl` to `apps/api/src/config/env.ts`.
|
||||
|
||||
Default local value:
|
||||
|
||||
```txt
|
||||
http://127.0.0.1:8001
|
||||
```
|
||||
|
||||
Environment variable:
|
||||
|
||||
```txt
|
||||
ML_SERVICE_URL=http://127.0.0.1:8001
|
||||
```
|
||||
|
||||
The API should trim trailing slashes before appending `/predict` so both `http://127.0.0.1:8001` and `http://127.0.0.1:8001/` work.
|
||||
|
||||
## Label mapping
|
||||
|
||||
The ML service returns labels. The API maps labels to catalog slugs:
|
||||
|
||||
- `Bercak Daun` → `bercak-daun`
|
||||
- `Daun Sehat` → `daun-sehat`
|
||||
- `Karat Daun` → `karat-daun`
|
||||
- `Hawar Daun` → `hawar-daun`
|
||||
|
||||
This mapping follows the trained model output order handled by the ML service while preserving API/catalog slugs.
|
||||
|
||||
## Error handling
|
||||
|
||||
`classifyImage(file)` throws `Error` when:
|
||||
|
||||
- The input file MIME type is not `image/jpeg` or `image/png`.
|
||||
- The ML service cannot be reached.
|
||||
- The ML service returns a non-2xx response.
|
||||
- The ML service response is malformed.
|
||||
- The ML service returns a label that does not map to a known disease slug.
|
||||
|
||||
Existing routes keep their current behavior:
|
||||
|
||||
- `POST /api/v1/classifications/image` catches the error and returns service unavailable with `Model service error: ...`.
|
||||
- `POST /api/v1/diagnoses` catches the error and stores a failed diagnosis record.
|
||||
|
||||
## Dependency cleanup
|
||||
|
||||
Remove API dependencies that only supported local TFJS inference:
|
||||
|
||||
- `@tensorflow/tfjs`
|
||||
- `jpeg-js`
|
||||
- `pngjs`
|
||||
|
||||
Keep dependencies needed by routes, database, auth, and upload unchanged.
|
||||
|
||||
## Verification
|
||||
|
||||
Required verification after implementation:
|
||||
|
||||
- Run API typecheck with `bun run typecheck` from the repository root.
|
||||
- Confirm no `@tensorflow/tfjs`, `jpeg-js`, or `pngjs` imports remain under `apps/api/src`.
|
||||
- Start `apps/ml-service` and confirm `GET /health` returns `model_loaded: true` when the Keras model exists.
|
||||
- Exercise `classifyImage(file)` against the running ML service with a local image file or synthetic image and confirm it returns `predictedDiseaseSlug`, `confidence`, and sorted probabilities.
|
||||
|
||||
If full API route testing is blocked by external database or upload service requirements, report that explicitly and include the lower-level verification evidence.
|
||||
Reference in New Issue
Block a user