Simpl-Resultat/specification_v2_analyse.md
2026-02-06 21:19:09 -05:00

36 KiB
Raw Permalink Blame History

Simpl-Résultat - Spécification Détaillée v2

Table des matières

  1. Résumé exécutif
  2. Description générale enrichie
  3. Architecture proposée
  4. Spécifications fonctionnelles détaillées
  5. Modèle de données
  6. Interface utilisateur
  7. Points faibles identifiés et recommandations
  8. Considérations techniques
  9. 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

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

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

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

interface BudgetEntry {
  id: string;
  categoryId: string;
  month: string;                  // "YYYY-MM"
  plannedAmount: number;
  notes?: string;
}

interface BudgetTemplate {
  id: string;
  name: string;
  entries: Omit<BudgetEntry, 'id' | 'month'>[];
}

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

-- 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
// 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"
}
// 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)

-- 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

// 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)

// 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

class LicenseManager {
  private cache: LicenseCache;
  
  async checkLicense(): Promise<LicenseStatus> {
    // 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é)

// 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

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

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

// 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

{
  "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": []
    }
  ]
}