130 lines
4.1 KiB
PHP
130 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\StudyRouter;
|
|
|
|
use App\Domain\ACL\Role;
|
|
use App\Domain\Rule\MatchCondition;
|
|
use App\Domain\Rule\MatchMode;
|
|
use App\Models\AssignmentPanel;
|
|
use App\Models\DicomRoutingRule;
|
|
use App\Models\DicomRuleCondition;
|
|
use App\Models\Organization;
|
|
use App\Models\User;
|
|
use App\Services\ContentMatcher;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
final class DicomStudyRouter
|
|
{
|
|
private static ?Collection $rules;
|
|
private static ?Collection $activeRads;
|
|
private static int $catchAll = -1;
|
|
|
|
const int CACHE_TTL = 15;
|
|
|
|
public static function matchStudy(array $dicomHeaders): array
|
|
{
|
|
self::initialize();
|
|
|
|
if (blank($dicomHeaders)) {
|
|
return self::fallbackRouting();
|
|
}
|
|
|
|
foreach (self::$rules as $rule) {
|
|
$conditions = $rule->conditions()->orderByDesc('priority')->get();
|
|
$matchCondition = MatchCondition::from($rule->match_condition);
|
|
$matches = $matchCondition === MatchCondition::ALL
|
|
? $conditions->every(fn ($condition) => self::matchCondition($condition, $dicomHeaders))
|
|
: $conditions->contains(fn ($condition) => self::matchCondition($condition, $dicomHeaders));
|
|
|
|
if ($matches) {
|
|
return [
|
|
'organization_id' => $rule->organization_id,
|
|
'department_id' => $rule->department_id,
|
|
'rule_id' => $rule->id,
|
|
'radiologists' => self::getRadiologists($rule),
|
|
];
|
|
}
|
|
}
|
|
|
|
return self::fallbackRouting();
|
|
}
|
|
|
|
private static function fallbackRouting(): array
|
|
{
|
|
return [
|
|
'organization_id' => self::$catchAll,
|
|
'department_id' => null,
|
|
'rule_id' => null,
|
|
'radiologists' => null,
|
|
];
|
|
}
|
|
|
|
private static function initialize(): void
|
|
{
|
|
if (is_null(self::$rules)) {
|
|
self::$rules = Cache::remember('dicom.rules',
|
|
now()->addMinutes(self::CACHE_TTL),
|
|
fn () => DicomRoutingRule::active()
|
|
->with('conditions')
|
|
->orderByDesc('priority')
|
|
->get()
|
|
);
|
|
}
|
|
|
|
if (is_null(self::$activeRads)) {
|
|
self::$activeRads = Cache::remember('dicom.rads',
|
|
now()->addMinutes(self::CACHE_TTL),
|
|
fn () => User::active()
|
|
->role(Role::Radiologist)
|
|
->pluck('id')
|
|
);
|
|
}
|
|
|
|
if (self::$catchAll < 0) {
|
|
self::$catchAll = Organization::where('name', 'CATCH-ALL')
|
|
->first('id')
|
|
->id;
|
|
}
|
|
}
|
|
|
|
private static function matchCondition(DicomRuleCondition $condition, array $dicomHeaders): bool
|
|
{
|
|
$dicomTag = $condition->dicom_tag;
|
|
$dicomValue = $dicomHeaders[$dicomTag] ?? '';
|
|
$searchPattern = $condition->search_pattern;
|
|
$matchMode = MatchMode::from($condition->match_mode);
|
|
|
|
if (! $condition->case_sensitive) {
|
|
$dicomValue = strtolower($dicomValue);
|
|
if ($matchMode != MatchMode::Regex) {
|
|
$searchPattern = strtolower($searchPattern);
|
|
}
|
|
}
|
|
|
|
return ContentMatcher::match($dicomValue, $searchPattern, $matchMode);
|
|
}
|
|
|
|
private static function getRadiologists(DicomRoutingRule $rule): array
|
|
{
|
|
if (!is_null($rule->assignment_panel_id)) {
|
|
$panel = AssignmentPanel::active()
|
|
->with('radiologists:id')
|
|
->find($rule->assignment_panel_id);
|
|
if ($panel) {
|
|
$rads = $panel->radiologists->pluck('id')->toArray();
|
|
if (! empty($rads)) {
|
|
// return only existing id from self::$activeRads
|
|
return array_intersect($rads, self::$activeRads->toArray());
|
|
}
|
|
}
|
|
} elseif (!is_null($rule->radiologist_id)) {
|
|
if (self::$activeRads->contains($rule->radiologist_id)) {
|
|
return [$rule->radiologist_id];
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|