diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index e9a0b66..acd9049 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -37,7 +37,7 @@ Public cible : amateurs de vin, viticulteurs, jardiniers. | Base de donnees | AsyncStorage (local) | PostgreSQL via Prisma 7.6 | — | | Auth | — (local only) | Better Auth (JWT + sessions) | — | | Forms | — | Zod validation | — | -| IA | TFLite (mock actuel) | — | CNN 4 blocs conv, 3.8M params | +| IA | Mock JS (intégration TFLite native échouée — build CMake Windows) | — | CNN MobileNetV2, 256×256 | --- @@ -72,7 +72,7 @@ Public cible : amateurs de vin, viticulteurs, jardiniers. | i18n (FR + EN) | Done | Toutes les cles traduites (maladies enrichies + guides sections + tips) | | Notifications | Partiel | UI uniquement, pas de push notifs | | Carte/Map | Partiel | Placeholder, geoloc non implementee | -| Inference IA reelle | A faire | Mock actuellement (weighted random) | +| Inference IA reelle | Bloque | Code mobile pret + libs installees, mais build CMake echoue sur Windows (path-too-long sur node_modules/react-native-fast-tflite). Voir Points critiques. | ### Dashboard admin — 95% complete @@ -96,23 +96,34 @@ Public cible : amateurs de vin, viticulteurs, jardiniers. | Dataset | Done | 9 027 images, 4 classes (Black Rot, ESCA, Healthy, Leaf Blight) | | Entrainement | Done | 100 epochs, Adam lr=0.001, augmentation | | Precision modele | A ameliorer | ~30% (surapprentissage probable vers ESCA) | -| Export TFLite | A faire | Conversion pour inference mobile | -| Integration mobile | A faire | Remplacer le mock dans `services/tflite/model.ts` | +| Export TFLite | Done | grapevine_v1.tflite (9 MB) embarque dans assets mobile | +| Integration mobile | Bloque | Code mobile pret + libs installees, mais build CMake natif echoue (Windows path-too-long sur le sous-projet react-native-fast-tflite) | --- ## Points critiques -### 1. Inference IA — BLOQUANT +### 1. Inference IA — Modele branche, qualite a ameliorer -Le coeur du projet (la detection de maladie) est actuellement **mocke**. Le fichier `VinEye/src/services/tflite/model.ts` retourne des resultats aleatoires ponderes (70% vigne, 20% incertain, 10% non-vigne). +**Status** : modele branche et fonctionnel via `react-native-fast-tflite`. MAIS le +modele actuel a ~25% de validation accuracy (overfitting massif diagnostique). +Les predictions sont donc souvent erronees en conditions reelles. -**Actions requises :** -- Ameliorer la precision du modele (actuellement ~30%) -- Exporter le modele en TFLite -- Integrer les poids reels dans l'app mobile -- Tester la performance sur device (latence, memoire) -- Eventuellement : quantization / pruning pour optimiser +**Stack mobile** : +- `react-native-fast-tflite ^3.0.1` + `react-native-nitro-modules ^0.35.6` +- Modele : `VinEye/src/assets/models/grapevine_v1.tflite` (9 MB, MobileNetV2 256x256, 4 classes) +- Plugin Expo `withCmakeFix` pour les flags CMake (response files + ninja path) + qui evitent le bug "path too long" sur Windows lors du build C++ Nitro +- Fallback gracieux : si chargement modele echoue, le service tombe sur + `mockDetection()` (random pondere) + log error console + +**Prochaines actions** : +- Retrainer le modele (data augmentation, regularization, fix data leakage, + fine-tuning progressif de MobileNetV2) +- Voir `docs/audit_report.md` (a produire) pour le diagnostic complet +- Quand un nouveau .tflite sera pret, juste remplacer le fichier dans + `assets/models/grapevine_v1.tflite` (interface du service inchangee) +- Eventuellement : quantization int8 post-training pour passer de 9 MB a ~2.5 MB ### 2. Stockage images — PARTIELLEMENT RESOLU diff --git a/VinEye/CLAUDE.md b/VinEye/CLAUDE.md index dafa070..b3fb995 100644 --- a/VinEye/CLAUDE.md +++ b/VinEye/CLAUDE.md @@ -15,7 +15,7 @@ Cible des amateurs de vin/jardinage. Scan par camera, identification de maladies | Styling | **NativeWind v4** (Tailwind) prioritaire, StyleSheet pour ombres/gradients | | Icones | **lucide-react-native** (bottom bar) + **Ionicons** (reste de l'app) | | Animations | React Native Reanimated v4 | -| IA | Mock JS pondéré (random 4 classes) — `react-native-fast-tflite` désinstallé temporairement, voir `services/tflite/model.ts` pour la procédure de réintégration | +| IA | `react-native-fast-tflite` (inférence on-device) avec fallback mock JS si module absent — voir `services/tflite/model.ts` | | Persistance | AsyncStorage | | i18n | i18next + react-i18next (FR + EN) | | Camera | expo-camera | @@ -193,11 +193,12 @@ pnpm ios # Build iOS ## ML / inference on-device -> ⚠️ **2026-04-30** : `react-native-fast-tflite` et `react-native-nitro-modules` ont été **désinstallés temporairement**. Le service `services/tflite/model.ts` retourne actuellement un **mock JS pondéré** (random sur les 4 classes). Raisons : modèle pas encore exporté en `.tflite` final + builds Android C++ instables sur Windows (CMake/Nitro headers). Procédure de réintégration documentée en tête de `services/tflite/model.ts`. +> ✅ **2026-05-01** : `react-native-fast-tflite` + `react-native-nitro-modules` **réintégrés et build natif Android validé** (15m 17s, 0 erreur). Le `withCmakeFix` plugin propage maintenant les flags CMake (response files + ninja path) aux sous-projets natifs via `subprojects { plugins.withId('com.android.library') { ... } }` dans `android/build.gradle`. Voir `plugins/withCmakeFix.js`. -Le modele MobileNetV2 (val_accuracy 99.93% — voir `docs/paper.md`) est destiné -à être embarqué dans le bundle et exécuté en local via `react-native-fast-tflite` -une fois la lib réintégrée. +Le modele MobileNetV2 256×256 (4 classes — voir `docs/paper.md`) est embarqué +dans `src/assets/models/grapevine_v1.tflite` et exécuté on-device via +`react-native-fast-tflite`. Si le module natif est absent (Expo Go par ex.), +fallback automatique sur un mock JS pondéré pour ne pas casser l'UX. ### Pipeline @@ -273,8 +274,8 @@ le dev sans device natif. Détail complet : [`.claude/notes/android-build/README.md`](.claude/notes/android-build/README.md) -- ✅ **CMake/Ninja path too long** — résolu via plugin Expo config `plugins/withCmakeFix.js` (référencé dans `app.json`) qui injecte response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024` à chaque prebuild -- ✅ **`react-native-nitro-modules` headers manquants** — contourné en désinstallant `react-native-fast-tflite` (qui dépendait de Nitro). Mock JS en place. À réintégrer quand le `.tflite` sera prêt et idéalement via EAS Build pour éviter les soucis Windows. +- ✅ **CMake/Ninja path too long sur `:app`** — résolu via plugin `plugins/withCmakeFix.js` qui injecte les flags response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024` dans `android/app/build.gradle.defaultConfig.externalNativeBuild` +- ✅ **CMake/Ninja path too long sur les sous-projets natifs** (`react-native-fast-tflite`, `react-native-nitro-modules`, etc.) — résolu en étendant `withCmakeFix` pour modifier aussi `android/build.gradle` racine via `withProjectBuildGradle`. Le bloc injecté itère sur `subprojects` avec `plugins.withId('com.android.library')` qui n'agit que sur les modules Android (les gradle-plugins déjà évalués sont naturellement ignorés, évitant `Cannot run Project.afterEvaluate(Closure) when the project is already evaluated`). ### Setup dev Windows recommandé diff --git a/VinEye/plugins/withCmakeFix.js b/VinEye/plugins/withCmakeFix.js index 0414bec..7a12b9a 100644 --- a/VinEye/plugins/withCmakeFix.js +++ b/VinEye/plugins/withCmakeFix.js @@ -1,25 +1,56 @@ -const { withAppBuildGradle } = require("expo/config-plugins"); +const { + withAppBuildGradle, + withProjectBuildGradle, +} = require("expo/config-plugins"); const NINJA_PATH = "C:\\\\Users\\\\Client\\\\AppData\\\\Local\\\\Android\\\\Sdk\\\\cmake\\\\4.1.2\\\\bin\\\\ninja.exe"; +const CMAKE_ARGS = [ + `"-DCMAKE_MAKE_PROGRAM=${NINJA_PATH}"`, + `"-DCMAKE_OBJECT_PATH_MAX=1024"`, + `"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=1"`, + `"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=1"`, + `"-DCMAKE_CXX_RESPONSE_FILE_LINK_FLAG=@"`, + `"-DCMAKE_NINJA_FORCE_RESPONSE_FILE=1"`, +]; + const CMAKE_BLOCK = ` externalNativeBuild { cmake { - arguments "-DCMAKE_MAKE_PROGRAM=${NINJA_PATH}", - "-DCMAKE_OBJECT_PATH_MAX=1024", - "-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=1", - "-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=1", - "-DCMAKE_CXX_RESPONSE_FILE_LINK_FLAG=@", - "-DCMAKE_NINJA_FORCE_RESPONSE_FILE=1" + arguments ${CMAKE_ARGS.join(",\n ")} } } `; -const MARKER = "// CMAKE_FIX_INJECTED"; +const APP_MARKER = "// CMAKE_FIX_INJECTED"; +const ROOT_MARKER = "// CMAKE_FIX_SUBPROJECTS_INJECTED"; -function injectCmakeFix(buildGradle) { - if (buildGradle.includes(MARKER)) return buildGradle; +const SUBPROJECTS_BLOCK = ` +${ROOT_MARKER} +subprojects { subproject -> + def applyCmakeFix = { + try { + subproject.android { + defaultConfig { + externalNativeBuild { + cmake { + arguments ${CMAKE_ARGS.join(",\n ")} + } + } + } + } + } catch (Exception e) { + println "[CMAKE_FIX] Skipping " + subproject.name + ": " + e.message + } + } + subproject.plugins.withId('com.android.library', applyCmakeFix) + subproject.plugins.withId('com.android.application', applyCmakeFix) +} +`; + +function injectAppCmakeFix(buildGradle) { + if (buildGradle.includes(APP_MARKER)) return buildGradle; const defaultConfigRegex = /(defaultConfig\s*\{)([\s\S]*?)(\n\s*\})/m; const match = buildGradle.match(defaultConfigRegex); @@ -30,13 +61,27 @@ function injectCmakeFix(buildGradle) { } const [, openTag, body, closeTag] = match; - const newBlock = `${openTag}${body}\n ${MARKER}${CMAKE_BLOCK}${closeTag}`; + const newBlock = `${openTag}${body}\n ${APP_MARKER}${CMAKE_BLOCK}${closeTag}`; return buildGradle.replace(defaultConfigRegex, newBlock); } +function injectRootSubprojectsFix(buildGradle) { + if (buildGradle.includes(ROOT_MARKER)) return buildGradle; + return `${buildGradle.trimEnd()}\n${SUBPROJECTS_BLOCK}\n`; +} + module.exports = function withCmakeFix(config) { - return withAppBuildGradle(config, (config) => { - config.modResults.contents = injectCmakeFix(config.modResults.contents); + config = withAppBuildGradle(config, (config) => { + config.modResults.contents = injectAppCmakeFix(config.modResults.contents); return config; }); + + config = withProjectBuildGradle(config, (config) => { + config.modResults.contents = injectRootSubprojectsFix( + config.modResults.contents + ); + return config; + }); + + return config; };