FOSSredder 0.5.0
Loading...
Searching...
No Matches
AnalysisFilter.cpp
Go to the documentation of this file.
1
5
6#include "AnalysisFilter.h"
7
12
13#include <algorithm>
14#include <memory>
15#include <optional>
16#include <sstream>
17#include <string_view>
18
20
21namespace {
22
23using core::domain::Transaction;
24using core::domain::catalog::WorkspaceCatalog;
27
28enum class AnalysisFilterOperator {
29 GreaterEqual,
30 LessEqual,
31 NotEqual,
32 Greater,
33 Less,
34 Equal
35};
36
37enum class AnalysisDateField { BookingDate, Valuta };
38
39struct AnalysisFilterClause {
40 std::string key;
41 std::string value;
42 AnalysisFilterOperator op;
43};
44
45std::string cleanValue(const std::string &value);
46
47std::vector<std::string> splitList(const std::string &value) {
48 std::vector<std::string> wanted;
49 std::string current;
50 for (const char ch : cleanValue(value)) {
53 const auto token = trim(current);
54 if (!token.empty())
55 wanted.push_back(token);
56 current.clear();
57 continue;
58 }
59
60 current.push_back(ch);
61 }
62
63 const auto tail = trim(current);
64 if (!tail.empty())
65 wanted.push_back(tail);
66 return wanted;
67}
68
69std::string cleanValue(const std::string &value) {
70 auto cleaned = trim(value);
71 if (cleaned.size() >= 2 &&
72 ((cleaned.front() == '"' && cleaned.back() == '"') ||
73 (cleaned.front() == '\'' && cleaned.back() == '\''))) {
74 cleaned = cleaned.substr(1, cleaned.size() - 2);
75 }
76 return trim(cleaned);
77}
78
79int dateToInt(const std::string &value) {
80 if (value.empty())
81 return 0;
82
83 std::string cleaned = cleanValue(value);
84 if (cleaned.find('-') != std::string::npos) {
85 std::string digits = cleaned;
86 digits.erase(std::remove(digits.begin(), digits.end(), '-'), digits.end());
87 if (digits.size() == 8) {
88 try {
89 return std::stoi(digits);
90 } catch (...) {
91 return 0;
92 }
93 }
94 return 0;
95 }
96
98 std::string::npos) {
99 std::vector<std::string> parts;
100 std::istringstream stream(cleaned);
101 std::string token;
102 while (std::getline(stream, token,
104 parts.push_back(trim(token));
105 }
106
107 if (parts.size() == 3) {
108 std::string dd = parts[0];
109 std::string mm = parts[1];
110 std::string yyyy = parts[2];
111 if (yyyy.size() == 4 && !dd.empty() && !mm.empty()) {
112 if (dd.size() == 1)
113 dd = std::string("0") + dd;
114 if (mm.size() == 1)
115 mm = std::string("0") + mm;
116 const std::string out = yyyy + mm + dd;
117 try {
118 return std::stoi(out);
119 } catch (...) {
120 return 0;
121 }
122 }
123 }
124 return 0;
125 }
126
127 std::string digits;
128 for (const char ch : cleaned) {
129 if (std::isdigit(static_cast<unsigned char>(ch)))
130 digits.push_back(ch);
131 }
132 if (digits.size() == 8) {
133 try {
134 return std::stoi(digits);
135 } catch (...) {
136 return 0;
137 }
138 }
139 return 0;
140}
141
142bool compareInt(AnalysisFilterOperator op, int lhs, int rhs) {
143 switch (op) {
144 case AnalysisFilterOperator::GreaterEqual:
145 return lhs >= rhs;
146 case AnalysisFilterOperator::LessEqual:
147 return lhs <= rhs;
148 case AnalysisFilterOperator::Greater:
149 return lhs > rhs;
150 case AnalysisFilterOperator::Less:
151 return lhs < rhs;
152 case AnalysisFilterOperator::Equal:
153 return lhs == rhs;
154 case AnalysisFilterOperator::NotEqual:
155 return lhs != rhs;
156 }
157 return false;
158}
159
160bool compareDouble(AnalysisFilterOperator op, double lhs, double rhs) {
161 switch (op) {
162 case AnalysisFilterOperator::GreaterEqual:
163 return lhs >= rhs;
164 case AnalysisFilterOperator::LessEqual:
165 return lhs <= rhs;
166 case AnalysisFilterOperator::Greater:
167 return lhs > rhs;
168 case AnalysisFilterOperator::Less:
169 return lhs < rhs;
170 case AnalysisFilterOperator::Equal:
171 return lhs == rhs;
172 case AnalysisFilterOperator::NotEqual:
173 return lhs != rhs;
174 }
175 return false;
176}
177
178std::optional<AnalysisFilterClause> parseClause(const std::string &token) {
179 struct OperatorSpec {
180 std::string_view token;
181 AnalysisFilterOperator op;
182 };
183
184 static const OperatorSpec operators[] = {
186 AnalysisFilterOperator::GreaterEqual},
188 AnalysisFilterOperator::LessEqual},
190 AnalysisFilterOperator::NotEqual},
192 AnalysisFilterOperator::Greater},
194 AnalysisFilterOperator::Less},
196 AnalysisFilterOperator::Equal},
197 };
198
199 for (const auto &spec : operators) {
200 const auto pos = token.find(spec.token);
201 if (pos == std::string::npos)
202 continue;
203
204 return AnalysisFilterClause{
205 trim(token.substr(0, pos)),
206 trim(token.substr(pos + spec.token.size())),
207 spec.op,
208 };
209 }
210
211 return std::nullopt;
212}
213
214std::vector<std::string> splitContractTypes(const std::string &value) {
215 std::vector<std::string> wanted;
216 std::string current;
217 for (const char ch : lowerAscii(cleanValue(value))) {
220 const auto token = trim(current);
221 if (!token.empty())
222 wanted.push_back(token);
223 current.clear();
224 continue;
225 }
226
227 current.push_back(ch);
228 }
229
230 const auto tail = trim(current);
231 if (!tail.empty())
232 wanted.push_back(tail);
233 return wanted;
234}
235
236AnalysisDateField parseDateField(const std::string &value) {
237 return lowerAscii(cleanValue(value)) == "valuta"
238 ? AnalysisDateField::Valuta
239 : AnalysisDateField::BookingDate;
240}
241
242void addDatePredicate(core::application::analysis::AnalysisFilter &filter,
243 const AnalysisFilterClause &clause,
244 AnalysisDateField dateField) {
245 const int target = dateToInt(clause.value);
246 filter.addPredicate([target, op = clause.op, dateField](
247 const std::shared_ptr<Transaction> &transaction,
248 const WorkspaceCatalog &) {
249 if (!transaction)
250 return false;
251 const std::string sourceDate = dateField == AnalysisDateField::Valuta
252 ? transaction->valuta()
253 : transaction->bookingDate();
254 return compareInt(op, dateToInt(sourceDate), target);
255 });
256}
257
258void addAmountPredicate(core::application::analysis::AnalysisFilter &filter,
259 const AnalysisFilterClause &clause) {
260 double value = 0.0;
261 try {
262 value = std::stod(clause.value);
263 } catch (...) {
264 value = 0.0;
265 }
266
267 filter.addPredicate(
268 [value, op = clause.op](const std::shared_ptr<Transaction> &transaction,
269 const WorkspaceCatalog &) {
270 return transaction && compareDouble(op, transaction->amount(), value);
271 });
272}
273
274void addContractTypePredicate(
275 core::application::analysis::AnalysisFilter &filter,
276 const AnalysisFilterClause &clause) {
277 const auto wanted = splitContractTypes(clause.value);
278 if (wanted.empty())
279 return;
280
281 filter.addPredicate([wanted](const std::shared_ptr<Transaction> &transaction,
282 const WorkspaceCatalog &state) {
283 if (!transaction)
284 return false;
285
286 const bool allowUnassigned =
287 std::find(wanted.begin(), wanted.end(),
289 wanted.end();
290 if (transaction->contractId().empty())
291 return allowUnassigned;
292
293 const std::string contractId =
294 lowerAscii(cleanValue(transaction->contractId()));
295 for (const auto &contract : state.contracts()) {
296 if (!contract)
297 continue;
298 if (lowerAscii(cleanValue(contract->id())) != contractId)
299 continue;
300
301 const std::string contractType = lowerAscii(cleanValue(contract->type()));
302 return std::find(wanted.begin(), wanted.end(), contractType) !=
303 wanted.end();
304 }
305
306 return false;
307 });
308}
309
310void addPropertyPredicate(core::application::analysis::AnalysisFilter &filter,
311 const AnalysisFilterClause &clause) {
312 const auto wanted = splitList(clause.value);
313 if (wanted.empty())
314 return;
315
316 filter.addPredicate([wanted](const std::shared_ptr<Transaction> &transaction,
317 const WorkspaceCatalog &state) {
318 if (!transaction)
319 return false;
320 const bool allowUnassigned =
321 std::find(wanted.begin(), wanted.end(),
323 wanted.end();
324 bool hasAnyProperty = false;
325
326 for (const auto &propertyId : wanted) {
327 if (std::find(transaction->propertyIds().begin(),
328 transaction->propertyIds().end(),
329 propertyId) != transaction->propertyIds().end()) {
330 return true;
331 }
332 }
333 if (!transaction->propertyIds().empty()) {
334 hasAnyProperty = true;
335 }
336
337 if (transaction->contractId().empty())
338 return allowUnassigned && !hasAnyProperty;
339 const std::string contractId = cleanValue(transaction->contractId());
340 for (const auto &contract : state.contracts()) {
341 if (!contract)
342 continue;
343 if (cleanValue(contract->id()) != contractId)
344 continue;
345
346 if (!contract->propertyIds().empty()) {
347 hasAnyProperty = true;
348 }
349 for (const auto &propertyId : wanted) {
350 if (std::find(contract->propertyIds().begin(),
351 contract->propertyIds().end(),
352 propertyId) != contract->propertyIds().end()) {
353 return true;
354 }
355 }
356 return allowUnassigned && !hasAnyProperty;
357 }
358
359 return allowUnassigned && !hasAnyProperty;
360 });
361}
362
363void addAllocatablePredicate(
364 core::application::analysis::AnalysisFilter &filter,
365 const AnalysisFilterClause &clause) {
366 const std::string mode = lowerAscii(cleanValue(clause.value));
367 if (mode != "allocatable" && mode != "non-allocatable")
368 return;
369
370 const bool expected = mode == "allocatable";
371 filter.addPredicate(
372 [expected](const std::shared_ptr<Transaction> &transaction,
373 const WorkspaceCatalog &) {
374 return transaction && transaction->isAllocatable() == expected;
375 });
376}
377
378} // namespace
379
380AnalysisFilter parseAnalysisFilterSpec(const std::string &spec) {
382 if (spec.empty())
383 return f;
384
385 std::vector<AnalysisFilterClause> clauses;
386 AnalysisDateField dateField = AnalysisDateField::BookingDate;
387 std::istringstream ss(spec);
388 std::string token;
389 while (
391 token = trim(token);
392 if (token.empty())
393 continue;
394
395 const auto clause = parseClause(token);
396 if (!clause)
397 continue;
399 dateField = parseDateField(clause->value);
400 continue;
401 }
402 clauses.push_back(*clause);
403 }
404
405 for (const auto &clause : clauses) {
407 addDatePredicate(f, clause, dateField);
408 } else if (clause.key == core::application::analysis::filterKeys::kAmount) {
409 addAmountPredicate(f, clause);
411 addContractTypePredicate(f, clause);
413 addPropertyPredicate(f, clause);
415 addAllocatablePredicate(f, clause);
416 }
417 }
418
419 return f;
420}
421
422std::vector<std::shared_ptr<core::domain::Transaction>>
425 const AnalysisFilter &filter) {
426 std::vector<std::shared_ptr<core::domain::Transaction>> out;
427 out.reserve(state.transactions().size());
428
429 for (const auto &transaction : state.transactions()) {
430 if (!transaction) {
431 continue;
432 }
433 if (!filter.empty() && !filter.matches(transaction, state)) {
434 continue;
435 }
436 out.push_back(transaction);
437 }
438
439 return out;
440}
441
442} // namespace core::application::analysis
Declares internal analysis filter predicates and collection helpers.
Defines stable analysis type, configuration, filter, and result keys.
Domain model for contracts.
Declares private string helpers shared across core implementation files.
Domain model for transactions.
const ContractList & contracts() const noexcept
Returns all contracts in the catalog.
Aggregates transaction predicates parsed from one analysis filter specification.
bool empty() const
Checks whether no predicates have been added.
void addPredicate(Pred p)
Adds one predicate to the composed filter.
bool matches(const std::shared_ptr< core::domain::Transaction > &t, const core::domain::catalog::WorkspaceCatalog &state) const
Evaluates all predicates against one transaction.
const TransactionList & transactions() const noexcept
Returns all transactions in the catalog.
constexpr std::string_view kDateField
constexpr std::string_view kContractType
constexpr std::string_view kAmount
constexpr std::string_view kDate
constexpr std::string_view kPropertyId
constexpr std::string_view kAllocatable
constexpr std::string_view kUnassigned
std::vector< std::shared_ptr< core::domain::Transaction > > collectAnalysisTransactions(const core::domain::catalog::WorkspaceCatalog &state, const AnalysisFilter &filter)
Collects transactions that satisfy the supplied filter.
AnalysisFilter parseAnalysisFilterSpec(const std::string &spec)
Parses a raw filter specification into predicate form.
std::string lowerAscii(std::string s)
Converts ASCII letters in a string to lowercase.
std::string trim(std::string s)
Removes leading and trailing ASCII whitespace from a string.
std::string lowerAscii(std::string s)
Converts ASCII letters in a string to lowercase.
std::string trim(std::string s)
Removes leading and trailing ASCII whitespace from a string.