Skip to content

Commit f3e883c

Browse files
Explain inter-rater reliability before the first reviewer pair completes
The section only rendered once a reviewer pair had produced comparable answers, so a dual-review project with nothing finished yet showed no trace of the feature at all. It now keeps its heading and says what unlocks the numbers whenever any study has two different reviewers, and stays hidden entirely on single-reviewer projects. Claude-Session: https://claude.ai/code/session_01H77BMkZ9GhK8D98TG4cpTg
1 parent 3d6b9a2 commit f3e883c

3 files changed

Lines changed: 39 additions & 13 deletions

File tree

packages/web/src/components/project/overview-tab/OverviewTab.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ export function OverviewTab() {
9090
return map;
9191
}, [studies]);
9292

93+
// Reliability only applies to studies two different people appraise, so the
94+
// section stays hidden entirely on a single-reviewer project.
95+
const dualReviewedStudies = studies.filter(
96+
s => s.reviewer1 && s.reviewer2 && s.reviewer1 !== s.reviewer2,
97+
).length;
98+
9399
const reliability = useMemo(() => {
94100
// getData throws while the pool has no active connection (a cold refresh
95101
// renders this tab from cached rows before the gate's effects run) --
@@ -115,7 +121,9 @@ export function OverviewTab() {
115121

116122
<ProgressSection counts={stageCounts} total={studies.length} />
117123

118-
{reliability.length > 0 && <ReliabilitySection tools={reliability} />}
124+
{(reliability.length > 0 || dualReviewedStudies > 0) && (
125+
<ReliabilitySection tools={reliability} dualReviewedStudies={dualReviewedStudies} />
126+
)}
119127

120128
{!empty && (
121129
<section aria-labelledby='overview-results-heading'>

packages/web/src/components/project/overview-tab/ReliabilitySection.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ReliabilityAboutDialog } from './ReliabilityAboutDialog';
2020

2121
interface ReliabilitySectionProps {
2222
tools: ToolReliability[];
23+
dualReviewedStudies: number;
2324
}
2425

2526
// Landis and Koch band boundaries, which getKappaInterpretation also uses
@@ -188,22 +189,31 @@ function ToolCard({ tool }: { tool: ToolReliability }) {
188189
);
189190
}
190191

191-
export function ReliabilitySection({ tools }: ReliabilitySectionProps) {
192+
export function ReliabilitySection({ tools, dualReviewedStudies }: ReliabilitySectionProps) {
192193
return (
193194
<section aria-labelledby='overview-reliability-heading'>
194195
<div className='mb-2.5 flex items-baseline justify-between gap-4'>
195196
<h2 id='overview-reliability-heading' className='text-sm font-semibold'>
196197
Inter-rater reliability
197198
</h2>
198-
<span className='text-muted-foreground text-xs'>
199-
Before reconciliation, pooled across reviewer pairs
200-
</span>
201-
</div>
202-
<div className='flex flex-col gap-3'>
203-
{tools.map(tool => (
204-
<ToolCard key={tool.definition.type} tool={tool} />
205-
))}
199+
{tools.length > 0 && (
200+
<span className='text-muted-foreground text-xs'>
201+
Before reconciliation, pooled across reviewer pairs
202+
</span>
203+
)}
206204
</div>
205+
{tools.length === 0 ?
206+
<p className='text-muted-foreground text-sm'>
207+
Agreement appears once both reviewers of a study have completed their appraisals.{' '}
208+
{dualReviewedStudies} {dualReviewedStudies === 1 ? 'study has' : 'studies have'} two
209+
reviewers.
210+
</p>
211+
: <div className='flex flex-col gap-3'>
212+
{tools.map(tool => (
213+
<ToolCard key={tool.definition.type} tool={tool} />
214+
))}
215+
</div>
216+
}
207217
</section>
208218
);
209219
}

packages/web/src/components/project/overview-tab/__tests__/ReliabilitySection.test.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function rob2Tool(): ToolReliability {
4848

4949
describe('ReliabilitySection', () => {
5050
it('renders one card per tool with agreement, kappa, overall and breakdown', () => {
51-
render(<ReliabilitySection tools={[rob2Tool()]} />);
51+
render(<ReliabilitySection tools={[rob2Tool()]} dualReviewedStudies={6} />);
5252

5353
expect(screen.getByRole('heading', { name: 'RoB 2' })).toBeInTheDocument();
5454
expect(screen.getByText('8 outcomes across 6 studies')).toBeInTheDocument();
@@ -79,14 +79,14 @@ describe('ReliabilitySection', () => {
7979
tool.definition.judgementScale,
8080
tool.definition.items,
8181
);
82-
render(<ReliabilitySection tools={[tool]} />);
82+
render(<ReliabilitySection tools={[tool]} dualReviewedStudies={6} />);
8383
expect(
8484
screen.getByText(`Needs ${MIN_PAIRS_FOR_KAPPA - 1} more comparisons`),
8585
).toBeInTheDocument();
8686
});
8787

8888
it('opens a dialog with the tool notes and the confusion matrix', () => {
89-
render(<ReliabilitySection tools={[rob2Tool()]} />);
89+
render(<ReliabilitySection tools={[rob2Tool()]} dualReviewedStudies={6} />);
9090
fireEvent.click(screen.getByRole('button', { name: 'How this is calculated' }));
9191

9292
const dialog = screen.getByRole('dialog', {
@@ -99,4 +99,12 @@ describe('ReliabilitySection', () => {
9999
expect(rows[0]).toHaveTextContent('Low2004');
100100
expect(rows[2]).toHaveTextContent('High004');
101101
});
102+
103+
it('explains what is missing when no reviewer pair has finished yet', () => {
104+
render(<ReliabilitySection tools={[]} dualReviewedStudies={1} />);
105+
106+
expect(screen.getByRole('heading', { name: 'Inter-rater reliability' })).toBeInTheDocument();
107+
expect(screen.getByText(/1 study has two reviewers/)).toBeInTheDocument();
108+
expect(screen.queryByText(/pooled across reviewer pairs/)).not.toBeInTheDocument();
109+
});
102110
});

0 commit comments

Comments
 (0)