-
Notifications
You must be signed in to change notification settings - Fork 0
Fix: signature du médecin validateur sur le document et échec visible du contrôle d'interactions #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: signature du médecin validateur sur le document et échec visible du contrôle d'interactions #66
Changes from 4 commits
6c187d6
3c9a91a
75d2b51
939cadb
c48b5f9
4fe9bfb
fd46220
38d9198
4755516
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ import { useTranslations } from "next-intl"; | |
| import { useForm, Controller } from "react-hook-form"; | ||
| import { zodResolver } from "@hookform/resolvers/zod"; | ||
| import { z } from "zod"; | ||
| import { Plus, Trash2, AlertTriangle, Pill, ClipboardList, ActivitySquare, Loader2 } from "lucide-react"; | ||
| import { Plus, Trash2, AlertTriangle, Pill, ClipboardList, ActivitySquare, Loader2, Check } from "lucide-react"; | ||
| import { AtcDrugSearch } from "@/components/diagnostics/atc-drug-search"; | ||
| import { IcfSearchField } from "@/components/diagnostics/icf-search-field"; | ||
| import { DrugInteractionWarning } from "@/components/diagnostics/drug-interaction-warning"; | ||
|
|
@@ -98,6 +98,9 @@ function VigibaseSignalBadge({ signal }: { signal: PharmacovigilanceSignal }) { | |
| ); | ||
| } | ||
|
|
||
| /** Issue du contrôle d'interactions, rendue visible dans tous les cas. */ | ||
| type InteractionCheckState = "idle" | "checking" | "clear" | "found" | "failed"; | ||
|
|
||
| interface PrescriptionBuilderStepProps { | ||
| validatedDiagnosisName: string; | ||
| patientAllergies: string[]; | ||
|
|
@@ -123,7 +126,8 @@ export function PrescriptionBuilderStep({ | |
|
|
||
| const [icfCodes, setIcfCodes] = useState<IcfCode[]>([]); | ||
| const [drugInteractions, setDrugInteractions] = useState<DrugInteractionPair[]>([]); | ||
| const [interactionLoading, setInteractionLoading] = useState(false); | ||
| const [interactionCheckState, setInteractionCheckState] = useState<InteractionCheckState>("idle"); | ||
| const [uncodedDrugCount, setUncodedDrugCount] = useState(0); | ||
| const [vigibaseSignals, setVigibaseSignals] = useState<Map<string, PharmacovigilanceSignal>>(new Map()); | ||
|
|
||
| const { register, handleSubmit, control, watch, formState: { errors } } = useForm<Omit<PrescriptionInput, "treatments" | "recommendations" | "follow_up_tests" | "icf_codes">>({ | ||
|
|
@@ -137,32 +141,49 @@ export function PrescriptionBuilderStep({ | |
|
|
||
| const selectedDocumentType = watch("document_type"); | ||
|
|
||
| // Check drug-drug interactions whenever treatments with rxcui change | ||
| // Contrôle des interactions à chaque changement de traitement. | ||
| // | ||
| // Chaque issue est rendue visible : un échec réseau ne doit jamais ressembler | ||
| // à « aucune interaction », sans quoi le prescripteur lit l'absence d'alerte | ||
| // comme un feu vert. Les médicaments sans code RxNorm sortent du contrôle et | ||
| // sont comptés pour être signalés explicitement. | ||
| const checkInteractions = useCallback(async (currentTreatments: PrescriptionTreatment[]) => { | ||
| const rxcuis = currentTreatments | ||
| const namedTreatments = currentTreatments.filter( | ||
| (treatment) => treatment.drug_name && treatment.drug_name.trim() !== "" | ||
| ); | ||
| const rxcuis = namedTreatments | ||
| .map((treatment) => treatment.rxcui) | ||
| .filter((code) => code && code.trim() !== "" && /^\d+$/.test(code)); | ||
| .filter((code): code is string => Boolean(code) && /^\d+$/.test(code!)); | ||
|
|
||
| setUncodedDrugCount(namedTreatments.length - rxcuis.length); | ||
|
|
||
| if (rxcuis.length < 2) { | ||
| setDrugInteractions([]); | ||
| setInteractionCheckState("idle"); | ||
| return; | ||
| } | ||
|
|
||
| setInteractionLoading(true); | ||
| setInteractionCheckState("checking"); | ||
| try { | ||
| const response = await fetch("/api/who/drug-interactions", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ rxcuis }), | ||
| }); | ||
| if (response.ok) { | ||
| const result: { interactions: DrugInteractionPair[] } = await response.json(); | ||
| setDrugInteractions(result.interactions ?? []); | ||
|
|
||
| if (!response.ok) { | ||
| setDrugInteractions([]); | ||
| setInteractionCheckState("failed"); | ||
| return; | ||
| } | ||
|
|
||
| const result: { interactions: DrugInteractionPair[] } = await response.json(); | ||
| const foundInteractions = result.interactions ?? []; | ||
| setDrugInteractions(foundInteractions); | ||
| setInteractionCheckState(foundInteractions.length > 0 ? "found" : "clear"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 -baRepository: 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' || trueRepository: 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)
}
}
JSRepository: 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.tsRepository: Zoubeir23/DocFlowAI Length of output: 2208 Validez la réponse La validation TypeScript de 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } catch { | ||
| // Silently ignore network errors — interactions are advisory only | ||
| } finally { | ||
| setInteractionLoading(false); | ||
| setDrugInteractions([]); | ||
| setInteractionCheckState("failed"); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| }, []); | ||
|
|
||
|
|
@@ -419,17 +440,62 @@ export function PrescriptionBuilderStep({ | |
| ))} | ||
| </section> | ||
|
|
||
| {/* Drug-drug interactions */} | ||
| {(drugInteractions.length > 0 || interactionLoading) && ( | ||
| {/* Contrôle des interactions : chaque issue est affichée, y compris l'échec */} | ||
| {(interactionCheckState !== "idle" || uncodedDrugCount > 0) && ( | ||
| <section className="space-y-3 border-t border-border pt-6"> | ||
| <div className="flex items-center gap-2"> | ||
| <AlertTriangle className="w-4 h-4 text-amber-500" /> | ||
| <h3 className="text-sm font-semibold uppercase tracking-wide text-amber-600"> | ||
| Interactions médicamenteuses | ||
| {interactionLoading && <Loader2 className="inline ml-2 w-3 h-3 animate-spin" />} | ||
| <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")} | ||
|
Comment on lines
+466
to
+494
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
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 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| </p> | ||
| )} | ||
|
|
||
| {interactionCheckState === "failed" && ( | ||
| <div className="rounded-xl border border-destructive/30 bg-destructive/5 p-4 space-y-3"> | ||
| <p className="text-sm text-destructive"> | ||
| {t("prescriptionStep.interactionsFailed")} | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={() => checkInteractions(treatments)} | ||
| className="text-xs font-semibold uppercase tracking-wide text-destructive underline underline-offset-4" | ||
| > | ||
| {t("prescriptionStep.interactionsRetry")} | ||
| </button> | ||
| </div> | ||
| )} | ||
|
|
||
| {uncodedDrugCount > 0 && ( | ||
| <p className="text-xs text-muted-foreground"> | ||
| {t("prescriptionStep.interactionsUncoded", { count: uncodedDrugCount })} | ||
| </p> | ||
| )} | ||
| </section> | ||
| )} | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.