{"id":50972830,"url":"https://github.com/dridk/smt2parquet","last_synced_at":"2026-06-19T04:01:54.828Z","repository":{"id":357221907,"uuid":"1235916286","full_name":"dridk/smt2parquet","owner":"dridk","description":"Création de fichier parquet à partir des fichiers de terminologies SMT","archived":false,"fork":false,"pushed_at":"2026-05-31T13:18:01.000Z","size":34,"stargazers_count":0,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T13:20:09.548Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dridk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T19:26:08.000Z","updated_at":"2026-05-31T13:18:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dridk/smt2parquet","commit_stats":null,"previous_names":["dridk/smt2parquet"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dridk/smt2parquet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dridk%2Fsmt2parquet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dridk%2Fsmt2parquet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dridk%2Fsmt2parquet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dridk%2Fsmt2parquet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dridk","download_url":"https://codeload.github.com/dridk/smt2parquet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dridk%2Fsmt2parquet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34516549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-19T04:01:51.729Z","updated_at":"2026-06-19T04:01:54.811Z","avatar_url":"https://github.com/dridk.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# smt2parquet\n\nConvertit les terminologies médicales du portail [**SMT**](https://smt.esante.gouv.fr/) (Serveur Multi-Terminologies, eSanté France) — fichiers RDF — vers du **Parquet** en préservant la hiérarchie via le [**modèle d'imbrication d'ensembles**](https://fr.wikipedia.org/wiki/Imbrication_d%27ensembles) (*nested set*).\n\nLe résultat : un Parquet par terminologie avec, pour chaque concept, les colonnes `code`, `label`, `path`, et le triplet `lft`/`rgt`/`depth` qui permettent de requêter ancêtres et descendants par une simple comparaison d'intervalles.\n\nLes fichiers parquets sont directement disponnible pour utilisation sur [data.gouv](https://www.data.gouv.fr/datasets/terminologie-au-format-parquet).\n\nCes fichiers peuvent être consommer avec de nombreux outils comme [duckdb](https://duckdb.org/), [pola.rs](https://pola.rs/) ou [clickhouse](https://clickhouse.com/).\n\n## Installation\n\n```bash\ngit clone https://github.com/dridk/smt2parquet.git\ncd smt2parquet\nuv sync\n```\n\n## Générer un Parquet\n\nLe principe est le même pour les trois terminologies :\n\n1. **Télécharger le RDF** depuis le portail [SMT](https://smt.esante.gouv.fr/) (sélectionner la terminologie, puis exporter au format RDF).\n2. **Le placer dans `rdf/`** sans renommer — le nom de fichier doit suivre le motif attendu (la version en est extraite automatiquement) :\n   - CIM10 → `rdf/terminologie-cim-10-\u003cversion\u003e.rdf` (ex. `terminologie-cim-10-2025-01-01.rdf`)\n   - CCAM → `rdf/terminologie-ccam-\u003cversion\u003e.rdf`\n   - ADICAP → `rdf/terminologie-adicap-\u003cversion\u003e.rdf` (ex. `terminologie-adicap-2024-10.rdf`)\n3. **Lancer la conversion** :\n   ```bash\n   uv run python -m smt2parquet cim10    # ou ccam, ou adicap\n   ```\n\nLes détails (commande, colonnes, exemples) propres à chaque terminologie sont dans les sections ci-dessous.\n\n## Le modèle nested set\n\n\nPour tout nœud P, ses descendants sont les lignes où lft ∈ ]P.lft, P.rgt[\n                  ses ancêtres   sont les lignes où lft \u003c P.lft ET rgt \u003e P.rgt\n\n\nLes nœuds avec plusieurs parents (DAG) sont **dupliqués** : chaque occurrence sous un parent différent reçoit son propre `lft`/`rgt`/`depth`/`path`. \n\nChaque Parquet embarque aussi des **métadonnées** dans le footer (`terminology`, `version`, `source_file`, `generated_at`) — voir l'exemple de lecture dans chaque section.\n\n---\n\n## CIM10\n\nClassification internationale des maladies, 10ᵉ révision (ICD-10)\n\n```bash\nuv run python -m smt2parquet cim10\n# rdf/terminologie-cim-10-\u003cversion\u003e.rdf  →  parquet/cim10-\u003cversion\u003e.parquet\n```\n\n### Colonnes\n\n| Colonne | Type | Description |\n|---|---|---|\n| `code` | `str` | Code du concept (`skos:notation`) |\n| `label` | `str` | Libellé (`rdfs:label`) |\n| `type` | `str` | `chapter` / `block` / `category` (`dc:type`) |\n| `depth` | `i64` | Profondeur dans l'arbre (0 = chapitre) |\n| `lft` | `i64` | Borne gauche du nested set |\n| `rgt` | `i64` | Borne droite du nested set |\n| `path` | `str` | Chaîne des codes des ancêtres, ex. `I/A00-A09/A00/A00.0` |\n| `synonymes` | `list[str]` | `skos:altLabel` dédupliqués |\n| `inclusion_note` | `str?` | `xkos:inclusionNote` |\n\n### Exemples\n\n```sql\n-- Lister les chapitres (depth = 0)\nSELECT code, label, lft, rgt\nFROM 'parquet/cim10-2025-01-01.parquet'\nWHERE depth = 0\nORDER BY lft;\n```\n\n```sql\n-- Tous les descendants du chapitre I\nWITH chapitre AS (\n    SELECT lft AS l, rgt AS r\n    FROM 'parquet/cim10-2025-01-01.parquet'\n    WHERE code = 'I'\n)\nSELECT code, label, depth\nFROM 'parquet/cim10-2025-01-01.parquet', chapitre\nWHERE lft BETWEEN chapitre.l AND chapitre.r\nORDER BY lft;\n```\n\n```sql\n-- Tous les ancêtres du code A00.0\nWITH cible AS (\n    SELECT lft AS l, rgt AS r\n    FROM 'parquet/cim10-2025-01-01.parquet'\n    WHERE code = 'A00.0'\n)\nSELECT code, label, depth\nFROM 'parquet/cim10-2025-01-01.parquet', cible\nWHERE cible.l BETWEEN lft AND rgt\nORDER BY depth;\n```\n\n```python\n# En Python avec DuckDB\nimport duckdb\n\ndescendants = duckdb.sql(\"\"\"\n    WITH t AS (SELECT lft, rgt FROM 'parquet/cim10-2025-01-01.parquet' WHERE code = 'A00')\n    SELECT c.code, c.label, c.depth\n    FROM 'parquet/cim10-2025-01-01.parquet' c, t\n    WHERE c.lft BETWEEN t.lft AND t.rgt\n    ORDER BY c.lft\n\"\"\").pl()  # → DataFrame Polars\n```\n\n```python\n# Lire les métadonnées du fichier\nimport pyarrow.parquet as pq\n\nmd = pq.read_metadata(\"parquet/cim10-2025-01-01.parquet\").metadata\nprint({k.decode(): v.decode() for k, v in md.items() if not k.startswith(b\"ARROW\")})\n# {'terminology': 'cim10', 'version': '2025-01-01', 'source_file': '...', 'generated_at': '...'}\n```\n\n---\n\n## CCAM\n\nClassification commune des actes médicaux — actes techniques. \n\n```bash\nuv run python -m smt2parquet ccam\n# rdf/terminologie-ccam-\u003cversion\u003e.rdf  →  parquet/ccam-\u003cversion\u003e.parquet\n```\n\n### Colonnes\n\n| Colonne | Type | Description |\n|---|---|---|\n| `code` | `str` | Code de l'acte (`skos:notation`) |\n| `label` | `str` | Libellé (`rdfs:label`) |\n| `depth` | `i64` | Profondeur dans l'arbre |\n| `lft` | `i64` | Borne gauche du nested set |\n| `rgt` | `i64` | Borne droite du nested set |\n| `path` | `str` | Chaîne des codes des ancêtres |\n| `synonymes` | `list[str]` | `skos:altLabel` dédupliqués |\n| `inclusion_note` | `str?` | `xkos:inclusionNote` |\n| `exclusion_note` | `str?` | `xkos:exclusionNote` |\n| `definition` | `str?` | `skos:definition` |\n| `topographie` | `str?` | Libellé du concept lié `ccam:topographie` |\n| `type_acte` | `str?` | Libellé du concept lié `ccam:typeActe` |\n| `mode_acces` | `str?` | Libellé du concept lié `ccam:modeAcces` |\n| `action` | `str?` | Libellé du concept lié `ccam:action` |\n\n### Exemples\n\n```sql\n-- Filtrer par topographie\nSELECT code, label, path\nFROM 'parquet/ccam-v82.00.parquet'\nWHERE topographie = 'Os de la main'\nLIMIT 20;\n```\n\n```sql\n-- Tous les descendants d'un nœud de la hiérarchie\nWITH n AS (\n    SELECT lft AS l, rgt AS r\n    FROM 'parquet/ccam-v82.00.parquet'\n    WHERE code = '01'\n)\nSELECT code, label, type_acte, mode_acces\nFROM 'parquet/ccam-v82.00.parquet', n\nWHERE lft BETWEEN n.l AND n.r\nORDER BY lft;\n```\n\n```python\n# En Python avec DuckDB\nimport duckdb\n\nactes = duckdb.sql(\"\"\"\n    SELECT code, label, topographie, action\n    FROM 'parquet/ccam-v82.00.parquet'\n    WHERE action IS NOT NULL\n\"\"\").pl()  # → DataFrame Polars\n```\n\n```python\n# Lire les métadonnées du fichier\nimport pyarrow.parquet as pq\n\nmd = pq.read_metadata(\"parquet/ccam-v82.00.parquet\").metadata\nprint({k.decode(): v.decode() for k, v in md.items() if not k.startswith(b\"ARROW\")})\n# {'terminology': 'ccam', 'version': 'v82.00', 'source_file': '...', 'generated_at': '...'}\n```\n\n---\n\n## ADICAP\n\nCodes ADICAP (anatomie et cytologie pathologiques). La racine masquée donne 9 dictionnaires (axes D1–D8 + D8L) à `depth 0`.\n\n```bash\nuv run python -m smt2parquet adicap\n# rdf/terminologie-adicap-\u003cversion\u003e.rdf  →  parquet/adicap-\u003cversion\u003e.parquet\n```\n\n### Colonnes\n\n| Colonne | Type | Description |\n|---|---|---|\n| `code` | `str` | Code du concept (`skos:notation`) |\n| `label` | `str` | Libellé (`rdfs:label`) |\n| `dictionary_code` | `str?` | Axe du dictionnaire D1–D8L (`adicap:dictionaryCode`) |\n| `depth` | `i64` | Profondeur dans l'arbre (0 = dictionnaire) |\n| `lft` | `i64` | Borne gauche du nested set |\n| `rgt` | `i64` | Borne droite du nested set |\n| `path` | `str` | Chaîne des codes des ancêtres |\n| `anatomy_code` | `str?` | `skos:notation` du concept lié `adicap:anatomy` |\n| `anatomy_label` | `str?` | `rdfs:label` du concept lié `adicap:anatomy` |\n\n### Exemples\n\n```sql\n-- Lister les 9 dictionnaires (depth = 0)\nSELECT code, label, dictionary_code, lft, rgt\nFROM 'parquet/adicap-2024-10.parquet'\nWHERE depth = 0\nORDER BY lft;\n```\n\n```sql\n-- Tous les concepts d'un dictionnaire donné\nWITH dico AS (\n    SELECT lft AS l, rgt AS r\n    FROM 'parquet/adicap-2024-10.parquet'\n    WHERE dictionary_code = 'D1'\n)\nSELECT code, label, anatomy_label\nFROM 'parquet/adicap-2024-10.parquet', dico\nWHERE lft BETWEEN dico.l AND dico.r\nORDER BY lft;\n```\n\n```python\n# En Python avec DuckDB\nimport duckdb\n\navec_anatomie = duckdb.sql(\"\"\"\n    SELECT code, label, anatomy_code, anatomy_label\n    FROM 'parquet/adicap-2024-10.parquet'\n    WHERE anatomy_code IS NOT NULL\n\"\"\").pl()  # → DataFrame Polars\n```\n\n```python\n# Lire les métadonnées du fichier\nimport pyarrow.parquet as pq\n\nmd = pq.read_metadata(\"parquet/adicap-2024-10.parquet\").metadata\nprint({k.decode(): v.decode() for k, v in md.items() if not k.startswith(b\"ARROW\")})\n# {'terminology': 'adicap', 'version': '2024-10', 'source_file': '...', 'generated_at': '...'}\n```\n\n---\n\n## Ajouter une nouvelle terminologie\n\n1. Créer `smt2parquet/\u003cnom\u003e.py` qui expose :\n   - `BASE_URI`, `RDF_FILENAME_PREFIX`, `TERMINOLOGY_NAME`,\n   - `EDGES_QUERY` et `ATTRS_QUERY` (SPARQL),\n   - `convert(rdf_path, out_path)`.\n2. Ajouter une entrée dans `TERMINOLOGIES` de `smt2parquet/__main__.py`.\n\n`smt2parquet/core.py` ne devrait pas avoir à bouger. Voir `smt2parquet/cim10.py` (arbre simple), `smt2parquet/ccam.py` (concepts liés via `OPTIONAL { ?concept ccam:topographie ?x . ?x rdfs:label ?topographie }`) et `smt2parquet/adicap.py` (racine réelle masquée + concept lié `anatomy`).\n\n## Licence\n\nLe **code** de `smt2parquet` est distribué sous licence **MIT** (voir [`LICENSE`](LICENSE)).\n\nLes **fichiers Parquet générés** contiennent des terminologies médicales issues du\n[**Serveur Multi-Terminologies (SMT)**](https://smt.esante.gouv.fr/) opéré par\nl'Agence du Numérique en Santé (ANS). Ils restent soumis à la licence de chaque\nterminologie source :\n\n| Terminologie | Licence | Page SMT |\n|---|---|---|\n| CIM-10 FR PMSI | CC BY-NC-ND 3.0 IGO | [terminologie-cim-10](https://smt.esante.gouv.fr/terminologie-cim-10/) |\n| CCAM | Licence Ouverte v2.0 (Etalab, LOv2) | [terminologie-ccam](https://smt.esante.gouv.fr/terminologie-ccam/) |\n| ADICAP | Licence Ouverte v2.0 (Etalab, LOv2) | [terminologie-adicap](https://smt.esante.gouv.fr/terminologie-adicap/) |\n| ATC | CC BY-ND 3.0 IGO | [terminologie-atc](https://smt.esante.gouv.fr/terminologie-atc/) |\n\nChaque Parquet embarque aussi sa propre licence dans les métadonnées du footer\n(clé `license`). Référez-vous au [portail SMT](https://smt.esante.gouv.fr/) pour\nles conditions d'utilisation faisant foi.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdridk%2Fsmt2parquet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdridk%2Fsmt2parquet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdridk%2Fsmt2parquet/lists"}