Skip to content

Commit 21da433

Browse files
authored
Prescribing info app (#14)
1 parent 436c007 commit 21da433

14 files changed

Lines changed: 492 additions & 0 deletions

prescribing-info-app/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
WRITER_API_KEY=your_writer_api_key
2+
GRAPH_ID=your_writer_graph_id
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"key": "drug-info", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
2+
{"id": "f0jnnqbgaq9egozo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
3+
{"id": "5l0szzvnaaxlt8tl", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 0}
4+
{"id": "se73vd8nc8w057x4", "type": "header", "content": {"text": "Prescription Information App"}, "handlers": {}, "isCodeManaged": false, "parentId": "5l0szzvnaaxlt8tl", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
5+
{"id": "zh10210i1bawmqp3", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 1}
6+
{"id": "820nw2mf596ezam3", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "zh10210i1bawmqp3", "position": 0}
7+
{"id": "medicine", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 0}
8+
{"id": "chat", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 1}
9+
{"id": "c5f7bp0fjv7ew40w", "type": "section", "content": {"title": "Availiable drugs"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1}
10+
{"id": "bdts4z8blerl33fw", "type": "tags", "content": {"tags": "{\n \"Xeljanz\": \"Xeljanz\",\n \"Stelara\": \"Stelara\",\n \"Dupixent\": \"Dupixent\",\n \"Lucentis\": \"Lucentis\",\n \"Bavencio\": \"Bavencio\",\n \"Trulicity\": \"Trulicity\",\n \"Entresto\": \"Entresto\",\n \"Humira\": \"Humira\",\n \"Tagrisso\": \"Tagrisso\"\n}"}, "handlers": {"wf-tag-click": "drug_switch"}, "isCodeManaged": false, "parentId": "c5f7bp0fjv7ew40w", "position": 0}
11+
{"id": "mhhffykuw46c3xgi", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 2}
12+
{"id": "0mpz722m992c8h06", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 0}
13+
{"id": "fm9r15fyiysf5fhl", "type": "section", "content": {"title": "Adverse reactions"}, "handlers": {}, "isCodeManaged": false, "parentId": "0mpz722m992c8h06", "position": 0}
14+
{"id": "fdvy87wyzk0pyg27", "type": "text", "content": {"cssClasses": "adv-reactions-scroller", "text": "@{adverse_reactions}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "fm9r15fyiysf5fhl", "position": 0}
15+
{"id": "693ecrbqbc942f6x", "type": "column", "content": {"width": "3"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 1}
16+
{"id": "xpjwnx9jurnxuy4z", "type": "tabs", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "693ecrbqbc942f6x", "position": 0}
17+
{"id": "y1x1a8xb2cdwfg77", "type": "tab", "content": {"cssClasses": "", "name": "Full Prescribing Info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 0}
18+
{"id": "4eg5lxq1vle8m9h4", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{raw_prescribing_info}\n", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "y1x1a8xb2cdwfg77", "position": 0}
19+
{"id": "3pb0e6kmi4soloz8", "type": "tab", "content": {"cssClasses": "", "name": "Summary of Prescribing Info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 1}
20+
{"id": "jz845bowxcjm606y", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{prescribing_info_summary}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "3pb0e6kmi4soloz8", "position": 0}
21+
{"id": "ob300350xax1esrg", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 2}
22+
{"id": "b0sg45pzixq3ra3w", "type": "section", "content": {"cssClasses": "pubmed-scroller", "title": "Related PubMed articles"}, "handlers": {}, "isCodeManaged": false, "parentId": "ob300350xax1esrg", "position": 0}
23+
{"id": "hnmadv68pz0jeak8", "type": "repeater", "content": {"keyVariable": "itemId", "repeaterObject": "@{related_pubmed_articles}", "valueVariable": "item"}, "handlers": {}, "isCodeManaged": false, "parentId": "b0sg45pzixq3ra3w", "position": 0}
24+
{"id": "vowbh61n9cbzv8ko", "type": "tags", "content": {"seed": "0", "tags": "{\n \"1\": \"@{item.Publication Date}\"\n}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 0}
25+
{"id": "i4sm8m3k228y7faw", "type": "text", "content": {"text": "@{item.Title}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 1}
26+
{"id": "drihhplzfxomn2ga", "type": "link", "content": {"primaryTextColor": "#5551ff", "target": "", "text": "PubMed", "url": "@{item.Article Link}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 2}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{"id": "181cq1mpgw34i8dl", "type": "page", "content": {"key": "chat", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 1, "visible": {"binding": "", "expression": true, "reversed": false}}
2+
{"id": "b6cillcjhtpg71tk", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 0}
3+
{"id": "5dxznhfw18bkp88o", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 0}
4+
{"id": "66m0u100zicdr0j3", "type": "header", "content": {"text": "Medicine app"}, "handlers": {}, "isCodeManaged": false, "parentId": "5dxznhfw18bkp88o", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
5+
{"id": "ptn01trkgrzmc11v", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 1}
6+
{"id": "y1x6gru1icypiguh", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "ptn01trkgrzmc11v", "position": 0}
7+
{"id": "medicine1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 0}
8+
{"id": "chat1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 1}
9+
{"id": "3yv1o3158qk6ewrp", "type": "section", "content": {"title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 1}
10+
{"id": "0na7dihjazzdlbjf", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "3yv1o3158qk6ewrp", "position": 0}
11+
{"id": "yl0x2lefilp1xnl6", "type": "column", "content": {"width": "13"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 0}
12+
{"id": "t76ns9nd93w1aaac", "type": "chatbot", "content": {"conversation": "@{conversation}", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "user_message"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 0}
13+
{"id": "4dyb1959ehmz6hoh", "type": "text", "content": {"text": "@{contributing_sources_button_text}"}, "handlers": {"wf-click": "contributing_sources_change_vis"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 1}
14+
{"id": "6gunwa2x9r6fw6c4", "type": "column", "content": {"title": "Contributing sources", "width": "7"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 1, "visible": {"binding": "contributing_sources_vis", "expression": "custom", "reversed": false}}
15+
{"id": "rfpjjy0krwyn5cfo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "6gunwa2x9r6fw6c4", "position": 0}
16+
{"id": "h8gtll4cw37fh3hf", "type": "column", "content": {"cssClasses": "files-list", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "rfpjjy0krwyn5cfo", "position": 0}
17+
{"id": "gq06l9kpvbeqkee1", "type": "repeater", "content": {"keyVariable": "index", "repeaterObject": "@{contributing_sources}", "valueVariable": "file"}, "handlers": {}, "isCodeManaged": false, "parentId": "h8gtll4cw37fh3hf", "position": 0}
18+
{"id": "0ggjzzhqd8vgttkg", "type": "section", "content": {"cssClasses": "@{file.file_css}", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "gq06l9kpvbeqkee1", "position": 0}
19+
{"id": "avu7ft9wkylm5mho", "type": "text", "content": {"text": "#### @{file.name}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 0}
20+
{"id": "nnirvi5yma8wsswp", "type": "text", "content": {"cssClasses": "@{file.content_css}", "text": "@{file.content}"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 1}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "root", "type": "root", "content": {"appName": "Prescription Information App"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "workflows_root", "position": 0}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"writer_version": "0.8.2"
3+
}

prescribing-info-app/main.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import asyncio
2+
import json
3+
import os
4+
from datetime import datetime
5+
from typing import Optional
6+
7+
import writer as wf
8+
from dotenv import load_dotenv
9+
from prompts import prescribing_summary_prompt
10+
from utils import (
11+
_get_drug_side_effects,
12+
_get_full_prescribing_info,
13+
_get_pubmed_sheets,
14+
_parse_prescribing_info,
15+
_upload_file_and_add_to_graph,
16+
)
17+
from writer import WriterState
18+
from writer.ai import Conversation, File, retrieve_file, retrieve_graph, stream_complete
19+
20+
load_dotenv()
21+
22+
GRAPH_ID = os.getenv("GRAPH_ID", "")
23+
24+
25+
def drug_switch(state: WriterState, payload: str) -> None:
26+
update_drug_data(state, payload)
27+
28+
29+
def update_drug_data(state: WriterState, drug_name: str) -> None:
30+
prescribing_info, adverse_reactions = asyncio.run(
31+
_get_drug_prescribing_info(drug_name)
32+
)
33+
state["raw_prescribing_info"] = _parse_prescribing_info(prescribing_info[0])
34+
state["adverse_reactions"] = adverse_reactions
35+
state["related_pubmed_articles"] = _get_pubmed_sheets(drug_name)
36+
37+
state["prescribing_info_summary"] = ""
38+
39+
for chunk in stream_complete(
40+
prescribing_summary_prompt.format(
41+
prescribing_details=state["raw_prescribing_info"]
42+
)
43+
):
44+
state["prescribing_info_summary"] += chunk
45+
46+
asyncio.run(_upload_drug_data_into_graph(state, drug_name))
47+
48+
49+
async def _get_drug_prescribing_info(drug_name: str) -> (dict, str):
50+
return await asyncio.gather(
51+
_get_full_prescribing_info(drug_name), _get_drug_side_effects(drug_name)
52+
)
53+
54+
55+
async def _upload_drug_data_into_graph(state: WriterState, drug_name: str) -> None:
56+
input_params = [
57+
{
58+
"file_data": state["prescribing_info_summary"],
59+
"file_name": f"prescribing_info_summary_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
60+
"graph_id": GRAPH_ID,
61+
},
62+
{
63+
"file_data": state["raw_prescribing_info"],
64+
"file_name": f"raw_prescribing_info_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
65+
"graph_id": GRAPH_ID,
66+
},
67+
{
68+
"file_data": state["adverse_reactions"],
69+
"file_name": f"adverse_reactions_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
70+
"graph_id": GRAPH_ID,
71+
},
72+
{
73+
"file_data": json.dumps(state["related_pubmed_articles"], default=str),
74+
"file_name": f"pubmed_articles_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
75+
"graph_id": GRAPH_ID,
76+
},
77+
]
78+
coroutines = [_upload_file_and_add_to_graph(**params) for params in input_params]
79+
await asyncio.gather(*coroutines)
80+
81+
82+
def _handle_contributing_sources(state: WriterState, graph_data: dict) -> None:
83+
sources = graph_data.get("sources")
84+
contributing_sources = {}
85+
if sources:
86+
for index, source in enumerate(sources):
87+
source_file = _get_file_from_graph(source["file_id"])
88+
source_snippet = source["snippet"]
89+
90+
raw_filename = f"📄 {source_file.name}"
91+
row_length = 30
92+
filename = " ".join(
93+
[
94+
raw_filename[i : i + row_length]
95+
for i in range(0, len(raw_filename), row_length)
96+
]
97+
)
98+
99+
contributing_sources.update(
100+
{
101+
str(index): {
102+
"name": filename,
103+
"file_css": "file",
104+
"content": source_snippet,
105+
"content_css": "file-text",
106+
}
107+
}
108+
)
109+
110+
state["contributing_sources"] = contributing_sources
111+
state["contributing_sources_vis"] = True
112+
state["contributing_sources_button_text"] = "View contributing sources ▸"
113+
114+
115+
def _get_file_from_graph(file_id: str) -> Optional[File]:
116+
try:
117+
return retrieve_file(file_id=file_id)
118+
except Exception as e:
119+
print(f"An error while file obtainment occurred: {str(e)}")
120+
return None
121+
122+
123+
def user_message(state: WriterState, payload: dict) -> None:
124+
try:
125+
state["conversation"] += payload
126+
graph = retrieve_graph(GRAPH_ID)
127+
response = state["conversation"].stream_complete(
128+
tools=graph,
129+
)
130+
131+
for chunk in response:
132+
state["conversation"] += chunk
133+
134+
graph_data = state["conversation"].messages[-1].get("graph_data")
135+
if graph_data:
136+
_handle_contributing_sources(state, graph_data)
137+
138+
except Exception as e:
139+
state["conversation"] += {
140+
"role": "assistant",
141+
"content": "Something went wrong. Please try again!",
142+
}
143+
raise e
144+
145+
146+
def contributing_sources_change_vis(state: WriterState) -> None:
147+
state["contributing_sources_vis"] = not state["contributing_sources_vis"]
148+
if state["contributing_sources_vis"]:
149+
state["contributing_sources_button_text"] = "View contributing sources ▸"
150+
else:
151+
state["contributing_sources_button_text"] = "View contributing sources ◂"
152+
153+
154+
initial_state = wf.init_state(
155+
{
156+
"adverse_reactions": "Drug adverse reactions will appear here...",
157+
"prescribing_info_summary": "Prescribing info summary will appear here...",
158+
"raw_prescribing_info": "Prescribing info will appear here...",
159+
"related_pubmed_articles": {},
160+
"contributing_sources_button_text": "View contributing sources ◂",
161+
"contributing_sources_vis": False,
162+
"contributing_sources": {},
163+
"conversation": Conversation(
164+
[
165+
{
166+
"role": "assistant",
167+
"content": "Hi! I can answer your questions "
168+
"about the selected medication "
169+
"based on its prescribing information "
170+
"and related Pubmed articles.",
171+
},
172+
],
173+
),
174+
}
175+
)
176+
177+
initial_state.import_stylesheet("style", "/static/custom.css?115")

prescribing-info-app/prompts.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
prescribing_summary_prompt = """
2+
3+
Variables:
4+
5+
{prescribing_details}
6+
7+
************************
8+
9+
Prompt:
10+
Here is the prescribing details text to summarize:
11+
12+
<text>
13+
{prescribing_details}
14+
</text>
15+
16+
Please summarize the prescribing details text.
17+
18+
"""
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[tool.poetry]
2+
name = "writer-framework-default"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Your Name <you@example.com>"]
6+
readme = "README.md"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.11"
10+
writer = "^0.8.2"
11+
bio = "^1.7.1"
12+
bs4 = "^0.0.2"
13+
python-dotenv = "^1.0.1"
14+
flake8 = "^7.1.1"
15+
isort = "^5.13.2"
16+
black = "^24.10.0"
17+
pre-commit = "^4.0.1"
18+
asyncio = "^3.4.3"
19+
aiohttp = "^3.11.11"
20+
21+
22+
[build-system]
23+
requires = ["poetry-core"]
24+
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
 (0)