83 bool sourceDebit =
false;
84 bool sourceCredit =
false;
86 auto tokenIndicatesNegative = [&](
const std::string &txt)->
bool{
89 if (s.empty())
return false;
90 if (s.front() ==
'(' && s.back() ==
')')
return true;
91 if (s.front() ==
'-')
return true;
92 if (s.back() ==
'-')
return true;
96 auto centerXOf = [&](
const OcrLine& l)->
int{
97 if (!l.wordSpans.empty())
return (l.wordSpans.front().first + l.wordSpans.back().second) / 2;
98 return (l.minX + l.maxX) / 2;
101 std::optional<double> creditVal;
102 std::optional<double> debitVal;
103 bool creditExplicitNeg =
false;
104 bool debitExplicitNeg =
false;
108 if (
auto v = helpers::findAndParseAmountInLine(block.
main.
credit.
line, -1, debugOut)) {
110 if (debugOut) debugOut->push_back(std::string(
"initial.credit.helperUsed\t") + block.
main.
credit.
line.
text + std::string(
" -> ") + std::to_string(*creditVal));
113 if (debugOut) debugOut->push_back(std::string(
"initial.credit.parse\t") + block.
main.
credit.
line.
text + std::string(
" -> ") + (creditVal ? std::to_string(*creditVal) : std::string(
"(none)")));
118 if (
auto v = helpers::findAndParseAmountInLine(block.
main.
debit.
line, -1, debugOut)) {
120 if (debugOut) debugOut->push_back(std::string(
"initial.debit.helperUsed\t") + block.
main.
debit.
line.
text + std::string(
" -> ") + std::to_string(*debitVal));
123 if (debugOut) debugOut->push_back(std::string(
"initial.debit.parse\t") + block.
main.
debit.
line.
text + std::string(
" -> ") + (debitVal ? std::to_string(*debitVal) : std::string(
"(none)")));
128 const double suspiciousThreshold = 1.0;
129 bool needFallback = (!creditVal && !debitVal);
131 if (creditVal && std::abs(*creditVal) < suspiciousThreshold) needFallback =
true;
132 if (debitVal && std::abs(*debitVal) < suspiciousThreshold) needFallback =
true;
135 if (debugOut) debugOut->push_back(std::string(
"recovery.scan.start\tcreditVal=") + (creditVal?
"1":
"0") + std::string(
" debitVal=") + (debitVal?
"1":
"0"));
137 if (
auto v = helpers::findAndParseAmountInLine(block.
main.
credit.
line, -1, debugOut)) { creditVal = v;
if (debugOut) debugOut->push_back(std::string(
"recovery.credit.helper\t") + block.
main.
credit.
line.
text + std::string(
" -> ") + std::to_string(*v)); }
140 if (
auto v = helpers::findAndParseAmountInLine(block.
main.
debit.
line, -1, debugOut)) { debitVal = v;
if (debugOut) debugOut->push_back(std::string(
"recovery.debit.helper\t") + block.
main.
debit.
line.
text + std::string(
" -> ") + std::to_string(*v)); }
142 if (!creditVal && !debitVal) {
144 if (
auto v = helpers::findAndParseAmountInLine(block.
main.
left.
line, -1, debugOut)) {
146 if (debugOut) debugOut->push_back(std::string(
"recovery.left.helper\t") + block.
main.
left.
line.
text + std::string(
" -> ") + std::to_string(*v));
150 if (creditVal || debitVal)
break;
151 if (
auto v = helpers::findAndParseAmountInLine(dl, -1, debugOut)) { creditVal = v;
if (debugOut) debugOut->push_back(std::string(
"recovery.detail.helper\t") + dl.text + std::string(
" -> ") + std::to_string(*v));
break; }
157 if (creditVal && debitVal) {
158 if (creditExplicitNeg && !debitExplicitNeg) {
160 if (debugOut) debugOut->push_back(std::string(
"resolve.both\tchoose.creditNeg->") + std::to_string(tx.
amount));
161 }
else if (debitExplicitNeg && !creditExplicitNeg) {
163 if (debugOut) debugOut->push_back(std::string(
"resolve.both\tchoose.debitNeg->") + std::to_string(tx.
amount));
165 if (std::abs(*debitVal) >= std::abs(*creditVal)) tx.
amount = *debitVal;
else tx.
amount = *creditVal;
166 if (debugOut) debugOut->push_back(std::string(
"resolve.both\tchoose.larger->") + std::to_string(tx.
amount));
168 }
else if (creditVal) {
170 if (debugOut) debugOut->push_back(std::string(
"resolve.credit->") + std::to_string(tx.
amount));
171 }
else if (debitVal) {
172 bool treatAsCredit =
false;
177 if (cxDebit > cxCredit) treatAsCredit =
true;
182 if (debugOut) debugOut->push_back(std::string(
"resolve.debitExpNeg->") + std::to_string(tx.
amount));
183 }
else if (treatAsCredit) {
185 if (debugOut) debugOut->push_back(std::string(
"resolve.debit.treatAsCredit->") + std::to_string(tx.
amount));
187 tx.
amount = -std::abs(*debitVal);
188 if (debugOut) debugOut->push_back(std::string(
"resolve.debit.defaultNegative->") + std::to_string(tx.
amount));
193 auto tryCombineParse = [&](
const std::string& a,
const std::string& b)->std::optional<double> {
195 if (a.empty() || b.empty())
return std::nullopt;
196 if (
auto p = amount_parser::parseAmountString(a + b))
return p;
197 if (
auto p2 = amount_parser::parseAmountString(a +
" " + b))
return p2;
202 bool creditRightOfDebit =
false;
204 if (creditRightOfDebit) tx.
amount = *p;
else tx.
amount = -std::abs(*p);
205 if (debugOut) debugOut->push_back(std::string(
"combine.debit+credit->") + std::to_string(tx.
amount));
208 if (debugOut) debugOut->push_back(std::string(
"combine.credit+debit->") + std::to_string(tx.
amount));
213 auto hasNegMarker = [&](
const std::string &t)->
bool{
214 if (t.empty())
return false;
215 for (
char c : t)
if (c ==
'-' || c ==
'(' || c ==
')')
return true;
220 if (debugOut) debugOut->push_back(std::string(
"enforce.negMarker->") + std::to_string(tx.
amount));
225 if (sourceDebit && sourceCredit) {
226 }
else if (sourceDebit) {
228 }
else if (sourceCredit) {
233 std::vector<OcrLine> lines;
236 if (!
trim(l.text).empty()) lines.push_back(l);
238 if (lines.empty())
return tx;
241 for (
const auto& l : lines) rightEdge = std::max(rightEdge,
textRightEdge(l));
243 std::vector<std::string> nameLines;
246 for (
size_t i = 1; i < lines.size(); ++i) {
247 const auto& prev = lines[i - 1];
248 const auto& cur = lines[i];
251 const int gap = std::max(0, rightEdge - prevRight);
252 const int w = std::max(1, rightEdge - lines.front().minX);
254 const int allowAbs = std::max(24, w / 40);
255 const int allowRel = w / 25;
256 const int allow = std::min(std::max(1, allowAbs), std::max(1, allowRel));
258 if (gap > allow)
break;
259 if (!tightContinuation(prev, cur))
break;
265 for (
const auto& l : nameLines) nm += l;
267 if (tx.
name.size() > 120) tx.
name.resize(120);
270 bool lastWasLabel =
false;
271 for (
size_t i = 0; i < lines.size(); ++i) {
273 t =
trim(std::move(t));
274 if (t.empty())
continue;
275 const bool isLabel = looksLikeLabelStart(t);
277 if (i == nameLines.size()) {
278 meta.push_back(
'\n');
281 if (isLabel || lastWasLabel) meta.push_back(
'\n');
286 lastWasLabel = isLabel;