Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion medcat-service/docs/setup/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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/).
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.
30 changes: 30 additions & 0 deletions medcat-service/medcat_service/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from pathlib import Path
from typing import Any

import torch
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 12 additions & 2 deletions medcat-service/medcat_service/demo/demo_content.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import importlib.resources
from functools import cache
from pathlib import Path

from medcat_service.config import Settings


@cache
Expand All @@ -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")
4 changes: 2 additions & 2 deletions medcat-service/medcat_service/demo/gradio_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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


Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 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).
53 changes: 53 additions & 0 deletions medcat-service/medcat_service/test/demo/test_demo_content.py
Original file line number Diff line number Diff line change
@@ -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()