feat: add desktop packaging (pywebview + PyInstaller)

- resource_path.py: frozen/dev path resolution for bundled data files
- app_desktop.py: pywebview entry point (Dash in daemon thread)
- app.spec: PyInstaller onedir config with data files and hidden imports
- Updated queries.py, card_browser.py, app.py to use get_resource_path()
- Added pywebview + pyinstaller to project dependencies
- Fixed unresolved merge conflict in .gitignore
- Removed stale 01_nhs_classic.html and AdditionalAnalytics.md
This commit is contained in:
Andrew Charlwood
2026-02-09 14:53:22 +00:00
parent ee56595292
commit 7e63e6ea45
11 changed files with 491 additions and 720 deletions
-6
View File
@@ -36,16 +36,10 @@ logs/*.jsonl
.web/ .web/
.states/ .states/
<<<<<<< Updated upstream
# SQLite database (will contain local data)
#*.db
#*.sqlite
=======
# SQLite databases (except pathways.db which contains pre-computed data) # SQLite databases (except pathways.db which contains pre-computed data)
*.db *.db
!data/pathways.db !data/pathways.db
*.sqlite *.sqlite
>>>>>>> Stashed changes
# Snowflake result cache # Snowflake result cache
data/cache/ data/cache/
-551
View File
@@ -1,551 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HCD Analysis — NHS Classic</title>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@300;400;600;700;900&display=swap" rel="stylesheet">
<style>
:root {
--nhs-blue: #005EB8;
--nhs-dark-blue: #003087;
--nhs-light-blue: #41B6E6;
--nhs-white: #FFFFFF;
--nhs-pale-grey: #E8EDEE;
--nhs-mid-grey: #768692;
--nhs-dark-grey: #425563;
--nhs-green: #009639;
--nhs-yellow: #FFB81C;
--nhs-red: #DA291C;
--sidebar-w: 240px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Source Sans 3', Arial, sans-serif;
background: #F0F4F5;
color: var(--nhs-dark-grey);
line-height: 1.5;
min-height: 100vh;
}
/* ── Top Header ── */
.top-header {
position: fixed; top: 0; left: 0; right: 0; z-index: 200;
height: 56px;
background: var(--nhs-dark-blue);
display: flex; align-items: center; justify-content: space-between;
padding: 0 24px 0 0;
}
.top-header__brand {
display: flex; align-items: center; gap: 16px;
height: 100%; padding: 0 24px;
background: var(--nhs-blue);
clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 100%, 0 100%);
padding-right: 40px;
}
.top-header__logo {
width: 40px; height: 40px;
background: var(--nhs-white);
border-radius: 4px;
display: grid; place-items: center;
font-weight: 900; font-size: 11px; color: var(--nhs-blue);
letter-spacing: 0.5px;
line-height: 1;
}
.top-header__title {
color: var(--nhs-white);
font-size: 20px; font-weight: 700;
letter-spacing: -0.01em;
}
.top-header__breadcrumb {
color: rgba(255,255,255,0.7);
font-size: 14px; font-weight: 400;
}
.top-header__breadcrumb strong { color: var(--nhs-white); font-weight: 600; }
.top-header__right {
display: flex; align-items: center; gap: 20px; color: rgba(255,255,255,0.8); font-size: 14px;
}
.top-header__right .status-dot {
width: 8px; height: 8px; border-radius: 50%; background: var(--nhs-green);
display: inline-block; margin-right: 4px;
}
/* ── Sidebar ── */
.sidebar {
position: fixed; top: 56px; left: 0; bottom: 0;
width: var(--sidebar-w);
background: var(--nhs-white);
border-right: 1px solid var(--nhs-pale-grey);
overflow-y: auto; z-index: 100;
display: flex; flex-direction: column;
}
.sidebar__section { padding: 16px 0; }
.sidebar__section + .sidebar__section { border-top: 1px solid var(--nhs-pale-grey); }
.sidebar__label {
padding: 0 20px 8px;
font-size: 11px; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.08em;
color: var(--nhs-mid-grey);
}
.sidebar__item {
display: flex; align-items: center; gap: 12px;
padding: 10px 20px;
font-size: 15px; font-weight: 400;
color: var(--nhs-dark-grey);
text-decoration: none;
border-left: 4px solid transparent;
transition: background 0.15s, border-color 0.15s;
cursor: pointer;
}
.sidebar__item:hover { background: #F0F4F5; }
.sidebar__item--active {
background: #E8F0FE;
border-left-color: var(--nhs-blue);
color: var(--nhs-blue);
font-weight: 600;
}
.sidebar__item svg { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar__footer {
margin-top: auto; padding: 16px 20px;
border-top: 1px solid var(--nhs-pale-grey);
font-size: 12px; color: var(--nhs-mid-grey);
}
/* ── Main Content ── */
.main {
margin-left: var(--sidebar-w);
margin-top: 56px;
padding: 24px;
min-height: calc(100vh - 56px);
display: flex; flex-direction: column; gap: 20px;
}
/* ── KPI Row ── */
.kpi-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.kpi-card {
background: var(--nhs-white);
border: 1px solid var(--nhs-pale-grey);
border-top: 4px solid var(--nhs-blue);
padding: 20px;
display: flex; flex-direction: column; gap: 2px;
}
.kpi-card--green { border-top-color: var(--nhs-green); }
.kpi-card__label {
font-size: 12px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.05em;
color: var(--nhs-mid-grey);
}
.kpi-card__value {
font-size: 32px; font-weight: 300;
color: var(--nhs-dark-blue);
line-height: 1.1;
font-variant-numeric: tabular-nums;
}
.kpi-card__sub {
font-size: 13px; color: var(--nhs-mid-grey); margin-top: 4px;
}
/* ── Filter Bar ── */
.filter-bar {
background: var(--nhs-white);
border: 1px solid var(--nhs-pale-grey);
padding: 12px 20px;
display: flex; align-items: center; gap: 16px;
flex-wrap: wrap;
}
.filter-bar__group {
display: flex; align-items: center; gap: 8px;
}
.filter-bar__divider {
width: 1px; height: 28px; background: var(--nhs-pale-grey);
}
.filter-bar__label {
font-size: 12px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.04em;
color: var(--nhs-mid-grey);
white-space: nowrap;
}
/* Toggle pills */
.toggle-pills {
display: flex; border: 1px solid var(--nhs-pale-grey); overflow: hidden;
}
.toggle-pill {
padding: 6px 16px;
font-size: 14px; font-weight: 600;
color: var(--nhs-dark-grey);
background: var(--nhs-white);
cursor: pointer;
border: none; outline: none;
transition: background 0.15s, color 0.15s;
}
.toggle-pill + .toggle-pill { border-left: 1px solid var(--nhs-pale-grey); }
.toggle-pill--active {
background: var(--nhs-blue);
color: var(--nhs-white);
}
.toggle-pill:hover:not(.toggle-pill--active) { background: #F0F4F5; }
.toggle-pill:focus-visible { box-shadow: 0 0 0 3px var(--nhs-yellow); z-index: 1; }
/* Selects */
.filter-select {
height: 34px; padding: 0 12px;
border: 1px solid var(--nhs-pale-grey);
font-family: inherit; font-size: 14px;
color: var(--nhs-dark-grey);
background: var(--nhs-white);
cursor: pointer;
min-width: 140px;
}
.filter-select:focus { outline: 3px solid var(--nhs-yellow); outline-offset: 0; }
/* ── Chart Area ── */
.chart-card {
background: var(--nhs-white);
border: 1px solid var(--nhs-pale-grey);
flex: 1;
display: flex; flex-direction: column;
}
.chart-card__header {
padding: 16px 20px;
border-bottom: 1px solid var(--nhs-pale-grey);
display: flex; justify-content: space-between; align-items: baseline;
}
.chart-card__title {
font-size: 18px; font-weight: 700;
color: var(--nhs-dark-blue);
}
.chart-card__subtitle {
font-size: 13px; color: var(--nhs-mid-grey);
}
.chart-card__tabs {
display: flex; gap: 0;
border-bottom: 1px solid var(--nhs-pale-grey);
}
.chart-tab {
padding: 10px 24px;
font-size: 14px; font-weight: 600;
color: var(--nhs-mid-grey);
border: none; background: none; cursor: pointer;
border-bottom: 3px solid transparent;
margin-bottom: -1px;
transition: color 0.15s, border-color 0.15s;
}
.chart-tab--active {
color: var(--nhs-blue);
border-bottom-color: var(--nhs-blue);
}
.chart-tab:hover:not(.chart-tab--active) { color: var(--nhs-dark-grey); }
.chart-tab:focus-visible { box-shadow: inset 0 0 0 3px var(--nhs-yellow); }
/* ── Icicle Chart Mock ── */
.icicle {
flex: 1; padding: 20px;
display: flex; flex-direction: column; gap: 3px;
min-height: 420px;
}
.icicle__row {
display: flex; gap: 3px;
flex: 1;
}
.icicle__cell {
display: flex; align-items: center; justify-content: center;
padding: 8px 12px;
color: var(--nhs-white);
font-size: 13px; font-weight: 600;
text-align: center;
position: relative;
overflow: hidden;
transition: filter 0.2s, transform 0.15s;
cursor: pointer;
line-height: 1.2;
}
.icicle__cell:hover { filter: brightness(1.15); transform: scaleY(1.03); z-index: 2; }
.icicle__cell span { position: relative; z-index: 1; text-shadow: 0 1px 2px rgba(0,0,0,0.3); }
.icicle__cell small {
display: block; font-weight: 400; font-size: 11px; opacity: 0.85;
}
/* Level colors */
.lvl-0 { background: var(--nhs-dark-blue); }
.lvl-1a { background: #005EB8; }
.lvl-1b { background: #0072CE; }
.lvl-1c { background: #41B6E6; }
.lvl-1d { background: #768692; }
.lvl-2a { background: #003D7A; }
.lvl-2b { background: #004F9F; }
.lvl-2c { background: #0066B8; }
.lvl-2d { background: #007CC2; }
.lvl-2e { background: #2D9CDB; }
.lvl-2f { background: #5DADE2; }
.lvl-3a { background: #003060; }
.lvl-3b { background: #003D78; }
.lvl-3c { background: #004A90; }
.lvl-3d { background: #0057A8; }
.lvl-3e { background: #0064C0; }
.lvl-3f { background: #2080D0; }
.lvl-3g { background: #4098D8; }
.lvl-3h { background: #60B0E0; }
/* ── Footer ── */
.page-footer {
background: var(--nhs-pale-grey);
border-top: 1px solid #D0D5D6;
padding: 16px 20px;
font-size: 13px; color: var(--nhs-mid-grey);
text-align: center;
}
/* ── Responsive ── */
@media (max-width: 1024px) {
.kpi-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.sidebar { display: none; }
.main { margin-left: 0; }
.kpi-row { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- Top Header -->
<header class="top-header">
<div class="top-header__brand">
<div class="top-header__logo">NHS</div>
<div>
<div class="top-header__title">HCD Analysis</div>
</div>
</div>
<div class="top-header__breadcrumb">
Dashboard &rsaquo; <strong>Pathway Analysis</strong>
</div>
<div class="top-header__right">
<span><span class="status-dot"></span> 656,247 records</span>
<span>Last updated: 2h ago</span>
</div>
</header>
<!-- Sidebar -->
<nav class="sidebar" aria-label="Main navigation">
<div class="sidebar__section">
<div class="sidebar__label">Analysis</div>
<a class="sidebar__item sidebar__item--active" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
Pathway Overview
</a>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
Drug Selection
</a>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>
Trust Selection
</a>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
Directory Selection
</a>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/></svg>
Indications
</a>
</div>
<div class="sidebar__section">
<div class="sidebar__label">Reports</div>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
Cost Analysis
</a>
<a class="sidebar__item" href="#">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Export Data
</a>
</div>
<div class="sidebar__footer">
NHS Norfolk &amp; Waveney ICB<br>High Cost Drugs Programme
</div>
</nav>
<!-- Main Content -->
<main class="main">
<!-- KPIs -->
<section class="kpi-row" aria-label="Key performance indicators">
<div class="kpi-card">
<div class="kpi-card__label">Unique Patients</div>
<div class="kpi-card__value">37,842</div>
<div class="kpi-card__sub">across all trusts</div>
</div>
<div class="kpi-card">
<div class="kpi-card__label">Drug Types</div>
<div class="kpi-card__value">89</div>
<div class="kpi-card__sub">high-cost drugs tracked</div>
</div>
<div class="kpi-card">
<div class="kpi-card__label">Total Cost</div>
<div class="kpi-card__value">&pound;145.2M</div>
<div class="kpi-card__sub">current period spend</div>
</div>
<div class="kpi-card kpi-card--green">
<div class="kpi-card__label">Indication Match</div>
<div class="kpi-card__value">93%</div>
<div class="kpi-card__sub">GP diagnosis confirmed</div>
</div>
</section>
<!-- Filter Bar -->
<section class="filter-bar" aria-label="Filters">
<div class="filter-bar__group">
<span class="filter-bar__label">View</span>
<div class="toggle-pills" role="radiogroup" aria-label="Chart view type">
<button class="toggle-pill toggle-pill--active" role="radio" aria-checked="true">By Directory</button>
<button class="toggle-pill" role="radio" aria-checked="false">By Indication</button>
</div>
</div>
<div class="filter-bar__divider"></div>
<div class="filter-bar__group">
<span class="filter-bar__label">Initiated</span>
<select class="filter-select" aria-label="Treatment initiated period">
<option selected>All years</option>
<option>Last 2 years</option>
<option>Last 1 year</option>
</select>
</div>
<div class="filter-bar__group">
<span class="filter-bar__label">Last seen</span>
<select class="filter-select" aria-label="Last seen period">
<option selected>Last 6 months</option>
<option>Last 12 months</option>
</select>
</div>
<div class="filter-bar__divider"></div>
<div class="filter-bar__group">
<span class="filter-bar__label">Drugs</span>
<select class="filter-select" aria-label="Drug filter">
<option>All 89 drugs</option>
</select>
</div>
<div class="filter-bar__group">
<span class="filter-bar__label">Directorates</span>
<select class="filter-select" aria-label="Directorate filter">
<option>All 14 directorates</option>
</select>
</div>
</section>
<!-- Chart Card -->
<section class="chart-card" aria-label="Patient pathway chart">
<div class="chart-card__header">
<div>
<div class="chart-card__title">Patient Pathway Visualization</div>
<div class="chart-card__subtitle">Trust &rarr; Directorate &rarr; Drug &rarr; Patient Pathway</div>
</div>
</div>
<div class="chart-card__tabs" role="tablist">
<button class="chart-tab chart-tab--active" role="tab" aria-selected="true">Icicle</button>
<button class="chart-tab" role="tab" aria-selected="false">Sankey</button>
<button class="chart-tab" role="tab" aria-selected="false">Timeline</button>
</div>
<!-- Icicle Chart -->
<div class="icicle" role="img" aria-label="Icicle chart showing patient treatment pathways">
<!-- Level 0: Root -->
<div class="icicle__row" style="flex: 0.8;">
<div class="icicle__cell lvl-0" style="flex: 1;">
<span>NHS Norfolk and Waveney ICB<small>37,842 patients &middot; &pound;145.2M</small></span>
</div>
</div>
<!-- Level 1: Directorates -->
<div class="icicle__row" style="flex: 1;">
<div class="icicle__cell lvl-1a" style="flex: 40;">
<span>Rheumatology<small>15,137 &middot; 40%</small></span>
</div>
<div class="icicle__cell lvl-1b" style="flex: 28;">
<span>Dermatology<small>10,596 &middot; 28%</small></span>
</div>
<div class="icicle__cell lvl-1c" style="flex: 18;">
<span>Gastroenterology<small>6,812 &middot; 18%</small></span>
</div>
<div class="icicle__cell lvl-1d" style="flex: 14;">
<span>Oncology<small>5,297 &middot; 14%</small></span>
</div>
</div>
<!-- Level 2: Drugs -->
<div class="icicle__row" style="flex: 1;">
<!-- Under Rheumatology -->
<div class="icicle__cell lvl-2a" style="flex: 22;"><span>ADALIMUMAB<small>8,325</small></span></div>
<div class="icicle__cell lvl-2b" style="flex: 12;"><span>ETANERCEPT<small>4,541</small></span></div>
<div class="icicle__cell lvl-2c" style="flex: 6;"><span>TOCILIZUMAB<small>2,271</small></span></div>
<!-- Under Dermatology -->
<div class="icicle__cell lvl-2d" style="flex: 16;"><span>SECUKINUMAB<small>6,057</small></span></div>
<div class="icicle__cell lvl-2e" style="flex: 12;"><span>DUPILUMAB<small>4,539</small></span></div>
<!-- Under Gastro -->
<div class="icicle__cell lvl-2c" style="flex: 10;"><span>INFLIXIMAB<small>3,787</small></span></div>
<div class="icicle__cell lvl-2a" style="flex: 8;"><span>VEDOLIZUMAB<small>3,025</small></span></div>
<!-- Under Oncology -->
<div class="icicle__cell lvl-2f" style="flex: 8;"><span>PEMBROLIZUMAB<small>3,025</small></span></div>
<div class="icicle__cell lvl-2b" style="flex: 6;"><span>NIVOLUMAB<small>2,272</small></span></div>
</div>
<!-- Level 3: Pathways -->
<div class="icicle__row" style="flex: 1.2;">
<div class="icicle__cell lvl-3a" style="flex: 14;"><span>ADA&rarr;ADA<small>5,310</small></span></div>
<div class="icicle__cell lvl-3b" style="flex: 8;"><span>ADA&rarr;ETA&rarr;SEC<small>3,015</small></span></div>
<div class="icicle__cell lvl-3c" style="flex: 7;"><span>ETA&rarr;ETA<small>2,648</small></span></div>
<div class="icicle__cell lvl-3d" style="flex: 5;"><span>ETA&rarr;ADA<small>1,893</small></span></div>
<div class="icicle__cell lvl-3e" style="flex: 6;"><span>TOC&rarr;TOC<small>2,271</small></span></div>
<div class="icicle__cell lvl-3f" style="flex: 10;"><span>SEC&rarr;SEC<small>3,785</small></span></div>
<div class="icicle__cell lvl-3a" style="flex: 6;"><span>SEC&rarr;DUP<small>2,272</small></span></div>
<div class="icicle__cell lvl-3g" style="flex: 6;"><span>DUP&rarr;DUP<small>2,267</small></span></div>
<div class="icicle__cell lvl-3h" style="flex: 6;"><span>DUP&rarr;SEC<small>2,272</small></span></div>
<div class="icicle__cell lvl-3c" style="flex: 6;"><span>INF&rarr;INF<small>2,272</small></span></div>
<div class="icicle__cell lvl-3b" style="flex: 4;"><span>INF&rarr;VED<small>1,515</small></span></div>
<div class="icicle__cell lvl-3d" style="flex: 5;"><span>VED&rarr;VED<small>1,893</small></span></div>
<div class="icicle__cell lvl-3e" style="flex: 3;"><span>VED&rarr;INF<small>1,132</small></span></div>
<div class="icicle__cell lvl-3f" style="flex: 5;"><span>PEM&rarr;PEM<small>1,893</small></span></div>
<div class="icicle__cell lvl-3g" style="flex: 3;"><span>PEM&rarr;NIV<small>1,132</small></span></div>
<div class="icicle__cell lvl-3h" style="flex: 4;"><span>NIV&rarr;NIV<small>1,514</small></span></div>
<div class="icicle__cell lvl-3a" style="flex: 2;"><span>NIV&rarr;PEM<small>758</small></span></div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="page-footer">
NHS Norfolk and Waveney ICB &mdash; High Cost Drugs Analysis &middot; Data as of Feb 2026
</footer>
<script>
// Toggle pills interaction
document.querySelectorAll('.toggle-pills').forEach(group => {
group.querySelectorAll('.toggle-pill').forEach(pill => {
pill.addEventListener('click', () => {
group.querySelectorAll('.toggle-pill').forEach(p => {
p.classList.remove('toggle-pill--active');
p.setAttribute('aria-checked', 'false');
});
pill.classList.add('toggle-pill--active');
pill.setAttribute('aria-checked', 'true');
});
});
});
// Chart tabs interaction
document.querySelectorAll('.chart-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.chart-tab').forEach(t => {
t.classList.remove('chart-tab--active');
t.setAttribute('aria-selected', 'false');
});
tab.classList.add('chart-tab--active');
tab.setAttribute('aria-selected', 'true');
});
});
</script>
</body>
</html>
-154
View File
@@ -1,154 +0,0 @@
# Additional Analytics Charts — Implementation Plan
## UI Approach: Tabbed Chart Area
Extend existing `chart_card.py` tab bar. Currently has Icicle (active), Sankey (disabled), Timeline (disabled). Replace/extend with new tabs.
## Charts to Build (Priority Order)
### Tab 1: Icicle (existing — no change)
### Tab 2: First-Line Market Share — Horizontal Bar Chart
**What**: % of patients starting on each first-line drug, grouped by directorate or indication
**Data source**: `pathway_nodes WHERE level = 3` (drug level). The `colour` column already holds proportion of parent. `value` = patient count.
**Query**: Filter by `chart_type`, `date_filter_id`, optionally `directory` or `trust_name`. Group by `directory`, then show drugs as bars.
**Viz**: Horizontal grouped bar chart. One cluster per directorate/indication (top N), bars within = drugs, length = % of patients. Sorted by total patients desc. NHS blue palette.
**Interaction**: Responds to all existing filters (date, chart type, trust, drug, directorate). Clicking a directorate cluster could filter the icicle.
### Tab 3: Pathway Cost Effectiveness — Lollipop/Dot Plot
**What**: Compare annualized cost per patient across complete treatment pathways within a directorate/indication. Highlights most vs least cost-effective pathways.
**Data source**: `pathway_nodes WHERE level >= 4` (pathway nodes). Fields: `cost_pp_pa` (annualized), `value` (patient count), `ids` (parse to get pathway sequence), `directory`.
**Calculation**: `cost_pp_pa` is already computed as `(total_cost / patients) * (365 / avg_days)` — this IS the "total cost over N years / N years" the user described.
**Query**: Filter to a specific directorate/indication, then show all pathway variants ranked by `cost_pp_pa`.
**Viz**: Horizontal lollipop chart (dot on stick). Y-axis = pathway label (e.g., "Adalimumab → Secukinumab → Rituximab"), X-axis = £ per patient per annum. Dot size = patient count. Colour gradient: green (cheap) → amber → red (expensive).
**Interaction**: Directorate/indication selector drives which pathways are shown. Could also compare across directorates at the drug level (level 3).
**Bonus metric — "Pathway Retention" (fewest switches)**:
- For each 2nd-line pathway (e.g., "Drug A → Drug B"), calculate what % of patients escalate to a 3rd line
- Derivation: `value("Drug A → Drug B") - SUM(value("Drug A → Drug B → *"))` = patients who stayed on 2nd line
- Show as a secondary annotation or companion chart: "Drug B retains 72% of patients (no 3rd-line switch needed)"
- This identifies the most effective 2nd-line choices
### Tab 4: Cost Waterfall — Waterfall Chart
**What**: Break down £ per patient per annum by directorate, showing relative cost contribution
**Data source**: `pathway_nodes WHERE level = 2` (directorate/indication level). Field: `cost_pp_pa`, `value`.
**Viz**: Plotly waterfall chart. Each bar = one directorate's average cost_pp_pa. Sorted highest to lowest. Running reference line optional. Use NHS colours.
**Note**: User specifically wants cost_pp_pa (annualized), not total cost.
**Interaction**: Responds to chart_type toggle, date filter, trust filter.
### Tab 5: Drug Switching Sankey — Sankey Diagram
**What**: Flow of patients from 1st-line → 2nd-line → 3rd-line drugs
**Data source**: `pathway_nodes WHERE level >= 3`. Parse `ids` to extract drug transition sequences.
**Parsing**: `ids` format at level 4+: `"TRUST - DIRECTORY - DRUG_A - DRUG_A|DRUG_B"`. Split by " - ", take segments from level 3 onwards, split by "|" to get ordered drug list.
**Viz**: Plotly Sankey. Left nodes = 1st-line drugs, middle = 2nd-line, right = 3rd-line. Link width = patient count. Colour by drug or by directorate.
**Interaction**: Filter by directorate/indication to see switching within a specialty. Filter by trust to compare switching patterns.
### Tab 6: Dosing Interval Comparison — Grouped Bar Chart
**What**: Compare average dosing frequency/weekly interval for a drug across trusts or directorates
**Data source**: Level 3+ nodes, `average_spacing` (HTML string), `average_administered` (JSON array)
**Parsing needed**:
- `average_spacing`: regex to extract weekly interval number from `"given X times with Y weekly interval"`
- `average_administered`: `json.loads()` to get dose counts
**Viz**: Horizontal grouped bars. Y-axis = trust or directorate, X-axis = weekly interval (or total administrations). One colour per drug if comparing multiple.
**Interaction**: Drug selector to pick which drug to compare. Group-by selector (trust vs directorate).
### Tab 7: Directorate × Drug Heatmap
**What**: Matrix showing which drugs are used in which directorates, cells coloured by patient count or cost_pp_pa
**Data source**: Level 3 nodes, pivot `directory` × drug (parsed from `labels` or `ids`)
**Viz**: Plotly heatmap. Rows = directorates (sorted by total patients), columns = drugs (sorted by frequency). Cell colour = patient count or cost. Hover shows details.
**Interaction**: Toggle between patient count / cost / cost_pp_pa colouring.
### Tab 8: Treatment Duration Bars
**What**: Compare average treatment durations across drugs within a directorate
**Data source**: Level 3 nodes, `avg_days` field
**Viz**: Horizontal bar chart. Y-axis = drug, X-axis = average days. Colour intensity by patient count.
**Interaction**: Directorate filter drives which drugs are shown.
---
## Data Layer Changes
### New query functions needed (in `src/data_processing/pathway_queries.py`):
```python
def get_drug_market_share(db_path, date_filter_id, chart_type, directory=None, trust=None):
"""Level 3 nodes grouped by directory, returning drug, value, colour."""
def get_pathway_costs(db_path, date_filter_id, chart_type, directory=None):
"""Level 4+ nodes with cost_pp_pa, parsed pathway labels, patient counts."""
def get_cost_waterfall(db_path, date_filter_id, chart_type, trust=None):
"""Level 2 nodes with cost_pp_pa per directorate/indication."""
def get_drug_transitions(db_path, date_filter_id, chart_type, directory=None):
"""Level 3+ nodes parsed into source→target drug transitions with patient counts."""
def get_dosing_intervals(db_path, date_filter_id, chart_type, drug=None):
"""Level 3 nodes for a specific drug, parsed average_spacing by trust/directory."""
def get_drug_directory_matrix(db_path, date_filter_id, chart_type):
"""Level 3 nodes pivoted as directory × drug with value/cost metrics."""
def get_treatment_durations(db_path, date_filter_id, chart_type, directory=None):
"""Level 3 nodes with avg_days by drug within a directorate."""
```
### Parsing utilities needed:
```python
def parse_average_spacing(spacing_html: str) -> dict:
"""Extract drug_name, dose_count, weekly_interval, total_weeks from HTML string."""
def parse_pathway_drugs(ids: str, level: int) -> list[str]:
"""Extract ordered drug list from ids column at level 4+."""
def calculate_retention_rate(nodes: list[dict]) -> dict:
"""For each N-drug pathway, calculate % not escalating to N+1 drugs."""
```
---
## Callback Architecture
Each tab gets its own callback triggered by `chart-data` store + `active-tab` state:
```
active-tab change → render selected chart
chart-data change → re-render active chart
```
Only the active tab's chart is computed (lazy rendering). Store the active tab in `app-state`.
New callback per chart type in `dash_app/callbacks/`:
- `market_share.py` — builds bar chart from level 3 data
- `pathway_costs.py` — builds lollipop + retention annotations
- `cost_waterfall.py` — builds waterfall from level 2 data
- `sankey.py` — builds Sankey from parsed transitions
- `dosing.py` — builds grouped bars from parsed spacing
- `heatmap.py` — builds heatmap from pivoted matrix
- `duration.py` — builds bar chart from avg_days
---
## Implementation Order
1. **Data parsing utilities** — shared parsing for spacing, pathway drugs, retention
2. **Query functions** — one per chart type in pathway_queries.py
3. **Tab infrastructure** — extend chart_card.py with all tab labels, lazy rendering
4. **Charts one at a time** (in priority order):
- First-Line Market Share (simplest, validates the tab pattern)
- Pathway Cost Effectiveness + Retention (user's key insight)
- Cost Waterfall
- Drug Switching Sankey
- Dosing Interval
- Heatmap
- Treatment Duration
---
## Verification
- Run `python run_dash.py` after each chart addition
- Verify each chart responds to filter changes (date, chart type, trust, directorate, drug)
- Test with both "directory" and "indication" chart types
- Verify icicle chart still works correctly (no regressions)
- Check tab switching is smooth with no unnecessary recomputation
+137
View File
@@ -0,0 +1,137 @@
# -*- mode: python ; coding: utf-8 -*-
"""PyInstaller spec for NHS Pathway Analysis desktop app."""
import os
block_cipher = None
# Project root (where this spec file lives)
ROOT = os.path.abspath(os.path.dirname(SPECPATH)) if 'SPECPATH' in dir() else os.path.abspath('.')
a = Analysis(
['app_desktop.py'],
pathex=[os.path.join(ROOT, 'src')],
binaries=[],
datas=[
('data/pathways.db', 'data'),
('data/DimSearchTerm.csv', 'data'),
('data/defaultTrusts.csv', 'data'),
('dash_app/assets/nhs.css', 'dash_app/assets'),
],
hiddenimports=[
# Dash internals
'dash',
'dash.dash',
'dash.dcc',
'dash.html',
'dash.exceptions',
'dash._utils',
'dash.dependencies',
'dash_mantine_components',
# Plotly
'plotly',
'plotly.graph_objects',
'plotly.io',
'plotly.express',
'plotly.subplots',
# Data
'pandas',
'pandas._libs.tslibs',
'numpy',
'sqlite3',
# App packages (src/ on pathex)
'core',
'core.config',
'core.models',
'core.logging_config',
'core.resource_path',
'data_processing',
'data_processing.database',
'data_processing.schema',
'data_processing.pathway_queries',
'data_processing.parsing',
'data_processing.diagnosis_lookup',
'analysis',
'analysis.pathway_analyzer',
'analysis.statistics',
'visualization',
'visualization.plotly_generator',
# Dash app
'dash_app',
'dash_app.app',
'dash_app.data',
'dash_app.data.queries',
'dash_app.data.card_browser',
'dash_app.callbacks',
'dash_app.callbacks.filters',
'dash_app.callbacks.chart',
'dash_app.callbacks.modals',
'dash_app.callbacks.navigation',
'dash_app.callbacks.trust_comparison',
'dash_app.callbacks.kpi',
'dash_app.callbacks.trends',
'dash_app.components',
'dash_app.components.header',
'dash_app.components.sub_header',
'dash_app.components.sidebar',
'dash_app.components.filter_bar',
'dash_app.components.chart_card',
'dash_app.components.footer',
'dash_app.components.modals',
'dash_app.components.trust_comparison',
'dash_app.components.trends',
# pywebview backend
'webview',
'clr',
'pythonnet',
# Flask (Dash's server)
'flask',
'flask.json',
'flask.json.provider',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
'snowflake',
'snowflake.connector',
'cli',
'pytest',
'tests',
'tkinter',
'matplotlib',
'IPython',
'jupyter',
],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='NHS_Pathway_Analysis',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False, # --windowed (no console)
icon=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='NHS_Pathway_Analysis',
)
+62
View File
@@ -0,0 +1,62 @@
"""Desktop entry point: Dash app inside a pywebview native window."""
import sys
import socket
import threading
import time
from pathlib import Path
# Ensure src/ is on sys.path so that core/, data_processing/, etc. are importable
_src_dir = str(Path(__file__).resolve().parent / "src")
if _src_dir not in sys.path:
sys.path.insert(0, _src_dir)
import webview
from dash_app.app import app
def find_free_port(start: int = 8050) -> int:
"""Find the first available port starting from *start*."""
for port in range(start, start + 100):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("127.0.0.1", port))
return port
except OSError:
continue
raise RuntimeError("No free port found")
def wait_for_server(port: int, timeout: float = 30.0) -> None:
"""Block until the Dash server accepts connections."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
if s.connect_ex(("127.0.0.1", port)) == 0:
return
time.sleep(0.1)
raise TimeoutError(f"Server did not start within {timeout}s")
def main() -> None:
port = find_free_port()
server_thread = threading.Thread(
target=app.run,
kwargs={"debug": False, "port": port, "use_reloader": False},
daemon=True,
)
server_thread.start()
wait_for_server(port)
webview.create_window(
"NHS Pathway Analysis",
f"http://127.0.0.1:{port}",
width=1400,
height=900,
)
webview.start()
if __name__ == "__main__":
main()
+8 -4
View File
@@ -1,7 +1,10 @@
"""Dash application entry point with layout root and state stores.""" """Dash application entry point with layout root and state stores."""
import sys
from dash import Dash, html, dcc from dash import Dash, html, dcc
import dash_mantine_components as dmc import dash_mantine_components as dmc
from core.resource_path import get_resource_path
from dash_app.components.header import make_header from dash_app.components.header import make_header
from dash_app.components.sub_header import make_sub_header from dash_app.components.sub_header import make_sub_header
from dash_app.components.sidebar import make_sidebar from dash_app.components.sidebar import make_sidebar
@@ -12,10 +15,11 @@ from dash_app.components.modals import make_modals
from dash_app.components.trust_comparison import make_tc_landing, make_tc_dashboard from dash_app.components.trust_comparison import make_tc_landing, make_tc_dashboard
from dash_app.components.trends import make_trends_landing, make_trends_detail from dash_app.components.trends import make_trends_landing, make_trends_detail
app = Dash( _app_kwargs = {"suppress_callback_exceptions": True}
__name__, if getattr(sys, "frozen", False):
suppress_callback_exceptions=True, _app_kwargs["assets_folder"] = str(get_resource_path("dash_app/assets"))
)
app = Dash(__name__, **_app_kwargs)
app.layout = dmc.MantineProvider( app.layout = dmc.MantineProvider(
children=[ children=[
+2 -3
View File
@@ -9,12 +9,11 @@ Also provides get_all_drugs() for the flat "All Drugs" card.
import csv import csv
from collections import defaultdict from collections import defaultdict
from pathlib import Path
from core.resource_path import get_resource_path
from data_processing.diagnosis_lookup import SEARCH_TERM_MERGE_MAP from data_processing.diagnosis_lookup import SEARCH_TERM_MERGE_MAP
DATA_DIR = Path(__file__).resolve().parents[2] / "data" DIM_SEARCH_TERM_PATH = get_resource_path("data/DimSearchTerm.csv")
DIM_SEARCH_TERM_PATH = DATA_DIR / "DimSearchTerm.csv"
def build_directorate_tree() -> dict[str, dict[str, list[str]]]: def build_directorate_tree() -> dict[str, dict[str, list[str]]]:
+2 -2
View File
@@ -5,9 +5,9 @@ Resolves the database path relative to this file's location and delegates
to the shared functions in src/data_processing/pathway_queries.py. to the shared functions in src/data_processing/pathway_queries.py.
""" """
from pathlib import Path
from typing import Optional from typing import Optional
from core.resource_path import get_resource_path
from data_processing.pathway_queries import ( from data_processing.pathway_queries import (
load_initial_data as _load_initial_data, load_initial_data as _load_initial_data,
load_pathway_nodes as _load_pathway_nodes, load_pathway_nodes as _load_pathway_nodes,
@@ -33,7 +33,7 @@ from data_processing.pathway_queries import (
get_trend_data as _get_trend_data, get_trend_data as _get_trend_data,
) )
DB_PATH = Path(__file__).resolve().parents[2] / "data" / "pathways.db" DB_PATH = get_resource_path("data/pathways.db")
def load_initial_data() -> dict: def load_initial_data() -> dict:
+2
View File
@@ -13,6 +13,8 @@ dependencies = [
"pillow>=10.0.0", "pillow>=10.0.0",
"plotly>=5.15.0", "plotly>=5.15.0",
"pyarrow>=20.0.0", "pyarrow>=20.0.0",
"pyinstaller>=6.18.0",
"pywebview>=6.1",
"snowflake-connector-python>=3.0.0", "snowflake-connector-python>=3.0.0",
"tomli>=2.0.0", "tomli>=2.0.0",
] ]
+18
View File
@@ -0,0 +1,18 @@
"""Resolve file paths for both development and PyInstaller frozen modes."""
import sys
from pathlib import Path
def get_resource_path(relative_path: str) -> Path:
"""Return absolute path to a bundled resource.
In frozen mode (PyInstaller), resolves from sys._MEIPASS.
In dev mode, resolves from the project root (3 parents up from this file:
src/core/resource_path.py → src/core → src → project root).
"""
if getattr(sys, "frozen", False):
base = Path(sys._MEIPASS)
else:
base = Path(__file__).resolve().parents[2]
return base / relative_path
Generated
+260
View File
@@ -5,6 +5,15 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
[[package]]
name = "altgraph"
version = "0.17.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228 },
]
[[package]] [[package]]
name = "asn1crypto" name = "asn1crypto"
version = "1.5.1" version = "1.5.1"
@@ -51,6 +60,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/a8/95656f91b795eb47b73a00d36c51c7a5729eafa632c7348caa068ff63e50/botocore-1.42.43-py3-none-any.whl", hash = "sha256:1c0e30f62e274978ac3bcab253e3a859febea634b72b5e343589db7d17f83cd6", size = 14610179 }, { url = "https://files.pythonhosted.org/packages/4c/a8/95656f91b795eb47b73a00d36c51c7a5729eafa632c7348caa068ff63e50/botocore-1.42.43-py3-none-any.whl", hash = "sha256:1c0e30f62e274978ac3bcab253e3a859febea634b72b5e343589db7d17f83cd6", size = 14610179 },
] ]
[[package]]
name = "bottle"
version = "0.13.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/71/cca6167c06d00c81375fd668719df245864076d284f7cb46a694cbeb5454/bottle-0.13.4.tar.gz", hash = "sha256:787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47", size = 98717 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/f6/b55ec74cfe68c6584163faa311503c20b0da4c09883a41e8e00d6726c954/bottle-0.13.4-py2.py3-none-any.whl", hash = "sha256:045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e", size = 103807 },
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2026.1.4" version = "2026.1.4"
@@ -243,6 +261,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 },
] ]
[[package]]
name = "clr-loader"
version = "0.2.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/61/cf819f8e8bb4d4c74661acf2498ba8d4a296714be3478d21eaabf64f5b9b/clr_loader-0.2.10-py3-none-any.whl", hash = "sha256:ebbbf9d511a7fe95fa28a95a4e04cd195b097881dfe66158dc2c281d3536f282", size = 56483 },
]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@@ -682,6 +712,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 }, { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 },
] ]
[[package]]
name = "macholib"
version = "1.16.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117 },
]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "2.1.3" version = "2.1.3"
@@ -799,6 +841,8 @@ dependencies = [
{ name = "pillow" }, { name = "pillow" },
{ name = "plotly" }, { name = "plotly" },
{ name = "pyarrow" }, { name = "pyarrow" },
{ name = "pyinstaller" },
{ name = "pywebview" },
{ name = "snowflake-connector-python" }, { name = "snowflake-connector-python" },
{ name = "tomli" }, { name = "tomli" },
] ]
@@ -819,12 +863,23 @@ requires-dist = [
{ name = "pillow", specifier = ">=10.0.0" }, { name = "pillow", specifier = ">=10.0.0" },
{ name = "plotly", specifier = ">=5.15.0" }, { name = "plotly", specifier = ">=5.15.0" },
{ name = "pyarrow", specifier = ">=20.0.0" }, { name = "pyarrow", specifier = ">=20.0.0" },
{ name = "pyinstaller", specifier = ">=6.18.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.0.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.0.0" },
{ name = "pytest-cov", marker = "extra == 'test'", specifier = ">=4.0.0" }, { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=4.0.0" },
{ name = "pywebview", specifier = ">=6.1" },
{ name = "snowflake-connector-python", specifier = ">=3.0.0" }, { name = "snowflake-connector-python", specifier = ">=3.0.0" },
{ name = "tomli", specifier = ">=2.0.0" }, { name = "tomli", specifier = ">=2.0.0" },
] ]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766 },
]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "10.0.0" version = "10.0.0"
@@ -897,6 +952,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
] ]
[[package]]
name = "proxy-tools"
version = "0.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978 }
[[package]] [[package]]
name = "pyarrow" name = "pyarrow"
version = "20.0.0" version = "20.0.0"
@@ -968,6 +1029,47 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
] ]
[[package]]
name = "pyinstaller"
version = "6.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
{ name = "macholib", marker = "sys_platform == 'darwin'" },
{ name = "packaging" },
{ name = "pefile", marker = "sys_platform == 'win32'" },
{ name = "pyinstaller-hooks-contrib" },
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/b8/0fe3359920b0a4e7008e0e93ff383003763e3eee3eb31a07c52868722960/pyinstaller-6.18.0.tar.gz", hash = "sha256:cdc507542783511cad4856fce582fdc37e9f29665ca596889c663c83ec8c6ec9", size = 4034976 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/e6/51b0146a1a3eec619e58f5d69fb4e3d0f65a31cbddbeef557c9bb83eeed9/pyinstaller-6.18.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:cb7aa5a71bfa7c0af17a4a4e21855663c89e4bd7c40f1d337c8370636d8847c3", size = 1040056 },
{ url = "https://files.pythonhosted.org/packages/4c/9c/a3634c0ec8e1ed31b373b548848b5c0b39b56edc191cf737e697d484ec23/pyinstaller-6.18.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:07785459b3bf8a48889eac0b4d0667ade84aef8930ce030bc7cbb32f41283b33", size = 734971 },
{ url = "https://files.pythonhosted.org/packages/2c/04/6756442078ccfcd552ccce636be1574035e62f827ffa1f5d8a0382682546/pyinstaller-6.18.0-py3-none-manylinux2014_i686.whl", hash = "sha256:f998675b7ccb2dabbb1dc2d6f18af61d55428ad6d38e6c4d700417411b697d37", size = 746637 },
{ url = "https://files.pythonhosted.org/packages/54/39/fbc56519000cdbf450f472692a7b9b55d42077ce8529f1be631db7b75a36/pyinstaller-6.18.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:779817a0cf69604cddcdb5be1fd4959dc2ce048d6355c73e5da97884df2f3387", size = 744343 },
{ url = "https://files.pythonhosted.org/packages/36/f2/50887badf282fee776e83d1e4feab74c026f50a1ea16e109ed939e32aa28/pyinstaller-6.18.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:31b5d109f8405be0b7cddcede43e7b074792bc9a5bbd54ec000a3e779183c2af", size = 741084 },
{ url = "https://files.pythonhosted.org/packages/1c/08/3a1419183e4713ef77d912ecbdd6ef858689ed9deb34d547133f724ca745/pyinstaller-6.18.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4328c9837f1aef4fe1a127d4ff1b09a12ce53c827ce87c94117628b0e1fd098b", size = 740943 },
{ url = "https://files.pythonhosted.org/packages/c2/47/309305e36d116f1434b42d91c420ff951fa79b2c398bbd59930c830450be/pyinstaller-6.18.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:3638fc81eb948e5e5eab1d4ad8f216e3fec6d4a350648304f0adb227b746ee5e", size = 740107 },
{ url = "https://files.pythonhosted.org/packages/83/0f/a59a95cd1df59ddbc9e74d5a663387551333bcf19a5dd3086f5c81a2e83c/pyinstaller-6.18.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe59da34269e637f97fd3c43024f764586fc319141d245ff1a2e9af1036aa3", size = 739843 },
{ url = "https://files.pythonhosted.org/packages/9a/09/e7a870e7205cdbd2f8785010a5d3fe48a9df2591156ee34a8b29b774fa14/pyinstaller-6.18.0-py3-none-win32.whl", hash = "sha256:496205e4fa92ec944f9696eb597962a83aef4d4c3479abfab83d730e1edf016b", size = 1323811 },
{ url = "https://files.pythonhosted.org/packages/fb/d5/48eef2002b6d3937ceac2717fe17e9ca3a43a4c9826bafee367dfc75ba85/pyinstaller-6.18.0-py3-none-win_amd64.whl", hash = "sha256:976fabd90ecfbda47571c87055ad73413ec615ff7dea35e12a4304174de78de9", size = 1384389 },
{ url = "https://files.pythonhosted.org/packages/1b/8d/1a88e6e94107de3ea1c842fd59c3aa132d344ad8e52ea458ffa9a748726e/pyinstaller-6.18.0-py3-none-win_arm64.whl", hash = "sha256:dba4b70e3c9ba09aab51152c72a08e58a751851548f77ad35944d32a300c8381", size = 1324869 },
]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2026.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/31/8f/8052ff65067697ee80fde45b9731842e160751c41ac5690ba232c22030e8/pyinstaller_hooks_contrib-2026.0.tar.gz", hash = "sha256:0120893de491a000845470ca9c0b39284731ac6bace26f6849dea9627aaed48e", size = 170311 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/b1/9da6ec3e88696018ee7bb9dc4a7310c2cfaebf32923a19598cd342767c10/pyinstaller_hooks_contrib-2026.0-py3-none-any.whl", hash = "sha256:0590db8edeba3e6c30c8474937021f5cd39c0602b4d10f74a064c73911efaca5", size = 452318 },
]
[[package]] [[package]]
name = "pyjwt" name = "pyjwt"
version = "2.11.0" version = "2.11.0"
@@ -977,6 +1079,109 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224 }, { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224 },
] ]
[[package]]
name = "pyobjc-core"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/bf/3dbb1783388da54e650f8a6b88bde03c101d9ba93dfe8ab1b1873f1cd999/pyobjc_core-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:93418e79c1655f66b4352168f8c85c942707cb1d3ea13a1da3e6f6a143bacda7", size = 676748 },
{ url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263 },
{ url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335 },
{ url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370 },
{ url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586 },
{ url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164 },
{ url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204 },
]
[[package]]
name = "pyobjc-framework-cocoa"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/aa/2b2d7ec3ac4b112a605e9bd5c5e5e4fd31d60a8a4b610ab19cc4838aa92a/pyobjc_framework_cocoa-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9b880d3bdcd102809d704b6d8e14e31611443aa892d9f60e8491e457182fdd48", size = 383825 },
{ url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812 },
{ url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590 },
{ url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689 },
{ url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843 },
{ url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932 },
{ url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970 },
]
[[package]]
name = "pyobjc-framework-quartz"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/f4/50c42c84796886e4d360407fb629000bb68d843b2502c88318375441676f/pyobjc_framework_quartz-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c6f312ae79ef8b3019dcf4b3374c52035c7c7bc4a09a1748b61b041bb685a0ed", size = 217799 },
{ url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795 },
{ url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798 },
{ url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206 },
{ url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317 },
{ url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558 },
{ url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580 },
]
[[package]]
name = "pyobjc-framework-security"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/67/31928b689b72a932c80e35662430355de09163bec8ee334f0994d16c4036/pyobjc_framework_security-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:787e9d873535247e2caca2036cbcdc956bcc92d0c06044bec7eefe0a456856b0", size = 41288 },
{ url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287 },
{ url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280 },
{ url = "https://files.pythonhosted.org/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284 },
{ url = "https://files.pythonhosted.org/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162 },
{ url = "https://files.pythonhosted.org/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348 },
{ url = "https://files.pythonhosted.org/packages/1a/d8/5aaa2a8124ed04a9d6ca7053dc0fa64e42be51497ed8263a24b744a95598/pyobjc_framework_security-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03d166371cefdef24908825148eb848f99ee2c0b865870a09dcbb94334dd3e0a", size = 42908 },
]
[[package]]
name = "pyobjc-framework-uniformtypeidentifiers"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901 },
]
[[package]]
name = "pyobjc-framework-webkit"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/79/b5582113b28cae64cec4aca63d36620421c21ca52f3897388b865a0dbb86/pyobjc_framework_webkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:231048d250e97323b25e5f1690d09e2415b691c0d57bc13241e442d486ef94c8", size = 49971 },
{ url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970 },
{ url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084 },
{ url = "https://files.pythonhosted.org/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096 },
{ url = "https://files.pythonhosted.org/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572 },
{ url = "https://files.pythonhosted.org/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210 },
{ url = "https://files.pythonhosted.org/packages/2d/86/637cda4983dc0936b73a385f3906256953ac434537b812814cb0b6d231a2/pyobjc_framework_webkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aaa3bf12c7b68e1a36c0b294d2728e06f2cc220775e6dc4541d5046290e4dc8", size = 50680 },
]
[[package]] [[package]]
name = "pyopenssl" name = "pyopenssl"
version = "25.3.0" version = "25.3.0"
@@ -1034,6 +1239,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 },
] ]
[[package]]
name = "pythonnet"
version = "3.0.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "clr-loader" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cd/f1/bfb6811df4745f92f14c47a29e50e89a36b1533130fcc56452d4660bd2d6/pythonnet-3.0.5-py3-none-any.whl", hash = "sha256:f6702d694d5d5b163c9f3f5cc34e0bed8d6857150237fae411fefb883a656d20", size = 297506 },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2023.3" version = "2023.3"
@@ -1043,6 +1260,49 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb", size = 502345 }, { url = "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb", size = 502345 },
] ]
[[package]]
name = "pywebview"
version = "6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bottle" },
{ name = "proxy-tools" },
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" },
{ name = "pythonnet", marker = "sys_platform == 'win32'" },
{ name = "qtpy", marker = "sys_platform == 'openbsd6'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/64/09/75cf92c4db19350ddb084d841e352ce30e89668815d0494d1dd595c85469/pywebview-6.1.tar.gz", hash = "sha256:f0b95047860caf3d921581f9e16b4edb1a125b23e2ce691c4da2968f8b160ff2", size = 496270 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/74/12/ce9006cdcc1834378826b5c0d4b554d899cd25ac9c665569d060f220b0e1/pywebview-6.1-py3-none-any.whl", hash = "sha256:2b552a340557e76a740b045a25937f72dae751e0985d5f5d9556f697ba622ec5", size = 511255 },
]
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 },
]
[[package]]
name = "qtpy"
version = "2.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045 },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.5" version = "2.32.5"