fix(confidence-engine): show canonical findings in previous learning

This commit is contained in:
2026-08-29 14:38:04 +01:00
parent a8539e2494
commit 0f4dfcbb17
3 changed files with 208 additions and 40 deletions
+70 -40
View File
@@ -138,6 +138,7 @@ function FocusedQuestionBody({
setFollowUpQuestion,
focusedContributions,
currentFindings,
findings,
onUpdateFindingDisposition,
onUpdateFindingProposition,
}) {
@@ -195,7 +196,7 @@ function FocusedQuestionBody({
{focused?.result && (
<>
{/* Prior accumulated learning (prior turns, current turn excluded — shown above) */}
<PriorContributionsSummary nodeId={nodeId} contributions={focusedContributions || []} />
<PriorContributionsSummary nodeId={nodeId} contributions={focusedContributions || []} findings={findings} />
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h3><ul className="list-disc pl-5 space-y-2">{(currentFindings?.length ? currentFindings : (focused.result.observations || [])).map((item, i) => {
const isFinding = typeof item === "object" && item !== null && "id" in item;
@@ -468,9 +469,25 @@ function EvidenceLimitCard({ summary }) {
);
}
// ── Canonical findings resolver for Previous Learning ──────────────
function getHistoricalPropositions(contribution, findings) {
const matching = (findings || []).filter(
(f) => f.contributionId === contribution.id,
);
if (matching.length === 0) {
return contribution.observations || [];
}
return matching
.filter((f) => f.userDisposition !== "not_relevant")
.map((f) => f.proposition);
}
// ── Prior contribution summary (embedded within FocusedQuestionBody) ───
function PriorContributionsSummary({ nodeId, contributions }) {
function PriorContributionsSummary({ nodeId, contributions, findings }) {
const threadContribs = (contributions || []).filter(
(c) => c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId,
);
@@ -488,19 +505,22 @@ function PriorContributionsSummary({ nodeId, contributions }) {
{priorContribs.map((c, idx) => (
<details key={c.id || idx} className="mb-2 border-b border-gray-200/40 last:border-0 pb-2 last:pb-0" open={idx === 0}>
<summary className="cursor-pointer text-xs font-medium text-gray-500 hover:text-gray-700 select-none py-1">
Turn {c.sequence || idx + 1} contribution ({c.observations?.length ?? 0} observations, {c.uncertainties?.length ?? 0} unclear)
Turn {c.sequence || idx + 1} contribution ({getHistoricalPropositions(c, findings).length ?? 0} observations, {c.uncertainties?.length ?? 0} unclear)
</summary>
<div className="pt-2 space-y-3">
{c.observations?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h5>
<ul className="list-disc pl-5 space-y-0.5">
{c.observations.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null}
{(() => {
const propositions = getHistoricalPropositions(c, findings);
return propositions.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h5>
<ul className="list-disc pl-5 space-y-0.5">
{propositions.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null;
})()}
{c.uncertainties?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h5>
@@ -520,7 +540,7 @@ function PriorContributionsSummary({ nodeId, contributions }) {
// ── Standalone previous learning block (for two-column secondary placement) ───
function SecondaryPreviousLearning({ nodeId, contributions }) {
function SecondaryPreviousLearning({ nodeId, contributions, findings }) {
const threadContribs = (contributions || []).filter(
(c) => c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId,
);
@@ -538,19 +558,22 @@ function SecondaryPreviousLearning({ nodeId, contributions }) {
{priorContribs.map((c, idx) => (
<details key={c.id || idx} className="mb-2 border-b border-gray-200/40 last:border-0 pb-2 last:pb-0" open={idx === 0}>
<summary className="cursor-pointer text-xs font-medium text-gray-500 hover:text-gray-700 select-none py-1">
Turn {c.sequence || idx + 1} contribution ({c.observations?.length ?? 0} observations, {c.uncertainties?.length ?? 0} unclear)
Turn {c.sequence || idx + 1} contribution ({getHistoricalPropositions(c, findings).length ?? 0} observations, {c.uncertainties?.length ?? 0} unclear)
</summary>
<div className="pt-2 space-y-3">
{c.observations?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h5>
<ul className="list-disc pl-5 space-y-0.5">
{c.observations.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null}
{(() => {
const propositions = getHistoricalPropositions(c, findings);
return propositions.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h5>
<ul className="list-disc pl-5 space-y-0.5">
{propositions.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null;
})()}
{c.uncertainties?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h5>
@@ -570,7 +593,7 @@ function SecondaryPreviousLearning({ nodeId, contributions }) {
// ── Thread contributions badge (standalone — used outside focused body) ───
function ThreadContributionsBadge({ nodeId, contributions }) {
function ThreadContributionsBadge({ nodeId, contributions, findings }) {
const threadContribs = (contributions || []).filter(
(c) => c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId,
);
@@ -601,17 +624,20 @@ function ThreadContributionsBadge({ nodeId, contributions }) {
{threadContribs.map((c, idx) => (
<div key={c.id || idx} className="space-y-2">
{idx > 0 && <div className="text-[9px] text-gray-400 tracking-wider uppercase mt-3">Contribution #{c.sequence || idx + 1}</div>}
{/* What this tells us */}
{c.observations?.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h4>
<ul className="list-disc pl-5 space-y-0.5">
{c.observations.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null}
{/* What this tells us — via canonical Findings */}
{(() => {
const propositions = getHistoricalPropositions(c, findings);
return propositions.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h4>
<ul className="list-disc pl-5 space-y-0.5">
{propositions.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null;
})()}
{/* Still unclear */}
{c.uncertainties?.length ? (
@@ -1070,6 +1096,7 @@ function FocusedInvestigationWorkspace({
hasCompletedInvestigation,
focusedContributions,
currentFindings,
findings,
onUpdateFindingDisposition,
onUpdateFindingProposition,
}) {
@@ -1107,7 +1134,7 @@ function FocusedInvestigationWorkspace({
{/* ── Secondary context: Previous Learning — visible on all breakpoints, placed in grid column on wide / flows below primary on narrow ── */}
{hasResult && (
<SecondaryPreviousLearning nodeId={nodeId} contributions={focusedContributions || []} />
<SecondaryPreviousLearning nodeId={nodeId} contributions={focusedContributions || []} findings={findings} />
)}
</div>
</div>
@@ -1122,6 +1149,7 @@ function OpenQuestionsPanel({
startFocused, handleDeconstructSubmit, retryFormulation, setSelectedPresentationItemId,
setFocusedPresentationItemId, setFocusedAnswer, focusedAnswer, setDoneForNowIds,
setFollowUpQuestion, focusedContributions, focusedInvestigations, setIsFocusedWorkspaceOpen,
findings,
onUpdateFindingDisposition,
onUpdateFindingProposition,
}) {
@@ -1206,7 +1234,7 @@ function OpenQuestionsPanel({
/>
{/* Thread contributions for this node */}
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} findings={findings} />
</div>
</div>
);
@@ -1220,7 +1248,7 @@ function OpenQuestionsPanel({
{graph.nodes.filter((n) => doneForNowIds.includes(n.id)).map((node) => (
<div key={node.id} className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-4 py-3 space-y-1">
<p className="text-sm text-gray-500 leading-snug">{node.label}</p>
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} findings={findings} />
<button onClick={() => setDoneForNowIds(doneForNowIds.filter(id => id !== node.id))} style={{ cursor: "pointer" }} className="mt-1 rounded border border-gray-300 px-3 py-1 text-xs font-medium text-gray-500 hover:bg-white transition">Reopen</button>
</div>
))}
@@ -1917,6 +1945,7 @@ export default function ReasoningWorkspace({
focusedContributions={focusedContributions}
focusedInvestigations={focusedInvestigations}
setIsFocusedWorkspaceOpen={setIsFocusedWorkspaceOpen}
findings={findings}
onUpdateFindingProposition={onUpdateFindingProposition}
/>
)}
@@ -2092,6 +2121,7 @@ export default function ReasoningWorkspace({
}}
focusedContributions={focusedContributions}
currentFindings={currentFindings || []}
findings={findings}
onUpdateFindingDisposition={onUpdateFindingDisposition}
onUpdateFindingProposition={onUpdateFindingProposition}
/>