Cleaned up old reflex files/legacy code etc
This commit is contained in:
@@ -4,12 +4,13 @@ A web-based application for analyzing secondary care patient treatment pathways.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- **Desktop App**: Native window experience via pywebview (no browser needed)
|
||||||
- **Interactive Visualization**: Plotly icicle charts showing patient treatment hierarchies with cost and frequency statistics
|
- **Interactive Visualization**: Plotly icicle charts showing patient treatment hierarchies with cost and frequency statistics
|
||||||
- **Dual Chart Types**: Directory-based (Trust → Directorate → Drug → Pathway) and Indication-based (Trust → GP Diagnosis → Drug → Pathway) views
|
- **Dual Chart Types**: Directory-based (Trust → Directorate → Drug → Pathway) and Indication-based (Trust → GP Diagnosis → Drug → Pathway) views
|
||||||
- **Pre-computed Pathways**: Treatment pathways pre-processed and stored in SQLite for sub-50ms filter response times
|
- **Pre-computed Pathways**: Treatment pathways pre-processed and stored in SQLite for sub-50ms filter response times
|
||||||
- **GP Diagnosis Matching**: Patient indications matched from GP records using SNOMED cluster codes (~93% match rate)
|
- **GP Diagnosis Matching**: Patient indications matched from GP records using SNOMED cluster codes (~93% match rate)
|
||||||
- **Modern Web Interface**: Browser-based UI using Dash (Plotly) + Dash Mantine Components with NHS branding
|
- **Trend Analysis**: Historical trend views showing how drug usage and costs change over time
|
||||||
- **Drug Browser**: Drawer-based card browser organized by clinical directorate for drug/indication selection
|
- **Modern Web Interface**: Dash (Plotly) + Dash Mantine Components with NHS branding
|
||||||
- **Flexible Filtering**: Filter by date range, NHS trusts, drugs, and medical directories
|
- **Flexible Filtering**: Filter by date range, NHS trusts, drugs, and medical directories
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -29,20 +30,21 @@ cd patient-pathway-analysis
|
|||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
uv sync
|
uv sync
|
||||||
|
|
||||||
# One-time dev setup: adds src/ to Python path via .pth file
|
|
||||||
uv run python setup_dev.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Run the Web Application
|
### Run the Application
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Run as desktop app (recommended)
|
||||||
|
python app_desktop.py
|
||||||
|
|
||||||
|
# Run in browser (development)
|
||||||
python run_dash.py
|
python run_dash.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Open http://localhost:8050 in your browser.
|
The desktop app opens automatically in a native window. For browser mode, open http://localhost:8050.
|
||||||
|
|
||||||
The application loads pre-computed pathway data from SQLite on startup. No additional configuration is needed for viewing existing data.
|
The application loads pre-computed pathway data from SQLite on startup. No additional configuration is needed for viewing existing data.
|
||||||
|
|
||||||
@@ -65,6 +67,19 @@ python -m cli.refresh_pathways --chart-type indication
|
|||||||
python -m cli.refresh_pathways --chart-type all --dry-run -v
|
python -m cli.refresh_pathways --chart-type all --dry-run -v
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Compute Trends (for Trends view):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compute historical trend snapshots
|
||||||
|
python -m cli.compute_trends
|
||||||
|
|
||||||
|
# Custom date range
|
||||||
|
python -m cli.compute_trends --start 2022-01-01 --end 2025-06-30
|
||||||
|
|
||||||
|
# Help
|
||||||
|
python -m cli.compute_trends --help
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Interface Overview
|
### Interface Overview
|
||||||
@@ -73,21 +88,28 @@ The application has a single-page layout with:
|
|||||||
|
|
||||||
| Component | Purpose |
|
| Component | Purpose |
|
||||||
|-----------|---------|
|
|-----------|---------|
|
||||||
| **Header** | NHS branding, data freshness indicator (patient count + relative time) |
|
| **Header** | NHS branding, fraction KPIs (patients, drugs, cost), data freshness indicator |
|
||||||
| **Sidebar** | Navigation items with drawer triggers for Drug Selection, Trust Selection, Indications |
|
| **Sidebar** | Navigation: Patient Pathways, Trust Comparison, Trends |
|
||||||
| **KPI Row** | 4 cards: Unique Patients, Drug Types, Total Cost, Indication Match Rate |
|
| **Sub-Header** | Chart type toggle (By Directory / By Indication) + date filter dropdowns |
|
||||||
| **Filter Bar** | Chart type toggle (By Directory / By Indication) + date filter dropdowns |
|
| **Filter Bar** | Patient Pathways drug/trust/directorate filter buttons with modals |
|
||||||
| **Chart Card** | Interactive Plotly icicle chart with loading spinner |
|
| **Chart Card** | 9-tab chart area (Icicle, Sankey, Heatmap, Funnel, Depth, Scatter, Network, Timeline, Doses) |
|
||||||
| **Drawer** | Right-side panel with drug chips, trust chips, and directorate card browser |
|
| **Trust Comparison** | Per-directorate 6-chart dashboard comparing drugs across trusts |
|
||||||
|
| **Trends** | Historical trend analysis with directorate overview + drug drill-down |
|
||||||
|
|
||||||
### Filtering Data
|
### Filtering Data
|
||||||
|
|
||||||
1. **Chart Type**: Toggle between "By Directory" and "By Indication" views
|
The application has three analytical views:
|
||||||
2. **Date Filters**: Select treatment initiation period and last-seen window
|
|
||||||
3. **Drug Selection**: Open the drawer to select specific drugs via chips
|
1. **Patient Pathways**: Icicle chart + 8 additional analytics tabs with drug/trust/directorate filtering
|
||||||
4. **Trust Selection**: Open the drawer to filter by NHS trusts
|
2. **Trust Comparison**: Per-directorate analysis comparing drugs across trusts
|
||||||
5. **Directorate Browser**: Navigate directorates → indications → drug fragments in the drawer
|
3. **Trends**: Historical trend analysis showing directorate and drug-level changes over time
|
||||||
6. **Clear Filters**: Reset all selections to show full dataset
|
|
||||||
|
Common controls across all views:
|
||||||
|
|
||||||
|
- **Chart Type**: Toggle between "By Directory" and "By Indication" views
|
||||||
|
- **Date Filters**: Select treatment initiation period and last-seen window
|
||||||
|
- **Drug/Trust/Directorate Selection**: Open modals to filter by specific drugs, trusts, or directorates (Patient Pathways)
|
||||||
|
- **Clear Filters**: Reset all selections to show full dataset
|
||||||
|
|
||||||
### Understanding the Pathway Chart
|
### Understanding the Pathway Chart
|
||||||
|
|
||||||
@@ -121,22 +143,23 @@ Root (Regional Total)
|
|||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── src/ # All application library code
|
├── core/ # Foundation: paths, models, logging
|
||||||
│ ├── core/ # Foundation: paths, models, logging
|
├── config/ # Snowflake connection settings
|
||||||
│ ├── config/ # Snowflake connection settings
|
├── data_processing/ # Data layer (SQLite, Snowflake, transforms)
|
||||||
│ ├── data_processing/ # Data layer (SQLite, Snowflake, transforms)
|
├── analysis/ # Analysis pipeline
|
||||||
│ ├── analysis/ # Analysis pipeline
|
├── visualization/ # Plotly chart generation
|
||||||
│ ├── visualization/ # Plotly chart generation
|
├── cli/ # CLI tools (refresh_pathways, compute_trends)
|
||||||
│ └── cli/ # CLI tools (refresh_pathways)
|
|
||||||
├── dash_app/ # Dash web application
|
├── dash_app/ # Dash web application
|
||||||
│ ├── app.py # App entry point, layout, stores
|
│ ├── app.py # App entry point, layout, stores
|
||||||
│ ├── assets/nhs.css # NHS design system CSS
|
│ ├── assets/nhs.css # NHS design system CSS
|
||||||
│ ├── data/ # Query wrappers + card browser data
|
│ ├── data/ # Query wrappers + card browser data
|
||||||
│ ├── components/ # UI components (header, sidebar, etc.)
|
│ ├── components/ # UI components (header, sidebar, chart_card, trends, etc.)
|
||||||
│ └── callbacks/ # Dash callbacks (filters, chart, KPI, drawer)
|
│ └── callbacks/ # Dash callbacks (filters, chart, KPI, trends, etc.)
|
||||||
├── run_dash.py # Entry point: python run_dash.py
|
├── app_desktop.py # Desktop entry point (pywebview native window)
|
||||||
|
├── run_dash.py # Browser entry point
|
||||||
|
├── app.spec # PyInstaller packaging spec
|
||||||
├── data/ # Reference data + SQLite DB (pathways.db)
|
├── data/ # Reference data + SQLite DB (pathways.db)
|
||||||
├── tests/ # Test suite (113 tests)
|
├── tests/ # Test suite (114 tests)
|
||||||
├── docs/ # Documentation
|
├── docs/ # Documentation
|
||||||
└── archive/ # Historical/deprecated code
|
└── archive/ # Historical/deprecated code
|
||||||
```
|
```
|
||||||
@@ -149,7 +172,7 @@ See `CLAUDE.md` for detailed architecture documentation.
|
|||||||
# Run all tests
|
# Run all tests
|
||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
# Run with coverage
|
# Run with coverage (114 tests)
|
||||||
python -m pytest tests/ -v --cov=core --cov=data_processing --cov=analysis
|
python -m pytest tests/ -v --cov=core --cov=data_processing --cov=analysis
|
||||||
|
|
||||||
# Run only fast tests
|
# Run only fast tests
|
||||||
@@ -158,7 +181,16 @@ python -m pytest tests/ -v -m "not slow"
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Snowflake Connection (`src/config/snowflake.toml`)
|
### Desktop Packaging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build standalone executable (Windows)
|
||||||
|
pyinstaller app.spec
|
||||||
|
|
||||||
|
# Output: dist/NHS_Pathway_Analysis/NHS_Pathway_Analysis.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Snowflake Connection (`config/snowflake.toml`)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[snowflake]
|
[snowflake]
|
||||||
@@ -177,11 +209,11 @@ authenticator = "externalbrowser" # Required for NHS SSO
|
|||||||
# Ensure dependencies are installed
|
# Ensure dependencies are installed
|
||||||
uv sync
|
uv sync
|
||||||
|
|
||||||
# Ensure src/ is on Python path
|
# Try desktop mode
|
||||||
uv run python setup_dev.py
|
python app_desktop.py
|
||||||
|
|
||||||
# Try running with uv
|
# Or browser mode
|
||||||
uv run python run_dash.py
|
python run_dash.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database not found
|
### Database not found
|
||||||
@@ -193,7 +225,7 @@ python -m data_processing.migrate
|
|||||||
|
|
||||||
### Snowflake connection issues
|
### Snowflake connection issues
|
||||||
|
|
||||||
1. Ensure `src/config/snowflake.toml` has the correct account identifier
|
1. Ensure `config/snowflake.toml` has the correct account identifier
|
||||||
2. A browser window will open for SSO authentication
|
2. A browser window will open for SSO authentication
|
||||||
3. Verify your network allows Snowflake connections
|
3. Verify your network allows Snowflake connections
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ ROOT = os.path.abspath(os.path.dirname(SPECPATH)) if 'SPECPATH' in dir() else os
|
|||||||
|
|
||||||
a = Analysis(
|
a = Analysis(
|
||||||
['app_desktop.py'],
|
['app_desktop.py'],
|
||||||
pathex=[os.path.join(ROOT, 'src')],
|
pathex=[ROOT],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[
|
datas=[
|
||||||
('data/pathways.db', 'data'),
|
('data/pathways.db', 'data'),
|
||||||
@@ -39,7 +39,7 @@ a = Analysis(
|
|||||||
'pandas._libs.tslibs',
|
'pandas._libs.tslibs',
|
||||||
'numpy',
|
'numpy',
|
||||||
'sqlite3',
|
'sqlite3',
|
||||||
# App packages (src/ on pathex)
|
# App packages (project root on pathex)
|
||||||
'core',
|
'core',
|
||||||
'core.config',
|
'core.config',
|
||||||
'core.models',
|
'core.models',
|
||||||
|
|||||||
+4
-4
@@ -6,10 +6,10 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Ensure src/ is on sys.path so that core/, data_processing/, etc. are importable
|
# Ensure project root is on sys.path so that core/, data_processing/, etc. are importable
|
||||||
_src_dir = str(Path(__file__).resolve().parent / "src")
|
_project_root = str(Path(__file__).resolve().parent)
|
||||||
if _src_dir not in sys.path:
|
if _project_root not in sys.path:
|
||||||
sys.path.insert(0, _src_dir)
|
sys.path.insert(0, _project_root)
|
||||||
|
|
||||||
import webview
|
import webview
|
||||||
from dash_app.app import app
|
from dash_app.app import app
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from datetime import date, timedelta
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
# Ensure src/ is on sys.path when run as `python -m cli.compute_trends`
|
# Ensure project root is on sys.path when run as `python -m cli.compute_trends`
|
||||||
_src_dir = str(Path(__file__).resolve().parent.parent)
|
_src_dir = str(Path(__file__).resolve().parent.parent)
|
||||||
if _src_dir not in sys.path:
|
if _src_dir not in sys.path:
|
||||||
sys.path.insert(0, _src_dir)
|
sys.path.insert(0, _src_dir)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
# Ensure src/ is on sys.path when run as `python -m cli.refresh_pathways`
|
# Ensure project root is on sys.path when run as `python -m cli.refresh_pathways`
|
||||||
_src_dir = str(Path(__file__).resolve().parent.parent)
|
_src_dir = str(Path(__file__).resolve().parent.parent)
|
||||||
if _src_dir not in sys.path:
|
if _src_dir not in sys.path:
|
||||||
sys.path.insert(0, _src_dir)
|
sys.path.insert(0, _src_dir)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ def get_resource_path(relative_path: str) -> Path:
|
|||||||
"""Return absolute path to a bundled resource.
|
"""Return absolute path to a bundled resource.
|
||||||
|
|
||||||
In frozen mode (PyInstaller), resolves from sys._MEIPASS.
|
In frozen mode (PyInstaller), resolves from sys._MEIPASS.
|
||||||
In dev mode, resolves from the project root (3 parents up from this file:
|
In dev mode, resolves from the project root (2 parents up from this file:
|
||||||
src/core/resource_path.py → src/core → src → project root).
|
core/resource_path.py → core → project root).
|
||||||
"""
|
"""
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
base = Path(sys._MEIPASS)
|
base = Path(sys._MEIPASS)
|
||||||
else:
|
else:
|
||||||
base = Path(__file__).resolve().parents[2]
|
base = Path(__file__).resolve().parents[1]
|
||||||
return base / relative_path
|
return base / relative_path
|
||||||
|
|||||||
+2
-2
@@ -26,7 +26,7 @@ test = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
pythonpath = ["src"]
|
pythonpath = ["."]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
python_files = ["test_*.py"]
|
python_files = ["test_*.py"]
|
||||||
python_classes = ["Test*"]
|
python_classes = ["Test*"]
|
||||||
@@ -43,7 +43,7 @@ markers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = ["src/core", "src/data_processing", "src/analysis", "src/visualization"]
|
source = ["core", "data_processing", "analysis", "visualization"]
|
||||||
branch = true
|
branch = true
|
||||||
omit = [
|
omit = [
|
||||||
"*/tests/*",
|
"*/tests/*",
|
||||||
|
|||||||
Binary file not shown.
+4
-4
@@ -2,10 +2,10 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Ensure src/ is on sys.path so that core/, data_processing/, etc. are importable
|
# Ensure project root is on sys.path so that core/, data_processing/, etc. are importable
|
||||||
_src_dir = str(Path(__file__).resolve().parent / "src")
|
_project_root = str(Path(__file__).resolve().parent)
|
||||||
if _src_dir not in sys.path:
|
if _project_root not in sys.path:
|
||||||
sys.path.insert(0, _src_dir)
|
sys.path.insert(0, _project_root)
|
||||||
|
|
||||||
from dash_app.app import app
|
from dash_app.app import app
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
"""One-time dev setup: adds src/ to the venv's Python path via a .pth file."""
|
|
||||||
|
|
||||||
import site
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
src_dir = Path(__file__).resolve().parent / "src"
|
|
||||||
site_packages = Path(site.getsitepackages()[-1])
|
|
||||||
pth_file = site_packages / "patient_pathways.pth"
|
|
||||||
|
|
||||||
pth_file.write_text(str(src_dir) + "\n")
|
|
||||||
print(f"Created {pth_file}")
|
|
||||||
print(f" -> {src_dir}")
|
|
||||||
Reference in New Issue
Block a user