feat: add professional drug information system
Browse filesMajor enhancements:
- Add detailed drug information fields (efficacy, usage_precautions, drug_interactions)
- Update VL model prompts to generate pharmacist-level explanations
- Create dedicated "์ฝ๋ฌผ ์์ธ ์ ๋ณด" tab with structured information
- Improve system prompt to emphasize pharmaceutical expertise
- Reorganize UI with tabs: ์ฝ๋ฌผ ์์ธ ์ ๋ณด, ์ฌ์ด ์ค๋ช
, ํ ์ปท ๋งํ, ์ผ์ ์นด๋
Drug information now includes:
- ๐ ์ด ์ฝ์ ๋ฌด์์
๋๊น? (ํจ๋ฅํจ๊ณผ)
- ๐ ์ด ์ฝ์ ์ด๋ป๊ฒ ๋ณต์ฉํฉ๋๊น? (๋ณต์ฉ๋ฒ ๋ฐ ์ฃผ์์ฌํญ)
- โ ๏ธ ๋ถ์์ฉ
- ๐ ์ฝ๋ฌผ ์ํธ์์ฉ
- โก ํน๋ณ ์ฃผ์์ฌํญ
Example: For Almagate 500mg, it now explains:
- Mechanism: neutralizes gastric acid, reduces pepsin activity
- Instructions: avoid with iron supplements (2hr gap), limit caffeine/spices
- Side effects and contraindications
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
import re
|
|
|
|
| 4 |
from typing import Any, Dict, List, Optional
|
| 5 |
|
| 6 |
import gradio as gr
|
|
@@ -116,6 +117,9 @@ def _sanitize_medication(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 116 |
"dosage_example": _to_str(item.get("dosage_example")),
|
| 117 |
"side_effects": _to_str(item.get("side_effects")),
|
| 118 |
"warnings": _to_str(item.get("warnings")),
|
|
|
|
|
|
|
|
|
|
| 119 |
}
|
| 120 |
|
| 121 |
|
|
@@ -170,28 +174,30 @@ def analyze_image_with_qwen(image: Image.Image) -> Dict[str, Any]:
|
|
| 170 |
" \"raw_text\": \"OCR๋ก ์ฝ์ ์ ์ฒด ๋ฌธ์ฅ\",\n"
|
| 171 |
" \"medications\": [\n"
|
| 172 |
" {\n"
|
| 173 |
-
" \"name\": \"์ฝ
|
| 174 |
-
" \"dose_per_intake\": \"1ํ ์ฉ๋ (์: 1์ , 5mL)\",\n"
|
| 175 |
" \"times_per_day\": \"ํ๋ฃจ ๋ณต์ฉ ํ์\",\n"
|
| 176 |
" \"time_slots\": [\"๋ณต์ฉ ์๊ฐ๋\"],\n"
|
| 177 |
-
" \"description\": \"์ฝ ์ค๋ช
\",\n"
|
| 178 |
-
" \"
|
| 179 |
-
" \"
|
| 180 |
-
" \"side_effects\": \"์ฃผ์
|
| 181 |
-
" \"
|
|
|
|
| 182 |
" }\n"
|
| 183 |
" ],\n"
|
| 184 |
" \"warnings\": [\"์ ์ฒด ๊ฒฝ๊ณ \"]\n"
|
| 185 |
"}"
|
| 186 |
)
|
| 187 |
user_prompt = (
|
| 188 |
-
"์ JSON ์คํค๋ง๋ฅผ ๋ฐ๋์ ๋ฐ๋ฅด์ธ์. ๋ชจ๋ ๊ฐ์ ํ๊ตญ์ด๋ก ์์ฑํ๊ณ , ๋น ์ ๋ณด๋ ๋น ๋ฌธ์์ด๋ก
|
|
|
|
| 189 |
)
|
| 190 |
|
| 191 |
messages = [
|
| 192 |
{
|
| 193 |
"role": "system",
|
| 194 |
-
"content": "๋น์ ์ ์ฝ์ฌ
|
| 195 |
},
|
| 196 |
{
|
| 197 |
"role": "user",
|
|
@@ -460,6 +466,7 @@ def run_pipeline(image: Optional[Image.Image], progress=gr.Progress()):
|
|
| 460 |
"๐ท ์ฝ ๋ดํฌ ์ฌ์ง์ ์ฌ๋ฆฌ๋ฉด ์ธ์์ด ์์๋ผ์.",
|
| 461 |
"",
|
| 462 |
None,
|
|
|
|
| 463 |
)
|
| 464 |
|
| 465 |
progress(0, desc="์ฝ๋ดํฌ ์ด๋ฏธ์ง ๋ถ์ ์ค...")
|
|
@@ -473,6 +480,33 @@ def run_pipeline(image: Optional[Image.Image], progress=gr.Progress()):
|
|
| 473 |
progress(0.66, desc="์ผ์ ์นด๋ ๋ ๋๋ง ์ค...")
|
| 474 |
card_img = render_card(medications)
|
| 475 |
csv_row = medications_to_csv(medications)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
markdown = (
|
| 477 |
"## ์ด๋ฅด์ ์ ์ํ ์ค๋ช
\n"
|
| 478 |
+ (narratives.get("elderly_narrative") or "- ์ค๋ช
์ ์ค๋นํ์ง ๋ชปํ์ต๋๋ค.")
|
|
@@ -488,7 +522,7 @@ def run_pipeline(image: Optional[Image.Image], progress=gr.Progress()):
|
|
| 488 |
cartoon_image = generate_cartoon_image(narratives.get("image_prompt"))
|
| 489 |
|
| 490 |
progress(1.0, desc="์๋ฃ!")
|
| 491 |
-
return json_text, card_img, csv_row, markdown, warnings_md, raw_text, cartoon_image
|
| 492 |
|
| 493 |
|
| 494 |
CUSTOM_CSS = """
|
|
@@ -531,18 +565,25 @@ with gr.Blocks(theme=gr.themes.Soft(), css=CUSTOM_CSS) as demo:
|
|
| 531 |
btn = gr.Button("์ธ์ & ์ค๋ช
์์ฑ", elem_classes=["primary-btn"])
|
| 532 |
with gr.Column(scale=6, elem_classes=["glass-panel"]):
|
| 533 |
gr.Markdown("### 2. ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์")
|
| 534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
raw_box = gr.Textbox(label="๋ชจ๋ธ์ด ์ฝ์ ์๋ฌธ ํ
์คํธ", lines=5, interactive=False)
|
| 536 |
-
|
| 537 |
-
card_out = gr.Image(type="pil", label="์ผ์ ์นด๋(๋ฏธ๋ฆฌ๋ณด๊ธฐ)")
|
| 538 |
-
csv_box = gr.Textbox(label="CSV(์ฝ๋ช
,1ํ์ฉ๋,1์ผํ์,์๊ฐ๋)", lines=2, elem_classes=["csv-box"])
|
| 539 |
with gr.Accordion("์ธ๋ถ JSON ๊ฒฐ๊ณผ", open=False, elem_classes=["accordion"]):
|
| 540 |
json_out = gr.Code(label="๋ชจ๋ธ ๋ถ์(JSON)")
|
| 541 |
|
| 542 |
btn.click(
|
| 543 |
run_pipeline,
|
| 544 |
inputs=img_in,
|
| 545 |
-
outputs=[json_out, card_out, csv_box, explain_md, warn_md, raw_box, cartoon_img],
|
| 546 |
)
|
| 547 |
|
| 548 |
gr.Markdown(
|
|
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
+
import urllib.parse
|
| 5 |
from typing import Any, Dict, List, Optional
|
| 6 |
|
| 7 |
import gradio as gr
|
|
|
|
| 117 |
"dosage_example": _to_str(item.get("dosage_example")),
|
| 118 |
"side_effects": _to_str(item.get("side_effects")),
|
| 119 |
"warnings": _to_str(item.get("warnings")),
|
| 120 |
+
"efficacy": _to_str(item.get("efficacy")), # ํจ๋ฅํจ๊ณผ
|
| 121 |
+
"usage_precautions": _to_str(item.get("usage_precautions")), # ๋ณต์ฉ ์ฃผ์์ฌํญ
|
| 122 |
+
"drug_interactions": _to_str(item.get("drug_interactions")), # ์ฝ๋ฌผ ์ํธ์์ฉ
|
| 123 |
}
|
| 124 |
|
| 125 |
|
|
|
|
| 174 |
" \"raw_text\": \"OCR๋ก ์ฝ์ ์ ์ฒด ๋ฌธ์ฅ\",\n"
|
| 175 |
" \"medications\": [\n"
|
| 176 |
" {\n"
|
| 177 |
+
" \"name\": \"์ฝ ์ด๋ฆ (์ฑ๋ถ๋ช
)\",\n"
|
| 178 |
+
" \"dose_per_intake\": \"1ํ ์ฉ๋ (์: 1์ , 500mg, 5mL)\",\n"
|
| 179 |
" \"times_per_day\": \"ํ๋ฃจ ๋ณต์ฉ ํ์\",\n"
|
| 180 |
" \"time_slots\": [\"๋ณต์ฉ ์๊ฐ๋\"],\n"
|
| 181 |
+
" \"description\": \"์ฝ ๊ฐ๋จ ์ค๋ช
\",\n"
|
| 182 |
+
" \"efficacy\": \"์ด ์ฝ์ ๋ฌด์์
๋๊น? (ํจ๋ฅํจ๊ณผ๋ฅผ ์์ธํ - ์: ์์ฐ์ ์คํํ๊ณ ํฉ์ ์ ์์ฉ์ ๊ฐ์์์ผ ์๋ด ํต์ฆ ๋ฐ ๊ถค์ํ์ฑ์ ๋ฐฉ์งํ๋ ์ฝ)\",\n"
|
| 183 |
+
" \"usage_precautions\": \"์ด ์ฝ์ ์ด๋ป๊ฒ ๋ณต์ฉํฉ๋๊น? (๋ณต์ฉ ์ฃผ์์ฌํญ์ ์์ธํ - ์: ์ฒ ๋ถ์ ์ 2์๊ฐ ๊ฐ๊ฒฉ ์ ์ง, ๊ฐํ ํฅ์ ๋ฃ/์นดํ์ธ ์ญ์ทจ ์ ํ, 2์ฃผ ๋ณต์ฉ ํ ์ฆ์ ๋ฏธ๊ฐ์ ์ ์ ๋ฌธ๊ฐ ์๋ด)\",\n"
|
| 184 |
+
" \"side_effects\": \"์ฃผ์ ๋ถ์์ฉ (๊ตฌ์ฒด์ ์ผ๋ก)\",\n"
|
| 185 |
+
" \"drug_interactions\": \"๋ค๋ฅธ ์ฝ๊ณผ์ ์ํธ์์ฉ (๋ณ์ฉ ์ฃผ์ ์ฝ๋ฌผ)\",\n"
|
| 186 |
+
" \"warnings\": \"ํน๋ณ ์ฃผ์์ฌํญ\"\n"
|
| 187 |
" }\n"
|
| 188 |
" ],\n"
|
| 189 |
" \"warnings\": [\"์ ์ฒด ๊ฒฝ๊ณ \"]\n"
|
| 190 |
"}"
|
| 191 |
)
|
| 192 |
user_prompt = (
|
| 193 |
+
"์ JSON ์คํค๋ง๋ฅผ ๋ฐ๋์ ๋ฐ๋ฅด์ธ์. ๋ชจ๋ ๊ฐ์ ํ๊ตญ์ด๋ก ์์ฑํ๊ณ , ๋น ์ ๋ณด๋ ๋น ๋ฌธ์์ด๋ก ๋์ธ์.\n"
|
| 194 |
+
"์ฝ ์ด๋ฆ์์ ์ฑ๋ถ๋ช
์ ํ์
ํ์ฌ, ํด๋น ์ฝ์ ํจ๋ฅํจ๊ณผ, ๋ณต์ฉ๋ฐฉ๋ฒ, ์ฃผ์์ฌํญ์ ์ฝ์ฌ ์์ค์ผ๋ก ์์ธํ ์์ฑํ์ธ์."
|
| 195 |
)
|
| 196 |
|
| 197 |
messages = [
|
| 198 |
{
|
| 199 |
"role": "system",
|
| 200 |
+
"content": "๋น์ ์ ์ฝ์ฌ ์๊ฒฉ์ฆ์ ๋ณด์ ํ ์ ๋ฌธ๊ฐ์
๋๋ค. ์ฝ๋ฌผ์ ํจ๋ฅ, ๋ณต์ฉ๋ฒ, ์ฃผ์์ฌํญ, ์ํธ์์ฉ์ ์ ํํ๊ณ ์์ธํ๊ฒ ์ค๋ช
ํ์ธ์.",
|
| 201 |
},
|
| 202 |
{
|
| 203 |
"role": "user",
|
|
|
|
| 466 |
"๐ท ์ฝ ๋ดํฌ ์ฌ์ง์ ์ฌ๋ฆฌ๋ฉด ์ธ์์ด ์์๋ผ์.",
|
| 467 |
"",
|
| 468 |
None,
|
| 469 |
+
"์ฝ๋ฌผ ์ ๋ณด๊ฐ ํ์๋ฉ๋๋ค.",
|
| 470 |
)
|
| 471 |
|
| 472 |
progress(0, desc="์ฝ๋ดํฌ ์ด๋ฏธ์ง ๋ถ์ ์ค...")
|
|
|
|
| 480 |
progress(0.66, desc="์ผ์ ์นด๋ ๋ ๋๋ง ์ค...")
|
| 481 |
card_img = render_card(medications)
|
| 482 |
csv_row = medications_to_csv(medications)
|
| 483 |
+
|
| 484 |
+
# ์ฝ๋ฌผ ์์ธ ์ ๋ณด ๋งํฌ๋ค์ด ์์ฑ
|
| 485 |
+
detailed_info = ""
|
| 486 |
+
if medications:
|
| 487 |
+
detailed_info = "# ๐ ์ฝ๋ฌผ ์์ธ ์ ๋ณด\n\n"
|
| 488 |
+
for idx, med in enumerate(medications):
|
| 489 |
+
detailed_info += f"## {idx + 1}. {med.get('name', '์ฝ ์ด๋ฆ ๋ฏธํ์ธ')}\n\n"
|
| 490 |
+
|
| 491 |
+
if med.get("efficacy"):
|
| 492 |
+
detailed_info += f"### ๐ ์ด ์ฝ์ ๋ฌด์์
๋๊น?\n{med.get('efficacy')}\n\n"
|
| 493 |
+
|
| 494 |
+
if med.get("usage_precautions"):
|
| 495 |
+
detailed_info += f"### ๐ ์ด ์ฝ์ ์ด๋ป๊ฒ ๋ณต์ฉํฉ๋๊น?\n{med.get('usage_precautions')}\n\n"
|
| 496 |
+
|
| 497 |
+
if med.get("side_effects"):
|
| 498 |
+
detailed_info += f"### โ ๏ธ ๋ถ์์ฉ\n{med.get('side_effects')}\n\n"
|
| 499 |
+
|
| 500 |
+
if med.get("drug_interactions"):
|
| 501 |
+
detailed_info += f"### ๐ ์ฝ๋ฌผ ์ํธ์์ฉ\n{med.get('drug_interactions')}\n\n"
|
| 502 |
+
|
| 503 |
+
if med.get("warnings"):
|
| 504 |
+
detailed_info += f"### โก ํน๋ณ ์ฃผ์์ฌํญ\n{med.get('warnings')}\n\n"
|
| 505 |
+
|
| 506 |
+
detailed_info += "---\n\n"
|
| 507 |
+
else:
|
| 508 |
+
detailed_info = "์ฝ๋ฌผ ์ ๋ณด๏ฟฝ๏ฟฝ ์ฐพ์ ์ ์์ต๋๋ค."
|
| 509 |
+
|
| 510 |
markdown = (
|
| 511 |
"## ์ด๋ฅด์ ์ ์ํ ์ค๋ช
\n"
|
| 512 |
+ (narratives.get("elderly_narrative") or "- ์ค๋ช
์ ์ค๋นํ์ง ๋ชปํ์ต๋๋ค.")
|
|
|
|
| 522 |
cartoon_image = generate_cartoon_image(narratives.get("image_prompt"))
|
| 523 |
|
| 524 |
progress(1.0, desc="์๋ฃ!")
|
| 525 |
+
return json_text, card_img, csv_row, markdown, warnings_md, raw_text, cartoon_image, detailed_info
|
| 526 |
|
| 527 |
|
| 528 |
CUSTOM_CSS = """
|
|
|
|
| 565 |
btn = gr.Button("์ธ์ & ์ค๋ช
์์ฑ", elem_classes=["primary-btn"])
|
| 566 |
with gr.Column(scale=6, elem_classes=["glass-panel"]):
|
| 567 |
gr.Markdown("### 2. ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์")
|
| 568 |
+
with gr.Tabs():
|
| 569 |
+
with gr.Tab("๐ ์ฝ๋ฌผ ์์ธ ์ ๋ณด"):
|
| 570 |
+
detailed_info_md = gr.Markdown("์ฝ๋ฌผ ์ ๋ณด๊ฐ ํ์๋ฉ๋๋ค.", elem_classes=["output-card"])
|
| 571 |
+
with gr.Tab("๐ด๐ถ ์ฌ์ด ์ค๋ช
"):
|
| 572 |
+
explain_md = gr.Markdown("์ฌ๊ธฐ์ ์ฝ ์ค๋ช
์ด ํ์๋ฉ๋๋ค.", elem_classes=["output-card"])
|
| 573 |
+
with gr.Tab("๐จ ํ ์ปท ๋งํ"):
|
| 574 |
+
cartoon_img = gr.Image(type="pil", label="ํ ์ปท ๋งํ")
|
| 575 |
+
with gr.Tab("๐
์ผ์ ์นด๋"):
|
| 576 |
+
card_out = gr.Image(type="pil", label="์ผ์ ์นด๋(๋ฏธ๋ฆฌ๋ณด๊ธฐ)")
|
| 577 |
+
|
| 578 |
raw_box = gr.Textbox(label="๋ชจ๋ธ์ด ์ฝ์ ์๋ฌธ ํ
์คํธ", lines=5, interactive=False)
|
| 579 |
+
csv_box = gr.Textbox(label="CSV(์ฝ๋ช
,1ํ์ฉ๋,1์ผํ์,์๊ฐ๋)", lines=4, elem_classes=["csv-box"])
|
|
|
|
|
|
|
| 580 |
with gr.Accordion("์ธ๋ถ JSON ๊ฒฐ๊ณผ", open=False, elem_classes=["accordion"]):
|
| 581 |
json_out = gr.Code(label="๋ชจ๋ธ ๋ถ์(JSON)")
|
| 582 |
|
| 583 |
btn.click(
|
| 584 |
run_pipeline,
|
| 585 |
inputs=img_in,
|
| 586 |
+
outputs=[json_out, card_out, csv_box, explain_md, warn_md, raw_box, cartoon_img, detailed_info_md],
|
| 587 |
)
|
| 588 |
|
| 589 |
gr.Markdown(
|