diff --git a/frontend/app/[lang]/can-sr/l1-screen/view/page.tsx b/frontend/app/[lang]/can-sr/l1-screen/view/page.tsx
index 67445858..f3d9a513 100644
--- a/frontend/app/[lang]/can-sr/l1-screen/view/page.tsx
+++ b/frontend/app/[lang]/can-sr/l1-screen/view/page.tsx
@@ -274,8 +274,8 @@ export default function CanSrL1ScreenPage() {
if (!res.ok) {
setError(
data?.error ||
- data?.detail ||
- `Failed to load citation (${res.status})`,
+ data?.detail ||
+ `Failed to load citation (${res.status})`,
)
setCitation(null)
} else {
@@ -561,6 +561,12 @@ export default function CanSrL1ScreenPage() {
) throw new Error('AI response did not match the requested citation')
if (currentCitationKeyRef.current !== requestKey) return false
+ // The run persists both the AI suggestion and the citation-backed display
+ // values. Reload the citation so the question area and selection control
+ // receive the same fresh state without requiring a browser refresh
+ await fetchCitationById(runContext.citationId)
+ if (currentCitationKeyRef.current !== requestKey) return false
+
// Targeted update: fetch only the llm_* column for this criterion and update
// aiPanels directly — no full page re-render.
try {
@@ -771,8 +777,8 @@ export default function CanSrL1ScreenPage() {
@@ -887,20 +893,20 @@ export default function CanSrL1ScreenPage() {
- {hasAgentic && crit ? (
-
-
- {critDisagrees ? (
-
- Critical review recommends a different answer: {String((crit as any)?.answer ?? '—')}
-
- ) : (
- Critical review supports the screening answer
- )}
-
-
Judgment confidence: {String((crit as any)?.confidence ?? '—')}
-
- ) : null}
+ {hasAgentic && crit ? (
+
+
+ {critDisagrees ? (
+
+ Critical review recommends a different answer: {String((crit as any)?.answer ?? '—')}
+
+ ) : (
+ Critical review supports the screening answer
+ )}
+
+
Judgment confidence: {String((crit as any)?.confidence ?? '—')}
+
+ ) : null}
) : null}
diff --git a/frontend/app/[lang]/can-sr/l2-screen/view/page.tsx b/frontend/app/[lang]/can-sr/l2-screen/view/page.tsx
index 40c9f273..cd549f35 100644
--- a/frontend/app/[lang]/can-sr/l2-screen/view/page.tsx
+++ b/frontend/app/[lang]/can-sr/l2-screen/view/page.tsx
@@ -18,6 +18,7 @@ import {
resolveConfiguredValue,
resolveDisplayedAnswer,
} from '@/components/can-sr/screening-citation-context'
+import { criterionKey, readScreeningValue } from '@/components/can-sr/screeningColumns'
type ValidationEntry = { user: string; validated_at: string }
@@ -111,11 +112,6 @@ function humanScreenColumn(name: string) {
return base.replace(/^llm_/, 'human_')
}
-function stageColumn(name: string, prefix: 'llm' | 'human', stage: 'l1' | 'l2') {
- const core = snakeCaseColumn(name).replace(/^llm_/, '')
- return `${prefix}_${stage}_${core}`.slice(0, 64)
-}
-
/* Types */
type CriteriaData = {
questions: string[]
@@ -350,8 +346,8 @@ export default function CanSrL2ScreenViewPage() {
if (!res.ok) {
setError(
data?.error ||
- data?.detail ||
- `Failed to load citation (${res.status})`,
+ data?.detail ||
+ `Failed to load citation (${res.status})`,
)
setCitation(null)
} else {
@@ -699,15 +695,11 @@ export default function CanSrL2ScreenViewPage() {
criteriaData.questions.forEach((q: string, idx: number) => {
const sourceStage = sourceFlags[idx] === 'l1' ? 'l1' : 'l2'
- const llmCol = stageColumn(q, 'llm', 'l2')
- const humanCol = stageColumn(q, 'human', 'l2')
- const l1HumanCol = stageColumn(q, 'human', 'l1')
- const legacyLlmCol = snakeCaseColumn(q)
const legacyHumanCol =
criteriaData.items?.[idx]?.answer_column || humanScreenColumn(q)
- const criterionKey = llmCol.replace(/^llm_l2_/, '')
- const fulltextRun = runsByCriterion[criterionKey]?.screening
- const titleAbstractRun = titleAbstractRunsByCriterion[criterionKey]
+ const criterion = criterionKey(q)
+ const fulltextRun = runsByCriterion[criterion]?.screening
+ const titleAbstractRun = titleAbstractRunsByCriterion[criterion]
const citationFulltextMd5 = String((citation as any)?.fulltext_md5 || '')
const runGuardrails = parseObject(fulltextRun?.guardrails)
const runFulltextMd5 = String(runGuardrails?.fulltext_md5 || '')
@@ -717,21 +709,19 @@ export default function CanSrL2ScreenViewPage() {
runFulltextMd5 === citationFulltextMd5,
)
- const humanRaw = resolveConfiguredValue(citation as any, humanCol)
+ const humanRaw = readScreeningValue(citation as any, 'l2', q, 'human')
// set-answer mirrors retrospective L1 answers into human_l2_*. The L1
// fallback keeps older rows usable and lets this screen display the
// copied answer before a reviewer changes it.
const copiedL1HumanRaw =
sourceStage === 'l1'
- ? resolveConfiguredValue(citation as any, l1HumanCol)
+ ? readScreeningValue(citation as any, 'l1', q, 'human')
: undefined
const legacyHumanRaw =
sourceStage === 'l1'
? resolveConfiguredValue(citation as any, legacyHumanCol)
: undefined
- const llmRaw =
- (citation as any)?.[llmCol] ??
- (citation as any)?.[legacyLlmCol]
+ const llmRaw = readScreeningValue(citation as any, 'l2', q, 'llm')
// Parse possible JSON payloads from DB
let humanParsed = humanRaw
@@ -759,18 +749,18 @@ export default function CanSrL2ScreenViewPage() {
typeof humanParsed === 'object' &&
((humanParsed as any).pipeline === 'fulltext' ||
String((humanParsed as any).screening_step || '').toLowerCase() ===
- 'l2')
+ 'l2')
const copiedHumanParsed =
humanRaw === undefined ? copiedL1HumanRaw : humanRaw
const parsedCopiedHuman =
typeof copiedHumanParsed === 'string'
? (() => {
- try {
- return JSON.parse(copiedHumanParsed)
- } catch {
- return copiedHumanParsed
- }
- })()
+ try {
+ return JSON.parse(copiedHumanParsed)
+ } catch {
+ return copiedHumanParsed
+ }
+ })()
: copiedHumanParsed
if (
isFulltextHuman &&
@@ -908,11 +898,11 @@ export default function CanSrL2ScreenViewPage() {
context ||
(srId && citationId && criteriaData
? {
- srId,
- citationId,
- questions: criteriaData.questions,
- model: selectedModel,
- }
+ srId,
+ citationId,
+ questions: criteriaData.questions,
+ model: selectedModel,
+ }
: null)
if (!runContext) return false
const requestKey = `${runContext.srId}:${runContext.citationId}`
@@ -920,13 +910,7 @@ export default function CanSrL2ScreenViewPage() {
const q = runContext.questions[questionIndex]
if (!q) return false
setRunAllError(null)
- const ck = q
- .trim()
- .toLowerCase()
- .replace(/[^\w]+/g, '_')
- .replace(/_+/g, '_')
- .replace(/^_+|_+$/g, '')
- .slice(0, 56)
+ const ck = criterionKey(q)
setCriterionStatus((prev) => ({ ...prev, [questionIndex]: 'running' }))
try {
const headers = {
@@ -950,8 +934,8 @@ export default function CanSrL2ScreenViewPage() {
if (!res.ok)
throw new Error(
runData?.detail ||
- runData?.error ||
- `AI request failed (${res.status})`,
+ runData?.error ||
+ `AI request failed (${res.status})`,
)
if (
String(runData?.citation_id ?? runContext.citationId) !==
@@ -961,10 +945,15 @@ export default function CanSrL2ScreenViewPage() {
}
if (currentCitationKeyRef.current !== requestKey) return false
+ // The run persists both the AI suggestion and the citation-backed display
+ // values. Reload the citation so the question area and selection control
+ // receive the same fresh state without requiring a browser refresh
+ await fetchCitationById(runContext.citationId)
+ if (currentCitationKeyRef.current !== requestKey) return false
+
// Targeted update: fetch only the llm_* column for this criterion and update
// aiPanels directly — avoids re-rendering the PDF viewer (no setCitation call).
try {
- const llmColName = `llm_${ck}`
const citRes = await fetch(
`/api/can-sr/citations/get?sr_id=${encodeURIComponent(runContext.srId)}&citation_id=${encodeURIComponent(runContext.citationId)}`,
{ method: 'GET', headers: getAuthHeaders() },
@@ -975,7 +964,7 @@ export default function CanSrL2ScreenViewPage() {
citRes.ok &&
citData
) {
- const llmRaw = citData[llmColName]
+ const llmRaw = readScreeningValue(citData, 'l2', q, 'llm')
let llmParsed = llmRaw
if (typeof llmRaw === 'string') {
try {
@@ -1109,13 +1098,13 @@ export default function CanSrL2ScreenViewPage() {
// We store normalized boxes as an array of {page,x,y,width,height}
const first = Array.isArray(bbox)
? bbox.find(
- (box: any) =>
- box &&
- typeof box === 'object' &&
- Number.isFinite(
- Number(box?.page ?? box?.page_number ?? box?.pageNum),
- ),
- )
+ (box: any) =>
+ box &&
+ typeof box === 'object' &&
+ Number.isFinite(
+ Number(box?.page ?? box?.page_number ?? box?.pageNum),
+ ),
+ )
: null
if (!first) return
viewerRef.current.scrollToCoord(first)
@@ -1149,9 +1138,9 @@ export default function CanSrL2ScreenViewPage() {
conversionId={null}
fileName={String(
citation?.title ||
- citation?.citation ||
- citation?.article_title ||
- 'Full text',
+ citation?.citation ||
+ citation?.article_title ||
+ 'Full text',
)}
coords={fulltextCoords || []}
pages={fulltextPages || []}
@@ -1317,12 +1306,12 @@ export default function CanSrL2ScreenViewPage() {
// - BUT do not highlight if this criterion is a confident-exclude (exclude + conf>=thr + critical agrees)
const criterionKey = q
? q
- .trim()
- .toLowerCase()
- .replace(/[^\w]+/g, '_')
- .replace(/_+/g, '_')
- .replace(/^_+|_+$/g, '')
- .slice(0, 56)
+ .trim()
+ .toLowerCase()
+ .replace(/[^\w]+/g, '_')
+ .replace(/_+/g, '_')
+ .replace(/^_+|_+$/g, '')
+ .slice(0, 56)
: ''
const r = (runsByCriterion as any)?.[criterionKey] || {}
@@ -1335,7 +1324,7 @@ export default function CanSrL2ScreenViewPage() {
run &&
currentPdfMd5 &&
String(guardrails?.fulltext_md5 || '') ===
- currentPdfMd5,
+ currentPdfMd5,
)
}
const scr = runMatchesCurrentPdf(r.screening)
@@ -1396,12 +1385,12 @@ export default function CanSrL2ScreenViewPage() {
aiData?.missing_fields,
)
? aiData.missing_fields.map((field: unknown) =>
- String(field),
- )
+ String(field),
+ )
: Array.isArray(screeningGuardrails?.missing_fields)
? screeningGuardrails.missing_fields.map(
- (field: unknown) => String(field),
- )
+ (field: unknown) => String(field),
+ )
: []
const missingLabels = missingFields.map(
(field: string) => {
@@ -1450,7 +1439,7 @@ export default function CanSrL2ScreenViewPage() {
))}
{answerStatus !== 'unconfigured' &&
- answerStatus !== 'matched' ? (
+ answerStatus !== 'matched' ? (
@@ -1635,7 +1624,7 @@ export default function CanSrL2ScreenViewPage() {
) : null}
{Array.isArray(aiData?.evidence_sentences) &&
- aiData.evidence_sentences.length > 0 ? (
+ aiData.evidence_sentences.length > 0 ? (
{dict.screening.evidence}
@@ -1679,7 +1668,7 @@ export default function CanSrL2ScreenViewPage() {
) : null}
{Array.isArray(aiData?.evidence_tables) &&
- aiData.evidence_tables.length > 0 ? (
+ aiData.evidence_tables.length > 0 ? (
Evidence tables:
@@ -1709,7 +1698,7 @@ export default function CanSrL2ScreenViewPage() {
) : null}
{Array.isArray(aiData?.evidence_figures) &&
- aiData.evidence_figures.length > 0 ? (
+ aiData.evidence_figures.length > 0 ? (