Aller au contenu principal
Version: 0.3.0

Historique et événements

Une ruche est déplacée entre des ruchers, une reine règne puis est remplacée, du miel est prélevé un jour donné. Pour que cet historique reste correct après que le monde a changé, chaque écriture d'historique fige le contexte qui était vrai à ce moment-là. Le code se trouve dans app/src/lib/local/history.ts.

Les événements figent leur contexte​

La table event est en ajout seul. Chaque ligne stocke, sous forme de colonnes ordinaires, les apiary_id, hive_id et queen_id qui s'appliquaient à la date de l'événement. Déplacez la ruche le mois prochain et l'inspection de la semaine dernière appartient toujours à l'ancien rucher ; remérez et une ancienne récolte reste attribuée à la reine qui l'a produite.

Les mêmes lignes servent de table de faits pour les statistiques : amount_kg est copié sur les événements de récolte, de sorte que le miel par rucher, par reine ou par année est un seul GROUP BY sur event, sans jointure.

Historiques par intervalle​

Deux tables contiennent des intervalles semi-ouverts [start, end) :

TableIntervalleSignification
queen[introduced_at, replaced_at)La reine est à la tête de la colonie de son introduction jusqu'à son remplacement. replaced_at est nul tant qu'elle règne ; active est aussi positionné.
placement[start_at, end_at)La ruche se trouve dans apiary_id de start_at jusqu'à son déplacement. end_at est nul pour le placement courant.

Les intervalles semi-ouverts se juxtaposent sans chevauchement : un jour de changement, exactement une ligne correspond.

resolveContext​

Les entrées sont souvent antidatées (la visite de samedi saisie le lundi), le contexte est donc résolu pour la date propre de l'entrée, pas pour maintenant :

resolveContext(hiveId: string, date: string) -> { apiaryId, queenId }

Elle exécute deux requêtes sur la base de données locale et se replie sur les valeurs courantes lorsqu'aucun intervalle ne couvre la date :

-- Where the hive lived on the date (falls back to hive.apiary_id).
SELECT apiary_id FROM placement
WHERE hive_id = ? AND deleted = 0 AND start_at <= ?
AND (end_at IS NULL OR end_at > ?)
ORDER BY start_at DESC LIMIT 1;

-- Who reigned on the date (falls back to the queen with active = 1).
SELECT id FROM queen
WHERE hive_id = ? AND deleted = 0 AND introduced_at <= ?
AND (replaced_at IS NULL OR replaced_at > ?)
ORDER BY introduced_at DESC LIMIT 1;

Fonctions qui écrivent l'historique​

FonctionÉcrit
createHiveligne hive, un placement ouvert, événement CREATED
setQueenferme la reine active (active = 0, replaced_at), insère la nouvelle, événements QUEEN_REPLACED et QUEEN_INTRODUCED
moveHiveferme le placement ouvert, en ouvre un nouveau, met à jour hive.apiary_id, événement MOVED avec detail = {from, to}
recordHarvestligne harvest avec apiary_id / queen_id figés, événement HARVEST avec amount_kg et ref_id
recordTreatmentligne treatment avec contexte figé, événement TREATMENT
recordInspectionligne inspection, événement INSPECTION

recordHarvest, recordTreatment et recordInspection appellent d'abord resolveContext. Toutes les écritures passent par patch() dans app/src/lib/local/repo.ts, elles atterrissent donc dans la table locale et dans l'outbox de synchronisation comme tout autre changement. Les lignes de détail (harvest, treatment, inspection) sont des lignes synchronisées normales ; seule event est traitée en ajout seul, par convention. Rien dans l'application ne modifie ni ne supprime un événement.

Les numéros de type d'événement sont listés dans le modèle de données.

Lire l'historique​

historyForHive(hiveId) // SELECT * FROM event WHERE deleted = 0 AND hive_id = ? ORDER BY date DESC
historyForApiary(apiaryId) // ... WHERE apiary_id = ?
historyForQueen(queenId) // ... WHERE queen_id = ?

Statistiques :

-- honeyByApiary
SELECT apiary_id AS key, SUM(amount_kg) AS kg
FROM event WHERE type = 7 AND deleted = 0
GROUP BY apiary_id ORDER BY kg DESC;

-- honeyByQueen
SELECT queen_id AS key, SUM(amount_kg) AS kg
FROM event WHERE type = 7 AND deleted = 0
GROUP BY queen_id ORDER BY kg DESC;

-- honeyByYear
SELECT substr(date, 1, 4) AS year, SUM(amount_kg) AS kg
FROM event WHERE type = 7 AND deleted = 0
GROUP BY year ORDER BY year;

type = 7 est HARVEST. Comme les dimensions sont figées sur la ligne, aucune jointure vers l'état courant n'est nécessaire.

Synchronisation​

Les lignes event portent scope_id (l'id du rucher) comme colonne et se synchronisent comme toute autre table, avec le « dernier écrivain gagne » par champ. Comme chaque événement a un UUID neuf et n'est jamais modifié, deux appareils qui ajoutent des événements hors ligne ne touchent jamais la même ligne et les deux ensembles sont conservés. Voir le protocole de synchronisation.