28template <
typename TEntity>
29std::vector<core::domain::Alias> aliasUsages(
const TEntity& entity)
31 std::vector<core::domain::Alias> out;
32 out.reserve(entity.aliases().size());
33 for (
const auto& alias : entity.aliases()) {
34 if (alias.value().empty())
continue;
40bool containsId(
const std::vector<std::string>& ids,
const std::string&
id)
42 return std::find(ids.begin(), ids.end(),
id) != ids.end();
45bool appendUnique(std::vector<std::string>& values,
const std::string& value)
47 if (value.empty() || containsId(values, value))
return false;
48 values.push_back(value);
54std::string
trim(std::string value)
64std::vector<std::string>
splitLines(
const std::string& text)
66 std::vector<std::string> lines;
69 if (c ==
'\r')
continue;
71 const auto line =
trim(current);
72 if (!line.empty()) lines.push_back(line);
78 const auto tail =
trim(current);
79 if (!tail.empty()) lines.push_back(tail);
83std::string
joinNonEmptyLines(
const std::vector<std::string>& values,
const std::string& separator)
85 std::ostringstream out;
87 for (
const auto& value : values) {
88 const auto trimmedValue =
trim(value);
89 if (trimmedValue.empty())
continue;
90 if (!first) out << separator;
99 const auto value =
trim(line);
100 if (value.empty())
return true;
101 if (value.find(
':') != std::string::npos)
return true;
103 static const std::regex amountPattern(
"(eur|usd|chf|gbp)\\s*[-+]?\\d|\\b\\d+(?:[\\.,]\\d{2})\\b", std::regex::icase);
104 return std::regex_search(value, amountPattern);
109 const auto value =
trim(line);
110 if (value.empty())
return true;
113 static const std::regex ibanPattern(
"^[A-Z]{2}\\d{2}[A-Z0-9]{10,32}$", std::regex::icase);
114 static const std::regex bicPattern(
"^[A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?$", std::regex::icase);
115 static const std::regex dateOnlyPattern(
"^\\d{1,2}[\\./-]\\d{1,2}[\\./-]\\d{2,4}$", std::regex::icase);
116 static const std::regex bankOrReferencePattern(
"(end-to-end|kundenreferenz|verwendungszweck|vertragskont|iban|bic|dauerauftrag|mandat|referenz|zweck)", std::regex::icase);
117 return std::regex_match(value, ibanPattern)
118 || std::regex_match(value, bicPattern)
119 || std::regex_match(value, dateOnlyPattern)
120 || std::regex_search(value, bankOrReferencePattern);
125 const auto value =
trim(line);
126 if (value.empty())
return false;
131 for (
const unsigned char ch : value) {
132 if (std::isdigit(ch)) ++digitCount;
133 if (std::isalpha(ch)) ++alphaCount;
136 if (alphaCount == 0)
return false;
137 if (digitCount == 0)
return true;
138 if (digitCount <= 2 && alphaCount >= digitCount * 2)
return true;
140 static const std::regex compactReferencePattern(
"^[A-Z]{1,6}(?:[ -]?[A-Z0-9]{2,8}){1,5}$", std::regex::icase);
141 return value.size() <= 36 && std::regex_match(value, compactReferencePattern);
147 if (!line.empty())
return line;
154 std::istringstream in(text);
155 std::ostringstream out;
157 std::size_t count = 0;
158 while (in >> token) {
159 if (count++ > 0) out <<
' ';
161 if (count >= maxTokens)
break;
163 return trim(out.str());
168 for (
auto& ch : text) {
169 const unsigned char byte =
static_cast<unsigned char>(ch);
170 if (
byte < 128 && std::isalpha(
byte)) {
171 ch =
static_cast<char>(std::tolower(
byte));
172 }
else if (
byte < 128 && std::isalnum(
byte)) {
173 ch =
static_cast<char>(byte);
181std::vector<std::string>
tokens(
const std::string& text)
183 std::vector<std::string> out;
185 for (std::string token; in >> token;) out.push_back(token);
189std::string
leadingText(
const std::string& text, std::size_t maxTokens)
191 const auto parts =
tokens(text);
192 std::ostringstream out;
193 for (std::size_t i = 0; i < parts.size() && i < maxTokens; ++i) {
194 if (i > 0) out <<
' ';
197 return trim(out.str());
202 if (token.size() >= 3)
return true;
203 return std::all_of(token.begin(), token.end(), [](
unsigned char c) { return std::isdigit(c) != 0; });
206int tokenOverlapScore(
const std::vector<std::string>& sourceTokens,
const std::vector<std::string>& targetTokens)
208 if (sourceTokens.empty() || targetTokens.empty())
return 0;
210 for (
const auto& token : targetTokens) {
212 if (std::find(sourceTokens.begin(), sourceTokens.end(), token) != sourceTokens.end()) ++matches;
219 return std::min(0.7, std::log1p(std::max(1, hitCount)) / 5.0);
230 std::ostringstream out;
233 for (std::size_t i = 0; i < lines.size(); ++i) {
234 const auto& line = lines[i];
235 const auto colon = line.find(
':');
236 if (colon != std::string::npos) {
237 const auto key =
trim(line.substr(0, colon));
238 const auto value =
trim(line.substr(colon + 1));
240 if (!first) out <<
' ';
244 if (!value.empty()) {
245 if (!first) out <<
' ';
248 }
else if (i + 1 < lines.size() && !
trim(lines[i + 1]).empty()) {
249 if (!first) out <<
' ';
251 out <<
trim(lines[i + 1]);
257 if (!first) out <<
' ';
261 return trim(out.str());
266 std::vector<std::string> out;
267 const std::regex refKeyword(
"(kund(?:en)?ref|kundenreferenz|referenz|verweis|vertragsnr|vertragsnummer|nr|nummer|ref)", std::regex::icase);
268 const std::regex digits(
"\\b\\d{3,}\\b");
270 for (
const auto& line :
splitLines(metadata)) {
271 if (line.empty())
continue;
272 if (std::regex_search(line, refKeyword)) appendUnique(out, line);
274 for (std::sregex_iterator it(line.begin(), line.end(), digits), end; it != end; ++it) {
275 appendUnique(out, it->str());
284 std::vector<std::string> types;
285 std::vector<std::string> normalizedSeen;
287 normalizedSeen.reserve(state.
contracts().size());
288 for (
const auto& contract : state.
contracts()) {
289 if (!contract)
continue;
290 const auto type =
trim(contract->type());
292 if (normalizedType.empty())
continue;
293 if (std::find(normalizedSeen.begin(), normalizedSeen.end(), normalizedType) != normalizedSeen.end())
continue;
294 normalizedSeen.push_back(normalizedType);
295 types.push_back(type);
301 const std::string& signalText,
302 const std::vector<std::string>& lines)
305 if (contractTypes.empty())
return {};
307 for (
const auto& line : lines) {
308 const auto value =
trim(line);
309 const auto separatorPos = value.find(
'-');
310 if (value.empty() || separatorPos == std::string::npos)
continue;
312 const auto left =
trim(value.substr(0, separatorPos));
313 if (left.empty())
continue;
316 for (
const auto& type : contractTypes) {
324 std::string bestType;
325 int bestScore = std::numeric_limits<int>::min();
326 for (
const auto& type : contractTypes) {
328 if (normalizedType.empty())
continue;
331 const auto fullPos = fullText.find(normalizedType);
332 if (fullPos != std::string::npos) {
334 score += std::max(0, 120 -
static_cast<int>(fullPos));
337 const auto headPos = headText.find(normalizedType);
338 if (headPos != std::string::npos) {
340 score += std::max(0, 80 -
static_cast<int>(headPos));
343 if (score <= 0)
continue;
344 score +=
static_cast<int>(normalizedType.size());
345 if (score > bestScore) {
354std::string
extractActorText(
const std::vector<std::string>& lines,
const std::string& typeText)
356 std::vector<std::string> actorLines;
357 actorLines.reserve(lines.size());
360 for (std::size_t index = 0; index < lines.size(); ++index) {
361 const auto& line = lines[index];
362 const auto value =
trim(line);
363 if (value.empty())
continue;
365 if (actorLines.empty()) {
366 actorLines.push_back(value);
372 actorLines.push_back(value);
373 if (index >= 1)
break;
376 if (actorLines.empty())
return {};
379 if (!normalizedType.empty() &&
normalizeText(actorText) == normalizedType && actorLines.size() > 1) {
382 return trim(actorText);
Declares draft matching policy helpers for import-time bank statement inference.
Declares private string helpers shared across core implementation files.
const ContractList & contracts() const noexcept
Returns all contracts in the catalog.
Collects common core includes and aliases for precompiled-header builds.
bool lineLooksActorStopSignal(const std::string &line)
Checks whether a line should stop actor extraction.
std::vector< std::string > splitLines(const std::string &text)
Splits text into non-empty normalized lines.
std::vector< std::string > referenceAliasesFromMetadata(const std::string &metadata)
Extracts reference-like aliases from metadata.
bool matchesDraftText(const std::string &left, const std::string &right)
Compares two pieces of draft text after normalization.
bool lineLooksMetadataBoundary(const std::string &line)
Checks whether a line looks like a metadata boundary.
bool tokenIsMatchable(const std::string &token)
Checks whether a token is useful for matching.
std::string extractActorText(const std::vector< std::string > &lines, const std::string &typeText)
Extracts an actor hint from draft text.
std::string trim(std::string value)
Trims whitespace from raw text.
bool lineLooksActorContinuation(const std::string &line)
Checks whether a line can continue actor extraction.
std::string metadataSignalText(const std::string &metadata)
Normalizes metadata into signal text.
int tokenOverlapScore(const std::vector< std::string > &sourceTokens, const std::vector< std::string > &targetTokens)
Scores token overlap between two token lists.
double aliasHitWeight(int hitCount)
Computes a score weight from alias hit count.
double aliasRecencyWeight(const std::string &lastUsedAt)
Computes a score weight from alias recency.
std::string leadingPhrase(const std::string &text, std::size_t maxTokens)
Extracts a leading phrase from text.
std::string extractTypeText(const core::domain::catalog::WorkspaceCatalog &state, const std::string &signalText, const std::vector< std::string > &lines)
Extracts a contract type hint from draft text.
std::string joinNonEmptyLines(const std::vector< std::string > &values, const std::string &separator)
Joins non-empty lines with a separator.
std::string leadingText(const std::string &text, std::size_t maxTokens)
Returns a leading slice of the normalized text.
std::string firstMeaningfulLine(const std::string &text)
Returns the first meaningful line of text.
std::vector< std::string > knownContractTypes(const core::domain::catalog::WorkspaceCatalog &state)
Collects known contract types from the workspace state.
std::vector< std::string > tokens(const std::string &text)
Splits normalized text into tokens.
std::string normalizeText(std::string text)
Normalizes draft text for matching.
std::string trim(std::string s)
Removes leading and trailing ASCII whitespace from a string.