Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mapping-cim-11.csv
Original file line number Diff line number Diff line change
Expand Up @@ -15559,3 +15559,4 @@ category,2,U84.9,XXII,Resistance to unspecified antimicrobial drugs,category,2,h
category,2,U84.9,XXII,Resistance to unspecified antimicrobial drugs,category,2,http://id.who.int/icd/entity/40564846,http://id.who.int/icd/release/11/2022-02/mms/40564846/unspecified,MG51.1Z,21,Streptococcus pneumoniae resistant to unspecified antibiotic,
category,2,U84.9,XXII,Resistance to unspecified antimicrobial drugs,category,2,http://id.who.int/icd/entity/612672352,http://id.who.int/icd/release/11/2022-02/mms/612672352,MG56,21,Finding of microorganism resistant to other multiple antimicrobial drugs,
category,1,U85,XXII,Resistance to antineoplastic drugs,category,1,http://id.who.int/icd/entity/1882742628,http://id.who.int/icd/release/11/2022-02/mms/1882742628/unspecified,MG5Z,21,"Finding of microorganism resistant to antimicrobial drugs, unspecified"
category,1,SD82,XXII,Depression (TM1),category,1,http://id.who.int/icd/entity/1882742628,http://id.who.int/icd/release/11/2022-02/mms/1882742628/unspecified,F32.9,21,"Episode dépressif, sans précision"
19 changes: 18 additions & 1 deletion src/ApiPlatform/Filter/AllergenFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,24 @@ protected function filterProperty(
return;
}

$this->addSearchFilter($queryBuilder, $value, $context['languages'] ?? []);
if ('search' === $property) {
$this->addSearchFilter($queryBuilder, $value, $context['languages'] ?? []);
}

if ('excluded_categories' === $property) {
$this->addExcludedCategoriesFilter($queryBuilder, $value);
}
}

public function addExcludedCategoriesFilter(QueryBuilder $queryBuilder, mixed $value): void
{
if (!is_array($value)) {
$value = [$value];
}

$alias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere("$alias.group NOT IN (:excluded_categories)");
$queryBuilder->setParameter('excluded_categories', $value);
}

/**
Expand Down
17 changes: 17 additions & 0 deletions src/ApiPlatform/Filter/Cim11Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ protected function filterProperty(
return;
}

if ('withCim10' === $property) {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$this->addWithCim10Code($queryBuilder, $value);

return;
}

if ('ids' === $property) {
$this->addIdsFilter($queryBuilder, $value);
}
Expand Down Expand Up @@ -87,6 +94,16 @@ protected function addCim10Code(QueryBuilder $queryBuilder, string $value): Quer
return $queryBuilder;
}

private function addWithCim10Code(QueryBuilder $queryBuilder, ?bool $value): void
{
if (!$value) {
return;
}

$alias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere("$alias.cim10Code IS NOT NULL");
}

/**
* @throws Exception
*/
Expand Down
14 changes: 13 additions & 1 deletion src/ApiPlatform/Filter/RPPSFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\City;
use App\Entity\RPPS;
use App\Entity\Specialty;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doctrine\ORM\Query\Expr\Join is imported but not used anywhere in this file. Please remove the unused use to avoid dead imports and keep the file clean.

Suggested change
use Doctrine\ORM\Query\Expr\Join;

Copilot uses AI. Check for mistakes.
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
Expand Down Expand Up @@ -207,7 +209,17 @@ protected function addExcludedRppsFilter(QueryBuilder $queryBuilder, mixed $excl

$alias = $queryBuilder->getRootAliases()[0];

$queryBuilder->andWhere("$alias.idRpps NOT IN (:excludedRpps)")
$subQb = $this->em->createQueryBuilder();
$subQb->select('1')
->from(RPPS::class, 'exclusion')
->where("exclusion.idRpps = $alias.idRpps")
->andWhere('exclusion.idRpps IN (:excludedRpps)');

$queryBuilder->andWhere(
$queryBuilder->expr()->not(
$queryBuilder->expr()->exists($subQb->getDQL())
)
)
->setParameter('excludedRpps', $excludedRpps);

return $queryBuilder;
Expand Down
22 changes: 21 additions & 1 deletion src/Command/Cim11Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private function importDiseases(): void
$hierarchyLevel = strlen($explode[1] ?? '') + $basicHierarchyLevel;

$cim11Disease->setHierarchyLevel($hierarchyLevel);
$cim11Disease->setCim10Code($this->cim11Mapping[$data['code']] ?? null);
$cim11Disease->setCim10Code($this->getCim10Code($data['code']));
$cim11Disease->setImportId($this->importId);

foreach (ModifierType::cases() as $case) {
Expand Down Expand Up @@ -234,4 +234,24 @@ private function buildCim10Cim11Database(): void
$this->cim11Mapping[$data['icd11Code']] = $data['icd10Code'];
});
}

private function getCim10Code(string $code): ?string
{
if ('SD82' === $code) {
dump($this->cim11Mapping[$code]);
exit;
}

Comment on lines +240 to +244
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCim10Code() contains a hard-coded dump() + exit for code SD82. This will abort the import command in real usage and must be removed (or replaced with proper logging/handling) before merging.

Suggested change
if ('SD82' === $code) {
dump($this->cim11Mapping[$code]);
exit;
}

Copilot uses AI. Check for mistakes.
if (isset($this->cim11Mapping[$code])) {
return $this->cim11Mapping[$code];
}

if (str_ends_with($code, '0')) {
$codeWithoutLastZero = substr($code, 0, -1);
Comment on lines +238 to +250
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback mapping for codes ending with 0 uses substr($code, 0, -1), which turns CA00.0 into CA00.. The mapping CSV stores ICD-11 codes without the trailing dot (e.g. CA00), so this fallback will still miss and return null. Consider normalizing the code by removing a trailing .0 (and/or trimming a trailing .) before looking up $this->cim11Mapping.

Suggested change
private function getCim10Code(string $code): ?string
{
if ('SD82' === $code) {
dump($this->cim11Mapping[$code]);
exit;
}
if (isset($this->cim11Mapping[$code])) {
return $this->cim11Mapping[$code];
}
if (str_ends_with($code, '0')) {
$codeWithoutLastZero = substr($code, 0, -1);
private function normalizeIcd11Code(string $code): string
{
// Remove a trailing ".0" (e.g. "CA00.0" -> "CA00")
$code = preg_replace('/\.0$/', '', $code);
// Trim any remaining trailing dot (e.g. "CA00." -> "CA00")
return rtrim($code, '.');
}
private function getCim10Code(string $code): ?string
{
$normalizedCode = $this->normalizeIcd11Code($code);
if ('SD82' === $normalizedCode) {
dump($this->cim11Mapping[$normalizedCode]);
exit;
}
if (isset($this->cim11Mapping[$normalizedCode])) {
return $this->cim11Mapping[$normalizedCode];
}
if (str_ends_with($normalizedCode, '0')) {
$codeWithoutLastZero = substr($normalizedCode, 0, -1);

Copilot uses AI. Check for mistakes.

return $this->cim11Mapping[$codeWithoutLastZero] ?? null;
}

return null;
}
}
22 changes: 22 additions & 0 deletions src/DataFixtures/LoadDCim11.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,28 @@ public function load(ObjectManager $manager): void

$this->em->persist($disease3);

// Add a disease without cim10Code for testing withCim10 filter
$disease4 = new Cim11();
$disease4->setCode('TEST01');
$disease4->setName('Test maladie sans CIM-10');
$disease4->setWhoId('9999999999');
$disease4->setHierarchyLevel(2);
$disease4->setCim10Code(null); // Explicitly set to null
$disease4->setSynonyms([
'test disease',
'maladie test',
]);

$disease4Ts = new Translation();
$disease4Ts->setLang('en');
$disease4Ts->setField('name');
$disease4Ts->setTranslation('Test disease without CIM-10');
$disease4->addTranslation($disease4Ts);

$disease4->setImportId('import_1');

$this->em->persist($disease4);

$this->em->flush();
}
}
4 changes: 2 additions & 2 deletions src/Entity/Allergen.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// Liste extracted from
// https://biologiepathologie.chu-lille.fr/fichiers/42_795catalogue-rast-i.pdf

#[ApiFilter(AllergenFilter::class, properties: ['search'])]
#[ApiFilter(AllergenFilter::class, properties: ['search', 'excluded_categories'])]
#[ORM\Entity(repositoryClass: AllergenRepository::class)]
#[ORM\Table(name: 'allergens')]
#[ORM\Index(columns: ['allergen_code'], name: 'allergens_index')]
Expand All @@ -37,7 +37,7 @@
),
],
paginationClientEnabled: true,
paginationPartial: true,
paginationPartial: false,
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting paginationPartial: false is a behavioral and performance change (it re-enables full pagination counts). Most other API resources in this repo use paginationPartial: true (e.g. src/Entity/Cim11.php, src/Entity/City.php). Please confirm this is intentional for allergens and add a brief rationale (or revert if not needed).

Suggested change
paginationPartial: false,
paginationPartial: true,

Copilot uses AI. Check for mistakes.
)]
class Allergen extends BaseEntity implements TranslatableEntityInterface
{
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/CCAM.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
),
],
paginationClientEnabled: true,
paginationPartial: true,
paginationPartial: false,
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting paginationPartial: false is a behavioral and performance change (it re-enables full pagination counts). Most other API resources in this repo use paginationPartial: true (e.g. src/Entity/Cim11.php, src/Entity/City.php). Please confirm this is intentional for CCAM and add a brief rationale (or revert if not needed).

Suggested change
paginationPartial: false,
paginationPartial: true,

Copilot uses AI. Check for mistakes.
)]
class CCAM extends BaseEntity implements ImportableEntityInterface
{
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Cim11.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Symfony\Component\Serializer\Attribute\Groups;

#[ORM\Entity(repositoryClass: Cim11Repository::class)]
#[ApiFilter(Cim11Filter::class, properties: ['search', 'ids', 'cim10Code'])]
#[ApiFilter(Cim11Filter::class, properties: ['search', 'ids', 'cim10Code', 'withCim10'])]
#[ORM\Table(name: 'cim_11')]
#[ORM\Index(columns: ['code'])]
#[UniqueEntity(['code', 'whoId'])]
Expand Down
1 change: 1 addition & 0 deletions src/Entity/RPPS.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
new ORM\Index(name: 'last_name_index', columns: ['last_name']),
new ORM\Index(name: 'full_name_index', columns: ['full_name']),
new ORM\Index(name: 'full_name_inversed_index', columns: ['full_name_inversed']),
new ORM\Index(name: 'idx_rpps_specialty_id_rpps', columns: ['specialty', 'id_rpps']),
new ORM\Index(name: 'rpps_index', columns: ['id_rpps']),
new ORM\Index(columns: ['canonical'], name: 'canonical_index'),
])]
Expand Down
23 changes: 23 additions & 0 deletions tests/Functional/AllergenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,27 @@ public function testGetData() : void
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertEquals('Chymopapaïne', $data['name']);
}

/**
* @throws ClientExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
* @throws TransportExceptionInterface
*/
public function testExcludedCategoriesFilter(): void
{
// Test excluding a single group
$data = $this->get('allergens', ['excluded_categories' => 'Médicaments']);
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertCollectionKeyNotContains($data['hydra:member'], 'group', ['Médicaments']);

// Test excluding multiple groups
$excludedGroups = ['Médicaments', 'Pollens de graminées'];
$data = $this->get('allergens', ['excluded_categories' => $excludedGroups]);
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertCollectionKeyNotContains($data['hydra:member'], 'group', $excludedGroups);

// Verify that at least some allergens remain after exclusion
$this->assertEquals(0, count($data['hydra:member']), 'No more allergens available');
Comment on lines +97 to +98
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment says you want to verify that some allergens remain after excluding categories, but the assertion expects an empty result set. Given fixtures only include the excluded groups, either update the comment to match the expected empty set (or add fixtures/assertions that ensure at least one allergen remains).

Suggested change
// Verify that at least some allergens remain after exclusion
$this->assertEquals(0, count($data['hydra:member']), 'No more allergens available');
// Verify that no allergens remain after exclusion when only excluded groups exist in fixtures
$this->assertEquals(0, count($data['hydra:member']), 'All allergens should be excluded');

Copilot uses AI. Check for mistakes.
}
}
36 changes: 36 additions & 0 deletions tests/Functional/Cim11sTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,40 @@ public function testGetData(): void
$this->assertEquals('Acute Nasopharyngitis', $data['name']);
$this->assertEquals('This is an english synonym', $data['synonyms'][0]);
}

/**
* @throws ClientExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
* @throws TransportExceptionInterface
*/
public function testWithCim10Filter(): void
{
// Test with withCim10=true - should only return entries with cim10Code
$data = $this->get('cim11s', ['withCim10' => 'true']);
$this->assertResponseStatusCodeSame(Response::HTTP_OK);

// All returned items should have a cim10Code
foreach ($data['hydra:member'] as $item) {
$this->assertNotNull($item['cim10Code'], 'All items should have a cim10Code when withCim10=true');
}

// Verify we have the expected items with cim10Code
$this->assertCollectionKeyContains($data['hydra:member'], 'cim10Code', ['C50.9', 'J00', 'J0B']);

// Test with withCim10=false - should return all entries
$dataAll = $this->get('cim11s', ['withCim10' => 'false']);
$this->assertResponseStatusCodeSame(Response::HTTP_OK);

// Should have more or equal items when not filtering
$this->assertGreaterThanOrEqual(count($data['hydra:member']), count($dataAll['hydra:member']));
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The count comparison is reversed: when withCim10=false (no filtering), the collection should have more or equal items than the filtered withCim10=true result. The current assertion checks the opposite and will fail when the filter actually reduces results.

Copilot uses AI. Check for mistakes.

// Test with withCim10=1 (also should be treated as true)
$data2 = $this->get('cim11s', ['withCim10' => '1']);
$this->assertResponseStatusCodeSame(Response::HTTP_OK);

foreach ($data2['hydra:member'] as $item) {
$this->assertNotNull($item['cim10Code'], 'All items should have a cim10Code when withCim10=1');
}
}
}