From b5dd611f82a5cb2e4a441fba43ad2697ddf9f247 Mon Sep 17 00:00:00 2001 From: Mia Wu Date: Wed, 15 Jul 2026 15:48:02 -0400 Subject: [PATCH] fix top priority tests --- backend/app.py | 18 ++++++++++- backend/scheduler.py | 44 ++++++++++++++++++++++---- frontend/src/App.jsx | 36 +++++++++++++++------ frontend/src/components/RightPanel.jsx | 25 ++++++++++++--- 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/backend/app.py b/backend/app.py index 2f1be41..03c2cff 100644 --- a/backend/app.py +++ b/backend/app.py @@ -85,6 +85,8 @@ class CompileScheduleRequest(BaseModel): daytime_testing_today: bool = False dual_device_weekend_start_enabled: bool = False dual_device_weekend_start_dates: list[str] = Field(default_factory=list) + top_priority_tests_dut: list[str] = Field(default_factory=list) + top_priority_tests_ref: list[str] = Field(default_factory=list) top_priority_tests: list[str] = Field(default_factory=list) lowest_priority_tests: list[str] = Field(default_factory=list) @@ -394,9 +396,23 @@ def compile_schedule_endpoint(request: CompileScheduleRequest) -> dict[str, Any] holiday_dates = db.list_holidays(DB_PATH) top_priority_pairs: set[tuple[str, str]] = set() - for test_id in {item.strip() for item in request.top_priority_tests if item.strip()}: + dut_top_priority_ids = {item.strip() for item in request.top_priority_tests_dut if item.strip()} + ref_top_priority_ids = {item.strip() for item in request.top_priority_tests_ref if item.strip()} + + # Backward compatibility: if only legacy top_priority_tests was sent, + # keep previous behavior by applying IDs to whichever device has that test. + if not dut_top_priority_ids and not ref_top_priority_ids and request.top_priority_tests: + for test_id in {item.strip() for item in request.top_priority_tests if item.strip()}: + if any(t.test_id == test_id and t.device == DUT for t in stored_tests): + dut_top_priority_ids.add(test_id) + if any(t.test_id == test_id and t.device == REF for t in stored_tests): + ref_top_priority_ids.add(test_id) + + for test_id in dut_top_priority_ids: if any(t.test_id == test_id and t.device == DUT for t in stored_tests): top_priority_pairs.add((test_id, DUT)) + + for test_id in ref_top_priority_ids: if any(t.test_id == test_id and t.device == REF for t in stored_tests): top_priority_pairs.add((test_id, REF)) diff --git a/backend/scheduler.py b/backend/scheduler.py index 6832feb..ce3b5be 100644 --- a/backend/scheduler.py +++ b/backend/scheduler.py @@ -4,7 +4,7 @@ import os from dataclasses import dataclass from datetime import date, timedelta from test_window import get_shift_sequence_with_capacity, get_shift_capacity_for_date, is_off_day, next_window_start_date -from test_bundle import Test, TestBundle, build_test_bundles, bundle_pair_lookup +from test_bundle import BUNDLE_PRIORITY_TOP, Test, TestBundle, build_test_bundles, bundle_pair_lookup DUT = (os.getenv("DUT") or "DUT").strip() REF = (os.getenv("REF") or "REF").strip() @@ -84,17 +84,47 @@ class Scheduler: window_index = 0 cursor_date = self.start_date - # Sort TC keys by bundle count (largest first); tie-break by TC name. + # Process TCs that include top-priority bundles first. + # REF top-priority TCs come before DUT top-priority TCs so that + # entering tests in the REF top-priority field always claims tonight's + # first window for REF. + def tc_sort_key(item: tuple[str | None, list[TestBundle]]) -> tuple[int, int, int, str]: + tc, tc_bundles = item + has_ref_top = any(bundle.priority == BUNDLE_PRIORITY_TOP and bundle.device == REF for bundle in tc_bundles) + has_dut_top = any(bundle.priority == BUNDLE_PRIORITY_TOP and bundle.device == DUT for bundle in tc_bundles) + min_priority = min((bundle.priority for bundle in tc_bundles), default=BUNDLE_PRIORITY_TOP + 99) + tc_name = tc if tc is not None else "~" + if has_dut_top: + tier = 0 # DUT top-priority TCs first (wins when both exist) + elif has_ref_top: + tier = 1 # REF-only top-priority TCs second + else: + tier = 2 # All other TCs + return (tier, min_priority, -len(tc_bundles), tc_name) + sorted_tc_items = sorted( - [(tc, bundles_by_tc[tc]) for tc in bundles_by_tc if tc is not None], - key=lambda x: (-len(x[1]), x[0]), + [(tc, bundles_by_tc[tc]) for tc in bundles_by_tc], + key=tc_sort_key, ) - if None in bundles_by_tc: - sorted_tc_items.append((None, bundles_by_tc[None])) for tc, tc_bundles in sorted_tc_items: - window_device = DUT + tc_has_ref_top_priority = any( + bundle.device == REF and bundle.priority == BUNDLE_PRIORITY_TOP + for bundle in tc_bundles + ) + tc_has_dut_top_priority = any( + bundle.device == DUT and bundle.priority == BUNDLE_PRIORITY_TOP + for bundle in tc_bundles + ) + + if tc_has_ref_top_priority: + window_device = REF + elif tc_has_dut_top_priority: + window_device = DUT + else: + window_device = DUT + dut_unscheduled = [b for b in tc_bundles if b.device == DUT] ref_unscheduled = [b for b in tc_bundles if b.device == REF] diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9877bbc..72d709f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -165,7 +165,8 @@ export default function App() { const [settingsOpen, setSettingsOpen] = useState(false) const [settings, setSettings] = useState(DEFAULT_SETTINGS) const [dualDeviceWeekendWeekSelections, setDualDeviceWeekendWeekSelections] = useState({}) - const [topPriority, setTopPriority] = useState('') + const [topPriorityDut, setTopPriorityDut] = useState('') + const [topPriorityRef, setTopPriorityRef] = useState('') const [lowestPriority, setLowestPriority] = useState('') const [startDateOverride, setStartDateOverride] = useState('') const [failedTests, setFailedTests] = useState([]) @@ -298,7 +299,8 @@ export default function App() { const result = await api.compileSchedule({ start_date: effectiveStartDate || null, dual_device_weekend_start_dates: dualDeviceWindowStartDates, - top_priority_tests: topPriority.split(',').map(s => s.trim()).filter(Boolean), + top_priority_tests_dut: topPriorityDut.split(',').map(s => s.trim()).filter(Boolean), + top_priority_tests_ref: topPriorityRef.split(',').map(s => s.trim()).filter(Boolean), lowest_priority_tests: lowestPriority.split(',').map(s => s.trim()).filter(Boolean), rule: settings.testExclusion ?? '', }) @@ -326,12 +328,26 @@ export default function App() { } function handleRerunDecision(_rerunDuringDay) { - setTopPriority(prev => { - const existing = prev.split(',').map(s => s.trim()).filter(Boolean) - const incoming = failedTests.map(t => t.test_id) - const merged = [...new Set([...existing, ...incoming])] + const appendUnique = (currentValue, incomingValues) => { + const existing = currentValue.split(',').map(s => s.trim()).filter(Boolean) + const merged = [...new Set([...existing, ...incomingValues])] return merged.join(', ') - }) + } + + const dutIncoming = failedTests + .filter(test => test.device === 'CGW453') + .map(test => test.test_id) + const refIncoming = failedTests + .filter(test => test.device === 'CGW452') + .map(test => test.test_id) + + if (dutIncoming.length > 0) { + setTopPriorityDut(prev => appendUnique(prev, dutIncoming)) + } + if (refIncoming.length > 0) { + setTopPriorityRef(prev => appendUnique(prev, refIncoming)) + } + setFailedTests([]) } @@ -390,8 +406,10 @@ export default function App() { completionDate={completionDate} startDateOverride={startDateOverride} onStartDateOverrideChange={setStartDateOverride} - topPriority={topPriority} - onTopPriorityChange={setTopPriority} + topPriorityDut={topPriorityDut} + onTopPriorityDutChange={setTopPriorityDut} + topPriorityRef={topPriorityRef} + onTopPriorityRefChange={setTopPriorityRef} lowestPriority={lowestPriority} onLowestPriorityChange={setLowestPriority} onRemakeSchedule={handleRemakeSchedule} diff --git a/frontend/src/components/RightPanel.jsx b/frontend/src/components/RightPanel.jsx index c3c22e5..2f27922 100644 --- a/frontend/src/components/RightPanel.jsx +++ b/frontend/src/components/RightPanel.jsx @@ -18,8 +18,10 @@ export default function RightPanel({ completionDate, startDateOverride, onStartDateOverrideChange, - topPriority, - onTopPriorityChange, + topPriorityDut, + onTopPriorityDutChange, + topPriorityRef, + onTopPriorityRefChange, lowestPriority, onLowestPriorityChange, onRemakeSchedule, @@ -48,12 +50,25 @@ export default function RightPanel({ {/* Top priority */}