This commit is contained in:
Nolan Kovacs 2026-04-04 14:20:37 -04:00
parent 0de9beec8e
commit 6bc851b03c
2 changed files with 44 additions and 38 deletions

80
dist/screener_gui.py vendored
View File

@ -1494,30 +1494,29 @@ class _MetricRow(QWidget):
self.val_lbl.setStyleSheet(f"color:{c}; font-size:12px; font-family:'Consolas';") self.val_lbl.setStyleSheet(f"color:{c}; font-size:12px; font-family:'Consolas';")
def _make_scroll_tab(fields): def _make_grid_tab(fields, cols=2):
"""Return (QWidget tab, dict of key->_MetricRow).""" """Return (QWidget tab, dict of key->MetricCell) using 2-col metric card grid."""
tab = QWidget() tab = QWidget()
scroll = QScrollArea() scroll = QScrollArea()
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame) scroll.setFrameShape(QFrame.NoFrame)
inner = QWidget() inner = QWidget()
lay = QVBoxLayout(inner) lay = QGridLayout(inner)
lay.setContentsMargins(0, 4, 0, 4) lay.setContentsMargins(10, 10, 10, 10)
lay.setSpacing(0) lay.setSpacing(6)
rows = {} cells = {}
for label, key in fields: for i, (label, key) in enumerate(fields):
row_w = _MetricRow(label, key) tip = _METRIC_TOOLTIPS.get(label, "")
rows[key] = row_w cell = MetricCell(label, key, tip)
lay.addWidget(row_w) cells[key] = cell
sep = QFrame(); sep.setFixedHeight(1) lay.addWidget(cell, i // cols, i % cols)
sep.setStyleSheet(f"background:{_C['border']};") # stretch the last row so cards stay top-aligned
lay.addWidget(sep) lay.setRowStretch((len(fields) - 1) // cols + 1, 1)
lay.addStretch()
scroll.setWidget(inner) scroll.setWidget(inner)
outer = QVBoxLayout(tab) outer = QVBoxLayout(tab)
outer.setContentsMargins(0, 0, 0, 0) outer.setContentsMargins(0, 0, 0, 0)
outer.addWidget(scroll) outer.addWidget(scroll)
return tab, rows return tab, cells
class DetailPanel(QWidget): class DetailPanel(QWidget):
@ -1560,43 +1559,41 @@ class DetailPanel(QWidget):
self._tabs.addTab(self._score_tab, "Scores") self._tabs.addTab(self._score_tab, "Scores")
# Valuation # Valuation
t, rows = _make_scroll_tab(_VAL_FIELDS) t, cells = _make_grid_tab(_VAL_FIELDS)
self._all_rows.update(rows) self._all_rows.update(cells)
self._tabs.addTab(t, "Valuation") self._tabs.addTab(t, "Valuation")
# Growth # Growth
t, rows = _make_scroll_tab(_GROWTH_FIELDS) t, cells = _make_grid_tab(_GROWTH_FIELDS)
self._all_rows.update(rows) self._all_rows.update(cells)
self._tabs.addTab(t, "Growth") self._tabs.addTab(t, "Growth")
# Momentum # Momentum
t, rows = _make_scroll_tab(_MOM_FIELDS) t, cells = _make_grid_tab(_MOM_FIELDS)
self._all_rows.update(rows) self._all_rows.update(cells)
self._tabs.addTab(t, "Momentum") self._tabs.addTab(t, "Momentum")
# Quality & Profit # Quality & Profit
t, rows = _make_scroll_tab(_QUAL_FIELDS) t, cells = _make_grid_tab(_QUAL_FIELDS)
self._all_rows.update(rows) self._all_rows.update(cells)
self._tabs.addTab(t, "Quality & Profit") self._tabs.addTab(t, "Quality & Profit")
# Analyst tab (metrics + commentary) # Analyst tab (metric grid + commentary)
analyst_tab = QWidget() analyst_tab = QWidget()
al = QVBoxLayout(analyst_tab) al = QVBoxLayout(analyst_tab)
al.setContentsMargins(0, 0, 0, 0) al.setContentsMargins(0, 0, 0, 0)
al.setSpacing(0) al.setSpacing(0)
# Metrics in a small scrollable area # Metrics as a 2-col grid
anlst_inner = QWidget() anlst_grid_w = QWidget()
anlst_lay = QVBoxLayout(anlst_inner) anlst_grid_lay = QGridLayout(anlst_grid_w)
anlst_lay.setContentsMargins(0, 4, 0, 4) anlst_grid_lay.setContentsMargins(10, 10, 10, 6)
anlst_lay.setSpacing(0) anlst_grid_lay.setSpacing(6)
for label, key in _ANLST_FIELDS: for i, (label, key) in enumerate(_ANLST_FIELDS):
row_w = _MetricRow(label, key) tip = _METRIC_TOOLTIPS.get(label, "")
self._all_rows[key] = row_w cell = MetricCell(label, key, tip)
anlst_lay.addWidget(row_w) self._all_rows[key] = cell
sep2 = QFrame(); sep2.setFixedHeight(1) anlst_grid_lay.addWidget(cell, i // 2, i % 2)
sep2.setStyleSheet(f"background:{_C['border']};") al.addWidget(anlst_grid_w)
anlst_lay.addWidget(sep2)
al.addWidget(anlst_inner)
hdiv = QFrame(); hdiv.setFixedHeight(1) hdiv = QFrame(); hdiv.setFixedHeight(1)
hdiv.setStyleSheet(f"background:{_C['border']};") hdiv.setStyleSheet(f"background:{_C['border']};")
@ -2044,6 +2041,7 @@ class ScreenerPage(QWidget):
self._run_btn.setEnabled(True) self._run_btn.setEnabled(True)
self._stop_btn.setEnabled(False) self._stop_btn.setEnabled(False)
self._progress.hide() self._progress.hide()
QMessageBox.critical(self, "Screen Error", f"The screening run failed:\n\n{text}")
@Slot() @Slot()
def _on_stopped(self): def _on_stopped(self):
@ -2054,6 +2052,14 @@ class ScreenerPage(QWidget):
def _apply_filters(self): def _apply_filters(self):
if self._df is None: return if self._df is None: return
try:
self._apply_filters_inner()
except Exception as exc:
import traceback
QMessageBox.critical(self, "Display Error",
f"Failed to display results:\n\n{traceback.format_exc()}")
def _apply_filters_inner(self):
df = self._df df = self._df
rule = _SECTOR_FILTER_MAP.get(self._sector_cb.currentText()) rule = _SECTOR_FILTER_MAP.get(self._sector_cb.currentText())
if rule: if rule:

View File

@ -19,7 +19,7 @@ import requests
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Current app version — kept in sync by push_update.py before each build. # Current app version — kept in sync by push_update.py before each build.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
APP_VERSION = "1.4.3" APP_VERSION = "1.4.4"
def _exe_dir() -> str: def _exe_dir() -> str: