File size: 18,533 Bytes
20e666e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
// components/MetricsBreakdown.jsx

"use client";

import React, { useState, useEffect, useMemo } from "react";
import {
  RadarChart,
  PolarGrid,
  PolarAngleAxis,
  PolarRadiusAxis,
  Radar,
  Tooltip as RechartsTooltip, // Renamed to avoid conflict with local Tooltip
  Legend,
  ResponsiveContainer,
} from "recharts";
import { getScoreColor, getMetricTooltip } from "../lib/utils";
import { Tooltip } from "./Tooltip"; // Your custom Tooltip component for headers etc.

// Component receives processed metrics data, model metadata, and category radar data
const MetricsBreakdown = ({
  metricsData,
  modelsMeta,
  radarData: categoryRadarDataProp, // Already processed radar data for categories
}) => {
  const [subTab, setSubTab] = useState("categories"); // 'categories' or 'metrics'
  const [selectedModels, setSelectedModels] = useState([]);

  // console.log("Metrics Data in Breakdown:", metricsData); // For debugging
  // console.log("Models Meta in Breakdown:", modelsMeta);
  // console.log("Category Radar Data Prop:", categoryRadarDataProp);

  // Extract data from props with defaults
  const { highLevelCategories, lowLevelMetrics } = metricsData || {
    highLevelCategories: {},
    lowLevelMetrics: {},
  };
  // Use modelsMeta directly for clarity, aliasing if preferred
  const models = modelsMeta || [];

  // Get sorted lists of category and metric names
  const sortedCategoryNames = useMemo(
    () =>
      Object.keys(highLevelCategories || {}).sort((a, b) => a.localeCompare(b)),
    [highLevelCategories]
  );
  const sortedMetricNames = useMemo(
    () => Object.keys(lowLevelMetrics || {}).sort((a, b) => a.localeCompare(b)),
    [lowLevelMetrics]
  );

  // Initialize selections
  useEffect(() => {
    if (selectedModels.length === 0 && models.length > 0) {
      setSelectedModels(models.map((m) => m.model));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [models]); // Only depends on models changing/loading

  // --- Memoized data generation functions ---

  // Radar data for LL Metrics (used when subTab === 'metrics') - CORRECTED ACCESSORS
  const metricRadarData = useMemo(() => {
    if (
      !lowLevelMetrics ||
      models.length === 0 ||
      sortedMetricNames.length === 0
    )
      return [];
    return sortedMetricNames.map((metricName) => {
      const entry = { category: metricName }; // Use metric name as the axis category
      const metricData = lowLevelMetrics[metricName];
      if (metricData) {
        models
          .filter((m) => selectedModels.includes(m.model))
          .forEach((model) => {
            // Use correct camelCase keys
            entry[model.model] =
              Number(metricData.modelScores?.[model.model]?.nationalScore) || 0;
            // Standard deviation per metric is NOT available, so we don't add it here
          });
      }
      return entry;
    });
  }, [lowLevelMetrics, models, selectedModels, sortedMetricNames]);

  // Custom tooltip (common for both radar charts) - CORRECTED (removed std dev logic)
  const CustomRadarTooltip = ({ active, payload, label }) => {
    if (active && payload && payload.length) {
      return (
        <div className="bg-white p-3 border rounded shadow-lg max-w-xs opacity-95">
          <p className="font-medium mb-1 text-gray-800">{label}</p>
          {/* Get tooltip description for the category/metric itself */}
          <p className="text-xs mb-3 text-gray-600 border-b pb-2">
            {getMetricTooltip(label)}
          </p>
          <div className="space-y-1">
            {payload
              // Sort models by score within tooltip
              .sort((a, b) => (b.value || 0) - (a.value || 0))
              .map((entry) => (
                <div
                  key={entry.dataKey} // dataKey is the model name here
                  className="flex items-center text-sm"
                >
                  <div
                    className="w-2.5 h-2.5 rounded-full mr-2 flex-shrink-0"
                    style={{ backgroundColor: entry.color || "#8884d8" }}
                  ></div>
                  <span className="mr-1 truncate flex-grow text-gray-700">
                    {entry.name}: {/* name is also the model name */}
                  </span>
                  <span className="font-medium flex-shrink-0 text-gray-900">
                    {/* Ensure value exists and format */}
                    {entry.value !== null && entry.value !== undefined
                      ? Number(entry.value).toFixed(1)
                      : "N/A"}
                    {/* Removed standard deviation display */}
                  </span>
                </div>
              ))}
          </div>
        </div>
      );
    }
    return null;
  };

  // Use the radar data passed via prop for categories view, filtered by selected models - CORRECTED (removed std dev logic)
  const filteredCategoryRadarData = useMemo(() => {
    if (!categoryRadarDataProp || models.length === 0) return [];
    // Filter based on selected models, removing std dev keys
    return categoryRadarDataProp.map((item) => {
      const newItem = { category: item.category };
      models
        .filter((m) => selectedModels.includes(m.model))
        .forEach((model) => {
          // We only need the model score itself for the radar data
          newItem[model.model] = item[model.model] ?? 0; // Use nullish coalescing for default
        });
      return newItem;
    });
  }, [categoryRadarDataProp, models, selectedModels]);

  return (
    <>
      {/* Top Controls: Model Selector & Sub-Tab Pills (No changes needed) */}
      <div className="mb-6 flex flex-col md:flex-row justify-between items-center gap-4">
        {/* Sub-Tab Pills */}
        <div className="flex space-x-1 p-1 bg-gray-200 rounded-lg">
          {" "}
          <button
            aria-pressed={subTab === "categories"}
            className={`px-4 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 ${
              subTab === "categories"
                ? "bg-white shadow text-blue-600"
                : "text-gray-600 hover:text-gray-800"
            }`}
            onClick={() => setSubTab("categories")}
          >
            {" "}
            High-Level Categories{" "}
          </button>{" "}
          <button
            aria-pressed={subTab === "metrics"}
            className={`px-4 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 ${
              subTab === "metrics"
                ? "bg-white shadow text-blue-600"
                : "text-gray-600 hover:text-gray-800"
            }`}
            onClick={() => setSubTab("metrics")}
          >
            {" "}
            Low-Level Metrics{" "}
          </button>{" "}
        </div>
        {/* Model Selector */}
        <div className="flex items-center flex-wrap gap-1">
          {" "}
          <span className="text-sm text-gray-500 mr-2">Models:</span>{" "}
          {models?.map((model) => (
            <button
              key={model.model}
              className={`px-2 py-0.5 text-xs rounded border ${
                selectedModels.includes(model.model)
                  ? "bg-sky-100 text-sky-800 border-sky-300 font-medium"
                  : "bg-gray-100 text-gray-600 border-gray-300 hover:bg-gray-200"
              }`}
              onClick={() => {
                if (selectedModels.includes(model.model)) {
                  if (selectedModels.length > 1) {
                    setSelectedModels(
                      selectedModels.filter((m) => m !== model.model)
                    );
                  }
                } else {
                  setSelectedModels([...selectedModels, model.model]);
                }
              }}
            >
              {" "}
              {model.model}{" "}
            </button>
          ))}{" "}
        </div>
      </div>

      {/* Conditional content based on sub-tab */}
      {subTab === "categories" && (
        <div className="space-y-6">
          {/* CATEGORIES VIEW */}
          {/* Summary Table: Models as Rows, Categories as Columns - CORRECTED ACCESSORS */}
          <div className="border rounded-lg overflow-hidden shadow-sm">
            <div className="px-4 py-3 bg-gray-50 border-b">
              <h3 className="font-semibold text-gray-800">
                Category Performance Summary
              </h3>
            </div>
            <div className="p-4 overflow-x-auto">
              {sortedCategoryNames.length > 0 ? (
                <table className="min-w-full divide-y divide-gray-200 border border-gray-200">
                  <thead>
                    <tr className="bg-gray-100">
                      <th
                        scope="col"
                        className="sticky left-0 bg-gray-100 px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider z-10"
                      >
                        Model
                      </th>
                      {sortedCategoryNames.map((catName) => (
                        <th
                          key={catName}
                          scope="col"
                          className="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider whitespace-nowrap"
                        >
                          {catName}
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody className="bg-white divide-y divide-gray-200">
                    {models
                      ?.filter((m) => selectedModels.includes(m.model))
                      .map((model, idx) => (
                        <tr
                          key={model.model}
                          className={
                            idx % 2 === 0
                              ? "bg-white hover:bg-gray-50"
                              : "bg-gray-50 hover:bg-gray-100"
                          }
                        >
                          <td className="sticky left-0 bg-inherit px-3 py-2 whitespace-nowrap z-10 text-left">
                            {" "}
                            {/* Keep sticky styles */}
                            <div className="flex items-center">
                              <div
                                className="w-3 h-3 rounded-full mr-2 shrink-0"
                                style={{ backgroundColor: model.color }}
                              ></div>
                              <span className="text-sm font-medium">
                                {model.model}
                              </span>
                            </div>
                          </td>
                          {sortedCategoryNames.map((catName) => {
                            // Use correct camelCase keys
                            const scoreData =
                              highLevelCategories[catName]?.modelScores?.[
                                model.model
                              ];
                            const score = scoreData?.nationalScore; // Access camelCase key
                            const displayScore =
                              score !== null && score !== undefined
                                ? Number(score).toFixed(1)
                                : "N/A";
                            return (
                              <td
                                key={catName}
                                className="px-3 py-2 whitespace-nowrap text-center"
                              >
                                <div
                                  className={`text-sm ${
                                    displayScore === "N/A"
                                      ? "text-gray-400"
                                      : getScoreColor(score)
                                  }`}
                                >
                                  {displayScore}
                                </div>
                              </td>
                            );
                          })}
                        </tr>
                      ))}
                  </tbody>
                </table>
              ) : (
                <p className="text-center text-gray-500 py-4">
                  No category data available.
                </p>
              )}
            </div>
          </div>

          {/* Radar Chart for Categories (Uses filteredCategoryRadarData) */}
          <div className="border rounded-lg overflow-hidden shadow-sm">
            <div className="px-4 py-3 bg-gray-50 border-b flex justify-between items-center">
              <h3 className="font-semibold text-gray-800">
                Performance Across Categories
              </h3>
              <div className="text-xs text-gray-500">
                National Average Scores
              </div>
            </div>
            <div className="p-4">
              {filteredCategoryRadarData &&
              filteredCategoryRadarData.length > 0 ? (
                <div className="h-96 md:h-[450px]">
                  <ResponsiveContainer width="100%" height="100%">
                    <RadarChart
                      outerRadius="80%"
                      data={filteredCategoryRadarData}
                    >
                      <PolarGrid gridType="polygon" stroke="#e5e7eb" />
                      <PolarAngleAxis
                        dataKey="category"
                        tick={{ fill: "#4b5563", fontSize: 12 }}
                      />
                      <PolarRadiusAxis
                        angle={90}
                        domain={[0, 100]}
                        axisLine={false}
                        tick={{ fill: "#6b7280", fontSize: 10 }}
                      />
                      {models
                        ?.filter((m) => selectedModels.includes(m.model))
                        .map((model) => (
                          <Radar
                            key={model.model}
                            name={model.model}
                            dataKey={model.model}
                            stroke={model.color}
                            fill={model.color}
                            fillOpacity={0.1}
                            strokeWidth={2}
                          />
                        ))}
                      {/* Use the corrected CustomRadarTooltip */}
                      <RechartsTooltip content={<CustomRadarTooltip />} />
                      <Legend
                        iconSize={10}
                        wrapperStyle={{ fontSize: "12px", paddingTop: "20px" }}
                      />
                    </RadarChart>
                  </ResponsiveContainer>
                </div>
              ) : (
                <p className="text-center text-gray-500 py-4">
                  Radar data not available.
                </p>
              )}
              <p className="text-xs text-gray-500 mt-4">
                This radar chart visualizes how each model performs across
                different high-level evaluation categories. The further out on
                each axis, the better the performance on that category.
              </p>
            </div>
          </div>
        </div>
      )}

      {subTab === "metrics" && (
        <div className="space-y-6">
          {/* METRICS VIEW */}
          {/* Radar Chart for Metrics (Uses metricRadarData) */}
          <div className="border rounded-lg overflow-hidden shadow-sm">
            <div className="px-4 py-3 bg-gray-50 border-b flex justify-between items-center">
              <h3 className="font-semibold text-gray-800">
                Performance Across All Metrics
              </h3>
              <div className="text-xs text-gray-500">
                National Average Scores
              </div>
            </div>
            <div className="p-4">
              {metricRadarData.length > 0 ? (
                <div className="h-96 md:h-[600px]">
                  {" "}
                  {/* Increased height */}
                  <ResponsiveContainer width="100%" height="100%">
                    <RadarChart outerRadius="80%" data={metricRadarData}>
                      {" "}
                      {/* Use metricRadarData */}
                      <PolarGrid gridType="polygon" stroke="#e5e7eb" />
                      <PolarAngleAxis
                        dataKey="category"
                        tick={{ fill: "#4b5563", fontSize: 10 }}
                      />{" "}
                      {/* Adjusted font size */}
                      <PolarRadiusAxis
                        angle={90}
                        domain={[0, 100]}
                        axisLine={false}
                        tick={{ fill: "#6b7280", fontSize: 10 }}
                      />
                      {models
                        ?.filter((m) => selectedModels.includes(m.model))
                        .map((model) => (
                          <Radar
                            key={model.model}
                            name={model.model}
                            dataKey={model.model}
                            stroke={model.color}
                            fill={model.color}
                            fillOpacity={0.1}
                            strokeWidth={2}
                          />
                        ))}
                      {/* Use the corrected CustomRadarTooltip */}
                      <RechartsTooltip content={<CustomRadarTooltip />} />
                      <Legend
                        iconSize={10}
                        wrapperStyle={{ fontSize: "12px", paddingTop: "20px" }}
                      />
                    </RadarChart>
                  </ResponsiveContainer>
                </div>
              ) : (
                <p className="text-center text-gray-500 py-4">
                  Metric data not available for radar chart.
                </p>
              )}
              <p className="text-xs text-gray-500 mt-4">
                This radar chart visualizes how each model performs across
                different low-level metrics. The further out on each axis, the
                better the performance on that metric.
              </p>
            </div>
          </div>
          {/* Optional: Add a table summary for low-level metrics similar to the categories one if desired */}
        </div>
      )}
    </>
  );
};

export default MetricsBreakdown;