From a0f79a282c4bafd1ea2db7699b1dc939c3f6cfb4 Mon Sep 17 00:00:00 2001 From: alhendrickson Date: Tue, 28 Jul 2026 15:53:14 +0000 Subject: [PATCH 1/2] feat(medcat-service): Demo app supports custom footers for display --- medcat-service/docs/setup/configuration.md | 16 +++++- medcat-service/medcat_service/config.py | 30 +++++++++++ .../medcat_service/demo/demo_content.py | 14 ++++- .../medcat_service/demo/gradio_demo.py | 4 +- .../demo/resources/anoncat_help_content.txt | 23 -------- .../demo/resources/article_footer.txt | 7 --- .../demo/resources/default_footer.md | 13 +++++ .../test/demo/test_demo_content.py | 53 +++++++++++++++++++ 8 files changed, 125 insertions(+), 35 deletions(-) delete mode 100644 medcat-service/medcat_service/demo/resources/anoncat_help_content.txt delete mode 100644 medcat-service/medcat_service/demo/resources/article_footer.txt create mode 100644 medcat-service/medcat_service/demo/resources/default_footer.md create mode 100644 medcat-service/medcat_service/test/demo/test_demo_content.py diff --git a/medcat-service/docs/setup/configuration.md b/medcat-service/docs/setup/configuration.md index 130b79c65..657de80eb 100644 --- a/medcat-service/docs/setup/configuration.md +++ b/medcat-service/docs/setup/configuration.md @@ -27,6 +27,7 @@ The following environment variables are available for tailoring the MedCAT Servi - `APP_ENABLE_METRICS` - Enable prometheus metrics collection served on the path /metrics - `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: `/`) +- `APP_DEMO_UI_CUSTOM_MARKDOWN_PATH` - Path to a custom markdown file for the demo UI footer (for example a Docker volume or Kubernetes ConfigMap mount). When unset, the bundled default footer is used. - `APP_USE_CDN` - Load Swagger UI and ReDoc assets from a CDN. (Default: `True`) Set to `False` to serve docs from bundled static files instead. This allows the docs UI to work for offline browsers. - `GRADIO_ANALYTICS_ENABLED` - Optionally disable the telemetry of gradio when using the demo UI @@ -74,4 +75,17 @@ The main settings that can be used to improve the performance when querying larg MedCAT parameters are defined in selected `envs/medcat*` file. -For details on available MedCAT parameters please refer to [the official GitHub repository](https://github.com/CogStack/cogstack-nlp/blob/main/medcat-v2/). \ No newline at end of file +For details on available MedCAT parameters please refer to [the official GitHub repository](https://github.com/CogStack/cogstack-nlp/blob/main/medcat-v2/). + +## Custom footer markdown + +Both the MedCAT and AnonCAT UIs show the same bundled footer by default (CogStack contact, repository link, and licence). You can replace it at runtime with a markdown file mounted into the container — for example a Docker volume or a Kubernetes ConfigMap. + +```yaml +environment: + - APP_DEMO_UI_CUSTOM_MARKDOWN_PATH=/config/demo-footer.md +volumes: + - ./demo-footer.md:/config/demo-footer.md:ro +``` + +If the path is unset, the bundled default footer is used. If a path is set to an empty/whitespace value, or the file does not exist or cannot be read, settings validation fails and the service will not start. diff --git a/medcat-service/medcat_service/config.py b/medcat-service/medcat_service/config.py index 28600c7b3..a2033479e 100644 --- a/medcat-service/medcat_service/config.py +++ b/medcat-service/medcat_service/config.py @@ -1,4 +1,5 @@ import logging +from pathlib import Path from typing import Any import torch @@ -54,6 +55,14 @@ class Settings(BaseSettings): 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") + demo_ui_custom_markdown_path: str | None = Field( + default=None, + description=( + "Path to a custom markdown file for the demo UI footer. " + "When unset, the bundled default footer is used." + ), + alias="APP_DEMO_UI_CUSTOM_MARKDOWN_PATH", + ) use_cdn_for_docs: bool = Field( default=True, @@ -94,6 +103,27 @@ class Settings(BaseSettings): observability: ObservabilitySettings = ObservabilitySettings() # ---- Normalizers --------------------------------------------------------- + @field_validator("demo_ui_custom_markdown_path", mode="after") + @classmethod + def _validate_demo_ui_custom_markdown_path(cls, v: str | None) -> str | None: + if v is None: + return None + path = v.strip() + if not path: + raise ValueError( + "APP_DEMO_UI_CUSTOM_MARKDOWN_PATH must be a non-empty path to a readable markdown file" + ) + markdown_path = Path(path) + if not markdown_path.is_file(): + raise ValueError( + f"APP_DEMO_UI_CUSTOM_MARKDOWN_PATH does not exist or is not a file: {path}" + ) + try: + markdown_path.read_text(encoding="utf-8") + except OSError as exc: + raise ValueError(f"APP_DEMO_UI_CUSTOM_MARKDOWN_PATH cannot be read: {path} ({exc})") from exc + return path + @field_validator("app_log_level", "medcat_log_level", mode="before") @classmethod def _val_log_levels(cls, v: Any) -> int: diff --git a/medcat-service/medcat_service/demo/demo_content.py b/medcat-service/medcat_service/demo/demo_content.py index 045f1ce2a..b7c56c224 100644 --- a/medcat-service/medcat_service/demo/demo_content.py +++ b/medcat-service/medcat_service/demo/demo_content.py @@ -1,5 +1,8 @@ import importlib.resources from functools import cache +from pathlib import Path + +from medcat_service.config import Settings @cache @@ -12,5 +15,12 @@ def _read_file(filename: str) -> str: short_example = _read_file('short_example.txt') long_example = _read_file('long_example.txt') anoncat_example = _read_file('anoncat_example.txt') -article_footer = _read_file('article_footer.txt') -anoncat_help_content = _read_file('anoncat_help_content.txt') +default_footer = _read_file('default_footer.md') + + +def resolve_demo_markdown(settings: Settings) -> str: + """Return custom demo markdown when a path is set, otherwise the bundled default.""" + path = settings.demo_ui_custom_markdown_path + if not path: + return default_footer + return Path(path).read_text(encoding="utf-8") diff --git a/medcat-service/medcat_service/demo/gradio_demo.py b/medcat-service/medcat_service/demo/gradio_demo.py index 5daefdf55..a80ab1d04 100644 --- a/medcat-service/medcat_service/demo/gradio_demo.py +++ b/medcat-service/medcat_service/demo/gradio_demo.py @@ -132,7 +132,7 @@ def output_column(): lambda: ("", None, None, annotation_details_placeholder_text), outputs=[input_text, highlighted, dataframe, annotation_details], ) - gr.Markdown(demo_content.anoncat_help_content) + gr.Markdown(demo_content.resolve_demo_markdown(settings)) return io @@ -171,7 +171,7 @@ def input_column(): lambda: ("", None, None, annotation_details_placeholder_text), outputs=[input_text, highlighted, dataframe, annotation_details], ) - gr.Markdown(demo_content.article_footer) + gr.Markdown(demo_content.resolve_demo_markdown(settings)) return io diff --git a/medcat-service/medcat_service/demo/resources/anoncat_help_content.txt b/medcat-service/medcat_service/demo/resources/anoncat_help_content.txt deleted file mode 100644 index e96f57d23..000000000 --- a/medcat-service/medcat_service/demo/resources/anoncat_help_content.txt +++ /dev/null @@ -1,23 +0,0 @@ -Demo app for the deidentification of private health information using the CogStack AnonCAT model - -Please DO NOT test with any real sensitive PHI data. - -Local validation and fine-tuning available via [MedCATtrainer]( -https://github.com/CogStack/cogstack-nlp/tree/main/medcat-trainer). -Email us, [contact@cogstack.org](mailto:contact@cogstack.org), to discuss model access, -model performance, and your use case. - -The following PHI items have been trained: - -| PHI Item | Description | -|----------|-------------| -| NHS Number | UK National Health Service Numbers. | -| Name | All names, first, middle, last of patients, relatives, care providers etc. Importantly, does not redact conditions that are named after a name, e.g. "Parkinsons's disease". | -| Date of Birth | DOBs. Does not include other dates that may be in the record, i.e. dates of visit etc. | -| Hospital Number | A unique number provided by the hospital. Distinct from the NHS number | -| Address Line | Address lines - first, second, third or fourth | -| Postcode | UK postal codes - 6 or 7 alphanumeric codes as part of addresses | -| Telephone Number | Telephone numbers, extensions, mobile / cell phone numbers | -| Email | Email addresses | -| Initials | Patient, relatives, care provider name initials. | - diff --git a/medcat-service/medcat_service/demo/resources/article_footer.txt b/medcat-service/medcat_service/demo/resources/article_footer.txt deleted file mode 100644 index 2c2f13c3e..000000000 --- a/medcat-service/medcat_service/demo/resources/article_footer.txt +++ /dev/null @@ -1,7 +0,0 @@ -## Disclaimer -This software is intended solely for the testing purposes and non-commercial use. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. - -contact@cogstack.com for more information. - -Please note this is a limited version of MedCAT and it is not trained or validated by clinicans. - diff --git a/medcat-service/medcat_service/demo/resources/default_footer.md b/medcat-service/medcat_service/demo/resources/default_footer.md new file mode 100644 index 000000000..5f278c070 --- /dev/null +++ b/medcat-service/medcat_service/demo/resources/default_footer.md @@ -0,0 +1,13 @@ +## About + +MedCAT Service is part of the [CogStack](https://cogstack.org/) open-source NLP toolkit. + +For support or enquiries, contact [contact@cogstack.org](mailto:contact@cogstack.org). + +Source code and documentation: [github.com/CogStack/cogstack-nlp](https://github.com/CogStack/cogstack-nlp). + +## Copyright and licence + +Copyright CogStack. MedCAT Service is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. diff --git a/medcat-service/medcat_service/test/demo/test_demo_content.py b/medcat-service/medcat_service/test/demo/test_demo_content.py new file mode 100644 index 000000000..c4f0cf077 --- /dev/null +++ b/medcat-service/medcat_service/test/demo/test_demo_content.py @@ -0,0 +1,53 @@ +"""Unit tests for demo markdown content resolution.""" + +import tempfile +import unittest +from pathlib import Path + +from pydantic import ValidationError + +from medcat_service.config import Settings +from medcat_service.demo import demo_content + + +class TestResolveDemoMarkdown(unittest.TestCase): + def test_unset_path_uses_bundled_footer(self): + settings = Settings() + self.assertIsNone(settings.demo_ui_custom_markdown_path) + self.assertEqual( + demo_content.resolve_demo_markdown(settings), + demo_content.default_footer, + ) + + def test_empty_path_raises_validation_error(self): + with self.assertRaises(ValidationError) as ctx: + Settings(demo_ui_custom_markdown_path="") + self.assertIn("APP_DEMO_UI_CUSTOM_MARKDOWN_PATH", str(ctx.exception)) + + def test_whitespace_path_raises_validation_error(self): + with self.assertRaises(ValidationError) as ctx: + Settings(demo_ui_custom_markdown_path=" ") + self.assertIn("APP_DEMO_UI_CUSTOM_MARKDOWN_PATH", str(ctx.exception)) + + def test_valid_file_returns_file_contents(self): + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".md", delete=False) as handle: + handle.write("# Custom Footer\n\nHello from mount.") + path = handle.name + + try: + settings = Settings(demo_ui_custom_markdown_path=path) + self.assertEqual( + demo_content.resolve_demo_markdown(settings), + "# Custom Footer\n\nHello from mount.", + ) + finally: + Path(path).unlink(missing_ok=True) + + def test_missing_file_raises_validation_error(self): + with self.assertRaises(ValidationError) as ctx: + Settings(demo_ui_custom_markdown_path="/tmp/medcat-demo-footer-missing.md") + self.assertIn("APP_DEMO_UI_CUSTOM_MARKDOWN_PATH", str(ctx.exception)) + + +if __name__ == "__main__": + unittest.main() From e84e950ea11b817fdf4874af9cf2d2b9789aaf77 Mon Sep 17 00:00:00 2001 From: alhendrickson Date: Tue, 28 Jul 2026 16:03:13 +0000 Subject: [PATCH 2/2] feat(medcat-service): Demo app supports custom footers for display --- .../medcat_service/demo/resources/default_footer.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/medcat-service/medcat_service/demo/resources/default_footer.md b/medcat-service/medcat_service/demo/resources/default_footer.md index 5f278c070..1f17ac974 100644 --- a/medcat-service/medcat_service/demo/resources/default_footer.md +++ b/medcat-service/medcat_service/demo/resources/default_footer.md @@ -5,9 +5,3 @@ MedCAT Service is part of the [CogStack](https://cogstack.org/) open-source NLP For support or enquiries, contact [contact@cogstack.org](mailto:contact@cogstack.org). Source code and documentation: [github.com/CogStack/cogstack-nlp](https://github.com/CogStack/cogstack-nlp). - -## Copyright and licence - -Copyright CogStack. MedCAT Service is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.