plan
40fcb39 parent: c7fd8a1 added
specs/004-categorisation-dossiers/contracts/regine-core-api.md +39 -0 | new file mode 100644 | ||
| @@ -0,0 +1,39 @@ | ||
| 1 | +# Contrat d'API interne : `regine_core.dossier` / `regine_core.config.categories` | |
| 2 | + | |
| 3 | +Cette fonctionnalité n'introduit aucune nouvelle commande CLI de premier niveau : elle expose une API Python interne à `regine-core`, consommée par la future façade CLI du module import (`specs/001-import-photos`) et, plus tard, par `regine-gui`/`regine-agent` — cohérent avec le Principe VI (bibliothèque centrale, façades minces, objets structurés plutôt que texte à parser). | |
| 4 | + | |
| 5 | +## `regine_core.dossier.root.determine_root(date, categorie=None) -> RootLocation` | |
| 6 | + | |
| 7 | +Résout le répertoire racine (année ou catégorie) pour un dossier ou dossier parent en cours de création (FR-001, FR-002, FR-003). | |
| 8 | + | |
| 9 | +**Entrée** : | |
| 10 | +- `date` : date EXIF (ou plage) du groupe d'import concerné. | |
| 11 | +- `categorie` : nom de catégorie choisi par l'utilisateur, ou `None` pour le placement par défaut par année. | |
| 12 | + | |
| 13 | +**Sortie** : objet `RootLocation` structuré (`type`, `nom`, `chemin_archive`, `chemin_local` — cf. `data-model.md`). Aucune exception levée pour une catégorie inconnue : `determine_root` ne fait que construire le chemin, la création effective du répertoire relève du module `archive`/`import`, pas de cette fonction. | |
| 14 | + | |
| 15 | +**Contrat de comportement** : | |
| 16 | +- Appelée une seule fois par dossier/dossier parent créé (FR-006) — jamais réappelée pour un sous-dossier d'un parent existant, qui réutilise directement le `RootLocation` déjà résolu du parent. | |
| 17 | +- `chemin_archive` et `chemin_local` partagent toujours le même `nom` et le même `type` (FR-002/FR-003, miroir garanti par construction plutôt que par synchronisation a posteriori). | |
| 18 | + | |
| 19 | +## `regine_core.config.categories.list_known_categories() -> list[str]` | |
| 20 | + | |
| 21 | +Retourne la liste des noms de catégories déjà utilisées au moins une fois (FR-004), triée par ordre d'utilisation la plus récente. Liste vide si aucune catégorie n'a jamais été utilisée (pas une erreur). | |
| 22 | + | |
| 23 | +## `regine_core.config.categories.suggest_categories(saisie: str) -> list[str]` | |
| 24 | + | |
| 25 | +Retourne, parmi les catégories déjà connues, celles dont le nom est proche de `saisie` (cf. `research.md` § 2), pour aider à éviter un doublon orthographique (Edge Case "Mariage" vs "mariage"). Liste vide si aucune correspondance proche — la saisie de l'utilisateur reste alors utilisable telle quelle comme nouvelle catégorie (FR-004). | |
| 26 | + | |
| 27 | +## `regine_core.config.categories.register_category_usage(nom: str) -> None` | |
| 28 | + | |
| 29 | +Enregistre `nom` dans la table `categories` s'il n'y figure pas déjà (première utilisation). Appelée automatiquement par le flux de création de dossier au moment où un `RootLocation` de type `categorie` est effectivement archivé — jamais à la simple saisie, pour ne pas polluer la liste de suggestions avec des catégories tapées puis abandonnées avant confirmation (cf. Principe II, confirmation explicite avant toute écriture). | |
| 30 | + | |
| 31 | +## Point d'intégration côté façade (futur, hors périmètre de ce plan) | |
| 32 | + | |
| 33 | +Le futur code CLI du module import (`specs/001-import-photos` FR-007) DEVRA, à l'étape de destination de chaque groupe créant un nouveau dossier simple ou un nouveau dossier parent : | |
| 34 | +1. Demander à l'utilisateur s'il choisit une catégorie ou le placement par défaut, en proposant `list_known_categories()`. | |
| 35 | +2. Si l'utilisateur saisit un nom non listé, appeler `suggest_categories(saisie)` et lui présenter les correspondances proches avant de confirmer la création d'une catégorie réellement nouvelle. | |
| 36 | +3. Appeler `determine_root(date, categorie)` pour obtenir le `RootLocation` à afficher dans le résumé de confirmation (`specs/001-import-photos` FR-018) et à utiliser pour la construction du chemin final. | |
| 37 | +4. Appeler `register_category_usage(nom)` uniquement après confirmation explicite de l'archivage, jamais avant. | |
| 38 | + | |
| 39 | +Ce point d'intégration n'est pas implémenté par ce plan (`specs/001-import-photos` n'a pas encore de plan) ; il est documenté ici pour que l'implémentation future de l'import consomme cette API sans la redéfinir. | |
| new file mode 100644 | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | +# Contrat d'API interne : `regine_core.dossier` / `regine_core.config.categories` | ||
| 2 | + | ||
| 3 | +Cette fonctionnalité n'introduit aucune nouvelle commande CLI de premier niveau : elle expose une API Python interne à `regine-core`, consommée par la future façade CLI du module import (`specs/001-import-photos`) et, plus tard, par `regine-gui`/`regine-agent` — cohérent avec le Principe VI (bibliothèque centrale, façades minces, objets structurés plutôt que texte à parser). | ||
| 4 | + | ||
| 5 | +## `regine_core.dossier.root.determine_root(date, categorie=None) -> RootLocation` | ||
| 6 | + | ||
| 7 | +Résout le répertoire racine (année ou catégorie) pour un dossier ou dossier parent en cours de création (FR-001, FR-002, FR-003). | ||
| 8 | + | ||
| 9 | +**Entrée** : | ||
| 10 | +- `date` : date EXIF (ou plage) du groupe d'import concerné. | ||
| 11 | +- `categorie` : nom de catégorie choisi par l'utilisateur, ou `None` pour le placement par défaut par année. | ||
| 12 | + | ||
| 13 | +**Sortie** : objet `RootLocation` structuré (`type`, `nom`, `chemin_archive`, `chemin_local` — cf. `data-model.md`). Aucune exception levée pour une catégorie inconnue : `determine_root` ne fait que construire le chemin, la création effective du répertoire relève du module `archive`/`import`, pas de cette fonction. | ||
| 14 | + | ||
| 15 | +**Contrat de comportement** : | ||
| 16 | +- Appelée une seule fois par dossier/dossier parent créé (FR-006) — jamais réappelée pour un sous-dossier d'un parent existant, qui réutilise directement le `RootLocation` déjà résolu du parent. | ||
| 17 | +- `chemin_archive` et `chemin_local` partagent toujours le même `nom` et le même `type` (FR-002/FR-003, miroir garanti par construction plutôt que par synchronisation a posteriori). | ||
| 18 | + | ||
| 19 | +## `regine_core.config.categories.list_known_categories() -> list[str]` | ||
| 20 | + | ||
| 21 | +Retourne la liste des noms de catégories déjà utilisées au moins une fois (FR-004), triée par ordre d'utilisation la plus récente. Liste vide si aucune catégorie n'a jamais été utilisée (pas une erreur). | ||
| 22 | + | ||
| 23 | +## `regine_core.config.categories.suggest_categories(saisie: str) -> list[str]` | ||
| 24 | + | ||
| 25 | +Retourne, parmi les catégories déjà connues, celles dont le nom est proche de `saisie` (cf. `research.md` § 2), pour aider à éviter un doublon orthographique (Edge Case "Mariage" vs "mariage"). Liste vide si aucune correspondance proche — la saisie de l'utilisateur reste alors utilisable telle quelle comme nouvelle catégorie (FR-004). | ||
| 26 | + | ||
| 27 | +## `regine_core.config.categories.register_category_usage(nom: str) -> None` | ||
| 28 | + | ||
| 29 | +Enregistre `nom` dans la table `categories` s'il n'y figure pas déjà (première utilisation). Appelée automatiquement par le flux de création de dossier au moment où un `RootLocation` de type `categorie` est effectivement archivé — jamais à la simple saisie, pour ne pas polluer la liste de suggestions avec des catégories tapées puis abandonnées avant confirmation (cf. Principe II, confirmation explicite avant toute écriture). | ||
| 30 | + | ||
| 31 | +## Point d'intégration côté façade (futur, hors périmètre de ce plan) | ||
| 32 | + | ||
| 33 | +Le futur code CLI du module import (`specs/001-import-photos` FR-007) DEVRA, à l'étape de destination de chaque groupe créant un nouveau dossier simple ou un nouveau dossier parent : | ||
| 34 | +1. Demander à l'utilisateur s'il choisit une catégorie ou le placement par défaut, en proposant `list_known_categories()`. | ||
| 35 | +2. Si l'utilisateur saisit un nom non listé, appeler `suggest_categories(saisie)` et lui présenter les correspondances proches avant de confirmer la création d'une catégorie réellement nouvelle. | ||
| 36 | +3. Appeler `determine_root(date, categorie)` pour obtenir le `RootLocation` à afficher dans le résumé de confirmation (`specs/001-import-photos` FR-018) et à utiliser pour la construction du chemin final. | ||
| 37 | +4. Appeler `register_category_usage(nom)` uniquement après confirmation explicite de l'archivage, jamais avant. | ||
| 38 | + | ||
| 39 | +Ce point d'intégration n'est pas implémenté par ce plan (`specs/001-import-photos` n'a pas encore de plan) ; il est documenté ici pour que l'implémentation future de l'import consomme cette API sans la redéfinir. | ||
added
specs/004-categorisation-dossiers/data-model.md +52 -0 | new file mode 100644 | ||
| @@ -0,0 +1,52 @@ | ||
| 1 | +# Data Model: Catégorisation des dossiers à la racine de l'archive | |
| 2 | + | |
| 3 | +Entités dérivées de `spec.md` § Key Entities et Functional Requirements. | |
| 4 | + | |
| 5 | +## Répertoire racine (archive) — concept, pas une table | |
| 6 | + | |
| 7 | +Pas une entité stockée en tant que telle : c'est un attribut calculé du chemin d'un dossier ou dossier parent. | |
| 8 | + | |
| 9 | +| Champ (objet `RootLocation` retourné par `determine_root`) | Type | Règles | | |
| 10 | +|---|---|---| | |
| 11 | +| `type` | énumération : `annee` \| `categorie` | Déterminé par le choix explicite de l'utilisateur à l'étape de destination (FR-001) ; `annee` par défaut (FR-002) | | |
| 12 | +| `nom` | texte | Si `type == annee` : dérivé directement de la date EXIF du groupe (`AAAA`), jamais saisi manuellement. Si `type == categorie` : nom choisi par l'utilisateur, nettoyé selon les mêmes règles que tout nom de dossier (`specs/001-import-photos` FR-010) | | |
| 13 | +| `chemin_archive` | chemin | `<racine archive>/<nom>/` | | |
| 14 | +| `chemin_local` | chemin | `<racine espace de travail local>/<nom>/` — même valeur de `nom`, en miroir (FR-002/FR-003) | | |
| 15 | + | |
| 16 | +**Règle d'unicité par dossier** : un dossier ou dossier parent a exactement un `RootLocation` à la fois (FR-005). Pour un dossier parent, ce `RootLocation` est fixé au premier import qui le crée et hérité tel quel par tout sous-dossier ajouté ensuite (FR-006) — pas de re-résolution à chaque sous-dossier. | |
| 17 | + | |
| 18 | +## Catégorie (table `categories`, dans la base de contexte centralisée de `specs/003-config-contexte-travail`) | |
| 19 | + | |
| 20 | +| Champ | Type | Règles | | |
| 21 | +|---|---|---| | |
| 22 | +| `id` | identifiant interne | Clé primaire | | |
| 23 | +| `nom` | texte | Unique ; nom de répertoire déjà utilisé au moins une fois (FR-004) | | |
| 24 | +| `premiere_utilisation` | horodatage | Date du premier dossier créé sous cette catégorie | | |
| 25 | + | |
| 26 | +**Alimentation** : une ligne est ajoutée automatiquement dès qu'un dossier ou dossier parent est créé avec `type == categorie` pour un `nom` non encore présent (FR-004, Edge Case "exposition"). Aucune ligne n'est créée pour les répertoires d'année (non ambigus, dérivés de la date). | |
| 27 | + | |
| 28 | +**Usage** : `suggest_categories(saisie_utilisateur)` compare la saisie aux `nom` déjà présents (cf. research.md § 2) pour réduire le risque de doublon orthographique (Edge Case "Mariage" vs "mariage") ; `list_known_categories()` retourne la liste complète pour proposition à l'étape de destination (FR-004). | |
| 29 | + | |
| 30 | +## Relation avec Dossier / Dossier parent (repris de `specs/001-import-photos`, non redéfinis) | |
| 31 | + | |
| 32 | +```text | |
| 33 | +Dossier ──1────1── RootLocation (année ou catégorie, fixé à la création) | |
| 34 | + │ | |
| 35 | + └─ Sous-dossier (dossier parent uniquement) ── hérite du même RootLocation que son parent, jamais recalculé | |
| 36 | +``` | |
| 37 | + | |
| 38 | +## État / transitions | |
| 39 | + | |
| 40 | +```text | |
| 41 | +[Dossier ou dossier parent] | |
| 42 | + │ création (import, choix explicite : année ou catégorie) | |
| 43 | + ▼ | |
| 44 | + RootLocation fixé (immuable en écriture directe) | |
| 45 | + │ | |
| 46 | + │ déplacement détecté par somme de contrôle de contenu inchangé | |
| 47 | + │ (mécanisme existant, section 8 — pas une opération dédiée de ce module) | |
| 48 | + ▼ | |
| 49 | + RootLocation mis à jour (reflète le nouveau chemin détecté) | |
| 50 | +``` | |
| 51 | + | |
| 52 | +Il n'existe pas d'opération « recatégoriser » au sens d'une écriture directe sur le `RootLocation` : la seule voie de changement est un déplacement physique du dossier, détecté comme tel à la réconciliation (FR-008, cf. `specs/004-categorisation-dossiers` User Story 4). | |
| new file mode 100644 | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | +# Data Model: Catégorisation des dossiers à la racine de l'archive | ||
| 2 | + | ||
| 3 | +Entités dérivées de `spec.md` § Key Entities et Functional Requirements. | ||
| 4 | + | ||
| 5 | +## Répertoire racine (archive) — concept, pas une table | ||
| 6 | + | ||
| 7 | +Pas une entité stockée en tant que telle : c'est un attribut calculé du chemin d'un dossier ou dossier parent. | ||
| 8 | + | ||
| 9 | +| Champ (objet `RootLocation` retourné par `determine_root`) | Type | Règles | | ||
| 10 | +|---|---|---| | ||
| 11 | +| `type` | énumération : `annee` \| `categorie` | Déterminé par le choix explicite de l'utilisateur à l'étape de destination (FR-001) ; `annee` par défaut (FR-002) | | ||
| 12 | +| `nom` | texte | Si `type == annee` : dérivé directement de la date EXIF du groupe (`AAAA`), jamais saisi manuellement. Si `type == categorie` : nom choisi par l'utilisateur, nettoyé selon les mêmes règles que tout nom de dossier (`specs/001-import-photos` FR-010) | | ||
| 13 | +| `chemin_archive` | chemin | `<racine archive>/<nom>/` | | ||
| 14 | +| `chemin_local` | chemin | `<racine espace de travail local>/<nom>/` — même valeur de `nom`, en miroir (FR-002/FR-003) | | ||
| 15 | + | ||
| 16 | +**Règle d'unicité par dossier** : un dossier ou dossier parent a exactement un `RootLocation` à la fois (FR-005). Pour un dossier parent, ce `RootLocation` est fixé au premier import qui le crée et hérité tel quel par tout sous-dossier ajouté ensuite (FR-006) — pas de re-résolution à chaque sous-dossier. | ||
| 17 | + | ||
| 18 | +## Catégorie (table `categories`, dans la base de contexte centralisée de `specs/003-config-contexte-travail`) | ||
| 19 | + | ||
| 20 | +| Champ | Type | Règles | | ||
| 21 | +|---|---|---| | ||
| 22 | +| `id` | identifiant interne | Clé primaire | | ||
| 23 | +| `nom` | texte | Unique ; nom de répertoire déjà utilisé au moins une fois (FR-004) | | ||
| 24 | +| `premiere_utilisation` | horodatage | Date du premier dossier créé sous cette catégorie | | ||
| 25 | + | ||
| 26 | +**Alimentation** : une ligne est ajoutée automatiquement dès qu'un dossier ou dossier parent est créé avec `type == categorie` pour un `nom` non encore présent (FR-004, Edge Case "exposition"). Aucune ligne n'est créée pour les répertoires d'année (non ambigus, dérivés de la date). | ||
| 27 | + | ||
| 28 | +**Usage** : `suggest_categories(saisie_utilisateur)` compare la saisie aux `nom` déjà présents (cf. research.md § 2) pour réduire le risque de doublon orthographique (Edge Case "Mariage" vs "mariage") ; `list_known_categories()` retourne la liste complète pour proposition à l'étape de destination (FR-004). | ||
| 29 | + | ||
| 30 | +## Relation avec Dossier / Dossier parent (repris de `specs/001-import-photos`, non redéfinis) | ||
| 31 | + | ||
| 32 | +```text | ||
| 33 | +Dossier ──1────1── RootLocation (année ou catégorie, fixé à la création) | ||
| 34 | + │ | ||
| 35 | + └─ Sous-dossier (dossier parent uniquement) ── hérite du même RootLocation que son parent, jamais recalculé | ||
| 36 | +``` | ||
| 37 | + | ||
| 38 | +## État / transitions | ||
| 39 | + | ||
| 40 | +```text | ||
| 41 | +[Dossier ou dossier parent] | ||
| 42 | + │ création (import, choix explicite : année ou catégorie) | ||
| 43 | + ▼ | ||
| 44 | + RootLocation fixé (immuable en écriture directe) | ||
| 45 | + │ | ||
| 46 | + │ déplacement détecté par somme de contrôle de contenu inchangé | ||
| 47 | + │ (mécanisme existant, section 8 — pas une opération dédiée de ce module) | ||
| 48 | + ▼ | ||
| 49 | + RootLocation mis à jour (reflète le nouveau chemin détecté) | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +Il n'existe pas d'opération « recatégoriser » au sens d'une écriture directe sur le `RootLocation` : la seule voie de changement est un déplacement physique du dossier, détecté comme tel à la réconciliation (FR-008, cf. `specs/004-categorisation-dossiers` User Story 4). | ||
added
specs/004-categorisation-dossiers/plan.md +100 -0 | new file mode 100644 | ||
| @@ -0,0 +1,100 @@ | ||
| 1 | +# Implementation Plan: Catégorisation des dossiers à la racine de l'archive | |
| 2 | + | |
| 3 | +**Branch**: `004-categorisation-dossiers` | **Date**: 2026-09-18 | **Spec**: [spec.md](./spec.md) | |
| 4 | + | |
| 5 | +**Input**: Feature specification from `/specs/004-categorisation-dossiers/spec.md` | |
| 6 | + | |
| 7 | +## Summary | |
| 8 | + | |
| 9 | +Ajoute, au-dessus des conventions de nommage de dossier déjà spécifiées (`specs/001-import-photos`), un niveau de placement racine à la création d'un dossier ou d'un dossier parent : un répertoire d'année par défaut, ou un répertoire de catégorie thématique (mariage, vacances, voyage, anniversaire, extensible) quand l'utilisateur le choisit explicitement à l'étape de destination déjà prévue. Ce placement est une propriété du dossier de plus haut niveau, héritée par ses sous-dossiers, et une recatégorisation a posteriori est traitée comme un simple déplacement détecté par somme de contrôle de contenu (aucun mécanisme dédié). Approche technique : une fonction de résolution de chemin (`determine_root`) dans le module `dossier` de `regine-core` (monorepo posé par `specs/003-config-contexte-travail`), consommée par le flux de destination du futur module `import` ; les catégories déjà utilisées sont mises en cache dans la base de contexte centralisée déjà définie par `specs/003-config-contexte-travail`, pour permettre une suggestion instantanée sans dépendre d'un accès au NAS. | |
| 10 | + | |
| 11 | +## Technical Context | |
| 12 | + | |
| 13 | +**Language/Version**: Python 3.11+ (cohérent avec `regine-core`, cf. `specs/003-config-contexte-travail/plan.md`) | |
| 14 | + | |
| 15 | +**Primary Dependencies**: bibliothèque standard uniquement (`pathlib` pour la construction de chemin, `difflib.get_close_matches` pour suggérer une catégorie proche d'une catégorie déjà utilisée, cf. Edge Case orthographe) ; pas de dépendance tierce nouvelle | |
| 16 | + | |
| 17 | +**Storage**: réutilise la base de contexte centralisée SQLite déjà définie par `specs/003-config-contexte-travail` (même fichier, mêmes conventions `PRAGMA user_version`/`application_id`) — nouvelle table `categories` (nom, date de première utilisation), en complément de la table `boitiers` existante ; les répertoires d'année ne sont pas mis en cache (dérivés directement de la date, aucune ambiguïté de frappe possible) | |
| 18 | + | |
| 19 | +**Testing**: pytest ; tests unitaires sur la résolution de chemin (année vs catégorie, casse/collision), tests d'intégration sur l'héritage du placement racine par un sous-dossier, tests de contrat sur la présentation du répertoire racine dans le résumé de confirmation | |
| 20 | + | |
| 21 | +**Target Platform**: identique à `specs/003-config-contexte-travail` (poste de bureau, macOS en priorité) — cette fonctionnalité ne touche pas au montage SMB, elle consomme le même contexte de travail déjà résolu | |
| 22 | + | |
| 23 | +**Project Type**: Monorepo existant (cf. `specs/003-config-contexte-travail/plan.md`) — cette fonctionnalité ajoute du code dans `regine-core` (module `dossier`, nouvelle table dans `regine-core/config`) et modifie la façade `regine-cli` au point d'intégration du futur module `import` ; aucun nouveau paquet créé | |
| 24 | + | |
| 25 | +**Performance Goals**: la résolution du répertoire racine et la suggestion de catégories proches DOIVENT rester quasi instantanées (lecture locale seule, pas d'accès réseau), cohérent avec SC-006 de `specs/003-config-contexte-travail` (pas de parcours NAS implicite) | |
| 26 | + | |
| 27 | +**Constraints**: le placement racine est immuable après création sauf déplacement explicite détecté par hash (FR-008) — aucune opération de « recatégorisation » en écriture directe à inventer ; les répertoires de catégorie restent plats (FR-009, pas de sous-organisation par année dans cette itération) | |
| 28 | + | |
| 29 | +**Scale/Scope**: nombre de catégories distinctes attendu faible à modéré (dizaines, pas milliers) sur la durée de vie d'une archive personnelle ; la table `categories` n'a pas de contrainte de volumétrie particulière | |
| 30 | + | |
| 31 | +## Constitution Check | |
| 32 | + | |
| 33 | +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | |
| 34 | + | |
| 35 | +| Principe / contrainte | Évaluation | Justification | | |
| 36 | +|---|---|---| | |
| 37 | +| I. Fichier maître intouchable | N/A | Cette fonctionnalité ne touche à aucun fichier maître, seulement à l'emplacement du dossier qui les contient. | | |
| 38 | +| II. Confirmation explicite avant toute action à risque | PASS | FR-010 impose l'affichage du répertoire racine dans le résumé déjà requis avant toute écriture sur l'archive (cf. `specs/001-import-photos` FR-018) ; aucune écriture supplémentaire non confirmée n'est introduite. | | |
| 39 | +| III. Identité par contenu, jamais par nom de fichier seul | PASS | FR-008 est une application directe de ce principe au niveau du dossier : une recatégorisation a posteriori est un déplacement détecté par somme de contrôle de contenu inchangé, jamais par comparaison de chemin ou de nom seul. | | |
| 40 | +| IV. Métadonnées ouvertes et embarquées | PASS | La catégorie n'est pas une métadonnée séparée du fichier au sens du Principe IV (légende, mots-clés, personnes, crédit, copyright, licence) : elle est directement visible dans le chemin du dossier sur le système de fichiers, lisible sans Régine ni outil spécialisé — cohérent avec l'exigence de « traçabilité humaine hors application » déjà actée par la constitution pour le nommage de dossier. | | |
| 41 | +| V. L'utilisateur décide, Régine suggère | PASS | FR-005 : Régine ne devine ni n'assigne jamais de catégorie ; FR-004/Edge Case : les catégories déjà utilisées sont proposées comme suggestions, jamais imposées. | | |
| 42 | +| VI. Bibliothèque centrale, façades minces | PASS | La résolution de chemin et le cache de catégories vivent dans `regine-core` (modules `dossier` et `config`) ; `regine-cli` ne fait qu'appeler cette logique à l'étape de destination et afficher son résultat. | | |
| 43 | +| CLI-first | PASS | Aucune interaction visuelle spécifique requise ; la question de catégorie s'intègre au même protocole texte in/out que le reste de l'étape de destination. | | |
| 44 | +| Exécution sans démon | PASS | Aucun état en arrière-plan requis ; la résolution de chemin est un calcul synchrone dans le même processus que l'import. | | |
| 45 | +| Formats ouverts et documentés | PASS | Réutilise la base SQLite déjà actée, aucun nouveau format introduit. | | |
| 46 | + | |
| 47 | +Aucune violation identifiée ; la section Complexity Tracking reste vide. | |
| 48 | + | |
| 49 | +**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/regine-core-api.md`, `quickstart.md`) : le modèle de données (table `categories` dans la base de contexte centralisée existante, aucune nouvelle base) et le contrat d'API interne (fonctions pures retournant des objets structurés, consommées identiquement par `regine-cli` aujourd'hui et par `regine-gui`/`regine-agent` demain) confirment chaque évaluation PASS ci-dessus. Aucune violation nouvelle introduite par la conception détaillée. | |
| 50 | + | |
| 51 | +## Project Structure | |
| 52 | + | |
| 53 | +### Documentation (this feature) | |
| 54 | + | |
| 55 | +```text | |
| 56 | +specs/004-categorisation-dossiers/ | |
| 57 | +├── plan.md # This file (/speckit-plan command output) | |
| 58 | +├── research.md # Phase 0 output (/speckit-plan command) | |
| 59 | +├── data-model.md # Phase 1 output (/speckit-plan command) | |
| 60 | +├── quickstart.md # Phase 1 output (/speckit-plan command) | |
| 61 | +├── contracts/ # Phase 1 output (/speckit-plan command) | |
| 62 | +│ └── regine-core-api.md | |
| 63 | +└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan) | |
| 64 | +``` | |
| 65 | + | |
| 66 | +### Source Code (repository root) — extension du monorepo posé par specs/003 | |
| 67 | + | |
| 68 | +```text | |
| 69 | +packages/ | |
| 70 | +├── regine-core/ | |
| 71 | +│ └── src/ | |
| 72 | +│ └── regine_core/ | |
| 73 | +│ ├── config/ | |
| 74 | +│ │ ├── db.py # (existant, specs/003) étendu : migration ajoutant la table `categories` | |
| 75 | +│ │ └── categories.py # NOUVEAU : list_known_categories(), suggest_categories(query), register_category_usage(nom) | |
| 76 | +│ └── dossier/ # NOUVEAU module (premier code du module `dossier`, cf. architecture) | |
| 77 | +│ ├── __init__.py | |
| 78 | +│ └── root.py # NOUVEAU : determine_root(date, categorie=None) -> RootLocation ; construit le chemin archive + local en miroir | |
| 79 | +│ | |
| 80 | +├── regine-cli/ | |
| 81 | +│ └── src/ | |
| 82 | +│ └── regine_cli/ | |
| 83 | +│ └── config_cmd.py # (existant, specs/003) étendu si besoin d'une commande `regine config categories list` | |
| 84 | +│ # Le point d'intégration réel (question de catégorie à l'étape de destination de l'import) | |
| 85 | +│ # relève de la future façade CLI du module import (specs/001-import-photos, pas encore implémentée) ; | |
| 86 | +│ # ce plan ne crée pas ce point d'intégration, il expose l'API que ce futur code appellera. | |
| 87 | +│ | |
| 88 | +└── regine-core/tests/ | |
| 89 | + ├── unit/ | |
| 90 | + │ ├── test_root_resolution.py # Année par défaut, catégorie explicite, une seule catégorie à la fois | |
| 91 | + │ └── test_categories_cache.py # Suggestion de catégories proches (orthographe), pas de doublon silencieux | |
| 92 | + └── integration/ | |
| 93 | + └── test_root_inheritance.py # Sous-dossier hérite du placement racine du dossier parent | |
| 94 | +``` | |
| 95 | + | |
| 96 | +**Structure Decision**: Extension du monorepo déjà posé par `specs/003-config-contexte-travail` — aucun nouveau paquet, seulement deux ajouts dans `regine-core` : la table `categories` dans la base de contexte centralisée existante (`regine_core/config/`), et le premier code du module `dossier` (`regine_core/dossier/root.py`), qui n'existait pas encore. `regine-cli` n'a pas de nouveau point d'entrée propre à cette fonctionnalité : l'intégration réelle (poser la question de catégorie) se fera dans le futur code CLI du module import (`specs/001-import-photos`), qui consommera l'API exposée ici. `regine-gui` et `regine-agent` restent des paquets vides, inchangés par ce plan. | |
| 97 | + | |
| 98 | +## Complexity Tracking | |
| 99 | + | |
| 100 | +*Aucune violation de gate à justifier — section laissée vide intentionnellement.* | |
| new file mode 100644 | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | +# Implementation Plan: Catégorisation des dossiers à la racine de l'archive | ||
| 2 | + | ||
| 3 | +**Branch**: `004-categorisation-dossiers` | **Date**: 2026-09-18 | **Spec**: [spec.md](./spec.md) | ||
| 4 | + | ||
| 5 | +**Input**: Feature specification from `/specs/004-categorisation-dossiers/spec.md` | ||
| 6 | + | ||
| 7 | +## Summary | ||
| 8 | + | ||
| 9 | +Ajoute, au-dessus des conventions de nommage de dossier déjà spécifiées (`specs/001-import-photos`), un niveau de placement racine à la création d'un dossier ou d'un dossier parent : un répertoire d'année par défaut, ou un répertoire de catégorie thématique (mariage, vacances, voyage, anniversaire, extensible) quand l'utilisateur le choisit explicitement à l'étape de destination déjà prévue. Ce placement est une propriété du dossier de plus haut niveau, héritée par ses sous-dossiers, et une recatégorisation a posteriori est traitée comme un simple déplacement détecté par somme de contrôle de contenu (aucun mécanisme dédié). Approche technique : une fonction de résolution de chemin (`determine_root`) dans le module `dossier` de `regine-core` (monorepo posé par `specs/003-config-contexte-travail`), consommée par le flux de destination du futur module `import` ; les catégories déjà utilisées sont mises en cache dans la base de contexte centralisée déjà définie par `specs/003-config-contexte-travail`, pour permettre une suggestion instantanée sans dépendre d'un accès au NAS. | ||
| 10 | + | ||
| 11 | +## Technical Context | ||
| 12 | + | ||
| 13 | +**Language/Version**: Python 3.11+ (cohérent avec `regine-core`, cf. `specs/003-config-contexte-travail/plan.md`) | ||
| 14 | + | ||
| 15 | +**Primary Dependencies**: bibliothèque standard uniquement (`pathlib` pour la construction de chemin, `difflib.get_close_matches` pour suggérer une catégorie proche d'une catégorie déjà utilisée, cf. Edge Case orthographe) ; pas de dépendance tierce nouvelle | ||
| 16 | + | ||
| 17 | +**Storage**: réutilise la base de contexte centralisée SQLite déjà définie par `specs/003-config-contexte-travail` (même fichier, mêmes conventions `PRAGMA user_version`/`application_id`) — nouvelle table `categories` (nom, date de première utilisation), en complément de la table `boitiers` existante ; les répertoires d'année ne sont pas mis en cache (dérivés directement de la date, aucune ambiguïté de frappe possible) | ||
| 18 | + | ||
| 19 | +**Testing**: pytest ; tests unitaires sur la résolution de chemin (année vs catégorie, casse/collision), tests d'intégration sur l'héritage du placement racine par un sous-dossier, tests de contrat sur la présentation du répertoire racine dans le résumé de confirmation | ||
| 20 | + | ||
| 21 | +**Target Platform**: identique à `specs/003-config-contexte-travail` (poste de bureau, macOS en priorité) — cette fonctionnalité ne touche pas au montage SMB, elle consomme le même contexte de travail déjà résolu | ||
| 22 | + | ||
| 23 | +**Project Type**: Monorepo existant (cf. `specs/003-config-contexte-travail/plan.md`) — cette fonctionnalité ajoute du code dans `regine-core` (module `dossier`, nouvelle table dans `regine-core/config`) et modifie la façade `regine-cli` au point d'intégration du futur module `import` ; aucun nouveau paquet créé | ||
| 24 | + | ||
| 25 | +**Performance Goals**: la résolution du répertoire racine et la suggestion de catégories proches DOIVENT rester quasi instantanées (lecture locale seule, pas d'accès réseau), cohérent avec SC-006 de `specs/003-config-contexte-travail` (pas de parcours NAS implicite) | ||
| 26 | + | ||
| 27 | +**Constraints**: le placement racine est immuable après création sauf déplacement explicite détecté par hash (FR-008) — aucune opération de « recatégorisation » en écriture directe à inventer ; les répertoires de catégorie restent plats (FR-009, pas de sous-organisation par année dans cette itération) | ||
| 28 | + | ||
| 29 | +**Scale/Scope**: nombre de catégories distinctes attendu faible à modéré (dizaines, pas milliers) sur la durée de vie d'une archive personnelle ; la table `categories` n'a pas de contrainte de volumétrie particulière | ||
| 30 | + | ||
| 31 | +## Constitution Check | ||
| 32 | + | ||
| 33 | +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | ||
| 34 | + | ||
| 35 | +| Principe / contrainte | Évaluation | Justification | | ||
| 36 | +|---|---|---| | ||
| 37 | +| I. Fichier maître intouchable | N/A | Cette fonctionnalité ne touche à aucun fichier maître, seulement à l'emplacement du dossier qui les contient. | | ||
| 38 | +| II. Confirmation explicite avant toute action à risque | PASS | FR-010 impose l'affichage du répertoire racine dans le résumé déjà requis avant toute écriture sur l'archive (cf. `specs/001-import-photos` FR-018) ; aucune écriture supplémentaire non confirmée n'est introduite. | | ||
| 39 | +| III. Identité par contenu, jamais par nom de fichier seul | PASS | FR-008 est une application directe de ce principe au niveau du dossier : une recatégorisation a posteriori est un déplacement détecté par somme de contrôle de contenu inchangé, jamais par comparaison de chemin ou de nom seul. | | ||
| 40 | +| IV. Métadonnées ouvertes et embarquées | PASS | La catégorie n'est pas une métadonnée séparée du fichier au sens du Principe IV (légende, mots-clés, personnes, crédit, copyright, licence) : elle est directement visible dans le chemin du dossier sur le système de fichiers, lisible sans Régine ni outil spécialisé — cohérent avec l'exigence de « traçabilité humaine hors application » déjà actée par la constitution pour le nommage de dossier. | | ||
| 41 | +| V. L'utilisateur décide, Régine suggère | PASS | FR-005 : Régine ne devine ni n'assigne jamais de catégorie ; FR-004/Edge Case : les catégories déjà utilisées sont proposées comme suggestions, jamais imposées. | | ||
| 42 | +| VI. Bibliothèque centrale, façades minces | PASS | La résolution de chemin et le cache de catégories vivent dans `regine-core` (modules `dossier` et `config`) ; `regine-cli` ne fait qu'appeler cette logique à l'étape de destination et afficher son résultat. | | ||
| 43 | +| CLI-first | PASS | Aucune interaction visuelle spécifique requise ; la question de catégorie s'intègre au même protocole texte in/out que le reste de l'étape de destination. | | ||
| 44 | +| Exécution sans démon | PASS | Aucun état en arrière-plan requis ; la résolution de chemin est un calcul synchrone dans le même processus que l'import. | | ||
| 45 | +| Formats ouverts et documentés | PASS | Réutilise la base SQLite déjà actée, aucun nouveau format introduit. | | ||
| 46 | + | ||
| 47 | +Aucune violation identifiée ; la section Complexity Tracking reste vide. | ||
| 48 | + | ||
| 49 | +**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/regine-core-api.md`, `quickstart.md`) : le modèle de données (table `categories` dans la base de contexte centralisée existante, aucune nouvelle base) et le contrat d'API interne (fonctions pures retournant des objets structurés, consommées identiquement par `regine-cli` aujourd'hui et par `regine-gui`/`regine-agent` demain) confirment chaque évaluation PASS ci-dessus. Aucune violation nouvelle introduite par la conception détaillée. | ||
| 50 | + | ||
| 51 | +## Project Structure | ||
| 52 | + | ||
| 53 | +### Documentation (this feature) | ||
| 54 | + | ||
| 55 | +```text | ||
| 56 | +specs/004-categorisation-dossiers/ | ||
| 57 | +├── plan.md # This file (/speckit-plan command output) | ||
| 58 | +├── research.md # Phase 0 output (/speckit-plan command) | ||
| 59 | +├── data-model.md # Phase 1 output (/speckit-plan command) | ||
| 60 | +├── quickstart.md # Phase 1 output (/speckit-plan command) | ||
| 61 | +├── contracts/ # Phase 1 output (/speckit-plan command) | ||
| 62 | +│ └── regine-core-api.md | ||
| 63 | +└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan) | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +### Source Code (repository root) — extension du monorepo posé par specs/003 | ||
| 67 | + | ||
| 68 | +```text | ||
| 69 | +packages/ | ||
| 70 | +├── regine-core/ | ||
| 71 | +│ └── src/ | ||
| 72 | +│ └── regine_core/ | ||
| 73 | +│ ├── config/ | ||
| 74 | +│ │ ├── db.py # (existant, specs/003) étendu : migration ajoutant la table `categories` | ||
| 75 | +│ │ └── categories.py # NOUVEAU : list_known_categories(), suggest_categories(query), register_category_usage(nom) | ||
| 76 | +│ └── dossier/ # NOUVEAU module (premier code du module `dossier`, cf. architecture) | ||
| 77 | +│ ├── __init__.py | ||
| 78 | +│ └── root.py # NOUVEAU : determine_root(date, categorie=None) -> RootLocation ; construit le chemin archive + local en miroir | ||
| 79 | +│ | ||
| 80 | +├── regine-cli/ | ||
| 81 | +│ └── src/ | ||
| 82 | +│ └── regine_cli/ | ||
| 83 | +│ └── config_cmd.py # (existant, specs/003) étendu si besoin d'une commande `regine config categories list` | ||
| 84 | +│ # Le point d'intégration réel (question de catégorie à l'étape de destination de l'import) | ||
| 85 | +│ # relève de la future façade CLI du module import (specs/001-import-photos, pas encore implémentée) ; | ||
| 86 | +│ # ce plan ne crée pas ce point d'intégration, il expose l'API que ce futur code appellera. | ||
| 87 | +│ | ||
| 88 | +└── regine-core/tests/ | ||
| 89 | + ├── unit/ | ||
| 90 | + │ ├── test_root_resolution.py # Année par défaut, catégorie explicite, une seule catégorie à la fois | ||
| 91 | + │ └── test_categories_cache.py # Suggestion de catégories proches (orthographe), pas de doublon silencieux | ||
| 92 | + └── integration/ | ||
| 93 | + └── test_root_inheritance.py # Sous-dossier hérite du placement racine du dossier parent | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +**Structure Decision**: Extension du monorepo déjà posé par `specs/003-config-contexte-travail` — aucun nouveau paquet, seulement deux ajouts dans `regine-core` : la table `categories` dans la base de contexte centralisée existante (`regine_core/config/`), et le premier code du module `dossier` (`regine_core/dossier/root.py`), qui n'existait pas encore. `regine-cli` n'a pas de nouveau point d'entrée propre à cette fonctionnalité : l'intégration réelle (poser la question de catégorie) se fera dans le futur code CLI du module import (`specs/001-import-photos`), qui consommera l'API exposée ici. `regine-gui` et `regine-agent` restent des paquets vides, inchangés par ce plan. | ||
| 97 | + | ||
| 98 | +## Complexity Tracking | ||
| 99 | + | ||
| 100 | +*Aucune violation de gate à justifier — section laissée vide intentionnellement.* | ||
added
specs/004-categorisation-dossiers/quickstart.md +63 -0 | new file mode 100644 | ||
| @@ -0,0 +1,63 @@ | ||
| 1 | +# Quickstart : validation de la catégorisation des dossiers | |
| 2 | + | |
| 3 | +Ce guide valide les 4 user stories de `spec.md` via l'API interne décrite dans `contracts/regine-core-api.md`. Comme cette fonctionnalité n'a pas de surface CLI propre (elle s'intègre au futur flux de destination de l'import, `specs/001-import-photos`), la validation se fait directement contre `regine_core` en attendant que ce point d'intégration soit implémenté. À exécuter une fois `regine_core.dossier.root` et `regine_core.config.categories` implémentés (cf. `tasks.md`). | |
| 4 | + | |
| 5 | +## Prérequis | |
| 6 | + | |
| 7 | +- `regine-core` installé (environnement de développement du monorepo, cf. `specs/003-config-contexte-travail`). | |
| 8 | +- Un contexte de travail déjà configuré (`specs/003-config-contexte-travail`), avec la base de contexte centralisée initialisée. | |
| 9 | + | |
| 10 | +## Scénario 1 — Placement par défaut par année (User Story 1, P1) | |
| 11 | + | |
| 12 | +```python | |
| 13 | +from regine_core.dossier.root import determine_root | |
| 14 | +from datetime import date | |
| 15 | + | |
| 16 | +root = determine_root(date(2026, 8, 15)) | |
| 17 | +assert root.type == "annee" | |
| 18 | +assert root.nom == "2026" | |
| 19 | +assert root.chemin_archive.name == "2026" and root.chemin_local.name == "2026" | |
| 20 | +``` | |
| 21 | + | |
| 22 | +**Résultat attendu** : `type == "annee"`, chemins archive et local en miroir sous `2026/`. | |
| 23 | + | |
| 24 | +## Scénario 2 — Catégorie thématique explicite (User Story 2, P2) | |
| 25 | + | |
| 26 | +```python | |
| 27 | +root = determine_root(date(2026, 6, 20), categorie="mariage") | |
| 28 | +assert root.type == "categorie" | |
| 29 | +assert root.nom == "mariage" | |
| 30 | +``` | |
| 31 | + | |
| 32 | +**Résultat attendu** : `type == "categorie"`, aucun répertoire d'année dans le chemin résultant. | |
| 33 | + | |
| 34 | +## Scénario 3 — Suggestion pour éviter un doublon orthographique (Edge Case) | |
| 35 | + | |
| 36 | +```python | |
| 37 | +from regine_core.config.categories import suggest_categories, register_category_usage | |
| 38 | + | |
| 39 | +register_category_usage("mariage") | |
| 40 | +suggestions = suggest_categories("Mariage") | |
| 41 | +assert "mariage" in suggestions | |
| 42 | +``` | |
| 43 | + | |
| 44 | +**Résultat attendu** : la catégorie déjà connue `mariage` est proposée en correspondance proche avant que l'utilisateur ne crée par erreur un second répertoire `Mariage`. | |
| 45 | + | |
| 46 | +## Scénario 4 — Héritage par un sous-dossier de voyage (User Story 3, P3) | |
| 47 | + | |
| 48 | +```python | |
| 49 | +parent_root = determine_root(date(2026, 8, 1), categorie="voyage") | |
| 50 | +# Le sous-dossier réutilise directement parent_root, sans nouvel appel à determine_root : | |
| 51 | +sous_dossier_root = parent_root | |
| 52 | +assert sous_dossier_root.nom == "voyage" | |
| 53 | +``` | |
| 54 | + | |
| 55 | +**Résultat attendu** : aucun second appel à `determine_root` pour le sous-dossier — le `RootLocation` du parent est réutilisé tel quel. | |
| 56 | + | |
| 57 | +## Scénario 5 — Recatégorisation a posteriori (User Story 4, P4) | |
| 58 | + | |
| 59 | +Ce scénario dépend du mécanisme de réconciliation par hash (section 8, pas encore couvert par une spec dédiée) : à valider une fois ce mécanisme disponible, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que la réconciliation le détecte comme un déplacement de contenu inchangé plutôt qu'une anomalie. | |
| 60 | + | |
| 61 | +## Critères de sortie | |
| 62 | + | |
| 63 | +Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 reste bloqué tant que le mécanisme de réconciliation par hash n'a pas sa propre implémentation — à retester à ce moment-là. | |
| new file mode 100644 | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | +# Quickstart : validation de la catégorisation des dossiers | ||
| 2 | + | ||
| 3 | +Ce guide valide les 4 user stories de `spec.md` via l'API interne décrite dans `contracts/regine-core-api.md`. Comme cette fonctionnalité n'a pas de surface CLI propre (elle s'intègre au futur flux de destination de l'import, `specs/001-import-photos`), la validation se fait directement contre `regine_core` en attendant que ce point d'intégration soit implémenté. À exécuter une fois `regine_core.dossier.root` et `regine_core.config.categories` implémentés (cf. `tasks.md`). | ||
| 4 | + | ||
| 5 | +## Prérequis | ||
| 6 | + | ||
| 7 | +- `regine-core` installé (environnement de développement du monorepo, cf. `specs/003-config-contexte-travail`). | ||
| 8 | +- Un contexte de travail déjà configuré (`specs/003-config-contexte-travail`), avec la base de contexte centralisée initialisée. | ||
| 9 | + | ||
| 10 | +## Scénario 1 — Placement par défaut par année (User Story 1, P1) | ||
| 11 | + | ||
| 12 | +```python | ||
| 13 | +from regine_core.dossier.root import determine_root | ||
| 14 | +from datetime import date | ||
| 15 | + | ||
| 16 | +root = determine_root(date(2026, 8, 15)) | ||
| 17 | +assert root.type == "annee" | ||
| 18 | +assert root.nom == "2026" | ||
| 19 | +assert root.chemin_archive.name == "2026" and root.chemin_local.name == "2026" | ||
| 20 | +``` | ||
| 21 | + | ||
| 22 | +**Résultat attendu** : `type == "annee"`, chemins archive et local en miroir sous `2026/`. | ||
| 23 | + | ||
| 24 | +## Scénario 2 — Catégorie thématique explicite (User Story 2, P2) | ||
| 25 | + | ||
| 26 | +```python | ||
| 27 | +root = determine_root(date(2026, 6, 20), categorie="mariage") | ||
| 28 | +assert root.type == "categorie" | ||
| 29 | +assert root.nom == "mariage" | ||
| 30 | +``` | ||
| 31 | + | ||
| 32 | +**Résultat attendu** : `type == "categorie"`, aucun répertoire d'année dans le chemin résultant. | ||
| 33 | + | ||
| 34 | +## Scénario 3 — Suggestion pour éviter un doublon orthographique (Edge Case) | ||
| 35 | + | ||
| 36 | +```python | ||
| 37 | +from regine_core.config.categories import suggest_categories, register_category_usage | ||
| 38 | + | ||
| 39 | +register_category_usage("mariage") | ||
| 40 | +suggestions = suggest_categories("Mariage") | ||
| 41 | +assert "mariage" in suggestions | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +**Résultat attendu** : la catégorie déjà connue `mariage` est proposée en correspondance proche avant que l'utilisateur ne crée par erreur un second répertoire `Mariage`. | ||
| 45 | + | ||
| 46 | +## Scénario 4 — Héritage par un sous-dossier de voyage (User Story 3, P3) | ||
| 47 | + | ||
| 48 | +```python | ||
| 49 | +parent_root = determine_root(date(2026, 8, 1), categorie="voyage") | ||
| 50 | +# Le sous-dossier réutilise directement parent_root, sans nouvel appel à determine_root : | ||
| 51 | +sous_dossier_root = parent_root | ||
| 52 | +assert sous_dossier_root.nom == "voyage" | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +**Résultat attendu** : aucun second appel à `determine_root` pour le sous-dossier — le `RootLocation` du parent est réutilisé tel quel. | ||
| 56 | + | ||
| 57 | +## Scénario 5 — Recatégorisation a posteriori (User Story 4, P4) | ||
| 58 | + | ||
| 59 | +Ce scénario dépend du mécanisme de réconciliation par hash (section 8, pas encore couvert par une spec dédiée) : à valider une fois ce mécanisme disponible, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que la réconciliation le détecte comme un déplacement de contenu inchangé plutôt qu'une anomalie. | ||
| 60 | + | ||
| 61 | +## Critères de sortie | ||
| 62 | + | ||
| 63 | +Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 reste bloqué tant que le mécanisme de réconciliation par hash n'a pas sa propre implémentation — à retester à ce moment-là. | ||
added
specs/004-categorisation-dossiers/research.md +34 -0 | new file mode 100644 | ||
| @@ -0,0 +1,34 @@ | ||
| 1 | +# Research: Catégorisation des dossiers à la racine de l'archive | |
| 2 | + | |
| 3 | +## 1. Où stocker la liste des catégories déjà utilisées (pour la suggestion, FR-004/Edge Case) | |
| 4 | + | |
| 5 | +**Decision**: Réutiliser la base de contexte centralisée SQLite déjà définie par `specs/003-config-contexte-travail` (même fichier, mêmes conventions `PRAGMA user_version`/`application_id`), avec une nouvelle table `categories` (`nom`, `premiere_utilisation`). Elle est alimentée automatiquement à chaque création d'un dossier ou dossier parent sous une catégorie (jamais pour les répertoires d'année, qui n'ont pas besoin d'être mis en cache). | |
| 6 | + | |
| 7 | +**Rationale**: Les répertoires d'année sont dérivés directement de la date EXIF, sans aucune ambiguïté d'orthographe possible — rien à mettre en cache. Les catégories, en revanche, sont des noms libres saisis par l'utilisateur (FR-004) : sans liste des catégories déjà utilisées, Régine ne pourrait pas proposer de suggestion au moment de la saisie (Edge Case "Mariage" vs "mariage"), et devrait soit interroger le NAS en direct (lenteur, dépendance réseau au moment même de l'import), soit laisser l'utilisateur créer des doublons par erreur. Réutiliser la base de contexte centralisée déjà actée par `specs/003-config-contexte-travail` (plutôt que d'en créer une nouvelle) suit le même raisonnement déjà retenu pour les boîtiers : éviter un aller-retour réseau, cohérence avec le Principe VI (une seule bibliothèque centrale, pas de mécanisme de persistance dupliqué pour un besoin très proche). | |
| 8 | + | |
| 9 | +**Alternatives considered**: | |
| 10 | +- Lister en direct les répertoires présents à la racine de l'archive NAS à chaque saisie — rejeté : nécessite un accès réseau à un moment où le NAS peut ne pas être monté (cf. `specs/003-config-contexte-travail` User Story 2), et serait plus lent qu'une lecture locale pour une simple suggestion de saisie. | |
| 11 | +- Une base séparée dédiée aux catégories — rejeté : même argument que pour les boîtiers (specs/003 research.md § 2), pas de justification à dupliquer le mécanisme de persistance pour un second besoin de nature identique (cache de suggestion local, alimenté à l'écriture). | |
| 12 | + | |
| 13 | +## 2. Suggestion de catégorie proche (orthographe/casse) | |
| 14 | + | |
| 15 | +**Decision**: Utiliser `difflib.get_close_matches` (bibliothèque standard Python) pour comparer la saisie de l'utilisateur aux catégories déjà connues (table `categories`) et proposer les correspondances proches avant de créer un nouveau répertoire de catégorie. | |
| 16 | + | |
| 17 | +**Rationale**: Besoin simple (comparaison de chaînes approximative sur un nombre de catégories attendu faible à modéré), entièrement couvert par la bibliothèque standard — cohérent avec la préférence du projet pour des dépendances minimales déjà actée en `specs/003-config-contexte-travail/research.md`. | |
| 18 | + | |
| 19 | +**Alternatives considered**: | |
| 20 | +- Bibliothèque tierce de correspondance floue (`rapidfuzz`, `fuzzywuzzy`) — rejeté : complexité et dépendance non justifiées pour un volume de catégories qui reste faible (dizaines, pas milliers). | |
| 21 | +- Comparaison stricte (sensible à la casse et à l'orthographe) sans suggestion — rejeté : ne répond pas à l'Edge Case explicitement identifié dans la spec (risque de créer deux répertoires équivalents par erreur). | |
| 22 | + | |
| 23 | +## 3. Emplacement du code de résolution de chemin | |
| 24 | + | |
| 25 | +**Decision**: Nouveau module `regine_core/dossier/root.py`, exposant une fonction `determine_root(date, categorie=None) -> RootLocation` qui retourne un objet structuré (type de racine, nom du répertoire, chemin archive et chemin local en miroir), consommée par le futur code d'import (`specs/001-import-photos`) et par `regine_core/config/categories.py` pour la mise à jour du cache. | |
| 26 | + | |
| 27 | +**Rationale**: Le placement racine (année/catégorie) est une règle de structure de dossier, pas une règle propre à l'import de carte mémoire — il relève naturellement du module `dossier` déjà identifié dans l'architecture du projet (`docs/interface-cli-gui-architecture.md`), qui n'avait encore aucun code avant ce plan. Le retour d'un objet structuré (plutôt qu'une simple chaîne de caractères) respecte le Principe VI : aucune façade n'a à reconstruire ou parser un chemin, elle reçoit directement de quoi afficher le résumé requis par FR-010. | |
| 28 | + | |
| 29 | +**Alternatives considered**: | |
| 30 | +- Intégrer cette logique directement dans le futur module `import` plutôt que dans `dossier` — rejeté : le placement racine est réutilisé ailleurs qu'à l'import (ex. affichage, recherche de dossiers candidats déjà prévue par `specs/001-import-photos` FR-008), la séparation par responsabilité reste plus cohérente avec le découpage de modules déjà documenté. | |
| 31 | + | |
| 32 | +## Résumé | |
| 33 | + | |
| 34 | +Tous les points du Technical Context sont résolus par les décisions ci-dessus. Aucune dépendance tierce nouvelle introduite ; une seule extension de schéma (table `categories`) sur une base déjà actée. | |
| new file mode 100644 | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | +# Research: Catégorisation des dossiers à la racine de l'archive | ||
| 2 | + | ||
| 3 | +## 1. Où stocker la liste des catégories déjà utilisées (pour la suggestion, FR-004/Edge Case) | ||
| 4 | + | ||
| 5 | +**Decision**: Réutiliser la base de contexte centralisée SQLite déjà définie par `specs/003-config-contexte-travail` (même fichier, mêmes conventions `PRAGMA user_version`/`application_id`), avec une nouvelle table `categories` (`nom`, `premiere_utilisation`). Elle est alimentée automatiquement à chaque création d'un dossier ou dossier parent sous une catégorie (jamais pour les répertoires d'année, qui n'ont pas besoin d'être mis en cache). | ||
| 6 | + | ||
| 7 | +**Rationale**: Les répertoires d'année sont dérivés directement de la date EXIF, sans aucune ambiguïté d'orthographe possible — rien à mettre en cache. Les catégories, en revanche, sont des noms libres saisis par l'utilisateur (FR-004) : sans liste des catégories déjà utilisées, Régine ne pourrait pas proposer de suggestion au moment de la saisie (Edge Case "Mariage" vs "mariage"), et devrait soit interroger le NAS en direct (lenteur, dépendance réseau au moment même de l'import), soit laisser l'utilisateur créer des doublons par erreur. Réutiliser la base de contexte centralisée déjà actée par `specs/003-config-contexte-travail` (plutôt que d'en créer une nouvelle) suit le même raisonnement déjà retenu pour les boîtiers : éviter un aller-retour réseau, cohérence avec le Principe VI (une seule bibliothèque centrale, pas de mécanisme de persistance dupliqué pour un besoin très proche). | ||
| 8 | + | ||
| 9 | +**Alternatives considered**: | ||
| 10 | +- Lister en direct les répertoires présents à la racine de l'archive NAS à chaque saisie — rejeté : nécessite un accès réseau à un moment où le NAS peut ne pas être monté (cf. `specs/003-config-contexte-travail` User Story 2), et serait plus lent qu'une lecture locale pour une simple suggestion de saisie. | ||
| 11 | +- Une base séparée dédiée aux catégories — rejeté : même argument que pour les boîtiers (specs/003 research.md § 2), pas de justification à dupliquer le mécanisme de persistance pour un second besoin de nature identique (cache de suggestion local, alimenté à l'écriture). | ||
| 12 | + | ||
| 13 | +## 2. Suggestion de catégorie proche (orthographe/casse) | ||
| 14 | + | ||
| 15 | +**Decision**: Utiliser `difflib.get_close_matches` (bibliothèque standard Python) pour comparer la saisie de l'utilisateur aux catégories déjà connues (table `categories`) et proposer les correspondances proches avant de créer un nouveau répertoire de catégorie. | ||
| 16 | + | ||
| 17 | +**Rationale**: Besoin simple (comparaison de chaînes approximative sur un nombre de catégories attendu faible à modéré), entièrement couvert par la bibliothèque standard — cohérent avec la préférence du projet pour des dépendances minimales déjà actée en `specs/003-config-contexte-travail/research.md`. | ||
| 18 | + | ||
| 19 | +**Alternatives considered**: | ||
| 20 | +- Bibliothèque tierce de correspondance floue (`rapidfuzz`, `fuzzywuzzy`) — rejeté : complexité et dépendance non justifiées pour un volume de catégories qui reste faible (dizaines, pas milliers). | ||
| 21 | +- Comparaison stricte (sensible à la casse et à l'orthographe) sans suggestion — rejeté : ne répond pas à l'Edge Case explicitement identifié dans la spec (risque de créer deux répertoires équivalents par erreur). | ||
| 22 | + | ||
| 23 | +## 3. Emplacement du code de résolution de chemin | ||
| 24 | + | ||
| 25 | +**Decision**: Nouveau module `regine_core/dossier/root.py`, exposant une fonction `determine_root(date, categorie=None) -> RootLocation` qui retourne un objet structuré (type de racine, nom du répertoire, chemin archive et chemin local en miroir), consommée par le futur code d'import (`specs/001-import-photos`) et par `regine_core/config/categories.py` pour la mise à jour du cache. | ||
| 26 | + | ||
| 27 | +**Rationale**: Le placement racine (année/catégorie) est une règle de structure de dossier, pas une règle propre à l'import de carte mémoire — il relève naturellement du module `dossier` déjà identifié dans l'architecture du projet (`docs/interface-cli-gui-architecture.md`), qui n'avait encore aucun code avant ce plan. Le retour d'un objet structuré (plutôt qu'une simple chaîne de caractères) respecte le Principe VI : aucune façade n'a à reconstruire ou parser un chemin, elle reçoit directement de quoi afficher le résumé requis par FR-010. | ||
| 28 | + | ||
| 29 | +**Alternatives considered**: | ||
| 30 | +- Intégrer cette logique directement dans le futur module `import` plutôt que dans `dossier` — rejeté : le placement racine est réutilisé ailleurs qu'à l'import (ex. affichage, recherche de dossiers candidats déjà prévue par `specs/001-import-photos` FR-008), la séparation par responsabilité reste plus cohérente avec le découpage de modules déjà documenté. | ||
| 31 | + | ||
| 32 | +## Résumé | ||
| 33 | + | ||
| 34 | +Tous les points du Technical Context sont résolus par les décisions ci-dessus. Aucune dépendance tierce nouvelle introduite ; une seule extension de schéma (table `categories`) sur une base déjà actée. | ||