Enhance Progression Path Evaluation and Optimization Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 45s
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 1m18s
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 45s
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 1m18s
- Updated `suggest_progression_path` to include additional evaluation parameters, allowing for more comprehensive path assessments. - Introduced `PathQaPipelineDetails` component to display detailed quality assessment metrics, including rematch and refine logs, in the frontend. - Enhanced `ProgressionGraphEditor` to manage proposed path evaluations and integrate quality assessment results into the draft workflow. - Improved `ProgressionOptimizeCompareModal` to present optimization hints and quality tier information for proposed paths. - Bumped version to reflect the new features and improvements.
This commit is contained in:
parent
5ed06002d9
commit
5bca5ef9eb
|
|
@ -2253,9 +2253,15 @@ def suggest_progression_path(
|
||||||
baseline_body = body.model_copy(
|
baseline_body = body.model_copy(
|
||||||
update={
|
update={
|
||||||
"evaluate_only": True,
|
"evaluate_only": True,
|
||||||
"evaluate_steps": list(body.slot_assignments or []),
|
"evaluate_steps": list(
|
||||||
|
body.evaluate_steps or body.slot_assignments or []
|
||||||
|
),
|
||||||
"compare_with_assignments": False,
|
"compare_with_assignments": False,
|
||||||
"preserve_slot_assignments": True,
|
"preserve_slot_assignments": False,
|
||||||
|
# Gleiche QS-Pipeline wie „Graph bewerten“ (kein Match/Rematch-Schönung)
|
||||||
|
"include_llm_intent": False,
|
||||||
|
"auto_rematch_after_qa": False,
|
||||||
|
"include_roadmap_preview": False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
baseline = suggest_progression_path(cur, tenant=tenant, body=baseline_body)
|
baseline = suggest_progression_path(cur, tenant=tenant, body=baseline_body)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,124 @@ function severityStyle(pathQa) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PathQaPipelineDetails({ pathQa, draft, title, compact = false }) {
|
||||||
|
const { fixHints: optimizationHints } = useMemo(
|
||||||
|
() => splitPathQaHints(pathQa),
|
||||||
|
[pathQa],
|
||||||
|
)
|
||||||
|
const rematchLog = Array.isArray(pathQa?.rematch_log) ? pathQa.rematch_log : []
|
||||||
|
const refineLog = Array.isArray(pathQa?.refine_log) ? pathQa.refine_log : []
|
||||||
|
const qaTiers = Array.isArray(pathQa?.qa_tiers) ? pathQa.qa_tiers : []
|
||||||
|
const qualityPct = pathQaQualityPercent(pathQa)
|
||||||
|
const hasContent =
|
||||||
|
qaTiers.length > 0
|
||||||
|
|| (pathQa?.rematch_applied && rematchLog.length > 0)
|
||||||
|
|| (pathQa?.refine_applied && refineLog.length > 0)
|
||||||
|
|| optimizationHints.length > 0
|
||||||
|
|
||||||
|
if (!pathQa || !hasContent) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: compact ? '10px' : '12px',
|
||||||
|
padding: compact ? '8px 10px' : '10px 12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid color-mix(in srgb, var(--accent) 30%, var(--border))',
|
||||||
|
background: 'color-mix(in srgb, var(--accent) 6%, var(--surface2))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ fontSize: compact ? '11px' : '12px' }}>
|
||||||
|
{title}
|
||||||
|
{qualityPct != null ? ` · ${pathQa.overall_ok ? 'OK' : 'Hinweise'} (${qualityPct} %)` : ''}
|
||||||
|
</strong>
|
||||||
|
{qaTiers.length > 0 ? (
|
||||||
|
<ul style={{ margin: '6px 0 0', paddingLeft: '16px', color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
{qaTiers.map((tier) => (
|
||||||
|
<li key={tier.id || tier.label}>
|
||||||
|
{tier.label || tier.id}
|
||||||
|
{tier.finding_count != null ? ` (${tier.finding_count})` : ''}
|
||||||
|
{tier.applied === false ? ' · LLM aus' : ''}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
{pathQa.rematch_applied && rematchLog.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
Auto-Rematch
|
||||||
|
{pathQa.rematch_rounds != null ? ` (${pathQa.rematch_rounds} Runde(n))` : ''}
|
||||||
|
</p>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: '16px', color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
{rematchLog.map((entry, i) => (
|
||||||
|
<li key={`rematch-${i}-${entry.roadmap_major_step_index}-${entry.action}`}>
|
||||||
|
{formatRematchLogEntry(entry)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{pathQa.refine_applied && refineLog.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
Stufen-Spec verfeinert ({refineLog.length})
|
||||||
|
</p>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: '16px', color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
{refineLog.map((entry, i) => (
|
||||||
|
<li key={`refine-${i}-${entry.roadmap_major_step_index}`}>
|
||||||
|
{formatRefineLogEntry(entry)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{optimizationHints.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
|
Handlungsbedarf ({optimizationHints.length})
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
listStyle: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '6px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{optimizationHints.slice(0, compact ? 4 : 8).map((hint, i) => {
|
||||||
|
const slotIdx = resolveHintSlotIndex(hint, draft)
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={`hint-${i}-${hint.action}-${hint.issue}-${slotIdx ?? 'x'}`}
|
||||||
|
style={{
|
||||||
|
padding: '6px 8px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
background: 'var(--surface2)',
|
||||||
|
fontSize: '11px',
|
||||||
|
color: 'var(--text2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="exercise-tag" style={{ marginBottom: '4px', display: 'inline-block' }}>
|
||||||
|
{optimizationHintActionLabel(hint.action)}
|
||||||
|
{slotIdx != null ? ` · Slot ${slotIdx + 1}` : ''}
|
||||||
|
</span>
|
||||||
|
{hint.title ? (
|
||||||
|
<div style={{ fontWeight: 600, color: 'var(--text1)' }}>{hint.title}</div>
|
||||||
|
) : null}
|
||||||
|
{hint.reason ? <p style={{ margin: '4px 0 0' }}>{hint.reason}</p> : null}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function GapOfferCard({
|
function GapOfferCard({
|
||||||
offer,
|
offer,
|
||||||
slotCount,
|
slotCount,
|
||||||
|
|
@ -163,6 +281,7 @@ export default function ProgressionFindingsPanel({
|
||||||
onGenerateGapAi,
|
onGenerateGapAi,
|
||||||
onRematchSlots = null,
|
onRematchSlots = null,
|
||||||
onOptimizeCompare = null,
|
onOptimizeCompare = null,
|
||||||
|
optimizationPreviewQa = null,
|
||||||
canOptimizeCompare = false,
|
canOptimizeCompare = false,
|
||||||
optimizeCompareBusy = false,
|
optimizeCompareBusy = false,
|
||||||
rematchBusy = false,
|
rematchBusy = false,
|
||||||
|
|
@ -187,7 +306,8 @@ export default function ProgressionFindingsPanel({
|
||||||
<div className="card" style={{ position: 'sticky', top: '12px' }}>
|
<div className="card" style={{ position: 'sticky', top: '12px' }}>
|
||||||
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Graph-Bewertung</h3>
|
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Graph-Bewertung</h3>
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: 0, lineHeight: 1.45 }}>
|
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: 0, lineHeight: 1.45 }}>
|
||||||
Prüft den Slot-Stand und listet KI-Angebote für leere Stufen und Lücken.
|
Bewertet den aktuellen Slot-Stand (3-Stufen-QS, ohne Auto-Rematch) — dieselbe Logik nach Match,
|
||||||
|
solange keine Zuordnung geändert wurde.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
@ -222,8 +342,8 @@ export default function ProgressionFindingsPanel({
|
||||||
</strong>
|
</strong>
|
||||||
{pathQa.assignments_preserved ? (
|
{pathQa.assignments_preserved ? (
|
||||||
<p style={{ margin: '6px 0 0', color: 'var(--text2)', fontSize: '11px' }}>
|
<p style={{ margin: '6px 0 0', color: 'var(--text2)', fontSize: '11px' }}>
|
||||||
Bestehende Slot-Zuordnungen beibehalten — QS wie „Graph bewerten“, ohne Auto-Rematch.
|
Bewertung des aktuellen Pfads — keine automatische Umzuordnung.
|
||||||
{showOptimizeCompare ? ' „Übungen matchen“ oder „Optimierung vergleichen“ prüft Alternativen.' : ''}
|
{showOptimizeCompare ? ' Match prüft zusätzlich Optimierungsvorschläge im Vergleich.' : ''}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{strongResult ? (
|
{strongResult ? (
|
||||||
|
|
@ -386,6 +506,14 @@ export default function ProgressionFindingsPanel({
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
{optimizationPreviewQa ? (
|
||||||
|
<PathQaPipelineDetails
|
||||||
|
pathQa={optimizationPreviewQa}
|
||||||
|
draft={draft}
|
||||||
|
title="3-Stufen-Optimierung (Vorschlag nach Match)"
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text3)', margin: 0 }}>
|
<p style={{ fontSize: '12px', color: 'var(--text3)', margin: 0 }}>
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const [comparePayload, setComparePayload] = useState(null)
|
const [comparePayload, setComparePayload] = useState(null)
|
||||||
const [comparing, setComparing] = useState(false)
|
const [comparing, setComparing] = useState(false)
|
||||||
const [compareApplying, setCompareApplying] = useState(false)
|
const [compareApplying, setCompareApplying] = useState(false)
|
||||||
|
const [proposedPathQa, setProposedPathQa] = useState(null)
|
||||||
|
|
||||||
const loadGraph = useCallback(async () => {
|
const loadGraph = useCallback(async () => {
|
||||||
if (!graphId) return
|
if (!graphId) return
|
||||||
|
|
@ -435,6 +436,35 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildEvaluateRequest = (synced) => {
|
||||||
|
const override = majorStepsToOverridePayload(synced.slots)
|
||||||
|
return {
|
||||||
|
query: (synced.goalQuery || '').trim(),
|
||||||
|
max_steps: synced.slots.length || draft?.maxSteps || 5,
|
||||||
|
include_path_qa: true,
|
||||||
|
include_llm_path_qa: true,
|
||||||
|
include_ai_gap_fill: true,
|
||||||
|
include_path_reorder: false,
|
||||||
|
include_llm_intent: false,
|
||||||
|
evaluate_only: true,
|
||||||
|
evaluate_steps: slotsToEvaluateSteps(synced),
|
||||||
|
roadmap_override: override,
|
||||||
|
slot_assignments: slotsToSlotAssignments(synced),
|
||||||
|
progression_graph_id: Number(graphId),
|
||||||
|
...roadmapStructuredPayload(synced.startSituation, synced.targetState, synced.roadmapNotes),
|
||||||
|
...catalogApiPayload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPathEvaluate = async (synced) => api.suggestProgressionPath(buildEvaluateRequest(synced))
|
||||||
|
|
||||||
|
const applyEvaluateResult = (synced, res) => {
|
||||||
|
setSemanticBrief(res?.semantic_brief_summary || null)
|
||||||
|
setPathQa(res?.path_qa || null)
|
||||||
|
const { draft: evaluated, remainingOffers } = applyEvaluateResponseToDraft(synced, res)
|
||||||
|
return { draft: { ...evaluated, lastFindings: res?.path_qa || null }, remainingOffers }
|
||||||
|
}
|
||||||
|
|
||||||
const buildMatchRequestBase = (synced) => {
|
const buildMatchRequestBase = (synced) => {
|
||||||
const override = majorStepsToOverridePayload(synced.slots)
|
const override = majorStepsToOverridePayload(synced.slots)
|
||||||
return {
|
return {
|
||||||
|
|
@ -460,6 +490,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const fetchOptimizeCompare = async (synced) => {
|
const fetchOptimizeCompare = async (synced) => {
|
||||||
const res = await api.suggestProgressionPath({
|
const res = await api.suggestProgressionPath({
|
||||||
...buildMatchRequestBase(synced),
|
...buildMatchRequestBase(synced),
|
||||||
|
evaluate_steps: slotsToEvaluateSteps(synced),
|
||||||
preserve_slot_assignments: false,
|
preserve_slot_assignments: false,
|
||||||
compare_with_assignments: true,
|
compare_with_assignments: true,
|
||||||
})
|
})
|
||||||
|
|
@ -475,6 +506,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const baselineQa = res?.baseline_path_qa || null
|
const baselineQa = res?.baseline_path_qa || null
|
||||||
const proposedQa = res?.proposed_path_qa || res?.path_qa || null
|
const proposedQa = res?.proposed_path_qa || res?.path_qa || null
|
||||||
setPathQa(baselineQa)
|
setPathQa(baselineQa)
|
||||||
|
setProposedPathQa(proposedQa)
|
||||||
|
|
||||||
const openCompareDialog = (diffCount, noticePrefix) => {
|
const openCompareDialog = (diffCount, noticePrefix) => {
|
||||||
setComparePayload(res)
|
setComparePayload(res)
|
||||||
|
|
@ -521,11 +553,16 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
try {
|
try {
|
||||||
const synced = syncProgressionRoadmapFromSlots(draft)
|
const synced = syncProgressionRoadmapFromSlots(draft)
|
||||||
const hasAssignments = draftHasLibrarySlotAssignments(synced)
|
const hasAssignments = draftHasLibrarySlotAssignments(synced)
|
||||||
|
setProposedPathQa(null)
|
||||||
|
|
||||||
if (hasAssignments) {
|
if (hasAssignments) {
|
||||||
const res = await fetchOptimizeCompare(synced)
|
const res = await fetchOptimizeCompare(synced)
|
||||||
const { opened, res: compareRes } = presentOptimizeCompare(res, { source: 'match' })
|
const { opened, res: compareRes } = presentOptimizeCompare(res, { source: 'match' })
|
||||||
if (opened) return
|
if (opened) {
|
||||||
|
const evalRes = await fetchPathEvaluate(synced)
|
||||||
|
setPathQa(evalRes?.path_qa || compareRes?.baseline_path_qa || null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { draft: matched, remainingOffers } = applyMatchResponseToDraft(
|
const { draft: matched, remainingOffers } = applyMatchResponseToDraft(
|
||||||
{
|
{
|
||||||
|
|
@ -536,19 +573,27 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
},
|
},
|
||||||
compareRes,
|
compareRes,
|
||||||
)
|
)
|
||||||
setDraft(matched)
|
const syncedMatched = syncProgressionRoadmapFromSlots(matched)
|
||||||
setPathQa(compareRes?.proposed_path_qa || compareRes?.path_qa || null)
|
const evalRes = await fetchPathEvaluate(syncedMatched)
|
||||||
setGapFillOffers(remainingOffers)
|
const { draft: evaluated, remainingOffers: evalOffers } = applyEvaluateResult(
|
||||||
|
syncedMatched,
|
||||||
|
evalRes,
|
||||||
|
)
|
||||||
|
setDraft(evaluated)
|
||||||
|
setGapFillOffers(evalOffers.length ? evalOffers : remainingOffers)
|
||||||
|
const evalPct = pathQaQualityPercent(evalRes?.path_qa)
|
||||||
const ms = compareRes?.match_summary
|
const ms = compareRes?.match_summary
|
||||||
if (ms) {
|
if (ms) {
|
||||||
setMatchNotice(
|
let notice = `Match: ${ms.library_matches ?? 0}/${ms.slot_count ?? '?'} Slots aus Bibliothek, ${ms.gap_fill_offer_count ?? 0} KI-Angebote.`
|
||||||
`Match: ${ms.library_matches ?? 0}/${ms.slot_count ?? '?'} Slots aus Bibliothek, ${ms.gap_fill_offer_count ?? 0} KI-Angebote. Bestehende Zuordnungen unverändert.`,
|
if (evalPct != null) {
|
||||||
)
|
notice += ` Pfad-QS (Bewertung): ${evalPct} %.`
|
||||||
|
}
|
||||||
|
setMatchNotice(notice)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await saveProgressionGraphDraft(api, graphId, {
|
await saveProgressionGraphDraft(api, graphId, {
|
||||||
...matched,
|
...evaluated,
|
||||||
lastFindings: compareRes?.proposed_path_qa || compareRes?.path_qa || null,
|
lastFindings: evalRes?.path_qa || null,
|
||||||
})
|
})
|
||||||
setDraft((prev) => (prev ? { ...prev, dirty: false } : prev))
|
setDraft((prev) => (prev ? { ...prev, dirty: false } : prev))
|
||||||
} catch (saveErr) {
|
} catch (saveErr) {
|
||||||
|
|
@ -625,6 +670,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const synced = syncProgressionRoadmapFromSlots(draft)
|
const synced = syncProgressionRoadmapFromSlots(draft)
|
||||||
const res = await fetchOptimizeCompare(synced)
|
const res = await fetchOptimizeCompare(synced)
|
||||||
presentOptimizeCompare(res, { source: 'manual' })
|
presentOptimizeCompare(res, { source: 'manual' })
|
||||||
|
const evalRes = await fetchPathEvaluate(synced)
|
||||||
|
setPathQa(evalRes?.path_qa || res?.baseline_path_qa || null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setActionErr(e.message || 'Optimierungs-Vergleich fehlgeschlagen')
|
setActionErr(e.message || 'Optimierungs-Vergleich fehlgeschlagen')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -642,15 +689,18 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
comparePayload.proposed_steps || comparePayload.steps,
|
comparePayload.proposed_steps || comparePayload.steps,
|
||||||
selectedMajorIndices,
|
selectedMajorIndices,
|
||||||
)
|
)
|
||||||
const proposedQa = comparePayload.proposed_path_qa || comparePayload.path_qa
|
const syncedNext = syncProgressionRoadmapFromSlots(nextDraft)
|
||||||
setDraft({ ...nextDraft, lastFindings: proposedQa || null })
|
const evalRes = await fetchPathEvaluate(syncedNext)
|
||||||
setPathQa(proposedQa || null)
|
const { draft: evaluated, remainingOffers } = applyEvaluateResult(syncedNext, evalRes)
|
||||||
|
setDraft({ ...evaluated, dirty: true })
|
||||||
|
setGapFillOffers(remainingOffers)
|
||||||
|
setProposedPathQa(null)
|
||||||
setCompareOpen(false)
|
setCompareOpen(false)
|
||||||
setComparePayload(null)
|
setComparePayload(null)
|
||||||
setMatchNotice('Ausgewählte Optimierungen übernommen.')
|
setMatchNotice('Ausgewählte Optimierungen übernommen — Pfad-QS neu bewertet.')
|
||||||
await saveProgressionGraphDraft(api, graphId, {
|
await saveProgressionGraphDraft(api, graphId, {
|
||||||
...nextDraft,
|
...evaluated,
|
||||||
lastFindings: proposedQa || null,
|
lastFindings: evalRes?.path_qa || null,
|
||||||
})
|
})
|
||||||
setDraft((prev) => (prev ? { ...prev, dirty: false } : prev))
|
setDraft((prev) => (prev ? { ...prev, dirty: false } : prev))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -668,29 +718,12 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
}
|
}
|
||||||
setEvaluating(true)
|
setEvaluating(true)
|
||||||
setActionErr('')
|
setActionErr('')
|
||||||
|
setProposedPathQa(null)
|
||||||
try {
|
try {
|
||||||
const synced = syncProgressionRoadmapFromSlots(draft)
|
const synced = syncProgressionRoadmapFromSlots(draft)
|
||||||
const override =
|
const res = await fetchPathEvaluate(synced)
|
||||||
validMajorSteps.length >= 2 ? majorStepsToOverridePayload(synced.slots) : undefined
|
const { draft: evaluated, remainingOffers } = applyEvaluateResult(synced, res)
|
||||||
const res = await api.suggestProgressionPath({
|
setDraft(evaluated)
|
||||||
query: q,
|
|
||||||
max_steps: synced.slots.length || draft.maxSteps || 5,
|
|
||||||
include_path_qa: true,
|
|
||||||
include_llm_path_qa: true,
|
|
||||||
include_ai_gap_fill: true,
|
|
||||||
include_path_reorder: false,
|
|
||||||
include_llm_intent: false,
|
|
||||||
evaluate_only: true,
|
|
||||||
evaluate_steps: slotsToEvaluateSteps(synced),
|
|
||||||
roadmap_override: override,
|
|
||||||
progression_graph_id: Number(graphId),
|
|
||||||
...roadmapStructuredPayload(draft.startSituation, draft.targetState, draft.roadmapNotes),
|
|
||||||
...catalogApiPayload,
|
|
||||||
})
|
|
||||||
setSemanticBrief(res?.semantic_brief_summary || null)
|
|
||||||
setPathQa(res?.path_qa || null)
|
|
||||||
const { draft: evaluated, remainingOffers } = applyEvaluateResponseToDraft(synced, res)
|
|
||||||
setDraft({ ...evaluated, lastFindings: res?.path_qa || null })
|
|
||||||
setGapFillOffers(remainingOffers)
|
setGapFillOffers(remainingOffers)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setActionErr(e.message || 'Bewertung fehlgeschlagen')
|
setActionErr(e.message || 'Bewertung fehlgeschlagen')
|
||||||
|
|
@ -1187,6 +1220,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
onGenerateGapAi={openGapFillPrep}
|
onGenerateGapAi={openGapFillPrep}
|
||||||
onRematchSlots={runMatch}
|
onRematchSlots={runMatch}
|
||||||
onOptimizeCompare={runOptimizeCompare}
|
onOptimizeCompare={runOptimizeCompare}
|
||||||
|
optimizationPreviewQa={proposedPathQa}
|
||||||
canOptimizeCompare={draftHasLibrarySlotAssignments(draft)}
|
canOptimizeCompare={draftHasLibrarySlotAssignments(draft)}
|
||||||
optimizeCompareBusy={comparing}
|
optimizeCompareBusy={comparing}
|
||||||
rematchBusy={matching}
|
rematchBusy={matching}
|
||||||
|
|
@ -1235,6 +1269,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
if (compareApplying) return
|
if (compareApplying) return
|
||||||
setCompareOpen(false)
|
setCompareOpen(false)
|
||||||
setComparePayload(null)
|
setComparePayload(null)
|
||||||
|
setProposedPathQa(null)
|
||||||
}}
|
}}
|
||||||
onApplySelected={applyOptimizeCompare}
|
onApplySelected={applyOptimizeCompare}
|
||||||
applying={compareApplying}
|
applying={compareApplying}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ export default function ProgressionOptimizeCompareModal({
|
||||||
const proposedQa = comparison.proposed_path_qa || comparison.path_qa
|
const proposedQa = comparison.proposed_path_qa || comparison.path_qa
|
||||||
const baselinePct = pathQaQualityPercent(baselineQa)
|
const baselinePct = pathQaQualityPercent(baselineQa)
|
||||||
const proposedPct = pathQaQualityPercent(proposedQa)
|
const proposedPct = pathQaQualityPercent(proposedQa)
|
||||||
|
const rematchRounds = proposedQa?.rematch_rounds
|
||||||
|
const rematchCount = Array.isArray(proposedQa?.rematch_log) ? proposedQa.rematch_log.length : 0
|
||||||
|
const refineCount = Array.isArray(proposedQa?.refine_log) ? proposedQa.refine_log.length : 0
|
||||||
|
const hintCount = Number(proposedQa?.optimization_hint_count || 0)
|
||||||
|
const tierCount = Array.isArray(proposedQa?.qa_tiers) ? proposedQa.qa_tiers.length : 0
|
||||||
|
|
||||||
const toggle = (midx) => {
|
const toggle = (midx) => {
|
||||||
setSelected((prev) => {
|
setSelected((prev) => {
|
||||||
|
|
@ -120,6 +125,19 @@ export default function ProgressionOptimizeCompareModal({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{tierCount > 0 || rematchCount > 0 || refineCount > 0 || hintCount > 0 ? (
|
||||||
|
<p style={{ fontSize: '11px', color: 'var(--text2)', margin: '0 0 14px', lineHeight: 1.45 }}>
|
||||||
|
3-Stufen-Optimierung im Vorschlag
|
||||||
|
{tierCount > 0 ? ` · ${tierCount} QS-Stufen` : ''}
|
||||||
|
{rematchCount > 0
|
||||||
|
? ` · Auto-Rematch ${rematchRounds != null ? `(${rematchRounds} Runde(n))` : ''}: ${rematchCount} Anpassung(en)`
|
||||||
|
: ''}
|
||||||
|
{refineCount > 0 ? ` · ${refineCount} Stufen-Spec verfeinert` : ''}
|
||||||
|
{hintCount > 0 ? ` · ${hintCount} Handlungshinweis(e)` : ''}
|
||||||
|
. Details im Panel „Graph-Bewertung“.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{slotDiffs.length === 0 ? (
|
{slotDiffs.length === 0 ? (
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text2)' }}>
|
<p style={{ fontSize: '12px', color: 'var(--text2)' }}>
|
||||||
Keine abweichenden Slot-Zuordnungen — der optimierte Lauf liefert denselben Pfad.
|
Keine abweichenden Slot-Zuordnungen — der optimierte Lauf liefert denselben Pfad.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user