Remettre un RSS à l’endroit

Aujourd’hui jour férié je vous propose juste une petite recette de tambouille RSS.

Depuis sa dernière mise-à-jour notre DPI/logiciel de codage s’est mis à produire un export RSS dons les RUM sont dans le désordre au sein d’un séjour et bien entendu Druides proteste et me groupe tout ces multirums en CMD 90.

Symptômes

Pour cet envoi de M3-2024, Druides s’est donc plaint à coup de 90z09z et autre erreurs en retour de la fonction groupage dans le csv de rapport, que l’enchaînement des RUMs de certains séjours était défectueux.

En effet, Druides attend qu’au sein du fichier RSS , les RUMs soient chronologiquement ordonnés au sein d’un séjour (il ne reconstitue pas en se basant sur les dates d’entrée et sortie, ni en se basant sur le numéro de RUM).

A regarder mon RSS, j’ai en effet constaté que les RUMs étaient parfois en vrac de façon imprévisible…

Traitement

Il me fallait donc trouver une solution car l’envoi était dû… il y a 8 jours… et le support n’allant probablement pas traiter ce bug en cette semaine de pont. Comme souvent, j’avais un R d’ouvert qui trainait et me faisait de l’oeil.

Conceptualisation

Le but était de faire un “truc simple qui marche”, donc pas de programmation propre, multiannée compatible, juste le minimum.

Les paramètres sont portion congrue et comme d’habitude, on va les fixer en haut du document pour le côté pratique.

  • Nom du fichier en entrée : nous n’avons besoin que du RSS, il n’y aura pas de jointure ou autre.
  • Nom du fichier en sortie, je sais, pas très original…

j’expérimente un peu avec les nommages de variables j’ai donc décidé de les appeler respectivement f_source et f_dest cette fois-ci.

# Correction de l'ordre des RUMs

f_source = "~/EXPORTS/2024/M03/DEF.RSS"
f_dest = "~/EXPORTS/2024/M03/DEFF.RSS"

# Pas d'autres modifications après ce point.
R

Ensuite, nous allons charger quelques librairies :

readr pour charger et sauvegarder les fichiers

dplyr et tidyr pour faire un code agréable à lire (pour un tel traitement, il n’y a pas vraiment de contingence de performances).

et stringr, mais j’aurais pû utiliser les fonctions de la librairie base, ça aurait aussi fonctionné.

library(readr)
library(dplyr)
library(tidyr)
library(stringr)
R

Maintenant, il est temps de verbaliser le traitement que nous désirons faire afin de le conceptualiser étape par étape. La question est simple et a déjà été évoquée un peu plus haut:

“je veux trier les RUMs au sein de chaque séjour par ordre chronologique”

sauf que ça ne suffit pas, surtout si vous avez un service d’urgences et un UHCD où les patients peuvent ne rester que quelques heures mais aussi si vous avez des chirurgies J0 qui font un passage en USC en sortie de bloc… En sortie, nous avons l’assez classique “transfert en soins palliatifs et décès le jour même”.

Il nous faut donc au sein d’une même journée, vérifier que le RUM ayant le mouvement d’entrée soit le premier et celui ayant le mouvement de sortie soit le dernier. Ensuite l’ordre des mutations sur le même jour n’a pas trop d’importance Druides ne saurait pas faire la différence avec les données qu’il possède.

De cette verbalisation, nous pouvons déjà repérer quelles valeurs nous allons devoir extraire de nos RUMs :

le numéro de RSS pour isoler les séjours, la date d’entrée dans l’UF, la date de sortie de l’UF ainsi que les modes d’entrée et sortie. Il n’est pas nécessaire de décomposer tout le RSS, nous allons garder la ligne en entier vu il s’agit juste de remettre les RUM dans l’ordre sans les modifier.

Les étapes

Lire le fichier source et récupérer les champs pertinents

Il y a 2 possibilités :

  • utiliser read_fwf() qui permet de lire directement un fichier à positions fixes dans un data.frame
  • ou utiliser read_lines() qui lit un fichier texte dans un vecteur de chaînes, puis convertir celui-ci en data.frame.

Nous avons déjà vu l’usage de read_fwf() donc on va utiliser l’autre méthode, un peu plus manuelle.

On lit donc le fichier dans un vecteur, puis on le transforme en data.frame (ici en tibble, ce qui revient grosso-modo au même et est plus adapté à dplyr) et enfin on crée les champs nécessaires via l’extraction de sous-chaines.

Pour les dates, on convertit le texte qui n’est pas dans un format pouvant être trié.

read_lines(f_source) %>% # lit le fichier f_source sous forme de vecteur de chaine
  as_tibble() %>% # Transforme en tibble (qui est un similaire de data.frame)
  mutate(
    NRSS = str_sub(value, 28, 47),
    DENT = as.Date(str_sub(value, 93, 100), format = "%d%m%Y"),
    DSORT = as.Date(str_sub(value, 103, 111), format = "%d%m%Y"),
    ENT = str_sub(value, 101, 102),
    SORT = str_sub(value, 111, 112)
  )
R

Pour les modes d’entrée/sortie, idéalement il faut se baser sur les 2 caractères (entrée+provenance et sortie+destination)car il y a certains cas particuliers comme la mutation depuis ou vers un SMR du même établissement qui sont des mouvements d’entrée ou sortie mais discriminable uniquement sur le 2e caractère.

Comment classer les entrées/sorties

il nous faut une fonction qui, pour une valeur x de mode d’entrée, va identifier si c’est une entrée (arbitrairement la valeur 1) ou un mouvement interne (valeur 2). Ainsi il n’y aura plus qu’a classer selon cette nouvelle valeur.

idem pour le mode de sortie.

Dans le PMSI, pour les modes possibles qui ne sont pas des entrées, nous trouvons uniquement “61” = mutation interne et “01” = PIE (01 peut être une entrée mais ça n’impacte pas sauf cas exceptionnellissime). Nous pouvons donc écrire une fonction o_ENT() :

o_ENT <- function(x){
  ifelse(x %in% c("61", "01"), 2,1)
}
R

Et de la même façon, les modes de sortie sont tous ceux qui ne sont pas des “61” et “01”.

o_SORT <- function(x){
  ifelse(x %in% c("61", "01"), 1,2)
}
R

Nous pouvons donc réécrire notre import ainsi :

(...)
    oENT = o_ENT(str_sub(value, 101, 102)),
    oSORT = o_SORT(str_sub(value, 111, 112))
(...)
R

Le traitement proprement dit

Nous avons un data.frame contentenant chaque ligne de RSS, le n° du RSS, les dates d’entrée et sortie et deux indicateurs permettant d’identifier les mouvements d’entrée et sortie. Il suffit donc de classer pour chaque RSS les lignes.

D’abord regroupons par N° de RSS :

(...) %>%
   group_by(NRSS)
R

Ainsi toutes les futures actions dplyr se feront par groupe. Nous pouvons donc trier via un arrange()

Pour préciser que nous voulons travailler par RSS, il faut le préciser par une option du arrange().

(...) %>%
  arrange(DENT, oENT, DSORT, oSORT,.by_group = TRUE)
R

Et nous nous retrouvons alors avec un RSS réaligné accompagné de colonnes désormais inutiles.

Mise au propre

Afin de pouvoir écrire notre fichier, il faut donc adapter nos données au format attendu de la fonction write_lines(). Nous allons donc retransformer le tout en vecteur de chaines.

(...) %>%
  ungroup %>%
  select(value) %>%
  unlist(use.names = FALSE)
R

Je vais détailler un peu :

on enlève le regroupement par NRSS (sinon, la colonne ne pourra pas être enlevée par le select() qui suit)

Nous ne gardons que la colonne qui contient les lignes de RSS (le tri a eu lieu, nous n’avons plus besoin du reste) via le select().

Nous retransformons la colonne restante en vecteur de chaines via le unlist().

Sauvegarde

Il s’agit simplement d’appeler write_lines() avec les paramètres de base à savoir juste lui passer le nom du fichier destination.

(...) %>%
  write_lines(f_dest)
R

La totalité du code :

# Correction de l'ordre des RUMs

f_source = "~/EXPORTS/2024/M03/DEF.RSS"
f_dest = "~/EXPORTS/2024/M03/DEFF.RSS"

# Pas d'autres modifications après ce point.
library(readr)
library(dplyr)
library(tidyr)
library(stringr)

o_ENT <- function(x){
  ifelse(x %in% c("61", "01"), 2,1)
}

o_SORT <- function(x){
  ifelse(x %in% c("61", "01"), 1,2)
}

read_lines(f_source) %>% # lit le fichier f_source sous forme de vecteur de chaine
  as_tibble() %>% # Transforme en tibble (qui est un similaire de data.frame)
  mutate(
    NRSS = str_sub(value, 28, 47),
    DENT = as.Date(str_sub(value, 93, 100), format = "%d%m%Y"),
    DSORT = as.Date(str_sub(value, 103, 111), format = "%d%m%Y"),
    oENT = o_ENT(str_sub(value, 101, 102)),
    oSORT = o_SORT(str_sub(value, 111, 112))
  ) %>%
  group_by(NRSS) %>%
  arrange(DENT, oENT, DSORT, oSORT, .by_group = TRUE) %>%
  ungroup %>%
  select(value) %>%
  unlist(use.names = FALSE) %>%
  write_lines(f_dest)
R

(Pour le fun, on aurait même pu ne pas définir o_ENT() et o_SORT() car le code est court, mais la lisibilité aurait été moins bonne.)

Conclusion

Et voilà, nous avons trié notre RSS et Druides est content.

En espérant bien sûr que le bug soit rapidement corrigé et que nous n’ayons plus besoin de ce petit bout de code les mois ou années prochains…

Un commentaire

  1. Merci à Denis Gustin (lespmsi.com et PMSISoft) qui m’a fait remarquer que j’avais oublié l’option .by_group = TRUE dans mon code au sein du arrange().

    Cela n’avait pas d’impact sur le fonctionnement mais le code ne correspondait pas exactement à ce que disait le texte d’accompagnement.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *