refactor: clean up dependencies and modernize build assets for improved stability
This commit is contained in:
+38
-15
@@ -1,26 +1,49 @@
|
||||
import numpy as np
|
||||
from fastapi import HTTPException
|
||||
from scipy import signal
|
||||
|
||||
from .config import BODE_MAX_MULTIPLIER, BODE_POINTS
|
||||
from .schemas import BodeParams
|
||||
from .schemas import BodeCompareParams, BodeParams
|
||||
from .validation import require_finite, validate_coefficients
|
||||
|
||||
|
||||
def calculate_bode_response(params: BodeParams):
|
||||
fs_val = require_finite(params.fs, "fs")
|
||||
b_vals = validate_coefficients(params.b, "b")
|
||||
a_vals = validate_coefficients(params.a, "a")
|
||||
f_min = params.fs / 50000.0
|
||||
f_max = params.fs * BODE_MAX_MULTIPLIER
|
||||
f_eval = np.logspace(np.log10(f_min), np.log10(f_max), BODE_POINTS)
|
||||
|
||||
def _bode_series(b_values, a_values, f_eval, fs_val):
|
||||
b_vals = validate_coefficients(b_values, "b")
|
||||
a_vals = validate_coefficients(a_values, "a")
|
||||
_, h = signal.freqz(b_vals, a_vals, worN=f_eval, fs=fs_val)
|
||||
mag = 20 * np.log10(np.abs(h) + 1e-12)
|
||||
phase = np.angle(h, deg=True)
|
||||
|
||||
return {
|
||||
"freq": f_eval.tolist(),
|
||||
"mag": mag.tolist(),
|
||||
"phase": phase.tolist(),
|
||||
"mag": (20 * np.log10(np.abs(h) + 1e-12)).tolist(),
|
||||
"phase": np.angle(h, deg=True).tolist(),
|
||||
}
|
||||
|
||||
|
||||
def _frequency_axis(fs):
|
||||
fs_val = require_finite(fs, "fs")
|
||||
f_min = fs_val / 50000.0
|
||||
f_max = fs_val * BODE_MAX_MULTIPLIER
|
||||
return fs_val, np.logspace(np.log10(f_min), np.log10(f_max), BODE_POINTS)
|
||||
|
||||
|
||||
def calculate_bode_response(params: BodeParams):
|
||||
fs_val, f_eval = _frequency_axis(params.fs)
|
||||
series = _bode_series(params.b, params.a, f_eval, fs_val)
|
||||
return {
|
||||
"freq": f_eval.tolist(),
|
||||
"mag": series["mag"],
|
||||
"phase": series["phase"],
|
||||
}
|
||||
|
||||
|
||||
def calculate_bode_compare_response(params: BodeCompareParams):
|
||||
fs_val, f_eval = _frequency_axis(params.ideal.fs)
|
||||
fixed_fs_val = require_finite(params.fixed.fs, "fixed.fs")
|
||||
if not np.isclose(fixed_fs_val, fs_val):
|
||||
raise HTTPException(status_code=400, detail="ideal 與 fixed 的 fs 必須相同")
|
||||
|
||||
ideal = _bode_series(params.ideal.b, params.ideal.a, f_eval, fs_val)
|
||||
fixed = _bode_series(params.fixed.b, params.fixed.a, f_eval, fs_val)
|
||||
return {
|
||||
"freq": f_eval.tolist(),
|
||||
"ideal": ideal,
|
||||
"fixed": fixed,
|
||||
}
|
||||
|
||||
@@ -38,3 +38,7 @@ class BodeParams(BaseModel):
|
||||
a: List[float] = Field(min_length=1, max_length=MAX_COEFFICIENTS)
|
||||
fs: float = Field(gt=0)
|
||||
|
||||
|
||||
class BodeCompareParams(BaseModel):
|
||||
ideal: BodeParams
|
||||
fixed: BodeParams
|
||||
|
||||
+12
-2
@@ -2,7 +2,7 @@ from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile
|
||||
from fastapi.responses import RedirectResponse, StreamingResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from dea.bode import calculate_bode_response
|
||||
from dea.bode import calculate_bode_compare_response, calculate_bode_response
|
||||
from dea.config import (
|
||||
BODE_MAX_MULTIPLIER,
|
||||
BODE_POINTS,
|
||||
@@ -19,7 +19,7 @@ from dea.csv_processing import (
|
||||
read_csv_upload,
|
||||
)
|
||||
from dea.filter_design import design_response
|
||||
from dea.schemas import BodeParams, DesignParams
|
||||
from dea.schemas import BodeCompareParams, BodeParams, DesignParams
|
||||
from dea.security import (
|
||||
SECURITY_HEADERS,
|
||||
add_security_headers,
|
||||
@@ -75,6 +75,16 @@ def calculate_bode(params: BodeParams):
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/bode/compare")
|
||||
def calculate_bode_compare(params: BodeCompareParams):
|
||||
try:
|
||||
return calculate_bode_compare_response(params)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/filter")
|
||||
async def filter_csv(
|
||||
file: UploadFile = File(...),
|
||||
|
||||
+3
-3
@@ -73,7 +73,7 @@
|
||||
濾波器設計
|
||||
</div>
|
||||
<button @click="resetFilterParams"
|
||||
class="text-[10px] font-bold role-bg-warning-soft role-text-warning role-hover-warning-soft px-2 py-1 rounded transition-colors uppercase">重設設計</button>
|
||||
class="text-[10px] font-bold role-bg-secondary-soft role-border-secondary-soft role-text-secondary role-hover-secondary-soft border px-2 py-1 rounded transition-colors uppercase">重設設計</button>
|
||||
</h2>
|
||||
<select v-model="filterType" @change="onFilterTypeChange"
|
||||
class="w-full bg-slate-50 dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary focus:ring-1 role-focus-ring-primary outline-none transition-all mb-3 text-slate-900 dark:text-gray-100 truncate">
|
||||
@@ -267,7 +267,7 @@
|
||||
濾波器係數設定</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button v-if="isManualDirty" @click="clearManualAdjustments"
|
||||
class="text-[10px] font-bold bg-slate-200 dark:bg-gray-800 hover:bg-slate-300 dark:hover:bg-gray-700 px-2 py-1 rounded text-slate-500 transition-colors uppercase mr-1">重設</button>
|
||||
class="text-[10px] font-bold role-bg-secondary-soft role-border-secondary-soft role-text-secondary role-hover-secondary-soft border px-2 py-1 rounded transition-colors uppercase mr-1">重設</button>
|
||||
</div>
|
||||
</h2>
|
||||
<div class="mb-3">
|
||||
@@ -475,7 +475,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button v-if="isFixedPointDirty" @click="resetFixedPointSettings"
|
||||
class="text-[10px] font-bold bg-slate-200 dark:bg-gray-800 hover:bg-slate-300 dark:hover:bg-gray-700 px-2 py-1 rounded text-slate-500 transition-colors uppercase mr-1">重設</button>
|
||||
class="text-[10px] font-bold role-bg-secondary-soft role-border-secondary-soft role-text-secondary role-hover-secondary-soft border px-2 py-1 rounded transition-colors uppercase mr-1">重設</button>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
|
||||
Regular → Executable
+1106
-1118
File diff suppressed because it is too large
Load Diff
+287
-64
@@ -4,36 +4,42 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Morandi green hierarchy. Feature colors are expressed through role classes only. */
|
||||
/* Pixel M3E hierarchy. Feature colors are expressed through role classes only. */
|
||||
:root {
|
||||
--m3-primary: #657868;
|
||||
--m3-primary: #566075;
|
||||
--m3-on-primary: #ffffff;
|
||||
--m3-primary-container: #dce7dc;
|
||||
--m3-on-primary-container: #1b2a1f;
|
||||
--m3-secondary: #73786f;
|
||||
--m3-primary-container: #d8deef;
|
||||
--m3-on-primary-container: #202637;
|
||||
--m3-secondary: #5e6472;
|
||||
--m3-on-secondary: #ffffff;
|
||||
--m3-secondary-container: #e2e6dc;
|
||||
--m3-on-secondary-container: #20251f;
|
||||
--m3-tertiary: #877b68;
|
||||
--m3-secondary-container: #e1e5f0;
|
||||
--m3-on-secondary-container: #242832;
|
||||
--m3-tertiary: #7a6689;
|
||||
--m3-on-tertiary: #ffffff;
|
||||
--m3-tertiary-container: #ece2d2;
|
||||
--m3-on-tertiary-container: #2d261a;
|
||||
--m3-error: #a25f5b;
|
||||
--m3-tertiary-container: #f0dcff;
|
||||
--m3-on-tertiary-container: #30203d;
|
||||
--m3-error: #b3261e;
|
||||
--m3-on-error: #ffffff;
|
||||
--m3-error-container: #f2ddda;
|
||||
--m3-warning: #806b43;
|
||||
--m3-warning-container: #f0e4c7;
|
||||
--m3-background: #fbfcf8;
|
||||
--m3-surface: #fbfcf8;
|
||||
--m3-error-container: #f9dedc;
|
||||
--m3-success: #2e7d61;
|
||||
--m3-success-container: #cceede;
|
||||
--m3-warning: #8c6d1f;
|
||||
--m3-warning-container: #f5e4b2;
|
||||
--m3-accent-cyan: #64d2ff;
|
||||
--m3-accent-pink: #ff9bd8;
|
||||
--m3-accent-orange: #ffb879;
|
||||
--m3-accent-lavender: #b9b2ff;
|
||||
--m3-background: #f6f7fb;
|
||||
--m3-surface: #f6f7fb;
|
||||
--m3-surface-container-lowest: #ffffff;
|
||||
--m3-surface-container-low: #f4f7ef;
|
||||
--m3-surface-container: #edf1e8;
|
||||
--m3-surface-container-high: #e4e9de;
|
||||
--m3-surface-container-highest: #dbe2d5;
|
||||
--m3-on-surface: #1b1d19;
|
||||
--m3-on-surface-variant: #464b43;
|
||||
--m3-outline: #747b70;
|
||||
--m3-outline-variant: #c9d0c3;
|
||||
--m3-surface-container-low: #eff1f7;
|
||||
--m3-surface-container: #e7e9f1;
|
||||
--m3-surface-container-high: #dde1ec;
|
||||
--m3-surface-container-highest: #d1d7e6;
|
||||
--m3-on-surface: #17191f;
|
||||
--m3-on-surface-variant: #525663;
|
||||
--m3-outline: #747987;
|
||||
--m3-outline-variant: #c3c8d4;
|
||||
--m3-divider: color-mix(in srgb, var(--m3-outline-variant) 78%, transparent);
|
||||
--m3-soft-outline: color-mix(in srgb, var(--m3-outline-variant) 88%, transparent);
|
||||
--m3-input-outline: color-mix(in srgb, var(--m3-outline) 82%, transparent);
|
||||
@@ -41,41 +47,58 @@
|
||||
--m3-secondary-state: color-mix(in srgb, var(--m3-secondary) 12%, transparent);
|
||||
--m3-tertiary-state: color-mix(in srgb, var(--m3-tertiary) 12%, transparent);
|
||||
--m3-warning-state: color-mix(in srgb, var(--m3-warning) 12%, transparent);
|
||||
--m3-shadow: 0 1px 2px rgba(29, 27, 32, 0.14), 0 2px 6px rgba(29, 27, 32, 0.10);
|
||||
--m3-elevated-shadow: 0 3px 8px rgba(29, 27, 32, 0.13), 0 12px 24px rgba(29, 27, 32, 0.08);
|
||||
--m3-shadow: 0 1px 2px rgba(15, 18, 26, 0.10), 0 8px 18px rgba(15, 18, 26, 0.08);
|
||||
--m3-elevated-shadow: 0 8px 22px rgba(15, 18, 26, 0.12), 0 18px 36px rgba(15, 18, 26, 0.10);
|
||||
--background: var(--m3-background);
|
||||
--surface: var(--m3-surface-container);
|
||||
--surfaceHigh: var(--m3-surface-container-high);
|
||||
--primary: var(--m3-primary);
|
||||
--onPrimary: var(--m3-on-primary);
|
||||
--textPrimary: var(--m3-on-surface);
|
||||
--textSecondary: var(--m3-on-surface-variant);
|
||||
--border: var(--m3-outline-variant);
|
||||
--danger: var(--m3-error);
|
||||
--success: var(--m3-success);
|
||||
--warning: var(--m3-warning);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--m3-primary: #b7c9b8;
|
||||
--m3-on-primary: #243429;
|
||||
--m3-primary-container: #3f5745;
|
||||
--m3-on-primary-container: #dce7dc;
|
||||
--m3-secondary: #c6cbbf;
|
||||
--m3-on-secondary: #2e332b;
|
||||
--m3-secondary-container: #454b40;
|
||||
--m3-on-secondary-container: #e2e6dc;
|
||||
--m3-tertiary: #d4c5aa;
|
||||
--m3-on-tertiary: #3b2f20;
|
||||
--m3-tertiary-container: #5d503e;
|
||||
--m3-on-tertiary-container: #f1e4cf;
|
||||
--m3-error: #e6b8b4;
|
||||
--m3-on-error: #5f1715;
|
||||
--m3-error-container: #7c3430;
|
||||
--m3-warning: #dcc48e;
|
||||
--m3-warning-container: #5a4828;
|
||||
--m3-background: #111510;
|
||||
--m3-surface: #111510;
|
||||
--m3-surface-container-lowest: #0b0e0a;
|
||||
--m3-surface-container-low: #1a1f18;
|
||||
--m3-surface-container: #20261e;
|
||||
--m3-surface-container-high: #2a3128;
|
||||
--m3-surface-container-highest: #343d31;
|
||||
--m3-on-surface: #e5e9df;
|
||||
--m3-on-surface-variant: #c7cec1;
|
||||
--m3-outline: #92998e;
|
||||
--m3-outline-variant: #464d43;
|
||||
--m3-shadow: 0 1px 2px rgba(0, 0, 0, 0.36), 0 4px 12px rgba(0, 0, 0, 0.28);
|
||||
--m3-elevated-shadow: 0 4px 12px rgba(0, 0, 0, 0.45), 0 16px 32px rgba(0, 0, 0, 0.32);
|
||||
--m3-primary: #c8cee0;
|
||||
--m3-on-primary: #202431;
|
||||
--m3-primary-container: #3f4453;
|
||||
--m3-on-primary-container: #eef1ff;
|
||||
--m3-secondary: #aeb6c9;
|
||||
--m3-on-secondary: #242832;
|
||||
--m3-secondary-container: #343842;
|
||||
--m3-on-secondary-container: #e4e8f6;
|
||||
--m3-tertiary: #d6b7f0;
|
||||
--m3-on-tertiary: #382047;
|
||||
--m3-tertiary-container: #4b3b59;
|
||||
--m3-on-tertiary-container: #f4dcff;
|
||||
--m3-error: #f2b8b5;
|
||||
--m3-on-error: #601410;
|
||||
--m3-error-container: #7d2e29;
|
||||
--m3-success: #9bd7bd;
|
||||
--m3-success-container: #254c3a;
|
||||
--m3-warning: #e6cb7f;
|
||||
--m3-warning-container: #55451f;
|
||||
--m3-accent-cyan: #5fd4ff;
|
||||
--m3-accent-pink: #ff9bd8;
|
||||
--m3-accent-orange: #ffb879;
|
||||
--m3-accent-lavender: #b9b2ff;
|
||||
--m3-background: #111216;
|
||||
--m3-surface: #111216;
|
||||
--m3-surface-container-lowest: #090a0d;
|
||||
--m3-surface-container-low: #1a1b20;
|
||||
--m3-surface-container: #2a2a2e;
|
||||
--m3-surface-container-high: #34353b;
|
||||
--m3-surface-container-highest: #41434b;
|
||||
--m3-on-surface: #f1f1f4;
|
||||
--m3-on-surface-variant: #b4b4bd;
|
||||
--m3-outline: #8b8d98;
|
||||
--m3-outline-variant: #4b4d56;
|
||||
--m3-shadow: 0 1px 2px rgba(0, 0, 0, 0.42), 0 8px 24px rgba(0, 0, 0, 0.34);
|
||||
--m3-elevated-shadow: 0 8px 24px rgba(0, 0, 0, 0.48), 0 24px 48px rgba(0, 0, 0, 0.36);
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -87,7 +110,16 @@ body,
|
||||
|
||||
body {
|
||||
font-family: "Google Sans", Roboto, "Noto Sans TC", system-ui, -apple-system, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
#app {
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, color-mix(in srgb, var(--m3-primary) 10%, transparent), transparent 30rem),
|
||||
var(--m3-background);
|
||||
}
|
||||
|
||||
header,
|
||||
@@ -106,11 +138,26 @@ header.bg-white,
|
||||
header.dark\:bg-dark,
|
||||
footer.bg-white,
|
||||
footer.dark\:bg-dark {
|
||||
background: var(--m3-surface-container-low) !important;
|
||||
background: color-mix(in srgb, var(--m3-background) 82%, var(--m3-surface-container) 18%) !important;
|
||||
border-color: var(--m3-divider) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
header {
|
||||
min-height: 72px;
|
||||
padding-inline: clamp(1rem, 2vw, 1.75rem) !important;
|
||||
}
|
||||
|
||||
header img {
|
||||
background: var(--m3-surface-container-high);
|
||||
border-radius: 18px !important;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: clamp(1.35rem, 1.8vw, 2rem) !important;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
aside.bg-white,
|
||||
aside.dark\:bg-dark,
|
||||
main.bg-slate-50,
|
||||
@@ -133,7 +180,23 @@ header h1 {
|
||||
background: none !important;
|
||||
color: var(--m3-on-surface) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-surface);
|
||||
font-weight: 700;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 0.95rem !important;
|
||||
font-weight: 500 !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
h3.font-bold {
|
||||
font-size: clamp(1.15rem, 1.35vw, 1.45rem) !important;
|
||||
}
|
||||
|
||||
h4.font-bold {
|
||||
font-size: 1.05rem !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -168,6 +231,22 @@ main > div,
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
aside section {
|
||||
background: var(--m3-surface-container) !important;
|
||||
border: 1px solid var(--m3-soft-outline);
|
||||
border-radius: 28px;
|
||||
padding: 1.15rem;
|
||||
}
|
||||
|
||||
main > div {
|
||||
border-radius: 30px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main > div > div:first-child {
|
||||
border-radius: 30px 30px 0 0;
|
||||
}
|
||||
|
||||
.border,
|
||||
.border-2 {
|
||||
border-color: var(--m3-outline-variant) !important;
|
||||
@@ -222,7 +301,7 @@ input[type="number"],
|
||||
textarea {
|
||||
background: var(--m3-surface-container-lowest) !important;
|
||||
border-color: var(--m3-input-outline) !important;
|
||||
border-radius: 12px !important;
|
||||
border-radius: 22px !important;
|
||||
color: var(--m3-on-surface) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -248,10 +327,17 @@ label.cursor-pointer {
|
||||
border-color: transparent !important;
|
||||
border-radius: 999px !important;
|
||||
box-shadow: none !important;
|
||||
font-weight: 650;
|
||||
font-weight: 500;
|
||||
transition: background-color 140ms ease, color 140ms ease, box-shadow 140ms ease, transform 120ms ease;
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
[role="button"]:focus-visible,
|
||||
label.cursor-pointer:focus-visible {
|
||||
outline: 3px solid color-mix(in srgb, var(--m3-primary) 42%, transparent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
[role="button"]:hover,
|
||||
label.cursor-pointer:hover {
|
||||
@@ -297,7 +383,7 @@ details,
|
||||
select,
|
||||
textarea,
|
||||
input[type="number"] {
|
||||
border-radius: 16px !important;
|
||||
border-radius: 22px !important;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
@@ -305,15 +391,17 @@ input[type="number"] {
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
background: color-mix(in srgb, var(--m3-primary) 22%, var(--m3-surface-container-high)) !important;
|
||||
background: color-mix(in srgb, var(--m3-primary) 28%, var(--m3-surface-container-high)) !important;
|
||||
border-radius: 999px;
|
||||
height: 6px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
background: var(--m3-primary);
|
||||
border: 3px solid var(--m3-surface-container-lowest);
|
||||
border: 4px solid var(--m3-surface-container-lowest);
|
||||
box-shadow: var(--m3-shadow);
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.dark input[type=range]::-webkit-slider-thumb {
|
||||
@@ -529,3 +617,138 @@ details .grid.grid-cols-\[2rem_1fr_2rem\] button {
|
||||
background: var(--m3-secondary-container) !important;
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > button,
|
||||
details .grid.grid-cols-\[2rem_1fr_2rem\] > button,
|
||||
details button.touch-none {
|
||||
background: var(--m3-secondary-container) !important;
|
||||
border: 1px solid color-mix(in srgb, var(--m3-secondary) 28%, var(--m3-outline-variant)) !important;
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > button:hover,
|
||||
details .grid.grid-cols-\[2rem_1fr_2rem\] > button:hover,
|
||||
details button.touch-none:hover {
|
||||
background: color-mix(in srgb, var(--m3-secondary-container) 82%, var(--m3-primary) 18%) !important;
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div,
|
||||
details .grid.grid-cols-\[2rem_1fr_2rem\] input,
|
||||
details button.touch-none,
|
||||
details .grid.grid-cols-2.gap-2 > div {
|
||||
background: var(--m3-surface-container-lowest) !important;
|
||||
border-color: var(--m3-soft-outline) !important;
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[2rem_1fr_2rem\] input,
|
||||
details button.touch-none span.font-mono,
|
||||
details .grid.grid-cols-2.gap-2 > div span.font-mono {
|
||||
color: var(--m3-on-surface) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-surface);
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div span.font-mono {
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-secondary-container);
|
||||
}
|
||||
|
||||
details .grid.grid-cols-\[2rem_1fr_2rem\] input:focus {
|
||||
border-color: var(--m3-primary) !important;
|
||||
box-shadow: 0 0 0 3px var(--m3-primary-state) !important;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > button,
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > button,
|
||||
button.touch-none {
|
||||
background: var(--m3-secondary-container) !important;
|
||||
border: 1px solid color-mix(in srgb, var(--m3-secondary) 28%, var(--m3-outline-variant)) !important;
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
border-radius: 999px !important;
|
||||
font-size: 0.875rem !important;
|
||||
font-weight: 500 !important;
|
||||
letter-spacing: 0 !important;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > button:hover,
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > button:hover,
|
||||
button.touch-none:hover {
|
||||
background: color-mix(in srgb, var(--m3-secondary-container) 82%, var(--m3-primary) 18%) !important;
|
||||
color: var(--m3-on-secondary-container) !important;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > input,
|
||||
button.touch-none,
|
||||
.grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div {
|
||||
background: var(--m3-surface-container-lowest) !important;
|
||||
border-color: var(--m3-soft-outline) !important;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > input,
|
||||
button.touch-none span.font-mono,
|
||||
.grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] span.font-mono,
|
||||
.grid.grid-cols-2.gap-2 span.font-mono {
|
||||
color: var(--m3-on-surface) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-surface);
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] span:not(.font-mono),
|
||||
.grid.grid-cols-2.gap-2 span:not(.font-mono) {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > input {
|
||||
text-align: center;
|
||||
font-size: 0.95rem !important;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.grid.grid-cols-\[2rem_1fr_2rem\] > input:focus {
|
||||
border-color: var(--m3-primary) !important;
|
||||
box-shadow: 0 0 0 3px var(--m3-primary-state) !important;
|
||||
}
|
||||
|
||||
#app .font-bold,
|
||||
#app .font-semibold,
|
||||
#app strong,
|
||||
#app b {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
#app label,
|
||||
#app summary,
|
||||
#app p,
|
||||
#app span {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
#app section .flex.flex-wrap.gap-1.mb-2 > div,
|
||||
#app section .grid.grid-cols-2.gap-2 > div,
|
||||
#app section .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div,
|
||||
#app section button.touch-none {
|
||||
background: var(--m3-surface-container-lowest) !important;
|
||||
border-color: var(--m3-soft-outline) !important;
|
||||
color: var(--m3-on-surface) !important;
|
||||
}
|
||||
|
||||
#app section .flex.flex-wrap.gap-1.mb-2 > div span,
|
||||
#app section .grid.grid-cols-2.gap-2 > div span,
|
||||
#app section .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div span,
|
||||
#app section button.touch-none span {
|
||||
color: var(--m3-on-surface) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-surface);
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
#app section .flex.flex-wrap.gap-1.mb-2 > div span:first-child,
|
||||
#app section .grid.grid-cols-2.gap-2 > div span:first-child,
|
||||
#app section .grid.grid-cols-\[3\.25rem_1fr_3\.25rem\] > div span:first-child,
|
||||
#app section button.touch-none span:first-child {
|
||||
color: var(--m3-on-surface-variant) !important;
|
||||
-webkit-text-fill-color: var(--m3-on-surface-variant);
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+23
-1
@@ -5,7 +5,16 @@ import unittest
|
||||
from fastapi import HTTPException, UploadFile
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from dea_api import BodeParams, DesignParams, add_security_headers, calculate_bode, design_filter, filter_csv
|
||||
from dea_api import (
|
||||
BodeCompareParams,
|
||||
BodeParams,
|
||||
DesignParams,
|
||||
add_security_headers,
|
||||
calculate_bode,
|
||||
calculate_bode_compare,
|
||||
design_filter,
|
||||
filter_csv,
|
||||
)
|
||||
|
||||
|
||||
class DeaApiTest(unittest.TestCase):
|
||||
@@ -65,6 +74,19 @@ class DeaApiTest(unittest.TestCase):
|
||||
raise AssertionError("Expected a[0] validation to fail")
|
||||
|
||||
|
||||
def test_bode_compare_reuses_one_frequency_axis(self):
|
||||
body = calculate_bode_compare(
|
||||
BodeCompareParams(
|
||||
ideal=BodeParams(fs=1000, b=[1.0], a=[1.0]),
|
||||
fixed=BodeParams(fs=1000, b=[0.5, 0.5], a=[1.0]),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(len(body["freq"]), len(body["ideal"]["mag"]))
|
||||
self.assertEqual(len(body["freq"]), len(body["fixed"]["mag"]))
|
||||
self.assertEqual(len(body["ideal"]["phase"]), len(body["fixed"]["phase"]))
|
||||
|
||||
|
||||
def test_filter_downsamples_plot_response_for_large_csv(self):
|
||||
rows = ["value"] + [str(i) for i in range(6001)]
|
||||
upload = UploadFile(
|
||||
|
||||
Reference in New Issue
Block a user