19using core::application::importing::statement::DefaultStatementParser;
20namespace poppler = core::ports::pdf_rendering;
21namespace tesseract = core::ports::text_recognition;
25constexpr auto kTsvArtifactPrefix =
"tesseract/page_";
26constexpr auto kTsvArtifactSuffix =
"_crop_0.tsv";
27constexpr auto kParserLogArtifactPrefix =
"parser/page_";
28constexpr auto kParserLogArtifactSuffix =
".log";
29constexpr auto kPausePollInterval = std::chrono::milliseconds(50);
31bool importCanceled(
const ImportRequest& req)
33 return req.cancelFlag && req.cancelFlag->load();
36bool waitWhilePaused(
const ImportRequest& req)
38 while (req.pauseFlag && req.pauseFlag->load()) {
39 if (importCanceled(req))
return false;
40 std::this_thread::sleep_for(kPausePollInterval);
42 return !importCanceled(req);
45void safeReleaseLimiter(core::jobs::SlotLimiter* ocrLimiter,
46 core::ports::diagnostics::IErrorReporter* errorReporter,
50 if (ocrLimiter) ocrLimiter->
release();
56core::ports::text_recognition::ExtractResult extractWithLimiter(
const std::shared_ptr<core::ports::text_recognition::ITextRecognizer>& tesseractService,
57 const tesseract::ExtractRequest& request,
58 core::jobs::SlotLimiter* ocrLimiter,
59 double& accumulatedOcrSec)
61 if (ocrLimiter) ocrLimiter->
acquire();
62 const auto ocrStart = std::chrono::steady_clock::now();
63 auto result = tesseractService->extract(request);
64 accumulatedOcrSec += std::chrono::duration<double>(std::chrono::steady_clock::now() - ocrStart).count();
65 if (ocrLimiter) ocrLimiter->
release();
69std::string makeTsvArtifactKey(
size_t pageIndex)
71 return std::string(kTsvArtifactPrefix) + std::to_string(pageIndex) + kTsvArtifactSuffix;
74std::string makeParserLogArtifactKey(
size_t pageIndex)
76 return std::string(kParserLogArtifactPrefix) + std::to_string(pageIndex) + kParserLogArtifactSuffix;
79void storeTsvArtifact(ImportResult& out,
81 const tesseract::ExtractResult& extractResult,
82 std::mutex& artifactsMutex,
83 core::ports::diagnostics::IErrorReporter* errorReporter)
86 std::vector<uint8_t> tsvData(extractResult.tsv.begin(), extractResult.tsv.end());
87 std::lock_guard<std::mutex> guard(artifactsMutex);
88 out.artifacts[makeTsvArtifactKey(pageIndex)] = std::move(tsvData);
103 std::vector<uint8_t> out;
105 std::ifstream ifs(path, std::ios::binary);
106 if (!ifs)
return out;
107 out.assign((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
115 const poppler::RenderResult& renderRes,
116 const poppler::ExtractResult& extractRes,
117 const std::shared_ptr<core::ports::document_image_processing::IDocumentImageProcessor>& documentImageProcessor,
118 const std::shared_ptr<core::ports::text_recognition::ITextRecognizer>& tesseract,
121 std::atomic<size_t>& doneUnits,
125 std::mutex& artifactsMutex)
127 const auto pageStart = std::chrono::steady_clock::now();
132 size_t localUnitsDone = 0;
133 auto unitDone = [&](
size_t inc,
const std::string& label) {
134 localUnitsDone += inc;
135 const size_t d = doneUnits.fetch_add(inc) + inc;
136 const double frac = std::min(1.0,
static_cast<double>(d) /
static_cast<double>(totalUnits));
138 report(progress, std::string(
"[") + std::to_string(pageIndex + 1) +
"/" + std::to_string(totalPages) +
"] " + label);
140 auto finishUnits = [&](
const std::string& label) {
141 if (localUnitsDone >= unitsPerPage)
return;
142 unitDone(unitsPerPage - localUnitsDone, label);
149 if (!waitWhilePaused(req)) {
154 std::vector<uint8_t> pageBytes;
155 if (pageIndex < renderRes.imageBytes.size() && !renderRes.imageBytes[pageIndex].empty()) {
156 pageBytes = renderRes.imageBytes[pageIndex];
157 }
else if (pageIndex < renderRes.images.size() && !renderRes.images[pageIndex].empty()) {
160 if (pageBytes.empty()) {
164 if (!waitWhilePaused(req)) {
171 if (maskRequest.useTesseract) {
172 if (!waitWhilePaused(req)) {
178 const auto tessResponse = extractWithLimiter(tesseract, tessRequest, ocrLimiter, page.
ocrSec);
180 maskRequest.tesseractTsv = tessResponse.tsv;
182 safeReleaseLimiter(ocrLimiter, errorReporter,
"core::import::DefaultImportStatementStrategy::ocrLimiterReleaseMask");
187 if (!waitWhilePaused(req)) {
191 auto maskResponse = documentImageProcessor->mask(maskRequest);
192 std::vector<uint8_t> maskedBytes = !maskResponse.maskedImageBytes.empty() ? maskResponse.maskedImageBytes : pageBytes;
195 if (!waitWhilePaused(req)) {
200 auto detectResponse = documentImageProcessor->detect(detectRequest);
203 if (!detectResponse.detected) {
208 if (!waitWhilePaused(req)) {
212 const auto cropRequest =
buildCropRequest(pageBytes, pageIndex, req, detectResponse);
213 auto cropResponse = documentImageProcessor->crop(cropRequest);
216 if (cropResponse.croppedImageBytes.empty() || cropResponse.croppedImageBytes.front().empty()) {
221 const auto tableRequest =
buildTableOcrRequest(cropResponse.croppedImageBytes.front(), pageIndex, req, detectResponse);
223 if (!waitWhilePaused(req)) {
228 page.
ocr = extractWithLimiter(tesseract, tableRequest, ocrLimiter, page.
ocrSec);
230 safeReleaseLimiter(ocrLimiter, errorReporter,
"core::import::DefaultImportStatementStrategy::ocrLimiterReleaseTable");
239 page.
table = detectResponse.table;
240 page.
cropBytes = std::move(cropResponse.croppedImageBytes.front());
243 storeTsvArtifact(out, pageIndex, page.
ocr, artifactsMutex, errorReporter);
246 page.
totalSec = std::chrono::duration<double>(std::chrono::steady_clock::now() - pageStart).count();
251 const std::vector<PageWork>& pages,
252 const std::shared_ptr<core::ports::document_image_processing::IDocumentImageProcessor>& documentImageProcessor,
254 std::vector<core::application::importing::draft::TransactionDraft>& all,
255 std::string& carriedBookingDate,
259 std::mutex& artifactsMutex)
262 const size_t finalizePageCount = std::max<size_t>(1, pages.size());
263 for (
size_t pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
264 if (!waitWhilePaused(req)) {
273 const auto& page = pages[pageIndex];
274 if (!page.hasTable)
continue;
276 const double fraction =
static_cast<double>(pageIndex + 1) /
static_cast<double>(finalizePageCount);
278 report(progress, std::string(
"[") + std::to_string(pageIndex + 1) +
"/" + std::to_string(finalizePageCount) +
"] " + std::string(
constants::pageSteps::kFinalize));
289 documentImageProcessor,
293 carriedBookingDate = parsed.lastBookingDate;
294 nextTxIndex = parsed.nextTransactionIndex;
296 if (!parsed.transactions.empty()) {
300 if (!parsed.debugLines.empty()) {
302 std::ostringstream outLog;
303 for (
const auto& line : parsed.debugLines) outLog << line <<
'\n';
304 const auto serialized = outLog.str();
305 std::lock_guard<std::mutex> guard(artifactsMutex);
306 out.
artifacts[makeParserLogArtifactKey(page.pageIndex)] = std::vector<uint8_t>(serialized.begin(), serialized.end());
310 "core::import::DefaultImportStatementStrategy::storeParserLogArtifact",
311 std::current_exception());
315 if (!parsed.artifacts.empty()) {
317 std::lock_guard<std::mutex> guard(artifactsMutex);
318 for (
auto& [key, value] : parsed.artifacts) out.
artifacts.emplace(key, std::move(value));
Declares the public default statement parser used by the import pipeline.
Provides shared helpers for reporting structured errors and exceptions.
Defines private constants shared by core import orchestration.
Declares private request-building helpers for per-page import processing.
Declares private helper types and functions for the import pipeline implementation.
static ParseResult parse(const core::ports::document_image_processing::Table &table, const core::ports::text_recognition::ExtractResult &ocr, const std::string &pageCropImagePath, std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > documentImageProcessor, const std::vector< uint8_t > &pageCropImageBytes, std::string initialBookingDate={}, int initialTransactionIndex=1)
Parses an OCR-backed table into transaction drafts and artifacts.
Limits concurrent access to a bounded number of slots.
void release()
Release one acquired slot and wake a waiting caller.
void acquire()
Block until a slot is available and acquire it.
Port for reporting structured diagnostic error events.
constexpr std::string_view kNoTable
constexpr std::string_view kOcr
constexpr std::string_view kMask
constexpr std::string_view kDetect
constexpr std::string_view kCrop
constexpr std::string_view kNoCrop
constexpr std::string_view kFinalize
constexpr std::string_view kNoImage
constexpr std::string_view kDone
constexpr std::string_view kOcrFailed
constexpr double kProgressPageWorkBase
constexpr double kProgressFinalizeBase
constexpr std::string_view kProgressCanceled
constexpr std::size_t kUnitsPerPage
constexpr double kProgressFinalizeSpan
constexpr double kProgressPageWorkSpan
PageWork processImportPage(size_t pageIndex, size_t totalPages, const ImportRequest &req, const poppler::RenderResult &renderRes, const poppler::ExtractResult &extractRes, const std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > &documentImageProcessor, const std::shared_ptr< core::ports::text_recognition::ITextRecognizer > &tesseract, core::jobs::SlotLimiter *ocrLimiter, const ProgressReporter &report, std::atomic< size_t > &doneUnits, size_t totalUnits, core::ports::diagnostics::IErrorReporter *errorReporter, ImportResult &out, std::mutex &artifactsMutex)
FinalizeStats finalizeParsedPages(const ImportRequest &req, const std::vector< PageWork > &pages, const std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > &documentImageProcessor, ImportResult &out, std::vector< core::application::importing::draft::TransactionDraft > &all, std::string &carriedBookingDate, int &nextTxIndex, core::ports::diagnostics::IErrorReporter *errorReporter, const ProgressReporter &report, std::mutex &artifactsMutex)
std::function< void(double, const std::string &)> ProgressReporter
std::vector< uint8_t > readImportBytes(const std::filesystem::path &path)
core::ports::document_image_processing::DetectRequest buildDetectRequest(const std::vector< uint8_t > &maskedBytes, size_t pageIndex, const ImportRequest &req)
core::ports::document_image_processing::CropRequest buildCropRequest(const std::vector< uint8_t > &pageBytes, size_t pageIndex, const ImportRequest &req, const core::ports::document_image_processing::DetectResult &detectResponse)
core::ports::text_recognition::ExtractRequest buildTableOcrRequest(const std::vector< uint8_t > &croppedBytes, size_t pageIndex, const ImportRequest &req, const core::ports::document_image_processing::DetectResult &detectResponse)
core::ports::document_image_processing::MaskRequest buildMaskRequest(const std::vector< uint8_t > &pageBytes, size_t pageIndex, const ImportRequest &req, const core::ports::pdf_rendering::ExtractResult &extractRes)
core::ports::text_recognition::ExtractRequest buildMaskOcrRequest(const std::vector< uint8_t > &pageBytes, const ImportRequest &req)
void reportException(core::ports::diagnostics::IErrorReporter *reporter, ErrorSeverity severity, const char *origin, std::exception_ptr exception)
Report an exception through the reporter's default exception handling.
Describes one import execution request.
std::shared_ptr< std::atomic< bool > > cancelFlag
std::map< std::string, std::vector< std::uint8_t > > artifacts
core::ports::text_recognition::ExtractResult ocr
std::vector< uint8_t > cropBytes
core::ports::document_image_processing::Table table