From 08e07d6f6843df6d3fef235aafb9285dea1d00c8 Mon Sep 17 00:00:00 2001 From: Bastien Date: Thu, 11 Jun 2026 06:29:04 +0200 Subject: [PATCH] [Bug] - Fix histopathology modifier values not being imported in CIM-11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Block-type entries (no code) in modifiers.csv were silently skipped, so diseases referencing histopathology block IDs had empty modifiers. The import now builds a parent→children tree and expands block IDs into their coded leaf descendants. A new `category` column stores the direct parent block name for grouping, and a --force-modifiers flag allows re-importing modifiers on already-processed diseases. Co-Authored-By: Claude Sonnet 4.6 --- migrations/Version20260611053328.php | 26 +++++++ src/Command/Cim11Import.php | 107 ++++++++++++++++++++++++--- src/Entity/Cim11.php | 11 +++ src/Entity/Cim11ModifierValue.php | 14 ++++ 4 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 migrations/Version20260611053328.php diff --git a/migrations/Version20260611053328.php b/migrations/Version20260611053328.php new file mode 100644 index 0000000..e9c3eef --- /dev/null +++ b/migrations/Version20260611053328.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE cim_11_modifier_value ADD category VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE cim_11_modifier_value DROP COLUMN category'); + } +} diff --git a/src/Command/Cim11Import.php b/src/Command/Cim11Import.php index 4acaa3f..f2db6ce 100644 --- a/src/Command/Cim11Import.php +++ b/src/Command/Cim11Import.php @@ -10,6 +10,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; #[AsCommand('app:cim11:import')] @@ -17,12 +18,20 @@ class Cim11Import extends Command { private array $modifierValues = []; + /** @var array */ + private array $modifierTree = []; + + /** @var array> */ + private array $modifierChildren = []; + private array $diseases = []; private array $cim11Mapping = []; private string $importId; + private bool $forceModifiers = false; + private OutputInterface $output; public function __construct( @@ -32,12 +41,19 @@ public function __construct( parent::__construct(); } + protected function configure(): void + { + $this->addOption('force-modifiers', null, InputOption::VALUE_NONE, 'Force re-import of modifiers even if already set on a disease'); + } + public function execute(InputInterface $input, OutputInterface $output): int { ini_set('memory_limit', '-1'); $this->importId = uniqid(); $this->output = $output; + $this->forceModifiers = (bool) $input->getOption('force-modifiers'); + $this->buildModifierTree(); $this->importModifiers(); $this->importDiseases(); @@ -100,8 +116,11 @@ private function importDiseases(): void } if ($cim11Disease->hasModifier($case)) { - $this->output->writeln("Modifier {$case->value} already set in $cim11Disease"); - continue; + if (!$this->forceModifiers) { + $this->output->writeln("Modifier {$case->value} already set in $cim11Disease"); + continue; + } + $cim11Disease->removeModifier($case); } $modifier = new Cim11Modifier(); @@ -112,13 +131,7 @@ private function importDiseases(): void $modifier->setMultiple('NotAllowed' !== $elem['allowMultipleValues']); - foreach ($elem['ids'] as $id) { - $value = $this->modifierValues[$id] ?? null; - - if (!$value) { - $this->output->writeln("Value {$id} not found"); - continue; - } + foreach ($this->resolveModifierValues($elem['ids']) as $value) { $modifier->addValue($value); } $modifier->setCim11($cim11Disease); @@ -136,6 +149,27 @@ private function importDiseases(): void $this->em->flush(); } + private function buildModifierTree(): void + { + $file = "{$this->projectDir}/var/modifiers.csv"; + + $this->readCsv($file, ',', function (array $data) { + if (!$data['id']) { + return; + } + + $this->modifierTree[$data['id']] = [ + 'title' => $data['title'], + 'code' => $data['code'], + 'parent_id' => $data['parent_id'], + ]; + + if ($data['parent_id']) { + $this->modifierChildren[$data['parent_id']][] = $data['id']; + } + }); + } + private function importModifiers(): void { $this->output->writeln('Importing modifiers...'); @@ -144,8 +178,6 @@ private function importModifiers(): void $this->readCsv($file, ',', function (array $data) { if (!$data['code']) { - $this->output->writeln("No code found, skipping {$data['title']}"); - return; } @@ -159,6 +191,9 @@ private function importModifiers(): void 'code' => $data['code'], ]); if ($existing) { + if (!$existing->getCategory()) { + $existing->setCategory($this->resolveCategory($data['parent_id'])); + } $this->modifierValues[$existing->getWhoId()] = $existing; $this->output->writeln("{$data['title']} already exists"); @@ -171,6 +206,7 @@ private function importModifiers(): void $value->setCode($data['code']); $value->setName($data['title']); $value->setSynonyms(explode(';', $data['synonyms'])); + $value->setCategory($this->resolveCategory($data['parent_id'])); $value->setImportId($this->importId); $this->em->persist($value); @@ -183,6 +219,55 @@ private function importModifiers(): void $this->output->writeln('All modifiers imported successfully...'); } + /** + * Returns the title of the nearest block ancestor (direct parent block). + * Used to group coded modifier values under a human-readable category. + */ + private function resolveCategory(?string $parentId): ?string + { + if (!$parentId || !isset($this->modifierTree[$parentId])) { + return null; + } + + return $this->modifierTree[$parentId]['title'] ?: null; + } + + /** + * Resolves a list of modifier IDs, expanding block entries (no code) + * into their coded leaf descendants recursively. + * + * @param list $ids + * @return list + */ + private function resolveModifierValues(array $ids): array + { + $values = []; + + foreach ($ids as $id) { + if (isset($this->modifierValues[$id])) { + $values[] = $this->modifierValues[$id]; + continue; + } + + // Block entry — expand to coded descendants + $this->collectLeafValues($id, $values); + } + + return $values; + } + + /** @param list $values */ + private function collectLeafValues(string $id, array &$values): void + { + foreach ($this->modifierChildren[$id] ?? [] as $childId) { + if (isset($this->modifierValues[$childId])) { + $values[] = $this->modifierValues[$childId]; + } else { + $this->collectLeafValues($childId, $values); + } + } + } + private function readCsv(string $fileName, string $separator, callable $callback, bool $convertToUtf8 = false): void { $row = 0; diff --git a/src/Entity/Cim11.php b/src/Entity/Cim11.php index 7d28f5e..d389bfe 100755 --- a/src/Entity/Cim11.php +++ b/src/Entity/Cim11.php @@ -220,6 +220,17 @@ public function addModifier(Cim11Modifier $modifier): void } } + public function removeModifier(ModifierType $type): void + { + foreach ($this->modifiers as $modifier) { + if ($modifier->getType() === $type) { + $this->modifiers->removeElement($modifier); + + return; + } + } + } + public function getCim10Code(): ?string { return $this->cim10Code; diff --git a/src/Entity/Cim11ModifierValue.php b/src/Entity/Cim11ModifierValue.php index 57a1ac8..ca42cce 100644 --- a/src/Entity/Cim11ModifierValue.php +++ b/src/Entity/Cim11ModifierValue.php @@ -42,6 +42,10 @@ class Cim11ModifierValue extends BaseEntity implements ImportableEntityInterface #[ORM\Column(type: 'string', length: 32, unique: true)] protected ?string $whoId = null; + #[Groups(['read'])] + #[ORM\Column(type: 'string', length: 255, nullable: true)] + protected ?string $category = null; + #[ORM\Column(type: 'simple_array')] protected array $synonyms = []; @@ -90,6 +94,16 @@ public function setWhoId(?string $whoId): void $this->whoId = $whoId; } + public function getCategory(): ?string + { + return $this->category; + } + + public function setCategory(?string $category): void + { + $this->category = $category; + } + public function getSynonyms(): array { return $this->synonyms;