Add findings_stale field to GraphPlanningRoadmapArtifact and update ProgressionGraphEditor for state management
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 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m34s
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 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m34s
- Introduced `findings_stale` field in `GraphPlanningRoadmapArtifact` to track the freshness of findings. - Updated `ProgressionGraphEditor` to manage `findingsStale` state across various functions, ensuring accurate representation of evaluation status. - Modified related utility functions and tests to accommodate the new state, enhancing overall functionality and user feedback in the progression graph management process.
This commit is contained in:
parent
5e5f4ca8d4
commit
7265cd5a01
|
|
@ -37,6 +37,7 @@ class GraphPlanningRoadmapArtifact(BaseModel):
|
||||||
path_skill_expectations: Optional[Dict[str, Any]] = None
|
path_skill_expectations: Optional[Dict[str, Any]] = None
|
||||||
slot_contents: Optional[List[SlotContentEntry]] = None
|
slot_contents: Optional[List[SlotContentEntry]] = None
|
||||||
last_findings: Optional[Dict[str, Any]] = None
|
last_findings: Optional[Dict[str, Any]] = None
|
||||||
|
findings_stale: bool = Field(default=False)
|
||||||
planning_catalog_context: Optional[Dict[str, Any]] = None
|
planning_catalog_context: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
@field_validator("progression_roadmap", "path_skill_expectations", "last_findings", "planning_catalog_context", mode="before")
|
@field_validator("progression_roadmap", "path_skill_expectations", "last_findings", "planning_catalog_context", mode="before")
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,15 @@ def test_normalize_slot_contents():
|
||||||
)
|
)
|
||||||
assert len(out["slot_contents"]) == 2
|
assert len(out["slot_contents"]) == 2
|
||||||
assert out["slot_contents"][1]["primary"]["kind"] == "proposal"
|
assert out["slot_contents"][1]["primary"]["kind"] == "proposal"
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_planning_roadmap_with_findings_stale():
|
||||||
|
out = normalize_planning_roadmap_payload(
|
||||||
|
{
|
||||||
|
"goal_query": "Mae Geri",
|
||||||
|
"last_findings": {"overall_ok": False},
|
||||||
|
"findings_stale": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert out["findings_stale"] is True
|
||||||
|
assert out["last_findings"]["overall_ok"] is False
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
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 [proposedPathQa, setProposedPathQa] = useState(null)
|
||||||
const [evaluationStale, setEvaluationStale] = useState(false)
|
|
||||||
|
|
||||||
const loadGraph = useCallback(async ({ preserveEvaluationStale = false } = {}) => {
|
const loadGraph = useCallback(async () => {
|
||||||
if (!graphId) return
|
if (!graphId) return
|
||||||
setBusy(true)
|
setBusy(true)
|
||||||
setLoadErr('')
|
setLoadErr('')
|
||||||
|
|
@ -154,7 +153,6 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
)
|
)
|
||||||
const findings = graph?.planning_roadmap?.last_findings
|
const findings = graph?.planning_roadmap?.last_findings
|
||||||
if (findings) setPathQa(findings)
|
if (findings) setPathQa(findings)
|
||||||
if (!preserveEvaluationStale) setEvaluationStale(false)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoadErr(e.message || 'Graph konnte nicht geladen werden')
|
setLoadErr(e.message || 'Graph konnte nicht geladen werden')
|
||||||
setDraft(null)
|
setDraft(null)
|
||||||
|
|
@ -202,9 +200,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
setDraft((prev) => {
|
setDraft((prev) => {
|
||||||
if (!prev) return prev
|
if (!prev) return prev
|
||||||
const next = patchFn(prev)
|
const next = patchFn(prev)
|
||||||
return { ...next, dirty: true }
|
return { ...next, dirty: true, findingsStale: true }
|
||||||
})
|
})
|
||||||
setEvaluationStale(true)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const gapContextParams = useMemo(() => {
|
const gapContextParams = useMemo(() => {
|
||||||
|
|
@ -367,9 +364,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
{ ...prev, progressionRoadmap: roadmap },
|
{ ...prev, progressionRoadmap: roadmap },
|
||||||
roadmap,
|
roadmap,
|
||||||
)
|
)
|
||||||
return { ...structured, dirty: true }
|
return { ...structured, dirty: true, findingsStale: true }
|
||||||
})
|
})
|
||||||
setEvaluationStale(true)
|
|
||||||
setStartTargetReady(true)
|
setStartTargetReady(true)
|
||||||
setSemanticBrief(res?.semantic_brief_summary || null)
|
setSemanticBrief(res?.semantic_brief_summary || null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -437,8 +433,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
graphName: draft.graphName,
|
graphName: draft.graphName,
|
||||||
})
|
})
|
||||||
const withPhases = syncSlotPhasesFromRoadmap(hydrated, roadmap)
|
const withPhases = syncSlotPhasesFromRoadmap(hydrated, roadmap)
|
||||||
setDraft({ ...withPhases, goalQuery: q, maxSteps: majorCount || withPhases.maxSteps, dirty: true })
|
setDraft({ ...withPhases, goalQuery: q, maxSteps: majorCount || withPhases.maxSteps, dirty: true, findingsStale: true })
|
||||||
setEvaluationStale(true)
|
|
||||||
setSemanticBrief(res?.semantic_brief_summary || null)
|
setSemanticBrief(res?.semantic_brief_summary || null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setActionErr(e.message || 'Roadmap-Generierung fehlgeschlagen')
|
setActionErr(e.message || 'Roadmap-Generierung fehlgeschlagen')
|
||||||
|
|
@ -473,9 +468,11 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const applyEvaluateResult = (synced, res) => {
|
const applyEvaluateResult = (synced, res) => {
|
||||||
setSemanticBrief(res?.semantic_brief_summary || null)
|
setSemanticBrief(res?.semantic_brief_summary || null)
|
||||||
setPathQa(res?.path_qa || null)
|
setPathQa(res?.path_qa || null)
|
||||||
setEvaluationStale(false)
|
|
||||||
const { draft: evaluated, remainingOffers } = applyEvaluateResponseToDraft(synced, res)
|
const { draft: evaluated, remainingOffers } = applyEvaluateResponseToDraft(synced, res)
|
||||||
return { draft: { ...evaluated, lastFindings: res?.path_qa || null }, remainingOffers }
|
return {
|
||||||
|
draft: { ...evaluated, lastFindings: res?.path_qa || null, findingsStale: false },
|
||||||
|
remainingOffers,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildMatchRequestBase = (synced) => {
|
const buildMatchRequestBase = (synced) => {
|
||||||
|
|
@ -651,13 +648,12 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
)
|
)
|
||||||
const syncedNext = syncProgressionRoadmapFromSlots(nextDraft)
|
const syncedNext = syncProgressionRoadmapFromSlots(nextDraft)
|
||||||
|
|
||||||
setDraft({ ...syncedNext, dirty: false })
|
setDraft({ ...syncedNext, dirty: false, findingsStale: true })
|
||||||
setEvaluationStale(true)
|
|
||||||
setCompareOpen(false)
|
setCompareOpen(false)
|
||||||
setComparePayload(null)
|
setComparePayload(null)
|
||||||
setProposedPathQa(null)
|
setProposedPathQa(null)
|
||||||
|
|
||||||
await saveProgressionGraphDraft(api, graphId, syncedNext)
|
await saveProgressionGraphDraft(api, graphId, { ...syncedNext, findingsStale: true })
|
||||||
setMatchNotice(
|
setMatchNotice(
|
||||||
'Übernommen und gespeichert. Bewertung bezieht sich noch auf den vorherigen Stand — bitte „Graph bewerten“.',
|
'Übernommen und gespeichert. Bewertung bezieht sich noch auf den vorherigen Stand — bitte „Graph bewerten“.',
|
||||||
)
|
)
|
||||||
|
|
@ -697,7 +693,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
setActionErr('')
|
setActionErr('')
|
||||||
try {
|
try {
|
||||||
await saveProgressionGraphDraft(api, graphId, { ...draft, lastFindings: pathQa })
|
await saveProgressionGraphDraft(api, graphId, { ...draft, lastFindings: pathQa })
|
||||||
await loadGraph({ preserveEvaluationStale: true })
|
await loadGraph()
|
||||||
if (typeof onSaved === 'function') await onSaved()
|
if (typeof onSaved === 'function') await onSaved()
|
||||||
alert('Progressionsgraph gespeichert.')
|
alert('Progressionsgraph gespeichert.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -710,9 +706,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const handleApplyGapOffer = (offer, slotIndex) => {
|
const handleApplyGapOffer = (offer, slotIndex) => {
|
||||||
setDraft((prev) => {
|
setDraft((prev) => {
|
||||||
const next = applyGapOfferToDraft(prev, offer, { slotIndex })
|
const next = applyGapOfferToDraft(prev, offer, { slotIndex })
|
||||||
return { ...next, dirty: true }
|
return { ...next, dirty: true, findingsStale: true }
|
||||||
})
|
})
|
||||||
setEvaluationStale(true)
|
|
||||||
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,9 +718,8 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
}
|
}
|
||||||
setDraft((prev) => {
|
setDraft((prev) => {
|
||||||
const next = applyGapOfferToDraft(prev, offer, { insertNewSlot: true })
|
const next = applyGapOfferToDraft(prev, offer, { insertNewSlot: true })
|
||||||
return { ...next, dirty: true }
|
return { ...next, dirty: true, findingsStale: true }
|
||||||
})
|
})
|
||||||
setEvaluationStale(true)
|
|
||||||
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -838,8 +832,10 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
: null
|
: null
|
||||||
if (resolvedSlot != null) {
|
if (resolvedSlot != null) {
|
||||||
setSlotQuickCreateIndex(resolvedSlot)
|
setSlotQuickCreateIndex(resolvedSlot)
|
||||||
setDraft((prev) => applyGapOfferToDraft(prev, enrichedOffer, { slotIndex: resolvedSlot }))
|
setDraft((prev) => ({
|
||||||
setEvaluationStale(true)
|
...applyGapOfferToDraft(prev, enrichedOffer, { slotIndex: resolvedSlot }),
|
||||||
|
findingsStale: true,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
setSlotQuickCreateDraft(aiDraft)
|
setSlotQuickCreateDraft(aiDraft)
|
||||||
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
setGapFillOffers((prev) => prev.filter((o) => o.offer_id !== offer?.offer_id))
|
||||||
|
|
@ -887,8 +883,11 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
const payload = buildQuickCreateExercisePayloadFromDraft(slotQuickCreateDraft)
|
const payload = buildQuickCreateExercisePayloadFromDraft(slotQuickCreateDraft)
|
||||||
const created = await api.createExercise(payload)
|
const created = await api.createExercise(payload)
|
||||||
if (!created?.id) throw new Error('Anlegen fehlgeschlagen')
|
if (!created?.id) throw new Error('Anlegen fehlgeschlagen')
|
||||||
setDraft((prev) => setSlotPrimaryLibrary(prev, slotQuickCreateIndex, created))
|
setDraft((prev) => ({
|
||||||
setEvaluationStale(true)
|
...setSlotPrimaryLibrary(prev, slotQuickCreateIndex, created),
|
||||||
|
dirty: true,
|
||||||
|
findingsStale: true,
|
||||||
|
}))
|
||||||
setSlotQuickCreateDraft(null)
|
setSlotQuickCreateDraft(null)
|
||||||
setSlotQuickCreateIndex(null)
|
setSlotQuickCreateIndex(null)
|
||||||
setActiveOffer(null)
|
setActiveOffer(null)
|
||||||
|
|
@ -1177,7 +1176,7 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
||||||
slotCount={draft.slots.length}
|
slotCount={draft.slots.length}
|
||||||
loading={evaluating}
|
loading={evaluating}
|
||||||
error=""
|
error=""
|
||||||
evaluationStale={evaluationStale}
|
evaluationStale={Boolean(draft?.findingsStale)}
|
||||||
onEvaluate={runEvaluate}
|
onEvaluate={runEvaluate}
|
||||||
onApplyGapOffer={handleApplyGapOffer}
|
onApplyGapOffer={handleApplyGapOffer}
|
||||||
onInsertGapSlot={handleInsertGapSlot}
|
onInsertGapSlot={handleInsertGapSlot}
|
||||||
|
|
|
||||||
|
|
@ -835,6 +835,7 @@ export function hydrateProgressionGraphDraft({
|
||||||
progressionRoadmap: artifact?.progression_roadmap || null,
|
progressionRoadmap: artifact?.progression_roadmap || null,
|
||||||
planningCatalogContext: parsePlanningCatalogContextFromArtifact(artifact),
|
planningCatalogContext: parsePlanningCatalogContextFromArtifact(artifact),
|
||||||
lastFindings: artifact?.last_findings || null,
|
lastFindings: artifact?.last_findings || null,
|
||||||
|
findingsStale: Boolean(artifact?.findings_stale),
|
||||||
primaryChainEdgeIds: primaryChain?.edges?.map((e) => e.id) || [],
|
primaryChainEdgeIds: primaryChain?.edges?.map((e) => e.id) || [],
|
||||||
siblingEdgeIds: siblingEdges.map((e) => e.id),
|
siblingEdgeIds: siblingEdges.map((e) => e.id),
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
|
@ -871,6 +872,7 @@ export function buildPlanningArtifactFromDraft(draft, { lastFindings = undefined
|
||||||
|
|
||||||
const findings = lastFindings !== undefined ? lastFindings : draft.lastFindings
|
const findings = lastFindings !== undefined ? lastFindings : draft.lastFindings
|
||||||
if (findings) artifact.last_findings = findings
|
if (findings) artifact.last_findings = findings
|
||||||
|
artifact.findings_stale = Boolean(draft.findingsStale)
|
||||||
|
|
||||||
return artifact
|
return artifact
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user