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,
|
applyEvaluateResponseToDraft,
|
||||||
applyGapOfferToDraft,
|
applyGapOfferToDraft,
|
||||||
applySelectedCompareSteps,
|
applySelectedCompareSteps,
|
||||||
|
buildProgressionComparePayload,
|
||||||
compareSlotDiffs,
|
compareSlotDiffs,
|
||||||
collectGapOffersFromApiResponse,
|
collectGapOffersFromApiResponse,
|
||||||
dedupeGapOffersBySlot,
|
dedupeGapOffersBySlot,
|
||||||
|
|
@ -489,26 +490,26 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMatchCompare = async (synced) => {
|
const fetchFullMatch = async (synced) =>
|
||||||
const res = await api.suggestProgressionPath({
|
api.suggestProgressionPath({
|
||||||
...buildMatchRequestBase(synced),
|
...buildMatchRequestBase(synced),
|
||||||
evaluate_steps: slotsToEvaluateSteps(synced),
|
preserve_slot_assignments: false,
|
||||||
compare_with_assignments: true,
|
|
||||||
include_llm_intent: false,
|
include_llm_intent: false,
|
||||||
include_llm_path_qa: 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 runMatchCompareFlow = async (synced, { source = 'match' } = {}) => {
|
||||||
const res = await fetchMatchCompare(synced)
|
setMatchNotice('Schritt 1/2: Aktuellen Pfad bewerten…')
|
||||||
setGapFillOffers(mergeGapOffersForDraft(synced, res))
|
const baselineRes = await fetchPathEvaluate(synced)
|
||||||
presentMatchCompare(res, { source })
|
setPathQa(baselineRes?.path_qa || null)
|
||||||
setPathQa(res?.baseline_path_qa || null)
|
|
||||||
return res
|
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' } = {}) => {
|
const presentMatchCompare = (res, { source = 'manual' } = {}) => {
|
||||||
|
|
|
||||||
|
|
@ -928,6 +928,115 @@ export function draftHasLibrarySlotAssignments(draft) {
|
||||||
return slotsToSlotAssignments(draft).length >= 1
|
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). */
|
/** Alle Slot-Diffs inkl. reiner ID-Tausche (gleicher Titel). */
|
||||||
export function compareSlotDiffs(comparison, { actionableOnly = false } = {}) {
|
export function compareSlotDiffs(comparison, { actionableOnly = false } = {}) {
|
||||||
if (actionableOnly && Array.isArray(comparison?.slot_diffs_actionable)) {
|
if (actionableOnly && Array.isArray(comparison?.slot_diffs_actionable)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user