MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 6 / 0 / 6
Functions: 100.0% 6 / 0 / 6
Branches: 100.0% 6 / 0 / 6

data/FolderEvidence.h
Line Branch Exec Source
1 #pragma once
2 #include <QHash>
3 #include <QList>
4 #include <QString>
5 #include <QStringList>
6
7 struct MailHeader;
8
9 // Sprint 105 S105.3.1/S105.3.2 (D-3): where a mail belongs, decided by the
10 // strongest *evidence* — not by one opaque score. In order:
11 // 1. Thread — an earlier mail of the same thread is filed in folder F.
12 // 2. List — mails of this List-Id were filed to F.
13 // 3. Sender — the sender's filings, weighted by recency (half-life), so a
14 // sender who moved to a new folder follows within a few mails.
15 // 4. Alias — mails to this recipient address went to F (only when that
16 // address concentrates on one folder: the user's main
17 // address, spread over every folder, decides nothing).
18 // 5. Subject — a small word model (Naive Bayes), only when 1–4 are silent.
19 // The confidence of a suggestion is the measured precision of its evidence
20 // kind at its strength (Calibration, filled by the replay over the user's
21 // own filing history), never a softmax. Pure and deterministic.
22 class FolderEvidence {
23 public:
24 enum class Kind { None = 0, Thread = 1, List = 2, Sender = 3, Alias = 4, Subject = 5 };
25 struct Mail {
26 QString messageId; // canonical, without <>
27 QStringList threadIds; // In-Reply-To and References
28 QString listId;
29 QString sender; // normalized address
30 QStringList recipients; // normalized To and Cc addresses
31 QString subject;
32 qint64 date = 0; // seconds since the epoch
33 };
34 struct Candidate {
35 QString folder;
36 Kind kind = Kind::None;
37 double strength = 0; // 0..1: share of the evidence for this folder
38 int support = 0; // filings behind the evidence
39 int total = 0;
40 };
41 struct Options {
42 double senderHalfLifeDays = 180;
43 int maxSenderHistory = 64;
44 };
45
46 529 FolderEvidence() = default;
47 explicit FolderEvidence(Options options) : m_options(options) {}
48 static Mail fromHeader(const MailHeader &header);
49
50 // A mail filed in `folder` (training, in date order).
51 void learn(const Mail &mail, const QString &folder);
52 // S105.3.3: the user moved a mail somewhere else than suggested —
53 // the latest choice wins for the next mail of that sender/list.
54 void correct(const Mail &mail, const QString &chosenFolder);
55 // A folder was renamed or removed (renameFolderData/resetFolder).
56 void renameFolder(const QString &from, const QString &to);
57 void removeFolder(const QString &folder);
58
59 // Candidates of distinct folders, best first (kind order, then strength).
60 QList<Candidate> rank(const Mail &mail) const;
61 434 int filings() const { return m_filings; }
62
63 private:
64 struct Placement {
65 qint64 date = 0;
66 QString folder;
67 };
68 Options m_options;
69 int m_filings = 0;
70 QHash<QString, QString> m_threadFolder; // message-id -> folder
71 QHash<QString, QHash<QString, int>> m_listFolders; // list-id -> folder -> count
72 QHash<QString, QList<Placement>> m_senderHistory; // sender -> newest last
73 QHash<QString, QHash<QString, int>> m_aliasFolders; // recipient -> folder -> count
74 QHash<QString, QHash<QString, int>> m_wordFolders; // word -> folder -> count
75 QHash<QString, int> m_folderDocs, m_folderWords;
76 QHash<QString, QString> m_senderOverride, m_listOverride; // S105.3.3
77 QList<Candidate> subjectCandidates(const Mail &mail) const;
78 };
79
80 // S105.3.2: measured precision per evidence kind and strength (10 bins),
81 // with a Laplace prior; filled by the replay, stored with the model.
82 class FolderCalibration {
83 public:
84 void record(FolderEvidence::Kind kind, double strength, bool correct);
85 // Precision estimate of a suggestion of this kind and strength (0..1).
86 double confidence(FolderEvidence::Kind kind, double strength) const;
87 QString serialize() const;
88 static FolderCalibration deserialize(const QString &text);
89 1 bool isEmpty() const { return m_bins.isEmpty(); }
90
91 private:
92 static int bin(double strength);
93 QHash<int, QPair<int, int>> m_bins; // kind * 16 + bin -> (correct, total)
94 };
95
96 // S105.0.5: the filing history replayed in date order — every mail is
97 // predicted from the evidence of earlier mails only, then learned.
98 // Aggregates only (no names, no addresses): what the diagnostic report and
99 // the tests print.
100 struct FolderReplayMetrics {
101 int mails = 0; // filed mails replayed
102 int shown = 0; // a suggestion at or above the display threshold
103 int shownCorrect = 0;
104 int top2Correct = 0; // the right folder among the first two candidates
105 int answered = 0; // any candidate at all
106 QList<QPair<int, int>> reliability; // 10 confidence bins: (correct, total)
107
2/2
✓ Branch 2 → 3 taken 68 times.
✓ Branch 2 → 4 taken 204 times.
272 double coverage() const { return mails ? double(shown) / mails : 0; }
108
2/2
✓ Branch 2 → 3 taken 50 times.
✓ Branch 2 → 4 taken 221 times.
271 double precision() const { return shown ? double(shownCorrect) / shown : 0; }
109
2/2
✓ Branch 2 → 3 taken 69 times.
✓ Branch 2 → 4 taken 204 times.
273 double top2() const { return mails ? double(top2Correct) / mails : 0; }
110 QString report() const;
111 // S105.3.2: the lowest confidence bin bound whose suggestions — together
112 // with all more confident ones — were right at least `precision` of the
113 // time in this replay (1.0 = show nothing). The display threshold.
114 double thresholdFor(double precision) const;
115 // Coverage and precision had `threshold` been the display threshold.
116 QPair<double, double> at(double threshold) const;
117 };
118
119 struct FolderReplayItem {
120 FolderEvidence::Mail mail;
121 QString folder;
122 };
123
124 // Replays `items` (sorted by date) through a fresh FolderEvidence and fills
125 // `calibration`; the confidence of each prediction is taken from the
126 // calibration *as learned so far* (an honest out-of-sample estimate).
127 FolderReplayMetrics replayFolderEvidence(const QList<FolderReplayItem> &items, double threshold,
128 FolderCalibration *calibration, FolderEvidence *trained = nullptr);
129