diff --git a/apps/electrochem/private/chronoOverlayWorkflow.m b/apps/electrochem/chrono_overlay/+chrono_overlay/+core/dispatch.m similarity index 94% rename from apps/electrochem/private/chronoOverlayWorkflow.m rename to apps/electrochem/chrono_overlay/+chrono_overlay/+core/dispatch.m index 43b3e48..688e2d5 100644 --- a/apps/electrochem/private/chronoOverlayWorkflow.m +++ b/apps/electrochem/chrono_overlay/+chrono_overlay/+core/dispatch.m @@ -1,12 +1,11 @@ -% App-owned chrono overlay workflow helper dispatch. Expected caller: -% labkit_ChronoOverlay_app callbacks and workflow tests. +% App-owned chrono overlay helper core dispatch. Expected caller: +% labkit_ChronoOverlay_app callbacks and package tests. % Inputs are a command string plus the original helper arguments; outputs match % the selected helper. Side effects are limited to drawing app-owned overlay % plots on caller axes. -function varargout = chronoOverlayWorkflow(command, varargin) -%CHRONOOVERLAYWORKFLOW Dispatch app-owned chrono overlay helpers. -% Expected caller: labkit_ChronoOverlay_app callbacks and temporary compatibility -% workflow tests. Inputs are a command string plus the original helper arguments. +function varargout = dispatch(command, varargin) +%DISPATCH Route chrono overlay package wrapper calls to app-owned helpers. +% Expected caller: labkit_ChronoOverlay_app callbacks and % package tests. Inputs are a command string plus the original helper arguments. % Outputs match the selected helper. Side effects are limited to drawing % app-owned overlay plots on caller axes. diff --git a/apps/electrochem/chrono_overlay/+chrono_overlay/+export/buildOverlayExportTable.m b/apps/electrochem/chrono_overlay/+chrono_overlay/+export/buildOverlayExportTable.m new file mode 100644 index 0000000..e3b5f25 --- /dev/null +++ b/apps/electrochem/chrono_overlay/+chrono_overlay/+export/buildOverlayExportTable.m @@ -0,0 +1,6 @@ +% Expected caller: chrono overlay app runner and export tests. Inputs are aligned +% chrono item structs. Output is the stable overlay export table. No file side +% effects. +function T = buildOverlayExportTable(items) + T = chrono_overlay.core.dispatch("buildOverlayExportTable", items); +end diff --git a/apps/electrochem/chrono_overlay/+chrono_overlay/+ops/alignByPulseGap.m b/apps/electrochem/chrono_overlay/+chrono_overlay/+ops/alignByPulseGap.m new file mode 100644 index 0000000..ed113e1 --- /dev/null +++ b/apps/electrochem/chrono_overlay/+chrono_overlay/+ops/alignByPulseGap.m @@ -0,0 +1,6 @@ +% Expected caller: chrono overlay app runner and unit tests. Inputs are one +% chrono item struct with time/current/voltage and pulse fields. Outputs return +% the aligned item and status message. No file or UI side effects. +function [item, msg] = alignByPulseGap(item) + [item, msg] = chrono_overlay.core.dispatch("alignByPulseGap", item); +end diff --git a/apps/electrochem/private/runChronoOverlayApp.m b/apps/electrochem/chrono_overlay/+chrono_overlay/+ui/runApp.m similarity index 96% rename from apps/electrochem/private/runChronoOverlayApp.m rename to apps/electrochem/chrono_overlay/+chrono_overlay/+ui/runApp.m index ff3849b..66de6cf 100644 --- a/apps/electrochem/private/runChronoOverlayApp.m +++ b/apps/electrochem/chrono_overlay/+chrono_overlay/+ui/runApp.m @@ -2,7 +2,7 @@ % Input is the debug context prepared by the public launcher. Output is the app % figure. Side effects are GUI creation, user-driven file I/O, exports, % plotting, and debug trace attachment exactly as in the original entrypoint body. -function fig = runChronoOverlayApp(debugLog) +function fig = runApp(debugLog) %RUNCHRONOOVERLAYAPP Build and run the app body. S = struct(); @@ -160,7 +160,7 @@ function postProcessAddedItems(filepaths) end item = S.session.items(idx); - [item, alignMsg] = chronoOverlayWorkflow("alignByPulseGap", item); + [item, alignMsg] = chrono_overlay.ops.alignByPulseGap(item); S.session.items(idx) = item; addLog(alignMsg); @@ -204,7 +204,7 @@ function refreshFileList() function refreshPlots() if isempty(S.items) - chronoOverlayWorkflow("plotVTIT", axV, axI, struct([]), plotOptions()); + chrono_overlay.view.plotVTIT(axV, axI, struct([]), plotOptions()); return; end @@ -215,7 +215,7 @@ function refreshPlots() return; end - chronoOverlayWorkflow("plotVTIT", axV, axI, items, plotOptions()); + chrono_overlay.view.plotVTIT(axV, axI, items, plotOptions()); end function onExportCSV(~, ~) @@ -235,7 +235,7 @@ function onExportCSV(~, ~) return; end - T = chronoOverlayWorkflow("buildOverlayExportTable", items); + T = chrono_overlay.export.buildOverlayExportTable(items); out = fullfile(p, f); writetable(T, out); addLog(sprintf('Exported CSV: %s', out)); diff --git a/apps/electrochem/chrono_overlay/+chrono_overlay/+view/plotVTIT.m b/apps/electrochem/chrono_overlay/+chrono_overlay/+view/plotVTIT.m new file mode 100644 index 0000000..bceb18c --- /dev/null +++ b/apps/electrochem/chrono_overlay/+chrono_overlay/+view/plotVTIT.m @@ -0,0 +1,6 @@ +% Expected caller: chrono overlay app runner. Inputs are voltage/current axes, +% aligned item structs, and plot option fields. Side effects are limited to +% redrawing the supplied axes. +function plotVTIT(axV, axI, items, opts) + chrono_overlay.core.dispatch("plotVTIT", axV, axI, items, opts); +end diff --git a/apps/electrochem/labkit_ChronoOverlay_app.m b/apps/electrochem/chrono_overlay/labkit_ChronoOverlay_app.m similarity index 95% rename from apps/electrochem/labkit_ChronoOverlay_app.m rename to apps/electrochem/chrono_overlay/labkit_ChronoOverlay_app.m index 48af2ff..3c12b9b 100644 --- a/apps/electrochem/labkit_ChronoOverlay_app.m +++ b/apps/electrochem/chrono_overlay/labkit_ChronoOverlay_app.m @@ -17,7 +17,7 @@ error('labkit_ChronoOverlay_app:TooManyOutputs', 'labkit_ChronoOverlay_app returns at most the app figure handle.'); end - fig = runChronoOverlayApp(debugLog); + fig = chrono_overlay.ui.runApp(debugLog); if nargout >= 1 varargout{1} = fig; end diff --git a/apps/electrochem/private/cicWorkflow.m b/apps/electrochem/cic/+cic/+core/dispatch.m similarity index 98% rename from apps/electrochem/private/cicWorkflow.m rename to apps/electrochem/cic/+cic/+core/dispatch.m index 946a8b9..ec1b6df 100644 --- a/apps/electrochem/private/cicWorkflow.m +++ b/apps/electrochem/cic/+cic/+core/dispatch.m @@ -1,10 +1,10 @@ -% App-owned CIC workflow helper dispatch. Expected caller: labkit_CIC_app -% callbacks and workflow tests. Inputs are a command string plus the original +% App-owned CIC helper core dispatch. Expected caller: labkit_CIC_app +% callbacks and package tests. Inputs are a command string plus the original % helper arguments; outputs match the selected helper. Side effects are limited % to CSV export writes and drawing app-owned plot annotations on caller axes. -function varargout = cicWorkflow(command, varargin) -%CICWORKFLOW Dispatch app-owned CIC analysis/export helpers. -% Expected caller: labkit_CIC_app callbacks and workflow tests. +function varargout = dispatch(command, varargin) +%DISPATCH Route CIC package wrapper calls to app-owned helpers. +% Expected caller: labkit_CIC_app callbacks and package tests. % Inputs are a command string plus the original helper arguments. Outputs match % the selected helper. Side effects are limited to CSV export writes and drawing % app-owned plot annotations on caller axes. diff --git a/apps/electrochem/cic/+cic/+export/buildResultsTable.m b/apps/electrochem/cic/+cic/+export/buildResultsTable.m new file mode 100644 index 0000000..3030a0a --- /dev/null +++ b/apps/electrochem/cic/+cic/+export/buildResultsTable.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app runner and export tests. Inputs are item structs and +% display unit label. Output is the stable CIC CSV result table. No file side +% effects. +function T = buildResultsTable(items, unitLabel) + T = cic.core.dispatch("buildResultsTable", items, unitLabel); +end diff --git a/apps/electrochem/cic/+cic/+export/writeResultsCSV.m b/apps/electrochem/cic/+cic/+export/writeResultsCSV.m new file mode 100644 index 0000000..af22c7f --- /dev/null +++ b/apps/electrochem/cic/+cic/+export/writeResultsCSV.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app runner and export tests. Inputs are item structs, +% output filepath, and display unit label. Side effect is writing the stable CIC +% CSV file. +function [ok, msg] = writeResultsCSV(items, filepath, unitLabel) + [ok, msg] = cic.core.dispatch("writeResultsCSV", items, filepath, unitLabel); +end diff --git a/apps/electrochem/cic/+cic/+ops/computeCIC.m b/apps/electrochem/cic/+cic/+ops/computeCIC.m new file mode 100644 index 0000000..0035142 --- /dev/null +++ b/apps/electrochem/cic/+cic/+ops/computeCIC.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app runner and unit tests. Inputs are a DTA item struct +% and CIC option struct. Output is the stable CIC analysis result struct. No file +% or UI side effects. +function A = computeCIC(item, opts) + A = cic.core.dispatch("computeCIC", item, opts); +end diff --git a/apps/electrochem/cic/+cic/+ops/interp1Safe.m b/apps/electrochem/cic/+cic/+ops/interp1Safe.m new file mode 100644 index 0000000..54777d7 --- /dev/null +++ b/apps/electrochem/cic/+cic/+ops/interp1Safe.m @@ -0,0 +1,5 @@ +% Expected caller: CIC app plotting helpers. Inputs are vectors x/y and query +% points. Output mirrors the app-owned safe interpolation helper. No side effects. +function v = interp1Safe(x, y, xq) + v = cic.core.dispatch("interp1Safe", x, y, xq); +end diff --git a/apps/electrochem/private/runCICApp.m b/apps/electrochem/cic/+cic/+ui/runApp.m similarity index 93% rename from apps/electrochem/private/runCICApp.m rename to apps/electrochem/cic/+cic/+ui/runApp.m index d987edc..da2349c 100644 --- a/apps/electrochem/private/runCICApp.m +++ b/apps/electrochem/cic/+cic/+ui/runApp.m @@ -2,7 +2,7 @@ % Input is the debug context prepared by the public launcher. Output is the app % figure. Side effects are GUI creation, user-driven file I/O, exports, % plotting, and debug trace attachment exactly as in the original entrypoint body. -function fig = runCICApp(debugLog) +function fig = runApp(debugLog) %RUNCICAPP Build and run the app body. S = struct(); @@ -284,7 +284,7 @@ function analyzeCurrentFile() opts.pulseMode = ddPulseMode.Value; opts.usedMeasuredCurrent = cbUseMeasuredCurrent.Value; - A = cicWorkflow("computeCIC", item, opts); + A = cic.ops.computeCIC(item, opts); item.analysis = A; if A.ok addLog(sprintf('%s: Emc=%.6f V, Ema=%.6f V, safe=%d', item.name, A.Emc, A.Ema, A.safe)); @@ -344,7 +344,7 @@ function refreshFileList() function refreshBatchTable() [~, unitLabel] = cicDisplayUnit(); - [C, columnNames] = cicWorkflow("buildBatchTableData", S.items, unitLabel); + [C, columnNames] = cic.view.buildBatchTableData(S.items, unitLabel); tbl.ColumnName = columnNames; if isempty(S.items) tbl.Data = cell(0,8); @@ -386,12 +386,12 @@ function refreshResultsSummary() A = it.analysis; S.txtDetect.Value = sprintf('%s | %s', A.detectMode, A.detectMsg); S.txtDelay.Value = sprintf('%.3f us', 1e6 * A.delay_s); - S.txtArea.Value = cicWorkflow("formatMaybeNum", A.area_cm2,'%.8g cm^2'); + S.txtArea.Value = cic.view.formatMaybeNum(A.area_cm2,'%.8g cm^2'); S.txtEmc.Value = sprintf('%.6f V @ %.6fus', A.Emc, 1e6*A.t_emc); S.txtEma.Value = sprintf('%.6f V @ %.6fus', A.Ema, 1e6*A.t_ema); - S.txtQc.Value = cicWorkflow("formatChargeDensity", A.Qc_C, A.CICc_mCcm2, ddCICUnit.Value); - S.txtQa.Value = cicWorkflow("formatChargeDensity", A.Qa_C, A.CICa_mCcm2, ddCICUnit.Value); - S.txtQt.Value = cicWorkflow("formatChargeDensity", A.Qt_C, A.CICt_mCcm2, ddCICUnit.Value); + S.txtQc.Value = cic.view.formatChargeDensity(A.Qc_C, A.CICc_mCcm2, ddCICUnit.Value); + S.txtQa.Value = cic.view.formatChargeDensity(A.Qa_C, A.CICa_mCcm2, ddCICUnit.Value); + S.txtQt.Value = cic.view.formatChargeDensity(A.Qt_C, A.CICt_mCcm2, ddCICUnit.Value); if A.safe safeText = 'SAFE'; else @@ -505,12 +505,12 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) if strcmp(xChoice,'Sample #') x = A.pt; xlab = 'Sample #'; - cathStartX = cicWorkflow("interp1Safe", A.t, A.pt, A.pulse.cath_start); - cathEndX = cicWorkflow("interp1Safe", A.t, A.pt, A.pulse.cath_end); - anodStartX = cicWorkflow("interp1Safe", A.t, A.pt, A.pulse.anod_start); - anodEndX = cicWorkflow("interp1Safe", A.t, A.pt, A.pulse.anod_end); - emcX = cicWorkflow("interp1Safe", A.t, A.pt, A.t_emc); - emaX = cicWorkflow("interp1Safe", A.t, A.pt, A.t_ema); + cathStartX = cic.ops.interp1Safe(A.t, A.pt, A.pulse.cath_start); + cathEndX = cic.ops.interp1Safe(A.t, A.pt, A.pulse.cath_end); + anodStartX = cic.ops.interp1Safe(A.t, A.pt, A.pulse.anod_start); + anodEndX = cic.ops.interp1Safe(A.t, A.pt, A.pulse.anod_end); + emcX = cic.ops.interp1Safe(A.t, A.pt, A.t_emc); + emaX = cic.ops.interp1Safe(A.t, A.pt, A.t_ema); else x = A.t; xlab = 'Time (s)'; @@ -530,8 +530,8 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) hold(ax,'on'); if cbShowShading.Value - cicWorkflow("shadeWindow", ax, cathStartX, cathEndX, [0.85 0.93 1.00]); - cicWorkflow("shadeWindow", ax, anodStartX, anodEndX, [1.00 0.92 0.85]); + cic.view.shadeWindow(ax, cathStartX, cathEndX, [0.85 0.93 1.00]); + cic.view.shadeWindow(ax, anodStartX, anodEndX, [1.00 0.92 0.85]); end if cbShowLimits.Value @@ -541,14 +541,14 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) 'Color',[0.85 0.2 0.2],'LabelHorizontalAlignment','left'); end - cicWorkflow("addBaselineYLines", ax, A); + cic.view.addBaselineYLines(ax, A); if cbShowMarkers.Value xline(ax, cathStartX, ':', 'Cath start','Color',[0.2 0.4 0.8]); xline(ax, cathEndX, ':', 'Cath end','Color',[0.2 0.4 0.8]); xline(ax, anodStartX, ':', 'Anod start','Color',[0.8 0.4 0.2]); xline(ax, anodEndX, ':', 'Anod end','Color',[0.8 0.4 0.2]); - cicWorkflow("addPaperStyleVTAnnotations", ax, A, xChoice, ... + cic.view.addPaperStyleVTAnnotations(ax, A, xChoice, ... cathStartX, cathEndX, anodStartX, anodEndX, emcX, emaX); end hold(ax,'off'); @@ -566,8 +566,8 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) hold(ax,'on'); if cbShowShading.Value - cicWorkflow("shadeWindow", ax, cathStartX, cathEndX, [0.85 0.93 1.00]); - cicWorkflow("shadeWindow", ax, anodStartX, anodEndX, [1.00 0.92 0.85]); + cic.view.shadeWindow(ax, cathStartX, cathEndX, [0.85 0.93 1.00]); + cic.view.shadeWindow(ax, anodStartX, anodEndX, [1.00 0.92 0.85]); end if cbShowMarkers.Value @@ -575,7 +575,7 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) xline(ax, cathEndX, ':', 'Cath end','Color',[0.2 0.4 0.8]); xline(ax, anodStartX, ':', 'Anod start','Color',[0.8 0.4 0.2]); xline(ax, anodEndX, ':', 'Anod end','Color',[0.8 0.4 0.2]); - cicWorkflow("addPaperStyleITAnnotations", ax, A, xChoice, ... + cic.view.addPaperStyleITAnnotations(ax, A, xChoice, ... cathStartX, cathEndX, anodStartX, anodEndX, emcX, emaX); end hold(ax,'off'); @@ -627,7 +627,7 @@ function exportResultsCSV() end out = fullfile(p,f); [~, unitLabel] = cicDisplayUnit(); - [ok, msg] = cicWorkflow("writeResultsCSV", S.items, out, unitLabel); + [ok, msg] = cic.export.writeResultsCSV(S.items, out, unitLabel); if ~ok uialert(fig,msg,'Export'); return; diff --git a/apps/electrochem/cic/+cic/+view/addBaselineYLines.m b/apps/electrochem/cic/+cic/+view/addBaselineYLines.m new file mode 100644 index 0000000..e2ff6a8 --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/addBaselineYLines.m @@ -0,0 +1,5 @@ +% Expected caller: CIC app plotting helpers. Inputs are an axes and CIC result +% struct. Side effects are limited to drawing baseline guides on the axes. +function addBaselineYLines(ax, A) + cic.core.dispatch("addBaselineYLines", ax, A); +end diff --git a/apps/electrochem/cic/+cic/+view/addPaperStyleITAnnotations.m b/apps/electrochem/cic/+cic/+view/addPaperStyleITAnnotations.m new file mode 100644 index 0000000..aead370 --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/addPaperStyleITAnnotations.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app plotting helpers. Inputs mirror the app-owned IT +% annotation helper. Side effects are limited to annotating the supplied axes. +function addPaperStyleITAnnotations(ax, A, xChoice, cathStartX, cathEndX, anodStartX, anodEndX) + cic.core.dispatch("addPaperStyleITAnnotations", ax, A, xChoice, ... + cathStartX, cathEndX, anodStartX, anodEndX); +end diff --git a/apps/electrochem/cic/+cic/+view/addPaperStyleVTAnnotations.m b/apps/electrochem/cic/+cic/+view/addPaperStyleVTAnnotations.m new file mode 100644 index 0000000..3570bfc --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/addPaperStyleVTAnnotations.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app plotting helpers. Inputs mirror the app-owned VT +% annotation helper. Side effects are limited to annotating the supplied axes. +function addPaperStyleVTAnnotations(ax, A, xChoice, cathStartX, cathEndX, anodStartX, anodEndX, emcX, emaX) + cic.core.dispatch("addPaperStyleVTAnnotations", ax, A, xChoice, ... + cathStartX, cathEndX, anodStartX, anodEndX, emcX, emaX); +end diff --git a/apps/electrochem/cic/+cic/+view/buildBatchTableData.m b/apps/electrochem/cic/+cic/+view/buildBatchTableData.m new file mode 100644 index 0000000..9d56188 --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/buildBatchTableData.m @@ -0,0 +1,6 @@ +% Expected caller: CIC app runner and unit tests. Inputs are item structs and a +% display unit label. Outputs are the stable UI table cell data and column names. +% No file side effects. +function [C, columnNames] = buildBatchTableData(items, unitLabel) + [C, columnNames] = cic.core.dispatch("buildBatchTableData", items, unitLabel); +end diff --git a/apps/electrochem/cic/+cic/+view/formatChargeDensity.m b/apps/electrochem/cic/+cic/+view/formatChargeDensity.m new file mode 100644 index 0000000..75f4721 --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/formatChargeDensity.m @@ -0,0 +1,5 @@ +% Expected caller: CIC app runner. Inputs are charge, density, and display unit +% label. Output is the stable read-only UI text. No side effects. +function txt = formatChargeDensity(charge_C, density_mCcm2, unitLabel) + txt = cic.core.dispatch("formatChargeDensity", charge_C, density_mCcm2, unitLabel); +end diff --git a/apps/electrochem/cic/+cic/+view/formatMaybeNum.m b/apps/electrochem/cic/+cic/+view/formatMaybeNum.m new file mode 100644 index 0000000..846b945 --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/formatMaybeNum.m @@ -0,0 +1,5 @@ +% Expected caller: CIC app runner. Inputs are a numeric value and sprintf format. +% Output is the stable UI text for finite or missing values. No side effects. +function txt = formatMaybeNum(value, fmt) + txt = cic.core.dispatch("formatMaybeNum", value, fmt); +end diff --git a/apps/electrochem/cic/+cic/+view/shadeWindow.m b/apps/electrochem/cic/+cic/+view/shadeWindow.m new file mode 100644 index 0000000..00a389d --- /dev/null +++ b/apps/electrochem/cic/+cic/+view/shadeWindow.m @@ -0,0 +1,5 @@ +% Expected caller: CIC app plotting helpers. Inputs are an axes, x-window, and +% color. Side effects are limited to adding the app-owned patch to the axes. +function shadeWindow(ax, x1, x2, color) + cic.core.dispatch("shadeWindow", ax, x1, x2, color); +end diff --git a/apps/electrochem/labkit_CIC_app.m b/apps/electrochem/cic/labkit_CIC_app.m similarity index 98% rename from apps/electrochem/labkit_CIC_app.m rename to apps/electrochem/cic/labkit_CIC_app.m index 0baa39a..b436691 100644 --- a/apps/electrochem/labkit_CIC_app.m +++ b/apps/electrochem/cic/labkit_CIC_app.m @@ -38,7 +38,7 @@ error('labkit_CIC_app:TooManyOutputs', 'labkit_CIC_app returns at most the app figure handle.'); end - fig = runCICApp(debugLog); + fig = cic.ui.runApp(debugLog); if nargout >= 1 varargout{1} = fig; end diff --git a/apps/electrochem/private/cscWorkflow.m b/apps/electrochem/csc/+csc/+core/dispatch.m similarity index 96% rename from apps/electrochem/private/cscWorkflow.m rename to apps/electrochem/csc/+csc/+core/dispatch.m index 3bc952b..4ccd49f 100644 --- a/apps/electrochem/private/cscWorkflow.m +++ b/apps/electrochem/csc/+csc/+core/dispatch.m @@ -1,10 +1,10 @@ -% App-owned CSC workflow helper dispatch. Expected caller: labkit_CSC_app -% callbacks and workflow tests. Inputs are a command +% App-owned CSC helper core dispatch. Expected caller: labkit_CSC_app +% callbacks and package tests. Inputs are a command % string plus the original helper arguments; outputs match the selected helper. % This helper has no file side effects. -function varargout = cscWorkflow(command, varargin) -%CSCWORKFLOW Dispatch app-owned CSC calculation helpers. -% Expected caller: labkit_CSC_app callbacks and workflow tests. Inputs are a +function varargout = dispatch(command, varargin) +%DISPATCH Route CSC package wrapper calls to app-owned helpers. +% Expected caller: labkit_CSC_app callbacks and package tests. Inputs are a % command string plus the original helper arguments. % Outputs match the selected helper. This helper has no file side effects. diff --git a/apps/electrochem/csc/+csc/+ops/computeCSC.m b/apps/electrochem/csc/+csc/+ops/computeCSC.m new file mode 100644 index 0000000..45fa6b3 --- /dev/null +++ b/apps/electrochem/csc/+csc/+ops/computeCSC.m @@ -0,0 +1,6 @@ +% Expected caller: CSC app runner and unit tests. Inputs are a CV/CT curve struct +% and CSC options. Output is the stable CSC comparison result struct. No file or +% UI side effects. +function A = computeCSC(curve, opts) + A = csc.core.dispatch("computeCSC", curve, opts); +end diff --git a/apps/electrochem/private/runCSCApp.m b/apps/electrochem/csc/+csc/+ui/runApp.m similarity index 99% rename from apps/electrochem/private/runCSCApp.m rename to apps/electrochem/csc/+csc/+ui/runApp.m index 1db978d..55a5555 100644 --- a/apps/electrochem/private/runCSCApp.m +++ b/apps/electrochem/csc/+csc/+ui/runApp.m @@ -2,7 +2,7 @@ % Input is the debug context prepared by the public launcher. Output is the app % figure. Side effects are GUI creation, user-driven file I/O, exports, % plotting, and debug trace attachment exactly as in the original entrypoint body. -function fig = runCSCApp(debugLog) +function fig = runApp(debugLog) %RUNCSCAPP Build and run the app body. S = struct(); @@ -461,7 +461,7 @@ function refreshCompare() opts.mode = ddMode.Value; opts.scanRate = S.scanRate; opts.area_cm2 = edArea.Value; - R = cscWorkflow("computeCSC", c, opts); + R = csc.ops.computeCSC(c, opts); if ~R.ok txtQct.Value = R.message; diff --git a/apps/electrochem/labkit_CSC_app.m b/apps/electrochem/csc/labkit_CSC_app.m similarity index 97% rename from apps/electrochem/labkit_CSC_app.m rename to apps/electrochem/csc/labkit_CSC_app.m index 6e687b8..3dccea6 100644 --- a/apps/electrochem/labkit_CSC_app.m +++ b/apps/electrochem/csc/labkit_CSC_app.m @@ -37,7 +37,7 @@ % Application state container - fig = runCSCApp(debugLog); + fig = csc.ui.runApp(debugLog); if nargout >= 1 varargout{1} = fig; end diff --git a/apps/electrochem/private/eisWorkflow.m b/apps/electrochem/eis/+eis/+core/dispatch.m similarity index 95% rename from apps/electrochem/private/eisWorkflow.m rename to apps/electrochem/eis/+eis/+core/dispatch.m index 71144af..4bd14bf 100644 --- a/apps/electrochem/private/eisWorkflow.m +++ b/apps/electrochem/eis/+eis/+core/dispatch.m @@ -1,10 +1,10 @@ -% App-owned EIS workflow helper dispatch. Expected caller: labkit_EIS_app -% callbacks and workflow tests. Inputs are a command +% App-owned EIS helper core dispatch. Expected caller: labkit_EIS_app +% callbacks and package tests. Inputs are a command % string plus the original helper arguments; outputs match the selected helper. % This helper has no file side effects. -function varargout = eisWorkflow(command, varargin) -%EISWORKFLOW Dispatch app-owned EIS plot/export helpers. -% Expected caller: labkit_EIS_app callbacks and workflow tests. Inputs are a +function varargout = dispatch(command, varargin) +%DISPATCH Route EIS package wrapper calls to app-owned helpers. +% Expected caller: labkit_EIS_app callbacks and package tests. Inputs are a % command string plus the original helper arguments. % Outputs match the selected helper. This helper has no file side effects. diff --git a/apps/electrochem/eis/+eis/+export/buildExportTable.m b/apps/electrochem/eis/+eis/+export/buildExportTable.m new file mode 100644 index 0000000..3eb06c9 --- /dev/null +++ b/apps/electrochem/eis/+eis/+export/buildExportTable.m @@ -0,0 +1,6 @@ +% Expected caller: EIS app runner and export tests. Inputs are EIS item structs, +% axis labels, and log flags. Output is the stable EIS export table. No file side +% effects. +function T = buildExportTable(items, xName, yName, useLogX, useLogY) + T = eis.core.dispatch("buildExportTable", items, xName, yName, useLogX, useLogY); +end diff --git a/apps/electrochem/eis/+eis/+ops/valuesForAxis.m b/apps/electrochem/eis/+eis/+ops/valuesForAxis.m new file mode 100644 index 0000000..bcceb12 --- /dev/null +++ b/apps/electrochem/eis/+eis/+ops/valuesForAxis.m @@ -0,0 +1,5 @@ +% Expected caller: EIS app runner and unit tests. Inputs are an EIS item struct +% and axis label. Output is the selected numeric vector. No side effects. +function values = valuesForAxis(item, axisName) + values = eis.core.dispatch("valuesForAxis", item, axisName); +end diff --git a/apps/electrochem/private/runEISApp.m b/apps/electrochem/eis/+eis/+ui/runApp.m similarity index 99% rename from apps/electrochem/private/runEISApp.m rename to apps/electrochem/eis/+eis/+ui/runApp.m index d9ee3c2..9f3772f 100644 --- a/apps/electrochem/private/runEISApp.m +++ b/apps/electrochem/eis/+eis/+ui/runApp.m @@ -2,7 +2,7 @@ % Input is the debug context prepared by the public launcher. Output is the app % figure. Side effects are GUI creation, user-driven file I/O, exports, % plotting, and debug trace attachment exactly as in the original entrypoint body. -function fig = runEISApp(debugLog) +function fig = runApp(debugLog) %RUNEISAPP Build and run the app body. S = struct(); @@ -286,7 +286,7 @@ function onExportCSV(~, ~) return; end - T = eisWorkflow("buildExportTable", items, ddX.Value, ddY.Value, cbLogX.Value, cbLogY.Value); + T = eis.export.buildExportTable(items, ddX.Value, ddY.Value, cbLogX.Value, cbLogY.Value); out = fullfile(p, f); writetable(T, out); addLog(sprintf('Exported CSV: %s', out)); diff --git a/apps/electrochem/eis/+eis/+view/buildSummary.m b/apps/electrochem/eis/+eis/+view/buildSummary.m new file mode 100644 index 0000000..bbd2b96 --- /dev/null +++ b/apps/electrochem/eis/+eis/+view/buildSummary.m @@ -0,0 +1,5 @@ +% Expected caller: EIS app runner. Input is EIS item structs. Output is the +% stable summary text cell array. No side effects. +function summary = buildSummary(items) + summary = eis.core.dispatch("buildSummary", items); +end diff --git a/apps/electrochem/eis/+eis/+view/labelForAxis.m b/apps/electrochem/eis/+eis/+view/labelForAxis.m new file mode 100644 index 0000000..51ed84b --- /dev/null +++ b/apps/electrochem/eis/+eis/+view/labelForAxis.m @@ -0,0 +1,5 @@ +% Expected caller: EIS app runner. Input is an axis selection label. Output is +% the display label used by the app. No side effects. +function txt = labelForAxis(axisName) + txt = eis.core.dispatch("labelForAxis", axisName); +end diff --git a/apps/electrochem/eis/+eis/+view/plotOverlay.m b/apps/electrochem/eis/+eis/+view/plotOverlay.m new file mode 100644 index 0000000..0889a51 --- /dev/null +++ b/apps/electrochem/eis/+eis/+view/plotOverlay.m @@ -0,0 +1,5 @@ +% Expected caller: EIS app runner. Inputs are an axes, EIS items, and plot +% options. Output is legend labels. Side effects are limited to redrawing axes. +function labels = plotOverlay(ax, items, opts) + labels = eis.core.dispatch("plotOverlay", ax, items, opts); +end diff --git a/apps/electrochem/labkit_EIS_app.m b/apps/electrochem/eis/labkit_EIS_app.m similarity index 96% rename from apps/electrochem/labkit_EIS_app.m rename to apps/electrochem/eis/labkit_EIS_app.m index ebdab79..f7594cc 100644 --- a/apps/electrochem/labkit_EIS_app.m +++ b/apps/electrochem/eis/labkit_EIS_app.m @@ -17,7 +17,7 @@ error('labkit_EIS_app:TooManyOutputs', 'labkit_EIS_app returns at most the app figure handle.'); end - fig = runEISApp(debugLog); + fig = eis.ui.runApp(debugLog); if nargout >= 1 varargout{1} = fig; end diff --git a/apps/electrochem/electrochemWorkflow.m b/apps/electrochem/electrochemWorkflow.m deleted file mode 100644 index eac8d72..0000000 --- a/apps/electrochem/electrochemWorkflow.m +++ /dev/null @@ -1,23 +0,0 @@ -function varargout = electrochemWorkflow(appKey, command, varargin) -%ELECTROCHEMWORKFLOW Dispatch app-owned electrochem workflow helpers. -% Expected caller: electrochem app tests and migration-time workflow checks. -% Inputs are an app key, a workflow command, and command-specific arguments. -% Outputs match the selected app-owned helper. Side effects are limited to CSV -% export commands and app-owned plot drawing commands on caller axes. - - switch string(appKey) - case "chronoOverlay" - [varargout{1:nargout}] = chronoOverlayWorkflow(command, varargin{:}); - case "cic" - [varargout{1:nargout}] = cicWorkflow(command, varargin{:}); - case "csc" - [varargout{1:nargout}] = cscWorkflow(command, varargin{:}); - case "eis" - [varargout{1:nargout}] = eisWorkflow(command, varargin{:}); - case "vtResistance" - [varargout{1:nargout}] = vtResistanceWorkflow(command, varargin{:}); - otherwise - error('labkit:Electrochem:UnknownWorkflow', ... - 'Unknown electrochem workflow key: %s.', appKey); - end -end diff --git a/apps/electrochem/private/vtResistanceWorkflow.m b/apps/electrochem/vt_resistance/+vt_resistance/+core/dispatch.m similarity index 97% rename from apps/electrochem/private/vtResistanceWorkflow.m rename to apps/electrochem/vt_resistance/+vt_resistance/+core/dispatch.m index f762294..736d938 100644 --- a/apps/electrochem/private/vtResistanceWorkflow.m +++ b/apps/electrochem/vt_resistance/+vt_resistance/+core/dispatch.m @@ -1,12 +1,11 @@ -% App-owned VT resistance workflow helper dispatch. Expected caller: -% labkit_VTResistance_app callbacks and workflow tests. +% App-owned VT resistance helper core dispatch. Expected caller: +% labkit_VTResistance_app callbacks and package tests. % Inputs are a command string plus the original helper arguments; outputs match % the selected helper. Side effects are limited to CSV export writes and drawing % app-owned plot annotations on caller axes. -function varargout = vtResistanceWorkflow(command, varargin) -%VTRESISTANCEWORKFLOW Dispatch app-owned VT resistance helpers. -% Expected caller: labkit_VTResistance_app callbacks and temporary compatibility -% workflow tests. Inputs are a command string plus the original helper arguments. +function varargout = dispatch(command, varargin) +%DISPATCH Route VT resistance package wrapper calls to app-owned helpers. +% Expected caller: labkit_VTResistance_app callbacks and % package tests. Inputs are a command string plus the original helper arguments. % Outputs match the selected helper. Side effects are limited to CSV export % writes and drawing app-owned plot annotations on caller axes. diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+export/buildResultsTable.m b/apps/electrochem/vt_resistance/+vt_resistance/+export/buildResultsTable.m new file mode 100644 index 0000000..ef9a06e --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+export/buildResultsTable.m @@ -0,0 +1,6 @@ +% Expected caller: VT resistance app runner and export tests. Input is item +% structs. Output is the stable VT resistance CSV result table. No file side +% effects. +function T = buildResultsTable(items) + T = vt_resistance.core.dispatch("buildResultsTable", items); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+export/writeResultsCSV.m b/apps/electrochem/vt_resistance/+vt_resistance/+export/writeResultsCSV.m new file mode 100644 index 0000000..32c49eb --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+export/writeResultsCSV.m @@ -0,0 +1,5 @@ +% Expected caller: VT resistance app runner and export tests. Inputs are item +% structs and output filepath. Side effect is writing the stable VT CSV file. +function [ok, msg] = writeResultsCSV(items, filepath) + [ok, msg] = vt_resistance.core.dispatch("writeResultsCSV", items, filepath); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+ops/computeResistance.m b/apps/electrochem/vt_resistance/+vt_resistance/+ops/computeResistance.m new file mode 100644 index 0000000..88ba1fe --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+ops/computeResistance.m @@ -0,0 +1,6 @@ +% Expected caller: VT resistance app runner and unit tests. Inputs are a DTA item +% struct and option struct. Output is the stable resistance result struct. No +% file or UI side effects. +function A = computeResistance(item, opts) + A = vt_resistance.core.dispatch("computeResistance", item, opts); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+ops/interp1Safe.m b/apps/electrochem/vt_resistance/+vt_resistance/+ops/interp1Safe.m new file mode 100644 index 0000000..441a02a --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+ops/interp1Safe.m @@ -0,0 +1,6 @@ +% Expected caller: VT resistance app plotting helpers. Inputs are vectors x/y and +% query points. Output mirrors the app-owned safe interpolation helper. No side +% effects. +function v = interp1Safe(x, y, xq) + v = vt_resistance.core.dispatch("interp1Safe", x, y, xq); +end diff --git a/apps/electrochem/private/runVTResistanceApp.m b/apps/electrochem/vt_resistance/+vt_resistance/+ui/runApp.m similarity index 88% rename from apps/electrochem/private/runVTResistanceApp.m rename to apps/electrochem/vt_resistance/+vt_resistance/+ui/runApp.m index 9ecd0ee..610e073 100644 --- a/apps/electrochem/private/runVTResistanceApp.m +++ b/apps/electrochem/vt_resistance/+vt_resistance/+ui/runApp.m @@ -2,7 +2,7 @@ % Input is the debug context prepared by the public launcher. Output is the app % figure. Side effects are GUI creation, user-driven file I/O, exports, % plotting, and debug trace attachment exactly as in the original entrypoint body. -function fig = runVTResistanceApp(debugLog) +function fig = runApp(debugLog) %RUNVTRESISTANCEAPP Build and run the app body. S = struct(); @@ -209,7 +209,7 @@ function analyzeCurrentFile() opts.voltageMode = ddVoltageMode.Value; opts.pulseMode = ddPulseMode.Value; - A = vtResistanceWorkflow("computeResistance", item, opts); + A = vt_resistance.ops.computeResistance(item, opts); if A.ok addLog(sprintf('%s: Rc=%.6g ohm, Ra=%.6g ohm, Ravg=%.6g ohm', ... item.name, A.Rc_abs_ohm, A.Ra_abs_ohm, A.Ravg_abs_ohm)); @@ -273,7 +273,7 @@ function refreshBatchTable() tbl.Data = cell(0,9); return; end - tbl.Data = vtResistanceWorkflow("buildBatchTableData", S.items); + tbl.Data = vt_resistance.view.buildBatchTableData(S.items); end function refreshResultsSummary() @@ -312,8 +312,8 @@ function refreshResultsSummary() S.txtAnodIV.Value = sprintf('I=%.6e A | Vss=%.6f V | dV=%.6f V', A.Ia_est_A, A.Va_ss_V, A.dVa_V); S.txtCathBase.Value = sprintf('%.6f V', A.Vc_baseline_V); S.txtAnodBase.Value = sprintf('%.6f V', A.Va_baseline_V); - S.txtCathBaseWin.Value = vtResistanceWorkflow("formatDurationUs", A.cathBaselineWindow_s); - S.txtAnodBaseWin.Value = vtResistanceWorkflow("formatDurationUs", A.anodBaselineWindow_s); + S.txtCathBaseWin.Value = vt_resistance.view.formatDurationUs(A.cathBaselineWindow_s); + S.txtAnodBaseWin.Value = vt_resistance.view.formatDurationUs(A.anodBaselineWindow_s); S.txtCathR.Value = sprintf('%.6g ohm (signed %.6g)', A.Rc_abs_ohm, A.Rc_ohm); S.txtAnodR.Value = sprintf('%.6g ohm (signed %.6g)', A.Ra_abs_ohm, A.Ra_ohm); S.txtAvgR.Value = sprintf('%.6g ohm', A.Ravg_abs_ohm); @@ -361,18 +361,18 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) if strcmp(xChoice,'Sample #') x = A.pt; xlab = 'Sample #'; - cathStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.cath_start); - cathEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.cath_end); - anodStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.anod_start); - anodEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.anod_end); - cathBaseStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.pre_start); - cathBaseEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.pulse.pre_end); - anodBaseStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.anodBaselineStart); - anodBaseEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.anodBaselineEnd); - cSteadyStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.cathSteadyStart); - cSteadyEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.cathSteadyEnd); - aSteadyStartX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.anodSteadyStart); - aSteadyEndX = vtResistanceWorkflow("interp1Safe", A.t, A.pt, A.anodSteadyEnd); + cathStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.cath_start); + cathEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.cath_end); + anodStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.anod_start); + anodEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.anod_end); + cathBaseStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.pre_start); + cathBaseEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.pulse.pre_end); + anodBaseStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.anodBaselineStart); + anodBaseEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.anodBaselineEnd); + cSteadyStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.cathSteadyStart); + cSteadyEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.cathSteadyEnd); + aSteadyStartX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.anodSteadyStart); + aSteadyEndX = vt_resistance.ops.interp1Safe(A.t, A.pt, A.anodSteadyEnd); else x = A.t; xlab = 'Time (s)'; @@ -403,10 +403,10 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) end if cbShowShading.Value - vtResistanceWorkflow("shadeWindow", ax, cathStartX, cathEndX, [0.90 0.95 1.00], 0.12); - vtResistanceWorkflow("shadeWindow", ax, anodStartX, anodEndX, [1.00 0.94 0.88], 0.12); - vtResistanceWorkflow("shadeWindow", ax, cSteadyStartX, cSteadyEndX, [0.65 0.82 1.00], 0.22); - vtResistanceWorkflow("shadeWindow", ax, aSteadyStartX, aSteadyEndX, [1.00 0.75 0.55], 0.22); + vt_resistance.view.shadeWindow(ax, cathStartX, cathEndX, [0.90 0.95 1.00], 0.12); + vt_resistance.view.shadeWindow(ax, anodStartX, anodEndX, [1.00 0.94 0.88], 0.12); + vt_resistance.view.shadeWindow(ax, cSteadyStartX, cSteadyEndX, [0.65 0.82 1.00], 0.22); + vt_resistance.view.shadeWindow(ax, aSteadyStartX, aSteadyEndX, [1.00 0.75 0.55], 0.22); end if cbShowMarkers.Value xline(ax, cathStartX, ':', 'Cath start','Color',[0.2 0.4 0.8]); @@ -414,10 +414,10 @@ function plotOneAxis(ax, A, xChoice, yChoice, showGrid) xline(ax, anodStartX, ':', 'Anod start','Color',[0.8 0.4 0.2]); xline(ax, anodEndX, ':', 'Anod end','Color',[0.8 0.4 0.2]); if startsWith(yChoice,'VT') - vtResistanceWorkflow("addResistanceVTAnnotations", ax, A, cathBaseStartX, cathBaseEndX, anodBaseStartX, anodBaseEndX, ... + vt_resistance.view.addResistanceVTAnnotations(ax, A, cathBaseStartX, cathBaseEndX, anodBaseStartX, anodBaseEndX, ... cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, cathStartX, cathEndX, anodStartX, anodEndX); else - vtResistanceWorkflow("addResistanceITAnnotations", ax, A, cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, ... + vt_resistance.view.addResistanceITAnnotations(ax, A, cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, ... cathStartX, cathEndX, anodStartX, anodEndX); end end @@ -471,7 +471,7 @@ function exportResultsCSV() return; end out = fullfile(p,f); - [ok, msg] = vtResistanceWorkflow("writeResultsCSV", S.items, out); + [ok, msg] = vt_resistance.export.writeResultsCSV(S.items, out); if ~ok uialert(fig,msg,'Export'); return; diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceITAnnotations.m b/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceITAnnotations.m new file mode 100644 index 0000000..0132f42 --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceITAnnotations.m @@ -0,0 +1,7 @@ +% Expected caller: VT resistance app plotting helpers. Inputs mirror the +% app-owned current annotation helper. Side effects are limited to axes labels. +function addResistanceITAnnotations(ax, A, cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, cathStartX, cathEndX, anodStartX, anodEndX) + vt_resistance.core.dispatch("addResistanceITAnnotations", ax, A, ... + cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, ... + cathStartX, cathEndX, anodStartX, anodEndX); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceVTAnnotations.m b/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceVTAnnotations.m new file mode 100644 index 0000000..4287cd2 --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+view/addResistanceVTAnnotations.m @@ -0,0 +1,8 @@ +% Expected caller: VT resistance app plotting helpers. Inputs mirror the +% app-owned voltage annotation helper. Side effects are limited to axes labels. +function addResistanceVTAnnotations(ax, A, cathBaseStartX, cathBaseEndX, anodBaseStartX, anodBaseEndX, cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, cathStartX, cathEndX, anodStartX, anodEndX) + vt_resistance.core.dispatch("addResistanceVTAnnotations", ax, A, ... + cathBaseStartX, cathBaseEndX, anodBaseStartX, anodBaseEndX, ... + cSteadyStartX, cSteadyEndX, aSteadyStartX, aSteadyEndX, ... + cathStartX, cathEndX, anodStartX, anodEndX); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+view/buildBatchTableData.m b/apps/electrochem/vt_resistance/+vt_resistance/+view/buildBatchTableData.m new file mode 100644 index 0000000..fde6d3a --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+view/buildBatchTableData.m @@ -0,0 +1,5 @@ +% Expected caller: VT resistance app runner and unit tests. Input is item structs. +% Output is the stable UI table cell data. No file side effects. +function C = buildBatchTableData(items) + C = vt_resistance.core.dispatch("buildBatchTableData", items); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+view/formatDurationUs.m b/apps/electrochem/vt_resistance/+vt_resistance/+view/formatDurationUs.m new file mode 100644 index 0000000..a1ee40f --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+view/formatDurationUs.m @@ -0,0 +1,5 @@ +% Expected caller: VT resistance app runner. Input is a duration in seconds. +% Output is the stable microsecond display text. No side effects. +function txt = formatDurationUs(value) + txt = vt_resistance.core.dispatch("formatDurationUs", value); +end diff --git a/apps/electrochem/vt_resistance/+vt_resistance/+view/shadeWindow.m b/apps/electrochem/vt_resistance/+vt_resistance/+view/shadeWindow.m new file mode 100644 index 0000000..ee2a9d9 --- /dev/null +++ b/apps/electrochem/vt_resistance/+vt_resistance/+view/shadeWindow.m @@ -0,0 +1,9 @@ +% Expected caller: VT resistance app plotting helpers. Inputs are an axes, +% x-window, color, and optional alpha. Side effects are limited to axes drawing. +function shadeWindow(ax, x1, x2, color, alpha) + if nargin < 5 + vt_resistance.core.dispatch("shadeWindow", ax, x1, x2, color); + else + vt_resistance.core.dispatch("shadeWindow", ax, x1, x2, color, alpha); + end +end diff --git a/apps/electrochem/labkit_VTResistance_app.m b/apps/electrochem/vt_resistance/labkit_VTResistance_app.m similarity index 96% rename from apps/electrochem/labkit_VTResistance_app.m rename to apps/electrochem/vt_resistance/labkit_VTResistance_app.m index 1c4c950..b6fc102 100644 --- a/apps/electrochem/labkit_VTResistance_app.m +++ b/apps/electrochem/vt_resistance/labkit_VTResistance_app.m @@ -25,7 +25,7 @@ error('labkit_VTResistance_app:TooManyOutputs', 'labkit_VTResistance_app returns at most the app figure handle.'); end - fig = runVTResistanceApp(debugLog); + fig = vt_resistance.ui.runApp(debugLog); if nargout >= 1 varargout{1} = fig; end diff --git a/buildfile.m b/buildfile.m index adbc232..53bfc4c 100644 --- a/buildfile.m +++ b/buildfile.m @@ -154,7 +154,7 @@ function packageDryRunTask(~) taskSpec("testAppsGui", "Run all app GUI tests.", "Suites", "apps", "IncludeGui", true), ... taskSpec("testAppsSmokeGui", "Run cross-app GUI smoke tests.", "Suites", "apps/smoke", "IncludeGui", true), ... taskSpec("testGuiStructural", "Run noninteractive GUI structural tests.", "Suites", "gui", "Tags", "Structural", "IncludeGui", true), ... - taskSpec("testGuiGesture", "Run noninteractive/manual GUI gesture tests.", "Tags", "Gesture", "IncludeGui", true), ... + taskSpec("testGuiGesture", "Run noninteractive GUI gesture tests.", "Tags", "Gesture", "IncludeGui", true), ... taskSpec("coverage", "Run official tests with coverage artifacts.", "Tags", ["Unit", "Integration"], "IncludeCoverage", true), ... taskSpec("checkProject", "Verify MATLAB Project metadata and path setup.", "RunTests", false), ... taskSpec("packageDryRun", "Verify package boundary inventory without exporting.", "RunTests", false)]; diff --git a/docs/apps.md b/docs/apps.md index ccb4fe8..378d9b3 100644 --- a/docs/apps.md +++ b/docs/apps.md @@ -119,6 +119,8 @@ Do not add new string-dispatch workflow adapters such as `*Workflow.m` for tests. Tests should call the app-owned package function that owns the behavior. Use `apps//private/` only for helpers that are genuinely shared by multiple apps in that family and are not ready for a reusable `+labkit` facade. +Existing DIC and wearable `private/` runners are migration debt, not the +preferred app structure. ## New App Checklist diff --git a/docs/architecture.md b/docs/architecture.md index bd894aa..d6f5a5f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -89,6 +89,11 @@ use a fixed `+app` namespace for every app. `apps//private/` should be reserved for helpers that are genuinely shared by multiple apps in that family and are not ready for a reusable `+labkit` facade. +Current image-measurement and electrochemistry apps already follow the +app-owned package shape. Older DIC and wearable apps may still contain +`private/` runners while they are being migrated; do not copy that shape into +new app work. + ## Library Extraction Rule A helper may move into `+labkit` only when it satisfies all of these: diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULod.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULod.xml new file mode 100644 index 0000000..0835748 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULop.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULop.xml new file mode 100644 index 0000000..e5aabde --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/HugybYBEuEkod2bN8Cjjv9fGULop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUd.xml new file mode 100644 index 0000000..a2391be --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUp.xml new file mode 100644 index 0000000..497db00 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/If8SwhnBtCmrtMxNhP-TsJsHUNUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zId.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zId.xml new file mode 100644 index 0000000..65b0c24 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zIp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zIp.xml new file mode 100644 index 0000000..daf5691 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/IqfyvktccVsxgbnNuEPzKqzy7zIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEd.xml new file mode 100644 index 0000000..db6ff84 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEp.xml new file mode 100644 index 0000000..910333f --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/Ll50Ztwg-ifpOl8RgBHWQt2AeTEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsd.xml new file mode 100644 index 0000000..419d129 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsp.xml new file mode 100644 index 0000000..263dc8a --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/VXq-RV6fbWaSXOvrjzgnrwIDBlsp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwd.xml new file mode 100644 index 0000000..533009e --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwp.xml new file mode 100644 index 0000000..677497a --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/mWk__sCaD7Ux4zNDnBrnl1l4puwp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/integration/project/ProjectDocumentationGuardrailTest.m b/tests/integration/project/ProjectDocumentationGuardrailTest.m index 3f38bb2..5524edd 100644 --- a/tests/integration/project/ProjectDocumentationGuardrailTest.m +++ b/tests/integration/project/ProjectDocumentationGuardrailTest.m @@ -52,6 +52,24 @@ function privateHelperContractDebtDoesNotGrow(testCase) fprintf('Private helper contract debt inventory: %d files missing top-of-file contracts.\n', ... totalMissing); end + + function appOwnedPackageHelpersDocumentImplementationContracts(testCase) + root = setupLabKitTestPath(); + files = collectAppOwnedPackageFiles(root); + testCase.assertFalse(isempty(files), ... + 'App-owned package contract guardrail should scan package helper files.'); + + missing = strings(1, 0); + for k = 1:numel(files) + if ~hasTopFileContract(files(k)) + missing(end+1) = string(relativePath(root, files(k))); %#ok + end + end + + testCase.verifyTrue(isempty(missing), ... + ['App-owned package helpers need top-of-file implementation contracts: ' ... + strjoin(cellstr(missing), ', ')]); + end end end @@ -106,6 +124,19 @@ function privateHelperContractDebtDoesNotGrow(testCase) end end +function files = collectAppOwnedPackageFiles(root) + entries = dir(fullfile(root, 'apps', '**', '+*', '**', '*.m')); + files = strings(1, 0); + for k = 1:numel(entries) + filepath = string(fullfile(entries(k).folder, entries(k).name)); + if contains(filepath, [filesep 'private' filesep]) + continue; + end + files(end+1) = filepath; %#ok + end + files = unique(files); +end + function folders = collectPrivateDirs(folder) folders = strings(1, 0); if ~isfolder(folder) diff --git a/tests/integration/project/ProjectStructureGuardrailTest.m b/tests/integration/project/ProjectStructureGuardrailTest.m index b67cd3e..5979753 100644 --- a/tests/integration/project/ProjectStructureGuardrailTest.m +++ b/tests/integration/project/ProjectStructureGuardrailTest.m @@ -146,6 +146,32 @@ function imageMeasurementAppsUseOwnedPackageNamespaces(testCase) {'+export', '+io', '+ops', '+state', '+view'}); end + function electrochemAppsUseOwnedPackageNamespaces(testCase) + root = setupLabKitTestPath(); + + testCase.verifyFalse(isfolder(fullfile(root, 'apps', 'electrochem', 'private')), ... + 'Electrochem apps should use app-owned packages, not apps/electrochem/private.'); + workflowFiles = dir(fullfile(root, 'apps', 'electrochem', '**', '*Workflow.m')); + testCase.verifyTrue(isempty(workflowFiles), ... + 'Electrochem apps should not keep string-dispatch workflow adapters.'); + + assertElectrochemPackageLayout(testCase, root, ... + 'chrono_overlay', 'chrono_overlay', ... + {'+core', '+export', '+ops', '+ui', '+view'}); + assertElectrochemPackageLayout(testCase, root, ... + 'cic', 'cic', ... + {'+core', '+export', '+ops', '+ui', '+view'}); + assertElectrochemPackageLayout(testCase, root, ... + 'csc', 'csc', ... + {'+core', '+ops', '+ui'}); + assertElectrochemPackageLayout(testCase, root, ... + 'eis', 'eis', ... + {'+core', '+export', '+ops', '+ui', '+view'}); + assertElectrochemPackageLayout(testCase, root, ... + 'vt_resistance', 'vt_resistance', ... + {'+core', '+export', '+ops', '+ui', '+view'}); + end + function sensitiveSampleHygieneScansTrackedText(testCase) root = setupLabKitTestPath(); files = collectTrackedTextScope(root); @@ -173,6 +199,10 @@ function startupPathKeepsPrivateHelpersPrivate(testCase) 'startup_labkit should add apps/ to the path.'); testCase.verifyTrue(pathContains(fullfile(root, 'apps', 'electrochem')), ... 'startup_labkit should add nested app category folders to the path.'); + testCase.verifyTrue(pathContains(fullfile(root, 'apps', 'electrochem', 'cic')), ... + 'startup_labkit should add nested electrochem app folders.'); + testCase.verifyFalse(pathContains(fullfile(root, 'apps', 'electrochem', 'cic', '+cic')), ... + 'startup_labkit should not expose electrochem app-owned package folders directly.'); testCase.verifyTrue(pathContains(fullfile(root, 'apps', 'image_measurement', 'curvature')), ... 'startup_labkit should add nested image measurement app folders.'); testCase.verifyFalse(pathContains(fullfile(root, 'apps', 'image_measurement', 'curvature', 'private')), ... @@ -183,6 +213,51 @@ function startupPathKeepsPrivateHelpersPrivate(testCase) end end +function assertElectrochemPackageLayout(testCase, root, appFolder, packageName, componentDirs) + appDir = fullfile(root, 'apps', 'electrochem', appFolder); + packageDir = fullfile(appDir, ['+' packageName]); + + testCase.verifyTrue(isfolder(appDir), ... + ['Missing electrochem app folder: apps/electrochem/' appFolder]); + testCase.verifyTrue(isfile(fullfile(appDir, appEntrypointName(appFolder))), ... + ['Missing electrochem app entrypoint under ' relativePath(root, appDir)]); + testCase.verifyFalse(isfolder(fullfile(appDir, 'private')), ... + ['Electrochem app should use an app-owned package, not private/: ' appFolder]); + testCase.verifyFalse(isfolder(fullfile(appDir, '+app')), ... + ['Electrochem app should not use a fixed +app namespace: ' appFolder]); + workflowFiles = dir(fullfile(appDir, '*Workflow.m')); + testCase.verifyTrue(isempty(workflowFiles), ... + ['Electrochem app should not keep workflow dispatch adapters: ' appFolder]); + testCase.verifyTrue(isfolder(packageDir), ... + ['Missing electrochem app-owned package namespace: ' relativePath(root, packageDir)]); + + packageFiles = dir(fullfile(packageDir, '**', '*.m')); + testCase.verifyFalse(isempty(packageFiles), ... + ['Electrochem app-owned package should contain helper files: ' relativePath(root, packageDir)]); + for iDir = 1:numel(componentDirs) + testCase.verifyTrue(isfolder(fullfile(packageDir, componentDirs{iDir})), ... + ['Missing electrochem component package ' componentDirs{iDir} ... + ' under ' relativePath(root, packageDir)]); + end +end + +function name = appEntrypointName(appFolder) + switch appFolder + case 'chrono_overlay' + name = 'labkit_ChronoOverlay_app.m'; + case 'cic' + name = 'labkit_CIC_app.m'; + case 'csc' + name = 'labkit_CSC_app.m'; + case 'eis' + name = 'labkit_EIS_app.m'; + case 'vt_resistance' + name = 'labkit_VTResistance_app.m'; + otherwise + error('Unknown electrochem app folder: %s', appFolder); + end +end + function assertImageMeasurementPackageLayout(testCase, root, appFolder, packageName, componentDirs) appDir = fullfile(root, 'apps', 'image_measurement', appFolder); packageDir = fullfile(appDir, ['+' packageName]); diff --git a/tests/integration/project/StartupBoundariesTest.m b/tests/integration/project/StartupBoundariesTest.m index 376d37b..65abd81 100644 --- a/tests/integration/project/StartupBoundariesTest.m +++ b/tests/integration/project/StartupBoundariesTest.m @@ -22,6 +22,14 @@ function verify_startup_boundaries() assert(pathContains(fullfile(root, 'apps')), 'startup_labkit should add apps/ to the path.'); assert(pathContains(fullfile(root, 'apps', 'electrochem')), ... 'startup_labkit should add nested app category folders to the path.'); + assert(pathContains(fullfile(root, 'apps', 'electrochem', 'cic')), ... + 'startup_labkit should add nested electrochem app folders to the path.'); + assert(pathContains(fullfile(root, 'apps', 'electrochem', 'vt_resistance')), ... + 'startup_labkit should add nested electrochem app folders to the path.'); + assert(~pathContains(fullfile(root, 'apps', 'electrochem', 'cic', '+cic')), ... + 'startup_labkit should not expose electrochem app-owned package folders as direct path entries.'); + assert(~pathContains(fullfile(root, 'apps', 'electrochem', 'vt_resistance', '+vt_resistance')), ... + 'startup_labkit should not expose electrochem app-owned package folders as direct path entries.'); assert(pathContains(fullfile(root, 'apps', 'image_measurement')), ... 'startup_labkit should add image measurement app category folders to the path.'); assert(pathContains(fullfile(root, 'apps', 'image_measurement', 'curvature')), ... diff --git a/tests/unit/apps/electrochem/ChronoOverlayExportTest.m b/tests/unit/apps/electrochem/ChronoOverlayExportTest.m index 776242d..fc100a0 100644 --- a/tests/unit/apps/electrochem/ChronoOverlayExportTest.m +++ b/tests/unit/apps/electrochem/ChronoOverlayExportTest.m @@ -28,7 +28,7 @@ function checkGapCenterAlignment() 'gap_end', 0.5, ... 'method', 'synthetic'); - [aligned, msg] = electrochemWorkflow("chronoOverlay", "alignByPulseGap", item); + [aligned, msg] = chrono_overlay.ops.alignByPulseGap(item); assertClose(aligned.alignTime, 0.4, 1e-12, ... 'Chrono overlay gap-center align time'); @@ -48,7 +48,7 @@ function checkFallbackAlignment() item.Im = zeros(size(item.t)); item.pulse = struct('ok', false, 'message', 'synthetic pulse not found'); - [aligned, msg] = electrochemWorkflow("chronoOverlay", "alignByPulseGap", item); + [aligned, msg] = chrono_overlay.ops.alignByPulseGap(item); assertClose(aligned.alignTime, 2, 1e-12, ... 'Chrono overlay fallback align time'); @@ -65,7 +65,7 @@ function checkMergedExportInterpolation() [100; 200], [10; 20]); itemC = makeOverlayItem('single sample.DTA', 0, 42, 5); - T = electrochemWorkflow("chronoOverlay", "buildOverlayExportTable", ... + T = chrono_overlay.export.buildOverlayExportTable(... [itemA, itemB, itemC]); assertClose(T.TimeGapCenterAligned_s, [-1; -0.5; 0; 0.5; 1], 1e-12, ... diff --git a/tests/unit/apps/electrochem/CicExportTest.m b/tests/unit/apps/electrochem/CicExportTest.m index 7046b92..bb207e0 100644 --- a/tests/unit/apps/electrochem/CicExportTest.m +++ b/tests/unit/apps/electrochem/CicExportTest.m @@ -78,17 +78,17 @@ function deleteIfExists(filepath) end function A = computeCIC(item, opts) - A = electrochemWorkflow("cic", "computeCIC", item, opts); + A = cic.ops.computeCIC(item, opts); end function T = buildCICResultsTable(items, unitLabel) - T = electrochemWorkflow("cic", "buildResultsTable", items, unitLabel); + T = cic.export.buildResultsTable(items, unitLabel); end function [C, cols] = buildCICBatchTableData(items, unitLabel) - [C, cols] = electrochemWorkflow("cic", "buildBatchTableData", items, unitLabel); + [C, cols] = cic.view.buildBatchTableData(items, unitLabel); end function writeCICResultsCSV(items, filepath, unitLabel) - electrochemWorkflow("cic", "writeResultsCSV", items, filepath, unitLabel); + cic.export.writeResultsCSV(items, filepath, unitLabel); end diff --git a/tests/unit/apps/electrochem/ComputeCICTest.m b/tests/unit/apps/electrochem/ComputeCICTest.m index c548066..e6181dc 100644 --- a/tests/unit/apps/electrochem/ComputeCICTest.m +++ b/tests/unit/apps/electrochem/ComputeCICTest.m @@ -78,5 +78,5 @@ function verify_computeCIC() end function A = computeCIC(item, opts) - A = electrochemWorkflow("cic", "computeCIC", item, opts); + A = cic.ops.computeCIC(item, opts); end diff --git a/tests/unit/apps/electrochem/ComputeCSCTest.m b/tests/unit/apps/electrochem/ComputeCSCTest.m index cda8423..b1b51b7 100644 --- a/tests/unit/apps/electrochem/ComputeCSCTest.m +++ b/tests/unit/apps/electrochem/ComputeCSCTest.m @@ -91,5 +91,5 @@ function verify_computeCSC() end function A = computeCSC(curve, opts) - A = electrochemWorkflow("csc", "computeCSC", curve, opts); + A = csc.ops.computeCSC(curve, opts); end diff --git a/tests/unit/apps/electrochem/ComputeVTResistanceTest.m b/tests/unit/apps/electrochem/ComputeVTResistanceTest.m index d7c3c97..64ca022 100644 --- a/tests/unit/apps/electrochem/ComputeVTResistanceTest.m +++ b/tests/unit/apps/electrochem/ComputeVTResistanceTest.m @@ -66,5 +66,5 @@ function verify_computeVTResistance() end function A = computeVTResistance(item, opts) - A = electrochemWorkflow("vtResistance", "computeResistance", item, opts); + A = vt_resistance.ops.computeResistance(item, opts); end diff --git a/tests/unit/apps/electrochem/EisOverlayExportTest.m b/tests/unit/apps/electrochem/EisOverlayExportTest.m index e1110aa..462e8cb 100644 --- a/tests/unit/apps/electrochem/EisOverlayExportTest.m +++ b/tests/unit/apps/electrochem/EisOverlayExportTest.m @@ -49,9 +49,9 @@ function verify_eisOverlayExport() assert(contains(source, 'axis(ax, ''equal'')'), ... 'EIS app should preserve equal-axis Nyquist plot behavior.'); - zreal = electrochemWorkflow("eis", "valuesForAxis", item, 'Zreal (ohm)'); + zreal = eis.ops.valuesForAxis(item, 'Zreal (ohm)'); assertClose(zreal, item.Zreal, 'EIS app axis-value hook should preserve Zreal values'); - T = electrochemWorkflow("eis", "buildExportTable", item, ... + T = eis.export.buildExportTable(item, ... 'Zreal (ohm)', '-Zimag (ohm)', false, false); assert(isequal(T.Properties.VariableNames(1), {'RowIndex'}), ... 'EIS export table hook should preserve RowIndex as the first column.'); @@ -60,6 +60,7 @@ function verify_eisOverlayExport() function source = readAppOwnedSource(appFile) appDir = fileparts(appFile); sourceParts = {fileread(appFile)}; + privateDir = fullfile(appDir, 'private'); if exist(privateDir, 'dir') == 7 fileEntries = dir(fullfile(privateDir, '*.m')); @@ -68,5 +69,17 @@ function verify_eisOverlayExport() sourceParts{end+1} = fileread(fullfile(privateDir, fileNames{iFile})); %#ok end end + + packageEntries = dir(fullfile(appDir, '+*')); + packageDirs = packageEntries([packageEntries.isdir]); + for iDir = 1:numel(packageDirs) + packageDir = fullfile(packageDirs(iDir).folder, packageDirs(iDir).name); + fileEntries = dir(fullfile(packageDir, '**', '*.m')); + filePaths = sort(fullfile({fileEntries.folder}, {fileEntries.name})); + for iFile = 1:numel(filePaths) + sourceParts{end+1} = fileread(filePaths{iFile}); %#ok + end + end + source = strjoin(sourceParts, newline); end diff --git a/tests/unit/apps/electrochem/VtResistanceExportTest.m b/tests/unit/apps/electrochem/VtResistanceExportTest.m index c44f48f..d849285 100644 --- a/tests/unit/apps/electrochem/VtResistanceExportTest.m +++ b/tests/unit/apps/electrochem/VtResistanceExportTest.m @@ -68,17 +68,17 @@ function deleteIfExists(filepath) end function A = computeVTResistance(item, opts) - A = electrochemWorkflow("vtResistance", "computeResistance", item, opts); + A = vt_resistance.ops.computeResistance(item, opts); end function T = buildVTResultsTable(items) - T = electrochemWorkflow("vtResistance", "buildResultsTable", items); + T = vt_resistance.export.buildResultsTable(items); end function C = buildVTBatchTableData(items) - C = electrochemWorkflow("vtResistance", "buildBatchTableData", items); + C = vt_resistance.view.buildBatchTableData(items); end function writeVTResultsCSV(items, filepath) - electrochemWorkflow("vtResistance", "writeResultsCSV", items, filepath); + vt_resistance.export.writeResultsCSV(items, filepath); end