<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Primary Engineering Blog]]></title><description><![CDATA[Primary Engineering Blog]]></description><link>https://engineeringblog.helloprimary.care</link><generator>RSS for Node</generator><lastBuildDate>Mon, 01 Jun 2026 04:16:41 GMT</lastBuildDate><atom:link href="https://engineeringblog.helloprimary.care/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[L'event-driven architecture de Primary]]></title><description><![CDATA[En complément des lunch talks que j’ai donnés à Devoxx France, au JUG Summer Camp et à BDX/IO en 2025 sur ce sujet, voici une synthèse concernant l’architecture event-driven du backend de Primary.
Il y a maintenant plus de deux ans, nous sommes parti...]]></description><link>https://engineeringblog.helloprimary.care/levent-driven-architecture-de-primary</link><guid isPermaLink="true">https://engineeringblog.helloprimary.care/levent-driven-architecture-de-primary</guid><category><![CDATA[architecture]]></category><category><![CDATA[event-driven-architecture]]></category><category><![CDATA[rabbitmq]]></category><dc:creator><![CDATA[Vincent Dubois]]></dc:creator><pubDate>Tue, 25 Nov 2025 06:05:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/zm7yw2qn0-k/upload/8f6d73d50c61524eeedacbaec51c215f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En complément des lunch talks que j’ai donnés à <a target="_blank" href="https://www.youtube.com/watch?v=nYjkFBXyxEc">Devoxx France</a>, au <a target="_blank" href="https://www.youtube.com/watch?v=Q5oTVYZWbds">JUG Summer Camp</a> et à BDX/IO en 2025 sur ce sujet, voici une synthèse concernant l’architecture event-driven du backend de Primary.</p>
<p>Il y a maintenant plus de deux ans, nous sommes partis en production avec une event-driven architecture que nous avons choisi pour de multiples raisons que j’évoquerai dans cet article. Depuis, nous maintenons au quotidien cette architecture et ses outils. Il est temps de faire un premier bilan sur ses avantages et ses inconvénients.</p>
<h2 id="heading-quest-ce-quune-event-driven-architecture">Qu’est ce qu’une Event-Driven Architecture ?</h2>
<p>Une architecture de type event-driven est une architecture dans laquelle un module métier va publier le résultat d’un traitement (par exemple la saisie d’une conclusion dans une consultation par un médecin) sous la forme d’un événement métier. On appelle ce module métier un <em>publisher</em>. Cette publication d’événement se fait au travers d’un <a target="_blank" href="https://fr.wikipedia.org/wiki/Agent_de_messages">message broker</a> (RabbitMQ dans notre cas) dans une file d’attente.</p>
<p>Chaque file d’attente peut être écoutée par de multiples consommateurs. On les appelle <em>subscribers</em> ou <em>event handlers</em>. A chaque fois qu’un événement arrive dans la file d’attente, chaque <em>event handler</em> qui s’est abonné à l’événement est notifié et son code est appelé pour consommer l’événement.</p>
<p>Cela garantit un découplage parfait des responsabilités. Les modules ne se connaissent pas à l’avance et n’interagissent qu’au travers de ces événements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763367554054/bce4b14c-2b90-46ff-aec0-7ad11727c58c.png" alt class="image--center mx-auto" /></p>
<p>Cela ouvre énormément le champ des possibles en termes d’architecture : on pourrait imaginer faire du <a target="_blank" href="https://www.linkedin.com/pulse/quest-ce-quun-monolithe-modulaire-sami-belhadj-f3jbf/">monolithe modulaire</a> (spoiler alert : c’est ce qu’on fait chez Primary) ou encore éclater nos modules en microservices si on en avait le besoin.</p>
<h2 id="heading-quels-sont-les-avantages-de-cette-architecture">Quels sont les avantages de cette architecture ?</h2>
<p>Notre démarche a été, dès le premier jour, de choisir une architecture event-driven. Ce choix a été fait pour de multiples raisons :</p>
<ul>
<li><p>Maintien de la <strong>simplicité</strong> des modules métier et <strong>découplage</strong> (le code de notre backend est un <a target="_blank" href="https://www.linkedin.com/pulse/quest-ce-quun-monolithe-modulaire-sami-belhadj-f3jbf/">monolithe modulaire</a>, autrement appelé modulith)</p>
</li>
<li><p><strong>Orientation métier</strong> des événements en accord avec notre démarche de conception orientée métier (<a target="_blank" href="https://en.wikipedia.org/wiki/Domain-driven_design#:~:text=Domain%2Ddriven%20design%20\(DDD\),which%20have%20their%20own%20model.">Domain-Driven Design</a>). Nous avons fait le choix d’utiliser des événements métier tels que “ConsultationTerminee” ou “BilanDePreventionValide” plutôt que des événements techniques comme par exemple “ConsultationAjoutee” ou “ConsultationModifiee”.</p>
</li>
<li><p><strong>Evolutivité</strong> : cela est bien plus pérenne de créer de nouveaux event handlers pour implémenter de nouvelles fonctionnalités que d’ajouter celles-ci dans du code existant.</p>
</li>
<li><p><strong>Scalabilité</strong> : c’est très facile pour nous d’ajouter de nouvelles instances de notre backend (scaling en un clic sur Clever Cloud) afin de répondre à des pics de charge.</p>
</li>
<li><p><strong>Disponibilité</strong> : le backend reste disponible même si un event handler est défaillant; de plus, il est possible de re-publier des événements après un incident. C’est le sujet dont je parle le plus dans mon lunch talk car je trouve que c’est un des plus gros avantages 🙂</p>
</li>
</ul>
<h2 id="heading-les-challenges-rencontres-par-lequipe">Les challenges rencontrés par l’équipe</h2>
<h3 id="heading-la-gestion-des-erreurs">La gestion des erreurs</h3>
<p>Je trouve que la gestion des erreurs est un des meilleurs arguments de vente de cette architecture : cela nous donne la capacité de récupérer d’une erreur après un incident. Cela rend tout simplement notre système résilient.</p>
<p>Pourtant, cette capacité à réessayer après une erreur a été un de nos plus gros défis.</p>
<p>Dans mon talk, je prends l’exemple d’une notification push que l’on envoie à nos patients sur leur app Primary lorsqu’ils prennent rendez-vous dans un de nos cabinets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763400962457/176a4830-0de7-4bbf-9759-17d1ddc1feb9.png" alt class="image--center mx-auto" /></p>
<p>Le problème se pose si l’event handler devient défaillant : que doit-on faire ? On ne doit pas perdre le contexte d’exécution car le patient ne recevrait jamais sa notification push.</p>
<p>Dans ce style d’architecture, on utilise un mécanisme de files d’attente qui permettent d’isoler les événements en erreur afin de pouvoir les retraiter plus tard. Cela s’appelle des <a target="_blank" href="https://en.wikipedia.org/wiki/Dead_letter_queue">Dead Letter Queues</a>.</p>
<p>Concrètement, si l’event handler fait son travail correctement, il informe le message broker de la consommation de l’événement et il peut passer à l’événement suivant dans la file d’attente. Mais s’il y a une erreur, l’événement ayant donné lieu à l’erreur sera alors re-routé vers une Dead Letter Queue afin d’être isolé pour être retraité plus tard.</p>
<p>Chez Primary, lorsqu’un événement part en Dead Letter Queue, on en profite pour ajouter des méta-données dans les entêtes de l’événement :</p>
<ul>
<li><p>Stacktrace et message de l’exception</p>
</li>
<li><p>File d’attente d’origine du message (pour nous, un event handler = une file d’attente, un événement = un exchange dans RabbitMQ)</p>
</li>
<li><p>Date à laquelle l’événement a été “refusé” pour la première fois</p>
</li>
<li><p>Nombre de retries</p>
</li>
<li><p>Date d’un rendez-vous rattaché potentiel, nom du cabinet de rattachement, identifiant du profil de patient concerné</p>
</li>
<li><p>ID de la trace de notre APM</p>
</li>
</ul>
<p>Cela nous permet de gagner énormément en observabilité.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763401427674/bf55ae92-cf6f-43d2-b79b-6b135c55c2f0.png" alt class="image--center mx-auto" /></p>
<p>Une fois l’incident sur l’event handler terminé, on peut alors ré-injecter l’événement dans le système dans sa file d’attente d’origine. Cette opération pourra être répétée autant de fois que nécessaire, jusqu’à ce que la consommation de l’événement par l’event handler puisse se faire 🙂.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763401797218/57e48b57-c1a1-4133-aabe-52c36b1aab0a.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-le-traitement-des-erreurs-en-production">Le traitement des erreurs en production</h3>
<p>RabbitMQ, notre message broker, dispose de ce mécanisme de Dead Letter Queue de base, et c’est un bonheur pour garder la maîtrise des erreurs en production.<br />Néanmoins, la console d’administration de RabbitMQ est assez austère et ne permet pas de gérer ces files d’attente de messages en erreur.</p>
<p>En 2023, dans les tous premiers mois de la construction de notre backend, je prends alors le temps de faire une étude afin de savoir s’il existe des produits sur étagère qui permettent de gérer ces Dead Letter Queues comme on le souhaite. Malheureusement, cela n’existe pas, alors je décide de réaliser une webapp qui permet :</p>
<ul>
<li><p>de lister les messages qui sont en Dead Letter Queue</p>
</li>
<li><p>de renvoyer tous les messages vers leurs files d’attente d’origine</p>
</li>
</ul>
<p>Ce projet a été réalisé au départ en moins d’une semaine avec le minimum d’investissement, ce qui veut dire qu’il n’était pas au point lors de la première mise en production, pour de multiples raisons :</p>
<ul>
<li><p>Evénements <strong>mélangés</strong> et en <strong>grand nombre</strong></p>
</li>
<li><p>Evénements en <strong>doublons</strong></p>
</li>
<li><p>Impossibilité de <strong>renvoyer</strong> des événements de manière <strong>unitaire</strong></p>
</li>
<li><p>Impossibilité de <strong>supprimer</strong> un événement de la Dead Letter Queue</p>
</li>
</ul>
<p>Pendant quelques mois, nos difficultés principales pour le traitement des erreurs en production ont été ces limitations qui nous ont fait passer beaucoup trop de temps en analyse des problèmes.</p>
<h3 id="heading-iterer-et-ameliorer-notre-dead-letter-ui">Itérer et améliorer notre “Dead Letter UI”</h3>
<p>“Dead Letter UI”, c’est le nom que l’on donne en interne à ce projet qui nous permet de superviser la Dead Letter Queue en production (mais aussi sur notre environnement de staging).</p>
<p>Forts d’un atelier <a target="_blank" href="https://fr.wikipedia.org/wiki/Kaizen_\(management\)">Kaïzen</a> qui nous a permis de lister clairement toutes nos difficultés rencontrées, nous avons amorcé un chantier d’amélioration de cette application :</p>
<ul>
<li><p>Ajout de la possibilité de visualiser les événements en erreur <strong>par event handler</strong></p>
</li>
<li><p>Ajout de la possibilité de renvoyer ou supprimer un événement <strong>unitairement ou par event handler</strong></p>
</li>
<li><p>Ajout d’un <strong>lien</strong> vers la trace de l’exécution du code dans notre <strong>APM</strong></p>
</li>
<li><p>Ajout de <strong>données de production</strong> lors de la mise en Dead Letter Queue qui nous permet de savoir si un événement est lié à un rendez-vous et/ou à un patient ainsi qu’à un cabinet. En bonus, on recrée les liens vers le logiciel médecin et le logiciel qu’utilisent les équipes soignantes</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763446579771/9e1d6516-517f-41d3-a006-af46383d4cdf.png" alt class="image--center mx-auto" /></p>
<p>Toutes ces améliorations nous ont permis de sortir la tête de l’eau. Nous avons enfin pu réagir rapidement et de manière éclairée lorsque nous avions des problèmes.</p>
<p>Aujourd’hui, un événement en Dead Letter Queue nous occupe en moyenne <strong>une minute</strong>. Dans ce temps, nous sommes capables de décider :</p>
<ul>
<li><p>Si c’est un événement qui a expiré (et on le supprime)</p>
</li>
<li><p>Si c’est un problème connu (donc pas besoin d’analyse complémentaire)</p>
</li>
<li><p>Si c’est un nouveau problème</p>
</li>
</ul>
<p>En tout cas, nous avons une politique de traitement systématique de ces cas et lorsque notre alerting nous avertit d’une erreur, nous intervenons très rapidement pour retrouver un compteur à zéro.</p>
<h3 id="heading-le-rejeu-des-evenements">Le rejeu des événements</h3>
<p>Le rejeu systématique des événements est tentant avec un outil tel que celui que nous avons développé; néanmoins, ce n’est pas une option, car les événements que nous produisons peuvent se retrouver obsolètes très vite.</p>
<p>A titre d’exemple, nous envoyons une notification push sur le téléphone du patient lorsqu’il arrive au cabinet. Si une erreur se produit lors de son temps d’attente, nous ne prenons jamais le risque de rejouer ce type d’événement, car cela pourrait produire des side effects au mauvais moment (si le patient est déjà dans le box du médecin par exemple).</p>
<p>Par design, nous avons prévu tous nos event handlers pour qu’ils soient idempotents. De cette façon, nous savons que nous pouvons rejouer un événement si son cadre temporel est correct. L’heure du rendez-vous associé à l’événement présenté par la Dead Letter UI nous permet de prendre la décision en quelques secondes seulement.</p>
<p><strong>Le rejeu des événements n’est donc clairement pas un sujet simple</strong>; nous avons choisi pour l’instant de traiter des événements rejetés <strong>manuellement</strong>; nous pourrions néanmoins automatiser ces traitements, que ce soit :</p>
<ul>
<li><p>par une expiration technique au niveau du message broker</p>
</li>
<li><p>par une expiration calculée par nos event handlers</p>
</li>
</ul>
<p>Nous avons pour l’instant fait le choix pragmatique de les traiter manuellement, compte tenu de notre faible volumétrie d’erreurs. Notre stratégie pourra changer bien entendu lorsque la volumétrie de la production évoluera.</p>
<h2 id="heading-bilan-et-challenges-a-venir">Bilan et challenges à venir</h2>
<p>Sur les deux dernières années en production, cela fait un peu plus d’une année que notre stack événementielle est suffisante pour nous permettre de gérer la production de manière sereine. Bien entendu, il reste encore des choses à améliorer :</p>
<ul>
<li><p>fiabiliser l’envoi des événements dans le message broker. RabbitMQ ne nous a jamais fait défaut en production, mais il serait fâcheux de perdre des événements</p>
</li>
<li><p>augmenter l’observabilité pour pouvoir reconstituer des parcours fonctionnels à partir d’événements (ceux-ci sont isolés pour l’instant)</p>
</li>
<li><p>documenter nos événements pour les futurs développeurs et développeuses qui vont nous rejoindre</p>
</li>
<li><p>absorber la croissance de traffic lorsque nous allons ouvrir de nouveaux cabinets (nous allons doubler le nombre de cabinets en 2026, et ce n’est qu’un début)</p>
</li>
</ul>
<p>Il y a encore énormément de sujets que j’aimerais développer à propos de l’architecture de notre backend. C’est pourquoi il se pourrait bien que vous me revoyiez en 2026 pour parler de ce sujet, plus en détail cette fois 😉.</p>
]]></content:encoded></item><item><title><![CDATA[Améliorons le quotidien des soignants avec l'IA générative]]></title><description><![CDATA[Introduction
Le sujet est sur toutes les lèvres depuis plus d'un an : comment pourrait-on profiter de l'énorme potentiel des intelligences artificielles génératives (basées sur les Large Language Models) afin de faire gagner du temps et donc de l'arg...]]></description><link>https://engineeringblog.helloprimary.care/ameliorons-le-quotidien-des-soignants-avec-lia-generative</link><guid isPermaLink="true">https://engineeringblog.helloprimary.care/ameliorons-le-quotidien-des-soignants-avec-lia-generative</guid><category><![CDATA[AI]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Vincent Dubois]]></dc:creator><pubDate>Mon, 28 Oct 2024 07:51:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/hJUl5BAhJec/upload/78281cba78df3a8f86912b142e7cde51.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>Le sujet est sur toutes les lèvres depuis plus d'un an : comment pourrait-on profiter de l'énorme potentiel des intelligences artificielles génératives (basées sur les <a target="_blank" href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Models</a>) afin de faire gagner du temps et donc de l'argent à notre entreprise ?</p>
<p>Chez Primary, nous n'avons pas choisi de baser notre modèle économique sur l'IA. Néanmoins, nous avons choisi de l'utiliser comme un booster, afin de faire gagner du temps à nos soignants, en remplacement de tâches chronophages qu'ils ou elles réalisaient systématiquement.</p>
<h3 id="heading-genese-de-lia-generative-chez-primary-automatiser-la-preparation-dune-consultation">Genèse de l'IA générative chez Primary : automatiser la préparation d'une consultation</h3>
<p>C'est lors d'une phase de discovery produit, alors que nous avions à réaliser un questionnaire dans le cadre d'une fonctionnalité de préparation de consultation par le patient depuis son app mobile, que nous avons envisagé pour la première fois d’utiliser l’IA générative.</p>
<p>Nous avions alors deux choix possibles :</p>
<ul>
<li><p>Réaliser une fonctionnalité de questionnaire “statique” (le patient choisit le motif principal, puis répond à des questions sur ses symptômes). Cela personnalise peu l’expérience utilisateur.</p>
</li>
<li><p>Réaliser une fonctionnalité avec un assistant IA qui interagit avec le patient. Cela personnalise beaucoup plus l’expérience l’utilisateur.</p>
</li>
</ul>
<p>Bien entendu, nous avons choisi la deuxième solution, car cela a permis au patient d'avoir le même ressenti que si une vraie personne lui posait les questions. Mais surtout, la synthèse issue de cet échange, générée par une IA, a une vraie plus value pour nous, car elle permet au médecin de mieux anticiper la consultation.</p>
<p>Voici un exemple d’échange :</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/dWseuQU4ksw">https://youtu.be/dWseuQU4ksw</a></div>
<p> </p>
<p>Cette fonctionnalité est aujourd'hui très utilisée : elle permet aux patients de saisir de manière détaillée la raison de leur venue, même dans le cas de multi-motifs. Si l'assistante reçoit en préconsultation le patient, elle voit la synthèse générée à partir du questionnaire auquel a répondu le patient, et si le médecin reçoit le patient directement, cette synthèse est disponible dans le logiciel médecin.</p>
<h3 id="heading-ameliorer-le-quotidien-des-assistantes-medicales-avec-lia">Améliorer le quotidien des assistantes médicales avec l'IA</h3>
<p>Forts de notre première expérience en prompt engineering, nous avons réfléchi à améliorer le processus des pré-consultations, réalisé par les assistantes médicales.</p>
<p>Voici d'abord un peu de contexte : lorsqu'elles ne sont pas surchargées par l'accueil, les tâches administratives ou encore le secrétariat, les assistantes médicales ont pour mission de réaliser une pré-consultation afin de préparer le terrain pour le médecin et lui faire gagner du temps lors de la consultation. Prise de constantes, interrogatoire de <a target="_blank" href="https://fr.wikipedia.org/wiki/Anamn%C3%A8se_\(m%C3%A9decine\)">pré-anamnèse</a> (interrogatoire médical) et actes techniques peuvent alors être effectués.</p>
<p>Nous souhaitions développer une fonctionnalité de saisie des pré-consultations réservée aux assistantes, mais nous ne savions pas comment faire pour réaliser une fonctionnalité qui pourrait être une vraie aide à la pré-anamnèse pour elles. L'option de réaliser le même questionnaire que dans l'application mobile pour le patient a été écartée dès le départ, car elle enlevait toute spontanéité. De plus, certaines assistantes étaient à l'aise pour réaliser cette tâche, d'autres moins. Comment faire alors pour aider nos assistantes à réaliser ces pré-anamnèses ?</p>
<p>C'est après de multiples itérations et ateliers que l'équipe est arrivée à une solution satisfaisante :</p>
<ul>
<li><p>L'assistante saisit sa pré-anamnèse dans un champ de texte à partir de sa discussion avec le patient</p>
</li>
<li><p>L'IA générative lui suggère des questions à poser au patient selon ce qu'elle a déjà saisi (symptômes, ...)</p>
</li>
<li><p>L'IA générative lui suggère des examens techniques à réaliser en fonction de l'état de santé du patient et de ses symptômes (test covid si le patient a de la fièvre et une rhinite par exemple)</p>
</li>
<li><p>L'IA générative permet finalement de mettre en forme la pré-anamnèse afin d'obtenir un format de synthèse homogène pour les médecins (synthèse poussée ensuite dans le logiciel médecin).</p>
</li>
</ul>
<p>Nous avons été agréablement surpris de l'engouement suscité par cette fonctionnalité de saisie de pré-anamnèse. Les assistantes médicales ont été ravies de trouver un allié d'aide à la saisie et à la mise en forme. Cela leur permet de gagner un temps fou et donc de dégager du temps pour d'autres tâches.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=FdNxqUCzXW8">https://www.youtube.com/watch?v=FdNxqUCzXW8</a></div>
<p> </p>
<h3 id="heading-fluidifier-la-communication-entre-le-patient-et-le-personnel-soignant-grace-a-lia">Fluidifier la communication entre le patient et le personnel soignant grâce à l'IA</h3>
<p>Depuis fin 2023, le secrétariat téléphonique a été coupé et remplacé par la messagerie. Les patients peuvent initier une demande depuis la messagerie dans leur application mobile, et recevoir une réponse avec une moyenne de 30 minutes.</p>
<p>Les assistantes médicales et les médecins peuvent également contacter les patients via cette messagerie.</p>
<p>La messagerie repose aujourd’hui activement sur l’IA, et les assistantes médicales et les médecins s’appuient dessus :</p>
<ul>
<li><p>Pour assigner automatiquement un titre à la conversation avec le patient</p>
</li>
<li><p>Pour corriger les fautes d’orthographe et reformuler une idée, via notre fonctionnalité de “Mise en forme”</p>
</li>
</ul>
<p>Ce sont des tâches pour lesquelles les IA génératives sont faites, et qui enlèvent un poids des épaules de nos équipes, qui investissent alors mieux leur temps pour du soin de qualité.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/D4EoEov261I">https://youtu.be/D4EoEov261I</a></div>
<p> </p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>L'implémentation de l'IA générative chez Primary représente une avancée significative dans l'amélioration de nos processus internes et l'optimisation du temps de travail des équipes médicales.</p>
<p>Bien entendu, chez Primary nous sommes très concernés par la sécurité des données de nos patients, et aucune donnée utilisée par nos fonctionnalités d’IA ne permet d’identifier nos patients.</p>
<p>En automatisant des tâches répétitives ou chronophages, telles que la préparation des consultations et la gestion de la communication via la messagerie, nous avons non seulement amélioré l'efficacité des soignants, mais aussi enrichi l'expérience des patients.</p>
<p>Cette démarche nous permet de nous concentrer sur l'essentiel : offrir des soins de qualité. L'IA ne remplace pas l'humain, elle l'accompagne, et chez Primary, nous sommes convaincus que son potentiel continuera de transformer nos pratiques pour le mieux.</p>
]]></content:encoded></item><item><title><![CDATA[Réaliser un chatbot médical grâce au Large Language Model]]></title><description><![CDATA[Chez Primary, nous sommes à la recherche de nouveaux outils afin d'améliorer l'expérience de nos patients en cabinet, et rendre l'exercice de nos médecins le plus confortable possible. La consultation est donc au centre de nos préoccupations, et sa p...]]></description><link>https://engineeringblog.helloprimary.care/realiser-un-chatbot-medical-grace-au-large-language-model</link><guid isPermaLink="true">https://engineeringblog.helloprimary.care/realiser-un-chatbot-medical-grace-au-large-language-model</guid><category><![CDATA[llm]]></category><category><![CDATA[healthcare]]></category><dc:creator><![CDATA[Anaïs Cuisinier]]></dc:creator><pubDate>Tue, 02 Jul 2024 16:35:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710318181904/ae237edd-d45a-4c06-9914-8a18b5db78bc.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chez Primary, nous sommes à la recherche de nouveaux outils afin d'améliorer l'expérience de nos patients en cabinet, et rendre l'exercice de nos médecins le plus confortable possible. La consultation est donc au centre de nos préoccupations, et sa préparation est un levier important afin de la rendre plus efficace.</p>
<p>Notre équipe a débuté la conception d'un outil de préparation de consultation, permettant d'obtenir un maximum d'informations sur l'état de santé du patient avant qu'il n'arrive au cabinet, et de restituer ces informations de manière claire et concise à notre équipe médicale.</p>
<p>C'est là qu'un <a target="_blank" href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Model (LLM)</a> peut apporter de la valeur !</p>
<p>Nous avons décidé de nous servir d'un LLM afin de simplifier la génération d'un questionnaire de préparation à la consultation (appelé dans notre jargon pré-<a target="_blank" href="https://fr.wikipedia.org/wiki/Anamn%C3%A8se_(m%C3%A9decine)">anamnèse</a>), et ne pas tomber dans un arbre décisionnel très complexe, peu flexible et difficilement maintenable.</p>
<p>Nous avons tellement aimé les tests que nous avons fait avant de passer à l'implémentation, que nous nous sommes dit qu'il était dommage de ne pas les partager. Nous allons construire ensemble un chatbot médical :)</p>
<h2 id="heading-a-vos-claviers">A vos claviers</h2>
<p>Bien entendu, nous avons évalué différentes technologies, ce petit test porte sur l'API de l'assistant OpenAI, car elle permet une prise en mains assez simple du LLM. L'assistant OpenAI garde en mémoire les échanges sur un fil de conversation. Cette fonctionnalité peut être intéressante dans notre cas, car elle nous évite de fournir l'ensemble de l'historique de la conversation à chaque interaction.</p>
<p>Pré-requis à suivre : <a target="_blank" href="https://platform.openai.com/docs/quickstart?context=python">ici</a></p>
<ul>
<li><p>avoir python installé</p>
</li>
<li><p>un environnement virtuel python (optionnel)</p>
</li>
<li><p>avoir une clef valide d'API OpenAI</p>
</li>
<li><p>installer la librairie openai</p>
</li>
</ul>
<p>Première étape, initialiser notre modèle, dans notre fichier test assistant-test.py</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI

client = OpenAI()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_assistant</span>():</span>
    assistant = client.beta.assistants.create(
        name=<span class="hljs-string">"Mon Assistant de Test"</span>,
        instructions=<span class="hljs-string">'Tu es un médecin généraliste.Tu demandes au patient quels sont ses symptômes et s\'il va bien. '</span>
                     <span class="hljs-string">'Pose une seule question à la fois et au maximum 5 questions dans la conversation'</span>
                     <span class="hljs-string">'Quand tu as terminé, écris "fin"'</span>,
        model=<span class="hljs-string">"gpt-3.5-turbo"</span>
    )
    <span class="hljs-keyword">return</span> assistant


assistant = create_assistant()
print(assistant)
</code></pre>
<p>Bravo, vous avez initialisé votre assistant, si vous regardez bien vos logs lorsque vous lancez ce bout de code, vous verrez qu'OpenAI a conservé vos instructions, et généré un assistant avec un id. Nous allons donc nous en servir afin de discuter avec lui.</p>
<pre><code class="lang-python">---------------
LOGS
Assistant(id=<span class="hljs-string">'votre_id_d_assistant'</span>, created_at=<span class="hljs-number">1719583152</span>, description=<span class="hljs-literal">None</span>, file_ids=[], instructions=<span class="hljs-string">'Tu es un médecin généraliste.Tu demandes au patient quels sont ses symptômes et s\'il va bien.Quand tu as terminé, écris "fin"'</span>, metadata={}, model=<span class="hljs-string">'gpt-3.5-turbo'</span>, name=<span class="hljs-string">'Mon Assistant de Test'</span>, object=<span class="hljs-string">'assistant'</span>, tools=[], top_p=<span class="hljs-number">1.0</span>, temperature=<span class="hljs-number">1.0</span>, response_format=<span class="hljs-string">'auto'</span>)
</code></pre>
<p>Dans un fichier bot.py, nous allons initialiser un thread de conversation grâce à la méthode create_thread, puis associer notre fil à notre assistant.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI

client = OpenAI()

assistant_id = <span class="hljs-string">"votre_id_d_assistant"</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_thread</span>():</span>
    <span class="hljs-keyword">return</span> client.beta.threads.create()
</code></pre>
<p>Ensuite, nous allons mettre en place la logique permettant de poser une question et de lire le dernier message envoyé par l'assistant. Le principe est simple, comme dans une discussion, vous aurez le rôle "user", et l'assistant le rôle..."assistant".</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer_to_a_question</span>(<span class="hljs-params">thread_id</span>):</span>
    <span class="hljs-comment"># méthode permettant de lire les réponses d'open ai</span>
    question = read_last_message(thread_id)
    <span class="hljs-comment"># vous allez discuter directement avec le bot</span>
    response = input(question)
    <span class="hljs-comment"># méthode permettant d'envoyer les réponses à open ai</span>
    answer(response, thread_id)
    <span class="hljs-comment"># méthode permettant de lancer l'assistant</span>
    run_assistant(thread_id)
</code></pre>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer</span>(<span class="hljs-params">response, thread_id</span>):</span>
    client.beta.threads.messages.create(
        thread_id=thread_id,
        role=<span class="hljs-string">"user"</span>,
        content=response
    )
</code></pre>
<pre><code class="lang-python"><span class="hljs-comment"># nous accédons au dernier message produit par le LLM </span>
<span class="hljs-comment"># via la liste de messages du thread</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">read_last_message</span>(<span class="hljs-params">thread_id</span>):</span>
    messages = client.beta.threads.messages.list(
        thread_id=thread_id
    )
    <span class="hljs-keyword">return</span> messages.data[<span class="hljs-number">0</span>].content[<span class="hljs-number">0</span>].text.value
</code></pre>
<p>Enfin, il faut implémenter la logique permettant de récupérer les questions de l'assistant de manière asynchrone (il faut pull les questions de l'assistant jusqu'à ce qu'elles soient présentes), mais aussi associer le thread que nous avons précédemment créé afin de lancer la conversation.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">retrieve_run_status</span>(<span class="hljs-params">run_id, thread_id</span>):</span>
    run = client.beta.threads.runs.retrieve(
        thread_id=thread_id,
        run_id=run_id
    )
    <span class="hljs-keyword">return</span> run.status
</code></pre>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_assistant</span>(<span class="hljs-params">thread_id</span>):</span>
    run = client.beta.threads.runs.create(
        thread_id=thread_id,
        assistant_id=assistant_id,
    )
    status = retrieve_run_status(run.id, thread_id)
    <span class="hljs-keyword">while</span> status != <span class="hljs-string">"completed"</span>:
        status = retrieve_run_status(run.id, thread_id)
    <span class="hljs-keyword">return</span> run
</code></pre>
<p>Lancez votre chatbot à l'aide du code ci-dessous et fournissez lui des réponses plausibles.</p>
<pre><code class="lang-python">thread = create_thread()
run_assistant(thread.id)
tours = <span class="hljs-number">0</span>
<span class="hljs-keyword">while</span> tours &lt; <span class="hljs-number">5</span>:
    answer_to_a_question(thread.id)
    tours += <span class="hljs-number">1</span>
</code></pre>
<pre><code class="lang-python">--------
Bonjour, comment je peux vous aider aujourd<span class="hljs-string">'hui ? Quels sont vos symptômes et comment vous sentez-vous ? 
j'</span>ai mal à la gorge

Est-ce que vous avez également de la fièvre ?
oui

Avez-vous aussi des difficultés à avaler ?
oui comme lorsq<span class="hljs-string">u'on a une angine

Avez-vous des ganglions enflés au niveau du cou ? 
non

D'</span>accord, je vous recommande de prendre un antalgique pour soulager la douleur et de boire beaucoup d<span class="hljs-string">'eau. Si les symptômes persistent, je vous conseille de consulter un médecin. Prenez soin de vous et reposez-vous. Si vous avez d'</span>autres préoccupations, n<span class="hljs-string">'hésitez pas à me le faire savoir. Bon rétablissement ! 

Fin</span>
</code></pre>
<p>Ici, nous avons sciemment bloqué le nombre de questions à 5 car le LLM n'est pas parfait :</p>
<ul>
<li><p>les questions ne sont pas idempotentes pour un ensemble de réponses donné</p>
</li>
<li><p>même si les instructions indiquent un nombre maximal de 5 questions, le LLM peut aller plus loin, il faut le bloquer programmatiquement</p>
</li>
<li><p>la qualité du chatbot dépend quasi-exclusivement de la qualité du prompt, il faut donc l'améliorer en réalisant plusieurs itérations, et en ajoutant des instructions. C'est un métier, cela s'appelle le <a target="_blank" href="https://www.promptingguide.ai/fr">prompt engineering.</a></p>
</li>
<li><p>les réponses du chatbot peuvent parfois être surprenantes : il faut envisager un monitoring du bot pour s'assurer de sa qualité, notamment lors d'une montée de version.</p>
</li>
</ul>
<p>Nous espérons que ce petit exercice de découverte du Large Language Model vous aura donné envie de creuser le sujet :-)</p>
]]></content:encoded></item><item><title><![CDATA[Lancer le MVP de Primary en 3 mois]]></title><description><![CDATA[Cela fait déjà 8 mois que l'aventure Primary Medical a commencé pour moi et il est temps de faire un premier bilan de cette superbe expérience.
Révolutionner la médecine générale
Primary Medical, c'est la rencontre d'un médecin généraliste, Pierre-He...]]></description><link>https://engineeringblog.helloprimary.care/lancer-le-mvp-de-primary-en-3-mois</link><guid isPermaLink="true">https://engineeringblog.helloprimary.care/lancer-le-mvp-de-primary-en-3-mois</guid><category><![CDATA[retour d'expérience]]></category><category><![CDATA[mvp]]></category><category><![CDATA[startup]]></category><category><![CDATA[healthcare]]></category><dc:creator><![CDATA[Vincent Dubois]]></dc:creator><pubDate>Thu, 14 Mar 2024 20:18:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/v9FQR4tbIq8/upload/d15ac3b106cf82bea0d89cae443be1a0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cela fait déjà 8 mois que l'aventure <a target="_blank" href="https://www.helloprimary.care/">Primary Medical</a> a commencé pour moi et il est temps de faire un premier bilan de cette superbe expérience.</p>
<h2 id="heading-revolutionner-la-medecine-generale">Révolutionner la médecine générale</h2>
<p>Primary Medical, c'est la rencontre d'un médecin généraliste, <a target="_blank" href="https://www.linkedin.com/in/pierre-henri-gorioux-686529146/">Pierre-Henri Gorioux</a>, d'un entrepreneur de la tech, <a target="_blank" href="https://www.linkedin.com/in/thibault-lanthier/">Thibault Lanthier</a> (ex. CEO de Mon Docteur), de <a target="_blank" href="https://www.linkedin.com/in/laurenemee/">Laure Némée</a> (ex. CTO de Leetchi et Mangopay) et d'<a target="_blank" href="https://www.linkedin.com/in/alexis-mathieu-00292060/">Alexis Mathieu</a> (ex. CEO de FeetMe).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709816198295/1188345d-698d-4eaa-9345-22605ad8472f.jpeg" alt class="image--center mx-auto" /></p>
<p>L'objectif de Primary est très clair : révolutionner le domaine de la médecine générale, en s'attaquant au fonctionnement des cabinets médicaux. Il s'agit de créer des cabinets nouvelle génération, mettant en avant la prévention afin d'apporter des soins de qualité aux patients, tout en permettant aux médecins généralistes de ces cabinets de pratiquer la médecine dans les meilleures conditions.</p>
<p>La première étape de Primary était de développer une application mobile compagnon afin d’apporter plus de transparence et de proximité entre le cabinet et le patient. En plus de pouvoir accéder à ses rendez-vous et ses documents médicaux, le patient a accès à un chat ouvert 6j/7 afin d’échanger facilement avec son équipe médicale et peut accéder au compte-rendu rédigé par son médecin après chaque consultation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709816347260/3811f13e-0cad-4b39-b331-ebf396ab3392.png" alt="Maquette de notre application mobile" class="image--center mx-auto" /></p>
<h2 id="heading-un-defi-dampleur">Un défi d'ampleur</h2>
<p>Cela fait quelques heures à peine que je viens d'arriver dans l'équipe <a target="_blank" href="https://www.helloprimary.care/">Primary Medical</a> et je dois relever mon premier défi : arriver à sortir en 3 mois à peine, non seulement un backend pour le MVP de notre application mobile mais également des intégrations avec les logiciels utilisés dans les cabinets, où sont stockées les données patients.</p>
<p>Il faut que l'app sorte avant la fin du mois d'octobre et le rush de l'hiver dans nos cabinets médicaux.</p>
<p>Créer une application de zéro est un tout autre défi que de créer des fonctionnalités sur une application existante. En plus du scope fonctionnel, il a fallu faire des choix structurants et mettre en place :</p>
<ul>
<li><p>l'organisation de l’équipe et la méthodologie de delivery</p>
</li>
<li><p>la stack technique</p>
</li>
<li><p>l'intégration backend/mobile</p>
</li>
<li><p>l’hébergement</p>
</li>
<li><p>la sécurisation de la plateforme</p>
</li>
<li><p>l'observabilité de la plateforme</p>
</li>
</ul>
<p>Autant dire qu'il a fallu faire preuve de pragmatisme dans beaucoup de nos choix : hébergement, services tiers, coûts des solutions, langages utilisés, architectures, ...</p>
<h3 id="heading-des-sprints-dune-semaine">Des sprints d'une semaine</h3>
<p>Le fait de fonctionner en sprints d'une semaine depuis le début nous a permis de nous imposer des deadlines agressives : nous avons ainsi pu réagir rapidement quand l'attendu n'était pas au rendez-vous. Aujourd'hui, je suis certain que ce mode de fonctionnement a énormément contribué à tenir ce délai de 3 mois.</p>
<h3 id="heading-choix-de-la-stack-technique-et-de-larchitecture-de-la-plateforme">Choix de la stack technique et de l'architecture de la plateforme</h3>
<p>Notre choix a été guidé par 2 objectifs pas forcément alignés : un enjeu très court terme de mise en production mais un objectif à long terme de scalabilité puisque l'ambition de Primary est d'ouvrir des dizaines de cabinets partout en France.</p>
<p>Le choix du langage <strong>Java</strong> et de <strong>Spring Boot</strong> pour supporter l'application a été le choix le plus pragmatique : c'est une stack technique stable et mature, qui nous permet de profiter d'une large communauté existante. Trouver des développeurs séniors maîtrisant ces technologies est un atout qui nous permet d'envisager l'avenir de Primary Medical sereinement.</p>
<p>Etant donné le périmètre fonctionnel très riche à couvrir, nous sommes partis sur un <a target="_blank" href="https://blog.mathieueveillard.com/modular-monolith-is-the-new-microservices/">monolithe modulaire</a>, avec pour chacun des sous modules une architecture hexagonale, ce qui permet de nous concentrer sur le métier de l'application, tout en fournissant les moyens nécessaires pour changer facilement les implémentations techniques au besoin. Ce choix là était logique au vu de la taille de l'équipe, ainsi que par rapport à nos ambitions de croissance. En effet, il sera très facile à posteriori d'extraire les sous modules en applications à part entière.</p>
<p>Enfin, pour construire les bases d'un backend maintenable à long terme, et le plus aligné possible avec notre métier, nous avons adopté une <a target="_blank" href="https://www.groupeonepoint.com/fr/nos-publications/event-driven-architecture-un-modele-darchitecture-repondant-aux-enjeux-daujourdhui-et-de-demain/">event driven architecture</a> : chacun de nos use cases émet des <a target="_blank" href="https://martinfowler.com/eaaDev/DomainEvent.html">événements métier</a> en sortie. Ainsi, à chaque modification dans le système, d'autres sous modules peuvent s'y abonner de manière très simple. Pour que cela fonctionne, nous attachons une important particulière au nommage de nos domain events afin qu'ils soient les plus alignés avec notre métier. Je reviendrai plus en détail sur cette architecture dans d'autres billets de blog.</p>
<p>Cette partie est celle où il y a le plus de défi : il faut arriver à faire comprendre et utiliser les concepts du <a target="_blank" href="https://blog.octo.com/domain-driven-design-des-armes-pour-affronter-la-complexite">Domain-Driven Design</a> à toute l'équipe tech de Primary, sans les assommer avec du jargon inutile. L'objectif est d'arriver à faire en sorte que le code de notre backend soit maintenable à long terme et donc le plus aligné possible avec notre métier.</p>
<h3 id="heading-hebergement-de-la-plateforme">Hébergement de la plateforme</h3>
<p>Difficile d'imaginer, avec une équipe tech très petite au démarrage, de réaliser nous même l'hébergement. Le choix le plus pragmatique est donc de partir sur un hébergement de type SaaS, qui nous rend le maximum de service et nous laisse le maximum de temps pour nous concentrer sur l'essentiel, la construction de notre app.</p>
<p>Le choix de <a target="_blank" href="https://www.clever-cloud.com">Clever Cloud</a> nous est apparu comme le plus pertinent : c'est un provider français, qui fournit des zones <a target="_blank" href="https://certification.afnor.org/numerique/hebergement-des-donnees-de-sante-hds">certifiées HDS</a>, c'est à dire des espaces informatiques spécialement conçus pour stocker des données médicales sensibles de manière sécurisée.</p>
<h3 id="heading-integration-backendmobile-et-choix-techniques">Intégration backend/mobile et choix techniques</h3>
<p>Nous sommes à presque 2 mois après le démarrage de l'aventure, mais aucune app mobile n'est encore commencée 🙃 ! En effet, mon collègue Thomas Pucci, développeur mobile, n'intégrera l'équipe qu'au mois d'octobre, c'est à dire le mois de la sortie de l'app. Heureusement, un freelance mobile que nous trouvons début août et qui travaillera avec nous depuis le début du mois de septembre réalisera l'exploit de développer l'ensemble des écrans ainsi que l'intégration avec le backend en l'espace d'à peine plus d'un mois.</p>
<p>Néanmoins, nous nous voyons avec Thomas une fois par semaine pour faire un état des lieux ainsi que pour prendre des décisions sur l'intégration entre le mobile et le backend. C'est à cette période que l'on choisit GraphQL, très utilisé dans la communauté mobile. Le choix de Flutter pour l'application mobile elle-même était un <a target="_blank" href="https://primaryengineeringblog.hashnode.dev/notre-choix-de-flutter">choix réalisé de longue date</a>.</p>
<p>Notre authentification étant une authentification par mobile, le choix le plus évident a été de partir sur Firebase, encore une fois pour des raisons de rapidité d'intégration. Une fois ce choix entériné, il a été très simple de préparer le terrain côté backend grâce à Spring Security, qui gère nativement l'Oauth2.</p>
<p>Dans l'ombre, Thomas travaille déjà sur l'intégration continue mobile et le déploiement de l'app sur les stores iOS et Android. À son arrivée dans l'équipe, il se coordonne avec le développeur freelance pour la continuité de l'app, deux semaines à peine avant son lancement officiel.</p>
<h3 id="heading-securisation-de-la-plateforme">Sécurisation de la plateforme</h3>
<p>Comme je l'écrivais plus haut, notre plateforme backend repose sur une event-driven architecture. Les sous modules métier de notre monolithe modulaire publient des événements dans RabbitMQ. Les processus qui s'abonnent à ces événements peuvent néanmoins générer des erreurs, ce que nous devons gérer.</p>
<p>Il est donc important de pouvoir corriger ces erreurs, et de pouvoir rejouer ces événements qui n'ont pas été consommés correctement.</p>
<p>Nous avons opté pour la mise en place d'une <a target="_blank" href="https://en.wikipedia.org/wiki/Dead_letter_queue">dead letter queue</a>. C'est une stratégie classique employée dans le monde des message oriented middlewares comme RabbitMQ : un événement (message) qui ne peut pas être consommé correctement part alors dans une file spécialisée, nommée dead letter queue, dans le but d'être rejoué plus tard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710184833538/2dce9eb2-dc8b-4b7c-a9e0-de2ce640876a.png" alt class="image--center mx-auto" /></p>
<p>Quelques semaines avant la première mise en production, nous nous outillons pour pouvoir visualiser le contenu des événements rejetés, l'exception Java liée à ce rejet, ainsi que pour pouvoir renvoyer ces événements une fois le code corrigé.</p>
<h3 id="heading-observabilite-de-la-plateforme">Observabilité de la plateforme</h3>
<p>Avant de partir en production, il était important que nous puissions observer ce qui se passe sur notre application.</p>
<p>Côté mobile, les analytics d'utilisation de nos écrans sont poussés dans <a target="_blank" href="https://mixpanel.com/">Mixpanel</a>, les erreurs dans <a target="_blank" href="https://firebase.google.com/products/crashlytics">Crashlytics</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709724850159/eb6a4c81-f4e3-4639-80af-c6466ce84f8a.png" alt class="image--center mx-auto" /></p>
<p>Côté backend, notre <a target="_blank" href="https://www.elastic.co/fr/observability/application-performance-monitoring">APM Elastic</a> nous permet d'avoir une bonne vision, avec d'une part les API servies au mobile et d'autre part nos event handlers et nos batchs. L'application de gestion de notre dead letter queue permet de compléter nos besoins en observabilité.</p>
<h2 id="heading-lancement-de-lapplication-en-production">Lancement de l'application en production</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709723525375/f2839562-78db-441d-9642-231e23bf6908.jpeg" alt class="image--center mx-auto" /></p>
<p>Malgré des délais serrés, un scope fonctionnel large et une application mobile qui n’avait pas été commencée à être développée avant début septembre, les choix techniques de l’été ont fini par payer.</p>
<p>Le 26 octobre 2023, toute l'équipe s'est retrouvée dans un de nos cabinets médicaux de Bordeaux pour le lancement officiel de l'application. Elle vient d'être validée par Google et Apple, nous sommes fins prêts !</p>
<p>L'objectif de la journée est d'assister les patients de ce premier cabinet afin de les onboarder dans l'application. Notre product manager participe activement, ainsi que les assistantes médicales, qui informent les patients sur l'app lors de leur arrivée en cabinet.</p>
<p>Nous recueillons le maximum de feedbacks afin de corriger les petits détails, tant visuels dans l'app que dans notre backend.</p>
<p>Cette journée a été un succès, mais surtout c'est l'aboutissement d'un sprint de 3 mois 😮‍💨</p>
<h2 id="heading-bilan">Bilan</h2>
<p>Avec des délais très serrés, nous sommes arrivés à livrer une application de qualité, majoritairement grâce au pragmatisme dont nous avons fait preuve, tant l'équipe tech, que Jean Lebas-Guenoun, notre PM, qui a su choisir les éléments à enlever du périmètre initialement prévu.</p>
<p>Le lancement de l'application en production, très "manuel" car il s'est fait patient par patient en face à face, a connu une courbe de démarrage très lente la première semaine. C'est normal, car aucun des patients n'avait connaissance de son existence tant qu'ils ne venaient pas au cabinet. Il a également fallu du temps aux équipes médicales pour systématiser le passage d'information.</p>
<p>La semaine suivant cette première mise en production, nous avons implémenté le système de notifications (mail, SMS, notifications push) qui nous a permis ensuite de connaître une croissance constante des inscriptions sur l'application. Les inscriptions se font au fil de l'eau, lorsque les patients prennent rendez-vous avec nos médecins.</p>
<p>Aujourd'hui, plus de 4 mois après notre lancement, 12% de notre patientèle a installé l'application et l'a déjà utilisée. Nos statistiques montrent une très bonne adhésion à l'application. La messagerie, notamment, est très utilisée en remplacement du télésecrétariat et permet à nos assistantes médicales de gagner beaucoup de temps.</p>
<p>Côté technique, notre bilan est très encourageant :</p>
<ul>
<li><p>Le découpage de notre monolithe en sous modules métier nous a permis, malgré la pression permanente, de maîtriser la complexité grandissante du scope fonctionnel.</p>
</li>
<li><p>Nous avons pu livrer un backend de qualité avec les caractéristiques minimales pour partir sereinement en production.</p>
</li>
<li><p>Nous avons pu sortir l'application mobile à temps malgré un énorme retard au démarrage.</p>
</li>
</ul>
<p>On a hâte d'écrire pour partager la suite des événements. Mais ça, ce sera pour un autre billet 😉.</p>
]]></content:encoded></item><item><title><![CDATA[Notre choix de Flutter]]></title><description><![CDATA[React Native, Jetpack Compose & Swift UI, avec ou sans KMM, Flutter… voilà nos candidats 💪
Nous vous exposons dans cet article les critères qui ont guidés notre choix.

💡
Chez Primary, pour nos patients, nous développons une appli mobile compagnon ...]]></description><link>https://engineeringblog.helloprimary.care/notre-choix-de-flutter</link><guid isPermaLink="true">https://engineeringblog.helloprimary.care/notre-choix-de-flutter</guid><category><![CDATA[mobile app development]]></category><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[Thomas Pucci]]></dc:creator><pubDate>Mon, 11 Mar 2024 13:05:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709575781578/19575bd5-b3c3-409f-81c8-2b8ff775e382.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>React Native, Jetpack Compose &amp; Swift UI, avec ou sans KMM, Flutter… voilà nos candidats 💪</p>
<p>Nous vous exposons dans cet article les critères qui ont guidés notre choix.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Chez Primary, pour nos patients, nous développons une appli mobile compagnon leur permettant notamment d'obtenir leur temps d'attente en salle d'attente ou de chatter avec l'équipe médicale de leur cabinet.</div>
</div>

<h2 id="heading-une-forte-attention-au-cout-dintegration-ui">Une forte attention au coût d'intégration UI</h2>
<p>Nos écrans sont les mêmes sous Android et iOS. De très rares éléments UI sont spécifiques à un OS (par exemple certaines icônes), mais c'est anecdotique. Ce critère favorise clairement React Native et Flutter.</p>
<h2 id="heading-un-cout-dintegration-des-fonctionnalites-natives-quasi-nul">Un coût d'intégration des fonctionnalités natives quasi-nul</h2>
<p>Parmi les API de géolocalisation, de bluetooth, de wifi, d'accéléromètre, nous comptons en utiliser… aucune 😅 Encore un critère qui favorise React Native et Flutter, et un peu KMM.</p>
<p>Seule l'API d'accéléromètre est utilisée pour une fonctionnalité cachée : l'affichage de menus cachés sur notre app de Staging.</p>
<hr />
<p><em>À ce stade, pour notre business case, il nous reste à trancher entre React Native et Flutter.</em></p>
<h2 id="heading-le-cout-de-montee-en-competence">Le coût de montée en compétence</h2>
<p>Sur les quatre membres de notre équipe tech, un seul possède une expérience mobile (et ce membre n'a pas d'expérience professionnelle en Flutter). Il nous apparaît important que le framework que nous choisirons permette une montée en compétence rapide.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Dans nos recrutements, nous apportons une forte attention à l'envie de progresser et la culture Craftsmanship - ou Artisanat Logiciel. Nous évitons le biais de choisir un framework maîtrisé par un membre d'équipe.</div>
</div>

<h3 id="heading-quels-sont-les-elements-dun-framework-permettant-une-bonne-montee-en-competence">Quels sont les éléments d'un framework permettant une bonne montée en compétence ?</h3>
<ul>
<li><p>La grammaire, la lisibilité et la verbosité du langage.</p>
</li>
<li><p>La sécurité du langage (= la facilité de se rendre compte de l'introduction d'un bug)</p>
</li>
<li><p>L'activité de la communauté.</p>
</li>
<li><p>La qualité des devtools.</p>
</li>
</ul>
<p>Si sur le premier point, Javascript et Dart sont à peu près au même niveau - et encore c'est un critère dont l'évaluation est très subjective - les trois derniers points sont plus faciles à trancher.</p>
<p>En effet, l'<code>analyzer</code> et le <code>linter</code> Dart sont indéniablement plus puissants que les linters et les LSP Typescript.</p>
<p>Ensuite, comme relevé par <a target="_blank" href="https://www.bam.tech/a-propos">Marek Kalnik de BAM</a> dans son introduction de la <a target="_blank" href="https://reactnativeconnection.io/">React Native / Flutter Connection 2023</a>, l'intérêt pour Flutter n'a cessé d'augmenter en France depuis 2017, jusqu'à dépasser React Native au printemps 2020 👇</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709574959032/9c5efdd7-3e7f-4e4a-bd04-d0631e17fc4d.png" alt class="image--center mx-auto" /></p>
<p>Enfin, le support intégré des DevTools Flutter via ses intégrations VSCode et Android Studio (ou plus globalement IntelliJ) nous apporte un confort supérieur aux outils de React Native comme <a target="_blank" href="https://fbflipper.com/">Flipper</a>. D'autant plus depuis la version 3.16 de Flutter qui apporte <a target="_blank" href="https://medium.com/@sahaj.blup/whats-new-in-flutter-3-16-e14dd0f4dc89">la capacité d'étendre les DevTools</a>.</p>
<h2 id="heading-retrospective">Rétrospective</h2>
<p>Notre app a 6 mois aujourd'hui ! C'est un beau bébé. Il se porte à merveille.</p>
<p>Une qualité qui nous est apparue et qui pourtant n'était pas dans nos critères initiaux est la facilité d'opérer une stratégie monorepo tant les actions de refactos sont complètes, faciles, sécurisantes et rapides.</p>
<p>Nous en parlerons dans un prochain billet de blog 😃</p>
<p>A bientôt !</p>
]]></content:encoded></item></channel></rss>