File size: 79,470 Bytes
bc82423
 
 
 
 
 
 
 
 
5953820
 
 
 
 
 
 
 
 
 
bc82423
 
 
 
 
7d17f4c
 
96c9169
d2098a1
 
e73429a
 
7501d78
cffc66d
670d70e
bc82423
8c0da88
0eeec33
bc82423
e50e989
e3ed8af
bc82423
 
 
e4a5950
 
0f0fa71
7d17f4c
17b2e11
 
 
 
cfdf65c
64663f2
f72a41f
7cbd168
e50e989
7cbd168
 
 
 
e50e989
7cbd168
 
 
 
 
 
 
 
 
 
7d17f4c
 
6695bfb
7d17f4c
 
17b2e11
7d17f4c
 
 
 
ddd40be
 
 
 
 
 
 
cfdf65c
 
 
 
 
 
1761dae
 
 
 
 
 
ae05d04
 
 
 
10e881d
5953820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10e881d
ba50cf6
 
 
 
 
 
 
bc82423
 
 
 
 
 
 
 
 
 
5d7e158
 
 
 
64663f2
 
 
 
5d7e158
 
a221f67
 
 
5953820
a221f67
bc82423
 
 
 
 
 
3ee7e9d
 
 
 
169c267
 
 
 
a74e8c6
3ee7e9d
64663f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc82423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96c9169
 
 
 
 
25ff23e
96c9169
 
5d7e158
88d7d63
 
 
7876ce0
 
88d7d63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2098a1
0c2c1b6
d2098a1
 
 
 
 
 
 
 
9f5cbd2
 
 
 
 
0c2c1b6
d2098a1
0c2c1b6
d2098a1
 
 
 
 
 
 
 
 
 
 
 
b2cf119
 
 
 
d2098a1
 
 
 
 
 
 
b2cf119
d2098a1
 
58df3e7
 
 
 
 
 
 
 
 
 
 
7bcac0a
 
 
 
 
 
 
88d7d63
 
11063f4
0c2c1b6
11063f4
88d7d63
 
b2cf119
88d7d63
 
 
0c2c1b6
88d7d63
 
 
 
f5ba578
88d7d63
96c9169
7d17f4c
d2098a1
7d17f4c
 
d2098a1
fb8b04c
 
 
bc82423
 
 
 
cffc66d
 
 
 
 
 
 
 
 
 
 
 
bc82423
722e15e
 
bc82423
 
722e15e
bc82423
 
 
 
 
 
 
 
 
 
 
 
 
7d17f4c
 
 
bc82423
 
 
 
 
 
 
 
 
cffc66d
bc82423
 
f032aa0
 
 
 
bc82423
f032aa0
8c0da88
0f0fa71
 
c635c46
f72a41f
f032aa0
 
bc82423
0f0fa71
f72a41f
0f0fa71
 
 
 
f032aa0
 
cffc66d
bc82423
 
 
e73429a
bc82423
 
 
 
ddd40be
bc82423
 
 
e73429a
 
 
96c9169
 
 
 
 
 
 
 
 
 
 
 
 
d2098a1
 
 
 
 
 
 
 
 
 
0c2c1b6
d2098a1
 
7d17f4c
 
 
 
e44edf8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2098a1
 
 
 
 
 
 
 
 
 
5953820
d2098a1
 
 
 
 
 
 
 
 
e44edf8
d2098a1
 
 
 
 
 
 
e44edf8
 
d2098a1
 
ae05d04
 
e44edf8
 
d2098a1
e44edf8
 
 
 
d2098a1
 
 
 
 
 
 
bc82423
7d17f4c
96c9169
d2098a1
bc82423
e73429a
670d70e
7d17f4c
5953820
 
 
 
eb37cb4
96c9169
eb37cb4
670d70e
e73429a
670d70e
 
eb37cb4
96c9169
eb37cb4
670d70e
e73429a
7d17f4c
96c9169
d2098a1
bc82423
e73429a
96c9169
7d17f4c
 
 
 
 
 
 
 
eb37cb4
 
 
 
 
 
 
 
7d17f4c
96c9169
 
 
 
 
cffc66d
 
96c9169
cffc66d
96c9169
 
cffc66d
 
 
 
5953820
96c9169
 
 
cffc66d
 
 
 
 
96c9169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12701b9
96c9169
 
 
 
 
 
 
 
 
 
 
 
5953820
96c9169
 
 
 
 
 
d2098a1
 
 
 
9f5cbd2
d2098a1
 
e44edf8
 
d2098a1
 
 
 
b2cf119
d2098a1
 
0c2c1b6
d2098a1
eb37cb4
 
 
 
 
 
 
 
 
d2098a1
e73429a
bc82423
 
fa4d87c
 
cffc66d
 
bc82423
cffc66d
fa4d87c
 
cffc66d
 
 
 
5953820
fa4d87c
 
 
cffc66d
 
 
 
 
622612d
fa4d87c
 
 
 
 
 
 
bc82423
e73429a
bc82423
 
 
e73429a
12701b9
0743ab4
bc82423
e73429a
 
bc82423
 
 
 
 
 
 
e73429a
5953820
bc82423
e73429a
 
 
 
 
 
 
 
 
 
12701b9
e73429a
 
 
 
 
 
 
 
 
5953820
e73429a
 
bc82423
 
 
 
7501d78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc82423
 
 
 
 
 
fb8b04c
bc82423
 
 
 
fb8b04c
f8a28ba
fb8b04c
7d17f4c
ff5e7c3
d2098a1
fb8b04c
670d70e
5953820
670d70e
7cbd168
 
 
 
 
 
64663f2
 
 
 
 
 
7cbd168
64663f2
 
2e24dfe
 
 
64663f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4306e97
64663f2
 
7cbd168
64663f2
eed3131
 
 
 
 
 
 
 
 
4306e97
64663f2
eed3131
d3a61f1
008b876
7cbd168
64663f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a134994
64663f2
a134994
 
 
eed3131
 
a134994
 
 
 
ea56b8f
 
a134994
 
 
 
 
 
64663f2
5d7e158
 
 
 
 
 
 
cdbdf97
 
5d7e158
 
cdbdf97
 
 
 
 
 
53cc7a5
 
29701c4
5d7e158
 
 
 
 
 
 
 
 
 
 
cdbdf97
5d7e158
 
 
cdbdf97
5d7e158
 
 
 
 
cdbdf97
5d7e158
7cbd168
5d7e158
 
 
 
 
 
 
 
42da361
5d7e158
7cbd168
 
 
 
74e75b5
 
 
 
 
5d7e158
 
cdbdf97
5d7e158
7cbd168
cdbdf97
5d7e158
7cbd168
5d7e158
 
 
cdbdf97
 
5d7e158
 
 
 
53cc7a5
 
29701c4
 
7cbd168
5d7e158
 
 
 
 
 
cdbdf97
5d7e158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7cbd168
5d7e158
 
 
bc82423
 
0eeec33
8c0da88
622612d
cffc66d
622612d
 
cffc66d
622612d
8c0da88
0eeec33
8476890
 
5953820
8476890
8c0da88
 
 
 
0eeec33
c635c46
f72a41f
25ff23e
 
 
 
 
8c0da88
 
 
25ff23e
 
f032aa0
8c0da88
cffc66d
cfdf65c
f032aa0
bc82423
e4a5950
bc82423
1761dae
cfdf65c
f72a41f
25ff23e
 
 
f032aa0
bc82423
 
 
cfdf65c
bc82423
 
 
 
 
 
 
 
 
 
e50e989
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc82423
e103846
670d70e
 
 
 
d7a1c5a
 
 
 
e50e989
 
 
 
 
f72a41f
d7a1c5a
e50e989
0eeec33
e50e989
bc82423
 
e50e989
d7a1c5a
7d17f4c
d7a1c5a
 
5d7e158
d7a1c5a
 
 
 
 
 
 
5d7e158
d7a1c5a
 
e50e989
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7a1c5a
 
 
 
1761dae
cfdf65c
f72a41f
d7a1c5a
 
772723d
5953820
772723d
d7a1c5a
 
 
772723d
 
 
e50e989
772723d
 
3508555
 
 
 
 
 
 
772723d
 
 
 
 
 
3508555
9f5cbd2
3508555
 
 
 
 
 
 
 
 
 
ddd40be
772723d
5953820
772723d
 
bc82423
fb8b04c
bc82423
7d17f4c
bc82423
e3ed8af
bc82423
 
 
 
 
 
7d17f4c
bc82423
 
 
 
670d70e
5953820
670d70e
 
e3ed8af
 
5953820
e3ed8af
 
7d17f4c
 
ddd40be
 
 
 
 
e3ed8af
7d17f4c
ddd40be
 
bc82423
 
7d17f4c
bc82423
 
 
 
 
 
 
5953820
ba50cf6
bc82423
 
ddd40be
 
 
bc82423
 
 
e3ed8af
bc82423
 
 
 
 
ba50cf6
 
 
 
 
 
 
 
 
 
 
 
5953820
ba50cf6
 
 
 
5953820
ba50cf6
 
 
 
 
5953820
 
 
 
 
 
ba50cf6
5953820
ba50cf6
 
 
5953820
 
 
 
 
 
ba50cf6
5953820
ba50cf6
10e881d
ba50cf6
5953820
ba50cf6
 
5953820
 
 
 
 
 
ba50cf6
5953820
ba50cf6
 
 
 
 
 
 
 
 
 
 
bc82423
670d70e
bc82423
ddd40be
7563072
bc82423
cffc66d
043eac0
 
 
5953820
043eac0
 
7563072
5953820
e4a5950
33766f6
043eac0
 
 
bc82423
 
670d70e
bc82423
ddd40be
7563072
 
043eac0
 
7563072
5953820
043eac0
 
5953820
e4a5950
33766f6
043eac0
 
 
 
 
 
 
 
7bcac0a
 
 
 
 
 
 
 
 
ddd40be
043eac0
 
58df3e7
9f5cbd2
043eac0
88d7d63
 
 
 
 
 
 
 
 
58df3e7
043eac0
5953820
33766f6
 
 
 
 
bc82423
 
 
 
33766f6
bc82423
 
 
33766f6
 
 
 
 
 
bc82423
33766f6
bc82423
 
 
 
fb8b04c
bc82423
5d7e158
bc82423
 
ba50cf6
bc82423
 
 
670d70e
7d17f4c
 
 
 
 
 
 
670d70e
a221f67
670d70e
33766f6
670d70e
8c0da88
 
f032aa0
 
0eeec33
 
 
 
e103846
 
 
 
670d70e
7501d78
670d70e
 
 
bc82423
6105dd2
 
 
 
 
 
 
 
 
 
 
 
 
bc82423
 
 
 
670d70e
bc82423
 
 
 
 
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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
const TIER_LABEL = {
	tier1: "Point · point accuracy",
	tier2: "Ranking · cross-sectional IC",
	tier3: "Decision · portfolio backtest"
};
const TIER_SHORT = { tier1: "Point", tier2: "Ranking", tier3: "Decision" };
const ASPECT = { tier1: "pointwise", tier2: "ic", tier3: "portfolio" };
const METRIC_BASE_ORDER = ["hr", "hrp", "hrpp", "mase", "mase_diff", "ic", "return", "sharpe", "mdd", "vol"];
const METRIC_INFO = {
	hr: ["Hit Rate", "Share of cases where the predicted direction matches the realized direction."],
	hrp: ["Hit Rate (Delta)", "Share of cases where both the level direction and first-difference direction are correct."],
	hrpp: ["Hit Rate (Delta2)", "Share of cases where the level, first-difference, and second-difference directions are all correct."],
	mase: ["MASE (relative)", "Normalized absolute error between predicted and realized percentage changes."],
	mase_diff: ["MASE (diff)", "Absolute forecast error normalized by the period-difference scale."],
	ic: ["Information Coefficient", "Cross-sectional rank correlation between predicted and realized percentage changes."],
	return: ["Return", "Annualized return of an equal-weight long portfolio built from top predicted assets."],
	sharpe: ["Sharpe", "Annualized Sharpe ratio of the strategy returns."],
	mdd: ["Max Drawdown", "Maximum peak-to-trough decline of the NAV path. Values closer to 0 are better."],
	vol: ["Volatility", "Annualized standard deviation of the strategy returns."]
};
const state = {
	data: null,
	records: [],
	models: [],
	modelConfigs: new Map(),
	excludedModelTypes: new Set(),
	excludedMetrics: new Set(),
	excludedTypes: new Set(),
	excludedTargets: new Set(),
	excludedDetails: new Set(),
	excludedFrequencies: new Set(),
	collapsedFilters: new Set(),
	collapsedSubcards: new Set(),
	recordsReady: false,
	view: "overview",
	showDetailRank: true,
	showModelInfo: true,
	sort: { key: "overall", asc: true },
	metricsSort: { key: "rank", asc: true },
	metric: { tier: "tier1", key: null, detail: "__all__", frequency: "__all__", sort: { key: "rank", asc: true } }
};

const $ = (id) => document.getElementById(id);
const isExaone = (model) => model === "exaone-forecast" || model.startsWith("exaone-");
const displayName = (model) => isExaone(model) ? "EXAONE Forecast Finance" : model;
const modelConfig = (model) => state.modelConfigs.get(model) || { model, model_type: "unknown", org: "Unknown" };
const modelType = (model) => modelConfig(model).model_type || "unknown";
const modelOrg = (model) => {
	const org = modelConfig(model).org || "";
	return org.toLowerCase() === "unknown" ? "" : org;
};
const modelLink = (model) => modelConfig(model).model_link || "";
const modelSize = (model) => modelConfig(model).model_size || "";
const modelSubmitTime = (model) => modelConfig(model).submit_time || "";
const orgColors = {
	Alibaba: "#0891b2",
	Amazon: "#2563eb",
	Datadog: "#16a34a",
	"Google Research": "#9333ea",
	"IBM Research": "#f97316",
	"LG AI Research": "#e11d48",
	PriorLabs: "#be185d",
	Salesforce: "#0f766e",
	ServiceNow: "#7c3aed",
	"Tsinghua University": "#a16207",
	Unspecified: "#64748b",
	"Zhejiang University": "#dc2626"
};
const fallbackOrgPalette = ["#1d4ed8", "#15803d", "#c026d3", "#0e7490", "#b45309", "#4338ca"];
const organizationName = (model) => modelOrg(model) || "Unspecified";
const organizationColor = (org) => orgColors[org] || fallbackOrgPalette[Math.abs(String(org).split("").reduce((sum, char) => sum + char.charCodeAt(0), 0)) % fallbackOrgPalette.length];
const modelTypeLabel = (type) => {
	const labels = {
		"zero-shot": "Pretrained",
		pretrained: "Pretrained",
		statistical: "Statistical",
		unknown: ""
	};
	return labels[type] || type;
};
const includedModels = () => state.models.filter((model) => !state.excludedModelTypes.has(modelType(model)));
const displayDetailLabel = (label) => {
	const map = {
		"Crypto Pair Close (Top 100)": "Crypto Pair Close",
		"OHLCV Close": "Close"
	};
	return map[label] || label;
};
const renderModelCell = (model) => {
	const link = modelLink(model);
	const label = displayName(model);
	if (!link) return `<td class="model-name">${label}</td>`;
	return `<td class="model-name model-link"><a href="${link}" target="_blank" rel="noopener noreferrer">${label}</a></td>`;
};
const renderModelTypeCell = (model) => {
	const type = modelType(model);
	const label = modelTypeLabel(type);
	if (!label) return '<td class="model-type-cell"></td>';
	return `<td class="model-type-cell"><span class="model-type-mark type-${type}" title="${label}" aria-label="${label}"></span></td>`;
};
const renderModelTypeFilterLabel = (type, label) => {
	if (!label) return "";
	return `<span class="model-type-mark type-${type}" aria-hidden="true"></span><span>${label}</span>`;
};
const DETAIL_CATEGORY_EXPLANATIONS = {
	"Bond Total Return Index": "A total return bond index that reflects both price changes and interest income. It evaluates the return path experienced by bond investors rather than the level of yields alone.",
	Breakeven: "A market-based inflation expectation measure derived from the spread between nominal government bond yields and inflation-linked bond yields.",
	"Corporate Yield": "The yield level required in corporate bond markets, reflecting both corporate funding costs and market pricing of credit risk.",
	"Credit Spread": "The spread between corporate or credit bond yields and safe asset yields. It tends to widen when default risk, liquidity stress, or recession risk rises.",
	"Forward Inflation": "A forward-looking market measure of expected inflation over a future period, useful for assessing shifts in the expected inflation path.",
	"Inflation Expectation": "Expected future inflation from consumers, experts, or markets. It is an important macro signal for monetary policy expectations and long-term rates.",
	"Money Market": "Short-term funding market indicators related to policy rates, liquidity, and financial institutions' funding conditions.",
	"Mortgage Rate": "Mortgage interest rate levels, which help explain housing affordability, real estate demand, and household financial conditions.",
	"Term Spread": "The difference between long-term and short-term interest rates, commonly used to assess growth expectations, policy expectations, and recession risk.",
	"TIPS Yield": "Real yields on U.S. Treasury Inflation-Protected Securities, used to evaluate real rate conditions and real discount rates.",
	"Treasury Yield": "U.S. Treasury yields, representing the term structure of risk-free rates and a benchmark for asset pricing and macro-financial conditions.",
	Activity: "Broad measures of economic activity such as production, consumption, transport, and coincident indicators, used to detect expansion and slowdown.",
	Banking: "Indicators of bank lending, deposits, reserves, and credit supply, important for evaluating financial intermediation and banking system liquidity.",
	"Capacity Utilization by Industry": "Industry-level measures of how intensively production capacity is being used, useful for assessing spare capacity and supply-side pressure.",
	Credit: "Credit volumes and lending flows in the private or public sector, reflecting leverage, funding supply, and financial cycle dynamics.",
	"Current Account": "External transaction indicators covering goods, services, income, and transfers, used to assess external balance and medium-term currency pressure.",
	"Energy Activity": "Energy-sector activity such as production, consumption, inventories, and drilling, capturing supply-demand shifts in commodity markets and industry.",
	External: "Macro indicators related to exports, imports, external transactions, and the balance of payments, used for global demand and external vulnerability analysis.",
	"FDI Flows": "Foreign direct investment inflows and outflows, reflecting long-term capital movement and firms' overseas production and investment activity.",
	"Financial Flows": "Financial account, capital flow, and financial asset transaction indicators, used to assess global fund flows and risk appetite.",
	Fiscal: "Government spending, revenue, fiscal balance, and debt indicators, describing fiscal policy stance and public-sector funding needs.",
	Freight: "Freight and logistics activity indicators, which quickly reflect real demand, manufacturing activity, and supply-chain conditions.",
	GDP: "Gross domestic product and related growth indicators, the broadest measure of total production and the business cycle.",
	Housing: "Housing starts, permits, sales, inventories, and other housing market activity indicators, often rate-sensitive and forward-looking.",
	"Housing Price": "Housing price or rent indicators, important for assessing asset prices, housing affordability, and household wealth.",
	Income: "Household or personal income indicators, key variables for evaluating consumption capacity and labor income flows.",
	"Industrial Production by Industry": "Industry-level production or production index measures, used to evaluate sector-specific cyclical sensitivity and manufacturing activity.",
	Inflation: "Consumer prices, producer prices, and other price level indicators, directly tied to monetary policy, real income, and rate expectations.",
	"International Investment Position": "A country's stock of external financial assets and liabilities, used to assess net external position and external vulnerability.",
	Inventory: "Inventory levels and changes for firms or industries, used to assess demand slowdown, oversupply, and production adjustment risk.",
	Labor: "Employment, unemployment, wages, labor participation, and other labor market indicators that explain both growth and inflation pressure.",
	"Labor by Industry": "Industry-level employment and labor market indicators, used to evaluate labor demand by sector rather than at the aggregate level.",
	Money: "Money supply, liquidity, deposits, and related indicators, describing financial conditions and macro liquidity.",
	"Portfolio Flows": "Cross-border portfolio investment inflows and outflows in equities, bonds, and other financial assets.",
	Productivity: "Labor productivity or total factor productivity indicators, used to assess long-term growth potential and unit labor cost pressure.",
	"Regional Fed": "Manufacturing and services survey indicators from regional Federal Reserve banks, reflecting regional business conditions and sentiment.",
	Saving: "Household or national saving rate indicators, describing consumption propensity, fiscal capacity, and long-term capital accumulation.",
	Sentiment: "Consumer, business, or market participant sentiment and expectations, often useful as leading indicators.",
	Spending: "Personal consumption expenditures, retail sales, and other spending activity indicators, used to assess domestic demand and consumer momentum.",
	Stress: "Financial market or macroeconomic stress indicators, capturing market instability, liquidity strain, and downside risk.",
	"TIC Flows": "International capital flow indicators from U.S. Treasury International Capital data, showing foreign transactions and holdings of U.S. securities.",
	"Implied Volatility": "Forward-looking volatility inferred from option prices, reflecting uncertainty, risk aversion, and potential short-term shocks.",
	"US Equity Index": "Major U.S. equity index levels, used as broad benchmarks for stock market direction and risk asset performance.",
	"Equity OHLCV Close": "Close price series for listed equities, a core target for company-level price forecasting, cross-sectional ranking, and portfolio backtests.",
	"ETF OHLCV Close": "Close price series for exchange-traded funds, covering tradable sector, style, asset class, and thematic exposures.",
	"Balance Sheet": "Corporate balance sheet items such as assets, liabilities, and equity, used to assess financial health, leverage, and asset-base changes.",
	"Cash Flow": "Cash flow statement items such as operating cash flow and capital expenditure, showing actual cash generation and investment activity.",
	"EPS Basic": "Basic earnings per share, measuring profit per common share without dilution effects.",
	"EPS Diluted": "Diluted earnings per share, a more conservative shareholder earnings measure that accounts for potential share dilution.",
	"Net Income": "A company's final net profit after revenue, expenses, interest, taxes, and other items.",
	"Operating Income": "Profit from core operations before interest, taxes, and one-off items, used to assess core business profitability.",
	Revenue: "A company's top-line sales from products and services, directly reflecting growth and demand.",
	"Commodity ETF Broad": "Broad commodity ETF prices that provide diversified exposure to commodity markets.",
	"Commodity ETF Single": "ETF prices tracking a single commodity or narrow commodity exposure, such as gold or oil.",
	"Commodity ETF Thematic": "Thematic commodity ETF prices covering areas such as batteries, uranium, or commodity-related industries.",
	"Commodity Spot Bdaily": "Business-daily commodity spot or benchmark price series, used to evaluate short-term commodity price changes and price discovery.",
	"Commodity Spot Monthly": "Monthly commodity spot or benchmark price series, used to evaluate lower-frequency commodity trends and supply-demand shifts.",
	"Future Front": "Nearest-maturity commodity futures prices, reflecting tradable prices that include storage costs, rates, and supply-demand expectations.",
	"Inventory Stock": "Commodity inventory or stock levels, used to evaluate shortage, excess inventory, and seasonal supply-demand changes.",
	"Crypto Pair Close (Top 100)": "USD-denominated close price series for crypto assets, used to evaluate direction, ranking, and investment performance in volatile digital assets.",
	"Crypto Pair Close": "USD-denominated close price series for crypto assets, used to evaluate direction, ranking, and investment performance in volatile digital assets.",
	"FX Pair": "Exchange rate series between two currencies, reflecting rate differentials, trade conditions, capital flows, and risk appetite.",
	"Country Index": "Country-level equity market index series, used for cross-country ranking and global asset allocation analysis.",
	"Regional Index": "Regional equity index series covering groups of countries such as Europe, Asia, or emerging markets."
};
const strengthHeaderLabel = (label) => {
	const words = String(label).split(/\s+/).filter(Boolean);
	if (words.length < 3 && String(label).length <= 16) return `<span>${label}</span>`;
	const mid = Math.ceil(words.length / 2);
	const lines = [words.slice(0, mid).join(" "), words.slice(mid).join(" ")].filter(Boolean);
	return `<span class="sp-stack">${lines.map((line) => `<span>${line}</span>`).join("")}</span>`;
};
const finite = (v) => Number.isFinite(v);
const fmt = (v, digits = 2) => finite(v) ? v.toFixed(digits) : '<span class="skip">—</span>';
const fmtValue = (v) => {
	if (!finite(v)) return '<span class="skip">—</span>';
	const a = Math.abs(v);
	if (a !== 0 && (a < 1e-3 || a >= 1e5)) return v.toExponential(2);
	if (a >= 100) return v.toFixed(1);
	if (a >= 1) return v.toFixed(3);
	return v.toFixed(4);
};
const arithmeticMean = (values) => {
	const valid = values.filter(finite);
	return valid.length ? valid.reduce((sum, value) => sum + value, 0) / valid.length : null;
};
const escapeHtml = (value) => String(value ?? "")
	.replace(/&/g, "&amp;")
	.replace(/</g, "&lt;")
	.replace(/>/g, "&gt;")
	.replace(/"/g, "&quot;")
	.replace(/'/g, "&#39;");
const formatAoeDate = (value) => {
	const date = new Date(value);
	const aoe = new Date(date.getTime() - 12 * 60 * 60 * 1000);
	return `${aoe.getUTCFullYear()}-${String(aoe.getUTCMonth() + 1).padStart(2, "0")}-${String(aoe.getUTCDate()).padStart(2, "0")} ${aoe.getUTCHours() < 12 ? "AM" : "PM"} ${aoe.getUTCHours() % 12 || 12}:${String(aoe.getUTCMinutes()).padStart(2, "0")}:${String(aoe.getUTCSeconds()).padStart(2, "0")} AoE`;
};
const rankBadge = (r) => {
	if (!finite(r)) return '<span class="skip">—</span>';
	const whole = Number.isInteger(r);
	const cls = whole && r <= 3 ? ` g${r}` : "";
	return `<span class="rankbadge${cls}">${whole ? r : r.toFixed(1)}</span>`;
};
const overviewRankBadge = (rank) => {
	if (!finite(rank)) return '<span class="skip">—</span>';
	const whole = Number.isInteger(rank);
	const cls = whole && rank <= 3 ? ` g${rank}` : "";
	return `<span class="rankbadge${cls}">${whole ? rank : rank.toFixed(1)}</span>`;
};
const overviewScoreText = (score) => {
	if (!finite(score)) return '<span class="skip">—</span>';
	return `<span class="score-align">${fmt(score)}</span>`;
};
const parseModelSize = (value) => {
	const raw = String(value || "").trim().toLowerCase();
	if (!raw) return null;
	const match = raw.match(/^<?\s*([0-9]+(?:\.[0-9]+)?)\s*([kmb])$/);
	if (!match) return null;
	const amount = Number(match[1]);
	const unit = match[2];
	if (!finite(amount)) return null;
	if (unit === "k") return amount / 1000;
	if (unit === "b") return amount * 1000;
	return amount;
};
const sizeTickLabel = (value) => {
	if (value < 1) return `${Math.round(value * 1000)}K`;
	if (value >= 1000) return `${fmt(value / 1000, value % 1000 ? 1 : 0)}B`;
	return `${fmt(value, value % 1 ? 1 : 0)}M`;
};
const metricBase = (metric) => {
	if (metric.startsWith("mase_diff")) return "mase_diff";
	if (metric.startsWith("hrpp")) return "hrpp";
	if (metric.startsWith("hrp")) return "hrp";
	if (metric.startsWith("hr")) return "hr";
	if (metric.startsWith("mase")) return "mase";
	return metric.split("_")[0];
};
const metricName = (metric) => {
	const base = metricBase(metric);
	return METRIC_INFO[base]?.[0] || base;
};
const metricDirection = (metric) => metric.startsWith("mase_") || metric.startsWith("vol_") ? -1 : 1;
const metricLabel = (metric) => {
	const horizon = metric.replace(/^(hrpp|hrp|hr|mase_diff|mase|ic|return|sharpe|mdd|vol)_?/, "");
	return `${metricName(metric)} · ${horizon || metric}`;
};
const metricGroupKey = (metric) => metric === "mase_pct0" ? "mase_pct0" : metricBase(metric);
const metricGroupLabel = (key) => {
	if (key === "hr") return "Hitrate";
	if (key === "hrp") return "Hitrate △";
	if (key === "hrpp") return "Hitrate △²";
	if (key === "mase_pct0") return "MASE";
	return METRIC_INFO[key]?.[0] || key;
};
const metricGroupDirection = (metric) => metric === "mase_pct0" || metric === "mase_diff" || metric === "mase" || metric === "vol" ? -1 : 1;
const drawerMetricName = (metric) => {
	const base = metricBase(metric);
	if (base === "hr") return "Hitrate";
	if (base === "hrp") return "Hitrate △";
	if (base === "hrpp") return "Hitrate △²";
	return METRIC_INFO[base]?.[0] || base;
};
const periodLabel = (token) => {
	const key = String(token || "").toLowerCase();
	const map = {
		pct1w: "WoW",
		pct1m: "MoM",
		pct1q: "QoQ",
		pct1h: "HoH",
		pct1y: "YoY",
		h1w: "WoW",
		h1m: "MoM",
		h1q: "QoQ",
		h1h: "HoH",
		h1y: "YoY"
	};
	return map[key] || token || "—";
};
const targetLabel = (token) => {
	const key = String(token || "").toLowerCase();
	const match = key.match(/^after(\d+)([wmqhy])$/);
	return match ? `${match[1]}${match[2].toUpperCase()}` : "—";
};
const optionNone = "__none__";
const predTargetLabel = (token) => token === optionNone ? "Original" : targetLabel(token);
const typeLabel = (token) => {
	const key = String(token || "").toLowerCase();
	const map = {
		pct1w: "WoW",
		pct1m: "MoM",
		pct1q: "QoQ",
		pct1h: "HoH",
		pct1y: "YoY",
		h1w: "WoW",
		h1m: "MoM",
		h1q: "QoQ",
		h1h: "HoH",
		h1y: "YoY",
		[optionNone]: "All"
	};
	return map[key] || token || "All";
};
const optionOrder = (token) => {
	const key = String(token || "").toLowerCase();
	if (key === optionNone) return 999;
	const pctOrder = { pct1w: 0, pct1m: 1, pct1q: 2, pct1h: 3, pct1y: 4 };
	const horizonOrderMap = { h1w: 10, h1m: 11, h1q: 12, h1h: 13, h1y: 14 };
	if (key in pctOrder) return pctOrder[key];
	if (key in horizonOrderMap) return horizonOrderMap[key];
	const match = key.match(/^after1([wmqhy])$/);
	if (match) return ({ w: 0, m: 1, q: 2, h: 3, y: 4 }[match[1]] ?? 99);
	return 99;
};
const targetFromType = (typeToken) => {
	const match = String(typeToken || "").toLowerCase().match(/^h1([wmqhy])$/);
	return match ? `after1${match[1]}` : "";
};
const metricOptionTokens = (metric) => {
	const base = metricBase(metric);
	if (base === "mase" && metric === "mase_pct0") return { type: optionNone, target: optionNone };
	const rest = metric.replace(new RegExp(`^${base}_?`), "");
	const [type, target] = rest.split("_");
	return {
		type: type || optionNone,
		target: target || targetFromType(type) || optionNone
	};
};
const freqCode = (frequency) => {
	const map = {
		"Annual": "Y",
		"Business-daily": "BD",
		"Daily": "D",
		"Monthly": "M",
		"Quarterly": "Q",
		"Weekly": "W"
	};
	return map[frequency] || frequency || "";
};
const horizonOrder = (value) => {
	const key = String(value || "").toLowerCase();
	const match = key.match(/(?:pct|after|h)(\d+)([wmqhy])$/);
	if (!match) return 99;
	const unitOrder = { w: 0, m: 1, q: 2, h: 3, y: 4 };
	return (Number(match[1]) * 10) + (unitOrder[match[2]] ?? 9);
};
const parseMetricParts = (metric) => {
	const base = metricBase(metric);
	if (base === "mase" && metric === "mase_pct0") {
		return { name: drawerMetricName(metric), type: typeLabel(optionNone), target: predTargetLabel(optionNone) };
	}
	const rest = metric.replace(new RegExp(`^${base}_?`), "");
	const [typeToken, targetToken] = rest.split("_");
	const derivedTarget = targetToken || targetFromType(typeToken);
	return {
		name: drawerMetricName(metric),
		type: periodLabel(typeToken),
		target: predTargetLabel(derivedTarget || optionNone)
	};
};
const rankText = (rank, total) => {
	if (!finite(rank)) return "—";
	return `${rankBadge(rank)}<small class="rank-total">/ ${total.toLocaleString()}</small>`;
};
const metricFilterKey = (row) => `${row.tier}|${metricGroupKey(row.metric)}`;
const includedRecords = ({ model = true, metric = true, option = true, detail = true, frequency = true } = {}) => state.records.filter((r) => {
	const options = metricOptionTokens(r.metric);
	return (!model || !state.excludedModelTypes.has(modelType(r.model)))
		&& (!metric || !state.excludedMetrics.has(metricFilterKey(r)))
		&& (!option || (!state.excludedTypes.has(options.type) && !state.excludedTargets.has(options.target)))
		&& (!detail || !state.excludedDetails.has(r.detail_category))
		&& (!frequency || !state.excludedFrequencies.has(r.frequency));
});
const geometricMean = (values) => {
	const valid = values.filter((v) => finite(v) && v > 0);
	return valid.length ? Math.exp(valid.reduce((s, v) => s + Math.log(v), 0) / valid.length) : null;
};
const rankSum = (values) => {
	const valid = values.filter(finite);
	return valid.length ? valid.reduce((sum, value) => sum + value, 0) : null;
};
const compareNullable = (a, b, asc = true) => {
	const af = finite(a);
	const bf = finite(b);
	if (!af && !bf) return 0;
	if (!af) return 1;
	if (!bf) return -1;
	return asc ? a - b : b - a;
};
function rankRows(rows, valueKey, direction = -1) {
	const rankValue = (value) => Math.round(value * 10000) / 10000;
	const sorted = rows.filter((r) => finite(r[valueKey])).sort((a, b) => direction > 0 ? rankValue(b[valueKey]) - rankValue(a[valueKey]) : rankValue(a[valueKey]) - rankValue(b[valueKey]));
	for (let i = 0; i < sorted.length;) {
		let j = i + 1;
		while (j < sorted.length && rankValue(sorted[j][valueKey]) === rankValue(sorted[i][valueKey])) j += 1;
		const rank = (i + 1 + j) / 2;
		for (let k = i; k < j; k += 1) sorted[k].rank = rank;
		i = j;
	}
	return sorted;
}
function tierStats(records = includedRecords()) {
	const byModelTier = new Map();
	for (const row of records) {
		const key = `${row.model}|${row.tier}`;
		if (!byModelTier.has(key)) byModelTier.set(key, []);
		byModelTier.get(key).push(row.rank);
	}
	const models = includedModels();
	const stats = new Map(models.map((model) => [model, { model, count: 0, pointwise_k: null, ic_k: null, portfolio_k: null }]));
	for (const model of models) {
		const count = records.filter((r) => r.model === model).length;
		const target = stats.get(model);
		target.count = count;
		for (const tier of Object.keys(ASPECT)) target[`${ASPECT[tier]}_k`] = geometricMean(byModelTier.get(`${model}|${tier}`) || []);
	}
	for (const aspect of ["pointwise", "ic", "portfolio"]) {
		const ranked = rankRows([...stats.values()].map((row) => ({ model: row.model, value: row[`${aspect}_k`] })), "value", -1);
		for (const row of ranked) stats.get(row.model)[aspect] = row.rank;
	}
	return [...stats.values()].map((row) => ({ ...row, overall: rankSum([row.pointwise, row.ic, row.portfolio]) }));
}
function sortedOverview() {
	const rows = (state.recordsReady ? tierStats() : [...(state.data?.overall || [])]).map((row) => ({
		...row,
		overall_k: geometricMean([row.pointwise_k, row.ic_k, row.portfolio_k])
	}));
	const s = state.sort;
	const valueForSort = (row, key) => {
		if (key === "pos") return row.overall;
		if (key === "model_type") return modelTypeLabel(modelType(row.model));
		if (key === "org") return modelOrg(row.model);
		if (key === "model_size") return parseModelSize(modelSize(row.model));
		if (key === "submit_time") return modelSubmitTime(row.model);
		return row[key];
	};
	rows.sort((a, b) => {
		if (s.key === "model") return s.asc ? displayName(a.model).localeCompare(displayName(b.model)) : displayName(b.model).localeCompare(displayName(a.model));
		if (s.key === "model_type" || s.key === "org" || s.key === "submit_time") {
			const av = valueForSort(a, s.key);
			const bv = valueForSort(b, s.key);
			return s.asc ? av.localeCompare(bv) : bv.localeCompare(av);
		}
		const av = valueForSort(a, s.key);
		const bv = valueForSort(b, s.key);
		return compareNullable(av, bv, s.asc);
	});
	return rows;
}
function detailsByScope() {
	const grouped = new Map();
	for (const row of state.records) {
		const group = `${row.scope || "Other"} · ${row.category || "Other"}`;
		if (!grouped.has(group)) grouped.set(group, new Map());
		if (!grouped.get(group).has(row.detail_category)) grouped.get(group).set(row.detail_category, displayDetailLabel(row.detail_category));
	}
	return [...grouped.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}
function allFrequencies() {
	return [...new Set(state.records.map((row) => row.frequency).filter(Boolean))].sort();
}
function metricGroupsByTier() {
	const order = new Map(["hr", "hrp", "hrpp", "mase", "mase_pct0", "mase_diff", "ic", "return", "sharpe", "mdd", "vol"].map((key, index) => [key, index]));
	const grouped = new Map();
	for (const row of state.records) {
		const key = metricGroupKey(row.metric);
		if (!grouped.has(row.tier)) grouped.set(row.tier, new Set());
		grouped.get(row.tier).add(key);
	}
	return Object.keys(TIER_LABEL).map((tier) => {
		const metrics = [...(grouped.get(tier) || [])].sort((a, b) => (order.get(a) ?? 99) - (order.get(b) ?? 99) || a.localeCompare(b));
		return [tier, metrics];
	});
}
function metricOptionGroups() {
	const types = new Set();
	const targets = new Set();
	for (const row of state.records) {
		const options = metricOptionTokens(row.metric);
		types.add(options.type);
		targets.add(options.target);
	}
	return {
		types: [...types].sort((a, b) => optionOrder(a) - optionOrder(b) || typeLabel(a).localeCompare(typeLabel(b))),
		targets: [...targets].sort((a, b) => optionOrder(a) - optionOrder(b) || predTargetLabel(a).localeCompare(predTargetLabel(b)))
	};
}
function modelTypeGroups() {
	return [...new Set(state.models.map(modelType))]
		.sort((a, b) => modelTypeLabel(a).localeCompare(modelTypeLabel(b)));
}
function optionButtonGroups(values, labeler, mergeByLabel = false) {
	if (!mergeByLabel) {
		return values.map((value) => ({
			label: labeler(value),
			tokens: [value],
			title: value === optionNone ? "No option" : value
		}));
	}
	const groups = new Map();
	for (const value of values) {
		const label = labeler(value);
		if (!groups.has(label)) groups.set(label, []);
		groups.get(label).push(value);
	}
	return [...groups.entries()]
		.map(([label, tokens]) => ({
			label,
			tokens,
			title: tokens.map((value) => value === optionNone ? "No option" : value).join(", ")
		}))
		.sort((a, b) => optionOrder(a.tokens[0]) - optionOrder(b.tokens[0]) || a.label.localeCompare(b.label));
}
function buildOptionSubcard({ container, key, title, values, excluded, labeler, mergeByLabel = false }) {
	const buttonGroups = optionButtonGroups(values, labeler, mergeByLabel);
	const allTokens = buttonGroups.flatMap((group) => group.tokens);
	const groupAllOn = allTokens.every((value) => !excluded.has(value));
	const subcardKey = `option|${key}`;
	const collapsed = state.collapsedSubcards.has(subcardKey);
	const wrap = document.createElement("div");
	wrap.className = `spec-group spec-subcard${collapsed ? " subcard-collapsed" : ""}`;
	wrap.innerHTML = `
		<div class="spec-group-head">
			<button class="spec-group-label subcard-trigger" type="button" aria-expanded="${!collapsed}">
				<span>${title}</span>
				<span class="collapse-mark" aria-hidden="true">${collapsed ? "+" : "-"}</span>
			</button>
			<button class="ghost-btn group-toggle" type="button">${groupAllOn ? "Deselect all" : "Select all"}</button>
		</div>
		<div class="spec-chips"></div>
	`;
	wrap.querySelector(".subcard-trigger").onclick = () => {
		if (state.collapsedSubcards.has(subcardKey)) state.collapsedSubcards.delete(subcardKey);
		else state.collapsedSubcards.add(subcardKey);
		buildSpecToggles();
	};
	wrap.querySelector(".group-toggle").onclick = () => {
		for (const value of allTokens) {
			if (groupAllOn) excluded.add(value);
			else excluded.delete(value);
		}
		buildSpecToggles();
		render();
	};
	const chips = wrap.querySelector(".spec-chips");
	for (const option of buttonGroups) {
		const allOn = option.tokens.every((value) => !excluded.has(value));
		const btn = document.createElement("button");
		btn.type = "button";
		if (key === "model-type") btn.innerHTML = renderModelTypeFilterLabel(option.tokens[0], option.label);
		else btn.textContent = option.label;
		btn.title = option.title;
		btn.className = `filter-chip ${allOn ? "on" : "off"}`;
		btn.onclick = () => {
			for (const value of option.tokens) {
				if (allOn) excluded.add(value);
				else excluded.delete(value);
			}
			buildSpecToggles();
			render();
		};
		chips.appendChild(btn);
	}
	container.appendChild(wrap);
}
function buildSpecToggles() {
	const modelBox = $("model-toggles");
	const metricBox = $("metric-toggles");
	const optionBox = $("metric-option-toggles");
	const box = $("spec-toggles");
	const freqBox = $("frequency-toggles");
	if (!state.recordsReady) {
		modelBox.innerHTML = '<p class="loading-note">Loading model configs...</p>';
		metricBox.innerHTML = '<p class="loading-note">Loading detailed records...</p>';
		optionBox.innerHTML = '<p class="loading-note">Loading detailed records...</p>';
		box.innerHTML = '<p class="loading-note">Loading detailed records...</p>';
		freqBox.innerHTML = '<p class="loading-note">Loading detailed records...</p>';
		$("toggle-all-models").disabled = true;
		$("toggle-all-metrics").disabled = true;
		$("toggle-all-metric-options").disabled = true;
		$("toggle-all-specs").disabled = true;
		$("toggle-all-frequencies").disabled = true;
		return;
	}
	$("toggle-all-models").disabled = false;
	$("toggle-all-metrics").disabled = false;
	$("toggle-all-metric-options").disabled = false;
	$("toggle-all-specs").disabled = false;
	$("toggle-all-frequencies").disabled = false;
	modelBox.innerHTML = "";
	metricBox.innerHTML = "";
	optionBox.innerHTML = "";
	box.innerHTML = "";
	freqBox.innerHTML = "";

	buildOptionSubcard({
		container: modelBox,
		key: "model-type",
		title: "Model Type",
		values: modelTypeGroups(),
		excluded: state.excludedModelTypes,
		labeler: modelTypeLabel
	});
	const modelTypes = modelTypeGroups();
	const allModelTypesOn = modelTypes.every((type) => !state.excludedModelTypes.has(type));
	$("toggle-all-models").textContent = allModelTypesOn ? "Deselect all" : "Select all";
	$("toggle-all-models").onclick = () => {
		state.excludedModelTypes = allModelTypesOn ? new Set(modelTypes) : new Set();
		buildSpecToggles();
		render();
	};

	const metricGroups = metricGroupsByTier();
	const presentMetrics = metricGroups.flatMap(([tier, metrics]) => metrics.map((metric) => `${tier}|${metric}`));
	for (const [tier, metrics] of metricGroups) {
		const metricKeys = metrics.map((metric) => `${tier}|${metric}`);
		const groupAllOn = metricKeys.every((key) => !state.excludedMetrics.has(key));
		const subcardKey = `metric|${tier}`;
		const collapsed = state.collapsedSubcards.has(subcardKey);
		const wrap = document.createElement("div");
		wrap.className = `spec-group spec-subcard${collapsed ? " subcard-collapsed" : ""}`;
		wrap.innerHTML = `
			<div class="spec-group-head">
				<button class="spec-group-label subcard-trigger" type="button" aria-expanded="${!collapsed}">
					<span>${TIER_SHORT[tier]}</span>
					<span class="collapse-mark" aria-hidden="true">${collapsed ? "+" : "-"}</span>
				</button>
				<button class="ghost-btn group-toggle" type="button">${groupAllOn ? "Deselect all" : "Select all"}</button>
			</div>
			<div class="spec-chips"></div>
		`;
		wrap.querySelector(".subcard-trigger").onclick = () => {
			if (state.collapsedSubcards.has(subcardKey)) state.collapsedSubcards.delete(subcardKey);
			else state.collapsedSubcards.add(subcardKey);
			buildSpecToggles();
		};
		wrap.querySelector(".group-toggle").onclick = () => {
			for (const key of metricKeys) {
				if (groupAllOn) state.excludedMetrics.add(key);
				else state.excludedMetrics.delete(key);
			}
			buildSpecToggles();
			render();
		};
		const chips = wrap.querySelector(".spec-chips");
		for (const metric of metrics) {
			const key = `${tier}|${metric}`;
			const btn = document.createElement("button");
			btn.type = "button";
			btn.textContent = metricGroupLabel(metric);
			btn.title = metric;
			btn.className = `filter-chip ${state.excludedMetrics.has(key) ? "off" : "on"}`;
			if (btn.textContent.length > 18) btn.classList.add("wide");
			btn.onclick = () => {
				if (state.excludedMetrics.has(key)) state.excludedMetrics.delete(key);
				else state.excludedMetrics.add(key);
				buildSpecToggles();
				render();
			};
			chips.appendChild(btn);
		}
		metricBox.appendChild(wrap);
	}
	const allMetricsOn = presentMetrics.every((key) => !state.excludedMetrics.has(key));
	$("toggle-all-metrics").textContent = allMetricsOn ? "Deselect all" : "Select all";
	$("toggle-all-metrics").onclick = () => {
		state.excludedMetrics = allMetricsOn ? new Set(presentMetrics) : new Set();
		buildSpecToggles();
		render();
	};

	const optionGroups = metricOptionGroups();
	buildOptionSubcard({
		container: optionBox,
		key: "type",
		title: "Target Type",
		values: optionGroups.types,
		excluded: state.excludedTypes,
		labeler: typeLabel,
		mergeByLabel: true
	});
	buildOptionSubcard({
		container: optionBox,
		key: "target",
		title: "Pred. Target",
		values: optionGroups.targets,
		excluded: state.excludedTargets,
		labeler: predTargetLabel
	});
	const allMetricOptionsOn = optionGroups.types.every((type) => !state.excludedTypes.has(type))
		&& optionGroups.targets.every((target) => !state.excludedTargets.has(target));
	$("toggle-all-metric-options").textContent = allMetricOptionsOn ? "Deselect all" : "Select all";
	$("toggle-all-metric-options").onclick = () => {
		state.excludedTypes = allMetricOptionsOn ? new Set(optionGroups.types) : new Set();
		state.excludedTargets = allMetricOptionsOn ? new Set(optionGroups.targets) : new Set();
		buildSpecToggles();
		render();
	};

	const groups = detailsByScope();
	const present = groups.flatMap(([, values]) => [...values.keys()]);
	for (const [group, values] of groups) {
		const groupDetails = [...values.keys()];
		const groupAllOn = groupDetails.every((detail) => !state.excludedDetails.has(detail));
		const subcardKey = `detail|${group}`;
		const collapsed = state.collapsedSubcards.has(subcardKey);
		const wrap = document.createElement("div");
		wrap.className = `spec-group spec-subcard${collapsed ? " subcard-collapsed" : ""}`;
		wrap.innerHTML = `
			<div class="spec-group-head">
				<button class="spec-group-label subcard-trigger" type="button" aria-expanded="${!collapsed}">
					<span>${group}</span>
					<span class="collapse-mark" aria-hidden="true">${collapsed ? "+" : "-"}</span>
				</button>
				<button class="ghost-btn group-toggle" type="button">${groupAllOn ? "Deselect all" : "Select all"}</button>
			</div>
			<div class="spec-chips"></div>
		`;
		wrap.querySelector(".subcard-trigger").onclick = () => {
			if (state.collapsedSubcards.has(subcardKey)) state.collapsedSubcards.delete(subcardKey);
			else state.collapsedSubcards.add(subcardKey);
			buildSpecToggles();
		};
		wrap.querySelector(".group-toggle").onclick = () => {
			for (const detail of groupDetails) {
				if (groupAllOn) state.excludedDetails.add(detail);
				else state.excludedDetails.delete(detail);
			}
			buildSpecToggles();
			render();
		};
		const chips = wrap.querySelector(".spec-chips");
		for (const [detail, label] of [...values.entries()].sort((a, b) => a[1].localeCompare(b[1]))) {
			const btn = document.createElement("button");
			btn.type = "button";
			btn.textContent = label;
			btn.title = detail;
			btn.className = `filter-chip ${state.excludedDetails.has(detail) ? "off" : "on"}`;
			if (label.length > 18) btn.classList.add("wide");
			btn.onclick = () => {
				if (state.excludedDetails.has(detail)) state.excludedDetails.delete(detail);
				else state.excludedDetails.add(detail);
				buildSpecToggles();
				render();
			};
			chips.appendChild(btn);
		}
		box.appendChild(wrap);
	}
	const allOn = present.every((detail) => !state.excludedDetails.has(detail));
	$("toggle-all-specs").textContent = allOn ? "Deselect all" : "Select all";
	$("toggle-all-specs").onclick = () => {
		state.excludedDetails = allOn ? new Set(present) : new Set();
		buildSpecToggles();
		render();
	};

	const frequencies = allFrequencies();
	for (const frequency of frequencies) {
		const btn = document.createElement("button");
		btn.type = "button";
		btn.textContent = frequency;
		btn.className = `filter-chip ${state.excludedFrequencies.has(frequency) ? "off" : "on"}`;
		btn.onclick = () => {
			if (state.excludedFrequencies.has(frequency)) state.excludedFrequencies.delete(frequency);
			else state.excludedFrequencies.add(frequency);
			buildSpecToggles();
			render();
		};
		freqBox.appendChild(btn);
	}
	const allFreqOn = frequencies.every((frequency) => !state.excludedFrequencies.has(frequency));
	$("toggle-all-frequencies").textContent = allFreqOn ? "Deselect all" : "Select all";
	$("toggle-all-frequencies").onclick = () => {
		state.excludedFrequencies = allFreqOn ? new Set(frequencies) : new Set();
		buildSpecToggles();
		render();
	};
}
function setupCollapsibles() {
	document.querySelectorAll(".collapse-trigger").forEach((button) => {
		const target = $(button.dataset.collapseTarget);
		if (!target) return;
		const sync = () => {
			const collapsed = state.collapsedFilters.has(button.dataset.collapseTarget);
			target.classList.toggle("collapsed", collapsed);
			button.setAttribute("aria-expanded", String(!collapsed));
			button.querySelector(".collapse-mark").textContent = collapsed ? "+" : "-";
		};
		button.onclick = () => {
			if (state.collapsedFilters.has(button.dataset.collapseTarget)) state.collapsedFilters.delete(button.dataset.collapseTarget);
			else state.collapsedFilters.add(button.dataset.collapseTarget);
			sync();
		};
		sync();
	});
}
function setupTabs() {
	document.querySelectorAll("#viewtabs button").forEach((button) => {
		button.onclick = () => {
			state.view = button.dataset.view;
			document.querySelectorAll("#viewtabs button").forEach((item) => item.classList.toggle("active", item === button));
			document.querySelectorAll("main > .panel").forEach((panel) => panel.hidden = panel.id !== `panel-${state.view}`);
			syncSidebarForView();
			render();
		};
	});
}
function syncSidebarForView() {
	const showSidebar = state.view === "overview" || state.view === "overview-plot" || state.view === "metrics" || state.view === "strength";
	$("sidebar").hidden = !showSidebar;
	$("model-filter-card").hidden = !showSidebar;
	$("metric-filter-card").hidden = !showSidebar;
	$("metric-option-card").hidden = !showSidebar;
}
function renderLoadingPanel(targetId) {
	$(targetId).innerHTML = '<tr><td class="skip" colspan="4">Loading detailed records. The overview is shown first.</td></tr>';
}
function renderOrganizationLegend() {
	const orgs = [...new Set(includedModels().map(organizationName))].sort();
	$("organization-legend-body").innerHTML = orgs
		.map((org) => `<span><i style="background:${organizationColor(org)}"></i><b>${escapeHtml(org)}</b></span>`)
		.join("");
}
function renderOverviewPlot(rows) {
	const target = $("overview-plot-body");
	const points = rows
		.map((row) => ({
			model: row.model,
			label: displayName(row.model),
			org: organizationName(row.model),
			sizeLabel: modelSize(row.model),
			size: parseModelSize(modelSize(row.model)),
			pointwise: row.pointwise,
			ic: row.ic,
			portfolio: row.portfolio,
			rank: row.overall
		}))
		.filter((row) => finite(row.size) && row.size > 0 && finite(row.rank));
	if (!points.length) {
		target.innerHTML = '<p class="loading-note">No model size metadata is available for the selected models.</p>';
		return;
	}
	const width = 860;
	const height = 360;
	const pad = { left: 58, right: 24, top: 22, bottom: 48 };
	const minX = Math.min(...points.map((p) => p.size));
	const maxX = Math.max(...points.map((p) => p.size));
	const minY = Math.min(...points.map((p) => p.rank));
	const maxY = Math.max(...points.map((p) => p.rank));
	const logMin = Math.log10(Math.max(minX * 0.8, 0.1));
	const logMax = Math.log10(maxX * 1.2);
	const yMin = Math.max(0, minY - 3);
	const yMax = maxY + 3;
	const x = (value) => pad.left + ((Math.log10(value) - logMin) / (logMax - logMin || 1)) * (width - pad.left - pad.right);
	const y = (value) => pad.top + ((value - yMin) / (yMax - yMin || 1)) * (height - pad.top - pad.bottom);
	const xTicks = [0.5, 1, 10, 100, 1000, 2500].filter((tick) => tick >= minX * 0.75 && tick <= maxX * 1.35);
	const yTicks = Array.from({ length: 5 }, (_, index) => yMin + ((yMax - yMin) * index) / 4);
	const byOrg = new Map();
	for (const point of points) {
		if (!byOrg.has(point.org)) byOrg.set(point.org, []);
		byOrg.get(point.org).push(point);
	}
	const lines = [...byOrg.entries()].map(([org, group]) => {
		if (org === "Unspecified") return "";
		const sorted = group.slice().sort((a, b) => a.size - b.size);
		if (sorted.length < 2) return "";
		return `<polyline class="plot-line" points="${sorted.map((p) => `${x(p.size).toFixed(1)},${y(p.rank).toFixed(1)}`).join(" ")}" stroke="${organizationColor(org)}" />`;
	}).join("");
	const tooltipRows = (p) => [
		["Model", p.label],
		["Org", p.org],
		["Model size", p.sizeLabel],
		["Point rank", fmt(p.pointwise, finite(p.pointwise) && p.pointwise % 1 ? 1 : 0)],
		["Ranking rank", fmt(p.ic, finite(p.ic) && p.ic % 1 ? 1 : 0)],
		["Decision rank", fmt(p.portfolio, finite(p.portfolio) && p.portfolio % 1 ? 1 : 0)],
		["Rank sum", fmt(p.rank, p.rank % 1 ? 1 : 0)]
	];
	const bestRank = Math.min(...points.map((p) => p.rank));
	const dots = points.map((p) => `
		<g class="plot-point" transform="translate(${x(p.size).toFixed(1)} ${y(p.rank).toFixed(1)})" data-tooltip='${escapeHtml(JSON.stringify(tooltipRows(p)))}'>
			${p.rank === bestRank
				? `<text class="plot-star" text-anchor="middle" dominant-baseline="central" fill="${organizationColor(p.org)}">★</text><text class="plot-best-label" x="12" y="-10">${escapeHtml(p.label)}</text>`
				: `<circle r="5.5" fill="${organizationColor(p.org)}"></circle>`}
		</g>
	`).join("");
	target.innerHTML = `
		<div class="plot-scroll">
			<svg class="rank-size-plot" viewBox="0 0 ${width} ${height}" role="img" aria-label="Model size and Rank sum scatter plot">
				${xTicks.map((tick) => `<line class="plot-grid" x1="${x(tick)}" x2="${x(tick)}" y1="${pad.top}" y2="${height - pad.bottom}" />`).join("")}
				${yTicks.map((tick) => `<line class="plot-grid" x1="${pad.left}" x2="${width - pad.right}" y1="${y(tick)}" y2="${y(tick)}" />`).join("")}
				<line class="plot-axis" x1="${pad.left}" x2="${width - pad.right}" y1="${height - pad.bottom}" y2="${height - pad.bottom}" />
				<line class="plot-axis" x1="${pad.left}" x2="${pad.left}" y1="${pad.top}" y2="${height - pad.bottom}" />
				${xTicks.map((tick) => `<text class="plot-label" x="${x(tick)}" y="${height - 20}" text-anchor="middle">${sizeTickLabel(tick)}</text>`).join("")}
				${yTicks.map((tick) => `<text class="plot-label" x="${pad.left - 10}" y="${y(tick) + 4}" text-anchor="end">${fmt(tick, 0)}</text>`).join("")}
				<text class="plot-axis-title" x="${width / 2}" y="${height - 4}" text-anchor="middle">Model size, parameters (log scale)</text>
				<text class="plot-axis-title" x="-${height / 2}" y="16" transform="rotate(-90)" text-anchor="middle">Rank sum</text>
				${lines}
				${dots}
			</svg>
		</div>
		<div class="plot-tooltip" hidden></div>
	`;
	const tooltip = target.querySelector(".plot-tooltip");
	target.querySelectorAll(".plot-point").forEach((point) => {
		point.addEventListener("mouseenter", () => {
			const rows = JSON.parse(point.dataset.tooltip);
			tooltip.innerHTML = `<table>${rows.map(([key, value]) => `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(value)}</td></tr>`).join("")}</table>`;
			tooltip.hidden = false;
		});
		point.addEventListener("mousemove", (event) => {
			const rect = target.getBoundingClientRect();
			const left = event.clientX - rect.left - tooltip.offsetWidth - 12;
			tooltip.style.left = `${Math.max(8, left)}px`;
			tooltip.style.top = `${event.clientY - rect.top + 12}px`;
		});
		point.addEventListener("mouseleave", () => {
			tooltip.hidden = true;
		});
	});
}
function renderMetricProgressPlots() {
	const target = $("metric-progress-body");
	if (!state.recordsReady) {
		target.innerHTML = '<p class="loading-note">Loading detailed records.</p>';
		return;
	}
	const models = includedModels().filter((model) => /^\d{4}-\d{2}-\d{2}$/.test(modelSubmitTime(model)));
	if (!models.length) {
		target.innerHTML = '<p class="loading-note">No dated model tier K data matches the selected filters.</p>';
		return;
	}
	const statsByModel = new Map(tierStats().map((row) => [row.model, row]));
	const tiers = [
		{ key: "pointwise_k", title: "Point rank", tier: "tier1" },
		{ key: "ic_k", title: "Ranking rank", tier: "tier2" },
		{ key: "portfolio_k", title: "Decision rank", tier: "tier3" }
	];
	const width = 860;
	const height = 300;
	const pad = { left: 74, right: 24, top: 22, bottom: 48 };
	const dateValue = (model) => Date.parse(`${modelSubmitTime(model)}T00:00:00Z`);
	const allDates = [...new Set(models.map((model) => dateValue(model)).filter(finite))].sort((a, b) => a - b);
	if (!allDates.length) {
		target.innerHTML = '<p class="loading-note">No submit date metadata is available for the selected models.</p>';
		return;
	}
	const minDate = allDates[0];
	const maxDate = allDates[allDates.length - 1];
	const x = (date) => pad.left + ((date - minDate) / (maxDate - minDate || 1)) * (width - pad.left - pad.right);
	const dateLabel = (date) => new Date(date).toISOString().slice(0, 10);
	const dateTicks = allDates.length <= 3 ? allDates : [allDates[0], allDates[Math.floor(allDates.length / 2)], allDates[allDates.length - 1]];
	const cards = tiers.map((tierInfo) => {
		const modelValues = models.map((model) => ({
			model,
			date: dateValue(model),
			value: statsByModel.get(model)?.[tierInfo.key]
		})).filter((row) => finite(row.value)).sort((a, b) => a.date - b.date || displayName(a.model).localeCompare(displayName(b.model)));
		const timeline = [];
		let best = null;
		for (const date of allDates) {
			for (const row of modelValues.filter((item) => item.date === date)) {
				if (!best || row.value < best.value) best = row;
			}
			if (best) timeline.push({ date, value: best.value, model: best.model, org: organizationName(best.model) });
		}
		if (timeline.length < 1) return "";
		const values = timeline.map((p) => p.value);
		const rawMin = Math.min(...values);
		const rawMax = Math.max(...values);
		const spread = rawMax - rawMin || Math.max(Math.abs(rawMax) * 0.1, 1);
		const yMin = rawMin - spread * 0.12;
		const yMax = rawMax + spread * 0.12;
		const y = (value) => pad.top + ((value - yMin) / (yMax - yMin || 1)) * (height - pad.top - pad.bottom);
		const yTicks = [yMin, (yMin + yMax) / 2, yMax];
		const segments = timeline.slice(1).map((p, index) => {
			const prev = timeline[index];
			return `<line class="metric-progress-line" x1="${x(prev.date).toFixed(1)}" y1="${y(prev.value).toFixed(1)}" x2="${x(p.date).toFixed(1)}" y2="${y(p.value).toFixed(1)}" stroke="${organizationColor(p.org)}" />`;
		}).join("");
		const pointTimeline = timeline.filter((p, index) => {
			if (index === 0 || index === timeline.length - 1) return true;
			return p.value !== timeline[index - 1].value;
		});
		const points = pointTimeline.map((p) => {
			const rows = [
				["Date", dateLabel(p.date)],
				["Tier", tierInfo.title],
				["Best model", displayName(p.model)],
				["Org", p.org],
				["K score", fmt(p.value)]
			];
			return `<g class="metric-progress-point" transform="translate(${x(p.date).toFixed(1)} ${y(p.value).toFixed(1)})" data-tooltip='${escapeHtml(JSON.stringify(rows))}'><circle r="4.5" fill="${organizationColor(p.org)}"></circle></g>`;
		}).join("");
		return `
			<section class="metric-progress-card">
				<h3>${tierInfo.title} <span>lower K is better</span></h3>
				<svg class="metric-progress-plot" viewBox="0 0 ${width} ${height}" role="img" aria-label="${tierInfo.title} K progress plot">
					${dateTicks.map((date) => `<line class="plot-grid" x1="${x(date)}" x2="${x(date)}" y1="${pad.top}" y2="${height - pad.bottom}" />`).join("")}
					${yTicks.map((tick) => `<line class="plot-grid" x1="${pad.left}" x2="${width - pad.right}" y1="${y(tick)}" y2="${y(tick)}" />`).join("")}
					<line class="plot-axis" x1="${pad.left}" x2="${width - pad.right}" y1="${height - pad.bottom}" y2="${height - pad.bottom}" />
					<line class="plot-axis" x1="${pad.left}" x2="${pad.left}" y1="${pad.top}" y2="${height - pad.bottom}" />
					${dateTicks.map((date) => `<text class="plot-label" x="${x(date)}" y="${height - 20}" text-anchor="middle">${dateLabel(date).slice(2, 7)}</text>`).join("")}
					${yTicks.map((tick) => `<text class="plot-label" x="${pad.left - 10}" y="${y(tick) + 4}" text-anchor="end">${fmtValue(tick)}</text>`).join("")}
					<text class="plot-axis-title" x="${width / 2}" y="${height - 4}" text-anchor="middle">Date</text>
					<text class="plot-axis-title" x="-${height / 2}" y="14" transform="rotate(-90)" text-anchor="middle">K score</text>
					${segments}
					${points}
				</svg>
			</section>
		`;
	}).filter(Boolean).join("");
	if (!cards) {
		target.innerHTML = '<p class="loading-note">No tier K progress data matches the selected filters.</p>';
		return;
	}
	target.innerHTML = `${cards}<div class="plot-tooltip" hidden></div>`;
	const tooltip = target.querySelector(".plot-tooltip");
	target.querySelectorAll(".metric-progress-point").forEach((point) => {
		point.addEventListener("mouseenter", () => {
			const rows = JSON.parse(point.dataset.tooltip);
			tooltip.innerHTML = `<table>${rows.map(([key, value]) => `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(value)}</td></tr>`).join("")}</table>`;
			tooltip.hidden = false;
		});
		point.addEventListener("mousemove", (event) => {
			const rect = target.getBoundingClientRect();
			const left = event.clientX - rect.left - tooltip.offsetWidth - 12;
			tooltip.style.left = `${Math.max(8, left)}px`;
			tooltip.style.top = `${event.clientY - rect.top + 12}px`;
		});
		point.addEventListener("mouseleave", () => {
			tooltip.hidden = true;
		});
	});
}
function renderOverviewPlotPage() {
	renderOrganizationLegend();
	renderOverviewPlot(sortedOverview());
	renderMetricProgressPlots();
}
function renderOverview() {
	const rows = sortedOverview();
	const showModelInfo = state.showModelInfo;
	const best = Math.min(...rows.map((r) => r.overall).filter(finite));
	const leader = [...(state.recordsReady ? tierStats() : (state.data?.overall || []))]
		.sort((a, b) => compareNullable(a.overall, b.overall) || a.model.localeCompare(b.model))[0];
	const strip = $("winner-strip");
	if (strip && leader) {
		strip.innerHTML = `<span>Overall #1</span><b>${displayName(leader.model)}</b><strong>Rank sum ${fmt(leader.overall, finite(leader.overall) && leader.overall % 1 ? 1 : 0)}</strong>`;
	}
	$("detail-rank-toggle").checked = state.showDetailRank;
	$("model-info-toggle").checked = state.showModelInfo;
	const hint = $("overview-mode-hint");
	if (hint) {
		hint.innerHTML = `Overall rank is the sum of the <b>Point·Ranking·Decision</b> tier ranks. Each tier rank is ordered by the geometric mean K of metric ranks under the selected Detail Category and Frequency filters. Each cell is shown as <span class="cell-example"><span class="rankbadge hint-badge">A</span> <b>(B)</b></span>. <b>(B appears when the Detail Value toggle is on</b>). <b>A</b> is the tier rank, and <b>B</b> is the K score used to produce that rank. Lower <b>Rank sum</b> is better.`;
	}
	document.querySelector('#overview-table th[data-sort="pointwise"]').textContent = "Point rank";
	document.querySelector('#overview-table th[data-sort="ic"]').textContent = "Ranking rank";
	document.querySelector('#overview-table th[data-sort="portfolio"]').textContent = "Decision rank";
	document.querySelector('#overview-table th[data-sort="overall"]').textContent = "Rank sum ↓";
	document.querySelector('#overview-table th[data-sort="org"]').hidden = !showModelInfo;
	document.querySelector('#overview-table th[data-sort="model_size"]').hidden = !showModelInfo;
	document.querySelector('#overview-table th[data-sort="submit_time"]').hidden = !showModelInfo;
	const tierCell = (row, tier) => {
		const aspect = ASPECT[tier];
		const rank = row[aspect];
		const k = row[`${aspect}_k`];
		if (!finite(k)) return `<td class="num">${fmt(k)}</td>`;
		const content = state.showDetailRank
			? `${overviewRankBadge(rank)} <small class="cell-sub">(${overviewScoreText(k)})</small>`
			: overviewRankBadge(rank);
		return `<td class="num tier-rank" data-model="${row.model}" data-tier="${tier}">${content}</td>`;
	};
	const overallCell = (row) => {
		const value = row.overall;
		const text = fmt(value, finite(value) && value % 1 ? 1 : 0);
		return `<td class="num rank-sum ${value === best ? "val-best" : ""}" data-model="${row.model}" title="View Point, Ranking, and Decision rank detail"><b>${text}</b></td>`;
	};
	$("overview-body").innerHTML = rows.map((row, index) => `
		<tr class="model-row ${isExaone(row.model) ? "exaone-row" : ""}">
			<td class="num">${rankBadge(index + 1)}</td>
			${renderModelTypeCell(row.model)}
			${renderModelCell(row.model)}
			${showModelInfo ? `<td>${modelOrg(row.model)}</td><td>${modelSize(row.model)}</td><td>${modelSubmitTime(row.model)}</td>` : ""}
			${tierCell(row, "tier1")}
			${tierCell(row, "tier2")}
			${tierCell(row, "tier3")}
			${overallCell(row)}
		</tr>
	`).join("");
	$("overview-body").querySelectorAll(".tier-rank").forEach((td) => td.onclick = () => showTierDetail(td.dataset.model, td.dataset.tier));
	$("overview-body").querySelectorAll(".rank-sum").forEach((td) => td.onclick = () => showModelDetail(td.dataset.model));
	document.querySelectorAll("#overview-table th[data-sort]").forEach((th) => {
		th.classList.toggle("sorted", th.dataset.sort === state.sort.key);
		th.classList.toggle("asc", th.dataset.sort === state.sort.key && state.sort.asc);
		th.onclick = () => {
			if (state.sort.key === th.dataset.sort) state.sort.asc = !state.sort.asc;
			else { state.sort.key = th.dataset.sort; state.sort.asc = th.dataset.sort !== "model"; }
			renderOverview();
		};
	});
}
function bindMetricsSortHeaders(columns) {
	const metricDirections = new Map(columns.map((column) => [`metric:${column.key}`, metricGroupDirection(column.metric)]));
	document.querySelectorAll("#metrics-head th[data-sort]").forEach((th) => {
		th.onclick = () => {
			const key = th.dataset.sort;
			if (state.metricsSort.key === key) {
				state.metricsSort.asc = !state.metricsSort.asc;
			} else {
				state.metricsSort.key = key;
				state.metricsSort.asc = key.startsWith("metric:") ? metricDirections.get(key) < 0 : true;
			}
			renderMetrics();
		};
	});
}
function renderMetrics() {
	$("metrics-model-info-toggle").checked = state.showModelInfo;
	if (!state.recordsReady) {
		renderLoadingPanel("metrics-body");
		return;
	}
	const scopedRecords = includedRecords();
	const columns = metricGroupsByTier()
		.flatMap(([tier, metrics]) => metrics.map((metric) => ({ tier, metric, key: `${tier}|${metric}` })))
		.filter((column) => !state.excludedMetrics.has(column.key) && scopedRecords.some((row) => metricFilterKey(row) === column.key));
	const sortState = state.metricsSort;
	const sortClass = (key) => `${sortState.key === key ? " sorted" : ""}${sortState.key === key && sortState.asc ? " asc" : ""}`;
	const orgHead = state.showModelInfo
		? `<th data-sort="org" class="${sortClass("org")}">Organization</th><th data-sort="model_size" class="${sortClass("model_size")}">Model size</th><th data-sort="submit_time" class="${sortClass("submit_time")}">Submit date</th>`
		: "";
	const orgColspan = state.showModelInfo ? 5 : 2;
	if (!columns.length) {
		$("metrics-head").innerHTML = `<tr><th class="model-type-head${sortClass("model_type")}" data-sort="model_type" title="Model Type"></th><th data-sort="model" class="${sortClass("model")}">Model</th>${orgHead}</tr>`;
		$("metrics-body").innerHTML = `<tr><td class="skip" colspan="${orgColspan}">No metric is selected.</td></tr>`;
		bindMetricsSortHeaders(columns);
		return;
	}
	const rows = tierStats();
	const valuesByModel = new Map();
	for (const model of includedModels()) {
		const map = new Map();
		for (const column of columns) {
			map.set(column.key, arithmeticMean(scopedRecords
				.filter((row) => row.model === model && metricFilterKey(row) === column.key)
				.map((row) => row.value)));
		}
		valuesByModel.set(model, map);
	}
	const bestByColumn = new Map(columns.map((column) => {
		const values = rows.map((row) => valuesByModel.get(row.model).get(column.key)).filter(finite);
		const direction = metricGroupDirection(column.metric);
		return [column.key, values.length ? (direction > 0 ? Math.max(...values) : Math.min(...values)) : null];
	}));
	const valueForSort = (row, key) => {
		if (key === "rank") return row.overall;
		if (key === "model_type") return modelTypeLabel(modelType(row.model));
		if (key === "model") return displayName(row.model);
		if (key === "org") return modelOrg(row.model);
		if (key === "model_size") return parseModelSize(modelSize(row.model));
		if (key === "submit_time") return modelSubmitTime(row.model);
		if (key.startsWith("metric:")) return valuesByModel.get(row.model)?.get(key.slice(7));
		return row[key];
	};
	rows.sort((a, b) => {
		const key = sortState.key;
		const av = valueForSort(a, key);
		const bv = valueForSort(b, key);
		if (["model_type", "model", "org", "submit_time"].includes(key)) {
			const cmp = String(av || "").localeCompare(String(bv || ""));
			return sortState.asc ? cmp : -cmp;
		}
		return compareNullable(av, bv, sortState.asc) || displayName(a.model).localeCompare(displayName(b.model));
	});
	$("metrics-head").innerHTML = `<tr><th class="num${sortClass("rank")}" data-sort="rank">#</th><th class="model-type-head${sortClass("model_type")}" data-sort="model_type" title="Model Type"></th><th data-sort="model" class="${sortClass("model")}">Model</th>${orgHead}${columns.map((column) => `<th class="num${sortClass(`metric:${column.key}`)}" data-sort="metric:${column.key}" title="${TIER_SHORT[column.tier]} · ${column.metric}">${TIER_SHORT[column.tier]}<br>${metricGroupLabel(column.metric)}</th>`).join("")}</tr>`;
	$("metrics-body").innerHTML = rows.map((row, index) => {
		const modelValues = valuesByModel.get(row.model);
		return `<tr class="${isExaone(row.model) ? "exaone-row" : ""}">
			<td class="num">${rankBadge(index + 1)}</td>
			${renderModelTypeCell(row.model)}
			${renderModelCell(row.model)}
			${state.showModelInfo ? `<td>${modelOrg(row.model)}</td><td>${modelSize(row.model)}</td><td>${modelSubmitTime(row.model)}</td>` : ""}
			${columns.map((column) => {
				const value = modelValues.get(column.key);
				return finite(value)
					? `<td class="num metric-score ${value === bestByColumn.get(column.key) ? "val-best" : ""}" data-model="${row.model}" data-tier="${column.tier}" data-metric-group="${column.metric}" title="View average components">${fmtValue(value)}</td>`
					: `<td class="num">${fmtValue(value)}</td>`;
			}).join("")}
		</tr>`;
	}).join("");
	$("metrics-body").querySelectorAll(".metric-score").forEach((td) => {
		td.onclick = () => showMetricScoreDetail(td.dataset.model, td.dataset.tier, td.dataset.metricGroup);
	});
	bindMetricsSortHeaders(columns);
}
function showMetricScoreDetail(model, tier, metricGroup) {
	const rows = includedRecords()
		.filter((row) => row.model === model && metricFilterKey(row) === `${tier}|${metricGroup}`)
		.sort((a, b) => a.category.localeCompare(b.category)
			|| a.detail_category.localeCompare(b.detail_category)
			|| a.frequency.localeCompare(b.frequency)
			|| metricBase(a.metric).localeCompare(metricBase(b.metric))
			|| a.metric.localeCompare(b.metric));
	const mean = (values) => {
		const valid = values.filter(finite);
		return valid.length ? valid.reduce((sum, value) => sum + value, 0) / valid.length : null;
	};
	const overall = mean(rows.map((row) => row.value));
	let html = `<p class="detail-k">${TIER_SHORT[tier]} · ${metricGroupLabel(metricGroup)} average = <b>${fmtValue(overall)}</b> · ${rows.length.toLocaleString()} cells</p>`;
	html += `<div class="period-card"><h3>Description for Date Codes</h3><div class="period-help"><span><b>D</b> Day</span><span><b>W</b> Week</span><span><b>M</b> Month</span><span><b>Q</b> Quarter</span><span><b>H</b> Half-year</span><span><b>Y</b> Year</span><span><b>B</b> Business</span><span><b>BD</b> Business-day</span></div></div>`;
	html += `<table><thead><tr><th>Detail Category</th><th>Freq</th><th>Metrics</th><th>Target Type</th><th>Pred. Target</th><th class="num">Value</th><th class="num">Rank</th></tr></thead><tbody>`;
	for (const row of rows) {
		const parts = parseMetricParts(row.metric);
		const direction = metricDirection(row.metric);
		const total = state.records.filter((candidate) => {
			return candidate.tier === row.tier
				&& candidate.detail_category === row.detail_category
				&& candidate.frequency === row.frequency
				&& candidate.series_id === row.series_id
				&& candidate.metric === row.metric;
		}).length;
		html += `<tr><td title="${row.category || ""}">${displayDetailLabel(row.detail_category)}</td><td title="${row.frequency}">${freqCode(row.frequency)}</td><td title="${row.metric}">${parts.name} <span class="dir-arrow ${direction > 0 ? "up" : "down"}">${direction > 0 ? "↑" : "↓"}</span></td><td>${parts.type}</td><td>${parts.target}</td><td class="num">${fmtValue(row.value)}</td><td class="num">${rankText(row.rank, total)}</td></tr>`;
	}
	html += `</tbody></table><p class="note">These are the raw metric values and ranks used to form this average cell under the current left-side Metric · Detail Category · Frequency filters.</p>`;
	openDrawer(`${displayName(model)} · ${metricGroupLabel(metricGroup)}`, html);
	closeDrawer2();
}
function unitK(tier, records = includedRecords()) {
	const out = new Map();
	for (const model of includedModels()) out.set(model, new Map());
	const grouped = new Map();
	for (const row of records.filter((r) => !tier || r.tier === tier)) {
		const key = JSON.stringify([row.model, row.unit]);
		if (!grouped.has(key)) grouped.set(key, []);
		grouped.get(key).push(row.rank);
	}
	for (const [key, ranks] of grouped) {
		const [model, unit] = JSON.parse(key);
		if (out.has(model)) out.get(model).set(unit, geometricMean(ranks));
	}
	return out;
}
function renderStrength() {
	if (!state.recordsReady) {
		$("strength-body").innerHTML = '<p class="loading-note">Loading detailed records.</p>';
		return;
	}
	const scopedRecords = includedRecords();
	if (!scopedRecords.length) {
		$("strength-body").innerHTML = '<p class="loading-note">No cells match the selected filters.</p>';
		return;
	}
	const models = includedModels();
	const overallScores = new Map(models.map((model) => [
		model,
		geometricMean(scopedRecords.filter((row) => row.model === model).map((row) => row.rank))
	]));
	const overallRanks = new Map(rankRows([...overallScores.entries()].map(([model, value]) => ({ model, value })), "value", -1).map((row) => [row.model, row.rank]));
	const units = [...new Map(scopedRecords.map((r) => [r.unit, displayDetailLabel(r.unit_label || r.unit)])).entries()].sort((a, b) => a[1].localeCompare(b[1]));
	const matrix = unitK(null, scopedRecords);
	const overall = models
		.filter((model) => finite(overallScores.get(model)))
		.sort((a, b) => compareNullable(overallScores.get(a), overallScores.get(b)) || a.localeCompare(b));
	const colRanks = new Map();
	for (const [unit] of units) {
		const vals = models.map((model) => ({ model, value: matrix.get(model).get(unit) }));
		colRanks.set(unit, new Map(rankRows(vals, "value", -1).map((r) => [r.model, r.rank])));
	}
	const color = (rank, n) => {
		if (!finite(rank)) return "#f3f4f6";
		const t = n > 1 ? (rank - 1) / (n - 1) : 0;
		return `hsl(${120 * (1 - Math.max(0, Math.min(1, t)))}, 62%, 78%)`;
	};
	let html = `<table class="smap"><thead><tr><th class="mh">Model (${overall.length})</th><th class="ov">Overall</th>`;
	for (const [, label] of units) html += `<th class="sp">${strengthHeaderLabel(label)}</th>`;
	html += `</tr></thead><tbody>`;
	for (const model of overall) {
		const ov = overallScores.get(model);
		const ovRank = overallRanks.get(model);
		html += `<tr class="${isExaone(model) ? "exaone-row" : ""}"><td class="mh">${displayName(model)}</td><td class="ov" style="background:${color(ovRank, overall.length)}" title="Overall K geomean">${fmt(ov)}</td>`;
		for (const [unit] of units) {
			const k = matrix.get(model).get(unit);
			const rank = colRanks.get(unit).get(model);
			html += `<td style="background:${color(rank, overall.length)}" title="Column rank ${finite(rank) ? rank : "—"}">${finite(k) ? k.toFixed(1) : "·"}</td>`;
		}
		html += `</tr>`;
	}
	$("strength-body").innerHTML = html + `</tbody></table>`;
}
function guideTable(title, rows) {
	return `
		<section class="guide-card">
			<h3>${title}</h3>
			<table class="board guide-table"><thead><tr><th>Button</th><th>Meaning</th></tr></thead><tbody>
				${rows.map(([button, meaning]) => `<tr><td class="model-name">${button}</td><td>${meaning}</td></tr>`).join("")}
			</tbody></table>
		</section>
	`;
}
function renderLeaderboardGuide() {
	if (!state.recordsReady) {
		$("leaderboard-guide-body").innerHTML = '<p class="loading-note">Loading detailed records.</p>';
		return;
	}
	const metricRows = metricGroupsByTier().flatMap(([tier, metrics]) => metrics.map((metric) => {
		const base = metric === "mase_pct0" ? "mase" : metric;
		const desc = metric === "mase_pct0" ? "Forecast error on the original target without a percentage-change option." : (METRIC_INFO[base]?.[1] || "Includes this metric group in rank calculations.");
		return [`${TIER_SHORT[tier]} · ${metricGroupLabel(metric)}`, desc];
	}));
	const optionGroups = metricOptionGroups();
	const typeRows = optionButtonGroups(optionGroups.types, typeLabel, true).map((group) => {
		const descriptions = {
			All: "Original-target metrics without a specific percentage-change type.",
			WoW: "Week-over-week percentage change as the target type.",
			MoM: "Month-over-month percentage change as the target type.",
			QoQ: "Quarter-over-quarter percentage change as the target type.",
			HoH: "Half-year-over-half-year percentage change as the target type.",
			YoY: "Year-over-year percentage change as the target type."
		};
		return [group.label, descriptions[group.label] || "Includes this target type in rank calculations."];
	});
	const targetRows = optionButtonGroups(optionGroups.targets, predTargetLabel).map((group) => {
		const descriptions = {
			Original: "Original target without a forecast horizon label.",
			"1W": "Forecast target one week ahead.",
			"1M": "Forecast target one month ahead.",
			"1Q": "Forecast target one quarter ahead.",
			"1H": "Forecast target one half-year ahead.",
			"1Y": "Forecast target one year ahead."
		};
		return [group.label, descriptions[group.label] || "Includes this prediction target horizon in rank calculations."];
	});
	const detailRows = detailsByScope().flatMap(([, values]) => [...values.entries()]
		.sort((a, b) => a[1].localeCompare(b[1]))
		.map(([detail, label]) => [label, DETAIL_CATEGORY_EXPLANATIONS[detail] || DETAIL_CATEGORY_EXPLANATIONS[label] || "Evaluation data for this Detail Category."]));
	const frequencyRows = allFrequencies().map((frequency) => {
		const descriptions = {
			Annual: "Time series observed at an annual frequency.",
			"Business-daily": "Time series observed at a business-daily frequency.",
			Daily: "Time series observed at a daily frequency.",
			Monthly: "Time series observed at a monthly frequency.",
			Quarterly: "Time series observed at a quarterly frequency.",
			Weekly: "Time series observed at a weekly frequency."
		};
		return [frequency, descriptions[frequency] || `Time series observed at a ${frequency} frequency.`];
	});
	$("leaderboard-guide-body").innerHTML = `
		<div class="guide-grid">
			${guideTable("Metric", metricRows)}
			${guideTable("Metric Additional Option · Target Type", typeRows)}
			${guideTable("Metric Additional Option · Pred. Target", targetRows)}
			${guideTable("Detail Category", detailRows)}
			${guideTable("Frequency", frequencyRows)}
		</div>
	`;
}
function showModelDetail(model) {
	if (!state.recordsReady) return;
	const stats = tierStats().find((r) => r.model === model);
	const units = [...new Map(includedRecords().filter((r) => r.model === model).map((r) => [r.unit, displayDetailLabel(r.unit_label || r.unit)])).entries()].sort((a, b) => a[1].localeCompare(b[1]));
	const categoryByUnit = new Map(includedRecords().filter((r) => r.model === model).map((r) => [r.unit, r.category || ""]));
	const matrixByTier = Object.fromEntries(Object.keys(TIER_LABEL).map((tier) => [tier, unitK(tier).get(model)]));
	let html = `<p class="detail-k">Rank sum = <b>${fmt(stats.overall, finite(stats.overall) && stats.overall % 1 ? 1 : 0)}</b></p><table><thead><tr><th>Category</th><th>Detail Category</th><th class="num">Point</th><th class="num">Ranking</th><th class="num">Decision</th></tr></thead><tbody>`;
	const cell = (tier, unit) => {
		const value = matrixByTier[tier].get(unit);
		return finite(value)
			? `<td class="num cat-rank" data-model="${model}" data-tier="${tier}" data-unit="${unit}" title="View ${TIER_SHORT[tier]} raw metric values">${fmt(value)}</td>`
			: `<td class="num">${fmt(value)}</td>`;
	};
	for (const [unit, label] of units) html += `<tr><td>${categoryByUnit.get(unit) || ""}</td><td title="${unit}">${label}</td>${cell("tier1", unit)}${cell("tier2", unit)}${cell("tier3", unit)}</tr>`;
	html += `</tbody></table><p class="note">Each cell is the geometric mean K of metric ranks for that Detail Category under the selected Frequency filters. Lower is better. <b>Click a number</b> to view raw metric values and ranks.</p>`;
	openDrawer(displayName(model), html);
	closeDrawer2();
	$("detail-body").querySelectorAll(".cat-rank").forEach((td) => {
		td.onclick = () => showMetricDetail(td.dataset.model, td.dataset.tier, td.dataset.unit);
	});
}
function showTierDetail(model, tier) {
	if (!state.recordsReady) return;
	const matrix = unitK(tier).get(model);
	const units = [...new Map(includedRecords().filter((r) => r.model === model && r.tier === tier).map((r) => [r.unit, displayDetailLabel(r.unit_label || r.unit)])).entries()].sort((a, b) => a[1].localeCompare(b[1]));
	const categoryByUnit = new Map(includedRecords().filter((r) => r.model === model && r.tier === tier).map((r) => [r.unit, r.category || ""]));
	let html = `<p class="detail-k">${TIER_SHORT[tier]} K = <b>${fmt(tierStats().find((r) => r.model === model)[`${ASPECT[tier]}_k`])}</b></p><table><thead><tr><th>Category</th><th>Detail Category</th><th class="num">K</th></tr></thead><tbody>`;
	for (const [unit, label] of units) {
		const value = matrix.get(unit);
		html += `<tr><td>${categoryByUnit.get(unit) || ""}</td><td title="${unit}">${label}</td>${finite(value)
			? `<td class="num cat-rank" data-model="${model}" data-tier="${tier}" data-unit="${unit}" title="View raw metric values">${fmt(value)}</td>`
			: `<td class="num">${fmt(value)}</td>`}</tr>`;
	}
	html += `</tbody></table><p class="note"><b>Click a K value</b> to view the raw metric values and ranks used in the calculation.</p>`;
	openDrawer(`${displayName(model)} · ${TIER_SHORT[tier]}`, html);
	closeDrawer2();
	$("detail-body").querySelectorAll(".cat-rank").forEach((td) => {
		td.onclick = () => showMetricDetail(td.dataset.model, td.dataset.tier, td.dataset.unit);
	});
}
function showMetricDetail(model, tier, unit) {
	if (!state.recordsReady) return;
	const rows = includedRecords()
		.filter((r) => r.model === model && r.tier === tier && r.unit === unit)
		.sort((a, b) => {
			const [aType, aTarget] = a.metric.replace(new RegExp(`^${metricBase(a.metric)}_?`), "").split("_");
			const [bType, bTarget] = b.metric.replace(new RegExp(`^${metricBase(b.metric)}_?`), "").split("_");
			return a.frequency.localeCompare(b.frequency)
				|| metricBase(a.metric).localeCompare(metricBase(b.metric))
				|| horizonOrder(aType) - horizonOrder(bType)
				|| horizonOrder(aTarget) - horizonOrder(bTarget)
				|| a.metric.localeCompare(b.metric);
		});
	const label = displayDetailLabel(rows[0]?.unit_label || unit);
	const k = geometricMean(rows.map((r) => r.rank));
	let html = `<p class="detail-k">${TIER_SHORT[tier]} · ${label} K = <b>${fmt(k)}</b> · ${rows.length.toLocaleString()} cells</p>`;
	html += `<div class="period-card"><h3>Description for Date Codes</h3><div class="period-help"><span><b>D</b> Day</span><span><b>W</b> Week</span><span><b>M</b> Month</span><span><b>Q</b> Quarter</span><span><b>H</b> Half-year</span><span><b>Y</b> Year</span><span><b>B</b> Business</span><span><b>BD</b> Business-day</span></div></div>`;
	html += `<table><thead><tr><th>Freq</th><th>Metrics</th><th>Target Type</th><th>Pred. Target</th><th class="num">Value</th><th class="num">Rank</th></tr></thead><tbody>`;
	for (const row of rows) {
		const parts = parseMetricParts(row.metric);
		const direction = metricDirection(row.metric);
		const total = state.records.filter((candidate) => {
			return candidate.tier === row.tier
				&& candidate.detail_category === row.detail_category
				&& candidate.frequency === row.frequency
				&& candidate.series_id === row.series_id
				&& candidate.metric === row.metric;
		}).length;
		html += `<tr><td title="${row.frequency}">${freqCode(row.frequency)}</td><td title="${row.metric}">${parts.name} <span class="dir-arrow ${direction > 0 ? "up" : "down"}">${direction > 0 ? "↑" : "↓"}</span></td><td>${parts.type}</td><td>${parts.target}</td><td class="num">${fmtValue(row.value)}</td><td class="num">${rankText(row.rank, total)}</td></tr>`;
	}
	html += `</tbody></table><p class="note">↑ means higher is better, and ↓ means lower is better. Rank is shown as rank/total within the same tier · Detail Category · Frequency · metric comparison unit.</p>`;
	$("detail2-title").textContent = `${label} · ${TIER_SHORT[tier]}`;
	$("detail2-body").innerHTML = html;
	$("detail2").hidden = false;
	$("detail").classList.add("shifted");
	$("drawer-scrim").hidden = false;
}
function openDrawer(title, html) {
	$("detail-title").textContent = title;
	$("detail-body").innerHTML = html;
	$("detail").classList.remove("shifted");
	$("detail").hidden = false;
	$("drawer-scrim").hidden = false;
}
function closeDrawer2() {
	const drawer = $("detail2");
	if (!drawer) return;
	drawer.hidden = true;
	$("detail").classList.remove("shifted");
}
function closeDrawer() {
	closeDrawer2();
	$("detail").hidden = true;
	$("drawer-scrim").hidden = true;
}
function render() {
	syncSidebarForView();
	if (state.view === "overview") renderOverview();
	if (state.view === "overview-plot") renderOverviewPlotPage();
	if (state.view === "metrics") renderMetrics();
	if (state.view === "strength") renderStrength();
	if (state.view === "leaderboard-guide") renderLeaderboardGuide();
}
async function boot() {
	state.data = await fetch("leaderboard_data.json", { cache: "no-store" }).then((response) => response.json());
	state.models = (state.data.overall || []).map((row) => row.model).sort();
	const configEntries = await Promise.all((state.data.result_files || []).map((item) => {
		const path = item.config_path || item.path.replace(/result\.json$/, "config.json");
		return fetch(path, { cache: "no-store" })
			.then((response) => response.ok ? response.json() : null)
			.then((config) => [item.model, config || { model: item.model, model_type: "unknown" }]);
	}));
	state.modelConfigs = new Map(configEntries);
	$("model-count").textContent = `${state.models.length} models`;
	$("updated").textContent = `updated ${formatAoeDate(state.data.generated_at)}`;
	$("detail-close").onclick = closeDrawer;
	$("detail2-close").onclick = closeDrawer2;
	$("drawer-scrim").onclick = closeDrawer;
	$("detail-rank-toggle").onchange = (event) => {
		state.showDetailRank = event.target.checked;
		renderOverview();
	};
	$("model-info-toggle").onchange = (event) => {
		state.showModelInfo = event.target.checked;
		render();
	};
	$("metrics-model-info-toggle").onchange = (event) => {
		state.showModelInfo = event.target.checked;
		render();
	};
	setupTabs();
	setupCollapsibles();
	buildSpecToggles();
	render();

	const columns = state.data.rank_record_columns || [];
	let chunks = [];
	if (state.data.result_files?.length) {
		const results = await Promise.all(state.data.result_files.map((item) => fetch(item.path, { cache: "no-store" }).then((response) => response.json())));
		chunks = results.flatMap((result) => result.records || []);
	} else if (state.data.result_index) {
		const index = await fetch(state.data.result_index, { cache: "no-store" }).then((response) => response.json());
		const results = await Promise.all(index.models.map((item) => fetch(item.path, { cache: "no-store" }).then((response) => response.json())));
		chunks = results.flatMap((result) => result.records || []);
	} else {
		chunks = state.data.rank_record_files
			? (await Promise.all(state.data.rank_record_files.map((file) => fetch(file, { cache: "no-store" }).then((response) => response.json())))).flat()
			: (state.data.rank_records || []);
	}
	state.records = chunks.map((row) => Array.isArray(row)
		? Object.fromEntries(columns.map((column, index) => [column, row[index]]))
		: row);
	state.models = [...new Set(state.records.map((r) => r.model))].sort();
	state.recordsReady = true;
	$("model-count").textContent = `${state.models.length} models`;
	buildSpecToggles();
	render();
}
boot();