From b69b32f5b3b069b831df9593abc4049cc4230200 Mon Sep 17 00:00:00 2001 From: Asep Haryana Saputra <90584806+MythEclipse@users.noreply.github.com> Date: Fri, 22 May 2026 19:46:48 +0000 Subject: [PATCH] docs: add API ML service migration spec Co-Authored-By: Claude Opus 4.7 --- ...6-05-22-api-ml-service-migration-design.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-22-api-ml-service-migration-design.md diff --git a/docs/superpowers/specs/2026-05-22-api-ml-service-migration-design.md b/docs/superpowers/specs/2026-05-22-api-ml-service-migration-design.md new file mode 100644 index 0000000..6d92c12 --- /dev/null +++ b/docs/superpowers/specs/2026-05-22-api-ml-service-migration-design.md @@ -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.