LCOV - code coverage report
Current view: top level - _classes/math - budget_prediction.dart Coverage Total Hit
Test: lcov.info Lines: 0.0 % 52 0
Test Date: 2026-11-14 15:03:01 Functions: - 0 0

            Line data    Source code
       1              : // Copyright 2025 The terCAD team. All rights reserved.
       2              : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
       3              : 
       4              : import 'package:app_finance/_classes/structure/bill_app_data.dart';
       5              : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
       6              : 
       7              : class BudgetPrediction {
       8              :   final Map<String, Map<String, int>> _descriptionPatterns = {};
       9              :   final Map<String, Map<String, int>> _accountCategories = {};
      10              :   final Map<String, Map<String, int>> _amountRanges = {};
      11              : 
      12            0 :   BudgetPrediction();
      13              : 
      14            0 :   void add(BillAppData item) {
      15            0 :     final category = item.category;
      16            0 :     _learnDescriptionPatterns(item.title, category);
      17            0 :     _learnAccountCategories(item.account, category);
      18            0 :     _learnAmountRanges(item.details, item.currency, category);
      19              :   }
      20              : 
      21            0 :   void _learnDescriptionPatterns(String? description, String category) {
      22            0 :     if (description == null || description.isEmpty) return;
      23              : 
      24            0 :     final words = description.toLowerCase().split(RegExp(r'\W+'));
      25            0 :     _descriptionPatterns.putIfAbsent(category, () => {});
      26              : 
      27            0 :     for (final word in words) {
      28            0 :       if (word.length > 2) {
      29            0 :         _descriptionPatterns[category]![word] = (_descriptionPatterns[category]![word] ?? 0) + 1;
      30              :       }
      31              :     }
      32              :   }
      33              : 
      34            0 :   void _learnAccountCategories(String? account, String category) {
      35              :     if (account == null) return;
      36              : 
      37            0 :     _accountCategories.putIfAbsent(category, () => {});
      38            0 :     _accountCategories[category]![account] = (_accountCategories[category]![account] ?? 0) + 1;
      39              :   }
      40              : 
      41            0 :   void _learnAmountRanges(double? amount, Currency? currency, String category) {
      42              :     if (amount == null) return;
      43              : 
      44            0 :     final range = _getAmountRange(amount, currency);
      45            0 :     _amountRanges.putIfAbsent(category, () => {});
      46            0 :     _amountRanges[category]![range] = (_amountRanges[category]![range] ?? 0) + 1;
      47              :   }
      48              : 
      49            0 :   String _getAmountRange(double amount, Currency? currency) {
      50            0 :     return amount.toStringAsFixed(6).length.toString() + (currency?.code ?? '');
      51              :   }
      52              : 
      53            0 :   List<String> predict(BillAppData item) {
      54            0 :     final scores = <String, double>{};
      55            0 :     _scoreByDescription(item.title, scores);
      56            0 :     _scoreByAccount(item.account, scores);
      57            0 :     _scoreByAmount(item.details, item.currency, scores);
      58              : 
      59            0 :     if (scores.isEmpty) return [];
      60              : 
      61            0 :     final sortedEntries = scores.entries.toList()..sort((a, b) => b.value.compareTo(a.value));
      62              : 
      63            0 :     return sortedEntries.take(3).map((e) => e.key).toList();
      64              :   }
      65              : 
      66            0 :   void _scoreByDescription(String? description, Map<String, double> scores) {
      67            0 :     if (description == null || description.isEmpty) return;
      68              : 
      69            0 :     final words = description.toLowerCase().split(RegExp(r'\W+'));
      70              : 
      71            0 :     for (final category in _descriptionPatterns.keys) {
      72              :       double score = 0;
      73            0 :       int totalWords = _descriptionPatterns[category]!.values.fold(0, (a, b) => a + b);
      74              : 
      75            0 :       for (final word in words) {
      76            0 :         if (word.length > 2 && _descriptionPatterns[category]!.containsKey(word)) {
      77            0 :           score += _descriptionPatterns[category]![word]! / totalWords;
      78              :         }
      79              :       }
      80              : 
      81            0 :       scores[category] = (scores[category] ?? 0) + score * 0.5; // 50% weight
      82              :     }
      83              :   }
      84              : 
      85            0 :   void _scoreByAccount(String? account, Map<String, double> scores) {
      86              :     if (account == null) return;
      87              : 
      88            0 :     for (final category in _accountCategories.keys) {
      89            0 :       if (_accountCategories[category]!.containsKey(account)) {
      90            0 :         final occurrences = _accountCategories[category]![account]!;
      91            0 :         final totalForCategory = _accountCategories[category]!.values.fold(0, (a, b) => a + b);
      92            0 :         scores[category] = (scores[category] ?? 0) + (occurrences / totalForCategory) * 0.3; // 30% weight
      93              :       }
      94              :     }
      95              :   }
      96              : 
      97            0 :   void _scoreByAmount(double? amount, Currency? currency, Map<String, double> scores) {
      98              :     if (amount == null) return;
      99              : 
     100            0 :     final range = _getAmountRange(amount, currency);
     101              : 
     102            0 :     for (final category in _amountRanges.keys) {
     103            0 :       if (_amountRanges[category]!.containsKey(range)) {
     104            0 :         final occurrences = _amountRanges[category]![range]!;
     105            0 :         final totalForCategory = _amountRanges[category]!.values.fold(0, (a, b) => a + b);
     106            0 :         scores[category] = (scores[category] ?? 0) + (occurrences / totalForCategory) * 0.2; // 20% weight
     107              :       }
     108              :     }
     109              :   }
     110              : }
        

Generated by: LCOV version 2.0-1