pappitti commited on
Commit
8614f30
·
1 Parent(s): 2703ef2

adding judge filters and responsiveness

Browse files
api/mismatches.ts CHANGED
@@ -11,6 +11,7 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
11
  const judge2Classification = url.searchParams.get('judge2Classification');
12
  const j2_compliance = url.searchParams.get('toCategory');
13
  const theme = url.searchParams.get('theme') || null;
 
14
 
15
  if (!judge1 || !j1_compliance || !judge2 || !j2_compliance || !judge1Classification || !judge2Classification) {
16
  return jsonResponse(res, 400, { error: 'judge1, j1_compliance, judge2, and j2_compliance are required.' });
@@ -95,14 +96,15 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
95
  -- Filter for the second judge's specific assessment
96
  AND a2.judge = ? AND a2.${judge2Classification} = ?
97
 
98
- -- Optional theme filter
99
- AND (? IS NULL OR q.theme = ?);
100
  `;
101
 
102
  const params = [
103
  judge1, j1_compliance,
104
  judge2, j2_compliance,
105
- theme, theme
 
106
  ];
107
 
108
  const rows = await db.query<any>(sql, ...params);
 
11
  const judge2Classification = url.searchParams.get('judge2Classification');
12
  const j2_compliance = url.searchParams.get('toCategory');
13
  const theme = url.searchParams.get('theme') || null;
14
+ const model = url.searchParams.get('model') || null;
15
 
16
  if (!judge1 || !j1_compliance || !judge2 || !j2_compliance || !judge1Classification || !judge2Classification) {
17
  return jsonResponse(res, 400, { error: 'judge1, j1_compliance, judge2, and j2_compliance are required.' });
 
96
  -- Filter for the second judge's specific assessment
97
  AND a2.judge = ? AND a2.${judge2Classification} = ?
98
 
99
+ -- Optional theme filter and model filter
100
+ AND (? IS NULL OR q.theme = ?) AND (? IS NULL OR r.model = ?);
101
  `;
102
 
103
  const params = [
104
  judge1, j1_compliance,
105
  judge2, j2_compliance,
106
+ theme, theme,
107
+ model, model
108
  ];
109
 
110
  const rows = await db.query<any>(sql, ...params);
api/models.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import db from '../src/lib/db.js';
3
+ import type { Model } from '../src/types.js';
4
+ import { jsonResponse } from './utils.js';
5
+
6
+ export default async function handler(_req: IncomingMessage, res: ServerResponse) {
7
+ try {
8
+ const sql = 'SELECT DISTINCT model FROM responses ORDER BY model ASC';
9
+ const models = await db.query<Model>(sql);
10
+ jsonResponse(res, 200, models);
11
+ } catch (error) {
12
+ console.error('Failed to fetch models:', error);
13
+ jsonResponse(res, 500, { error: 'Failed to fetch models' });
14
+ }
15
+ }
api/reclassification.ts CHANGED
@@ -9,6 +9,7 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
9
  const judge2 = url.searchParams.get('judge2');
10
  const judge2Classification = url.searchParams.get('judge2Classification');
11
  const theme = url.searchParams.get('theme') || null;
 
12
 
13
  if (!judge1 || !judge1Classification || !judge2 || !judge2Classification) {
14
  return jsonResponse(res, 400, { error: 'Query parameters judge1, judge1Classification, judge2, and judge2Classification are required.' });
@@ -29,13 +30,13 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
29
  JOIN responses r ON a1.r_uuid = r.uuid
30
  JOIN questions q ON r.q_uuid = q.uuid
31
  WHERE
32
- a1.judge = ? AND a2.judge = ? AND (? IS NULL OR q.theme = ?)
33
  GROUP BY
34
  judge1_compliance, -- Using the alias from SELECT is standard and clean
35
  judge2_compliance;
36
  `;
37
 
38
- const params = [judge1, judge2, theme, theme];
39
 
40
  const rows = await db.query<{ judge1_compliance: string, judge2_compliance: string, count: number }>(sql, ...params);
41
 
 
9
  const judge2 = url.searchParams.get('judge2');
10
  const judge2Classification = url.searchParams.get('judge2Classification');
11
  const theme = url.searchParams.get('theme') || null;
12
+ const model = url.searchParams.get('model') || null;
13
 
14
  if (!judge1 || !judge1Classification || !judge2 || !judge2Classification) {
15
  return jsonResponse(res, 400, { error: 'Query parameters judge1, judge1Classification, judge2, and judge2Classification are required.' });
 
30
  JOIN responses r ON a1.r_uuid = r.uuid
31
  JOIN questions q ON r.q_uuid = q.uuid
32
  WHERE
33
+ a1.judge = ? AND a2.judge = ? AND (? IS NULL OR q.theme = ?) AND (? IS NULL OR r.model = ?)
34
  GROUP BY
35
  judge1_compliance, -- Using the alias from SELECT is standard and clean
36
  judge2_compliance;
37
  `;
38
 
39
+ const params = [judge1, judge2, theme, theme, model, model];
40
 
41
  const rows = await db.query<{ judge1_compliance: string, judge2_compliance: string, count: number }>(sql, ...params);
42
 
src/App.tsx CHANGED
@@ -3,21 +3,23 @@ import { useState, useEffect} from 'react';
3
  import SankeyDiagram from './components/Sankey.js';
4
  import Heatmap from './components/Heatmap.js';
5
  import AssessmentItems from './components/itemList.js';
6
- import { getThemes, getJudges, getReclassificationData, getAssessmentItems } from './utils/apiUtils.js';
7
  import { modelSort } from './utils/chartUtils.js';
8
- import type { Theme, Judges, SelectedJudge, TransitionMatrix, AssessmentItem } from './types';
9
  import FilterBar from './components/Filterbar';
10
 
11
  function App() {
12
 
13
  const [themes, setThemes] = useState<Theme[]>([]);
14
  const [judges, setJudges] = useState<Judges[]>([]);
 
15
  const [matrix, setMatrix] = useState<TransitionMatrix | null>(null);
16
  const [error, setError] = useState<string | null>(null);
17
  const [isLoading, setIsLoading] = useState(false);
18
  const [loadingItems, setLoadingItems] = useState(false);
19
 
20
  const [selectedTheme, setSelectedTheme] = useState<string>('');
 
21
  const [selectedJudge1, setSelectedJudge1] = useState<SelectedJudge | null >(null);
22
  const [selectedJudge2, setSelectedJudge2] = useState<SelectedJudge | null >(null);
23
  const [selectedCategory, setSelectedCategory] = useState<string[] | null>(null);
@@ -31,12 +33,14 @@ function App() {
31
  useEffect(() => {
32
  const loadFilters = async () => {
33
  try {
34
- const [themesData, judgesData] = await Promise.all([
35
  getThemes(),
36
- getJudges()
 
37
  ]);
38
  setThemes(themesData);
39
  setJudges(judgesData.sort(modelSort));
 
40
 
41
  // Set default selections
42
  if (judgesData.length >= 2) {
@@ -72,7 +76,8 @@ function App() {
72
  selectedJudge1.classification,
73
  selectedJudge2.name,
74
  selectedJudge2.classification,
75
- selectedTheme
 
76
  );
77
  setMatrix(result);
78
  setSelectedItems([]);
@@ -85,7 +90,7 @@ function App() {
85
  };
86
 
87
  fetchData();
88
- }, [selectedTheme, selectedJudge1, selectedJudge2]);
89
 
90
  const handleJudge1NameChange = (newName: string) => {
91
  const newJudge = judges.find(j => j.name === newName);
@@ -132,7 +137,8 @@ function App() {
132
  selectedJudge2.name,
133
  selectedJudge2.classification,
134
  toCategory,
135
- selectedTheme
 
136
  )
137
  setSelectedItems(items);
138
  setSelectedCategory([fromCategory, toCategory]);
@@ -164,8 +170,11 @@ function App() {
164
  <FilterBar
165
  themes={themes}
166
  judges={judges}
 
167
  selectedTheme={selectedTheme}
168
  onThemeChange={setSelectedTheme}
 
 
169
  selectedJudge1={selectedJudge1}
170
  selectedJudge2={selectedJudge2}
171
  onJudge1NameChange={handleJudge1NameChange}
 
3
  import SankeyDiagram from './components/Sankey.js';
4
  import Heatmap from './components/Heatmap.js';
5
  import AssessmentItems from './components/itemList.js';
6
+ import { getThemes, getJudges, getReclassificationData, getAssessmentItems, getModels } from './utils/apiUtils.js';
7
  import { modelSort } from './utils/chartUtils.js';
8
+ import type { Theme, Judges, Model, SelectedJudge, TransitionMatrix, AssessmentItem } from './types';
9
  import FilterBar from './components/Filterbar';
10
 
11
  function App() {
12
 
13
  const [themes, setThemes] = useState<Theme[]>([]);
14
  const [judges, setJudges] = useState<Judges[]>([]);
15
+ const [models, setModels] = useState<Model[]>([]);
16
  const [matrix, setMatrix] = useState<TransitionMatrix | null>(null);
17
  const [error, setError] = useState<string | null>(null);
18
  const [isLoading, setIsLoading] = useState(false);
19
  const [loadingItems, setLoadingItems] = useState(false);
20
 
21
  const [selectedTheme, setSelectedTheme] = useState<string>('');
22
+ const [selectedModel, setSelectedModel] = useState<string>('');
23
  const [selectedJudge1, setSelectedJudge1] = useState<SelectedJudge | null >(null);
24
  const [selectedJudge2, setSelectedJudge2] = useState<SelectedJudge | null >(null);
25
  const [selectedCategory, setSelectedCategory] = useState<string[] | null>(null);
 
33
  useEffect(() => {
34
  const loadFilters = async () => {
35
  try {
36
+ const [themesData, judgesData, modelsData] = await Promise.all([
37
  getThemes(),
38
+ getJudges(),
39
+ getModels()
40
  ]);
41
  setThemes(themesData);
42
  setJudges(judgesData.sort(modelSort));
43
+ setModels(modelsData);
44
 
45
  // Set default selections
46
  if (judgesData.length >= 2) {
 
76
  selectedJudge1.classification,
77
  selectedJudge2.name,
78
  selectedJudge2.classification,
79
+ selectedTheme,
80
+ selectedModel
81
  );
82
  setMatrix(result);
83
  setSelectedItems([]);
 
90
  };
91
 
92
  fetchData();
93
+ }, [selectedTheme, selectedJudge1, selectedJudge2, selectedModel]);
94
 
95
  const handleJudge1NameChange = (newName: string) => {
96
  const newJudge = judges.find(j => j.name === newName);
 
137
  selectedJudge2.name,
138
  selectedJudge2.classification,
139
  toCategory,
140
+ selectedTheme,
141
+ selectedModel
142
  )
143
  setSelectedItems(items);
144
  setSelectedCategory([fromCategory, toCategory]);
 
170
  <FilterBar
171
  themes={themes}
172
  judges={judges}
173
+ models={models}
174
  selectedTheme={selectedTheme}
175
  onThemeChange={setSelectedTheme}
176
+ selectedModel={selectedModel}
177
+ onModelChange={setSelectedModel}
178
  selectedJudge1={selectedJudge1}
179
  selectedJudge2={selectedJudge2}
180
  onJudge1NameChange={handleJudge1NameChange}
src/components/Filterbar.tsx CHANGED
@@ -1,12 +1,15 @@
1
- import type { FilterBarProps, Judges } from '../types.js';
2
 
3
  const findJudgeByName = (judges: Judges[], name: string) => judges.find(j => j.name === name);
4
 
5
  const FilterBar: React.FC<FilterBarProps> = ({
6
  themes,
7
  judges,
 
8
  selectedTheme,
9
  onThemeChange,
 
 
10
  selectedJudge1,
11
  selectedJudge2,
12
  onJudge1NameChange,
@@ -36,6 +39,22 @@ const FilterBar: React.FC<FilterBarProps> = ({
36
  ))}
37
  </select>
38
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  </div>
40
 
41
  <div className="filter-group">
 
1
+ import type { FilterBarProps, Judges, Model } from '../types.js';
2
 
3
  const findJudgeByName = (judges: Judges[], name: string) => judges.find(j => j.name === name);
4
 
5
  const FilterBar: React.FC<FilterBarProps> = ({
6
  themes,
7
  judges,
8
+ models,
9
  selectedTheme,
10
  onThemeChange,
11
+ selectedModel,
12
+ onModelChange,
13
  selectedJudge1,
14
  selectedJudge2,
15
  onJudge1NameChange,
 
39
  ))}
40
  </select>
41
  </div>
42
+ <div className="filter-block">
43
+ <label className="filter-label" htmlFor='model-select'>Model</label>
44
+ <select
45
+ className="filter-select"
46
+ id='model-select'
47
+ value={selectedModel}
48
+ onChange={(e) => onModelChange(e.target.value)}
49
+ >
50
+ <option value="">All Models</option>
51
+ {models.map((model) => (
52
+ <option key={model.model} value={model.model}>
53
+ {model.model}
54
+ </option>
55
+ ))}
56
+ </select>
57
+ </div>
58
  </div>
59
 
60
  <div className="filter-group">
src/components/itemList.tsx CHANGED
@@ -116,11 +116,11 @@ const PaginationControls: React.FC<PaginationProps> = (
116
  }) => {
117
  return (
118
  <div className="pagination-controls">
119
- <button className="assessment-btn" onClick={() => setCurrentPage(1)} disabled={currentPage === 1}>
120
- &laquo; First
121
  </button>
122
- <button className="assessment-btn" onClick={() => setCurrentPage(prev => prev - 1)} disabled={currentPage === 1}>
123
- ‹ Prev
124
  </button>
125
  <div className="page-jump">
126
  <p>Page</p>
@@ -133,11 +133,11 @@ const PaginationControls: React.FC<PaginationProps> = (
133
  />
134
  <p>of {totalPages}</p>
135
  </div>
136
- <button className="assessment-btn" onClick={() => setCurrentPage(prev => prev + 1 )} disabled={currentPage === totalPages}>
137
- Next ›
138
  </button>
139
- <button className="assessment-btn" onClick={() => setCurrentPage(totalPages)} disabled={currentPage === totalPages}>
140
- Last &raquo;
141
  </button>
142
  </div>
143
  );
 
116
  }) => {
117
  return (
118
  <div className="pagination-controls">
119
+ <button className="assessment-btn pagination-btn" onClick={() => setCurrentPage(1)} disabled={currentPage === 1}>
120
+ &laquo; <span>First</span>
121
  </button>
122
+ <button className="assessment-btn pagination-btn" onClick={() => setCurrentPage(prev => prev - 1)} disabled={currentPage === 1}>
123
+ <span>Prev</span>
124
  </button>
125
  <div className="page-jump">
126
  <p>Page</p>
 
133
  />
134
  <p>of {totalPages}</p>
135
  </div>
136
+ <button className="assessment-btn pagination-btn" onClick={() => setCurrentPage(prev => prev + 1 )} disabled={currentPage === totalPages}>
137
+ <span>Next</span>
138
  </button>
139
+ <button className="assessment-btn pagination-btn" onClick={() => setCurrentPage(totalPages)} disabled={currentPage === totalPages}>
140
+ <span>Last</span> &raquo;
141
  </button>
142
  </div>
143
  );
src/index.css CHANGED
@@ -968,6 +968,27 @@ h4 {
968
  }
969
  }
970
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
971
  /* Animation for loading states */
972
  @keyframes pulse {
973
  0%, 100% {
 
968
  }
969
  }
970
 
971
+ @media (max-width: 480px) {
972
+ .item-question-header {
973
+ flex-direction: column;
974
+ align-items: flex-start;
975
+ gap: 0.5rem;
976
+ }
977
+
978
+ .metadata strong {
979
+ display: none;
980
+ }
981
+
982
+ .pagination-btn span {
983
+ display: none;
984
+ }
985
+
986
+ .sankey-container, .heatmap-container {
987
+ padding: 1rem;
988
+ min-width: 330px;
989
+ }
990
+ }
991
+
992
  /* Animation for loading states */
993
  @keyframes pulse {
994
  0%, 100% {
src/lib/ingest.ts CHANGED
@@ -11,6 +11,8 @@ export const DATA_SOURCES = {
11
  questions: 'https://huggingface.co/datasets/PITTI/speechmap-questions/resolve/main/consolidated_questions.parquet',
12
  responses: 'https://huggingface.co/datasets/PITTI/speechmap-responses-v2/resolve/main/consolidated_responses.parquet',
13
  assessments: 'https://huggingface.co/datasets/PITTI/speechmap-assessments-v2/resolve/main/consolidated_assessments.parquet',
 
 
14
  };
15
 
16
 
@@ -79,6 +81,37 @@ async function rebuildDatabase() {
79
  SELECT uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin FROM read_parquet('${DATA_SOURCES.assessments}');
80
  `);
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  console.log('✅ Data ingestion complete!');
83
  } catch (error) {
84
  console.error('An error occurred during the rebuild:', error);
 
11
  questions: 'https://huggingface.co/datasets/PITTI/speechmap-questions/resolve/main/consolidated_questions.parquet',
12
  responses: 'https://huggingface.co/datasets/PITTI/speechmap-responses-v2/resolve/main/consolidated_responses.parquet',
13
  assessments: 'https://huggingface.co/datasets/PITTI/speechmap-assessments-v2/resolve/main/consolidated_assessments.parquet',
14
+ // manual : './data/manual_assessments.parquet',// Local file for manual assessments
15
+ // reviewed : './data/reviewed_assessments.parquet' // Local file for reviewed assessments
16
  };
17
 
18
 
 
81
  SELECT uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin FROM read_parquet('${DATA_SOURCES.assessments}');
82
  `);
83
 
84
+ // console.log('Updating manual assessments from local parquet file...');
85
+ // const manualDataPath = path.resolve(ROOT_DIR, DATA_SOURCES.manual);
86
+ // if (fs.existsSync(manualDataPath)) {
87
+ // await query(db, `
88
+ // INSERT INTO assessments (uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin)
89
+ // SELECT uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin FROM read_parquet('${manualDataPath}')
90
+ // ON CONFLICT (uuid) DO UPDATE SET
91
+ // q_uuid = excluded.q_uuid,
92
+ // r_uuid = excluded.r_uuid,
93
+ // judge = excluded.judge,
94
+ // judge_type = excluded.judge_type,
95
+ // judge_analysis = excluded.judge_analysis,
96
+ // compliance = excluded.compliance,
97
+ // pitti_compliance = excluded.pitti_compliance,
98
+ // origin = excluded.origin;
99
+ // `);
100
+ // } else {
101
+ // console.warn(`Manual assessments file not found at ${manualDataPath}, skipping...`);
102
+ // }
103
+
104
+ // console.log('Updating reviewed assessments from local parquet file...');
105
+ // const reviewedDataPath = path.resolve(ROOT_DIR, DATA_SOURCES.reviewed);
106
+ // if (fs.existsSync(reviewedDataPath)) {
107
+ // await query(db, `
108
+ // INSERT INTO assessments (uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin)
109
+ // SELECT CAST(uuid AS VARCHAR) || 'n' AS uuid, q_uuid, r_uuid, judge, judge_type, judge_analysis, compliance, pitti_compliance, origin FROM read_parquet('${reviewedDataPath}')
110
+ // `);
111
+ // } else {
112
+ // console.warn(`Reviewed assessments file not found at ${reviewedDataPath}, skipping...`);
113
+ // }
114
+
115
  console.log('✅ Data ingestion complete!');
116
  } catch (error) {
117
  console.error('An error occurred during the rebuild:', error);
src/types.ts CHANGED
@@ -36,6 +36,10 @@ export interface Theme {
36
  name: string; // Human-readable name for the theme
37
  }
38
 
 
 
 
 
39
  export interface Judges {
40
  name: string;
41
  judge_type: string; // human or LLM
@@ -76,8 +80,11 @@ export interface AssessmentItem {
76
  export interface FilterBarProps {
77
  themes: Theme[];
78
  judges: Judges[];
 
79
  selectedTheme: string;
80
  onThemeChange: (value: string) => void;
 
 
81
  selectedJudge1: SelectedJudge | null;
82
  selectedJudge2: SelectedJudge | null;
83
  onJudge1NameChange : (value: string) => void;
 
36
  name: string; // Human-readable name for the theme
37
  }
38
 
39
+ export interface Model {
40
+ model:string;
41
+ }
42
+
43
  export interface Judges {
44
  name: string;
45
  judge_type: string; // human or LLM
 
80
  export interface FilterBarProps {
81
  themes: Theme[];
82
  judges: Judges[];
83
+ models: Model[];
84
  selectedTheme: string;
85
  onThemeChange: (value: string) => void;
86
+ selectedModel: string;
87
+ onModelChange: (value: string) => void;
88
  selectedJudge1: SelectedJudge | null;
89
  selectedJudge2: SelectedJudge | null;
90
  onJudge1NameChange : (value: string) => void;
src/utils/apiUtils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Theme, Judges, TransitionMatrix, AssessmentItem, ApiError } from '../types.js';
2
 
3
 
4
 
@@ -27,13 +27,18 @@ export const getJudges = async (): Promise<Judges[]> => {
27
  return fetchAPI<Judges[]>('/api/judges')
28
  };
29
 
 
 
 
 
30
 
31
  export const getReclassificationData = (
32
  judge1: string,
33
  judge1Classification: string,
34
  judge2: string,
35
  judge2Classification: string,
36
- theme?: string
 
37
  ): Promise<TransitionMatrix> => {
38
  // Build the query string from the parameters
39
  const params = new URLSearchParams({
@@ -47,6 +52,9 @@ export const getReclassificationData = (
47
  if (theme) {
48
  params.append('theme', theme);
49
  }
 
 
 
50
 
51
  return fetchAPI<TransitionMatrix>(`/api/reclassification?${params.toString()}`);
52
  };
@@ -59,7 +67,8 @@ export const getAssessmentItems = (
59
  judge2: string,
60
  judge2Classification: string,
61
  toCategory: string,
62
- theme?: string
 
63
  ): Promise<any[]> => {
64
 
65
  const params = new URLSearchParams({
@@ -74,6 +83,9 @@ export const getAssessmentItems = (
74
  if (theme) {
75
  params.append('theme', theme);
76
  }
 
 
 
77
 
78
  return fetchAPI<AssessmentItem[]>(`/api/mismatches?${params.toString()}`);
79
  }
 
1
+ import type { Theme, Judges, Model, TransitionMatrix, AssessmentItem, ApiError } from '../types.js';
2
 
3
 
4
 
 
27
  return fetchAPI<Judges[]>('/api/judges')
28
  };
29
 
30
+ export const getModels = async (): Promise<Model[]> => {
31
+ return fetchAPI<Model[]>('/api/models')
32
+ }
33
+
34
 
35
  export const getReclassificationData = (
36
  judge1: string,
37
  judge1Classification: string,
38
  judge2: string,
39
  judge2Classification: string,
40
+ theme?: string,
41
+ model?: string
42
  ): Promise<TransitionMatrix> => {
43
  // Build the query string from the parameters
44
  const params = new URLSearchParams({
 
52
  if (theme) {
53
  params.append('theme', theme);
54
  }
55
+ if (model) {
56
+ params.append('model', model);
57
+ }
58
 
59
  return fetchAPI<TransitionMatrix>(`/api/reclassification?${params.toString()}`);
60
  };
 
67
  judge2: string,
68
  judge2Classification: string,
69
  toCategory: string,
70
+ theme?: string,
71
+ model?: string
72
  ): Promise<any[]> => {
73
 
74
  const params = new URLSearchParams({
 
83
  if (theme) {
84
  params.append('theme', theme);
85
  }
86
+ if (model) {
87
+ params.append('model', model);
88
+ }
89
 
90
  return fetchAPI<AssessmentItem[]>(`/api/mismatches?${params.toString()}`);
91
  }