Implement Progression Comparison Logic and Refactor Fetching Methods
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `buildProgressionComparePayload` to create a structured comparison response from baseline and proposed evaluation results, enhancing clarity in slot differences. - Refactored `fetchMatchCompare` to `fetchFullMatch` for improved clarity and functionality in fetching progression paths. - Updated `runMatchCompareFlow` to streamline the evaluation process, integrating baseline and match results for a comprehensive comparison. - Enhanced utility functions for managing slot differences and gap fill offers, improving overall data handling in the progression graph editor. - Adjusted frontend components to reflect these changes, ensuring a more intuitive user experience in managing progression paths.
This commit is contained in:
parent
53f1c7161f
commit
cec96ae473
|
|
@ -28,6 +28,7 @@ import {
|
|||
applyEvaluateResponseToDraft,
|
||||
applyGapOfferToDraft,
|
||||
applySelectedCompareSteps,
|
||||
buildProgressionComparePayload,
|
||||
compareSlotDiffs,
|
||||
collectGapOffersFromApiResponse,
|
||||
dedupeGapOffersBySlot,
|
||||
|
|
@ -489,26 +490,26 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
|||
}
|
||||
}
|
||||
|
||||
const fetchMatchCompare = async (synced) => {
|
||||
const res = await api.suggestProgressionPath({
|
||||
const fetchFullMatch = async (synced) =>
|
||||
api.suggestProgressionPath({
|
||||
...buildMatchRequestBase(synced),
|
||||
evaluate_steps: slotsToEvaluateSteps(synced),
|
||||
compare_with_assignments: true,
|
||||
preserve_slot_assignments: false,
|
||||
include_llm_intent: false,
|
||||
include_llm_path_qa: false,
|
||||
})
|
||||
if (!res?.comparison_mode) {
|
||||
throw new Error('Kein Vergleich in der Antwort')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const runMatchCompareFlow = async (synced, { source = 'match' } = {}) => {
|
||||
const res = await fetchMatchCompare(synced)
|
||||
setGapFillOffers(mergeGapOffersForDraft(synced, res))
|
||||
presentMatchCompare(res, { source })
|
||||
setPathQa(res?.baseline_path_qa || null)
|
||||
return res
|
||||
setMatchNotice('Schritt 1/2: Aktuellen Pfad bewerten…')
|
||||
const baselineRes = await fetchPathEvaluate(synced)
|
||||
setPathQa(baselineRes?.path_qa || null)
|
||||
|
||||
setMatchNotice('Schritt 2/2: Match für alle Slots (Bibliothek + Lücken)…')
|
||||
const matchRes = await fetchFullMatch(synced)
|
||||
|
||||
const compareRes = buildProgressionComparePayload(baselineRes, matchRes)
|
||||
setGapFillOffers(mergeGapOffersForDraft(synced, baselineRes, matchRes))
|
||||
presentMatchCompare(compareRes, { source })
|
||||
return compareRes
|
||||
}
|
||||
|
||||
const presentMatchCompare = (res, { source = 'manual' } = {}) => {
|
||||
|
|
|
|||
|
|
@ -928,6 +928,115 @@ export function draftHasLibrarySlotAssignments(draft) {
|
|||
return slotsToSlotAssignments(draft).length >= 1
|
||||
}
|
||||
|
||||
function normalizeCompareSlotTitle(title) {
|
||||
return (title || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
function stepsByMajorIndex(steps) {
|
||||
const out = new Map()
|
||||
for (const step of steps || []) {
|
||||
if (step?.roadmap_major_step_index == null || !Number.isFinite(Number(step.roadmap_major_step_index))) {
|
||||
continue
|
||||
}
|
||||
out.set(Number(step.roadmap_major_step_index), step)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function buildProgressionSlotDiffs(baselineSteps, proposedSteps) {
|
||||
const baseBy = stepsByMajorIndex(baselineSteps)
|
||||
const propBy = stepsByMajorIndex(proposedSteps)
|
||||
const indices = new Set([...baseBy.keys(), ...propBy.keys()])
|
||||
const diffs = []
|
||||
for (const midx of [...indices].sort((a, b) => a - b)) {
|
||||
const base = baseBy.get(midx) || {}
|
||||
const prop = propBy.get(midx) || {}
|
||||
const baseId = base.exercise_id
|
||||
const propId = prop.exercise_id
|
||||
if (baseId != null && propId != null && Number(baseId) === Number(propId)) continue
|
||||
const baseTitle = (base.title || '').trim() || null
|
||||
const propTitle = (prop.title || '').trim() || null
|
||||
diffs.push({
|
||||
roadmap_major_step_index: midx,
|
||||
baseline_exercise_id: baseId != null ? Number(baseId) : null,
|
||||
baseline_title: baseTitle,
|
||||
proposed_exercise_id: propId != null ? Number(propId) : null,
|
||||
proposed_title: propTitle,
|
||||
baseline_slot_status: base.slot_status,
|
||||
proposed_slot_status: prop.slot_status,
|
||||
changed: baseId !== propId || baseTitle !== propTitle,
|
||||
})
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
function annotateCompareSlotDiffs(diffs) {
|
||||
return (diffs || []).map((raw) => {
|
||||
const bt = normalizeCompareSlotTitle(raw.baseline_title)
|
||||
const pt = normalizeCompareSlotTitle(raw.proposed_title)
|
||||
return {
|
||||
...raw,
|
||||
trivial_id_swap: Boolean(bt && pt && bt === pt),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function actionableCompareSlotDiffs(diffs) {
|
||||
return (diffs || []).filter((d) => !d.trivial_id_swap)
|
||||
}
|
||||
|
||||
function mergeGapFillOffersFromSteps(steps, offers) {
|
||||
const merged = (offers || []).map((o) => ({ ...o }))
|
||||
const seen = new Set(merged.map((o) => o.offer_id).filter(Boolean))
|
||||
for (const step of steps || []) {
|
||||
const go = step?.gap_offer
|
||||
if (!go || typeof go !== 'object') continue
|
||||
if (go.offer_id && seen.has(go.offer_id)) continue
|
||||
if (go.offer_id) seen.add(go.offer_id)
|
||||
merged.push({ ...go })
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
/**
|
||||
* Vergleich aus zwei kaskadierten Antworten (Evaluate → Match) — spiegelt Backend-Compare.
|
||||
*/
|
||||
export function buildProgressionComparePayload(baselineRes, proposedRes) {
|
||||
const baselineSteps = Array.isArray(baselineRes?.steps) ? baselineRes.steps : []
|
||||
const proposedSteps = Array.isArray(proposedRes?.steps) ? proposedRes.steps : []
|
||||
const baselineQa = baselineRes?.path_qa || null
|
||||
const pipelineQa = proposedRes?.path_qa || null
|
||||
const slotDiffs = annotateCompareSlotDiffs(
|
||||
buildProgressionSlotDiffs(baselineSteps, proposedSteps),
|
||||
)
|
||||
const actionableDiffs = actionableCompareSlotDiffs(slotDiffs)
|
||||
const gapFillOffers = mergeGapFillOffersFromSteps(
|
||||
proposedSteps,
|
||||
proposedRes?.gap_fill_offers || [],
|
||||
)
|
||||
const proposedQa =
|
||||
actionableDiffs.length === 0 && baselineQa ? baselineQa : pipelineQa
|
||||
|
||||
return {
|
||||
...proposedRes,
|
||||
comparison_mode: true,
|
||||
baseline_steps: baselineSteps,
|
||||
baseline_path_qa: baselineQa,
|
||||
proposed_steps: proposedSteps,
|
||||
proposed_steps_pipeline: proposedSteps,
|
||||
proposed_path_qa: proposedQa,
|
||||
proposed_path_qa_pipeline: pipelineQa,
|
||||
gap_fill_offers: gapFillOffers,
|
||||
slot_diffs: slotDiffs,
|
||||
slot_diffs_actionable: actionableDiffs,
|
||||
slot_diff_count: actionableDiffs.length,
|
||||
slot_diff_count_including_trivial: slotDiffs.length,
|
||||
slot_diffs_source: 'steps',
|
||||
path_qa: proposedQa,
|
||||
steps: proposedSteps,
|
||||
}
|
||||
}
|
||||
|
||||
/** Alle Slot-Diffs inkl. reiner ID-Tausche (gleicher Titel). */
|
||||
export function compareSlotDiffs(comparison, { actionableOnly = false } = {}) {
|
||||
if (actionableOnly && Array.isArray(comparison?.slot_diffs_actionable)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user