From d3e6d01ad572ab3204a7030e7dc122f638220c03 Mon Sep 17 00:00:00 2001 From: King Fu <111188248+Le-King-Fu@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:19:09 -0500 Subject: [PATCH] spec v2 --- specification_v2_analyse.md | 1057 +++++++++++++++++++++++++++++++++++ 1 file changed, 1057 insertions(+) create mode 100644 specification_v2_analyse.md diff --git a/specification_v2_analyse.md b/specification_v2_analyse.md new file mode 100644 index 0000000..53d6082 --- /dev/null +++ b/specification_v2_analyse.md @@ -0,0 +1,1057 @@ +# Simpl-Résultat - Spécification Détaillée v2 + +## Table des matières +1. [Résumé exécutif](#résumé-exécutif) +2. [Description générale enrichie](#description-générale-enrichie) +3. [Architecture proposée](#architecture-proposée) +4. [Spécifications fonctionnelles détaillées](#spécifications-fonctionnelles-détaillées) +5. [Modèle de données](#modèle-de-données) +6. [Interface utilisateur](#interface-utilisateur) +7. [Points faibles identifiés et recommandations](#points-faibles-identifiés-et-recommandations) +8. [Considérations techniques](#considérations-techniques) +9. [Plan de développement suggéré](#plan-de-développement-suggéré) + +--- + +## Résumé exécutif + +**Simpl-Résultat** est une application de bureau locale permettant aux utilisateurs de suivre leurs dépenses personnelles à partir d'exports CSV bancaires. L'application vise à catégoriser automatiquement les transactions, permettre des ajustements manuels, et fournir des rapports visuels pour le suivi budgétaire. + +### Objectifs principaux +- Centraliser les données financières de multiples sources (comptes chèques, épargne, cartes de crédit) +- Automatiser la catégorisation des dépenses via mots-clés +- Permettre un suivi budgétaire mensuel avec comparaisons historiques +- Offrir une expérience utilisateur simple et locale (pas de cloud) + +--- + +## Description générale enrichie + +### Vision du produit +Une application de finances personnelles **100% locale** (privacy-first) qui transforme des exports CSV bancaires bruts en insights financiers actionnables, sans nécessiter de connexion à des services tiers ou de partage de données sensibles. + +### Proposition de valeur +| Aspect | Description | +|--------|-------------| +| **Confidentialité** | Données stockées localement uniquement | +| **Simplicité** | Import CSV sans configuration bancaire complexe | +| **Flexibilité** | Support multi-sources, multi-formats | +| **Automatisation** | Catégorisation intelligente par mots-clés | +| **Contrôle** | Écritures d'ajustement et personnalisation complète | + +### Utilisateurs cibles +- Particuliers souhaitant suivre leur budget sans services cloud +- Utilisateurs soucieux de la confidentialité de leurs données financières +- Personnes ayant plusieurs comptes bancaires à consolider + +--- + +## Architecture proposée + +### Stack technologique recommandé + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PRÉSENTATION │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Electron / Tauri (Frontend) │ │ +│ │ React/Vue/Svelte + TailwindCSS │ │ +│ │ Charts: Recharts / Chart.js │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ LOGIQUE │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Service Layer (TypeScript) │ │ +│ │ • ImportService • CategoryService │ │ +│ │ • TransactionService • BudgetService │ │ +│ │ • ReportingService • AdjustmentService │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ DONNÉES │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ SQLite (Base locale) │ │ +│ │ + better-sqlite3 / sql.js │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Fichiers de configuration │ │ +│ │ JSON (préférences) │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Justification des choix + +| Composant | Option recommandée | Raison | +|-----------|-------------------|--------| +| **Framework desktop** | **Tauri** (P1) ou Electron | Tauri = plus léger, Rust backend, meilleure sécurité. Electron = écosystème plus mature | +| **Frontend** | Svelte ou React | Svelte = bundle léger, React = plus de ressources/libs | +| **Base de données** | SQLite | Locale, performante, pas de serveur, portable | +| **Charts** | Recharts ou Chart.js | Intégration React facile, bonne personnalisation | +| **Parsing CSV** | PapaParse | Robuste, gère les edge cases (quotes, encoding) | + +### Architecture des fichiers + +``` +simpl-resultat/ +├── src/ +│ ├── main/ # Process principal (Electron/Tauri) +│ │ ├── database/ +│ │ │ ├── schema.sql +│ │ │ ├── migrations/ +│ │ │ └── repository/ +│ │ ├── services/ +│ │ │ ├── import.service.ts +│ │ │ ├── category.service.ts +│ │ │ ├── transaction.service.ts +│ │ │ ├── budget.service.ts +│ │ │ ├── adjustment.service.ts +│ │ │ └── report.service.ts +│ │ └── ipc/ # Communication main/renderer +│ │ +│ ├── renderer/ # Interface utilisateur +│ │ ├── components/ +│ │ ├── pages/ +│ │ ├── stores/ +│ │ └── utils/ +│ │ +│ └── shared/ # Types partagés +│ ├── types/ +│ └── constants/ +│ +├── data/ # Données utilisateur (gitignored) +│ ├── database.sqlite +│ └── backups/ +│ +├── imports/ # Dossiers d'import configurés +│ ├── carte_credit/ +│ ├── compte_cheque/ +│ └── epargne/ +│ +└── config/ + ├── categories_default.json + └── import_profiles.json +``` + +--- + +## Spécifications fonctionnelles détaillées + +### 1. Module d'importation + +#### 1.1 Configuration des sources + +```typescript +interface ImportSource { + id: string; + name: string; // Ex: "Carte de crédit Visa" + directoryPath: string; // Chemin du répertoire surveillé + filePattern: string; // Ex: "export_cc*.csv" + encoding: 'utf-8' | 'iso-8859-1' | 'windows-1252'; + delimiter: ',' | ';' | '\t'; + hasHeader: boolean; + dateFormat: string; // Ex: "YYYY-MM-DD", "DD/MM/YYYY" + columnMapping: ColumnMapping; + amountRule: AmountRule; + skipLines?: number; // Lignes à ignorer en début de fichier + lastImportDate?: Date; +} + +interface ColumnMapping { + date: number | string; // Index ou nom de colonne + description: number | string; + transactionCode?: number | string; + debitAmount?: number | string; + creditAmount?: number | string; + amount?: number | string; // Si montant unique +} + +interface AmountRule { + type: 'separate' | 'combined'; + // Si combined: + signConvention?: 'negative_is_debit' | 'negative_is_credit'; +} +``` + +#### 1.2 Processus d'importation + +``` +[Sélection source] → [Détection fichiers] → [Preview données] + ↓ ↓ +[Validation mapping] ← [Ajustement mapping si nécessaire] + ↓ +[Détection doublons] → [Confirmation import] + ↓ +[Import en base] → [Catégorisation auto] → [Rapport d'import] +``` + +#### 1.3 Gestion des doublons + +**Critères de détection de doublon:** +- Date + Description + Montant identiques +- Ou: Hash SHA256 de la ligne complète déjà présent + +**Options utilisateur:** +- Ignorer les doublons (défaut) +- Forcer l'import +- Demander confirmation pour chaque + +### 2. Module de catégorisation + +#### 2.1 Structure hiérarchique + +```typescript +interface Category { + id: string; + name: string; + type: 'recurring' | 'occasional' | 'special' | 'transfer' | 'income'; + parentId?: string; // Pour sous-catégories + color?: string; // Pour les graphiques + icon?: string; + isSystem: boolean; // Non supprimable si true +} + +interface Supplier { + id: string; + name: string; + categoryId: string; + keywords: string[]; // Mots-clés pour matching + priority: number; // En cas de conflit de matching +} +``` + +#### 2.2 Algorithme de matching + +``` +Pour chaque transaction non catégorisée: + 1. Normaliser la description (minuscules, sans accents, trim) + 2. Pour chaque fournisseur (trié par priorité DESC): + a. Pour chaque mot-clé du fournisseur: + - Si description.contains(keyword) → MATCH + b. Si MATCH → Assigner catégorie du fournisseur, STOP + 3. Si aucun match → Catégorie "Autres dépenses" +``` + +**Améliorations suggérées:** +- Support des expressions régulières +- Matching par montant (ex: "Netflix" toujours ~15.99$) +- Apprentissage des assignations manuelles + +#### 2.3 Interface de gestion des non-matchés + +| Élément | Description | +|---------|-------------| +| Liste des transactions | Triées par date, regroupables par description similaire | +| Recherche rapide | Filtrer par texte dans description | +| Action rapide | Créer mot-clé à partir de la sélection | +| Action groupée | Catégoriser plusieurs transactions similaires en un clic | + +### 3. Module d'ajustements + +#### 3.1 Types d'écritures + +```typescript +interface Adjustment { + id: string; + originalTransactionId: string; + adjustmentDate: Date; // Fin du mois concerné + effectiveMonth: string; // Format "YYYY-MM" + entries: AdjustmentEntry[]; + reason?: string; + createdAt: Date; +} + +interface AdjustmentEntry { + categoryId: string; + amount: number; // Positif = augmente cette catégorie +} +``` + +#### 3.2 Exemple d'usage + +Transaction Amazon de 150$: +- Original: 100% → "Achats divers" +- Ajustement: + - -150$ "Achats divers" + - +80$ "Sport" + - +70$ "Musique" + +### 4. Module Budget + +#### 4.1 Structure + +```typescript +interface BudgetEntry { + id: string; + categoryId: string; + month: string; // "YYYY-MM" + plannedAmount: number; + notes?: string; +} + +interface BudgetTemplate { + id: string; + name: string; + entries: Omit[]; +} +``` + +#### 4.2 Fonctionnalités + +- **Templates**: Créer des budgets types réutilisables +- **Copie**: Dupliquer le budget d'un mois vers un autre +- **Alertes**: Notification visuelle si dépassement (>80%, >100%) +- **Carry-over**: Option pour reporter le surplus/déficit + +### 5. Module Reporting + +#### 5.1 Rapports disponibles + +| Rapport | Description | Visualisation | +|---------|-------------|---------------| +| Suivi mensuel 12 mois | Évolution des dépenses par catégorie | Graphique barres empilées | +| Mois courant vs précédent | Comparaison détaillée | Tableau + barres horizontales | +| Cumulatif YTD | Année courante vs précédente | Graphique ligne + aires | +| Répartition | Distribution par catégorie | Pie chart / Treemap | +| Tendances | Évolution sur période personnalisée | Graphique ligne | +| Budget vs Réel | Écarts budgétaires | Barres avec indicateurs | + +#### 5.2 Filtres et dimensions + +- Par source (compte) +- Par catégorie / sous-catégorie +- Par type de dépense +- Par période personnalisée +- Inclusion/exclusion des transferts + +--- + +## Modèle de données + +### Schéma SQL + +```sql +-- Sources d'import +CREATE TABLE import_sources ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + directory_path TEXT NOT NULL, + file_pattern TEXT DEFAULT '*.csv', + encoding TEXT DEFAULT 'utf-8', + delimiter TEXT DEFAULT ',', + has_header INTEGER DEFAULT 1, + date_format TEXT NOT NULL, + column_mapping TEXT NOT NULL, -- JSON + amount_rule TEXT NOT NULL, -- JSON + skip_lines INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Fichiers importés (pour éviter les doublons) +CREATE TABLE imported_files ( + id TEXT PRIMARY KEY, + source_id TEXT NOT NULL REFERENCES import_sources(id), + filename TEXT NOT NULL, + file_hash TEXT NOT NULL, -- SHA256 + row_count INTEGER NOT NULL, + imported_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(source_id, file_hash) +); + +-- Catégories +CREATE TABLE categories ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL CHECK(type IN ('recurring','occasional','special','transfer','income')), + parent_id TEXT REFERENCES categories(id), + color TEXT, + icon TEXT, + is_system INTEGER DEFAULT 0, + display_order INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Fournisseurs +CREATE TABLE suppliers ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + category_id TEXT NOT NULL REFERENCES categories(id), + priority INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Mots-clés +CREATE TABLE keywords ( + id TEXT PRIMARY KEY, + supplier_id TEXT NOT NULL REFERENCES suppliers(id) ON DELETE CASCADE, + keyword TEXT NOT NULL, + is_regex INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(supplier_id, keyword) +); + +-- Transactions +CREATE TABLE transactions ( + id TEXT PRIMARY KEY, + source_id TEXT NOT NULL REFERENCES import_sources(id), + imported_file_id TEXT NOT NULL REFERENCES imported_files(id), + transaction_date DATE NOT NULL, + description TEXT NOT NULL, + transaction_code TEXT, + debit_amount REAL DEFAULT 0, -- Note: SQLite REAL = 8 bytes IEEE float + credit_amount REAL DEFAULT 0, -- Pour précision crypto, stocker en INTEGER (satoshis/cents) + -- Alternative crypto-safe: + -- debit_amount_raw INTEGER DEFAULT 0, -- Montant × 10^8 + -- credit_amount_raw INTEGER DEFAULT 0, + currency TEXT DEFAULT 'CAD', + supplier_id TEXT REFERENCES suppliers(id), + category_id TEXT REFERENCES categories(id), + is_manually_categorized INTEGER DEFAULT 0, + raw_data TEXT, -- Ligne CSV originale + row_hash TEXT NOT NULL, -- Pour détection doublons + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(row_hash) +); + +-- Index pour performance +CREATE INDEX idx_transactions_date ON transactions(transaction_date); +CREATE INDEX idx_transactions_category ON transactions(category_id); +CREATE INDEX idx_transactions_month ON transactions(strftime('%Y-%m', transaction_date)); + +-- Écritures d'ajustement +CREATE TABLE adjustments ( + id TEXT PRIMARY KEY, + original_transaction_id TEXT NOT NULL REFERENCES transactions(id), + adjustment_date DATE NOT NULL, + effective_month TEXT NOT NULL, -- Format YYYY-MM + reason TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE adjustment_entries ( + id TEXT PRIMARY KEY, + adjustment_id TEXT NOT NULL REFERENCES adjustments(id) ON DELETE CASCADE, + category_id TEXT NOT NULL REFERENCES categories(id), + amount REAL NOT NULL +); + +-- Budget +CREATE TABLE budget_entries ( + id TEXT PRIMARY KEY, + category_id TEXT NOT NULL REFERENCES categories(id), + month TEXT NOT NULL, -- Format YYYY-MM + planned_amount REAL NOT NULL, + notes TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(category_id, month) +); + +-- Templates de budget +CREATE TABLE budget_templates ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE budget_template_entries ( + id TEXT PRIMARY KEY, + template_id TEXT NOT NULL REFERENCES budget_templates(id) ON DELETE CASCADE, + category_id TEXT NOT NULL REFERENCES categories(id), + planned_amount REAL NOT NULL +); + +-- Préférences utilisateur +CREATE TABLE user_preferences ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Valeurs par défaut suggérées: +-- 'language' : 'fr' | 'en' +-- 'fiscal_year_start_month' : '1' (janvier par défaut, '4' pour avril, etc.) +-- 'fiscal_year_start_day' : '1' +-- 'default_currency' : 'CAD' +-- 'date_format' : 'DD/MM/YYYY' | 'YYYY-MM-DD' | 'MM/DD/YYYY' +-- 'decimal_separator' : ',' | '.' +-- 'theme' : 'light' | 'dark' | 'system' +``` + +--- + +## Interface utilisateur + +### Navigation principale + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [Logo] Simpl-Résultat [⚙️ Paramètres] [?] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 📊 Tableau de bord │ +│ 📥 Importation │ +│ 💳 Transactions │ +│ 🏷️ Catégories │ +│ 📝 Ajustements │ +│ 💰 Budget │ +│ 📈 Rapports │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ [Version Free/Premium] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Écrans principaux + +#### Tableau de bord +- Résumé du mois courant +- Graphique dépenses vs budget +- Alertes (transactions non catégorisées, dépassements) +- Raccourcis actions fréquentes + +#### Importation +- Liste des sources configurées +- Bouton "Nouvelle source" +- Pour chaque source: dernière import, fichiers en attente +- Assistant d'import avec preview + +#### Transactions +- Liste paginée avec recherche/filtres +- Colonnes: Date, Description, Catégorie, Débit, Crédit, Source +- Actions: Modifier catégorie, Voir détails, Créer ajustement + +--- + +## Points faibles identifiés et recommandations + +### 🔴 Points critiques + +| # | Problème | Impact | Recommandation | +|---|----------|--------|----------------| +| 1 | **Pas de gestion des devises** | Impossible de consolider des comptes multi-devises | Ajouter un champ `currency` aux sources et transactions, prévoir conversion | +| 2 | **Pas de backup/export** | Perte de données si crash | Implémenter export SQLite + JSON, backup auto | +| 3 | **Pas de gestion des revenus** | Budget incomplet sans les entrées | Ajouter type "income" aux catégories, rapport balance | +| 4 | **Règle montant unique simpliste** | Banques ont des formats variés | Support regex, formules personnalisées | +| 5 | **Pas de gestion de l'encodage** | CSV corrompus si mauvais encoding | Détection auto (chardet) + sélection manuelle | + +### 🟠 Points importants + +| # | Problème | Impact | Recommandation | +|---|----------|--------|----------------| +| 6 | **Pas de détection de doublons explicite** | Imports multiples du même fichier | Hash des lignes, table des fichiers importés | +| 7 | **Matching mots-clés basique** | Faux positifs/négatifs fréquents | Priorités, regex, matching partiel pondéré | +| 8 | **Pas de gestion des dates de valeur** | Date opération ≠ date valeur | Ajouter champ optionnel `value_date` | +| 9 | **Forfait freemium flou** | Confusion utilisateur | Détailler précisément les limitations | +| 10 | **Pas de recherche/filtre** | Difficile de retrouver une transaction | Recherche full-text, filtres avancés | + +### 🟡 Améliorations suggérées + +| # | Amélioration | Bénéfice | +|---|--------------|----------| +| 11 | **Import récurrent automatisé** | Surveiller les dossiers, import auto | +| 12 | **Règles de catégorisation avancées** | Combinaison montant + description + source | +| 13 | **Mode sombre** | Confort utilisateur | +| 14 | **Raccourcis clavier** | Productivité power users | +| 15 | **Export PDF des rapports** | Partage/archivage facile | +| 16 | **Graphiques interactifs** | Drill-down par clic | +| 17 | **Objectifs d'épargne** | Fonctionnalité motivante | +| 18 | **Tags personnalisés** | Catégorisation orthogonale (ex: "Vacances 2024") | +| 19 | **Notes sur transactions** | Contexte personnel | +| 20 | **Historique des modifications** | Audit trail | + +### 🔵 Décisions prises + +| Question | Décision | Notes | +|----------|----------|-------| +| **Multilinguisme** | ✅ Français + Anglais | i18n dès le départ | +| **Période fiscale** | ✅ Configurable | Année calendaire par défaut, option personnalisée | +| **Précision décimale** | ✅ 8 décimales | Support crypto-friendly | +| **Pièces jointes** | ❌ V2 | Hors scope pour MVP | +| **Licence freemium** | ✅ Code d'activation + serveur | Validation en ligne | + +### 🔵 Questions encore ouvertes + +1. **Synchronisation multi-appareils?** Local seulement ou option sync future? +2. **Comptes joints?** Gestion multi-utilisateurs sur mêmes données? +3. **API/Plugins?** Extensibilité future? +4. **Format date système?** Respect des préférences OS ou configurable? +5. **Transactions programmées?** Dépenses récurrentes futures? + +--- + +## Considérations techniques + +### Internationalisation (i18n) + +Structure recommandée pour le support FR/EN : + +``` +src/ +└── locales/ + ├── fr.json + ├── en.json + └── index.ts +``` + +```json +// fr.json +{ + "app.name": "Simpl-Résultat", + "nav.dashboard": "Tableau de bord", + "nav.import": "Importation", + "nav.transactions": "Transactions", + "transaction.date": "Date", + "transaction.description": "Description", + "transaction.debit": "Débit", + "transaction.credit": "Crédit", + "category.other": "Autres dépenses", + "budget.planned": "Prévu", + "budget.actual": "Réel", + "budget.remaining": "Restant" +} +``` + +```json +// en.json +{ + "app.name": "Simpl-Result", + "nav.dashboard": "Dashboard", + "nav.import": "Import", + "nav.transactions": "Transactions", + "transaction.date": "Date", + "transaction.description": "Description", + "transaction.debit": "Debit", + "transaction.credit": "Credit", + "category.other": "Other expenses", + "budget.planned": "Planned", + "budget.actual": "Actual", + "budget.remaining": "Remaining" +} +``` + +Librairies recommandées : +- **Svelte** : svelte-i18n +- **React** : react-i18next +- **Python/Flet** : gettext ou babel + +### Architecture de licence Freemium + +``` +┌─────────────────┐ ┌──────────────────────┐ +│ Application │────▶│ Serveur licence │ +│ (locale) │◀────│ (ton serveur) │ +└─────────────────┘ └──────────────────────┘ + │ │ + │ POST /api/activate │ + │ {license_key, hw_id} │ + │ │ + │ Response: │ + │ {valid, features, │ + │ expires_at} │ + │ │ + ▼ ▼ +┌─────────────────┐ ┌──────────────────────┐ +│ License cache │ │ Base de données │ +│ (local, chiffré)│ │ - licenses │ +│ │ │ - activations │ +└─────────────────┘ └──────────────────────┘ +``` + +#### Schéma serveur (PostgreSQL/SQLite) + +```sql +-- Licences générées à l'achat +CREATE TABLE licenses ( + id UUID PRIMARY KEY, + license_key VARCHAR(32) UNIQUE NOT NULL, -- Ex: XXXX-XXXX-XXXX-XXXX + email VARCHAR(255), + plan VARCHAR(20) NOT NULL, -- 'premium', 'lifetime' + max_activations INT DEFAULT 3, + created_at TIMESTAMP DEFAULT NOW(), + expires_at TIMESTAMP, -- NULL = lifetime + is_revoked BOOLEAN DEFAULT FALSE +); + +-- Activations (machines) +CREATE TABLE activations ( + id UUID PRIMARY KEY, + license_id UUID REFERENCES licenses(id), + hardware_id VARCHAR(64) NOT NULL, -- Hash unique de la machine + app_version VARCHAR(20), + os_info VARCHAR(100), + activated_at TIMESTAMP DEFAULT NOW(), + last_seen_at TIMESTAMP DEFAULT NOW(), + is_active BOOLEAN DEFAULT TRUE, + UNIQUE(license_id, hardware_id) +); +``` + +#### Génération du Hardware ID + +```typescript +// Côté client - génère un ID unique par machine +import { machineIdSync } from 'node-machine-id'; +import * as crypto from 'crypto'; + +function getHardwareId(): string { + const machineId = machineIdSync(); + // Hash pour anonymisation + return crypto + .createHash('sha256') + .update(machineId + 'simpl-resultat-salt') + .digest('hex') + .substring(0, 32); +} +``` + +#### API Serveur (exemple Node/Express) + +```typescript +// POST /api/license/activate +app.post('/api/license/activate', async (req, res) => { + const { licenseKey, hardwareId, appVersion, osInfo } = req.body; + + // 1. Vérifier la licence + const license = await db.licenses.findByKey(licenseKey); + if (!license || license.is_revoked) { + return res.status(400).json({ error: 'invalid_license' }); + } + + // 2. Vérifier expiration + if (license.expires_at && license.expires_at < new Date()) { + return res.status(400).json({ error: 'license_expired' }); + } + + // 3. Vérifier nombre d'activations + const activeCount = await db.activations.countActive(license.id); + const existing = await db.activations.findByHardware(license.id, hardwareId); + + if (!existing && activeCount >= license.max_activations) { + return res.status(400).json({ + error: 'max_activations_reached', + max: license.max_activations + }); + } + + // 4. Créer ou mettre à jour l'activation + await db.activations.upsert({ + licenseId: license.id, + hardwareId, + appVersion, + osInfo, + lastSeenAt: new Date() + }); + + // 5. Retourner le token de session + const token = generateSignedToken({ + licenseId: license.id, + plan: license.plan, + features: getPlanFeatures(license.plan), + expiresAt: license.expires_at + }); + + res.json({ + valid: true, + token, + plan: license.plan, + features: getPlanFeatures(license.plan) + }); +}); + +function getPlanFeatures(plan: string): string[] { + const features = { + free: ['import', 'basic_categories', 'monthly_report'], + premium: ['import', 'custom_categories', 'adjustments', + 'all_reports', 'export_pdf', 'budget_templates'] + }; + return features[plan] || features.free; +} +``` + +#### Côté client - Vérification + +```typescript +class LicenseManager { + private cache: LicenseCache; + + async checkLicense(): Promise { + // 1. Vérifier le cache local d'abord + const cached = await this.cache.get(); + if (cached && !this.isExpired(cached) && !this.needsRevalidation(cached)) { + return cached; + } + + // 2. Sinon, valider en ligne + try { + const response = await this.validateOnline(); + await this.cache.set(response); + return response; + } catch (e) { + // 3. Mode hors-ligne : grace period de 7 jours + if (cached && this.withinGracePeriod(cached)) { + return { ...cached, offline: true }; + } + return { valid: false, plan: 'free' }; + } + } + + private needsRevalidation(cached: LicenseCache): boolean { + // Revalider toutes les 72h + const lastCheck = new Date(cached.lastValidated); + const hoursSince = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60); + return hoursSince > 72; + } + + private withinGracePeriod(cached: LicenseCache): boolean { + const lastCheck = new Date(cached.lastValidated); + const daysSince = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60 * 24); + return daysSince <= 7; + } +} +``` + +#### Sécurité + +| Risque | Mitigation | +|--------|------------| +| Clé partagée | Limite d'activations (3 machines) | +| Crack local | Fonctionnalités premium côté validation, pas juste UI | +| Man-in-the-middle | HTTPS + signature des tokens | +| Reverse engineering | Obfuscation (limité mais aide) | +| Serveur down | Grace period 7 jours + mode dégradé | + +**Note réaliste** : Aucune protection n'est parfaite pour une app desktop. L'objectif est de rendre le crack plus difficile que l'achat pour l'utilisateur moyen, pas d'être inviolable. + +### Précision décimale (8 décimales) + +Pour supporter les cryptomonnaies (Bitcoin = 8 décimales, certains tokens = plus), deux approches : + +**Approche 1 : Stockage INTEGER (recommandé)** + +```typescript +// Stocker les montants en plus petite unité (satoshis, cents × 10^6) +const DECIMAL_PLACES = 8; +const MULTIPLIER = 10 ** DECIMAL_PLACES; // 100_000_000 + +function toStorageAmount(displayAmount: number): bigint { + return BigInt(Math.round(displayAmount * MULTIPLIER)); +} + +function toDisplayAmount(storageAmount: bigint): number { + return Number(storageAmount) / MULTIPLIER; +} + +// Exemple +const btcAmount = 0.00123456; +const stored = toStorageAmount(btcAmount); // 123456n +const display = toDisplayAmount(stored); // 0.00123456 +``` + +Avantages : +- Pas de floating point errors (0.1 + 0.2 ≠ 0.3) +- Calculs exacts +- SQLite INTEGER = 64 bits signés = jusqu'à ~92 milliards en unités de base + +**Approche 2 : TEXT avec Decimal.js** + +```typescript +import Decimal from 'decimal.js'; + +Decimal.set({ precision: 20 }); + +const amount = new Decimal('0.12345678'); +const total = amount.plus('0.00000001'); // Exact: 0.12345679 + +// Stockage en TEXT dans SQLite +db.run('INSERT INTO transactions (amount_text) VALUES (?)', + [amount.toString()]); +``` + +**Formatage d'affichage** + +```typescript +function formatAmount(amount: number, currency: string): string { + const decimals = currency === 'BTC' ? 8 + : currency === 'ETH' ? 8 + : 2; // CAD, USD, EUR + + return new Intl.NumberFormat('fr-CA', { + minimumFractionDigits: decimals > 2 ? 2 : decimals, + maximumFractionDigits: decimals, + }).format(amount); +} + +// 0.00123456 BTC → "0,00123456" +// 1234.50 CAD → "1 234,50" +``` + +### Performance + +```typescript +// Indexes recommandés pour queries fréquentes +const CRITICAL_QUERIES = { + // Dashboard: dépenses du mois courant par catégorie + monthlyByCategory: ` + SELECT category_id, SUM(debit_amount) as total + FROM transactions + WHERE strftime('%Y-%m', transaction_date) = ? + GROUP BY category_id + `, + // Budget vs Réel + budgetComparison: ` + SELECT + b.category_id, + b.planned_amount, + COALESCE(SUM(t.debit_amount), 0) as actual_amount + FROM budget_entries b + LEFT JOIN transactions t + ON t.category_id = b.category_id + AND strftime('%Y-%m', t.transaction_date) = b.month + WHERE b.month = ? + GROUP BY b.category_id + ` +}; +``` + +### Sécurité + +| Risque | Mitigation | +|--------|------------| +| Données sensibles en clair | Chiffrement SQLite optionnel (SQLCipher) | +| Injection CSV | Sanitization des imports | +| Accès fichiers arbitraires | Sandbox des chemins d'import | + +### Tests + +``` +tests/ +├── unit/ +│ ├── services/ +│ │ ├── import.service.test.ts +│ │ └── categorization.service.test.ts +│ └── utils/ +│ └── csv-parser.test.ts +├── integration/ +│ ├── database.test.ts +│ └── import-flow.test.ts +└── e2e/ + ├── import-wizard.test.ts + └── reporting.test.ts +``` + +--- + +## Plan de développement suggéré + +### Phase 1 - MVP (4-6 semaines) + +**Objectif**: Import et visualisation basique + +- [ ] Setup projet (Tauri + React/Svelte) +- [ ] Schéma BDD SQLite +- [ ] Import CSV basique (1 format fixe) +- [ ] Liste transactions +- [ ] Catégories par défaut +- [ ] Matching mots-clés simple +- [ ] Rapport mensuel simple (tableau) + +### Phase 2 - Core (4-6 semaines) + +**Objectif**: Fonctionnalités complètes freemium + +- [ ] Configuration sources multiples +- [ ] Assistant mapping colonnes +- [ ] Gestion catégories/fournisseurs +- [ ] Interface transactions non-matchées +- [ ] Budget mensuel +- [ ] 3 rapports de base +- [ ] Graphiques simples + +### Phase 3 - Premium (4-6 semaines) + +**Objectif**: Différenciation payant + +- [ ] Écritures d'ajustement +- [ ] Personnalisation catégories +- [ ] Rapports avancés +- [ ] Export PDF +- [ ] Graphiques interactifs +- [ ] Templates budget + +### Phase 4 - Polish (2-4 semaines) + +**Objectif**: Production-ready + +- [ ] Tests complets +- [ ] Installateurs (Windows, macOS, Linux) +- [ ] Documentation utilisateur +- [ ] Système de licence +- [ ] Backup/restore +- [ ] Performance tuning + +--- + +## Annexe: Catégories par défaut suggérées + +```json +{ + "categories": [ + { + "name": "Logement", + "type": "recurring", + "subcategories": ["Loyer/Hypothèque", "Électricité", "Chauffage", "Internet", "Assurance habitation", "Taxes"] + }, + { + "name": "Transport", + "type": "recurring", + "subcategories": ["Essence", "Transport en commun", "Assurance auto", "Entretien véhicule", "Stationnement"] + }, + { + "name": "Alimentation", + "type": "recurring", + "subcategories": ["Épicerie", "Restaurant", "Livraison", "Café"] + }, + { + "name": "Santé", + "type": "occasional", + "subcategories": ["Pharmacie", "Médecin", "Dentiste", "Optométriste", "Assurance santé"] + }, + { + "name": "Loisirs", + "type": "occasional", + "subcategories": ["Sport", "Culture", "Streaming", "Jeux", "Hobbies"] + }, + { + "name": "Shopping", + "type": "occasional", + "subcategories": ["Vêtements", "Électronique", "Maison", "Cadeaux"] + }, + { + "name": "Services", + "type": "recurring", + "subcategories": ["Téléphone", "Abonnements", "Banque"] + }, + { + "name": "Transferts", + "type": "transfer", + "subcategories": ["Entre comptes", "Épargne", "Investissement"] + }, + { + "name": "Revenus", + "type": "income", + "subcategories": ["Salaire", "Freelance", "Remboursements", "Autres revenus"] + }, + { + "name": "Autres dépenses", + "type": "occasional", + "subcategories": [] + } + ] +} +```