Skip to content

Fix: signature du médecin validateur sur le document et échec visible du contrôle d'interactions - #66

Merged
Zoubeir23 merged 9 commits into
mainfrom
Fix/066-signature-du-validateur-et-echec-controle-interactions
Aug 2, 2026
Merged

Fix: signature du médecin validateur sur le document et échec visible du contrôle d'interactions#66
Zoubeir23 merged 9 commits into
mainfrom
Fix/066-signature-du-validateur-et-echec-controle-interactions

Conversation

@Zoubeir23

@Zoubeir23 Zoubeir23 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Contexte

Deux défauts relevés lors de l'audit des fonctionnalités signature / carnet / ordonnance, tous deux
sur des documents médicaux opposables ou la sécurité du patient. Les retours de review ont révélé
que la première version de cette PR ne corrigeait qu'à moitié le second point.

1. Le document portait la signature du lecteur, pas du médecin validateur

app/(app)/app/diagnostics/[id]/page.tsx appelait getDoctorSignature(), qui retourne la
signature de l'utilisateur connecté, puis l'apposait sous le nom et le n° RPPS du praticien
enregistré dans le diagnostic. Si le Dr B ouvrait une ordonnance validée par le Dr A, le document
imprimé portait le nom et le RPPS de A avec la signature manuscrite de B.

La page appelle désormais getSignatureForValidatedDiagnostic(diagnosticId) : le serveur résout
lui-même le validateur
. Aucune Server Action ne prend plus d'identifiant d'utilisateur libre, ce
qui supprime au passage une primitive d'énumération — n'importe quel membre de la clinique pouvait
appeler getDoctorSignatureByUserId(userId) depuis son navigateur et récupérer l'image de
signature d'un confrère sans ouvrir le moindre document.

Situation Avant Après
Validé par le lecteur signature du lecteur inchangé
Validé par un autre médecin signature du lecteur sous le nom d'autrui signature du validateur
Non validé signature apposée aucune signature
Validateur sans signature signature du lecteur trait vierge
Validateur d'une autre clinique signature du lecteur aucune signature

validated_by_user_id est ajouté à DiagnosticRecord : la colonne existe depuis la migration 010
et select("*") la remontait déjà, mais le type l'ignorait.

2. Une panne du service d'interactions s'affichait comme « aucune interaction »

Le défaut avait deux couches, et la première version de cette PR n'en traitait qu'une.

Couche serveur (la plus grave, corrigée depuis). lib/who-drug-interactions.ts renvoyait
{ hasCritical: false, interactions: [] } quand RxNav répondait en erreur ou levait. La route API
retournait donc HTTP 200 avec une liste vide, et le prescripteur lisait une confirmation rassurante
alors qu'aucun contrôle n'avait eu lieu. Le résultat porte maintenant un status
(checked | unavailable) et la route répond 502 sur indisponibilité.

Couche client. Le panneau n'était rendu que s'il y avait des interactions ou un chargement en
cours : « API tombée » et « aucune interaction » produisaient le même écran vide. Le contrôle
expose désormais son issue dans tous les cas :

  • checking — contrôle en cours
  • found — liste des interactions
  • clear — confirmation explicite qu'aucune interaction n'est connue
  • failed — message en rouge « vérifiez manuellement avant de prescrire » + bouton de relance

Corrections complémentaires issues de la review :

  • Réponses obsolètes — deux contrôles pouvaient se chevaucher et une réponse ancienne écraser
    une plus récente, un clear périmé masquant une alerte réelle. Un identifiant monotone écarte
    toute réponse qui n'appartient pas au dernier contrôle.
  • Forme de réponse inattendue — un corps JSON sans tableau interactions était lu comme une
    liste vide, donc affiché comme clear. Il bascule maintenant en failed.
  • Médicaments sans code RxNorm — ils sortaient silencieusement du contrôle ; leur nombre est
    affiché sous le panneau.

Six libellés ajoutés en FR et EN sous diagnostics.prescriptionStep.

Tests

__tests__/lib/drug-interactions.test.ts — 6 tests couvrant le correctif de fond :
indisponibilité sur réponse non-OK, sur exception réseau, distinction avec une absence réelle
d'interaction, absence d'appel réseau sous deux codes valides, remontée des sévérités hautes,
déduplication inter-sources.

Ces tests ont été vérifiés contre le défaut : en réintroduisant l'ancien comportement
(!response.ok renvoyant checked), la suite échoue.

  • npx tsc --noEmit, npm run lint — propres, et la dette any de doctor-signature.ts
    passe de 6 à 4 occurrences
  • npx vitest run — 199 tests
  • npm run build — succès

Limite restante : la correction n° 1 n'est pas atteignable par le harnais actuel (Vitest en
environnement node) et sa vérification au navigateur suppose un diagnostic validé en base avec
une signature enregistrée, données que je n'ai pas créées dans le projet Supabase.

Non traité — relevé pendant l'audit

  • doctor_signatures_select_clinic laisse tout membre de la clinique lire l'image de signature de
    n'importe quel médecin au niveau RLS. Cette PR ferme la voie applicative, pas la policy.
  • « Signature électronique » désigne ici un tampon PNG : aucun scellement, aucune empreinte du
    contenu signé.
  • checkAllergyConflicts compare par sous-chaîne : « pénicilline » ne déclenche rien sur
    « Amoxicilline ».
  • L'import de carnet par code porteur n'est ni journalisé ni révocable.

Copilot AI review requested due to automatic review settings August 2, 2026 04:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
doc-flow-ai Ready Ready Preview Aug 2, 2026 4:27am

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Zoubeir23, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf9e71ec-96de-4819-9fbe-fa6736323fa5

📥 Commits

Reviewing files that changed from the base of the PR and between 939cadb and 4755516.

📒 Files selected for processing (6)
  • __tests__/lib/drug-interactions.test.ts
  • actions/doctor-signature.ts
  • app/(app)/app/diagnostics/[id]/page.tsx
  • app/api/who/drug-interactions/route.ts
  • components/diagnostics/prescription-builder-step.tsx
  • lib/who-drug-interactions.ts
📝 Walkthrough

Walkthrough

Le diagnostic charge la signature du médecin validateur lorsque les conditions de validation sont réunies. Le contrôle des interactions médicamenteuses utilise des états explicites et affiche les résultats, les erreurs, les relances et les médicaments sans code RxNorm.

Changes

Diagnostic et prescription

Layer / File(s) Summary
Signature du médecin validateur
types/index.ts, app/(app)/app/diagnostics/[id]/page.tsx
DiagnosticRecord inclut validated_by_user_id. La page charge la signature correspondante uniquement pour un diagnostic validé.
États du contrôle des interactions
components/diagnostics/prescription-builder-step.tsx, messages/en.json, messages/fr.json
Le contrôle distingue les états idle, checking, clear, found et failed. L’interface affiche les résultats, les erreurs relançables et les médicaments sans code RxNorm. Les traductions anglaises et françaises couvrent ces messages.

Estimated code review effort: 3 (Modéré) | ~20 minutes

Suggested reviewers: copilot

Poem

Un lapin vérifie les potions,
Avec des états et des précisions.
La signature suit le bon médecin,
Les alertes indiquent le chemin.
RxNorm absent, le message prévient,
Et chaque contrôle revient serein.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Le titre décrit clairement les deux changements principaux : la signature du médecin validateur et la visibilité de l’échec du contrôle des interactions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
app/(app)/app/diagnostics/[id]/page.tsx (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralisez la valeur de statut validated.

La ligne 25 code une valeur métier en dur. Réutilisez une constante partagée dans cette condition et dans les autres contrôles de statut afin d’éviter une divergence future.

As per coding guidelines — les valeurs métier ne doivent pas être codées en dur; définissez-les dans une constante ou une configuration partagée.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(app)/app/diagnostics/[id]/page.tsx at line 25, Replace the hard-coded
"validated" status in the diagnostic validation condition with the shared status
constant, and update the other status checks in this flow to reuse the same
constant. Keep the existing validation behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/`(app)/app/diagnostics/[id]/page.tsx:
- Around line 24-27: Restrict the getDoctorSignatureByUserId call in the
diagnostic page to callers authorized by lib/rbac.ts: only owner or super_admin
roles may retrieve the signature. Apply the existing role guard before invoking
it, and preserve the null result for unvalidated diagnostics or unauthorized
callers.

In `@components/diagnostics/prescription-builder-step.tsx`:
- Around line 150-186: Prevent stale checkInteractions responses from updating
current state when treatment changes trigger overlapping requests. Track a
monotonically increasing check identifier or cancel the previous fetch, and
guard every response, error, and completion state update so only the latest
invocation of checkInteractions can update drugInteractions, uncoded count, or
interactionCheckState.
- Around line 447-475: Replace the raw Tailwind color classes in the interaction
status UI—specifically the amber classes on the AlertTriangle and heading plus
the emerald class in the clear-state paragraph—with the semantic alert and
success theme tokens defined in the global theme configuration. Preserve the
existing failed-state destructive styling and all interactionCheckState
behavior.
- Around line 180-183: Validate the parsed response in the drug-interactions
request before updating state: define or reuse a Zod schema for the response and
each DrugInteractionPair, including the allowed interaction severity values, and
parse the JSON result. Only call setDrugInteractions and
setInteractionCheckState with validated data; on schema failure, set the
interaction check state to "failed" instead of treating missing, null, or
malformed interactions as clear.

---

Nitpick comments:
In `@app/`(app)/app/diagnostics/[id]/page.tsx:
- Line 25: Replace the hard-coded "validated" status in the diagnostic
validation condition with the shared status constant, and update the other
status checks in this flow to reuse the same constant. Keep the existing
validation behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba6ccd7c-e3f8-4e79-9ee1-718e09b618ad

📥 Commits

Reviewing files that changed from the base of the PR and between 819370f and 939cadb.

📒 Files selected for processing (5)
  • app/(app)/app/diagnostics/[id]/page.tsx
  • components/diagnostics/prescription-builder-step.tsx
  • messages/en.json
  • messages/fr.json
  • types/index.ts

Comment thread app/(app)/app/diagnostics/[id]/page.tsx Outdated
Comment thread components/diagnostics/prescription-builder-step.tsx
Comment on lines +180 to +183
const result: { interactions: DrugInteractionPair[] } = await response.json();
const foundInteractions = result.interactions ?? [];
setDrugInteractions(foundInteractions);
setInteractionCheckState(foundInteractions.length > 0 ? "found" : "clear");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Localiser les schémas de validation existants avant d'ajouter un schéma de réponse.
fd -a '^validations\.ts$' . | while IFS= read -r file; do
  ast-grep outline "$file" --items all
done

# Rechercher les contrats et schémas existants pour les interactions médicamenteuses.
rg -n -C 3 --glob '*.{ts,tsx}' \
  'DrugInteractionPair|drugInteraction.*Schema|interactions.*z\.(array|object)'

Repository: Zoubeir23/DocFlowAI

Length of output: 1542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspectez le composant concerné et ses dépendances d'interaction médicamenteuse.
target="components/diagnostics/prescription-builder-step.tsx"
if [ -f "$target" ]; then
  echo "== file exists =="
  wc -l "$target"
  echo "== outline =="
  ast-grep outline "$target" --items all || true
  echo "== relevant lines =="
  sed -n '1,260p' "$target" | nl -ba
fi

echo "== search DrugInteractionPair and related symbols =="
rg -n -C 2 --glob '*.{ts,tsx}' 'DrugInteractionPair|drugInteraction|interactions|Warning|severity|failure|failed' || true

echo "== validations file =="
sed -n '1,180p' lib/validations.ts | nl -ba

Repository: Zoubeir23/DocFlowAI

Length of output: 2019


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Listiez les outils disponibles pour lire les segments de fichier.
command -v sed || true
command -v awk || true

file="components/diagnostics/prescription-builder-step.tsx"
types_file="types.ts"
for target in "$file" "$types_file"; do
  if [ -f "$target" ]; then
    echo "== $target =="
    wc -l "$target"
    sed -n '1,260p' "$target"
  else
    echo "== missing $target =="
  fi
done

echo "== DrugInteractionWarning =="
fd -a 'drug-interaction-warning\.(tsx|ts)$' . | while IFS= read -r f; do
  echo "== $f =="
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo "== DrugInteractionPair candidates =="
rg -n -C 3 --glob '*.{ts,tsx}' 'type DrugInteractionPair|interface DrugInteractionPair|DrugInteractionPair' || true

Repository: Zoubeir23/DocFlowAI

Length of output: 13094


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate types files =="
fd -a '\.(ts|tsx)$' . | rg '/types\.ts$|types/$|types' || true

echo "== search DrugInteractionPair in tracked files =="
git ls-files | xargs rg -n -C 4 'DrugInteractionPair' || true

echo "== read candidate types files =="
for f in $(git ls-files | rg '/types\.ts$'); do
  echo "== $f =="
  wc -l "$f"
  sed -n '1,260p' "$f"
done

echo "== inspect response body path if any =="
git ls-files | rg 'api/who/drug-interactions|drug.*interaction' | while IFS= read -r f; do
  echo "== $f =="
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo "== validate empty/missing JSON behavior =="
node - <<'JS'
for const value of [{}, {interactions: undefined}, {interactions: null}, {interactions: true}, {interactions: [{severity: "unknown"}]}] {
  const found = value.interactions ?? []
  const state = found.length > 0 ? "found" : "clear"
  try {
    const severityOrder = { high: 0, moderate: 1, low: 2 }
    const sorted = [...found].sort((first, second) => severityOrder[first.severity] - second.severity)
    console.log(JSON.stringify(value), "=>", state, "sorted:", JSON.stringify(sorted))
  } catch (e) {
    console.log(JSON.stringify(value), "=> crashed:", e.message)
  }
}
JS

Repository: Zoubeir23/DocFlowAI

Length of output: 17442


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== node behavior probe =="
node - <<'JS'
for (const value of [
  {},
  {interactions: undefined},
  {interactions: null},
  {interactions: true},
  {interactions: [{drug1Name: "A", drug2Name: "B", severity: "unknown", description: "x", source: "r"}]},
  {interactions: [{drug1Name: "A", drug2Name: "B", severity: "high", description: "x", source: "r"}]},
]) {
  const result = { interactions: value.interactions ?? [] }
  const foundInteractions = result.interactions
  const state = foundInteractions.length > 0 ? "found" : "clear"
  try {
    const sortedInteractions = [...foundInteractions].sort((first, second) => {
      const severityOrder = { high: 0, moderate: 1, low: 2 }
      return severityOrder[first.severity] - second.severity
    })
    const config = {
      high: { containerClass: "bg-red-50 border-red-200" },
      moderate: { containerClass: "bg-amber-50 border-amber-200" },
      low: { containerClass: "bg-blue-50 border-blue-200" },
    }
    const interaction = sortedInteractions[0]
    const actualConfig = config[interaction && interaction.severity] || {}
    console.log(JSON.stringify(value), "=> result.interactions =", JSON.stringify(result.interactions), "state =", state, "className =", actualConfig.containerClass || "undefined")
  } catch (error) {
    console.log(JSON.stringify(value), "=> crashed =", error.message)
  }
}
JS

echo "== read full route =="
sed -n '1,160p' app/api/who/drug-interactions/route.ts

Repository: Zoubeir23/DocFlowAI

Length of output: 2208


Validez la réponse /api/who/drug-interactions avant de mettre à jour l’état.

La validation TypeScript de result.interactions ne protège pas les réponses JSON corrompues. Une clé interactions manquante ou null est affichée comme interactionsClear; un tableau contenant une sévérité invalide peut faire échouer DrugInteractionWarning. Ajoutez un schéma Zod pour interactions et chaque DrugInteractionPair, puis passez l’état à failed en cas d’échec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/diagnostics/prescription-builder-step.tsx` around lines 180 - 183,
Validate the parsed response in the drug-interactions request before updating
state: define or reuse a Zod schema for the response and each
DrugInteractionPair, including the allowed interaction severity values, and
parse the JSON result. Only call setDrugInteractions and
setInteractionCheckState with validated data; on schema failure, set the
interaction check state to "failed" instead of treating missing, null, or
malformed interactions as clear.

Source: Coding guidelines

Comment on lines +447 to +475
<AlertTriangle
className={`w-4 h-4 ${interactionCheckState === "failed" ? "text-destructive" : "text-amber-500"}`}
/>
<h3
className={`text-sm font-semibold uppercase tracking-wide ${
interactionCheckState === "failed" ? "text-destructive" : "text-amber-600"
}`}
>
{t("prescriptionStep.interactionsTitle")}
{interactionCheckState === "checking" && (
<Loader2 className="inline ml-2 w-3 h-3 animate-spin" />
)}
</h3>
</div>
<DrugInteractionWarning interactions={drugInteractions} />

{interactionCheckState === "checking" && (
<p className="text-sm text-muted-foreground">
{t("prescriptionStep.interactionsChecking")}
</p>
)}

{interactionCheckState === "found" && (
<DrugInteractionWarning interactions={drugInteractions} />
)}

{interactionCheckState === "clear" && (
<p className="flex items-center gap-2 text-sm text-emerald-600">
<Check className="w-4 h-4" />
{t("prescriptionStep.interactionsClear")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remplacez les couleurs Tailwind brutes par des tokens de thème.

text-amber-500, text-amber-600 et text-emerald-600 ne suivent pas les tokens définis dans app/globals.css. Ils peuvent aussi produire un contraste incohérent dans le thème sombre.

Utilisez des tokens sémantiques pour les états alerte et succès.

As per coding guidelines, « Utiliser uniquement les tokens de thème définis dans app/globals.css; ne pas coder de couleurs hexadécimales ou de couleurs sombres en dur ».

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/diagnostics/prescription-builder-step.tsx` around lines 447 - 475,
Replace the raw Tailwind color classes in the interaction status UI—specifically
the amber classes on the AlertTriangle and heading plus the emerald class in the
clear-state paragraph—with the semantic alert and success theme tokens defined
in the global theme configuration. Preserve the existing failed-state
destructive styling and all interactionCheckState behavior.

Source: Coding guidelines

@Zoubeir23
Zoubeir23 merged commit ded1dfe into main Aug 2, 2026
5 checks passed
@Zoubeir23
Zoubeir23 deleted the Fix/066-signature-du-validateur-et-echec-controle-interactions branch August 2, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants