From 7b37c207dcc517c96a84d1e9237b64995c27a414 Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Fri, 6 Jun 2025 00:22:36 +0200 Subject: [PATCH 01/12] Create README.MD This folder contains a referral letter generator for general practitioners. --- README.MD | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.MD diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/README.MD @@ -0,0 +1 @@ + From 5c4846f9a4af860e40f589b6d6751efe42f2724f Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Fri, 6 Jun 2025 00:35:00 +0200 Subject: [PATCH 02/12] Delete README.MD --- README.MD | 1 - 1 file changed, 1 deletion(-) delete mode 100644 README.MD diff --git a/README.MD b/README.MD deleted file mode 100644 index 8b13789..0000000 --- a/README.MD +++ /dev/null @@ -1 +0,0 @@ - From 8aa8e9df3fac5c1f484c92fc81d9be3220021dbe Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Fri, 6 Jun 2025 00:35:33 +0200 Subject: [PATCH 03/12] Create README.md This folder contains the GP referral automation notebook and example data. --- gp_referral_toolkit/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 gp_referral_toolkit/README.md diff --git a/gp_referral_toolkit/README.md b/gp_referral_toolkit/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gp_referral_toolkit/README.md @@ -0,0 +1 @@ + From bace59f98aa50579313f32f32d2527468e8842f1 Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Fri, 6 Jun 2025 00:37:20 +0200 Subject: [PATCH 04/12] Add files via upload Upload notebook and sample data --- gp_referral_toolkit/patient_note.txt | 17 ++++++++ gp_referral_toolkit/referral_letter_bot.py | 46 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 gp_referral_toolkit/patient_note.txt create mode 100644 gp_referral_toolkit/referral_letter_bot.py diff --git a/gp_referral_toolkit/patient_note.txt b/gp_referral_toolkit/patient_note.txt new file mode 100644 index 0000000..bef096a --- /dev/null +++ b/gp_referral_toolkit/patient_note.txt @@ -0,0 +1,17 @@ +45F, fatigue and weight gain. Reports cold intolerance and constipation. No palpitations. Family history of thyroid disease. + +--- + +56M, chest pain on exertion for 3 weeks. No SOB or nausea. Hypertension, diabetes. Family history of CAD. + +--- + +22F, recurrent UTIs. Sexually active. No fever or flank pain. Normal renal function. History of E. coli positive urine cultures. + +--- + +60M, progressive shortness of breath. Former smoker. Bilateral wheezing on auscultation. Awaiting spirometry. History of COPD. + +--- + +32F, persistent headaches. Worse with stress. Normal neuro exam. No aura. Family history of migraines. Normal MRI last year. diff --git a/gp_referral_toolkit/referral_letter_bot.py b/gp_referral_toolkit/referral_letter_bot.py new file mode 100644 index 0000000..4c6784d --- /dev/null +++ b/gp_referral_toolkit/referral_letter_bot.py @@ -0,0 +1,46 @@ +import openai + +# Step 1: Summarize the patient consultation note +def summarize_patient_note(note_text): + response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "user", "content": f"Please summarize the following patient consultation note in a clear, clinical style:\n\n{note_text}"} + ] + ) + return response.choices[0].message.content + +# Step 2: Generate a specialist referral letter +def generate_referral_letter(summary_text, specialist_type): + system_prompt = f"You are an experienced general practitioner. Based on the consultation summary, write a concise, professional referral letter to a {specialist_type}." + + response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": f"Consultation summary:\n\n{summary_text}"} + ] + ) + return response.choices[0].message.content + +# Main logic +if __name__ == "__main__": + try: + with open('patient_note.txt', 'r', encoding='utf-8') as file: + patient_note = file.read() + + # Step 1: Summarize the note + summary = summarize_patient_note(patient_note) + print("\n🩺 Consultation Summary:") + print(summary) + + # Step 2: Ask user which specialist to refer to + specialist = input("\nāž”ļø Which specialist is this referral for (e.g., cardiologist, neurologist)?\n") + + # Step 3: Generate the referral letter + referral_letter = generate_referral_letter(summary, specialist) + print("\nšŸ“Ø Generated Referral Letter:\n") + print(referral_letter) + + except FileNotFoundError: + print("āŒ The file 'patient_note.txt' was not found. Please ensure it exists in the project folder.") From 3ba4c0af91e9cc2d106a0c6e99539fb1fe1241c1 Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Sun, 8 Jun 2025 16:25:08 +0200 Subject: [PATCH 05/12] Rename gp_referral_toolkit/README.md to /community-contributions/gp_referral_toolkit/README.md --- .../{ => community-contributions/gp_referral_toolkit}/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gp_referral_toolkit/{ => community-contributions/gp_referral_toolkit}/README.md (100%) diff --git a/gp_referral_toolkit/README.md b/gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md similarity index 100% rename from gp_referral_toolkit/README.md rename to gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md From 96dc3a9e1dbc6113c963952dd0d11c5f5b84a56e Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Sun, 8 Jun 2025 16:26:46 +0200 Subject: [PATCH 06/12] Update and rename gp_referral_toolkit/patient_note.txt to /communit-contributions/gp_referral_toolkit/patient_note.txt --- .../gp_referral_toolkit}/patient_note.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gp_referral_toolkit/{ => communit-contributions/gp_referral_toolkit}/patient_note.txt (100%) diff --git a/gp_referral_toolkit/patient_note.txt b/gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt similarity index 100% rename from gp_referral_toolkit/patient_note.txt rename to gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt From 066d6c552f96a34549e998c493301c834483c723 Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Sun, 8 Jun 2025 16:28:37 +0200 Subject: [PATCH 07/12] Delete gp_referral_toolkit/communit-contributions/gp_referral_toolkit directory --- .../gp_referral_toolkit/patient_note.txt | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt diff --git a/gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt b/gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt deleted file mode 100644 index bef096a..0000000 --- a/gp_referral_toolkit/communit-contributions/gp_referral_toolkit/patient_note.txt +++ /dev/null @@ -1,17 +0,0 @@ -45F, fatigue and weight gain. Reports cold intolerance and constipation. No palpitations. Family history of thyroid disease. - ---- - -56M, chest pain on exertion for 3 weeks. No SOB or nausea. Hypertension, diabetes. Family history of CAD. - ---- - -22F, recurrent UTIs. Sexually active. No fever or flank pain. Normal renal function. History of E. coli positive urine cultures. - ---- - -60M, progressive shortness of breath. Former smoker. Bilateral wheezing on auscultation. Awaiting spirometry. History of COPD. - ---- - -32F, persistent headaches. Worse with stress. Normal neuro exam. No aura. Family history of migraines. Normal MRI last year. From 3c9d1fecfcea7b8a371202ea8c368fb1ce281db5 Mon Sep 17 00:00:00 2001 From: SUKIHEALTH Date: Sun, 8 Jun 2025 16:28:53 +0200 Subject: [PATCH 08/12] Delete gp_referral_toolkit directory --- .../gp_referral_toolkit/README.md | 1 - gp_referral_toolkit/referral_letter_bot.py | 46 ------------------- 2 files changed, 47 deletions(-) delete mode 100644 gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md delete mode 100644 gp_referral_toolkit/referral_letter_bot.py diff --git a/gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md b/gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md deleted file mode 100644 index 8b13789..0000000 --- a/gp_referral_toolkit/community-contributions/gp_referral_toolkit/README.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/gp_referral_toolkit/referral_letter_bot.py b/gp_referral_toolkit/referral_letter_bot.py deleted file mode 100644 index 4c6784d..0000000 --- a/gp_referral_toolkit/referral_letter_bot.py +++ /dev/null @@ -1,46 +0,0 @@ -import openai - -# Step 1: Summarize the patient consultation note -def summarize_patient_note(note_text): - response = openai.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "user", "content": f"Please summarize the following patient consultation note in a clear, clinical style:\n\n{note_text}"} - ] - ) - return response.choices[0].message.content - -# Step 2: Generate a specialist referral letter -def generate_referral_letter(summary_text, specialist_type): - system_prompt = f"You are an experienced general practitioner. Based on the consultation summary, write a concise, professional referral letter to a {specialist_type}." - - response = openai.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": f"Consultation summary:\n\n{summary_text}"} - ] - ) - return response.choices[0].message.content - -# Main logic -if __name__ == "__main__": - try: - with open('patient_note.txt', 'r', encoding='utf-8') as file: - patient_note = file.read() - - # Step 1: Summarize the note - summary = summarize_patient_note(patient_note) - print("\n🩺 Consultation Summary:") - print(summary) - - # Step 2: Ask user which specialist to refer to - specialist = input("\nāž”ļø Which specialist is this referral for (e.g., cardiologist, neurologist)?\n") - - # Step 3: Generate the referral letter - referral_letter = generate_referral_letter(summary, specialist) - print("\nšŸ“Ø Generated Referral Letter:\n") - print(referral_letter) - - except FileNotFoundError: - print("āŒ The file 'patient_note.txt' was not found. Please ensure it exists in the project folder.") From 1ab4b2ceb30200e0fee30bbfbd757946208570cc Mon Sep 17 00:00:00 2001 From: Sabine Fonderson | CEO Date: Sun, 22 Jun 2025 14:16:21 +0200 Subject: [PATCH 09/12] create folder sf-patient-brochure --- community-contributions/sf-patient-brochure/.gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 community-contributions/sf-patient-brochure/.gitkeep diff --git a/community-contributions/sf-patient-brochure/.gitkeep b/community-contributions/sf-patient-brochure/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/community-contributions/sf-patient-brochure/.gitkeep @@ -0,0 +1 @@ + From 72432fdf54be520d68b1e1f20e43212725256ca0 Mon Sep 17 00:00:00 2001 From: Sabine Fonderson | CEO Date: Sun, 22 Jun 2025 14:17:13 +0200 Subject: [PATCH 10/12] add patient brochure notebook and summaries --- .../Patient brochure.ipynb | 517 ++++++++++++++++++ .../brochure_summaries.txt | 40 ++ 2 files changed, 557 insertions(+) create mode 100644 community-contributions/sf-patient-brochure/Patient brochure.ipynb create mode 100644 community-contributions/sf-patient-brochure/brochure_summaries.txt diff --git a/community-contributions/sf-patient-brochure/Patient brochure.ipynb b/community-contributions/sf-patient-brochure/Patient brochure.ipynb new file mode 100644 index 0000000..4f6bc85 --- /dev/null +++ b/community-contributions/sf-patient-brochure/Patient brochure.ipynb @@ -0,0 +1,517 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 9, + "id": "fc57c47f-31fc-4527-af71-ce117d35c480", + "metadata": {}, + "outputs": [], + "source": [ + "# imports\n", + "# If these fail, please check you're running from an 'activated' environment with (llms) in the command prompt\n", + "\n", + "import os\n", + "import requests\n", + "import json\n", + "from typing import List\n", + "from dotenv import load_dotenv\n", + "from bs4 import BeautifulSoup\n", + "from IPython.display import Markdown, display, update_display\n", + "from openai import OpenAI\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "d74ea4e7-7d4a-4c85-92d3-8cdb231bc261", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3eb884ea-02db-4ff8-91f9-c71e40b1cf4a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "API key looks good so far\n" + ] + } + ], + "source": [ + "# Initialize and constants\n", + "\n", + "load_dotenv(override=True)\n", + "api_key = os.getenv('OPENAI_API_KEY')\n", + "\n", + "if api_key and api_key.startswith('sk-proj-') and len(api_key)>10:\n", + " print(\"API key looks good so far\")\n", + "else:\n", + " print(\"There might be a problem with your API key? Please visit the troubleshooting notebook!\")\n", + " \n", + "MODEL = 'gpt-4o-mini'\n", + "openai = OpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d48a7b9b-273d-4bc9-997b-c7112e02528c", + "metadata": {}, + "outputs": [], + "source": [ + "# A class to represent a Webpage\n", + "\n", + "# Some websites need you to use proper headers when fetching them:\n", + "headers = {\n", + " \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\"\n", + "}\n", + "\n", + "class Website:\n", + " def __init__(self, url):\n", + " self.url = url\n", + " response = requests.get(url, headers=headers)\n", + " self.body = response.content\n", + " soup = BeautifulSoup(self.body, 'html.parser')\n", + " self.title = soup.title.string if soup.title else \"No title found\"\n", + "\n", + " if soup.body:\n", + " for irrelevant in soup.body([\"script\", \"style\", \"img\", \"input\"]):\n", + " irrelevant.decompose()\n", + " self.text = soup.body.get_text(separator=\"\\n\", strip=True)\n", + " else:\n", + " self.text = \"\"\n", + "\n", + " links = [link.get('href') for link in soup.find_all('a')]\n", + " self.links = [link for link in links if link]\n", + "\n", + " def get_contents(self):\n", + " return f\"Webpage Title:\\n{self.title}\\nWebpage Contents:\\n{self.text}\\n\\n\"\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "bf51ae6e-91ae-46eb-ac39-dc860454ea4a", + "metadata": {}, + "outputs": [], + "source": [ + "def get_condition_links_from_topics_page():\n", + " topics_url = \"https://www.thuisarts.nl/overzicht/onderwerpen\"\n", + " response = requests.get(topics_url, headers=headers)\n", + " soup = BeautifulSoup(response.content, 'html.parser')\n", + "\n", + " # Find all tags that look like condition pages\n", + " links = soup.find_all(\"a\", href=True)\n", + " condition_links = []\n", + "\n", + " for link in links:\n", + " href = link['href']\n", + " if href.startswith(\"/\"):\n", + " href = \"https://www.thuisarts.nl\" + href\n", + " if href.startswith(\"https://www.thuisarts.nl/\") and len(href.split(\"/\")) > 3:\n", + " condition_links.append(href)\n", + "\n", + " # Remove duplicates and return\n", + " return list(set(condition_links))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a246ac9f-73fb-4c2d-ab92-6f3f2bf7afac", + "metadata": {}, + "outputs": [], + "source": [ + "link_system_prompt = \"\"\"You are an assistant that filters URLs for patient education content. \n", + "\n", + "Only return links that lead to pages about symptoms, health conditions, treatments, or diseases — for example: pages on 'headache', 'diarrhea', 'stomach pain', 'asthma', etc.\n", + "\n", + "DO NOT return:\n", + "- contact pages\n", + "- overview/video/image/keuzekaart lists unless they directly link to medical complaints\n", + "- navigation or privacy/cookie/social media links\n", + "\n", + "Respond only with full https links in JSON format, like this:\n", + "{\n", + " \"links\": [\n", + " {\"type\": \"symptom or condition page\", \"url\": \"https://www.thuisarts.nl/hoofdpijn\"},\n", + " {\"type\": \"symptom or condition page\", \"url\": \"https://www.thuisarts.nl/buikpijn\"}\n", + " ]\n", + "}\n", + "\"\"\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b3ac761e-f583-479e-b8ef-70e70f8f361a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are an assistant that filters URLs for patient education content. \n", + "\n", + "Only return links that lead to pages about symptoms, health conditions, treatments, or diseases — for example: pages on 'headache', 'diarrhea', 'stomach pain', 'asthma', etc.\n", + "\n", + "DO NOT return:\n", + "- contact pages\n", + "- overview/video/image/keuzekaart lists unless they directly link to medical complaints\n", + "- navigation or privacy/cookie/social media links\n", + "\n", + "Respond only with full https links in JSON format, like this:\n", + "{\n", + " \"links\": [\n", + " {\"type\": \"symptom or condition page\", \"url\": \"https://www.thuisarts.nl/hoofdpijn\"},\n", + " {\"type\": \"symptom or condition page\", \"url\": \"https://www.thuisarts.nl/buikpijn\"}\n", + " ]\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "print(link_system_prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "5548e8d4-2813-40fe-a807-cf3661d3a0a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "āœ… Found 680 condition pages.\n" + ] + } + ], + "source": [ + "condition_links = get_condition_links_from_topics_page()\n", + "print(f\"āœ… Found {len(condition_links)} condition pages.\")\n", + "\n", + "# Format for summary function\n", + "selected_links = [{\"url\": link} for link in condition_links]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8d264592-8b77-425a-be4a-73ef7d32d744", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "def load_existing_summaries(filepath=\"brochure_cache.json\"):\n", + " if os.path.exists(filepath):\n", + " with open(filepath, \"r\", encoding=\"utf-8\") as f:\n", + " return json.load(f)\n", + " return {}\n", + "\n", + "def save_summaries_to_cache(summaries, filepath=\"brochure_cache.json\"):\n", + " with open(filepath, \"w\", encoding=\"utf-8\") as f:\n", + " json.dump(summaries, f, indent=2, ensure_ascii=False)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1cdd9456-1262-40a0-bc3f-28d23010ed7f", + "metadata": {}, + "outputs": [], + "source": [ + "selected_links = [{\"url\": link} for link in get_condition_links_from_topics_page()][:10]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0c2f24ea-fa6b-4431-849a-e1aeaa936022", + "metadata": {}, + "outputs": [], + "source": [ + "summary_cache = {}\n", + "\n", + "def summarize_for_brochure(url):\n", + " if url in summary_cache:\n", + " summary = summary_cache[url]\n", + " print(f\"āœ… [Cached] {url}\")\n", + " print(f\"šŸ“„ Summary:\\n{summary}\\n\") # šŸ‘ˆ this prints the cached summary too\n", + " return summary\n", + "\n", + " page = Website(url)\n", + "\n", + " example = \"\"\"\n", + "Example:\n", + "\n", + "Title: Keelpijn \n", + "Summary: Sore throat is a common symptom, often caused by a virus. It usually goes away on its own within a few days. Drink warm fluids, rest your voice, and take paracetamol if needed. See a doctor if the pain lasts more than a week or gets worse.\n", + "\n", + "Title: Hoofdpijn \n", + "Summary: Headaches can have many causes like stress, fatigue, or dehydration. Most are harmless and go away with rest and fluids. Painkillers like paracetamol can help. If headaches are severe, frequent, or different than usual, contact your GP.\n", + "\"\"\"\n", + "\n", + " prompt = f\"\"\"\n", + "You are a health writer. Based on the Dutch content below, write a clear, short, brochure-style summary in **English** for patients.\n", + "\n", + "Use the format: \n", + "Title: {page.title} \n", + "Summary: \n", + "\n", + "Keep it under 100 words, easy to read, friendly, and medically accurate.\n", + "\n", + "{example}\n", + "\n", + "Now use this for:\n", + "Title: {page.title}\n", + "Content:\n", + "{page.text[:3000]}\n", + "\"\"\"\n", + "\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[{\"role\": \"user\", \"content\": prompt}],\n", + " temperature=0.4\n", + " )\n", + "\n", + " summary = response.choices[0].message.content.strip()\n", + " summary_cache[url] = summary\n", + " return summary\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "af8f9d81-d848-4fb9-ac79-782b39fed4a2", + "metadata": {}, + "outputs": [], + "source": [ + "def build_symptom_brochure(links, cache_file=\"brochure_cache.json\"):\n", + " brochure = []\n", + " cached = load_existing_summaries(cache_file)\n", + " print(\"šŸ“„ Building summaries for brochure:\\n\")\n", + "\n", + " for i, item in enumerate(links, 1):\n", + " url = item[\"url\"]\n", + " if url in cached:\n", + " print(f\"āœ… [Cached] {url}\")\n", + " brochure.append({\"url\": url, \"summary\": cached[url]})\n", + " continue\n", + " \n", + " print(f\"šŸ”„ [{i}/{len(links)}] Summarizing: {url}\")\n", + " try:\n", + " summary = summarize_for_brochure(url)\n", + " print(f\"āœ… Summary:\\n{summary}\\n\")\n", + " brochure.append({\"url\": url, \"summary\": summary})\n", + " cached[url] = summary # Save new summary\n", + " save_summaries_to_cache(cached, cache_file)\n", + " except Exception as e:\n", + " print(f\"āŒ Error summarizing {url}: {e}\\n\")\n", + " brochure.append({\"url\": url, \"summary\": \"Error generating summary.\"})\n", + "\n", + " return brochure\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "e9079d6b-538f-4681-9776-4628a111246a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ“„ Building summaries for brochure:\n", + "\n", + "šŸ”„ [1/10] Summarizing: https://www.thuisarts.nl/sociale-angststoornis\n", + "āœ… [New] https://www.thuisarts.nl/sociale-angststoornis\n", + "šŸ“„ Summary:\n", + "Title: Social Anxiety Disorder\n", + "Summary: Social anxiety disorder, or social phobia, is a fear of what others think of you, often leading to panic attacks. Writing down what happens, your thoughts, and feelings can help manage this fear. Positive thinking can also be beneficial when you're feeling anxious. Discussing your concerns with your GP or practice nurse can be helpful. If there's no improvement or symptoms are severe, treatments such as therapy with a psychologist or anxiety medication may be considered.\n", + "\n", + "āœ… Summary:\n", + "Title: Social Anxiety Disorder\n", + "Summary: Social anxiety disorder, or social phobia, is a fear of what others think of you, often leading to panic attacks. Writing down what happens, your thoughts, and feelings can help manage this fear. Positive thinking can also be beneficial when you're feeling anxious. Discussing your concerns with your GP or practice nurse can be helpful. If there's no improvement or symptoms are severe, treatments such as therapy with a psychologist or anxiety medication may be considered.\n", + "\n", + "āœ… [Cached] https://www.thuisarts.nl/diabetes-type-2\n", + "šŸ”„ [3/10] Summarizing: https://www.thuisarts.nl/morton-neuroom\n", + "āœ… [New] https://www.thuisarts.nl/morton-neuroom\n", + "šŸ“„ Summary:\n", + "Title: Morton's Neuroma | Thuisarts.nl \n", + "Summary: Morton's Neuroma is a pinched nerve in the forefoot, causing burning pain in the forefoot and toes. It often results from wearing too narrow shoes or high heels. Wearing comfortable, roomy shoes can help alleviate symptoms. For severe pain, paracetamol can be taken. Sometimes, a custom shoe insole can also help.\n", + "\n", + "āœ… Summary:\n", + "Title: Morton's Neuroma | Thuisarts.nl \n", + "Summary: Morton's Neuroma is a pinched nerve in the forefoot, causing burning pain in the forefoot and toes. It often results from wearing too narrow shoes or high heels. Wearing comfortable, roomy shoes can help alleviate symptoms. For severe pain, paracetamol can be taken. Sometimes, a custom shoe insole can also help.\n", + "\n", + "šŸ”„ [4/10] Summarizing: https://www.thuisarts.nl/borstvergroting\n", + "āœ… [New] https://www.thuisarts.nl/borstvergroting\n", + "šŸ“„ Summary:\n", + "Title: Breast Augmentation | Thuisarts.nl \n", + "Summary: A breast augmentation is a procedure where a plastic surgeon inserts fillings into your breasts, under general anesthesia. The surgery takes about an hour. Consider the pros and cons carefully. Benefits may include a more positive body image and increased self-confidence. Risks may include infection, bleeding, scarring, or hardening of the breasts over time. Often, a follow-up surgery is needed later. If you smoke, it's important to quit three weeks before surgery.\n", + "\n", + "āœ… Summary:\n", + "Title: Breast Augmentation | Thuisarts.nl \n", + "Summary: A breast augmentation is a procedure where a plastic surgeon inserts fillings into your breasts, under general anesthesia. The surgery takes about an hour. Consider the pros and cons carefully. Benefits may include a more positive body image and increased self-confidence. Risks may include infection, bleeding, scarring, or hardening of the breasts over time. Often, a follow-up surgery is needed later. If you smoke, it's important to quit three weeks before surgery.\n", + "\n", + "šŸ”„ [5/10] Summarizing: https://www.thuisarts.nl/kijkoperatie-in-buik\n", + "āœ… [New] https://www.thuisarts.nl/kijkoperatie-in-buik\n", + "šŸ“„ Summary:\n", + "Title: Abdominal Laparoscopy | Thuisarts.nl\n", + "Summary: An abdominal laparoscopy allows the doctor to examine or operate in your abdomen. Small tubes with a camera and tools are inserted through tiny incisions. You'll have a pre-operation discussion with your surgeon and anesthesiologist. You will be deeply sedated for the procedure. You cannot drive home post-operation, so arrange for someone to pick you up. Recovery usually requires a week off work, sometimes longer.\n", + "\n", + "āœ… Summary:\n", + "Title: Abdominal Laparoscopy | Thuisarts.nl\n", + "Summary: An abdominal laparoscopy allows the doctor to examine or operate in your abdomen. Small tubes with a camera and tools are inserted through tiny incisions. You'll have a pre-operation discussion with your surgeon and anesthesiologist. You will be deeply sedated for the procedure. You cannot drive home post-operation, so arrange for someone to pick you up. Recovery usually requires a week off work, sometimes longer.\n", + "\n", + "šŸ”„ [6/10] Summarizing: https://www.thuisarts.nl/veranderingen-in-zorg-als-je-18-wordt\n", + "āœ… [New] https://www.thuisarts.nl/veranderingen-in-zorg-als-je-18-wordt\n", + "šŸ“„ Summary:\n", + "Title: Changes in Care When You Turn 18 | Thuisarts.nl\n", + "Summary: As you become an adult, usually around 18, you transition from child to adult healthcare. You will start to take more responsibility, such as making appointments and requesting medications, giving you more control over your care. You will create a plan detailing what you need to manage this independently, with support provided to help you. This transition is a gradual process, with preparation beginning before you turn 18.\n", + "\n", + "āœ… Summary:\n", + "Title: Changes in Care When You Turn 18 | Thuisarts.nl\n", + "Summary: As you become an adult, usually around 18, you transition from child to adult healthcare. You will start to take more responsibility, such as making appointments and requesting medications, giving you more control over your care. You will create a plan detailing what you need to manage this independently, with support provided to help you. This transition is a gradual process, with preparation beginning before you turn 18.\n", + "\n", + "šŸ”„ [7/10] Summarizing: https://www.thuisarts.nl/zon-en-zonnebrand\n", + "āœ… [New] https://www.thuisarts.nl/zon-en-zonnebrand\n", + "šŸ“„ Summary:\n", + "Title: Sun and Sunburn | Thuisarts.nl\n", + "Summary: Protect your skin from excessive sunlight to avoid sunburn. If you notice your skin burning, immediately move out of the sun. Cool your skin with wet cloths if it hurts and take paracetamol for severe pain. Stay out of the sun for at least three days to allow your skin to recover. If you have symptoms of sunstroke, sun allergy, or eczema, seek medical advice.\n", + "\n", + "āœ… Summary:\n", + "Title: Sun and Sunburn | Thuisarts.nl\n", + "Summary: Protect your skin from excessive sunlight to avoid sunburn. If you notice your skin burning, immediately move out of the sun. Cool your skin with wet cloths if it hurts and take paracetamol for severe pain. Stay out of the sun for at least three days to allow your skin to recover. If you have symptoms of sunstroke, sun allergy, or eczema, seek medical advice.\n", + "\n", + "šŸ”„ [8/10] Summarizing: https://www.thuisarts.nl/ganglion\n", + "āœ… [New] https://www.thuisarts.nl/ganglion\n", + "šŸ“„ Summary:\n", + "Title: Ganglion | Thuisarts.nl \n", + "Summary: A ganglion is a small bump that can appear on your wrist, finger, or foot. It is a protrusion from the joint and is harmless. In half of the cases, a ganglion disappears on its own. If you notice such a bump, there is usually no cause for concern.\n", + "\n", + "āœ… Summary:\n", + "Title: Ganglion | Thuisarts.nl \n", + "Summary: A ganglion is a small bump that can appear on your wrist, finger, or foot. It is a protrusion from the joint and is harmless. In half of the cases, a ganglion disappears on its own. If you notice such a bump, there is usually no cause for concern.\n", + "\n", + "šŸ”„ [9/10] Summarizing: https://www.thuisarts.nl/kunstheup\n", + "āœ… [New] https://www.thuisarts.nl/kunstheup\n", + "šŸ“„ Summary:\n", + "Title: Hip Replacement | Thuisarts.nl\n", + "Summary: A hip replacement can be an option if you are experiencing severe pain or stiffness in your hip, such as from advanced arthritis or another hip disease. This is usually considered when other treatments like physiotherapy and painkillers have not provided enough relief. You can discuss with your hospital doctor whether a hip replacement is suitable for you. A hip prosthesis typically lasts longer than 20 years.\n", + "\n", + "āœ… Summary:\n", + "Title: Hip Replacement | Thuisarts.nl\n", + "Summary: A hip replacement can be an option if you are experiencing severe pain or stiffness in your hip, such as from advanced arthritis or another hip disease. This is usually considered when other treatments like physiotherapy and painkillers have not provided enough relief. You can discuss with your hospital doctor whether a hip replacement is suitable for you. A hip prosthesis typically lasts longer than 20 years.\n", + "\n", + "šŸ”„ [10/10] Summarizing: https://www.thuisarts.nl/gezond-leven\n", + "āœ… [New] https://www.thuisarts.nl/gezond-leven\n", + "šŸ“„ Summary:\n", + "Title: Healthy Living | Thuisarts.nl\n", + "Summary: For good health, it's important to eat, drink, and sleep well, stay active, relax, and maintain social contacts. Avoiding substances like alcohol is also beneficial. If you want to make changes to your lifestyle, take it step by step. Discuss your plans with your GP or practice nurse. Whether it's about healthy eating, exercise, sleep, stress management, social contact, or substance use, they can provide guidance and support.\n", + "\n", + "āœ… Summary:\n", + "Title: Healthy Living | Thuisarts.nl\n", + "Summary: For good health, it's important to eat, drink, and sleep well, stay active, relax, and maintain social contacts. Avoiding substances like alcohol is also beneficial. If you want to make changes to your lifestyle, take it step by step. Discuss your plans with your GP or practice nurse. Whether it's about healthy eating, exercise, sleep, stress management, social contact, or substance use, they can provide guidance and support.\n", + "\n" + ] + } + ], + "source": [ + "brochure = build_symptom_brochure(selected_links)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e2121c3c-aa6a-4640-8e19-6ca6ccf84783", + "metadata": {}, + "outputs": [], + "source": [ + "def export_brochure_to_txt(brochure, filepath=\"brochure_summaries.txt\"):\n", + " if not brochure:\n", + " print(\"āš ļø No summaries to export.\")\n", + " return\n", + "\n", + " with open(filepath, \"w\", encoding=\"utf-8\") as f:\n", + " for item in brochure:\n", + " url = item.get(\"url\", \"Unknown URL\")\n", + " summary = item.get(\"summary\", \"No summary available.\")\n", + " f.write(f\"URL: {url}\\n\")\n", + " f.write(f\"{summary}\\n\\n\")\n", + "\n", + " print(f\"šŸ“ Exported {len(brochure)} summaries to {filepath}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "f14288f9-4d1c-4a0e-aaf4-9f86324b0602", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ“ Exported 10 summaries to brochure_summaries.txt\n" + ] + } + ], + "source": [ + "export_brochure_to_txt(brochure)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c23e89db-3ded-4189-a227-6ca6ac2f1332", + "metadata": {}, + "outputs": [], + "source": [ + "###---it works---" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a700e4f3-fb6a-499a-a579-6f9b8ad35c9f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/community-contributions/sf-patient-brochure/brochure_summaries.txt b/community-contributions/sf-patient-brochure/brochure_summaries.txt new file mode 100644 index 0000000..0ba4556 --- /dev/null +++ b/community-contributions/sf-patient-brochure/brochure_summaries.txt @@ -0,0 +1,40 @@ +URL: https://www.thuisarts.nl/sociale-angststoornis +Title: Social Anxiety Disorder +Summary: Social anxiety disorder, or social phobia, is a fear of what others think of you, often leading to panic attacks. Writing down what happens, your thoughts, and feelings can help manage this fear. Positive thinking can also be beneficial when you're feeling anxious. Discussing your concerns with your GP or practice nurse can be helpful. If there's no improvement or symptoms are severe, treatments such as therapy with a psychologist or anxiety medication may be considered. + +URL: https://www.thuisarts.nl/diabetes-type-2 +Title: Diabetes type 2 | Thuisarts.nl +Summary: Type 2 diabetes, also known as sugar disease, is characterized by high blood sugar levels. Leading a healthy lifestyle is crucial: eat healthily, lose weight, exercise regularly, relax, and quit smoking. If blood sugar levels remain high, medication may be required. Regular check-ups, usually every three months, with your GP or practice nurse are essential. + +URL: https://www.thuisarts.nl/morton-neuroom +Title: Morton's Neuroma | Thuisarts.nl +Summary: Morton's Neuroma is a pinched nerve in the forefoot, causing burning pain in the forefoot and toes. It often results from wearing too narrow shoes or high heels. Wearing comfortable, roomy shoes can help alleviate symptoms. For severe pain, paracetamol can be taken. Sometimes, a custom shoe insole can also help. + +URL: https://www.thuisarts.nl/borstvergroting +Title: Breast Augmentation | Thuisarts.nl +Summary: A breast augmentation is a procedure where a plastic surgeon inserts fillings into your breasts, under general anesthesia. The surgery takes about an hour. Consider the pros and cons carefully. Benefits may include a more positive body image and increased self-confidence. Risks may include infection, bleeding, scarring, or hardening of the breasts over time. Often, a follow-up surgery is needed later. If you smoke, it's important to quit three weeks before surgery. + +URL: https://www.thuisarts.nl/kijkoperatie-in-buik +Title: Abdominal Laparoscopy | Thuisarts.nl +Summary: An abdominal laparoscopy allows the doctor to examine or operate in your abdomen. Small tubes with a camera and tools are inserted through tiny incisions. You'll have a pre-operation discussion with your surgeon and anesthesiologist. You will be deeply sedated for the procedure. You cannot drive home post-operation, so arrange for someone to pick you up. Recovery usually requires a week off work, sometimes longer. + +URL: https://www.thuisarts.nl/veranderingen-in-zorg-als-je-18-wordt +Title: Changes in Care When You Turn 18 | Thuisarts.nl +Summary: As you become an adult, usually around 18, you transition from child to adult healthcare. You will start to take more responsibility, such as making appointments and requesting medications, giving you more control over your care. You will create a plan detailing what you need to manage this independently, with support provided to help you. This transition is a gradual process, with preparation beginning before you turn 18. + +URL: https://www.thuisarts.nl/zon-en-zonnebrand +Title: Sun and Sunburn | Thuisarts.nl +Summary: Protect your skin from excessive sunlight to avoid sunburn. If you notice your skin burning, immediately move out of the sun. Cool your skin with wet cloths if it hurts and take paracetamol for severe pain. Stay out of the sun for at least three days to allow your skin to recover. If you have symptoms of sunstroke, sun allergy, or eczema, seek medical advice. + +URL: https://www.thuisarts.nl/ganglion +Title: Ganglion | Thuisarts.nl +Summary: A ganglion is a small bump that can appear on your wrist, finger, or foot. It is a protrusion from the joint and is harmless. In half of the cases, a ganglion disappears on its own. If you notice such a bump, there is usually no cause for concern. + +URL: https://www.thuisarts.nl/kunstheup +Title: Hip Replacement | Thuisarts.nl +Summary: A hip replacement can be an option if you are experiencing severe pain or stiffness in your hip, such as from advanced arthritis or another hip disease. This is usually considered when other treatments like physiotherapy and painkillers have not provided enough relief. You can discuss with your hospital doctor whether a hip replacement is suitable for you. A hip prosthesis typically lasts longer than 20 years. + +URL: https://www.thuisarts.nl/gezond-leven +Title: Healthy Living | Thuisarts.nl +Summary: For good health, it's important to eat, drink, and sleep well, stay active, relax, and maintain social contacts. Avoiding substances like alcohol is also beneficial. If you want to make changes to your lifestyle, take it step by step. Discuss your plans with your GP or practice nurse. Whether it's about healthy eating, exercise, sleep, stress management, social contact, or substance use, they can provide guidance and support. + From b840ea6b58ba79dd2ab63fb2f76b14628fd94c59 Mon Sep 17 00:00:00 2001 From: Sabine Fonderson | CEO Date: Fri, 27 Jun 2025 16:46:48 +0200 Subject: [PATCH 11/12] Add files via upload Hi Ed, --- .../clinic_booking_bot.ipynb | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 community-contributions/clinic_booking_bot.ipynb diff --git a/community-contributions/clinic_booking_bot.ipynb b/community-contributions/clinic_booking_bot.ipynb new file mode 100644 index 0000000..d2d8b57 --- /dev/null +++ b/community-contributions/clinic_booking_bot.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 170, + "id": "a1aa1b43-7a47-4aca-ae5f-94a9d4ba2d89", + "metadata": {}, + "outputs": [], + "source": [ + "## Clinic Booking Bot\n", + "\n", + "##Easily book your clinic visit – available only on weekdays between **14:00 and 15:00**. \n", + "##Speak or type, and get instant confirmation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "id": "fe798c6a-f8da-46aa-8c0e-9d2623def3d2", + "metadata": {}, + "outputs": [], + "source": [ + "# import library\n", + "\n", + "import os\n", + "import json\n", + "from dotenv import load_dotenv\n", + "from openai import OpenAI\n", + "import gradio as gr\n", + "import base64\n", + "from io import BytesIO\n", + "from datetime import date\n", + "from PIL import Image, ImageDraw, ImageFont\n" + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "id": "0ad4e526-e95d-4e70-9faa-b4236b105dd5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API Key exists and begins sk-proj-\n" + ] + } + ], + "source": [ + "# Save keys\n", + "\n", + "load_dotenv(override=True)\n", + "\n", + "openai_api_key = os.getenv('OPENAI_API_KEY')\n", + "if openai_api_key:\n", + " print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n", + "else:\n", + " print(\"OpenAI API Key not set\")\n", + " \n", + "MODEL = \"gpt-4o-mini\"\n", + "openai = OpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "id": "ae95308e-0002-4017-9f2c-fcb1ddb248fa", + "metadata": {}, + "outputs": [], + "source": [ + "# --- CONFIG ---\n", + "BOOKING_START = 14\n", + "BOOKING_END = 15\n", + "WEEKDAYS = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"]\n", + "PHONE = \"010-1234567\"\n", + "confirmed_bookings = []\n" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "id": "e21b0fd0-4cda-4938-8867-dc2c6e7af4b1", + "metadata": {}, + "outputs": [], + "source": [ + "# --- TTS ---\n", + "def generate_tts(text, voice=\"fable\", filename=\"output.mp3\"):\n", + " response = openai.audio.speech.create(\n", + " model=\"tts-1\",\n", + " voice=\"fable\",\n", + " input=text\n", + " )\n", + " with open(filename, \"wb\") as f:\n", + " f.write(response.content)\n", + " return filename" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "id": "e28a5c3b-bd01-4845-a41e-87823f6bb078", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Translate Booking Confirmation ---\n", + "def translate_text(text, target_language=\"nl\"):\n", + " prompt = f\"Translate this message to {target_language}:\\n{text}\"\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful translator.\"},\n", + " {\"role\": \"user\", \"content\": prompt}\n", + " ]\n", + " )\n", + " return response.choices[0].message.content.strip()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "id": "8ed57cc9-7d54-4a5d-831b-0efcc5b7a7a9", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Booking Logic ---\n", + "def book_appointment(name, time_str):\n", + " try:\n", + " booking_time = datetime.strptime(time_str, \"%H:%M\")\n", + " except ValueError:\n", + " return \"Invalid time format. Use HH:MM.\", None, None\n", + "\n", + " hour = booking_time.hour\n", + " weekday = datetime.today().strftime(\"%A\")\n", + "\n", + " if weekday not in WEEKDAYS:\n", + " response = \"Bookings are only available on weekdays.\"\n", + " elif BOOKING_START <= hour < BOOKING_END:\n", + " confirmation = f\"Booking confirmed for {name} at {time_str}.\"\n", + " confirmed_bookings.append((name, time_str))\n", + " translated = translate_text(confirmation)\n", + " audio = generate_tts(translated)\n", + " image = generate_booking_image(name, time_str)\n", + " return translated, audio, image\n", + " else:\n", + " response = \"Sorry, bookings are only accepted between 14:00 and 15:00 on weekdays.\"\n", + " translated = translate_text(response)\n", + " audio = generate_tts(translated)\n", + " return translated, audio, None" + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "id": "19b52115-f0f3-4d63-a463-886163d4cfd1", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Booking Card ---\n", + "def generate_booking_image(name, time_str):\n", + " img = Image.new(\"RGB\", (500, 250), color=\"white\")\n", + " draw = ImageDraw.Draw(img)\n", + " msg = f\"\\u2705 Booking Confirmed\\nName: {name}\\nTime: {time_str}\"\n", + " draw.text((50, 100), msg, fill=\"black\")\n", + " return img" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "id": "2c446b6c-d410-4ba1-b0c7-c475e5259ff5", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Voice Booking ---\n", + "def voice_booking(audio_path, name):\n", + " with open(audio_path, \"rb\") as f:\n", + " response = openai.audio.transcriptions.create(model=\"whisper-1\", file=f)\n", + " transcription = response.text.strip()\n", + "\n", + " system_prompt = \"\"\"\n", + " You are a clinic assistant. Extract only the appointment time from the user's sentence in 24-hour HH:MM format.\n", + " If no time is mentioned, respond with 'No valid time found.'\n", + " \"\"\"\n", + "\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": transcription}\n", + " ]\n", + " )\n", + " extracted_time = response.choices[0].message.content.strip()\n", + "\n", + " if \":\" in extracted_time:\n", + " return book_appointment(name, extracted_time)\n", + " else:\n", + " message = \"Sorry, I couldn't understand the time. Please try again.\"\n", + " translated = translate_text(message)\n", + " audio_path = generate_tts(translated)\n", + " return translated, audio_path, None" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "id": "121d2907-7fa8-4248-b2e7-83617ea66ff0", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Chat Bot Handler ---\n", + "def chat_bot(messages):\n", + " system_prompt = \"\"\"\n", + " You are a clinic booking assistant. Your job is to:\n", + " - Greet the patient and explain your role\n", + " - Only assist with making appointments\n", + " - Accept bookings only on weekdays between 14:00 and 15:00\n", + " - Do not provide medical advice\n", + " - Always respond with empathy and clarity\n", + " \"\"\"\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[{\"role\": \"system\", \"content\": system_prompt}] + messages\n", + " )\n", + " reply = response.choices[0].message.content.strip()\n", + " audio = generate_tts(reply)\n", + " return reply, audio" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "id": "2427b694-8c57-40cb-b202-4a8989547925", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* Running on local URL: http://127.0.0.1:7898\n", + "* To create a public link, set `share=True` in `launch()`.\n" + ] + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Gradio interface\n", + "with gr.Blocks(theme=gr.themes.Soft()) as demo:\n", + " gr.Markdown(\"\"\"## 🩺 GP Booking Assistant \n", + "Only available weekdays between **14:00 and 15:00** \n", + "ā˜Žļø Contact: {PHONE}\n", + "---\"\"\")\n", + "\n", + " name_global = gr.Textbox(label=\"Your Name\", placeholder=\"Enter your name\", interactive=True)\n", + "\n", + " with gr.Tab(\"šŸ’¬ Chat Mode\"):\n", + " chatbot = gr.Chatbot(label=\"Booking Chat\", type=\"messages\", height=400)\n", + " text_input = gr.Textbox(label=\"Type your message or use your voice below\")\n", + " audio_input = gr.Audio(type=\"filepath\", label=\"šŸŽ™ļø Or speak your request\")\n", + " chat_audio_output = gr.Audio(label=\"šŸ”Š Assistant's Reply\", type=\"filepath\")\n", + " send_btn = gr.Button(\"Send\")\n", + "\n", + " def handle_chat(user_message, chat_history):\n", + " chat_history = chat_history or []\n", + " chat_history.append({\"role\": \"user\", \"content\": user_message})\n", + " reply, audio = chat_bot(chat_history)\n", + " chat_history.append({\"role\": \"assistant\", \"content\": reply})\n", + " return chat_history, \"\", audio\n", + "\n", + " def handle_audio_chat(audio_path, chat_history):\n", + " with open(audio_path, \"rb\") as f:\n", + " transcription = openai.audio.transcriptions.create(model=\"whisper-1\", file=f).text.strip()\n", + " return handle_chat(transcription, chat_history)\n", + "\n", + " send_btn.click(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + " text_input.submit(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + " audio_input.change(handle_audio_chat, [audio_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + "\n", + "\n", + " \n", + " with gr.Tab(\"šŸ“ Text Booking\"):\n", + " time_text = gr.Textbox(label=\"Preferred Time (HH:MM)\", placeholder=\"e.g., 14:30\")\n", + " btn_text = gr.Button(\"šŸ“… Book via Text\")\n", + "\n", + " with gr.Tab(\"šŸŽ™ļø Voice Booking\"):\n", + " voice_input = gr.Audio(type=\"filepath\", label=\"Say your preferred time\")\n", + " btn_voice = gr.Button(\"šŸ“… Book via Voice\")\n", + "\n", + " output_text = gr.Textbox(label=\"Response\", interactive=False)\n", + " output_audio = gr.Audio(label=\"Audio Reply\", type=\"filepath\")\n", + " output_image = gr.Image(label=\"Booking Confirmation\")\n", + "\n", + " btn_text.click(fn=book_appointment, inputs=[name_global, time_text], outputs=[output_text, output_audio, output_image])\n", + " btn_voice.click(fn=voice_booking, inputs=[voice_input, name_global], outputs=[output_text, output_audio, output_image])\n", + "\n", + " gr.Markdown(\"\"\"---\n", + "This assistant does **not** give medical advice. It only books appointments within allowed hours.\n", + "\"\"\")\n", + "\n", + " demo.launch()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f359de0a-28b1-4895-b21d-91d79e494a0d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 966a5f6c385a6d235d2e79bb2950a1943a66bc24 Mon Sep 17 00:00:00 2001 From: Sabine Fonderson | CEO Date: Fri, 27 Jun 2025 16:50:39 +0200 Subject: [PATCH 12/12] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hi Ed, This is a Gradio-based booking assistant bot that: - Accepts text and voice inputs - Uses OpenAI for transcription and chat - Restricts booking times to weekdays 14:00–15:00 - Responds with translated audio confirmations Looking forward to your feedback! Sabine --- .../clinic_booking_bot.ipynb | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 week2/community-contributions/clinic_booking_bot.ipynb diff --git a/week2/community-contributions/clinic_booking_bot.ipynb b/week2/community-contributions/clinic_booking_bot.ipynb new file mode 100644 index 0000000..d2d8b57 --- /dev/null +++ b/week2/community-contributions/clinic_booking_bot.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 170, + "id": "a1aa1b43-7a47-4aca-ae5f-94a9d4ba2d89", + "metadata": {}, + "outputs": [], + "source": [ + "## Clinic Booking Bot\n", + "\n", + "##Easily book your clinic visit – available only on weekdays between **14:00 and 15:00**. \n", + "##Speak or type, and get instant confirmation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "id": "fe798c6a-f8da-46aa-8c0e-9d2623def3d2", + "metadata": {}, + "outputs": [], + "source": [ + "# import library\n", + "\n", + "import os\n", + "import json\n", + "from dotenv import load_dotenv\n", + "from openai import OpenAI\n", + "import gradio as gr\n", + "import base64\n", + "from io import BytesIO\n", + "from datetime import date\n", + "from PIL import Image, ImageDraw, ImageFont\n" + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "id": "0ad4e526-e95d-4e70-9faa-b4236b105dd5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API Key exists and begins sk-proj-\n" + ] + } + ], + "source": [ + "# Save keys\n", + "\n", + "load_dotenv(override=True)\n", + "\n", + "openai_api_key = os.getenv('OPENAI_API_KEY')\n", + "if openai_api_key:\n", + " print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n", + "else:\n", + " print(\"OpenAI API Key not set\")\n", + " \n", + "MODEL = \"gpt-4o-mini\"\n", + "openai = OpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "id": "ae95308e-0002-4017-9f2c-fcb1ddb248fa", + "metadata": {}, + "outputs": [], + "source": [ + "# --- CONFIG ---\n", + "BOOKING_START = 14\n", + "BOOKING_END = 15\n", + "WEEKDAYS = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"]\n", + "PHONE = \"010-1234567\"\n", + "confirmed_bookings = []\n" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "id": "e21b0fd0-4cda-4938-8867-dc2c6e7af4b1", + "metadata": {}, + "outputs": [], + "source": [ + "# --- TTS ---\n", + "def generate_tts(text, voice=\"fable\", filename=\"output.mp3\"):\n", + " response = openai.audio.speech.create(\n", + " model=\"tts-1\",\n", + " voice=\"fable\",\n", + " input=text\n", + " )\n", + " with open(filename, \"wb\") as f:\n", + " f.write(response.content)\n", + " return filename" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "id": "e28a5c3b-bd01-4845-a41e-87823f6bb078", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Translate Booking Confirmation ---\n", + "def translate_text(text, target_language=\"nl\"):\n", + " prompt = f\"Translate this message to {target_language}:\\n{text}\"\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful translator.\"},\n", + " {\"role\": \"user\", \"content\": prompt}\n", + " ]\n", + " )\n", + " return response.choices[0].message.content.strip()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "id": "8ed57cc9-7d54-4a5d-831b-0efcc5b7a7a9", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Booking Logic ---\n", + "def book_appointment(name, time_str):\n", + " try:\n", + " booking_time = datetime.strptime(time_str, \"%H:%M\")\n", + " except ValueError:\n", + " return \"Invalid time format. Use HH:MM.\", None, None\n", + "\n", + " hour = booking_time.hour\n", + " weekday = datetime.today().strftime(\"%A\")\n", + "\n", + " if weekday not in WEEKDAYS:\n", + " response = \"Bookings are only available on weekdays.\"\n", + " elif BOOKING_START <= hour < BOOKING_END:\n", + " confirmation = f\"Booking confirmed for {name} at {time_str}.\"\n", + " confirmed_bookings.append((name, time_str))\n", + " translated = translate_text(confirmation)\n", + " audio = generate_tts(translated)\n", + " image = generate_booking_image(name, time_str)\n", + " return translated, audio, image\n", + " else:\n", + " response = \"Sorry, bookings are only accepted between 14:00 and 15:00 on weekdays.\"\n", + " translated = translate_text(response)\n", + " audio = generate_tts(translated)\n", + " return translated, audio, None" + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "id": "19b52115-f0f3-4d63-a463-886163d4cfd1", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Booking Card ---\n", + "def generate_booking_image(name, time_str):\n", + " img = Image.new(\"RGB\", (500, 250), color=\"white\")\n", + " draw = ImageDraw.Draw(img)\n", + " msg = f\"\\u2705 Booking Confirmed\\nName: {name}\\nTime: {time_str}\"\n", + " draw.text((50, 100), msg, fill=\"black\")\n", + " return img" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "id": "2c446b6c-d410-4ba1-b0c7-c475e5259ff5", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Voice Booking ---\n", + "def voice_booking(audio_path, name):\n", + " with open(audio_path, \"rb\") as f:\n", + " response = openai.audio.transcriptions.create(model=\"whisper-1\", file=f)\n", + " transcription = response.text.strip()\n", + "\n", + " system_prompt = \"\"\"\n", + " You are a clinic assistant. Extract only the appointment time from the user's sentence in 24-hour HH:MM format.\n", + " If no time is mentioned, respond with 'No valid time found.'\n", + " \"\"\"\n", + "\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": transcription}\n", + " ]\n", + " )\n", + " extracted_time = response.choices[0].message.content.strip()\n", + "\n", + " if \":\" in extracted_time:\n", + " return book_appointment(name, extracted_time)\n", + " else:\n", + " message = \"Sorry, I couldn't understand the time. Please try again.\"\n", + " translated = translate_text(message)\n", + " audio_path = generate_tts(translated)\n", + " return translated, audio_path, None" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "id": "121d2907-7fa8-4248-b2e7-83617ea66ff0", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Chat Bot Handler ---\n", + "def chat_bot(messages):\n", + " system_prompt = \"\"\"\n", + " You are a clinic booking assistant. Your job is to:\n", + " - Greet the patient and explain your role\n", + " - Only assist with making appointments\n", + " - Accept bookings only on weekdays between 14:00 and 15:00\n", + " - Do not provide medical advice\n", + " - Always respond with empathy and clarity\n", + " \"\"\"\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-4\",\n", + " messages=[{\"role\": \"system\", \"content\": system_prompt}] + messages\n", + " )\n", + " reply = response.choices[0].message.content.strip()\n", + " audio = generate_tts(reply)\n", + " return reply, audio" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "id": "2427b694-8c57-40cb-b202-4a8989547925", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* Running on local URL: http://127.0.0.1:7898\n", + "* To create a public link, set `share=True` in `launch()`.\n" + ] + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Gradio interface\n", + "with gr.Blocks(theme=gr.themes.Soft()) as demo:\n", + " gr.Markdown(\"\"\"## 🩺 GP Booking Assistant \n", + "Only available weekdays between **14:00 and 15:00** \n", + "ā˜Žļø Contact: {PHONE}\n", + "---\"\"\")\n", + "\n", + " name_global = gr.Textbox(label=\"Your Name\", placeholder=\"Enter your name\", interactive=True)\n", + "\n", + " with gr.Tab(\"šŸ’¬ Chat Mode\"):\n", + " chatbot = gr.Chatbot(label=\"Booking Chat\", type=\"messages\", height=400)\n", + " text_input = gr.Textbox(label=\"Type your message or use your voice below\")\n", + " audio_input = gr.Audio(type=\"filepath\", label=\"šŸŽ™ļø Or speak your request\")\n", + " chat_audio_output = gr.Audio(label=\"šŸ”Š Assistant's Reply\", type=\"filepath\")\n", + " send_btn = gr.Button(\"Send\")\n", + "\n", + " def handle_chat(user_message, chat_history):\n", + " chat_history = chat_history or []\n", + " chat_history.append({\"role\": \"user\", \"content\": user_message})\n", + " reply, audio = chat_bot(chat_history)\n", + " chat_history.append({\"role\": \"assistant\", \"content\": reply})\n", + " return chat_history, \"\", audio\n", + "\n", + " def handle_audio_chat(audio_path, chat_history):\n", + " with open(audio_path, \"rb\") as f:\n", + " transcription = openai.audio.transcriptions.create(model=\"whisper-1\", file=f).text.strip()\n", + " return handle_chat(transcription, chat_history)\n", + "\n", + " send_btn.click(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + " text_input.submit(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + " audio_input.change(handle_audio_chat, [audio_input, chatbot], [chatbot, text_input, chat_audio_output])\n", + "\n", + "\n", + " \n", + " with gr.Tab(\"šŸ“ Text Booking\"):\n", + " time_text = gr.Textbox(label=\"Preferred Time (HH:MM)\", placeholder=\"e.g., 14:30\")\n", + " btn_text = gr.Button(\"šŸ“… Book via Text\")\n", + "\n", + " with gr.Tab(\"šŸŽ™ļø Voice Booking\"):\n", + " voice_input = gr.Audio(type=\"filepath\", label=\"Say your preferred time\")\n", + " btn_voice = gr.Button(\"šŸ“… Book via Voice\")\n", + "\n", + " output_text = gr.Textbox(label=\"Response\", interactive=False)\n", + " output_audio = gr.Audio(label=\"Audio Reply\", type=\"filepath\")\n", + " output_image = gr.Image(label=\"Booking Confirmation\")\n", + "\n", + " btn_text.click(fn=book_appointment, inputs=[name_global, time_text], outputs=[output_text, output_audio, output_image])\n", + " btn_voice.click(fn=voice_booking, inputs=[voice_input, name_global], outputs=[output_text, output_audio, output_image])\n", + "\n", + " gr.Markdown(\"\"\"---\n", + "This assistant does **not** give medical advice. It only books appointments within allowed hours.\n", + "\"\"\")\n", + "\n", + " demo.launch()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f359de0a-28b1-4895-b21d-91d79e494a0d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}