v1.3.4 : 2 crashs v1.3.3 reproduits sur émulateur (données réelles) et corrigés — #47 Doses : stringResource hrt_duration 3 placeholders vs 2 args (né v1.3.1) ; #48 export logs : LocalDate.format('…-HHmm') levait UnsupportedTemporalTypeException au tap → util ExportFileNames + 3 tests ; #49 notif dose StringFormatMatches ; lint filet bloquant (app/lint.xml) ; recette émulateur §16.ter + scripts/seed-emulator.py ; 90 tests verts
This commit is contained in:
parent
e5fefc90df
commit
71659b5647
11
README.md
11
README.md
@ -16,7 +16,7 @@ sauvegarde JSON. **100 % local, aucun compte, aucun serveur.**
|
||||
> à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de
|
||||
> sang et aux consignes de ton endocrinologue.
|
||||
|
||||
- **Statut** : v1.3.3 — build Android ✅, **87 tests unitaires** ✅ (44 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅
|
||||
- **Statut** : v1.3.4 — build Android ✅, **lint vert** ✅, **90 tests unitaires** ✅ (72 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅
|
||||
- **Journal des versions** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||
- **Guide utilisateur** : [docs/GUIDE_INSTALLATION.md](docs/GUIDE_INSTALLATION.md)
|
||||
- **Doc de développement** (architecture, maths, décisions, bugs) : [docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md)
|
||||
@ -73,7 +73,8 @@ par AGP si les licences sont signées). Le wrapper télécharge Gradle 9.7.1.
|
||||
git clone <repo> && cd HormoneTrack
|
||||
echo "sdk.dir=/chemin/vers/android-sdk" > local.properties # ou ANDROID_HOME
|
||||
./gradlew assembleDebug # APK : app/build/outputs/apk/debug/app-debug.apk
|
||||
./gradlew testDebugUnitTest # 87 tests (44 sans les données locales)
|
||||
./gradlew testDebugUnitTest # 90 tests (72 sans les données locales)
|
||||
./gradlew lint # lint vert obligatoire avant release
|
||||
```
|
||||
|
||||
Installation sur un téléphone : mode développeur + Débogage USB, puis Android Studio
|
||||
@ -84,7 +85,7 @@ Pas de Play Store : l'app est sideloadée. Détails pas-à-pas : [docs/GUIDE_INS
|
||||
|
||||
Dépôts : **gitea.cloudyfy.fr** et **gitea.farewell.dev** (miroir) —
|
||||
`Siphonight/HormoneTrack` sur les deux (privé), avec
|
||||
**releases taguées** (`v1.1.0` → `v1.3.3`) et **deux APK par release** (depuis v1.2.5)
|
||||
**releases taguées** (`v1.1.0` → `v1.3.4`) et **deux APK par release** (depuis v1.2.5)
|
||||
(téléchargeables sans compiler, cf [docs/DEVELOPPEMENT.md §16.bis](docs/DEVELOPPEMENT.md)) :
|
||||
`-release.apk` (**recommandé**, optimisé R8, 2,4 Mo) et `-debug.apk` (20 Mo) :
|
||||
|
||||
@ -140,7 +141,9 @@ HormoneTrack/
|
||||
│ ├── CHANGELOG.md journal détaillé des versions
|
||||
│ └── MONTRE-GADGETBRIDGE.md montre Huawei GT 3 : options + limites
|
||||
├── scripts/
|
||||
│ └── gitea-release.py publie une release (corps = CHANGELOG + APK)
|
||||
│ ├── gitea-release.py publie le corps d'une release (CHANGELOG + APK)
|
||||
│ ├── publish-release.py publie les 2 APK d'un tag (vérification par téléchargement)
|
||||
│ └── seed-emulator.py injecte un backup JSON dans la DB d'un émulateur (cf §16.ter)
|
||||
├── local-test-data/ ← gitignoré : backups réels pour les tests
|
||||
│ de régression (JAMAIS dans le dépôt, cf §8.bis)
|
||||
├── build.gradle.kts config Gradle racine (AGP/Kotlin/KSP épinglés)
|
||||
|
||||
@ -17,8 +17,8 @@ android {
|
||||
// tag : v1.3.0→v1.3.2 contenaient tous versionCode 14 / "1.3.0"
|
||||
// (bump jamais commité) → BuildConfig.VERSION_NAME était faux dans
|
||||
// les APK publiés (Paramètres + titre du dialog « Nouveautés »).
|
||||
versionCode = 17
|
||||
versionName = "1.3.3"
|
||||
versionCode = 18
|
||||
versionName = "1.3.4"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
@ -61,6 +61,15 @@ android {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
|
||||
// Lint (v1.3.4) : les checks Compose 1.12+ (NonObservableLocale,
|
||||
// LocalContextGetResourceValueCall) sont rétrogradés en WARNING via
|
||||
// app/lint.xml (meilleure compatibilité que la DSL AGP) — cas THÉORIQUES de staleness de
|
||||
// configuration présents dans tout le code v1, jamais à l'origine des
|
||||
// crashs remontés. Les vrais problèmes de format de strings
|
||||
// (StringFormatMatches — aurait attrapé les bugs #47/#48, épinglés §14)
|
||||
// restent des ERREURS bloquantes. Re-traiter ces warnings lors d'une
|
||||
// refonte i18n (cf §20).
|
||||
}
|
||||
|
||||
// Synchronise docs/CHANGELOG.md → src/main/assets/changelog.md (dialog
|
||||
|
||||
20
app/lint.xml
Normal file
20
app/lint.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Lint (/v1.3.4). Cf commentaires dans app/build.gradle.kts et §19 de la doc de dev.
|
||||
|
||||
Rétrogradation des deux checks Compose 1.12+ : signalements de staleness
|
||||
de configuration (lecture de resources via LocalContext dans les callbacks
|
||||
et coroutines, Locale.getDefault() dans un remember{}) — présents dans tout
|
||||
le code v1, jamais à l'origine des crashs remontés. À re-traiter lors d'une
|
||||
refonte i18n (cf docs/DEVELOPPEMENT.md §20).
|
||||
|
||||
RESTENT EN ERREUR (bloquants, aucun override nécessaire) :
|
||||
- StringFormatMatches / StringFormatCount / StringFormatInvalid :
|
||||
auraient attrapé les bugs #47 (hrt_duration : 3 placeholders vs 2 args
|
||||
passés) et #48 (pattern horaire formaté sur LocalDate) — crashs réels
|
||||
v1.3.1→v1.3.3 retrouvé au téléphone, cf §14.
|
||||
-->
|
||||
<lint>
|
||||
<issue id="NonObservableLocale" severity="warning" />
|
||||
<issue id="LocalContextGetResourceValueCall" severity="warning" />
|
||||
</lint>
|
||||
@ -182,8 +182,11 @@ class ReminderReceiver : BroadcastReceiver() {
|
||||
} else {
|
||||
context.getString(R.string.reminder_title_plain)
|
||||
}
|
||||
// ⚠️ reminder_text_with_dose attend un %s : passer le Double brut
|
||||
// déclenche un warning lint StringFormatMatches (« suspicious type ») —
|
||||
// formater explicitement en dose humaine (ex. « 4,0 »).
|
||||
val text = if (dose != null && dose > 0.0) {
|
||||
context.getString(R.string.reminder_text_with_dose, dose)
|
||||
context.getString(R.string.reminder_text_with_dose, dose.toString())
|
||||
} else {
|
||||
context.getString(R.string.reminder_text)
|
||||
}
|
||||
|
||||
@ -122,8 +122,13 @@ fun DosesScreen() {
|
||||
val totalDays = HrtDuration.daysSince(first, System.currentTimeMillis())
|
||||
if (totalDays > 0) {
|
||||
val (months, days) = HrtDuration.monthsAndDays(totalDays)
|
||||
// ⚠️ hrt_duration a TROIS placeholders (%1$d mois, %2$d jours,
|
||||
// %3$d total) : passer aussi totalDays, sinon
|
||||
// MissingFormatArgumentException = crash de l'écran Doses
|
||||
// dès que totalDays > 0 (donc à chaque affichage avec des
|
||||
// données réelles). Testé au bug réel v1.3.3 (cf §14 #47).
|
||||
Text(
|
||||
if (months > 0) stringResource(R.string.hrt_duration, months, days)
|
||||
if (months > 0) stringResource(R.string.hrt_duration, months, days, totalDays)
|
||||
else stringResource(R.string.hrt_duration_days_only, days),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
|
||||
@ -52,12 +52,11 @@ import com.hormonetrack.pk.PharmacokineticEngine
|
||||
import com.hormonetrack.pk.TConfig
|
||||
import com.hormonetrack.reminder.AlarmScheduler
|
||||
import com.hormonetrack.ui.LocalAppContainer
|
||||
import com.hormonetrack.util.ExportFileNames
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -362,8 +361,7 @@ fun SettingsScreen(onBack: () -> Unit) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = {
|
||||
val date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
exportLauncher.launch("hormonetrack-backup-$date.json")
|
||||
exportLauncher.launch(ExportFileNames.backupFileName())
|
||||
}) { Text(stringResource(R.string.export_json)) }
|
||||
OutlinedButton(onClick = {
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
@ -385,8 +383,10 @@ fun SettingsScreen(onBack: () -> Unit) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = {
|
||||
val date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmm"))
|
||||
logExportLauncher.launch("hormonetrack-logs-$date.txt")
|
||||
// Nom de fichier via ExportFileNames (cf §14 #48) :
|
||||
// extrait et testé — passer un LocalDate au pattern
|
||||
// horaire crashait l'app au tap (bug v1.3.1→v1.3.3).
|
||||
logExportLauncher.launch(ExportFileNames.diagnosticLogFileName())
|
||||
}) { Text(stringResource(R.string.logs_export)) }
|
||||
TextButton(onClick = {
|
||||
AppLog.clear()
|
||||
|
||||
46
app/src/main/java/com/hormonetrack/util/ExportFileNames.kt
Normal file
46
app/src/main/java/com/hormonetrack/util/ExportFileNames.kt
Normal file
@ -0,0 +1,46 @@
|
||||
package com.hormonetrack.util
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* Noms de fichiers d'export (horodatés) — helper PUR, testable en JVM.
|
||||
*
|
||||
* Pourquoi cet objet existe (v1.3.4, cf §14 #48) : le nom du fichier des logs
|
||||
* était généré INLINE dans le handler du bouton via
|
||||
* `LocalDate.now().format(ofPattern("yyyyMMdd-HHmm"))`. Un `LocalDate` n'a
|
||||
* PAS de champ horaire : le pattern `HHmm` levait
|
||||
* `UnsupportedTemporalTypeException: Unsupported field: HourOfDay` de façon
|
||||
* SYNCHRONE dans le onClick (thread UI) → crash de l'app au tap, sans même
|
||||
* ouvrir le sélecteur de fichiers. Crash présent depuis v1.3.1 ; les « fixes »
|
||||
* v1.3.2 et v1.3.3 réparaient l'IO mais pas cette ligne.
|
||||
*
|
||||
* Règle anti-régression : les helpers d'ici centralisent le couplage
|
||||
* type ↔ pattern, chaque fonction documentant quel type porte le pattern.
|
||||
* ⚠️ Règle générale : un pattern contenant des champs horaires (HH, mm, ss)
|
||||
* exige LocalDateTime/LocalTime/Instant.withZone ; yyyyMMdd seul sur LocalDate.
|
||||
*/
|
||||
object ExportFileNames {
|
||||
|
||||
private val LOG_FMT = DateTimeFormatter.ofPattern("yyyyMMdd-HHmm")
|
||||
private val BACKUP_FMT = DateTimeFormatter.ofPattern("yyyyMMdd")
|
||||
|
||||
/**
|
||||
* Nom du fichier d'export des logs de diagnostic :
|
||||
* `hormonetrack-logs-20260906-1234.txt`.
|
||||
* Le pattern contient HHmm → il faut un type TEMPOREL COMPLET
|
||||
* (LocalDateTime). Passer un LocalDate ici = UnsupportedTemporalTypeException
|
||||
* au tap sur « Exporter » (bug #48, crash réel v1.3.1→v1.3.3).
|
||||
*/
|
||||
fun diagnosticLogFileName(now: LocalDateTime = LocalDateTime.now()): String =
|
||||
LOG_FMT.format(now).let { "hormonetrack-logs-$it.txt" }
|
||||
|
||||
/**
|
||||
* Nom du fichier de sauvegarde JSON :
|
||||
* `hormonetrack-backup-20260906.json`. Pattern SANS heure →
|
||||
* `LocalDate` est correct ici (c'est le cas qui marchait, à garder tel quel).
|
||||
*/
|
||||
fun backupFileName(today: LocalDate = LocalDate.now()): String =
|
||||
"hormonetrack-backup-${BACKUP_FMT.format(today)}.json"
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.hormonetrack.util
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Régression du bug #48 (v1.3.4) : le nom de fichier des logs était formatté
|
||||
* `LocalDate.now().format(ofPattern("yyyyMMdd-HHmm"))` → TypeError
|
||||
* UnsupportedTemporalTypeException: Unsupported field: HourOfDay
|
||||
* levée SYNCHRONEMENT dans le onClick du bouton « Exporter » des logs
|
||||
* (thread UI) → crash de l'app au tap, avant même le sélecteur de fichiers.
|
||||
* Préalablement invisible en tests JVM : la logique vivait inline dans la
|
||||
* SettingsScreen (composable). Le helper est désormais pur et testé ici.
|
||||
*/
|
||||
class ExportFileNamesTest {
|
||||
|
||||
@Test
|
||||
fun `log filename is built from a LocalDateTime (has a time field)`() {
|
||||
val name = ExportFileNames.diagnosticLogFileName(
|
||||
LocalDateTime.of(2026, 9, 6, 12, 34)
|
||||
)
|
||||
assertEquals("hormonetrack-logs-20260906-1234.txt", name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backup filename is date-only (LocalDate is valid for yyyyMMdd)`() {
|
||||
val name = ExportFileNames.backupFileName(
|
||||
LocalDate.of(2026, 9, 6)
|
||||
)
|
||||
assertEquals("hormonetrack-backup-20260906.json", name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default filenames never throw and match the expected shape`() {
|
||||
assertTrue(
|
||||
ExportFileNames.diagnosticLogFileName()
|
||||
.matches(Regex("hormonetrack-logs-\\d{8}-\\d{4}\\.txt"))
|
||||
)
|
||||
assertTrue(
|
||||
ExportFileNames.backupFileName()
|
||||
.matches(Regex("hormonetrack-backup-\\d{8}\\.json"))
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,51 @@
|
||||
# Changelog — HormoneTrack
|
||||
|
||||
Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/).
|
||||
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.3.3`, …).
|
||||
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.3.4`, …).
|
||||
|
||||
## [1.3.4] — 2026-09-06 (versionCode 18)
|
||||
|
||||
### Corrigé
|
||||
- **L'écran Doses faisait crasher l'app** (remontée) : `MissingFormatArgumentException:
|
||||
Format specifier '%3$d'` — la string `hrt_duration` a TROIS placeholders
|
||||
(`%1$d mois, %2$d jours, %3$d total`) mais l'appel transmettait 2 arguments.
|
||||
Crash dès le premier affichage de l'écran Doses avec une donnée antérieure à
|
||||
aujourd'hui. **Le bug est né en v1.3.1** (string + appel dans le même commit,
|
||||
jamais testés ensemble) et a survécu aux releases v1.3.1→v1.3.3. Fix : passer
|
||||
totalDays en 3ᵉ argument (cf §14 #47).
|
||||
- **L'export des logs de diagnostic faisait crasher l'app** (remontée répétée ;
|
||||
les « fixes » IO de v1.3.2 et v1.3.3 étaient corrects mais à côté du vrai
|
||||
problème) : le nom de fichier était construit via
|
||||
`LocalDate.now().format(ofPattern("yyyyMMdd-HHmm"))` — un `LocalDate` n'a pas
|
||||
de champ horaire → `UnsupportedTemporalTypeException: Unsupported field:
|
||||
HourOfDay`, levée SYNCHRONEMENT dans le onClick (thread UI) au tap sur
|
||||
« Exporter », avant même l'ouverture du gestionnaire de fichiers. L'export
|
||||
JSON fonctionnait car son pattern `yyyyMMdd` est valide pour un LocalDate.
|
||||
FIX : utilitaire `ExportFileNames` (pur, testé) — `diagnosticLogFileName()`
|
||||
utilise un `LocalDateTime` ; `backupFileName()` reste sur `LocalDate`.
|
||||
Cf §14 #48.
|
||||
- **Lint `StringFormatMatches`** (détecté en implantant le lint — nouveau filet
|
||||
de la v1.3.4) : la notification de rappel passait un Double à une `%s`.
|
||||
Fix : `dose.toString()` (comportement d'affichage inchangé, #49).
|
||||
|
||||
### Ajouté
|
||||
- **Lint en filet bloquant** : `./gradlew lint` est désormais vert (it aurait
|
||||
attrapé dès v1.3.1 les deux crashs ci-dessus via `StringFormatMatches`).
|
||||
Les deux checks Compose 1.12+ théoriques (`NonObservableLocale`,
|
||||
`LocalContextGetResourceValueCall`) sont rétrogradés en warning via
|
||||
`app/lint.xml` (staleness de config, refonte i18n plus tard, cf §19/§20).
|
||||
- **Recette reproductible : test manuel sur émulateur** (documentée §16.ter) :
|
||||
émulateur + image ATD ou google_apis, seed de la DB du debug avec un backup
|
||||
JSON via le nouveau script **`scripts/seed-emulator.py`** (générique, sans
|
||||
aucune donnée embarquée), installation du APK release par-dessus (données
|
||||
conservées, même signature), pilotage par `uiautomator dump` + `input tap`,
|
||||
crashs via `adb logcat -b crash`. C'est cette recette qui a reproduit et
|
||||
confirmé les deux crashs ci-dessus ainsi que LEUR correction (flux SAF
|
||||
complet : picker → SAVE → fichier écrit → message de confirmation, sur
|
||||
l'APK release données réelles).
|
||||
- **3 nouveaux tests** (`ExportFileNamesTest`, 90 au total / 72 sans les
|
||||
données locales) : épinglent le format des noms de fichiers d'export —
|
||||
auraient attrapé le bug #48 le jour même.
|
||||
|
||||
## [1.3.3] — 2026-09-06 (versionCode 17)
|
||||
|
||||
|
||||
@ -80,6 +80,7 @@ Fonctionnalités v1 :
|
||||
| 6 sept. 2026 (session v1.3.2) | 3 bugs remontés : toggle agenda (callback async fix + ContextCompat au save + Posologie requise), export logs plantait (pattern JSON réutilisé + AppLog), dialog changelog récurrent (version vue mémorisée avant affichage + titre BuildConfig documenté) ; export réel mis à jour (3 traitements : EV inactif + EEn actif + CPA oral) ; **87 tests verts**, APK v1.3.2 + releases. |
|
||||
| 6 sept. 2026 (session farewell) | Repo créé côté farewell → **push SSH (alias `farewell` : giteassh:2222) + 12 releases publiées avec APK vérifiés par téléchargement** ; piège lexicographique `v1.2.10 < v1.2.5` en comparaison de chaînes épinglé (§14 #38) ; les deux instances Gitea sont synchrones. |
|
||||
| 6 sept. 2026 (session v1.3.3) | **Audit de reprise de maintenance** (nouvelle session IA) : 3 bugs racines trouvés — permissions agenda ABSENTES du manifest (jamais déclarées, §14 #44), export logs plantait TOUJOURS (le fix v1.3.2 réimplémentait l'IO au lieu de réutiliser BackupManager, §14 #45), bump de version JAMAIS commité (tags v1.3.0–1.3.2 tous `versionCode 14 / "1.3.0"`, §14 #46). Corrigés + doc rafraîchie (DB v3, targetSdk 36, régression 3 = export v1.3.1, §19/§20 staleness) ; **téléphone de test documenté : Google Pixel 9 /e/OS** (AOSP ; SAF DocumentsUI standard). |
|
||||
| 6 sept. 2026 (session v1.3.4) | **Deux crashs v1.3.3 remontés (Doses + export logs)** → reproduits sur ÉMULATEUR avec les vraies données (recette §16.ter, désormais standard) : (a) Doses = string `hrt_duration` 3 placeholders vs 2 args (**né en v1.3.1**, §14 #47) ; (b) export logs = `LocalDate.format("…-HHmm")` levant `UnsupportedTemporalTypeException` au TAP (§14 #48) — les fixes IO v1.3.2/v1.3.3 étaient à côté du vrai problème. Lint mis en filet bloquant (aurait attrapé les deux) + #49 notif ; helper `ExportFileNames` + 3 tests (90 verts) ; fixes VÉRIFIÉS sur émulateur avec l'APK release + données réelles (flux SAF complet : SAVE → fichier + message). Publication v1.3.4. |
|
||||
| 6 sept. 2026 (session v1.3.0) | Dialog « Nouveautés » après mise à jour (CHANGELOG embarqué en asset, tâche `copyChangelog`, version vue en DataStore) ; **événements d'agenda récurrents** (calendrier local HormoneTrack, RRULE posologie, permission runtime, Room v3 `calendarEventId`) ; Paramètres : version + lien releases ; **78 tests verts**, APK v1.3.0 + releases. |
|
||||
| 5 sept. 2026 (session v1.2.10) | Sens des boutons de zoom inversé (+ = zoom avant, convention carte — retour utilisateur) ; 67 tests verts, APK v1.2.10 + releases. |
|
||||
| 5 sept. 2026 (session v1.2.9) | **Zoom du graphique** (pinch + boutons, 6 h → 300 j, focal stable, échantillonnage adaptatif `stepForRange`, labels X 1 h/3 h) ; **README : disclaimer IA-assisté** en en-tête ; 3 tests ; **67 tests verts**, APK v1.2.9 + releases. |
|
||||
@ -136,6 +137,15 @@ Notes importantes (v1.2.0) :
|
||||
par `adb install` ou APK direct. Les retours utilisateur (bugs, exports JSON) viennent
|
||||
de ce téléphone. (Historique : la montre est une Huawei Watch GT 3 via Gadgetbridge,
|
||||
cf §17 — ce n'est PAS le téléphone.)
|
||||
- **Émulateur local (installé en v1.3.4)** : SDK brew =
|
||||
`/opt/homebrew/share/android-commandlinetools`. Packages installés :
|
||||
`emulator` 37.1.11 + `system-images;android-31;aosp_atd;arm64-v8a` (ATD : léger,
|
||||
boot rapide, MAIS DocumentsUI STUBBÉ (`fakesystemapp`) → SAF non pilotable) et
|
||||
`system-images;android-36;google_apis;arm64-v8a` (image complète — REQUISE pour
|
||||
tester les flux SAF). AVD `hrt` (ATD 31) et `hrt36` (API 36) créés via
|
||||
avdmanager (erreur béninie « devices.xml » au create : l'AVD est créé quand même).
|
||||
Boot headless : `emulator -avd hrt36 -no-window -no-audio -no-boot-anim -no-snapshot
|
||||
-gpu swiftshader_indirect`. Recette de test → **§16.ter**.
|
||||
- **SDK Android** : installé via `brew install --cask android-commandlinetools`
|
||||
→ `/opt/homebrew/share/android-commandlinetools` (524 MB)
|
||||
- licences acceptées : `yes | sdkmanager --licenses`
|
||||
@ -361,8 +371,9 @@ Type de retour : `LevelPoint(timestamp, e2, t)`.
|
||||
|
||||
## 8. Tests unitaires
|
||||
|
||||
**87 tests JVM, tous verts** (`./gradlew testDebugUnitTest`) — 44 sans les
|
||||
données de test locales (cf §8.bis). Dépendance : JUnit 4.13.2.
|
||||
**87 tests JVM, tous verts** → **90 depuis v1.3.4** (`./gradlew testDebugUnitTest`) —
|
||||
**72 sans les données de test locales** (cf §8.bis : les 3 classes de régression,
|
||||
6 tests chacune, sont skippées via `Assume`). Dépendance : JUnit 4.13.2.
|
||||
Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'exécution =
|
||||
`app/` → l'asset est lu via `src/main/assets/pk_profiles.json` (fallback `app/src/…`).
|
||||
|
||||
@ -837,6 +848,37 @@ aucun contexte, dans l'esprit de cette doc) :**
|
||||
AVANT le tag ; le bump fait partie du commit de release (jamais un
|
||||
état local non commité au moment du build).
|
||||
|
||||
**Session v1.3.4 (deux crashs remontés → reproduits sur émulateur) :**
|
||||
|
||||
47. **L'écran Doses crashait l'app** (`MissingFormatArgumentException:
|
||||
Format specifier '%3$d'`) : la string `hrt_duration` a TROIS placeholders
|
||||
(`%1$d mois, %2$d jours, %3$d total`) mais
|
||||
`stringResource(R.string.hrt_duration, months, days)` ne passait que DEUX
|
||||
arguments. Crash dès que le fragment `totalDays > 0` s'affiche (donc pour
|
||||
TOUTE donnée antérieure à aujourd'hui) — né en v1.3.1 (string + appel dans
|
||||
le même commit fa5d2df, jamais testés ensemble) et passé à travers
|
||||
v1.3.1→v1.3.3.
|
||||
48. **Le bouton « Exporter » des logs crashait l'app** — LE bug récurrent des
|
||||
v1.3.1→v1.3.3 : le nom de fichier était construit INLINE via
|
||||
`LocalDate.now().format(ofPattern("yyyyMMdd-HHmm"))`. Un `LocalDate` n'a
|
||||
PAS de champ horaire → `UnsupportedTemporalTypeException:
|
||||
Unsupported field: HourOfDay` levée dans le onClick (thread principal,
|
||||
synchrone, touch dispatch) → crash au tap, avant même le sélecteur SAF
|
||||
(stack obtenue sur émulateur : LTSI + frames R8 obfusquées). L'export
|
||||
JSON marchait car `yyyyMMdd` est un pattern valide pour LocalDate.
|
||||
Zones d'ombre emboîtées : v1.3.2 et v1.3.3 « corrigent » l'IO (cf #41/#45)
|
||||
sans voir cette ligne. FIX : `util/ExportFileNames.kt` (pur, testé,
|
||||
documenté — couplage type↔pattern centralisé) ; `FileNamesTest` épinglant
|
||||
les deux helpers (aurait attrapé le bug le jour même). Leçon : la
|
||||
génération de nom de fichier ne doit JAMAIS vivre inline dans un onClick.
|
||||
49. **StringFormatMatches** (capture du lint, v1.3.4) : la notification de
|
||||
rappel passait un Double à `%s`. Fix `dose.toString()` (l'affichage ne
|
||||
change pas). L'intérêt des règles lint est confirmé PAR CETTE SESSION :
|
||||
`./gradlew lint` aurait signalé #47 dès v1.3.1 → le lint fait désormais
|
||||
partie de la vérification avant release (les checks Compose 1.12+
|
||||
théoriques `NonObservableLocale`/`LocalContextGetResourceValueCall` sont
|
||||
tombés en warning via `app/lint.xml`, cf §19).
|
||||
|
||||
**Leçons** : (a) ne jamais croire un build « probablement bon » sans l'avoir lancé ;
|
||||
(b) les tests sémantiques attrapent ce que la compilation ne voit pas ; (c) se méfier des
|
||||
constantes stdlib « de mémoire » (`ln2`), des mélanges Float/Double, et des APIs M3
|
||||
@ -852,10 +894,15 @@ perd les garde-fous acquis à l'usage (cf #45) ; (j) vérifier les POSTULATS dan
|
||||
code réel, pas dans la doc (« le manifest contient WRITE_CALENDAR », cf #44) ;
|
||||
(k) un bump de version non commité = métadonnées fausses dans les APK publiés
|
||||
(cf #46) — le bump fait partie du commit de release ; (l) le scan de
|
||||
confidentialité s'ADAPTE aux nouveaux exports (`local-test-data/`) : se régénère
|
||||
donc à CHECK chaque push — un historique « propre hier » peut devenir hit dès
|
||||
qu'un nouvel export introduit des motifs qui collent ; juger les hits avec
|
||||
l'utilisatrice (cf dette documentée §8.bis).
|
||||
confidentialité s'ADAPTE automatiquement aux nouveaux exports
|
||||
(`local-test-data/`) : un historique « propre hier » peut devenir hit dès
|
||||
qu'un nouvel export introduit des motifs qui collent — re-scanner PUIS juger
|
||||
avec l'utilisatrice (dette déjà jugée, cf §8.bis) ; (m) **un crash sans stack
|
||||
trace = d'abord le reproduire** (émulateur + données réelles, §16.ter) — les
|
||||
deux « fixes » aveugles de v1.3.2/v1.3.3 (#41/#45) ont laissé passer un bug
|
||||
trivial (#48) seulement visible sur l'émulateur ; (n) `./gradlew lint` fait
|
||||
désormais partie de la vérification avant release : `StringFormatMatches`
|
||||
aurait signalé #47 dès v1.3.1.
|
||||
|
||||
## 15. Comment régénérer l'asset pk_profiles.json
|
||||
|
||||
@ -980,8 +1027,11 @@ vérifie pas en tests JVM).
|
||||
|
||||
1. **Bumper la version** dans `app/build.gradle.kts` : `versionCode = N+1`,
|
||||
`versionName = "X.Y.Z+1"` (SemVer : fix = Z, feature = Y).
|
||||
2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 87 au total,
|
||||
44 si `local-test-data/` est absent (les régressions réelles sont skippées).
|
||||
2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 90 au total
|
||||
(v1.3.4), 72 si `local-test-data/` est absent (les 3 classes de régression
|
||||
réelles sont skippées via `Assume`, 6 tests chacune) ; **lint vert
|
||||
obligatoire** : `./gradlew lint` (v1.3.4 — `StringFormatMatches` aurait
|
||||
attrapé les crashs #47/#48 dès v1.3.1).
|
||||
3. **Docs** : section `## [X.Y.Z]` en tête de `docs/CHANGELOG.md` (le corps des
|
||||
releases Gitea en sera extrait automatiquement par le script), + §14 si bug
|
||||
corrigé, + §2 (historique) si notable.
|
||||
@ -1012,10 +1062,83 @@ Notes :
|
||||
de santé dans le dépôt, cf §8.bis).
|
||||
|
||||
- Téléphone : mode développeur + Débogage USB (détails : GUIDE_INSTALLATION.md)
|
||||
- À ma charge (assistant) : build + tests JVM ✓ ; émulateur possible sur demande ;
|
||||
- À ma charge (assistant) : build + tests JVM ✓ ; **émulateur installé et
|
||||
opérationnel depuis v1.3.4 (recette §16.ter)** ;
|
||||
**les tests humains sur vrai téléphone restent la référence**
|
||||
(notifs → montre, UX de saisie, pickers, panoramique du chart)
|
||||
|
||||
### 16.ter Recette : test manuel sur émulateur (reproductible — v1.3.4)
|
||||
|
||||
But : reproduire/valider un crash ou une UI sur l'app RÉELLE (sans téléphone
|
||||
branché) — c'est cette recette qui a diagnostiqué les crashs #47/#48 en une
|
||||
session. ⚠️ AUCUNE donnée personnelle n'est embarquée : le backup utilisé vit
|
||||
sous `local-test-data/` (gitignoré, §8.bis) et est passé au script en argument.
|
||||
|
||||
**0. Installation (one-shot, cf §4)** : `sdkmanager "emulator"
|
||||
"system-images;android-31;aosp_atd;arm64-v8a"` (léger, pour le LOGIQUE sans
|
||||
SAF) et/ou `"system-images;android-36;google_apis;arm64-v8a"` (image COMPLÈTE,
|
||||
nécessaire pour piloter les sélecteurs de fichiers). AVD :
|
||||
`avdmanager create avd -n hrt36 -k "system-images;…" -d pixel_6` (l'erreur
|
||||
béninie « devices.xml » ne bloque pas la création). Boot headless :
|
||||
`emulator -avd hrt36 -no-window -no-audio -no-boot-anim -no-snapshot
|
||||
-gpu swiftshader_indirect &` puis `adb wait-for-device` + attente
|
||||
`getprop sys.boot_completed`.
|
||||
|
||||
**1. Seed des « données réelles »** — la DB est modifiable uniquement sur un
|
||||
APK DEBUGGABLE (`run-as`) :
|
||||
|
||||
```bash
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n com.hormonetrack/.MainActivity # 1er lancement → crée la DB
|
||||
adb shell am force-stop com.hormonetrack # flush de la DB
|
||||
adb shell run-as com.hormonetrack cat databases/hormonetrack.db > /tmp/hrt.db
|
||||
python3 scripts/seed-emulator.py /tmp/hrt.db local-test-data/backup-v1.3.1.json
|
||||
adb push /tmp/hrt.db /data/local/tmp/hrt.db
|
||||
adb shell run-as com.hormonetrack cp /data/local/tmp/hrt.db databases/hormonetrack.db
|
||||
adb shell run-as com.hormonetrack rm -f databases/hormonetrack.db-wal \
|
||||
databases/hormonetrack.db-shm # WAL périmé sinon ! (checkpoint implicite fait par le seed)
|
||||
```
|
||||
|
||||
Règles du seed (le script les impose) : colonnes = noms de propriétés Kotlin
|
||||
(Room n'applique pas de snake_case), enums stockés en String — on insère
|
||||
SEULEMENT des lignes, on ne touche NI au schéma NI à `room_master_table`
|
||||
(Room valide le schéma à l'ouverture, pas les lignes).
|
||||
|
||||
**2. Passer en RELEASE (c'est LE build à tester)** — `install -r` PAR-DESSUS
|
||||
le debug CONSERVE la DB (les APK release/signés debug sont la même clé) :
|
||||
|
||||
```bash
|
||||
adb install -r app/build/outputs/apk/release/app-release.apk
|
||||
adb logcat -c && adb shell am start -n com.hormonetrack/.MainActivity
|
||||
```
|
||||
|
||||
**3. Piloter l'UI headless** : à l'aide de `uiautomator dump` + `input tap` —
|
||||
l'UI se pilote SANS écran :
|
||||
|
||||
```bash
|
||||
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
|
||||
# → parser text=…/content-desc=… + bounds=[x1,y1][x2,y2] → centre (cx,cy)
|
||||
adb shell input tap <cx> <cy> # naviguer (5 tabs ≈ y=2280 à 1080×2400)
|
||||
adb shell input keyevent KEYCODE_BACK
|
||||
```
|
||||
|
||||
**4. Attraper le crash** : le process meurt et repasse au launcher — c'est le
|
||||
signal ; stack : `adb logcat -b crash -d` (grep FATAL). **0 crash attendu :
|
||||
`grep -c FATAL` reste à 0** après chaque interaction.
|
||||
|
||||
**5. Flux SAF (export JSON/logs)** : sur android-31 ATD le
|
||||
DocumentsUI est STUBBÉ (`fakesystemapp`) → INUTILISABLE ; utiliser l'image
|
||||
android-36 google_apis. Le picker se pilote pareil (bouton `SAVE` en bas à
|
||||
droite) ; vérification : fichier présent dans `/sdcard/Download/` + le message
|
||||
de confirmation (v1.3.3 : « Logs de diagnostic exportés ») visible via un
|
||||
nouveau dump UI.
|
||||
|
||||
**6. Vérifications post-fix** : Doses (en-tête « temps sous THS » + liste),
|
||||
Settings → Export logs (SAF + SAVE + message), export/import JSON, changelog
|
||||
dialog au premier lancement (sinon re-seed un `changelog_seen_version`).
|
||||
⚠️ Les coordonnées UI (x/y) évoluent avec l'écran/l'app : TOUJOURS re-dumper
|
||||
`uiautomator` avant de taper — ne jamais figer des coordonnées hors dump.
|
||||
|
||||
## 17. Montre : Gadgetbridge & options
|
||||
|
||||
Doc dédiée : [MONTRE-GADGETBRIDGE.md](MONTRE-GADGETBRIDGE.md). Synthèse :
|
||||
@ -1062,6 +1185,11 @@ Volontaires (v1) :
|
||||
explicites, mais R8 ne se vérifie pas en tests JVM → **smoke-test sur
|
||||
téléphone** (export/import backup, graphiques) avant chaque publication ;
|
||||
signé clé debug → upgradable sans perte, mais pas une signature « officielle »
|
||||
- **Lint (v1.3.4)** : `./gradlew lint` est vert — `StringFormatMatches` et
|
||||
famille restent des ERREURS bloquantes ; les checks Compose 1.12+
|
||||
théoriques (`NonObservableLocale`, `LocalContextGetResourceValueCall`,
|
||||
staleness de configuration) sont rétrogradés en WARNING via `app/lint.xml`
|
||||
→ à re-traiter lors d'une refonte i18n (§20)
|
||||
|
||||
## 20. Idées d'évolution
|
||||
|
||||
@ -1135,11 +1263,25 @@ Sur le téléphone de test (à compléter par l'utilisatrice) :
|
||||
accord, l'événement récurrent apparaît dans l'app d'agenda du Pixel 9 ;
|
||||
le smoke-test R8 de la checklist §16 doit aussi couvrir export/import
|
||||
de backup + graphiques (réflexion Gson sous R8)
|
||||
- [ ] **v1.3.4** : **écran Doses** s'ouvre et affiche l'en-tête « temps sous
|
||||
THS » avec les données réelles (bug #47 : crash dès que des doses
|
||||
antérieures à aujourd'hui existaient — né v1.3.1)
|
||||
- [ ] **v1.3.4** : **export des logs** ouvre VRAIMENT le gestionnaire de
|
||||
fichiers et le message « Logs de diagnostic exportés » apparaît après
|
||||
la sauvegarde (bug #48 : crash au tap depuis v1.3.1) — vérifié sur
|
||||
émulateur API 36 (§16.ter), à confirmer au téléphone
|
||||
- [ ] **v1.3.4** : rappel de notification affiche la dose proprement
|
||||
(lint #49, ex format « 4.0 »)
|
||||
- [ ] **v1.3.4** : `./gradlew lint` vert — intégré à la vérification de
|
||||
release (step 2 de la checklist §16 désormais : tests + lint)
|
||||
|
||||
---
|
||||
*Doc mise à jour le 6 sept. 2026 (v1.3.3) — build OK, 87/87 tests verts (44 sans les
|
||||
données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée
|
||||
*Doc mise à jour le 6 sept. 2026 (v1.3.4) — build OK, lint vert, 90/90 tests verts (72 sans
|
||||
les données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée
|
||||
de santé dans le dépôt ni l'historique. v1.3.3 = session de reprise de maintenance :
|
||||
3 bugs racines corrigés (permissions agenda, export logs, bump de version) ; doc
|
||||
raccourcie (DB v3, targetSdk 36, régression 3 = export v1.3.1, numéros #38/#43 rétablis,
|
||||
staleness §19/§20 nettoyé).*
|
||||
3 bugs racines corrigés (permissions agenda, export logs, bump de version). v1.3.4 :
|
||||
2 crashs remontés reproduits sur émulateur avec les données réelles (recette §16.ter,
|
||||
scripts/seed-emulator.py versionné) — bugs #47 (stringResource arity, né v1.3.1) et #48
|
||||
(LocalDate + pattern horaire au tap), lint mis en filet bloquant (#49, app/lint.xml),
|
||||
ExportFileNames + 3 tests. Dette connue : fragments de labs dans l'historique git
|
||||
(v1.1.0→v1.2.3) — cf §8.bis.*
|
||||
|
||||
@ -108,9 +108,12 @@ Les notifications de l'app remontent automatiquement sur la montre via **Huawei
|
||||
- Choisis bien l'unité : elle est convertie automatiquement pour l'affichage et la
|
||||
calibration (E2 en pg/mL ; T en ng/mL, ng/dL, ng/L, nmol/L)
|
||||
4. **Calibration** (dans l'édition d'un traitement E2) → « Calibrer avec les analyses »
|
||||
- Paramètres → **Logs de diagnostic** : exporte le journal de l'app (utile
|
||||
en cas de bug à signaler)
|
||||
→ calcule le facteur d'échelle = médiane(lab ÷ prédiction), comme le « Scale factor » du `.ods`
|
||||
- Paramètres → **Logs de diagnostic** : « Exporter » ouvre le gestionnaire de
|
||||
fichiers → choisis où enregistrer le .txt → un message de confirmation
|
||||
(ou d'erreur) s'affiche en haut de Paramètres — utile pour joindre les
|
||||
logs à un rapport de bug (≥ v1.3.4 ; les versions 1.3.1–1.3.3
|
||||
faisaient planter ce bouton)
|
||||
→ calcule le facteur d'échelle = médiane(lab ÷ prédiction), comme le « Scale factor » du `.ods`
|
||||
5. **Paramètres** :
|
||||
- **Langue** : Système / Français / English
|
||||
- **Estimation T** : modèle `T = plancher + (base − plancher) ÷ (1 + k·E2)` (ng/mL),
|
||||
@ -164,6 +167,7 @@ Les profils viennent de ta feuille `Estrogen.ods` (table « Models ») :
|
||||
| Notif absente sur la montre | Huawei Santé → Notifications → autorise l'app ; redémarre la montre |
|
||||
| Rappels en retard | Paramètres → « Accorder les alarmes exactes » + désactive l'optimisation de batterie pour l'app |
|
||||
| Import JSON échoué | Le fichier doit venir d'un export de l'app même version (IDs conservés) |
|
||||
| L'export des logs ou JSON a planté (versions ≤ 1.3.3) | Mettre à jour vers la v1.3.4+ (fixons confirmés sur émulateur) — le message de confirmation doit apparaître en haut de Paramètres |
|
||||
|
||||
## 10. Données & vie privée
|
||||
|
||||
|
||||
91
scripts/seed-emulator.py
Executable file
91
scripts/seed-emulator.py
Executable file
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Seed la DB Room d'un émulateur avec un backup JSON exporté par l'app.
|
||||
|
||||
OUTIL DE TEST MANUEL (post-v1.3.4) — cf recette complète dans
|
||||
docs/DEVELOPPEMENT.md §16.ter « Test manuel sur émulateur ».
|
||||
|
||||
Constitue l'étape « données réelles dans l'émulateur » : lance l'APK debug une
|
||||
fois (crée la DB via Room), récupère la DB via run-as, injecte les lignes du
|
||||
backup passé en argument (SANS toucher au schéma ni à room_master_table — Room
|
||||
valide le schéma, pas les lignes), puis la repousse.
|
||||
|
||||
Usage (le téléphone = émulateur démarré, avec com.hormonetrack - APK DEBUG -
|
||||
installé et lancé au moins une fois) :
|
||||
|
||||
adb shell am force-stop com.hormonetrack
|
||||
adb shell run-as com.hormonetrack cat databases/hormonetrack.db > /tmp/hrt.db
|
||||
python3 scripts/seed-emulator.py /tmp/hrt.db <backup.json> # modifie la BD locale
|
||||
adb push /tmp/hrt.db /data/local/tmp/hrt.db
|
||||
adb shell run-as com.hormonetrack cp /data/local/tmp/hrt.db databases/hormonetrack.db
|
||||
adb shell run-as com.hormonetrack rm -f databases/hormonetrack.db-wal databases/hormonetrack.db-shm
|
||||
|
||||
⚠️ PRIVATITÉ : le fichier backup contient des données de santé personnelles et
|
||||
vit sous `local-test-data/` (gitignoré, cf §8.bis). Ce script ne contains
|
||||
AUCUNE donnée : tout passe par arguments/lignes insérées à l'exécution. Ne
|
||||
JAMAIS copier un backup dans le dépôt ni en nommer dans les commentaires.
|
||||
"""
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("usage: seed-emulator.py <db-sqlite> <backup.json>")
|
||||
db_path, backup_path = sys.argv[1], sys.argv[2]
|
||||
|
||||
data = json.load(open(backup_path, encoding="utf-8"))
|
||||
|
||||
con = sqlite3.connect(db_path)
|
||||
cur = con.cursor()
|
||||
|
||||
# ⚠️ Les colonnes portent les noms des propriétés Kotlin SANS renommage
|
||||
# (Room ne convertit pas en snake_case) ; les enums sont stockés en String.
|
||||
for tr in data["treatments"]:
|
||||
cur.execute(
|
||||
"INSERT OR REPLACE INTO treatments (id, name, type, route, doseAmount,"
|
||||
" doseUnit, isActive, notes, esterType, pkModel, absorptionHours,"
|
||||
" eliminationHalfLifeHours, bioavailabilityFraction, scaleFactor,"
|
||||
" forecastIntervalDays, reminderHour, reminderMinute, reminderEnabled,"
|
||||
" calendarEventId, createdAt)"
|
||||
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(
|
||||
tr["id"], tr["name"], tr["type"], tr["route"], tr["doseAmount"],
|
||||
tr["doseUnit"], tr["isActive"], tr.get("notes"),
|
||||
tr.get("esterType", "NONE"), tr.get("pkModel", "ESE"),
|
||||
tr.get("absorptionHours", 4.0),
|
||||
tr.get("eliminationHalfLifeHours", 24.0),
|
||||
tr.get("bioavailabilityFraction", 1.0), tr.get("scaleFactor", 1.0),
|
||||
tr.get("forecastIntervalDays"), tr.get("reminderHour"),
|
||||
tr.get("reminderMinute"), tr.get("reminderEnabled", False),
|
||||
tr.get("calendarEventId"), tr["createdAt"],
|
||||
),
|
||||
)
|
||||
|
||||
for d in data["doseLogs"]:
|
||||
cur.execute(
|
||||
"INSERT OR REPLACE INTO dose_logs (id, treatmentId, timestamp,"
|
||||
" doseAmount, notes, esterType) VALUES (?,?,?,?,?,?)",
|
||||
(d["id"], d["treatmentId"], d["timestamp"], d["doseAmount"],
|
||||
d.get("notes"), d.get("esterType")),
|
||||
)
|
||||
|
||||
for l in data["labResults"]:
|
||||
cur.execute(
|
||||
"INSERT OR REPLACE INTO lab_results (id, marker, value, unit,"
|
||||
" timestamp, notes) VALUES (?,?,?,?,?,?)",
|
||||
(l["id"], l["marker"], l["value"], l["unit"], l["timestamp"],
|
||||
l.get("notes")),
|
||||
)
|
||||
|
||||
con.commit()
|
||||
cur.execute(
|
||||
"SELECT (SELECT COUNT(*) FROM treatments), (SELECT COUNT(*) FROM dose_logs),"
|
||||
" (SELECT COUNT(*) FROM lab_results)")
|
||||
t, d, l = cur.fetchone()
|
||||
print(f"seed OK : {t} traitements, {d} doses, {l} labs")
|
||||
con.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user