add second layer of result validation

This commit is contained in:
KS Jannette
2026-02-24 12:56:18 -05:00
parent d55f58985b
commit b7757ca406
12 changed files with 472 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
import { useQuery, useMutation } from '@tanstack/react-query';
import type { Sticky, Cluster } from '../types/types';
import type { Sticky, ClusterResponse } from '../types/types';
const API_BASE = `${import.meta.env.VITE_API_BASE}/v1/notes`;
@@ -11,7 +11,7 @@ const fetchStickies = async (): Promise<Sticky[]> => {
return response.json();
};
const fetchClusters = async (): Promise<Cluster[]> => {
const fetchClusters = async (): Promise<ClusterResponse> => {
const response = await fetch(`${API_BASE}/cluster`, {
method: 'POST',
});
@@ -29,7 +29,7 @@ export const useGetStickies = () => {
};
export const useClusterStickies = () => {
return useMutation<Cluster[]>({
return useMutation<ClusterResponse>({
mutationFn: fetchClusters,
});
};

View File

@@ -4,12 +4,22 @@ import Sticky from './sticky';
import Button from './button';
import '../styles/stickies.css';
const scoreLabel = (score: number): string => {
if (score >= 0.7) return 'Strong';
if (score >= 0.4) return 'Moderate';
if (score >= 0.1) return 'Weak';
return 'Poor';
};
const Stickies = () => {
const { data: stickies, isLoading, error } = useGetStickies();
const { mutate: cluster, data: clusters, isPending } = useClusterStickies();
const { mutate: cluster, data: clusterResponse, isPending } = useClusterStickies();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const clusters = clusterResponse?.clusters;
const score = clusterResponse?.score;
const handleCluster = () => {
cluster();
};
@@ -30,6 +40,11 @@ const Stickies = () => {
<Button onClick={handleCluster} isLoading={isPending} label="Group Stickies By Topic" />
{clusters ? (
<div className="clusters-container">
{score != null && (
<div className="cohesion-score">
Cluster cohesion: <strong>{score.toFixed(2)}</strong> {scoreLabel(score)}
</div>
)}
{clusters.map((group) => (
<div key={group.label} className="cluster-group">
<h3 className="cluster-label">{group.label}</h3>

View File

@@ -29,3 +29,14 @@
font-size: 1.2em;
font-weight: 600;
}
.cohesion-score {
text-align: center;
font-size: 0.95em;
color: #e0e0e0;
padding: 8px 16px;
background: rgba(109, 214, 244, 0.1);
border-radius: 6px;
width: fit-content;
margin: 0 auto;
}

View File

@@ -9,10 +9,13 @@ const MOCK_STICKIES = [
{ id: 'note_002', text: 'Export takes too long', x: 798, y: 211, author: 'user_2', color: 'green' },
];
const MOCK_CLUSTERS = [
{ label: 'Auth Issues', noteIds: ['note_001'] },
{ label: 'Export Issues', noteIds: ['note_002'] },
];
const MOCK_CLUSTER_RESPONSE = {
clusters: [
{ label: 'Auth Issues', noteIds: ['note_001'] },
{ label: 'Export Issues', noteIds: ['note_002'] },
],
score: 0.74,
};
let fetchMock: ReturnType<typeof vi.fn>;
@@ -73,7 +76,7 @@ describe('Stickies', () => {
// First call = GET notes, second call = POST cluster
fetchMock
.mockReturnValueOnce(mockFetchOk(MOCK_STICKIES))
.mockReturnValueOnce(mockFetchOk(MOCK_CLUSTERS));
.mockReturnValueOnce(mockFetchOk(MOCK_CLUSTER_RESPONSE));
const { Wrapper } = createTestWrapper();
render(<Stickies />, { wrapper: Wrapper });
@@ -90,7 +93,7 @@ describe('Stickies', () => {
it('should still render sticky note text inside clusters', async () => {
fetchMock
.mockReturnValueOnce(mockFetchOk(MOCK_STICKIES))
.mockReturnValueOnce(mockFetchOk(MOCK_CLUSTERS));
.mockReturnValueOnce(mockFetchOk(MOCK_CLUSTER_RESPONSE));
const { Wrapper } = createTestWrapper();
render(<Stickies />, { wrapper: Wrapper });

View File

@@ -11,3 +11,8 @@ export type Cluster = {
label: string;
noteIds: string[];
};
export type ClusterResponse = {
clusters: Cluster[];
score: number;
};