Skip to content

Brand UI, Excel spreadsheet link, worked example and a test suite - #7

Merged
saudzahirr merged 9 commits into
masterfrom
feat/modern-ui-redesign
Aug 19, 2026
Merged

Brand UI, Excel spreadsheet link, worked example and a test suite#7
saudzahirr merged 9 commits into
masterfrom
feat/modern-ui-redesign

Conversation

@saudzahirr

Copy link
Copy Markdown
Contributor

Six commits. The app had no icon, no tests, no examples, and no way to touch a
spreadsheet; it now has all four.

Application icon

setWindowIcon appeared nowhere in the repo, so the app inherited the Python
interpreter's icon. The crystal-ball logo now ships inside the package at eight
resolutions plus a Windows .ico, installed as both the application and window
icon, with an AppUserModelID so the task bar shows it. Verified from a clean
wheel install.

Interface

vatic/theme.py replaces the hand-written XP-era style sheet with a token
system and a contrast auditor. Body ink is a neutral near-black at 15.6:1;
the four logo hues are reserved for the primary action, selection, panel
washes and the charts. Every audited ink-on-surface pairing clears WCAG AA,
and a test fails the build if that stops being true.

Layout: a branded header carrying the logo and the active analysis, and with it
the Run Simulation button, which previously existed only as F5 and a menu
entry. Group boxes that rendered as empty frames got titles, the tables got
add/remove actions, and the input sidebar scrolls -- the window's minimum
height drops from 1197px to 523px, so it fits a 1366x768 laptop.

Formulas are edited in a dialog rather than in a cell editor a few characters
wide. JetBrains Mono is bundled under the SIL OFL so the interface renders the
same on machines that do not have it.

Charts serve plotly.js from a bundled copy through Plotly.react against a
persistent page, so they no longer wait on a CDN round-trip (6.3s measured
here) and work with no network at all. All 13 chart types and the PDF/PPTX
exports moved onto the brand palette.

Maths

compute_statistics now also reports variance, skewness and kurtosis, which
the reference implementation reports per variable and this one did not. The
conventions match it deliberately: population variance, the standardised third
moment with no sample-size correction, and Pearson kurtosis where a normal
distribution sits at 3.0.

Runs are reproducible. Nothing seeded numpy's global state, which mcerp samples
through, so two runs of the same model returned different Cpk, different PPM
and a different tornado ordering. There is now a Seed control, defaulting to
"random" so behaviour is unchanged unless a seed is chosen.

Excel link

vatic began as a spreadsheet application -- Excel was the model, Python only
drove the sampling -- and that capability was missing here entirely.

vatic/sheetmodel.py is the pure data model and imports on every platform.
vatic/excel/ is the Windows half: typed errors with a worksheet-error
decoder, a session owning the COM connection, and a runner.

The original wrote one cell and recalculated once per trial: a cross-process
call per cell per iteration, about eighteen minutes for ten thousand trials.
The runner writes the whole sample matrix to a hidden sheet, points each
assumption cell at its column through INDEX, and builds a one-variable data
table over a trial index, so Excel resolves every trial in a single
recalculation. Measured against live Excel 16.0: 1.4s for ten thousand
trials
, 0.45s to re-run.

Three details were settled by probing Excel rather than by assumption:

  • Workbook.Calculate does not exist; the Application and Worksheet forms do.
  • An unfilled cell reports Color 16777215 with Pattern xlNone, so restoring
    the colour alone leaves a white fill behind.
  • A data table's input cell must live on the table's own sheet, or Excel
    rejects it outright.

Restoration runs in a finally block, so a crash or a cancel still puts the
workbook back, and it restores the original formula rather than its
computed value -- the original snapshotted the value and wrote that back,
silently destroying a formula in any tagged input cell. CoUninitialize is
deliberately not called on close: callers still hold Range proxies, and tearing
the apartment down beneath them turns their collection into
RPC_E_DISCONNECTED, which crashes the interpreter instead of raising.

A Spreadsheet menu connects a workbook and tags the cell selected in Excel
as an assumption or a forecast; tagged cells are tinted in the sheet and their
original fill is restored. Help > Using vatic with Excel documents the
whole flow, what a run changes, what it restores, and what each failure means.

Examples and tests

Neither existed before.

examples/seal_tolerance.py runs the Double-D seal gland tolerance stack-up
from the original project through this app's API. build_seal_workbook.py
regenerates the same model as a spreadsheet with named ranges. The upstream
repository carries no licence, so its workbook is not redistributed; the
generator rebuilds an equivalent one from the same published dimensions and
formulas, which also keeps it reproducible from source control.

113 tests, checked against closed-form values and independent derivations
rather than against the implementation. Ten drive a private hidden Excel
instance and skip where Excel is absent, so they never touch an open session.
A new Tests workflow runs Linux, Windows and macOS on 3.12 and 3.13.

Notes for review

  • The Excel run happens on the GUI thread, so the window is unresponsive for
    the ~1.4s a run takes. A COM apartment belongs to its creating thread and a
    correct threaded version needs message pumping; at 1.4s that trade seemed
    right, but it is worth doing properly before anyone points this at a heavy
    workbook.
  • The reference implementation's capability metrics could never have run: it
    calls ss.norm(...) without importing scipy.stats, relying on a
    from mcerp import * leak that modern mcerp closed with __all__. Its other
    defects -- the normal density used where the CDF is required, a Cpm
    exponent written with bitwise XOR, and an ignored zshift leaving Zst equal
    to Zlt -- are not reproduced here, and a test pins down each one.

🤖 Generated with Claude Code

saudzahirr and others added 7 commits August 19, 2026 16:03
The window had no application icon at all and wore a dated light-blue
Windows-XP-era style sheet whose colours matched nothing in the brand.

Icon
  Bundle the crystal-ball logo inside the package at eight resolutions plus
  a Windows .ico, and install it as both the QApplication and the window
  icon. Register an AppUserModelID so the Windows task bar shows it instead
  of the host interpreter's icon.

Theme
  Add vatic/theme.py: a closed palette of white plus the four hues taken
  from the banner (#2323FF, #7E3DFF, #C04AFF, #24AEFF), with every other
  value derived as a tint or shade of one of them. White is the dominant
  surface and the lightest hue, cyan, carries the primary action. Ships a
  contrast auditor; every ink-on-surface pairing clears WCAG AA. JetBrains
  Mono is bundled under the SIL OFL so the interface renders identically on
  machines that do not have it installed.

Layout
  Add a branded header carrying the logo and the active analysis name, and
  with it the Run Simulation button, which previously existed only as F5 and
  a menu entry. Title the group boxes that were rendering as empty frames,
  give the assumption and formula tables their own add/remove actions, and
  put the input sidebar in a scroll area. The window's minimum height drops
  from 1197px to 523px, so it now fits a 1366x768 laptop screen.

Charts
  Serve plotly.js from a bundled local copy and re-plot through Plotly.react
  against a persistent page, so charts no longer wait on a CDN round-trip
  (measured at 6.3s here) and work with no network at all. Re-colour all 13
  chart types and the PDF/PPTX exports onto the brand palette; Box and
  Violin now cycle the ramp instead of drawing every series in one colour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Palette
  Add four light washes, one per logo hue, and tint each sidebar panel with
  a different one so the column reads as distinct sections instead of a
  single undifferentiated stack. Titles pick up the matching hue. All four
  are pale enough that body and muted ink still clear WCAG AA on them; the
  contrast audit covers every new pairing.

Popups
  A QMenu and a combo box drop-down are top level windows, so the style
  sheet's border radius left the square native corners painted behind the
  rounded edge. Popups are now given a translucent background, applied when
  each menu is created rather than mid-show so Qt never has to recreate the
  native window while it is appearing.

Scroll wheel
  A spin box or combo box inside the new scrolling sidebar swallowed the
  wheel and edited itself, so scrolling past the iteration count silently
  changed it and invalidated the results. Unfocused controls now hand the
  wheel back to the scroll area; focused ones still adjust normally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Formulas
  The expression column was edited in place, so a formula was typed into a
  cell a few characters wide and scrolled out of sight as it grew. The
  formula table is now read-only and double-clicking a row, the toolbar
  button, the Edit menu or the context menu all open a ForecastDialog with
  full-width fields for name, expression and the spec limits, validated
  before the row is written. The keypad still appends to the selected row,
  which never needed a cell editor.

Statistics
  compute_statistics now also reports variance, skewness and kurtosis,
  which the reference implementation reports per variable and this one did
  not. The conventions deliberately match it: population variance, the
  standardised third moment with no sample-size correction, and Pearson
  kurtosis where a normal distribution sits at 3.0. Excess kurtosis is
  offered alongside it since that is what most readers expect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The project had no examples and no tests at all.

examples/
  seal_tolerance.py runs the Double-D seal gland tolerance stack-up from the
  original vatic project through this app's own API, with no spreadsheet
  involved: eight toleranced dimensions modelled as Normal(nominal, tol/3),
  three intermediate areas, and two characteristics judged against their
  spec limits. build_seal_workbook.py regenerates the same model as a
  spreadsheet with named ranges, ready for a future spreadsheet-driven run.

  The upstream repository carries no licence, so its workbook is not
  redistributed; the generator rebuilds an equivalent one from the same
  published dimensions and formulas, which also keeps the spreadsheet
  reproducible from source control instead of being an opaque binary.

tests/
  72 tests covering the statistics, the capability metrics, the formula
  evaluator and the theme. The maths is checked against closed-form values
  and an independent derivation rather than against itself, and three
  tests pin down defects in the reference implementation that are
  deliberately not reproduced: the normal density used where the cumulative
  distribution is required, the Cpm exponent written with bitwise XOR, and
  the ignored zshift that left Zst equal to Zlt.

  The seal example is verified end to end: every simulated mean is checked
  against the closed-form nominal, including the fact that Gland Fill %
  overfills at nominal and must report a negative Cpk.

Adds a Tests workflow across Linux, Windows and macOS on 3.12 and 3.13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mcerp draws its Latin hypercube through numpy's legacy global random state,
and nothing seeded it, so two runs of the same model returned different Cpk,
different PPM and a different tornado ordering. That is untenable when the
output is used to sign off a design, and it made the maths untestable.

Adds seed_sampler() and a Seed control beside the iteration count, defaulting
to "random" so behaviour is unchanged unless a seed is chosen. The worked
example now pins a seed, so its printed numbers are stable, and two tests
assert that the same seed reproduces a run exactly while a different one does
not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vatic began life as a spreadsheet application: Excel was the model and Python
only drove the sampling. That capability was missing here entirely. This adds
it back, several orders of magnitude faster and without the data loss.

Engine
  vatic/sheetmodel.py holds the pure data model (cell references, tagged
  assumptions and forecasts) and imports on every platform. vatic/excel/ is
  the Windows half: typed errors with the worksheet-error decoder, a session
  owning the COM connection, and a runner.

  The reference implementation wrote one cell and recalculated once per trial
  -- a cross-process call per cell per iteration, about eighteen minutes for
  ten thousand trials. The runner instead writes the whole sample matrix to a
  hidden sheet, points each assumption cell at its column through INDEX, and
  builds a one-variable data table over a trial index, so Excel resolves every
  trial in a single recalculation. Measured against live Excel 16.0: 1.4s for
  ten thousand trials, 0.45s to re-run.

  Three details were established by probing Excel rather than by assumption.
  Workbook.Calculate does not exist, so the runner uses the Application and
  Worksheet forms. An unfilled cell reports Color 16777215 with Pattern
  xlNone, so restoring the colour alone leaves a white fill behind and the
  pattern must be restored too. A data table's input cell has to live on the
  table's own sheet or Excel rejects it outright.

  Restoration runs in a finally block, so a crash or a cancel still puts the
  workbook back. Crucially the original FORMULA is restored, not its computed
  value: the reference implementation snapshotted the value and wrote that
  back, silently destroying a formula in any tagged input cell.

  CoUninitialize is deliberately not called when a session closes. Callers
  routinely still hold Range proxies, and tearing the apartment down beneath
  them turns their eventual collection into RPC_E_DISCONNECTED, which crashes
  the interpreter instead of raising.

UI
  A Spreadsheet menu connects a workbook, tags the cell selected in Excel as
  an assumption or a forecast, and clears the tags again. Tagged cells are
  tinted in the sheet and their original fill is put back. Run Simulation
  drives Excel when a workbook is connected and the in-app model otherwise,
  and results flow into the same charts, statistics and reports.

  Help > Using vatic with Excel explains the whole flow, what the run changes,
  what it restores, and what each failure means.

Tests
  Ten of them drive a private hidden Excel instance and skip where Excel is
  absent, so they never touch a session the user has open. They check the
  results against numpy exactly, that a formula in a tagged cell survives,
  that the scratch sheets and application settings are restored, that
  cancellation still restores, and that worksheet errors are counted rather
  than silently read as zero. Thirty more cover the model and error decoder
  on every platform.

Also fixes an in-table editor that was too short for its own text, and
declares pywin32 as an optional dependency so it stops being pruned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vatic/__init__.py imported main eagerly, so `import vatic.analytics` pulled in
the whole Qt stack, QtWebEngine included. On a head-less Linux runner that
import fails outright for want of the system libraries Qt needs, which took
the Ubuntu test job down even though nothing under test uses Qt.

The numerical, theme and spreadsheet-model modules have no Qt dependency of
their own, so nothing should force one on them. `main` now resolves through a
module __getattr__: `from vatic import main` still works, and importing any
other module pulls in zero Qt modules.

Also ignores Excel's ~$ owner files, which appear beside any workbook that
gets opened and must never be committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@saudzahirr saudzahirr self-assigned this Aug 19, 2026
@saudzahirr saudzahirr added the enhancement New feature or request label Aug 19, 2026
saudzahirr and others added 2 commits August 19, 2026 21:41
… button

CI
  excel_available() only checked for Windows and pywin32, so on a GitHub
  Windows runner it reported True and the spreadsheet tests tried to start an
  Excel that is not installed, failing with CO_E_CLASSSTRING. It now confirms
  Excel is registered by looking the ProgID up in the registry, which proves
  the install without launching anything. That HRESULT is also mapped, so a
  user without Excel gets the explanation rather than a raw COM error.

Contrast
  The disabled primary button painted white text on a near-white fill, 1.37:1.
  It now uses muted ink on the sunken surface.

  A new test walks the generated style sheet and fails on any rule that sets
  both a colour and a background under 3:1. The existing audit only covered
  pairs the palette declares, which is exactly how this one slipped through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A selected row showed white text on a near-white background. The style
sheet painted the row with the pale selection wash, but a cell's text is
drawn by the delegate using selection-color, which the global QWidget rule
had set to white for the solid blue selection used by lists and menus. The
result was white on #E5E5FF, a contrast ratio of 1.16:1.

Both selection colours are now pinned on the table itself, so the pale row
carries near-black ink at about 14:1. Setting them through the widget's
palette does not work: a style sheet's selection-color always wins over the
palette, which is why the first attempt at this changed nothing.

A test now walks the generated sheet and fails any rule that sets a
selection background without a legible selection-color to go with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@saudzahirr
saudzahirr merged commit 7a90855 into master Aug 19, 2026
7 checks passed
@saudzahirr
saudzahirr deleted the feat/modern-ui-redesign branch August 19, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant