PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::client::routing_manager Class Reference

#include <routing_manager.h>

Collaboration diagram for kcenon::pacs::client::routing_manager:
Collaboration graph

Public Member Functions

 routing_manager (std::shared_ptr< storage::routing_repository > repo, std::shared_ptr< job_manager > job_manager, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a routing manager with default configuration.
 
 routing_manager (const routing_manager_config &config, std::shared_ptr< storage::routing_repository > repo, std::shared_ptr< job_manager > job_manager, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a routing manager with custom configuration.
 
 ~routing_manager ()
 Destructor.
 
 routing_manager (const routing_manager &)=delete
 
auto operator= (const routing_manager &) -> routing_manager &=delete
 
 routing_manager (routing_manager &&)=delete
 
auto operator= (routing_manager &&) -> routing_manager &=delete
 
auto add_rule (const routing_rule &rule) -> kcenon::pacs::VoidResult
 Add a new routing rule.
 
auto update_rule (const routing_rule &rule) -> kcenon::pacs::VoidResult
 Update an existing routing rule.
 
auto remove_rule (std::string_view rule_id) -> kcenon::pacs::VoidResult
 Remove a routing rule.
 
auto get_rule (std::string_view rule_id) const -> std::optional< routing_rule >
 Get a routing rule by ID.
 
auto list_rules () const -> std::vector< routing_rule >
 List all routing rules.
 
auto list_enabled_rules () const -> std::vector< routing_rule >
 List only enabled routing rules.
 
auto set_rule_priority (std::string_view rule_id, int priority) -> kcenon::pacs::VoidResult
 Set the priority of a rule.
 
auto reorder_rules (const std::vector< std::string > &rule_ids) -> kcenon::pacs::VoidResult
 Reorder rules by specifying the desired order.
 
auto evaluate (const core::dicom_dataset &dataset) -> std::vector< routing_action >
 Evaluate rules against a dataset.
 
auto evaluate_with_rule_ids (const core::dicom_dataset &dataset) -> std::vector< std::pair< std::string, std::vector< routing_action > > >
 Evaluate rules and return with matched rule IDs.
 
void route (const core::dicom_dataset &dataset)
 Route a DICOM dataset based on matching rules.
 
void route (std::string_view sop_instance_uid)
 Route a stored instance by SOP Instance UID.
 
void enable ()
 Enable routing globally.
 
void disable ()
 Disable routing globally.
 
auto is_enabled () const noexcept -> bool
 Check if routing is enabled.
 
void attach_to_storage_scp (services::storage_scp &scp)
 Attach to a Storage SCP for automatic routing.
 
void detach_from_storage_scp ()
 Detach from the currently attached Storage SCP.
 
void set_routing_callback (routing_event_callback callback)
 Set callback for routing events.
 
auto test_rules (const core::dicom_dataset &dataset) const -> routing_test_result
 Test rules against a dataset without executing actions.
 
auto get_statistics () const -> routing_statistics
 Get overall routing statistics.
 
auto get_rule_statistics (std::string_view rule_id) const -> routing_statistics
 Get statistics for a specific rule.
 
void reset_statistics ()
 Reset all statistics.
 
auto config () const noexcept -> const routing_manager_config &
 Get current configuration.
 

Private Member Functions

auto match_condition (const routing_condition &condition, const core::dicom_dataset &dataset) const -> bool
 Check if a condition matches a dataset.
 
auto match_pattern (std::string_view pattern, std::string_view value, bool case_sensitive) const -> bool
 Match a wildcard pattern against a value.
 
auto get_field_value (routing_field field, const core::dicom_dataset &dataset) const -> std::string
 Get DICOM field value from dataset.
 
void execute_actions (const std::string &sop_instance_uid, const std::vector< routing_action > &actions)
 Execute routing actions.
 
void load_rules ()
 Load rules from repository into cache.
 

Private Attributes

routing_manager_config config_
 
std::shared_ptr< storage::routing_repositoryrepo_
 
std::shared_ptr< job_managerjob_manager_
 
std::shared_ptr< di::ILoggerlogger_
 
std::vector< routing_rulerules_
 
std::shared_mutex rules_mutex_
 
std::atomic< bool > enabled_ {true}
 
routing_event_callback routing_callback_
 
std::atomic< size_t > total_evaluated_ {0}
 
std::atomic< size_t > total_matched_ {0}
 
std::atomic< size_t > total_forwarded_ {0}
 
std::atomic< size_t > total_failed_ {0}
 
services::storage_scpattached_scp_ {nullptr}
 

Detailed Description

Definition at line 93 of file routing_manager.h.

Constructor & Destructor Documentation

◆ routing_manager() [1/4]

kcenon::pacs::client::routing_manager::routing_manager ( std::shared_ptr< storage::routing_repository > repo,
std::shared_ptr< job_manager > job_manager,
std::shared_ptr< di::ILogger > logger = nullptr )
explicit

Construct a routing manager with default configuration.

Parameters
repoRouting repository for persistence (required)
job_managerJob manager for executing forward jobs (required)
loggerLogger instance (optional, defaults to NullLogger)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 58 of file routing_manager.cpp.

62 : config_{}
63 , repo_(std::move(repo))
64 , job_manager_(std::move(job_mgr))
65 , logger_(logger ? std::move(logger) : di::null_logger()) {
66
67 load_rules();
68
69 if (logger_) {
70 logger_->info_fmt("routing_manager: Initialized with {} rules", rules_.size());
71 }
72}
std::shared_ptr< storage::routing_repository > repo_
void load_rules()
Load rules from repository into cache.
std::shared_ptr< di::ILogger > logger_
std::shared_ptr< job_manager > job_manager_
std::vector< routing_rule > rules_
std::shared_ptr< ILogger > null_logger()
Get a shared null logger instance.
Definition ilogger.h:271

References load_rules(), logger_, and rules_.

Here is the call graph for this function:

◆ routing_manager() [2/4]

kcenon::pacs::client::routing_manager::routing_manager ( const routing_manager_config & config,
std::shared_ptr< storage::routing_repository > repo,
std::shared_ptr< job_manager > job_manager,
std::shared_ptr< di::ILogger > logger = nullptr )
explicit

Construct a routing manager with custom configuration.

Parameters
configManager configuration
repoRouting repository for persistence (required)
job_managerJob manager for executing forward jobs (required)
loggerLogger instance (optional, defaults to NullLogger)

Definition at line 74 of file routing_manager.cpp.

80 , repo_(std::move(repo))
81 , job_manager_(std::move(job_mgr))
82 , logger_(logger ? std::move(logger) : di::null_logger()) {
83
85 load_rules();
86
87 if (logger_) {
88 logger_->info_fmt("routing_manager: Initialized with {} rules (enabled={})",
89 rules_.size(), enabled_.load());
90 }
91}
auto config() const noexcept -> const routing_manager_config &
Get current configuration.

References config_, kcenon::pacs::client::routing_manager_config::enabled, enabled_, load_rules(), logger_, and rules_.

Here is the call graph for this function:

◆ ~routing_manager()

kcenon::pacs::client::routing_manager::~routing_manager ( )

Destructor.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 93 of file routing_manager.cpp.

93 {
95}
void detach_from_storage_scp()
Detach from the currently attached Storage SCP.

References detach_from_storage_scp().

Here is the call graph for this function:

◆ routing_manager() [3/4]

kcenon::pacs::client::routing_manager::routing_manager ( const routing_manager & )
delete

◆ routing_manager() [4/4]

kcenon::pacs::client::routing_manager::routing_manager ( routing_manager && )
delete

Member Function Documentation

◆ add_rule()

kcenon::pacs::VoidResult kcenon::pacs::client::routing_manager::add_rule ( const routing_rule & rule) -> kcenon::pacs::VoidResult
nodiscard

Add a new routing rule.

Parameters
ruleThe rule to add
Returns
VoidResult indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 101 of file routing_manager.cpp.

101 {
102 // Validate
103 if (rule.rule_id.empty()) {
104 return kcenon::pacs::pacs_void_error(-1, "Rule ID cannot be empty");
105 }
106
107 {
108 std::shared_lock lock(rules_mutex_);
109 if (rules_.size() >= config_.max_rules) {
110 return kcenon::pacs::pacs_void_error(-1, "Maximum number of rules reached");
111 }
112 }
113
114 // Save to repository
115 auto result = repo_->save(rule);
116 if (result.is_err()) {
118 result.error().code,
119 "Failed to save rule: " + result.error().message);
120 }
121
122 // Update cache
123 {
124 std::unique_lock lock(rules_mutex_);
125 rules_.push_back(rule);
126
127 // Sort by priority (descending)
128 std::sort(rules_.begin(), rules_.end(),
129 [](const routing_rule& a, const routing_rule& b) {
130 return a.priority > b.priority;
131 });
132 }
133
134 if (logger_) {
135 logger_->info_fmt("routing_manager: Added rule: {} ({})", rule.rule_id, rule.name);
136 }
137
138 return kcenon::pacs::ok();
139}
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
size_t max_rules
Maximum number of rules.

References config_, logger_, kcenon::pacs::client::routing_manager_config::max_rules, kcenon::pacs::client::routing_rule::name, kcenon::pacs::pacs_void_error(), repo_, kcenon::pacs::client::routing_rule::rule_id, rules_, and rules_mutex_.

Here is the call graph for this function:

◆ attach_to_storage_scp()

void kcenon::pacs::client::routing_manager::attach_to_storage_scp ( services::storage_scp & scp)

Attach to a Storage SCP for automatic routing.

Registers a post-store handler to automatically route received images.

Parameters
scpThe Storage SCP to attach to
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 443 of file routing_manager.cpp.

443 {
445
446 attached_scp_ = &scp;
447
448 // Register post-store handler
450 [this](const core::dicom_dataset& dataset,
451 const std::string& /*patient_id*/,
452 const std::string& /*study_uid*/,
453 const std::string& /*series_uid*/,
454 const std::string& /*sop_instance_uid*/) {
455 this->route(dataset);
456 });
457
458 if (logger_) {
459 logger_->info("routing_manager: Attached to Storage SCP");
460 }
461}
void route(const core::dicom_dataset &dataset)
Route a DICOM dataset based on matching rules.
void set_post_store_handler(post_store_handler handler)

References attached_scp_, detach_from_storage_scp(), logger_, route(), and kcenon::pacs::services::storage_scp::set_post_store_handler().

Here is the call graph for this function:

◆ config()

const routing_manager_config & kcenon::pacs::client::routing_manager::config ( ) const -> const routing_manager_config&
nodiscardnoexcept

Get current configuration.

Returns
Current manager configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 574 of file routing_manager.cpp.

574 {
575 return config_;
576}

References config_.

◆ detach_from_storage_scp()

void kcenon::pacs::client::routing_manager::detach_from_storage_scp ( )

Detach from the currently attached Storage SCP.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 463 of file routing_manager.cpp.

463 {
464 if (attached_scp_) {
466 attached_scp_ = nullptr;
467
468 if (logger_) {
469 logger_->info("routing_manager: Detached from Storage SCP");
470 }
471 }
472}

References attached_scp_, logger_, and kcenon::pacs::services::storage_scp::set_post_store_handler().

Referenced by attach_to_storage_scp(), and ~routing_manager().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ disable()

void kcenon::pacs::client::routing_manager::disable ( )

Disable routing globally.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 428 of file routing_manager.cpp.

428 {
429 enabled_.store(false);
430 if (logger_) {
431 logger_->info("routing_manager: Routing disabled");
432 }
433}

References enabled_, and logger_.

◆ enable()

void kcenon::pacs::client::routing_manager::enable ( )

Enable routing globally.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 421 of file routing_manager.cpp.

421 {
422 enabled_.store(true);
423 if (logger_) {
424 logger_->info("routing_manager: Routing enabled");
425 }
426}

References enabled_, and logger_.

◆ evaluate()

std::vector< routing_action > kcenon::pacs::client::routing_manager::evaluate ( const core::dicom_dataset & dataset) -> std::vector<routing_action>
nodiscard

Evaluate rules against a dataset.

Returns all actions that should be executed based on matching rules.

Parameters
datasetThe DICOM dataset to evaluate
Returns
Vector of actions to execute
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 291 of file routing_manager.cpp.

291 {
292 std::vector<routing_action> result;
293
294 if (!enabled_.load()) {
295 return result;
296 }
297
299
300 std::shared_lock lock(rules_mutex_);
301
302 for (const auto& rule : rules_) {
303 if (!rule.is_effective_now()) {
304 continue;
305 }
306
307 // Check all conditions (AND logic)
308 bool all_match = !rule.conditions.empty();
309 for (const auto& condition : rule.conditions) {
310 if (!match_condition(condition, dataset)) {
311 all_match = false;
312 break;
313 }
314 }
315
316 if (all_match) {
318
319 // Add all actions from this rule
320 for (const auto& action : rule.actions) {
321 result.push_back(action);
322 }
323
324 // Only first matching rule is executed (stop after first match)
325 // Uncomment the break below if you want first-match-only behavior
326 // break;
327 }
328 }
329
330 return result;
331}
auto match_condition(const routing_condition &condition, const core::dicom_dataset &dataset) const -> bool
Check if a condition matches a dataset.

References enabled_, match_condition(), rules_, rules_mutex_, total_evaluated_, and total_matched_.

Here is the call graph for this function:

◆ evaluate_with_rule_ids()

std::vector< std::pair< std::string, std::vector< routing_action > > > kcenon::pacs::client::routing_manager::evaluate_with_rule_ids ( const core::dicom_dataset & dataset) -> std::vector<std::pair<std::string, std::vector<routing_action>>>
nodiscard

Evaluate rules and return with matched rule IDs.

Parameters
datasetThe DICOM dataset to evaluate
Returns
Vector of pairs (rule_id, actions)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 334 of file routing_manager.cpp.

334 {
335 std::vector<std::pair<std::string, std::vector<routing_action>>> result;
336
337 if (!enabled_.load()) {
338 return result;
339 }
340
342
343 std::shared_lock lock(rules_mutex_);
344
345 for (const auto& rule : rules_) {
346 if (!rule.is_effective_now()) {
347 continue;
348 }
349
350 bool all_match = !rule.conditions.empty();
351 for (const auto& condition : rule.conditions) {
352 if (!match_condition(condition, dataset)) {
353 all_match = false;
354 break;
355 }
356 }
357
358 if (all_match) {
360 result.emplace_back(rule.rule_id, rule.actions);
361 }
362 }
363
364 return result;
365}

References enabled_, match_condition(), rules_, rules_mutex_, total_evaluated_, and total_matched_.

Referenced by route().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ execute_actions()

void kcenon::pacs::client::routing_manager::execute_actions ( const std::string & sop_instance_uid,
const std::vector< routing_action > & actions )
private

Execute routing actions.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 672 of file routing_manager.cpp.

673 {
674 for (const auto& action : actions) {
675 if (action.destination_node_id.empty()) {
676 if (logger_) {
677 logger_->warn_fmt("routing_manager: Skipping action with empty destination for UID: {}",
678 sop_instance_uid);
679 }
680 continue;
681 }
682
683 // Create a store job via job_manager
684 std::vector<std::string> instance_uids{sop_instance_uid};
685
686 auto job_id = job_manager_->create_store_job(
687 action.destination_node_id,
688 instance_uids,
689 action.priority);
690
692
693 if (logger_) {
694 logger_->info_fmt("routing_manager: Created forward job {} for UID {} -> {}",
695 job_id, sop_instance_uid, action.destination_node_id);
696 }
697
698 // Note: Delayed forwarding would require a scheduler/timer
699 // For now, jobs are created immediately
700 if (action.delay.count() > 0 && logger_) {
701 logger_->debug_fmt("routing_manager: Delayed forwarding ({} min) not yet implemented",
702 action.delay.count());
703 }
704 }
705}
constexpr dicom_tag sop_instance_uid
SOP Instance UID.

References job_manager_, logger_, and total_forwarded_.

Referenced by route().

Here is the caller graph for this function:

◆ get_field_value()

std::string kcenon::pacs::client::routing_manager::get_field_value ( routing_field field,
const core::dicom_dataset & dataset ) const -> std::string
nodiscardprivate

Get DICOM field value from dataset.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 634 of file routing_manager.cpp.

635 {
636 switch (field) {
638 return dataset.get_string(core::tags::modality);
639
641 return dataset.get_string(core::tags::station_name);
642
644 return dataset.get_string(core::tags::institution_name);
645
647 return dataset.get_string(institutional_department_name);
648
650 return dataset.get_string(core::tags::referring_physician_name);
651
653 return dataset.get_string(core::tags::study_description);
654
656 return dataset.get_string(core::tags::series_description);
657
659 return dataset.get_string(body_part_examined);
660
662 return dataset.get_string(core::tags::patient_id);
663
665 return dataset.get_string(core::tags::sop_class_uid);
666
667 default:
668 return "";
669 }
670}
@ series_description
(0008,103E) Series Description
@ department
(0008,1040) Institutional Department Name
@ body_part
(0018,0015) Body Part Examined
@ study_description
(0008,1030) Study Description
@ modality
(0008,0060) Modality - CT, MR, US, etc.
@ sop_class_uid
(0008,0016) SOP Class UID
@ institution
(0008,0080) Institution Name
@ station_ae
(0008,1010) Station Name or calling AE
@ patient_id_pattern
(0010,0020) Patient ID (pattern matching)
@ referring_physician
(0008,0090) Referring Physician's Name
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag institution_name
Institution Name.
constexpr dicom_tag study_description
Study Description.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag modality
Modality.
constexpr dicom_tag station_name
Station Name.
constexpr dicom_tag series_description
Series Description.
constexpr dicom_tag sop_class_uid
SOP Class UID.

References kcenon::pacs::client::body_part, kcenon::pacs::client::department, kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::client::institution, kcenon::pacs::core::tags::institution_name, kcenon::pacs::client::modality, kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_id, kcenon::pacs::client::patient_id_pattern, kcenon::pacs::client::referring_physician, kcenon::pacs::core::tags::referring_physician_name, kcenon::pacs::client::series_description, kcenon::pacs::core::tags::series_description, kcenon::pacs::client::sop_class_uid, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::client::station_ae, kcenon::pacs::core::tags::station_name, kcenon::pacs::client::study_description, and kcenon::pacs::core::tags::study_description.

Referenced by match_condition().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_rule()

std::optional< routing_rule > kcenon::pacs::client::routing_manager::get_rule ( std::string_view rule_id) const -> std::optional<routing_rule>
nodiscard

Get a routing rule by ID.

Parameters
rule_idThe rule ID to retrieve
Returns
Optional containing the rule if found
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 202 of file routing_manager.cpp.

202 {
203 std::shared_lock lock(rules_mutex_);
204 auto it = std::find_if(rules_.begin(), rules_.end(),
205 [&](const routing_rule& r) {
206 return r.rule_id == rule_id;
207 });
208 if (it != rules_.end()) {
209 return *it;
210 }
211 return std::nullopt;
212}

References rules_, and rules_mutex_.

◆ get_rule_statistics()

routing_statistics kcenon::pacs::client::routing_manager::get_rule_statistics ( std::string_view rule_id) const -> routing_statistics
nodiscard

Get statistics for a specific rule.

Parameters
rule_idThe rule ID
Returns
Statistics for the specified rule
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 528 of file routing_manager.cpp.

528 {
529 routing_statistics stats;
530
531#ifdef PACS_WITH_DATABASE_SYSTEM
532 auto rule_result = repo_->find_by_id(std::string(rule_id));
533 if (rule_result.is_ok()) {
534 const auto& rule = rule_result.value();
535 stats.total_evaluated = 0; // Not tracked per-rule
536 stats.total_matched = rule.triggered_count;
537 stats.total_forwarded = rule.success_count;
538 stats.total_failed = rule.failure_count;
539 }
540#else
541 auto rule = repo_->find_by_id(rule_id);
542 if (rule) {
543 stats.total_evaluated = 0; // Not tracked per-rule
544 stats.total_matched = rule->triggered_count;
545 stats.total_forwarded = rule->success_count;
546 stats.total_failed = rule->failure_count;
547 }
548#endif
549
550 return stats;
551}

References repo_.

◆ get_statistics()

routing_statistics kcenon::pacs::client::routing_manager::get_statistics ( ) const -> routing_statistics
nodiscard

Get overall routing statistics.

Returns
Aggregate statistics for all routing operations
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 519 of file routing_manager.cpp.

519 {
520 routing_statistics stats;
521 stats.total_evaluated = total_evaluated_.load();
522 stats.total_matched = total_matched_.load();
523 stats.total_forwarded = total_forwarded_.load();
524 stats.total_failed = total_failed_.load();
525 return stats;
526}

References total_evaluated_, total_failed_, total_forwarded_, and total_matched_.

◆ is_enabled()

bool kcenon::pacs::client::routing_manager::is_enabled ( ) const -> bool
nodiscardnoexcept

Check if routing is enabled.

Returns
true if routing is enabled
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 435 of file routing_manager.cpp.

435 {
436 return enabled_.load();
437}

References enabled_.

◆ list_enabled_rules()

std::vector< routing_rule > kcenon::pacs::client::routing_manager::list_enabled_rules ( ) const -> std::vector<routing_rule>
nodiscard

List only enabled routing rules.

Returns
Vector of enabled rules
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 219 of file routing_manager.cpp.

219 {
220 std::shared_lock lock(rules_mutex_);
221 std::vector<routing_rule> result;
222 std::copy_if(rules_.begin(), rules_.end(), std::back_inserter(result),
223 [](const routing_rule& r) { return r.enabled; });
224 return result;
225}

References rules_, and rules_mutex_.

◆ list_rules()

std::vector< routing_rule > kcenon::pacs::client::routing_manager::list_rules ( ) const -> std::vector<routing_rule>
nodiscard

List all routing rules.

Returns
Vector of all rules
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 214 of file routing_manager.cpp.

214 {
215 std::shared_lock lock(rules_mutex_);
216 return rules_;
217}

References rules_, and rules_mutex_.

◆ load_rules()

void kcenon::pacs::client::routing_manager::load_rules ( )
private

Load rules from repository into cache.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 707 of file routing_manager.cpp.

707 {
708#ifdef PACS_WITH_DATABASE_SYSTEM
709 auto loaded_result = repo_->find_enabled_rules();
710 if (loaded_result.is_err()) {
711 if (logger_) {
712 logger_->warn_fmt("routing_manager: Failed to load rules: {}",
713 loaded_result.error().message);
714 }
715 return;
716 }
717
718 std::unique_lock lock(rules_mutex_);
719 rules_ = std::move(loaded_result.value());
720#else
721 auto loaded = repo_->find_enabled_rules();
722
723 std::unique_lock lock(rules_mutex_);
724 rules_ = std::move(loaded);
725#endif
726
727 // Sort by priority (descending)
728 std::sort(rules_.begin(), rules_.end(),
729 [](const routing_rule& a, const routing_rule& b) {
730 return a.priority > b.priority;
731 });
732}

References logger_, repo_, rules_, and rules_mutex_.

Referenced by routing_manager(), and routing_manager().

Here is the caller graph for this function:

◆ match_condition()

bool kcenon::pacs::client::routing_manager::match_condition ( const routing_condition & condition,
const core::dicom_dataset & dataset ) const -> bool
nodiscardprivate

Check if a condition matches a dataset.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 582 of file routing_manager.cpp.

583 {
584 auto value = get_field_value(condition.match_field, dataset);
585 bool matched = match_pattern(condition.pattern, value, condition.case_sensitive);
586
587 // Apply negation if needed
588 if (condition.negate) {
589 matched = !matched;
590 }
591
592 return matched;
593}
auto get_field_value(routing_field field, const core::dicom_dataset &dataset) const -> std::string
Get DICOM field value from dataset.
auto match_pattern(std::string_view pattern, std::string_view value, bool case_sensitive) const -> bool
Match a wildcard pattern against a value.

References kcenon::pacs::client::routing_condition::case_sensitive, get_field_value(), kcenon::pacs::client::routing_condition::match_field, match_pattern(), kcenon::pacs::client::routing_condition::negate, and kcenon::pacs::client::routing_condition::pattern.

Referenced by evaluate(), evaluate_with_rule_ids(), and test_rules().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ match_pattern()

bool kcenon::pacs::client::routing_manager::match_pattern ( std::string_view pattern,
std::string_view value,
bool case_sensitive ) const -> bool
nodiscardprivate

Match a wildcard pattern against a value.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 595 of file routing_manager.cpp.

597 {
598 // Convert to lowercase if case-insensitive
599 std::string pat_str = case_sensitive ? std::string(pattern) : to_lower(pattern);
600 std::string val_str = case_sensitive ? std::string(value) : to_lower(value);
601
602 // Simple wildcard matching (* and ?)
603 // * matches any sequence of characters
604 // ? matches any single character
605
606 const char* p = pat_str.c_str();
607 const char* v = val_str.c_str();
608
609 const char* star_p = nullptr;
610 const char* star_v = nullptr;
611
612 while (*v) {
613 if (*p == '*') {
614 star_p = p++;
615 star_v = v;
616 } else if (*p == '?' || *p == *v) {
617 ++p;
618 ++v;
619 } else if (star_p) {
620 p = star_p + 1;
621 v = ++star_v;
622 } else {
623 return false;
624 }
625 }
626
627 while (*p == '*') {
628 ++p;
629 }
630
631 return *p == '\0';
632}

Referenced by match_condition().

Here is the caller graph for this function:

◆ operator=() [1/2]

auto kcenon::pacs::client::routing_manager::operator= ( const routing_manager & ) -> routing_manager &=delete
delete

◆ operator=() [2/2]

auto kcenon::pacs::client::routing_manager::operator= ( routing_manager && ) -> routing_manager &=delete
delete

◆ remove_rule()

kcenon::pacs::VoidResult kcenon::pacs::client::routing_manager::remove_rule ( std::string_view rule_id) -> kcenon::pacs::VoidResult
nodiscard

Remove a routing rule.

Parameters
rule_idThe ID of the rule to remove
Returns
VoidResult indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 177 of file routing_manager.cpp.

177 {
178 // Remove from repository
179 auto result = repo_->remove(std::string(rule_id));
180 if (!result.is_ok()) {
181 return result;
182 }
183
184 // Remove from cache
185 {
186 std::unique_lock lock(rules_mutex_);
187 rules_.erase(
188 std::remove_if(rules_.begin(), rules_.end(),
189 [&](const routing_rule& r) {
190 return r.rule_id == rule_id;
191 }),
192 rules_.end());
193 }
194
195 if (logger_) {
196 logger_->info_fmt("routing_manager: Removed rule: {}", rule_id);
197 }
198
199 return kcenon::pacs::ok();
200}

References logger_, repo_, rules_, and rules_mutex_.

◆ reorder_rules()

kcenon::pacs::VoidResult kcenon::pacs::client::routing_manager::reorder_rules ( const std::vector< std::string > & rule_ids) -> kcenon::pacs::VoidResult
nodiscard

Reorder rules by specifying the desired order.

Parameters
rule_idsVector of rule IDs in desired order (first = highest priority)
Returns
VoidResult indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 257 of file routing_manager.cpp.

257 {
258 std::unique_lock lock(rules_mutex_);
259
260 // Assign priorities based on order (first = highest)
261 int priority = static_cast<int>(rule_ids.size());
262 for (const auto& rule_id : rule_ids) {
263 auto repo_result = repo_->update_priority(rule_id, priority);
264 if (!repo_result.is_ok()) {
265 return repo_result;
266 }
267
268 auto it = std::find_if(rules_.begin(), rules_.end(),
269 [&](const routing_rule& r) {
270 return r.rule_id == rule_id;
271 });
272 if (it != rules_.end()) {
273 it->priority = priority;
274 }
275 --priority;
276 }
277
278 // Re-sort
279 std::sort(rules_.begin(), rules_.end(),
280 [](const routing_rule& a, const routing_rule& b) {
281 return a.priority > b.priority;
282 });
283
284 return kcenon::pacs::ok();
285}
constexpr dicom_tag priority
Priority.

References repo_, rules_, and rules_mutex_.

◆ reset_statistics()

void kcenon::pacs::client::routing_manager::reset_statistics ( )

Reset all statistics.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 553 of file routing_manager.cpp.

553 {
554 total_evaluated_.store(0);
555 total_matched_.store(0);
556 total_forwarded_.store(0);
557 total_failed_.store(0);
558
559 // Reset per-rule statistics
560 std::shared_lock lock(rules_mutex_);
561 for (const auto& rule : rules_) {
562 auto result = repo_->reset_statistics(rule.rule_id);
563 if (!result.is_ok() && logger_) {
564 logger_->warn_fmt("routing_manager: Failed to reset statistics for rule: {}",
565 rule.rule_id);
566 }
567 }
568}

References logger_, repo_, rules_, rules_mutex_, total_evaluated_, total_failed_, total_forwarded_, and total_matched_.

◆ route() [1/2]

void kcenon::pacs::client::routing_manager::route ( const core::dicom_dataset & dataset)

Route a DICOM dataset based on matching rules.

Evaluates rules and creates forward jobs for matching actions.

Parameters
datasetThe DICOM dataset to route
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 371 of file routing_manager.cpp.

371 {
372 if (!enabled_.load()) {
373 return;
374 }
375
376 auto sop_instance_uid = dataset.get_string(core::tags::sop_instance_uid);
377 if (sop_instance_uid.empty()) {
378 if (logger_) {
379 logger_->warn("routing_manager: Cannot route dataset without SOP Instance UID");
380 }
381 return;
382 }
383
384 auto matches = evaluate_with_rule_ids(dataset);
385
386 for (const auto& [rule_id, actions] : matches) {
387 // Update statistics
388 auto stat_result = repo_->increment_triggered(rule_id);
389 if (!stat_result.is_ok() && logger_) {
390 logger_->warn_fmt("routing_manager: Failed to update statistics for rule: {}",
391 rule_id);
392 }
393
394 // Notify callback
395 if (routing_callback_) {
396 routing_callback_(rule_id, sop_instance_uid, actions);
397 }
398
399 // Execute actions
400 execute_actions(sop_instance_uid, actions);
401 }
402}
auto evaluate_with_rule_ids(const core::dicom_dataset &dataset) -> std::vector< std::pair< std::string, std::vector< routing_action > > >
Evaluate rules and return with matched rule IDs.
void execute_actions(const std::string &sop_instance_uid, const std::vector< routing_action > &actions)
Execute routing actions.

References enabled_, evaluate_with_rule_ids(), execute_actions(), kcenon::pacs::core::dicom_dataset::get_string(), logger_, repo_, routing_callback_, and kcenon::pacs::core::tags::sop_instance_uid.

Referenced by attach_to_storage_scp().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ route() [2/2]

void kcenon::pacs::client::routing_manager::route ( std::string_view sop_instance_uid)

Route a stored instance by SOP Instance UID.

Parameters
sop_instance_uidThe SOP Instance UID to route

Definition at line 404 of file routing_manager.cpp.

404 {
405 if (!enabled_.load()) {
406 return;
407 }
408
409 // Note: This would require loading the dataset from storage
410 // For now, log a warning as this requires additional infrastructure
411 if (logger_) {
412 logger_->warn_fmt("routing_manager: route(sop_instance_uid) not fully implemented - "
413 "use route(dataset) instead. UID: {}", sop_instance_uid);
414 }
415}

References enabled_, and logger_.

◆ set_routing_callback()

void kcenon::pacs::client::routing_manager::set_routing_callback ( routing_event_callback callback)

Set callback for routing events.

Called when a rule matches and actions are triggered.

Parameters
callbackThe callback function
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 478 of file routing_manager.cpp.

478 {
479 routing_callback_ = std::move(callback);
480}

References routing_callback_.

◆ set_rule_priority()

kcenon::pacs::VoidResult kcenon::pacs::client::routing_manager::set_rule_priority ( std::string_view rule_id,
int priority ) -> kcenon::pacs::VoidResult
nodiscard

Set the priority of a rule.

Parameters
rule_idThe rule ID to update
priorityThe new priority (higher = evaluated first)
Returns
VoidResult indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 231 of file routing_manager.cpp.

231 {
232 auto result = repo_->update_priority(rule_id, priority);
233 if (!result.is_ok()) {
234 return result;
235 }
236
237 {
238 std::unique_lock lock(rules_mutex_);
239 auto it = std::find_if(rules_.begin(), rules_.end(),
240 [&](const routing_rule& r) {
241 return r.rule_id == rule_id;
242 });
243 if (it != rules_.end()) {
244 it->priority = priority;
245 }
246
247 // Re-sort
248 std::sort(rules_.begin(), rules_.end(),
249 [](const routing_rule& a, const routing_rule& b) {
250 return a.priority > b.priority;
251 });
252 }
253
254 return kcenon::pacs::ok();
255}

References repo_, rules_, and rules_mutex_.

◆ test_rules()

routing_test_result kcenon::pacs::client::routing_manager::test_rules ( const core::dicom_dataset & dataset) const -> routing_test_result
nodiscard

Test rules against a dataset without executing actions.

Parameters
datasetThe DICOM dataset to test
Returns
Test result showing matches
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 486 of file routing_manager.cpp.

486 {
487 routing_test_result result;
488
489 std::shared_lock lock(rules_mutex_);
490
491 for (const auto& rule : rules_) {
492 if (!rule.is_effective_now()) {
493 continue;
494 }
495
496 bool all_match = !rule.conditions.empty();
497 for (const auto& condition : rule.conditions) {
498 if (!match_condition(condition, dataset)) {
499 all_match = false;
500 break;
501 }
502 }
503
504 if (all_match) {
505 result.matched = true;
506 result.matched_rule_id = rule.rule_id;
507 result.actions = rule.actions;
508 break; // Return first match for test
509 }
510 }
511
512 return result;
513}

References kcenon::pacs::client::routing_test_result::actions, match_condition(), kcenon::pacs::client::routing_test_result::matched, kcenon::pacs::client::routing_test_result::matched_rule_id, rules_, and rules_mutex_.

Here is the call graph for this function:

◆ update_rule()

kcenon::pacs::VoidResult kcenon::pacs::client::routing_manager::update_rule ( const routing_rule & rule) -> kcenon::pacs::VoidResult
nodiscard

Update an existing routing rule.

Parameters
ruleThe rule to update (identified by rule_id)
Returns
VoidResult indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/client/routing_manager.h.

Definition at line 141 of file routing_manager.cpp.

141 {
142 // Save to repository
143 auto result = repo_->save(rule);
144 if (result.is_err()) {
146 result.error().code,
147 "Failed to update rule: " + result.error().message);
148 }
149
150 // Update cache
151 {
152 std::unique_lock lock(rules_mutex_);
153 auto it = std::find_if(rules_.begin(), rules_.end(),
154 [&](const routing_rule& r) {
155 return r.rule_id == rule.rule_id;
156 });
157 if (it != rules_.end()) {
158 *it = rule;
159 } else {
160 rules_.push_back(rule);
161 }
162
163 // Re-sort by priority
164 std::sort(rules_.begin(), rules_.end(),
165 [](const routing_rule& a, const routing_rule& b) {
166 return a.priority > b.priority;
167 });
168 }
169
170 if (logger_) {
171 logger_->info_fmt("routing_manager: Updated rule: {} ({})", rule.rule_id, rule.name);
172 }
173
174 return kcenon::pacs::ok();
175}

References logger_, kcenon::pacs::client::routing_rule::name, kcenon::pacs::pacs_void_error(), repo_, kcenon::pacs::client::routing_rule::rule_id, rules_, and rules_mutex_.

Here is the call graph for this function:

Member Data Documentation

◆ attached_scp_

services::storage_scp* kcenon::pacs::client::routing_manager::attached_scp_ {nullptr}
private

◆ config_

routing_manager_config kcenon::pacs::client::routing_manager::config_
private

◆ enabled_

std::atomic<bool> kcenon::pacs::client::routing_manager::enabled_ {true}
private

◆ job_manager_

std::shared_ptr<job_manager> kcenon::pacs::client::routing_manager::job_manager_
private

◆ logger_

◆ repo_

◆ routing_callback_

routing_event_callback kcenon::pacs::client::routing_manager::routing_callback_
private

◆ rules_

◆ rules_mutex_

◆ total_evaluated_

std::atomic<size_t> kcenon::pacs::client::routing_manager::total_evaluated_ {0}
private

◆ total_failed_

std::atomic<size_t> kcenon::pacs::client::routing_manager::total_failed_ {0}
private

◆ total_forwarded_

std::atomic<size_t> kcenon::pacs::client::routing_manager::total_forwarded_ {0}
private

◆ total_matched_

std::atomic<size_t> kcenon::pacs::client::routing_manager::total_matched_ {0}
private

The documentation for this class was generated from the following files: