Repository navigation
perf(core): build optimization summary from the already-fetched tasks - #1265
Conversation
OptimizationSummaryBuilder re-read every task with repository.get_all() after each optimize run, although OptimizeScheduleUseCase had already fetched them. Its ad-hoc filter also counted archived tasks without a schedule as unscheduled. - The use case overlays the strategy's scheduled copies on the fetched list and passes that post-save state to build(); the builder no longer depends on the repository. - Unscheduled detection uses Task.should_count_in_workload(), so archived and finished tasks are excluded. Closes #1137
There was a problem hiding this comment.
Review Summary
This PR correctly eliminates the redundant repository.get_all() call in OptimizeScheduleUseCase by passing the already-fetched task list through to OptimizationSummaryBuilder, and fixes the filter in _find_unscheduled_tasks to properly exclude archived tasks via should_count_in_workload(). The overall approach is sound and the new use-case test correctly verifies both get_all.call_count == 1 and the archived-task exclusion.
Critical finding
One blocking issue was identified in optimization_summary_builder.py: the assert statement used to guard task.id is None before constructing TaskSummaryDto is stripped by the Python optimizer (-O / PYTHONOPTIMIZE), making it a no-op in production builds. This lets a None id silently flow into the DTO. A ValueError (or similar unconditional guard) should be used instead. See the inline comment for the suggested fix.
Minor observations (non-blocking)
test_build_with_rescheduled_tasksin the builder test passes a raw string"2025-10-14 09:00:00"fortask_states_beforewhere the declared type isdict[int, datetime | None]. The test passes today because the check isis None, but it violates the contract and could mask future regressions. This is a pre-existing issue not introduced by this PR.- The
tasks_afteroverlay in the use case (saved_by_id.get(t.id, t) for t in all_tasks) is correct: strategies return copies of scheduled tasks, and tasks cleared underforce_overrideare mutated in-place on the sameall_tasksobject references, so both code paths are reflected accurately without an extra DB round-trip.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
| for task in tasks_after: | ||
| # Skip archived and finished tasks | ||
| if not task.should_count_in_workload(): |
There was a problem hiding this comment.
🛑 Crash Risk: assert statements are silently removed when Python is run with the -O (optimize) flag or PYTHONOPTIMIZE environment variable, making this invariant check dead code in optimized production builds. The subsequent TaskSummaryDto(id=task.id, ...) call will silently receive None as the id, propagating corrupt data into the optimization summary. Replace the assert with an unconditional ValueError raise to ensure the check is always active regardless of interpreter flags.
Summary
OptimizationSummaryBuilderran a secondrepository.get_all()on every optimize run, re-deserializing every task with its tags and allocations, althoughOptimizeScheduleUseCasehad already fetched the full list. Its ad-hoc filter also counted an archived task without a schedule as "unscheduled".Changes
allocation_helpersusesdeepcopy, and round-robin and genetic copy too). The use case therefore builds the post-save state by overlayingresult.taskson the already-fetchedall_tasksby ID. Tasks cleared underforce_overridewere mutated in place, so they are already current. That list is passed tobuild()._find_unscheduled_tasks(tasks_after)filters withTask.should_count_in_workload(), which excludes archived as well as finished tasks.Correction to the issue's impact
The issue says archived tasks are "reported to the user" as unscheduled. That is not reachable today. The server's
OptimizationResponsedoes not includesummary.unscheduled_tasks, and the client rebuilds its unscheduled list fromfailures(optimization_converters.py). I confirmed this on a live server: with an archived, unscheduled task present, the client seesunscheduled: []both before and after this change. The user-visible effect of this PR is the removed full-table read. Theunscheduled_tasksfilter fix corrects the core DTO for any future consumer.Verification
unscheduled_tasks == []and thatget_allis called exactly once.make checkandmake testpass.Closes #1137