38constexpr int kKeepImportRuns = 20;
41std::string normalizedAllocatableMode(std::string mode) {
43 std::transform(mode.begin(), mode.end(), mode.begin(), [](
unsigned char ch) {
44 return static_cast<char>(std::tolower(ch));
49std::string fallbackDraftTransactionId() {
50 static thread_local std::mt19937_64 generator{std::random_device{}()};
51 std::uniform_int_distribution<std::uint64_t> distribution;
52 std::ostringstream out;
53 out << std::hex << distribution(generator) << distribution(generator);
57std::string fileStemFromPath(
const std::string &path) {
59 return std::filesystem::path(path).stem().string();
84core::ports::workspace::StatementSnapshot
85toStatementSnapshot(
const std::shared_ptr<core::domain::Statement> &
statement) {
86 core::ports::workspace::StatementSnapshot out;
97core::ports::importing::draft::TransactionDraft
98toPortDraft(
const core::application::importing::draft::TransactionDraft &
draft) {
99 core::ports::importing::draft::TransactionDraft out;
125core::application::importing::draft::TransactionDraft
126toCoreDraft(
const core::ports::importing::draft::TransactionDraft &
draft) {
127 core::application::importing::draft::TransactionDraft out;
153core::ports::importing::draft::StatementDraft toPortStatementDraft(
154 const core::application::importing::draft::StatementDraft &
draft) {
155 core::ports::importing::draft::StatementDraft out;
168core::application::importing::draft::StatementDraft toCoreStatementDraft(
169 const core::ports::importing::draft::StatementDraft &
draft) {
170 core::application::importing::draft::StatementDraft out;
183core::application::importing::draft::DraftSuggestionBucket toCoreBucket(
184 const core::ports::importing::draft::DraftSuggestionBucket &bucket) {
185 core::application::importing::draft::DraftSuggestionBucket out;
188 for (
const auto &candidate : bucket.
candidates) {
189 core::application::importing::draft::DraftSuggestionCandidate item;
191 item.
label = candidate.label;
198 item.
score = candidate.score;
207core::ports::importing::draft::DraftSuggestionBucket toPortBucket(
208 const core::application::importing::draft::DraftSuggestionBucket &bucket) {
209 core::ports::importing::draft::DraftSuggestionBucket out;
212 for (
const auto &candidate : bucket.
candidates) {
213 core::ports::importing::draft::DraftSuggestionCandidate item;
215 item.
label = candidate.label;
222 item.
score = candidate.score;
231core::application::importing::draft::DraftLinkSelection toCoreSelection(
232 const core::ports::importing::draft::DraftLinkSelection &selection) {
233 core::application::importing::draft::DraftLinkSelection out;
252core::ports::importing::draft::DraftDerivedState toPortDerivedState(
253 const core::application::importing::draft::DraftDerivedState &derived) {
254 core::ports::importing::draft::DraftDerivedState out;
281 out.
actorChoices.push_back({row.id, row.name, row.display, row.type,
282 row.allocatableMode, row.aliases, row.actorIds,
283 row.propertyIds, row.synthetic, row.confidence,
286 out.
contractChoices.push_back({row.id, row.name, row.display, row.type,
287 row.allocatableMode, row.aliases,
288 row.actorIds, row.propertyIds,
289 row.synthetic, row.confidence,
292 out.
propertyRows.push_back({row.id, row.name, row.display, row.type,
293 row.allocatableMode, row.aliases, row.actorIds,
294 row.propertyIds, row.synthetic, row.confidence,
300core::ports::importing::draft::DraftLinkSelection toPortSelection(
301 const core::ports::importing::draft::TransactionDraft &
draft) {
302 core::ports::importing::draft::DraftLinkSelection out;
318core::application::importing::draft::DraftSuggestionCandidate toCoreCandidate(
319 const core::ports::importing::draft::DraftSuggestionCandidate &candidate) {
334core::application::importing::draft::DraftChoiceRow toCoreChoiceRow(
335 const core::ports::importing::draft::DraftChoiceRow &row) {
341core::application::importing::draft::DraftDerivedState toCoreDerivedState(
342 const core::ports::importing::draft::DraftDerivedState &derived) {
343 core::application::importing::draft::DraftDerivedState out;
380toCoreAutoSelectionMode(
384 return core::application::importing::draft::DraftAutoSelectionMode::
388 return core::application::importing::draft::DraftAutoSelectionMode::
393core::application::importing::draft::TransactionDraftPatch toCorePatch(
394 const core::ports::importing::draft::TransactionDraftPatch &patch) {
395 core::application::importing::draft::TransactionDraftPatch out;
412core::ports::workspace::WorkspaceSnapshot
413mergeSnapshots(core::ports::workspace::WorkspaceSnapshot primary,
414 const core::ports::workspace::WorkspaceSnapshot &secondary) {
415 if (primary.
actors.empty())
442std::filesystem::path defaultRunBasePath() {
443 return std::filesystem::temp_directory_path() /
"fossredder" /
"import-runs";
446std::string utcTimestamp() {
447 const auto now = std::chrono::system_clock::now();
449 std::chrono::duration_cast<std::chrono::milliseconds>(
450 now.time_since_epoch()) %
452 const auto time = std::chrono::system_clock::to_time_t(now);
455 gmtime_s(&tm, &time);
457 gmtime_r(&time, &tm);
459 std::ostringstream out;
460 out << std::put_time(&tm,
"%Y%m%d%H%M%S") << std::setw(3)
461 << std::setfill(
'0') << millis.count();
465bool isImportRunDir(
const std::filesystem::directory_entry &entry) {
466 if (!entry.is_directory())
468 const auto name = entry.path().filename().string();
469 return name.find(std::string(
"_") + kRunNameImport +
"_") !=
473void cleanupOldImportRuns(
const std::filesystem::path &basePath) {
474 if (!std::filesystem::exists(basePath))
477 std::vector<std::filesystem::directory_entry> entries;
478 for (
const auto &entry : std::filesystem::directory_iterator(basePath)) {
479 if (isImportRunDir(entry))
480 entries.push_back(entry);
483 std::sort(entries.begin(), entries.end(), [](
const auto &a,
const auto &b) {
484 return a.last_write_time() > b.last_write_time();
487 for (std::size_t i = kKeepImportRuns; i < entries.size(); ++i) {
489 std::filesystem::remove_all(entries[i].path(), ec);
494buildImportSpec(
const core::ports::importing::ImportRequest &request,
495 const std::filesystem::path &basePath) {
496 cleanupOldImportRuns(basePath);
497 std::filesystem::create_directories(basePath);
499 const auto timestamp = utcTimestamp();
503 std::to_string(suffix);
504 const auto runRoot = basePath / runName;
505 if (!std::filesystem::exists(runRoot)) {
506 std::filesystem::create_directories(runRoot);
507 return {request.
sourcePath, runRoot.string(), timestamp};
519 std::shared_ptr<core::ports::diagnostics::IErrorReporter>
errorReporter)
531 std::shared_ptr<IImportStatement> importService, std::string runBasePath,
532 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter)
536 std::move(runBasePath), std::move(errorReporter)) {}
539 std::shared_ptr<StatementImportJobSystem> jobSystem, std::string runBasePath,
540 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter)
541 : impl_(std::make_unique<
Impl>(std::move(jobSystem), std::move(runBasePath),
542 std::move(errorReporter))) {}
550 if (!impl_->jobSystem)
554 const auto spec = buildImportSpec(request, impl_->runBasePath);
555 const auto importId = impl_->jobSystem->startImportStatement(spec);
556 const auto subscriptionId = impl_->jobSystem->subscribe(
557 importId, [callback = std::move(callback)](
561 callback({
event.jobId, toStatementImportState(event.
state),
562 event.progress,
event.message});
564 return {importId, subscriptionId};
565 }
catch (
const std::exception &) {
566 if (impl_->errorReporter) {
567 impl_->errorReporter->reportException(
569 "core::application::importing::StatementImportRunner::startStatementImport",
570 std::current_exception());
574 if (impl_->errorReporter) {
575 impl_->errorReporter->reportException(
577 "core::application::importing::StatementImportRunner::startStatementImport",
578 std::current_exception());
592 if (impl_->jobSystem && !handle.
importId.empty())
593 impl_->jobSystem->cancel(handle.
importId);
598 if (impl_->jobSystem && !handle.
importId.empty())
599 impl_->jobSystem->pause(handle.
importId);
604 if (impl_->jobSystem && !handle.
importId.empty())
605 impl_->jobSystem->resume(handle.
importId);
612 if (!impl_->jobSystem || handle.
importId.empty())
616 toStatementSnapshot(impl_->jobSystem->statementResult(handle.
importId));
619 const auto transactions =
620 impl_->jobSystem->statementTransactions(handle.
importId);
626 impl_->jobSystem->takeStatementArtifacts(handle.
importId);
636 toCoreSelection(selection)));
643 auto coreDraft = toCoreDraft(
draft);
644 bool changed =
false;
666 case core::ports::importing::draft::TransactionDraftEditKind::
672 toCoreChoiceRow(edit.
choice));
674 case core::ports::importing::draft::TransactionDraftEditKind::
675 SelectContractFromCatalog:
683 case core::ports::importing::draft::TransactionDraftEditKind::
684 ApplyDerivedSelections:
686 coreDraft, toCoreDerivedState(edit.
derived),
693 draft = toPortDraft(coreDraft);
701 auto coreDraft = toCoreStatementDraft(
draft);
708 case core::ports::importing::draft::StatementDraftEditKind::
709 InsertTransactionAfter:
714 case core::ports::importing::draft::StatementDraftEditKind::
722 draft = toPortStatementDraft(coreDraft);
729 const std::string &sourceFile,
732 const std::vector<core::ports::importing::draft::TransactionDraft>
734 const std::string &draftId)
const {
741 draft.transactions.reserve(transactions.size());
743 for (std::size_t i = 0; i < transactions.size(); ++i) {
753 transaction.name =
"Transaction " + std::to_string(i + 1);
759 edit.
kind = core::ports::importing::draft::TransactionDraftEditKind::
760 ApplyDerivedSelections;
786 transaction.statementDraftId = tx.statementDraftId;
802 std::sort(
draft.transactions.begin(),
draft.transactions.end(),
803 [](
const auto &lhs,
const auto &rhs) {
804 return lhs.position < rhs.position;
806 if (
draft.transactionIds.empty()) {
807 draft.transactionIds.reserve(
draft.transactions.size());
826 for (
const auto &draftTransaction :
draft.transactions) {
831 transaction.bookingDate = draftTransaction.bookingDate;
834 transaction.proofImageData = draftTransaction.proofImageData;
837 transaction.contractId = draftTransaction.contractId;
841 draftTransaction.contractId);
842 if (!draftTransaction.contractId.empty()) {
843 for (
const auto &contract : state.
contracts) {
844 if (contract.id != draftTransaction.contractId) {
847 const auto mode = normalizedAllocatableMode(contract.allocatableMode);
848 if (mode ==
"allocatable") {
849 inferredAllocatable =
true;
850 }
else if (mode ==
"non-allocatable") {
851 inferredAllocatable =
false;
857 draftTransaction.allocatableSelected
858 ? draftTransaction.allocatable
859 : (inferredAllocatable || draftTransaction.allocatable);
860 transaction.propertyIds = draftTransaction.propertyIds;
873 return mergeSnapshots(std::move(primary), secondary);
Declares helpers for parsing imported transaction amount strings.
Declares import-draft matching helpers shared by the import pipeline and draft finalization.
Declares the structured diagnostics reporting port.
Declares the public statement import service interface and factory.
Defines shared job identifiers, states, events, and snapshots.
Domain model for statements.
Declares asynchronous statement import job orchestration.
Declares the application-level asynchronous statement import runner.
Domain model for transactions.
Draft transaction model used by import finalization and core application flow.
Shared transaction transition and normalization helpers.
Immutable workspace read-model snapshot exposed through workspace ports.
Maps workspace port snapshots into internal catalog state.
std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter
std::filesystem::path runBasePath
Impl(std::shared_ptr< StatementImportJobSystem > jobSystem, std::string runBasePath, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter)
std::shared_ptr< StatementImportJobSystem > jobSystem
core::ports::workspace::WorkspaceSnapshot mergeWorkspaceState(core::ports::workspace::WorkspaceSnapshot primary, const core::ports::workspace::WorkspaceSnapshot &secondary) const override
Merges two workspace snapshots for import matching context.
core::ports::importing::ImportResult importResult(const core::ports::importing::StatementImportHandle &handle) override
Returns the completed import result for a handle.
void resume(const core::ports::importing::StatementImportHandle &handle) override
Resumes a paused import.
core::ports::importing::draft::StatementDraft restoreStatementDraft(const core::ports::workspace::StatementDraftSnapshot &draft) const override
Restores a runtime statement draft from a workspace draft snapshot.
core::ports::importing::draft::StatementDraftEditResult updateStatementDraft(core::ports::importing::draft::StatementDraft &draft, const core::ports::importing::draft::StatementDraftEdit &edit) const override
Applies one edit command to a statement draft.
void cancel(const core::ports::importing::StatementImportHandle &handle) override
Requests cancellation of an import.
core::ports::importing::StatementImportHandle startStatementImport(const core::ports::importing::ImportRequest &request, core::ports::importing::StatementImportEventCallback callback) override
Starts an asynchronous statement import.
core::ports::workspace::StatementDraftSnapshot buildStatementDraftSnapshot(const core::ports::importing::draft::StatementDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state) const override
Converts a runtime statement draft to a workspace draft snapshot.
StatementImportRunner(std::shared_ptr< IImportStatement > importService, std::string runBasePath, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter)
~StatementImportRunner() override
void unsubscribe(const core::ports::importing::StatementImportHandle &handle) override
Removes a progress subscription for an import.
void pause(const core::ports::importing::StatementImportHandle &handle) override
Pauses a running import.
core::ports::importing::draft::DraftDerivedState buildDraftDerivedState(const core::ports::workspace::WorkspaceSnapshot &state, const core::ports::importing::draft::DraftLinkSelection &selection) const override
Builds derived selection state for a draft link selection.
core::ports::importing::draft::StatementDraft buildStatementDraft(const std::string &sourceFile, const core::ports::workspace::StatementSnapshot &statement, const core::ports::workspace::WorkspaceSnapshot &state, const std::vector< core::ports::importing::draft::TransactionDraft > &transactions, const std::string &draftId) const override
Builds a statement draft from imported statement data.
bool updateTransactionDraft(core::ports::importing::draft::TransactionDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state, const core::ports::importing::draft::TransactionDraftEdit &edit) const override
Applies one edit command to a transaction draft.
std::vector< TransactionDraft > transactions
std::vector< std::string > transactionIds
core::domain::Transaction::Status status
std::vector< uint8_t > proofImageData
std::vector< std::string > propertyIds
std::string statementDraftId
constexpr std::string_view kRunNameImport
bool setPropertySelected(TransactionDraft &draft, const std::string &propertyId, bool selected)
bool contractIsFullyAllocatable(const core::domain::catalog::WorkspaceCatalog &state, const std::string &contractId)
Checks whether all transactions assigned to a contract are allocatable.
bool applyDerivedSelections(TransactionDraft &draft, const DraftDerivedState &derived, DraftAutoSelectionMode mode)
int insertTransactionAfter(StatementDraft &draft, int currentIndex)
DraftDerivedState buildDraftDerivedState(const core::domain::catalog::WorkspaceCatalog &state, const DraftLinkSelection &selection)
Derives UI-agnostic matching state for the currently edited draft link.
bool applyContractSelection(TransactionDraft &draft, const DraftChoiceRow &contract)
bool clearContractSelection(TransactionDraft &draft)
DraftTextSignals buildDraftTextSignals(const core::domain::catalog::WorkspaceCatalog &state, const TransactionDraft &transaction)
Derives text signals from a workspace transaction draft.
bool applyActorSelection(TransactionDraft &draft, const std::string &actorId)
bool renameStatementDraft(StatementDraft &draft, const std::string &name)
int removeTransactionAt(StatementDraft &draft, int index)
bool applyPropertySelection(TransactionDraft &draft, const std::string &propertyId)
bool clearActorSelection(TransactionDraft &draft)
bool applyTransactionPatch(TransactionDraft &draft, const TransactionDraftPatch &patch)
std::optional< double > parseAmountString(const std::string &s)
core::domain::catalog::WorkspaceCatalog toWorkspaceCatalog(const core::ports::workspace::WorkspaceSnapshot &snapshot)
std::string trimCopy(std::string value)
Trims leading and trailing whitespace from text.
JobState
Runtime lifecycle state of a job.
std::function< void(const StatementImportEvent &)> StatementImportEventCallback
bool hasPropertyTopSuggestion
std::string propertySuggestionSummary
double allocatableSuggestionConfidence
std::string actorSeedText
double actorSuggestionConfidence
std::vector< std::string > autoPropertyIds
DraftSuggestionCandidate contractTopSuggestion
std::vector< DraftChoiceRow > propertyRows
bool hasContractTopSuggestion
std::vector< DraftChoiceRow > contractChoices
bool effectiveAllocatable
std::string contractSeedText
std::vector< DraftChoiceRow > actorChoices
DraftSuggestionCandidate actorTopSuggestion
DraftSuggestionCandidate propertyTopSuggestion
std::string actorSuggestionSummary
std::string allocatableSuggestionSummary
std::string contractSuggestionSummary
double contractSuggestionConfidence
bool hasActorTopSuggestion
std::string actorDisplayText
std::string contractDisplayText
double propertySuggestionConfidence
DraftSuggestionBucket propertySuggestions
DraftSuggestionBucket contractSuggestions
std::vector< std::string > propertyIds
DraftSuggestionBucket actorSuggestions
std::vector< DraftSuggestionCandidate > candidates
std::vector< std::string > matchedAliases
Event emitted when a job changes state or progress.
std::map< std::string, std::vector< std::uint8_t > > artifacts
std::vector< core::ports::importing::draft::TransactionDraft > transactions
core::ports::workspace::StatementSnapshot statement
std::uint64_t subscriptionId
std::vector< std::string > aliases
std::vector< std::string > actorIds
std::vector< std::string > propertyIds
std::string allocatableMode
std::string actorSeedText
double contractSuggestionConfidence
bool hasContractTopSuggestion
DraftSuggestionCandidate actorTopSuggestion
double actorSuggestionConfidence
bool hasActorTopSuggestion
std::string actorDisplayText
std::vector< std::string > autoPropertyIds
DraftSuggestionCandidate propertyTopSuggestion
std::vector< DraftChoiceRow > actorChoices
std::string actorSuggestionSummary
std::string contractDisplayText
DraftSuggestionCandidate contractTopSuggestion
std::string allocatableSuggestionSummary
std::string contractSeedText
std::string contractSuggestionSummary
std::vector< DraftChoiceRow > propertyRows
bool effectiveAllocatable
double propertySuggestionConfidence
std::string propertySuggestionSummary
double allocatableSuggestionConfidence
std::vector< DraftChoiceRow > contractChoices
bool hasPropertyTopSuggestion
std::vector< std::string > propertyIds
DraftSuggestionBucket propertySuggestions
DraftSuggestionBucket contractSuggestions
DraftSuggestionBucket actorSuggestions
std::vector< DraftSuggestionCandidate > candidates
std::vector< std::string > matchedAliases
StatementDraftEditKind kind
int selectedTransactionIndex
std::vector< TransactionDraft > transactions
std::vector< std::string > transactionIds
DraftAutoSelectionMode autoSelectionMode
DraftDerivedState derived
TransactionDraftEditKind kind
TransactionDraftPatch patch
std::vector< std::string > propertyIds
std::vector< std::uint8_t > proofImageData
std::string statementDraftId
std::vector< std::string > transactionIds
std::vector< TransactionDraftSnapshot > transactions
std::vector< std::string > transactionIds
std::vector< AnalysisSnapshot > analyses
std::vector< PropertySnapshot > properties
std::vector< ActorSnapshot > actors
std::vector< ContractSnapshot > contracts
std::vector< StatementDraftSnapshot > statementDrafts
std::vector< StatementSnapshot > statements
std::vector< ImportLogSnapshot > importLogs
std::vector< AnnualSnapshot > annuals
std::vector< ExportLogSnapshot > exportLogs
std::vector< TransactionSnapshot > transactions