From 9c501972b9ce9cc98f2fb25ae0aa7c188d4eb14c Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 12:08:57 +0000 Subject: [PATCH 01/10] refactor(medcat-service): Add type hints for demo --- medcat-service/medcat_service/demo/demo_logic.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/medcat-service/medcat_service/demo/demo_logic.py b/medcat-service/medcat_service/demo/demo_logic.py index 050dd66b7..3660abcf9 100644 --- a/medcat-service/medcat_service/demo/demo_logic.py +++ b/medcat-service/medcat_service/demo/demo_logic.py @@ -7,6 +7,7 @@ """ import logging +from typing import Any from pydantic import BaseModel @@ -108,7 +109,9 @@ def convert_display_model_to_list_of_lists(entity_display_model: list[EntityAnno ] -def perform_named_entity_resolution(input_text: str, redact: bool | None = None): +def perform_named_entity_resolution( + input_text: str, redact: bool | None = None +) -> tuple[dict[str, Any], list[list[str]], str]: """ Performs clinical coding by processing the input text with MedCAT to extract and annotate medical concepts (entities). @@ -135,7 +138,7 @@ def perform_named_entity_resolution(input_text: str, redact: bool | None = None) """ logger.debug("Performing named entity resolution") if not input_text or not input_text.strip(): - return None, None, None + return {}, [], "" processor = get_medcat_processor(get_settings()) input = ProcessAPIInputContent(text=input_text) @@ -160,7 +163,7 @@ def perform_named_entity_resolution(input_text: str, redact: bool | None = None) return response_tuple -def medcat_demo_perform_named_entity_resolution(input_text: str): +def medcat_demo_perform_named_entity_resolution(input_text: str) -> tuple[dict[str, Any], list[list[str]]]: """ Performs named entity resolution for the MedCAT demo. """ @@ -168,7 +171,7 @@ def medcat_demo_perform_named_entity_resolution(input_text: str): return result[0], result[1] -def anoncat_demo_perform_deidentification(input_text: str, redact: bool): +def anoncat_demo_perform_deidentification(input_text: str, redact: bool) -> tuple[dict[str, Any], list[list[str]], str]: """ Performs deidentification for the AnonCAT demo. """ From 5d9d9eb56aedeea9127df846579472425eae2262 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 12:42:50 +0000 Subject: [PATCH 02/10] refactor(medcat-service): Demo - move interface out of global context --- .../medcat_service/demo/gradio_demo.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index eb0da47d7..13159d7ac 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -71,12 +71,13 @@ def on_select(value, annotation_details, dataframe, evt: gr.SelectData): return annotation_details_placeholder_text -if settings.deid_mode: +def anoncat_demo_interface(): with gr.Blocks(title="AnonCAT Demo", fill_width=True) as io: gr.Markdown("# AnonCAT Demo") with gr.Row(): with gr.Column(): # noqa with gr.Tab("Input"): + # Using a tab here just to make the input text box align with the output that is also tabbed input_text = gr.Textbox( label="Input Text", lines=3, placeholder="Enter some text and click Deidentify..." ) @@ -85,10 +86,12 @@ def on_select(value, annotation_details, dataframe, evt: gr.SelectData): inputs=input_text, example_labels=["Short Example", "Note with personally identifiable information"], ) - redact = gr.Checkbox(label="Redact") - with gr.Row(): - clear_btn = gr.Button("Clear", variant="secondary") - annotate_btn = gr.Button("Deidentify", variant="primary") + + redact = gr.Checkbox(label="Redact", container=False, + info="Replace sensitive information with ****") + + clear_btn = gr.Button("Clear", variant="secondary") + annotate_btn = gr.Button("Deidentify", variant="primary") with gr.Column(): with gr.Tab("Deidentification"): @@ -105,21 +108,25 @@ def on_select(value, annotation_details, dataframe, evt: gr.SelectData): label="All Annotations", headers=headers, interactive=False, max_chars=50 ) - highlighted.select(on_select, [highlighted, annotation_details, dataframe], outputs=annotation_details) + highlighted.select(on_select, [highlighted, annotation_details, + dataframe], outputs=annotation_details) - annotate_btn.click( - anoncat_demo_perform_deidentification, - inputs=[input_text, redact], - outputs=[highlighted, dataframe, deidentified_text], - ) - annotate_btn.click(lambda: (annotation_details_placeholder_text), outputs=[annotation_details]) + annotate_btn.click( + anoncat_demo_perform_deidentification, + inputs=[input_text, redact], + outputs=[highlighted, dataframe, deidentified_text], + ) + annotate_btn.click(lambda: (annotation_details_placeholder_text), outputs=[annotation_details]) - clear_btn.click( - lambda: ("", None, None, annotation_details_placeholder_text), - outputs=[input_text, highlighted, dataframe, annotation_details], - ) + clear_btn.click( + lambda: ("", None, None, annotation_details_placeholder_text), + outputs=[input_text, highlighted, dataframe, annotation_details], + ) gr.Markdown(demo_content.anoncat_help_content) -else: + return io + + +def medcat_demo_interface(): with gr.Blocks(title="MedCAT Demo", fill_width=True) as io: gr.Markdown("# MedCAT Demo") with gr.Row(): @@ -159,6 +166,7 @@ def on_select(value, annotation_details, dataframe, evt: gr.SelectData): outputs=[input_text, highlighted, dataframe, annotation_details], ) gr.Markdown(demo_content.article_footer) + return io def mount_gradio_app(app, path: str = "/demo") -> None: @@ -170,4 +178,7 @@ def mount_gradio_app(app, path: str = "/demo") -> None: path: The path at which to mount the Gradio app (default: "/demo") """ theme = gr.themes.Default(primary_hue="blue", secondary_hue="teal") + + io = anoncat_demo_interface() if settings.deid_mode else medcat_demo_interface() + gr.mount_gradio_app(app, io, path=path, theme=theme, css=highlighted_text_css) From d4a3f39dae48469066842bc2a699161f2d28e6b4 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 13:16:27 +0000 Subject: [PATCH 03/10] refactor(medcat-service): Demo - Reduce nesting layers extracting functions --- .../medcat_service/demo/gradio_demo.py | 143 +++++++++--------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index 13159d7ac..6dd80b2f7 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -1,4 +1,5 @@ import gradio as gr +import pandas as pd import medcat_service.demo.demo_content as demo_content from medcat_service.demo.demo_logic import ( @@ -22,7 +23,7 @@ annotation_details_placeholder_text = "Click on a highlighted entity to view its details" -def format_annotation_details(row, selected_text: str): +def format_annotation_details(row: pd.Series | None, selected_text: str) -> str: """Format a pandas Series row as markdown for display.""" if row is None: return "**No annotation selected**\n\nClick on a highlighted entity to view its details." @@ -52,7 +53,7 @@ def format_annotation_details(row, selected_text: str): return details -def on_select(value, annotation_details, dataframe, evt: gr.SelectData): +def on_select_annotation(value, annotation_details, dataframe, evt: gr.SelectData): """ On select of annotations in the highlighted text component. @@ -71,90 +72,94 @@ def on_select(value, annotation_details, dataframe, evt: gr.SelectData): return annotation_details_placeholder_text +def output_details_interface(): + """ + Output details interface for the demo. + Based on gradio Namd-Entity Recognition Demo + https://www.gradio.app/guides/named-entity-recognition + """ + highlighted = gr.HighlightedText(label="Processed Text", elem_id="highlighted-text-output", interactive=False) + annotation_details = gr.Markdown(label="Annotation Details", value=annotation_details_placeholder_text) + with gr.Accordion(label="All Annotations", open=False): + dataframe = gr.Dataframe(label="All Annotations", headers=headers, interactive=False, max_chars=50) + + highlighted.select(on_select_annotation, [highlighted, annotation_details, dataframe], outputs=annotation_details) + return highlighted, annotation_details, dataframe + + def anoncat_demo_interface(): + def input_column(): + with gr.Tab("Input"): + with gr.Group(): + # Using a tab here just to make the input text box align with the output that is also tabbed + input_text = gr.Textbox( + label="Input Text", lines=3, placeholder="Enter some text and click Deidentify..." + ) + redact = gr.Checkbox(label="Redact", info="Replace sensitive information with ****") + examples = gr.Examples( + examples=[demo_content.short_example, demo_content.anoncat_example], + inputs=input_text, + example_labels=["Short Example", "Note with personally identifiable information"], + ) + with gr.Row(): + clear_btn = gr.Button("Clear", variant="secondary") + annotate_btn = gr.Button("Deidentify", variant="primary") + return input_text, redact, clear_btn, annotate_btn + + def output_column(): + with gr.Tab("Deidentification"): + deidentified_text = gr.Textbox(label="Deidentified Text", value="", lines=3, interactive=False) + with gr.Tab("Details"): + highlighted, annotation_details, dataframe = output_details_interface() + return highlighted, dataframe, deidentified_text, annotation_details + with gr.Blocks(title="AnonCAT Demo", fill_width=True) as io: gr.Markdown("# AnonCAT Demo") with gr.Row(): with gr.Column(): # noqa - with gr.Tab("Input"): - # Using a tab here just to make the input text box align with the output that is also tabbed - input_text = gr.Textbox( - label="Input Text", lines=3, placeholder="Enter some text and click Deidentify..." - ) - examples = gr.Examples( - examples=[demo_content.short_example, demo_content.anoncat_example], - inputs=input_text, - example_labels=["Short Example", "Note with personally identifiable information"], - ) - - redact = gr.Checkbox(label="Redact", container=False, - info="Replace sensitive information with ****") - - clear_btn = gr.Button("Clear", variant="secondary") - annotate_btn = gr.Button("Deidentify", variant="primary") - + input_text, redact, clear_btn, annotate_btn = input_column() with gr.Column(): - with gr.Tab("Deidentification"): - deidentified_text = gr.Textbox(label="Deidentified Text", value="", interactive=False) - with gr.Tab("Details"): - highlighted = gr.HighlightedText( - label="Processed Text", elem_id="highlighted-text-output", interactive=False - ) - annotation_details = gr.Markdown( - label="Annotation Details", value=annotation_details_placeholder_text - ) - with gr.Accordion(label="All Annotations", open=False): - dataframe = gr.Dataframe( - label="All Annotations", headers=headers, interactive=False, max_chars=50 - ) - - highlighted.select(on_select, [highlighted, annotation_details, - dataframe], outputs=annotation_details) - - annotate_btn.click( - anoncat_demo_perform_deidentification, - inputs=[input_text, redact], - outputs=[highlighted, dataframe, deidentified_text], - ) - annotate_btn.click(lambda: (annotation_details_placeholder_text), outputs=[annotation_details]) + highlighted, dataframe, deidentified_text, annotation_details = output_column() + annotate_btn.click( + anoncat_demo_perform_deidentification, + inputs=[input_text, redact], + outputs=[highlighted, dataframe, deidentified_text], + ) + annotate_btn.click(lambda: (annotation_details_placeholder_text), outputs=[annotation_details]) - clear_btn.click( - lambda: ("", None, None, annotation_details_placeholder_text), - outputs=[input_text, highlighted, dataframe, annotation_details], - ) + clear_btn.click( + lambda: ("", None, None, annotation_details_placeholder_text), + outputs=[input_text, highlighted, dataframe, annotation_details], + ) gr.Markdown(demo_content.anoncat_help_content) return io def medcat_demo_interface(): + def input_column(): + input_text = gr.Textbox(label="Input Text", lines=6, placeholder="Enter some text and click Annotate...") + with gr.Row(): + examples = gr.Examples( + examples=[demo_content.short_example, demo_content.long_example, demo_content.anoncat_example], + inputs=input_text, + example_labels=[ + "Short Example", + "Patient Discharge Summary in Neurology", + "Note with personally identifiable information", + ], + ) + with gr.Row(): + clear_btn = gr.Button("Clear", variant="secondary") + annotate_btn = gr.Button("Annotate", variant="primary") + return input_text, clear_btn, annotate_btn + with gr.Blocks(title="MedCAT Demo", fill_width=True) as io: gr.Markdown("# MedCAT Demo") with gr.Row(): with gr.Column(): - input_text = gr.Textbox( - label="Input Text", lines=6, placeholder="Enter some text and click Annotate..." - ) - with gr.Row(): - examples = gr.Examples( - examples=[demo_content.short_example, demo_content.long_example, demo_content.anoncat_example], - inputs=input_text, - example_labels=[ - "Short Example", - "Patient Discharge Summary in Neurology", - "Note with personally identifiable information", - ], - ) - with gr.Row(): - clear_btn = gr.Button("Clear", variant="secondary") - annotate_btn = gr.Button("Annotate", variant="primary") + input_text, clear_btn, annotate_btn = input_column() with gr.Column(): - highlighted = gr.HighlightedText( - label="Processed Text", elem_id="highlighted-text-output", interactive=False - ) - annotation_details = gr.Markdown(label="Annotation Details", value=annotation_details_placeholder_text) - with gr.Accordion(label="All Annotations", open=False): - dataframe = gr.Dataframe(label="All Annotations", headers=headers, interactive=False, max_chars=50) - highlighted.select(on_select, [highlighted, annotation_details, dataframe], outputs=annotation_details) + highlighted, annotation_details, dataframe = output_details_interface() annotate_btn.click(lambda: (annotation_details_placeholder_text), outputs=[annotation_details]) annotate_btn.click( From fd23adfb81d1e91fe31c0119ac384fb3e3fdd38b Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:01:08 +0000 Subject: [PATCH 04/10] refactor(medcat-service): Demo - Reduce nesting layers extracting functions --- medcat-service/medcat_service/demo/gradio_demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index 6dd80b2f7..f7adb1d59 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -96,7 +96,7 @@ def input_column(): label="Input Text", lines=3, placeholder="Enter some text and click Deidentify..." ) redact = gr.Checkbox(label="Redact", info="Replace sensitive information with ****") - examples = gr.Examples( + examples = gr.Examples( # noqa examples=[demo_content.short_example, demo_content.anoncat_example], inputs=input_text, example_labels=["Short Example", "Note with personally identifiable information"], @@ -139,7 +139,7 @@ def medcat_demo_interface(): def input_column(): input_text = gr.Textbox(label="Input Text", lines=6, placeholder="Enter some text and click Annotate...") with gr.Row(): - examples = gr.Examples( + examples = gr.Examples( # noqa examples=[demo_content.short_example, demo_content.long_example, demo_content.anoncat_example], inputs=input_text, example_labels=[ From 7061dbbbc384de7911954c9aa7d793faf2eda503 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:13:47 +0000 Subject: [PATCH 05/10] refactor(medcat-service): Demo - Fix tests --- .../medcat_service/test/demo/test_demo_logic.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/medcat-service/medcat_service/test/demo/test_demo_logic.py b/medcat-service/medcat_service/test/demo/test_demo_logic.py index 4a55be15c..959570923 100644 --- a/medcat-service/medcat_service/test/demo/test_demo_logic.py +++ b/medcat-service/medcat_service/test/demo/test_demo_logic.py @@ -97,10 +97,10 @@ def test_perform_named_entity_resolution_with_empty_string(self, mock_get_proces # Execute result_dict, result_table, result_text = perform_named_entity_resolution("") - # Assert - self.assertIsNone(result_dict) - self.assertIsNone(result_table) - self.assertIsNone(result_text) + # Assert - should return empty objects, not None + self.assertEqual(result_dict, {}) + self.assertEqual(result_table, []) + self.assertEqual(result_text, "") @patch("medcat_service.demo.demo_logic.get_settings") @patch("medcat_service.demo.demo_logic.get_medcat_processor") @@ -113,10 +113,10 @@ def test_perform_named_entity_resolution_with_whitespace_only(self, mock_get_pro # Execute result_dict, result_table, result_text = perform_named_entity_resolution(" \n\t ") - # Assert - self.assertIsNone(result_dict) - self.assertIsNone(result_table) - self.assertIsNone(result_text) + # Assert - should return empty objects, not None + self.assertEqual(result_dict, {}) + self.assertEqual(result_table, []) + self.assertEqual(result_text, "") @patch("medcat_service.demo.demo_logic.get_settings") @patch("medcat_service.demo.demo_logic.get_medcat_processor") From 1d7ed248167dcc1444bbe0c7fa1b3e96eaff7ea9 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:20:58 +0000 Subject: [PATCH 06/10] feat(medcat-service): Demo - make disabled by default with env vars --- medcat-service/medcat_service/config.py | 3 +++ medcat-service/medcat_service/demo/gradio_demo.py | 10 +++++----- medcat-service/medcat_service/main.py | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/medcat-service/medcat_service/config.py b/medcat-service/medcat_service/config.py index febb84469..25896bff9 100644 --- a/medcat-service/medcat_service/config.py +++ b/medcat-service/medcat_service/config.py @@ -52,6 +52,9 @@ class Settings(BaseSettings): description="Enable DEID redaction. Returns text like [***] instead of [ANNOTATION]", ) + enable_demo_app : bool = Field(default=False, description="Run the demo app", alias="APP_ENABLE_DEMO_APP") + demo_app_path: str = Field(default="/demo", description="Path to the demo app", alias="APP_DEMO_APP_PATH") + # Model paths model_cdb_path: str | None = Field("/cat/models/medmen/cdb.dat", alias="APP_MODEL_CDB_PATH") model_vocab_path: str | None = Field("/cat/models/medmen/vocab.dat", alias="APP_MODEL_VOCAB_PATH") diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index f7adb1d59..accf961a3 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -53,7 +53,7 @@ def format_annotation_details(row: pd.Series | None, selected_text: str) -> str: return details -def on_select_annotation(value, annotation_details, dataframe, evt: gr.SelectData): +def on_select_annotation(value, annotation_details: str, dataframe: pd.DataFrame, evt: gr.SelectData) -> str: """ On select of annotations in the highlighted text component. @@ -72,7 +72,7 @@ def on_select_annotation(value, annotation_details, dataframe, evt: gr.SelectDat return annotation_details_placeholder_text -def output_details_interface(): +def output_details_interface() -> tuple[gr.HighlightedText, gr.Markdown, gr.Dataframe]: """ Output details interface for the demo. Based on gradio Namd-Entity Recognition Demo @@ -87,7 +87,7 @@ def output_details_interface(): return highlighted, annotation_details, dataframe -def anoncat_demo_interface(): +def anoncat_demo_interface() -> gr.Blocks: def input_column(): with gr.Tab("Input"): with gr.Group(): @@ -135,7 +135,7 @@ def output_column(): return io -def medcat_demo_interface(): +def medcat_demo_interface() -> gr.Blocks: def input_column(): input_text = gr.Textbox(label="Input Text", lines=6, placeholder="Enter some text and click Annotate...") with gr.Row(): @@ -174,7 +174,7 @@ def input_column(): return io -def mount_gradio_app(app, path: str = "/demo") -> None: +def mount_gradio_app(app, path) -> None: """ Mount the Gradio interface to the FastAPI app with a custom theme. diff --git a/medcat-service/medcat_service/main.py b/medcat-service/medcat_service/main.py index 1a260f000..e561fd0e4 100644 --- a/medcat-service/medcat_service/main.py +++ b/medcat-service/medcat_service/main.py @@ -36,7 +36,9 @@ app.include_router(health.router) app.include_router(process.router) -mount_gradio_app(app, path="/demo") + +if settings.enable_demo_app: + mount_gradio_app(app, path=settings.demo_app_path) def configure_observability(settings: Settings, app: FastAPI): From 467f467af1f4402344b960ceba8328fcfa258f7e Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:25:10 +0000 Subject: [PATCH 07/10] feat(medcat-service): Demo - make disabled by default with env vars --- medcat-service/README.md | 2 ++ medcat-service/medcat_service/config.py | 4 ++-- medcat-service/medcat_service/main.py | 4 ++-- medcat-service/start_service_debug.sh | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/medcat-service/README.md b/medcat-service/README.md index 4b726ae0f..7e7d31b62 100644 --- a/medcat-service/README.md +++ b/medcat-service/README.md @@ -329,6 +329,8 @@ The following environment variables are available for tailoring the MedCAT Servi - `APP_BULK_NPROC` - the number of threads used in bulk processing (default: `8`), - `APP_MEDCAT_MODEL_PACK` - MedCAT Model Pack path, if this parameter has a value IT WILL BE LOADED FIRST OVER EVERYTHING ELSE (CDB, Vocab, MetaCATs, etc.) declared above. - `APP_ENABLE_METRICS` - Enable prometheus metrics collection served on the path /metrics +- `APP_ENABLE_DEMO_UI` - Enable the demo user interface to try models +- `APP_DEMO_UI_PATH` - Customise the path of the demo UI. Default is /demo ### Shared Memory (`DOCKER_SHM_SIZE`) diff --git a/medcat-service/medcat_service/config.py b/medcat-service/medcat_service/config.py index 25896bff9..39877eea5 100644 --- a/medcat-service/medcat_service/config.py +++ b/medcat-service/medcat_service/config.py @@ -52,8 +52,8 @@ class Settings(BaseSettings): description="Enable DEID redaction. Returns text like [***] instead of [ANNOTATION]", ) - enable_demo_app : bool = Field(default=False, description="Run the demo app", alias="APP_ENABLE_DEMO_APP") - demo_app_path: str = Field(default="/demo", description="Path to the demo app", alias="APP_DEMO_APP_PATH") + enable_demo_ui: bool = Field(default=False, description="Run the demo app", alias="APP_ENABLE_DEMO_UI") + demo_ui_path: str = Field(default="/demo", description="Path to the demo app", alias="APP_DEMO_UI_PATH") # Model paths model_cdb_path: str | None = Field("/cat/models/medmen/cdb.dat", alias="APP_MODEL_CDB_PATH") diff --git a/medcat-service/medcat_service/main.py b/medcat-service/medcat_service/main.py index e561fd0e4..d6318e745 100644 --- a/medcat-service/medcat_service/main.py +++ b/medcat-service/medcat_service/main.py @@ -37,8 +37,8 @@ app.include_router(process.router) -if settings.enable_demo_app: - mount_gradio_app(app, path=settings.demo_app_path) +if settings.enable_demo_ui: + mount_gradio_app(app, path=settings.demo_ui_path) def configure_observability(settings: Settings, app: FastAPI): diff --git a/medcat-service/start_service_debug.sh b/medcat-service/start_service_debug.sh index 798d66d3f..69c711607 100644 --- a/medcat-service/start_service_debug.sh +++ b/medcat-service/start_service_debug.sh @@ -12,6 +12,8 @@ if [ -z "${APP_MODEL_CDB_PATH}" ] && [ -z "${APP_MODEL_VOCAB_PATH}" ] && [ -z "$ fi export APP_ENABLE_METRICS=${APP_ENABLE_METRICS:-True} +export APP_ENABLE_DEMO_UI=${APP_ENABLE_DEMO_UI:-True} +export APP_DEMO_UI_PATH=${APP_DEMO_UI_PATH:-/demo} if [ "${HOT_MODULE_RELOADING}" = "True" ]; then # Experimental: Hot module reloading. Need to `pip install -r requirements-dev.txt` From b23b09b7ee122ac4ac4caae55d080a9d74171282 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:36:16 +0000 Subject: [PATCH 08/10] feat(medcat-service): Demo - Add tracing. Mount on / --- medcat-service/README.md | 4 ++-- medcat-service/medcat_service/config.py | 4 ++-- medcat-service/medcat_service/demo/demo_logic.py | 4 ++++ medcat-service/start_service_debug.sh | 1 - 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/medcat-service/README.md b/medcat-service/README.md index 7e7d31b62..791d58ae3 100644 --- a/medcat-service/README.md +++ b/medcat-service/README.md @@ -329,8 +329,8 @@ The following environment variables are available for tailoring the MedCAT Servi - `APP_BULK_NPROC` - the number of threads used in bulk processing (default: `8`), - `APP_MEDCAT_MODEL_PACK` - MedCAT Model Pack path, if this parameter has a value IT WILL BE LOADED FIRST OVER EVERYTHING ELSE (CDB, Vocab, MetaCATs, etc.) declared above. - `APP_ENABLE_METRICS` - Enable prometheus metrics collection served on the path /metrics -- `APP_ENABLE_DEMO_UI` - Enable the demo user interface to try models -- `APP_DEMO_UI_PATH` - Customise the path of the demo UI. Default is /demo +- `APP_ENABLE_DEMO_UI` - Enable the demo user interface to try models. (Default: `False`) +- `APP_DEMO_UI_PATH` - Customise the path of the demo UI. (Default: `/`) ### Shared Memory (`DOCKER_SHM_SIZE`) diff --git a/medcat-service/medcat_service/config.py b/medcat-service/medcat_service/config.py index 39877eea5..fed350dfd 100644 --- a/medcat-service/medcat_service/config.py +++ b/medcat-service/medcat_service/config.py @@ -52,8 +52,8 @@ class Settings(BaseSettings): description="Enable DEID redaction. Returns text like [***] instead of [ANNOTATION]", ) - enable_demo_ui: bool = Field(default=False, description="Run the demo app", alias="APP_ENABLE_DEMO_UI") - demo_ui_path: str = Field(default="/demo", description="Path to the demo app", alias="APP_DEMO_UI_PATH") + enable_demo_ui: bool = Field(default=False, description="Enable the demo app", alias="APP_ENABLE_DEMO_UI") + demo_ui_path: str = Field(default="", description="Path to the demo app", alias="APP_DEMO_UI_PATH") # Model paths model_cdb_path: str | None = Field("/cat/models/medmen/cdb.dat", alias="APP_MODEL_CDB_PATH") diff --git a/medcat-service/medcat_service/demo/demo_logic.py b/medcat-service/medcat_service/demo/demo_logic.py index 3660abcf9..c25ef3d37 100644 --- a/medcat-service/medcat_service/demo/demo_logic.py +++ b/medcat-service/medcat_service/demo/demo_logic.py @@ -9,6 +9,7 @@ import logging from typing import Any +from opentelemetry import trace from pydantic import BaseModel from medcat_service.dependencies import get_medcat_processor, get_settings @@ -16,6 +17,7 @@ from medcat_service.types_entities import Entity logger = logging.getLogger(__name__) +tracer = trace.get_tracer("medcat_service") class EntityAnnotation(BaseModel): @@ -163,6 +165,7 @@ def perform_named_entity_resolution( return response_tuple +@tracer.start_as_current_span("medcat_demo_perform_named_entity_resolution") def medcat_demo_perform_named_entity_resolution(input_text: str) -> tuple[dict[str, Any], list[list[str]]]: """ Performs named entity resolution for the MedCAT demo. @@ -171,6 +174,7 @@ def medcat_demo_perform_named_entity_resolution(input_text: str) -> tuple[dict[s return result[0], result[1] +@tracer.start_as_current_span("anoncat_demo_perform_deidentification") def anoncat_demo_perform_deidentification(input_text: str, redact: bool) -> tuple[dict[str, Any], list[list[str]], str]: """ Performs deidentification for the AnonCAT demo. diff --git a/medcat-service/start_service_debug.sh b/medcat-service/start_service_debug.sh index 69c711607..f975ac382 100644 --- a/medcat-service/start_service_debug.sh +++ b/medcat-service/start_service_debug.sh @@ -13,7 +13,6 @@ fi export APP_ENABLE_METRICS=${APP_ENABLE_METRICS:-True} export APP_ENABLE_DEMO_UI=${APP_ENABLE_DEMO_UI:-True} -export APP_DEMO_UI_PATH=${APP_DEMO_UI_PATH:-/demo} if [ "${HOT_MODULE_RELOADING}" = "True" ]; then # Experimental: Hot module reloading. Need to `pip install -r requirements-dev.txt` From 68b0c06406efdd5f1ba2ece4b9e6bdccbc18044f Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:38:12 +0000 Subject: [PATCH 09/10] feat(medcat-service): Demo - Add typing --- medcat-service/medcat_service/demo/gradio_demo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index accf961a3..5043b7c4b 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -1,5 +1,6 @@ import gradio as gr import pandas as pd +from fastapi import FastAPI import medcat_service.demo.demo_content as demo_content from medcat_service.demo.demo_logic import ( @@ -174,7 +175,7 @@ def input_column(): return io -def mount_gradio_app(app, path) -> None: +def mount_gradio_app(app: FastAPI, path: str) -> None: """ Mount the Gradio interface to the FastAPI app with a custom theme. From 10441ff4f02820e68947818215c48e4575948781 Mon Sep 17 00:00:00 2001 From: alhendrickson <159636032+alhendrickson@users.noreply.github.com.> Date: Fri, 6 Feb 2026 14:41:04 +0000 Subject: [PATCH 10/10] feat(medcat-service): Demo - Rename to just anoncat instead of anoncat demo --- medcat-service/medcat_service/demo/gradio_demo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index 5043b7c4b..5daefdf55 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -114,8 +114,8 @@ def output_column(): highlighted, annotation_details, dataframe = output_details_interface() return highlighted, dataframe, deidentified_text, annotation_details - with gr.Blocks(title="AnonCAT Demo", fill_width=True) as io: - gr.Markdown("# AnonCAT Demo") + with gr.Blocks(title="AnonCAT", fill_width=True) as io: + gr.Markdown("# AnonCAT") with gr.Row(): with gr.Column(): # noqa input_text, redact, clear_btn, annotate_btn = input_column() @@ -154,8 +154,8 @@ def input_column(): annotate_btn = gr.Button("Annotate", variant="primary") return input_text, clear_btn, annotate_btn - with gr.Blocks(title="MedCAT Demo", fill_width=True) as io: - gr.Markdown("# MedCAT Demo") + with gr.Blocks(title="MedCAT", fill_width=True) as io: + gr.Markdown("# MedCAT") with gr.Row(): with gr.Column(): input_text, clear_btn, annotate_btn = input_column()