Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 15x 15x 5x 5x 5x 15x 4x 1x 4x 4x 4x 15x 1x 1x 1x 1x 1x 15x 1x 15x 1x 1x 1x 1x 15x 5x 5x 5x | /**
* Part gating for session plans — the pure decision of which requested part
* types survive the readiness gates, and WHY the others were dropped.
*
* Kept free of DB imports so it is unit-testable and so the modal can show the
* same reasons the planner acted on (`skippedParts` on the plan summary and in
* the `plan_structure_ready` progress event).
*/
import type { SessionPartType, SkippedPart } from '@/db/schema/session-plans'
export interface ResolveEnabledPartTypesInput {
/** Parts the caller asked for */
partsToInclude: Record<SessionPartType, boolean>
/** At least one skill is at the visual practice level */
hasVisualSkills: boolean
/** The L3 derived-readiness flag */
linearReadinessEnabled: boolean
/** Linear-ready skills after teacher vetoes (flag on only) */
linearReadyCount: number
/** Linear-ready skills ignoring vetoes (flag on only) — separates "vetoed" from "not ready" */
linearReadyBeforeVetoCount: number
}
export interface ResolvedPartTypes {
enabledPartTypes: SessionPartType[]
skippedParts: SkippedPart[]
}
export function resolveEnabledPartTypes(input: ResolveEnabledPartTypesInput): ResolvedPartTypes {
const {
partsToInclude,
hasVisualSkills,
linearReadinessEnabled,
linearReadyCount,
linearReadyBeforeVetoCount,
} = input
const enabledPartTypes: SessionPartType[] = []
const skippedParts: SkippedPart[] = []
for (const type of ['abacus', 'visualization', 'linear'] as const) {
if (!partsToInclude[type]) continue
if (type === 'abacus') {
enabledPartTypes.push(type)
continue
}
if (type === 'visualization') {
if (hasVisualSkills) enabledPartTypes.push(type)
else skippedParts.push({ type, reason: 'no-visual-skills' })
continue
}
// linear
if (!linearReadinessEnabled) {
// Flag off: linear rides on the visual coupling (legacy behaviour)
if (hasVisualSkills) enabledPartTypes.push(type)
else skippedParts.push({ type, reason: 'no-visual-skills' })
continue
}
if (linearReadyCount > 0) {
enabledPartTypes.push(type)
} else if (linearReadyBeforeVetoCount > 0) {
skippedParts.push({ type, reason: 'vetoed' })
} else {
skippedParts.push({ type, reason: 'not-ready' })
}
}
return { enabledPartTypes, skippedParts }
}
|