pierjoe commited on
Commit
cfb29dd
Β·
1 Parent(s): 8d727ea

Switch to static HTML + Plotly.js dashboard

Browse files

- Replace Gradio with a pure HTML/CSS/JS dashboard (sdk: static)
- Plotly.js CDN for all interactive charts
- Custom Inter-based design with full color control
- Loads pre-computed dashboard_data.json at runtime
- 4 tabs: Pipeline, Compensation, Top Earners, Data Quality

Files changed (3) hide show
  1. README.md +17 -9
  2. dashboard_data.json +697 -0
  3. index.html +611 -0
README.md CHANGED
@@ -1,14 +1,22 @@
1
  ---
2
- title: Execcomp AI Dashboard
3
- emoji: πŸ’»
4
  colorFrom: blue
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 6.8.0
8
- app_file: app.py
9
- pinned: false
10
  license: mit
11
- short_description: dashboard showing data from pierjoe/execcomp-ai dataset
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
1
  ---
2
+ title: Execcomp-AI Dashboard
3
+ emoji: πŸ“Š
4
  colorFrom: blue
5
+ colorTo: green
6
+ sdk: static
7
+ pinned: true
 
 
8
  license: mit
9
+ short_description: AI-extracted SEC executive compensation analytics
10
+ tags:
11
+ - finance
12
+ - sec
13
+ - executive-compensation
14
+ - analytics
15
  ---
16
 
17
+ # Execcomp-AI Dashboard
18
+
19
+ Interactive dashboard for exploring executive compensation data extracted from **100K+ SEC DEF 14A** proxy statements (2005–2022) using vision-language models.
20
+
21
+ - **Dataset**: [pierjoe/execcomp-ai-sample](https://huggingface.co/datasets/pierjoe/execcomp-ai-sample)
22
+ - **GitHub**: [pierpierpy/Execcomp-AI](https://github.com/pierpierpy/Execcomp-AI)
dashboard_data.json ADDED
@@ -0,0 +1,697 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-03-05T13:42:51.454903",
3
+ "pipeline": {
4
+ "total_docs": 83020,
5
+ "funds": 15540,
6
+ "with_sct": 44555,
7
+ "no_sct": 3119,
8
+ "pending": 19806,
9
+ "total_tables": 55446,
10
+ "multi_table_docs": 10891,
11
+ "non_funds": 67480
12
+ },
13
+ "compensation": {
14
+ "total_exec_records": 1254296,
15
+ "unique_companies": 7696,
16
+ "year_range": [
17
+ 2005,
18
+ 2022
19
+ ],
20
+ "mean_total": 2456896.521673243,
21
+ "median_total": 1153750.0,
22
+ "max_total": 2284044884.0,
23
+ "mean_salary": 2860569.116454856,
24
+ "median_salary": 350000.0,
25
+ "breakdown": {
26
+ "salary": {
27
+ "mean": 2860569.116454856,
28
+ "median": 350000.0,
29
+ "max": 522917979305.0
30
+ },
31
+ "bonus": {
32
+ "mean": 113074.67574370805,
33
+ "median": 0.0,
34
+ "max": 75000000.0
35
+ },
36
+ "stock_awards": {
37
+ "mean": 919397.2323141032,
38
+ "median": 150240.0,
39
+ "max": 413369623.0
40
+ },
41
+ "option_awards": {
42
+ "mean": 349692.59025496925,
43
+ "median": 0.0,
44
+ "max": 2283988504.0
45
+ },
46
+ "non_equity_incentive": {
47
+ "mean": 369550.0949133218,
48
+ "median": 64000.0,
49
+ "max": 71237675.0
50
+ },
51
+ "change_in_pension": {
52
+ "mean": 72289.63039287206,
53
+ "median": 0.0,
54
+ "max": 45422412.0
55
+ },
56
+ "other_compensation": {
57
+ "mean": 116803.38838251916,
58
+ "median": 19253.0,
59
+ "max": 4412354000.0
60
+ },
61
+ "total": {
62
+ "mean": 2456896.521673243,
63
+ "median": 1153750.0,
64
+ "max": 2284044884.0
65
+ }
66
+ }
67
+ },
68
+ "top10": [
69
+ {
70
+ "name": "Elon Musk",
71
+ "company": "Tesla, Inc.",
72
+ "title": "Chief Executive Officer",
73
+ "fiscal_year": 2018,
74
+ "total": 2284044884.0
75
+ },
76
+ {
77
+ "name": "Alexander Karp",
78
+ "company": "Palantir Technologies Inc.",
79
+ "title": "Chief Executive Officer",
80
+ "fiscal_year": 2020,
81
+ "total": 1098513297.0
82
+ },
83
+ {
84
+ "name": "Eric M. Green",
85
+ "company": "WEST PHARMACEUTICAL SERVICES INC",
86
+ "title": "President & Chief Executive Officer",
87
+ "fiscal_year": 2016,
88
+ "total": 641423648.127
89
+ },
90
+ {
91
+ "name": "Eric M. Green",
92
+ "company": "WEST PHARMACEUTICAL SERVICES INC",
93
+ "title": "President & Chief Executive Officer",
94
+ "fiscal_year": 2017,
95
+ "total": 611724756.563
96
+ },
97
+ {
98
+ "name": "Alan N. Forman",
99
+ "company": "STR HOLDINGS, INC.",
100
+ "title": "Senior Vice President, General Counsel and Secretary",
101
+ "fiscal_year": 2010,
102
+ "total": 577181228.0
103
+ },
104
+ {
105
+ "name": "Max Levchin",
106
+ "company": "Affirm Holdings, Inc.",
107
+ "title": "Chief Executive Officer",
108
+ "fiscal_year": 2021,
109
+ "total": 451207726.0
110
+ },
111
+ {
112
+ "name": "Eric M. Green",
113
+ "company": "WEST PHARMACEUTICAL SERVICES INC",
114
+ "title": "President & Chief Executive Officer",
115
+ "fiscal_year": 2018,
116
+ "total": 426215378.813
117
+ },
118
+ {
119
+ "name": "Tony Xu",
120
+ "company": "DoorDash, Inc.",
121
+ "title": "Chief Executive Officer and Chair",
122
+ "fiscal_year": 2020,
123
+ "total": 413669920.0
124
+ },
125
+ {
126
+ "name": "Timothy Cook",
127
+ "company": "APPLE INC",
128
+ "title": "Chief Executive Officer",
129
+ "fiscal_year": 2011,
130
+ "total": 377996537.0
131
+ },
132
+ {
133
+ "name": "Robert Antokol",
134
+ "company": "Playtika Holding Corp.",
135
+ "title": "Chief Executive Officer",
136
+ "fiscal_year": 2020,
137
+ "total": 372008176.0
138
+ }
139
+ ],
140
+ "tables_by_year": {
141
+ "2005": 1872,
142
+ "2006": 1968,
143
+ "2007": 2048,
144
+ "2008": 1944,
145
+ "2009": 1888,
146
+ "2010": 1716,
147
+ "2011": 1736,
148
+ "2012": 6771,
149
+ "2013": 6531,
150
+ "2014": 6646,
151
+ "2015": 6676,
152
+ "2016": 11192,
153
+ "2017": 10816,
154
+ "2018": 10452,
155
+ "2019": 15176,
156
+ "2020": 19468,
157
+ "2021": 15750,
158
+ "2022": 1544
159
+ },
160
+ "trends": [
161
+ {
162
+ "year": 2005,
163
+ "mean": 1176120.5818593563,
164
+ "median": 378068.0,
165
+ "count": 1678
166
+ },
167
+ {
168
+ "year": 2006,
169
+ "mean": 1538449.196346523,
170
+ "median": 571105.0,
171
+ "count": 16149
172
+ },
173
+ {
174
+ "year": 2007,
175
+ "mean": 1501451.7845541562,
176
+ "median": 593075.0,
177
+ "count": 19850
178
+ },
179
+ {
180
+ "year": 2008,
181
+ "mean": 1556407.9691994055,
182
+ "median": 641000.0,
183
+ "count": 18836
184
+ },
185
+ {
186
+ "year": 2009,
187
+ "mean": 1501001.4062882576,
188
+ "median": 687160.0,
189
+ "count": 29994
190
+ },
191
+ {
192
+ "year": 2010,
193
+ "mean": 1784004.6605899297,
194
+ "median": 800000.0,
195
+ "count": 46175
196
+ },
197
+ {
198
+ "year": 2011,
199
+ "mean": 1907676.4098720532,
200
+ "median": 854464.0,
201
+ "count": 65027
202
+ },
203
+ {
204
+ "year": 2012,
205
+ "mean": 2014388.0489255064,
206
+ "median": 922869.0,
207
+ "count": 68395
208
+ },
209
+ {
210
+ "year": 2013,
211
+ "mean": 2102510.0059140464,
212
+ "median": 999010.0,
213
+ "count": 77693
214
+ },
215
+ {
216
+ "year": 2014,
217
+ "mean": 2404274.576444018,
218
+ "median": 1139784.0,
219
+ "count": 91280
220
+ },
221
+ {
222
+ "year": 2015,
223
+ "mean": 2351306.0412694626,
224
+ "median": 1149955.0,
225
+ "count": 106423
226
+ },
227
+ {
228
+ "year": 2016,
229
+ "mean": 2415104.6348501956,
230
+ "median": 1225273.0,
231
+ "count": 114843
232
+ },
233
+ {
234
+ "year": 2017,
235
+ "mean": 2656081.7441396704,
236
+ "median": 1353473.0,
237
+ "count": 133450
238
+ },
239
+ {
240
+ "year": 2018,
241
+ "mean": 2943145.039610786,
242
+ "median": 1420392.0,
243
+ "count": 154442
244
+ },
245
+ {
246
+ "year": 2019,
247
+ "mean": 2809051.7051841086,
248
+ "median": 1410006.0,
249
+ "count": 135871
250
+ },
251
+ {
252
+ "year": 2020,
253
+ "mean": 3169163.9675259963,
254
+ "median": 1474761.0,
255
+ "count": 76934
256
+ },
257
+ {
258
+ "year": 2021,
259
+ "mean": 3639795.761156471,
260
+ "median": 1563576.5,
261
+ "count": 16412
262
+ },
263
+ {
264
+ "year": 2022,
265
+ "mean": 4849358.698901099,
266
+ "median": 1460000.0,
267
+ "count": 1365
268
+ }
269
+ ],
270
+ "distribution": {
271
+ "values": [
272
+ 222170,
273
+ 191104,
274
+ 143572,
275
+ 106907,
276
+ 79511,
277
+ 61439,
278
+ 47401,
279
+ 38199,
280
+ 32203,
281
+ 25248,
282
+ 22382,
283
+ 18732,
284
+ 16406,
285
+ 13647,
286
+ 11475,
287
+ 10104,
288
+ 8838,
289
+ 8089,
290
+ 7242,
291
+ 6227,
292
+ 5575,
293
+ 4776,
294
+ 5140,
295
+ 4422,
296
+ 3831,
297
+ 3326,
298
+ 3190,
299
+ 2585,
300
+ 2595,
301
+ 2300,
302
+ 2292,
303
+ 2312,
304
+ 1886,
305
+ 1585,
306
+ 1517,
307
+ 1478,
308
+ 1376,
309
+ 1358,
310
+ 1193,
311
+ 1090,
312
+ 1053,
313
+ 998,
314
+ 795,
315
+ 945,
316
+ 942,
317
+ 676,
318
+ 670,
319
+ 657,
320
+ 776,
321
+ 666
322
+ ],
323
+ "edges": [
324
+ 1e-06,
325
+ 0.38450903999999997,
326
+ 0.76901708,
327
+ 1.1535251199999998,
328
+ 1.53803316,
329
+ 1.9225412,
330
+ 2.30704924,
331
+ 2.69155728,
332
+ 3.07606532,
333
+ 3.46057336,
334
+ 3.8450814,
335
+ 4.22958944,
336
+ 4.61409748,
337
+ 4.99860552,
338
+ 5.38311356,
339
+ 5.7676216,
340
+ 6.15212964,
341
+ 6.53663768,
342
+ 6.92114572,
343
+ 7.30565376,
344
+ 7.6901618,
345
+ 8.074669839999999,
346
+ 8.459177879999999,
347
+ 8.843685919999999,
348
+ 9.228193959999999,
349
+ 9.612701999999999,
350
+ 9.997210039999999,
351
+ 10.381718079999999,
352
+ 10.766226119999999,
353
+ 11.150734159999999,
354
+ 11.535242199999999,
355
+ 11.919750239999999,
356
+ 12.304258279999999,
357
+ 12.68876632,
358
+ 13.07327436,
359
+ 13.4577824,
360
+ 13.84229044,
361
+ 14.22679848,
362
+ 14.61130652,
363
+ 14.99581456,
364
+ 15.3803226,
365
+ 15.76483064,
366
+ 16.14933868,
367
+ 16.53384672,
368
+ 16.91835476,
369
+ 17.3028628,
370
+ 17.68737084,
371
+ 18.07187888,
372
+ 18.45638692,
373
+ 18.84089496,
374
+ 19.225403
375
+ ],
376
+ "median": 1201612.0,
377
+ "p99": 19225403.0,
378
+ "n_outliers": 11439
379
+ },
380
+ "comp_components": {
381
+ "Salary": 2860569.116454856,
382
+ "Bonus": 113074.67574370805,
383
+ "Stock Awards": 919397.2323141032,
384
+ "Option Awards": 349692.59025496925,
385
+ "Non Equity Incentive": 369550.0949133218,
386
+ "Change In Pension": 72289.63039287206,
387
+ "Other Compensation": 116803.38838251916
388
+ },
389
+ "probability": {
390
+ "total_tables": 124194,
391
+ "unique_docs": 44087,
392
+ "high_confidence": 107276,
393
+ "medium_confidence": 4871,
394
+ "low_confidence": 12047,
395
+ "multi_table_docs": 31780,
396
+ "could_disambiguate": 1740,
397
+ "hist_values": [
398
+ 7524,
399
+ 1514,
400
+ 974,
401
+ 778,
402
+ 645,
403
+ 613,
404
+ 596,
405
+ 523,
406
+ 538,
407
+ 594,
408
+ 569,
409
+ 611,
410
+ 681,
411
+ 760,
412
+ 906,
413
+ 1085,
414
+ 1340,
415
+ 2165,
416
+ 5444,
417
+ 96334
418
+ ],
419
+ "hist_edges": [
420
+ 0.0,
421
+ 0.05,
422
+ 0.1,
423
+ 0.15000000000000002,
424
+ 0.2,
425
+ 0.25,
426
+ 0.30000000000000004,
427
+ 0.35000000000000003,
428
+ 0.4,
429
+ 0.45,
430
+ 0.5,
431
+ 0.55,
432
+ 0.6000000000000001,
433
+ 0.65,
434
+ 0.7000000000000001,
435
+ 0.75,
436
+ 0.8,
437
+ 0.8500000000000001,
438
+ 0.9,
439
+ 0.9500000000000001,
440
+ 1.0
441
+ ]
442
+ },
443
+ "comp_trends": {
444
+ "Salary": {
445
+ "years": [
446
+ 2005,
447
+ 2006,
448
+ 2007,
449
+ 2008,
450
+ 2009,
451
+ 2010,
452
+ 2011,
453
+ 2012,
454
+ 2013,
455
+ 2014,
456
+ 2015,
457
+ 2016,
458
+ 2017,
459
+ 2018,
460
+ 2019,
461
+ 2020,
462
+ 2021,
463
+ 2022
464
+ ],
465
+ "values": [
466
+ 289673.6112024149,
467
+ 305504.0500901093,
468
+ 307699.87467316433,
469
+ 332849.9686060666,
470
+ 341661.36546020804,
471
+ 348770.9186022568,
472
+ 359306.5973609728,
473
+ 371527.72918842104,
474
+ 20020935.132538397,
475
+ 394378.4618114263,
476
+ 402812.109019359,
477
+ 413050.0089484407,
478
+ 428295.94908233127,
479
+ 443570.0813739475,
480
+ 444032.7906676899,
481
+ 18061986.906439733,
482
+ 426821.5507361153,
483
+ 454383.66282595514
484
+ ]
485
+ },
486
+ "Stock Awards": {
487
+ "years": [
488
+ 2005,
489
+ 2006,
490
+ 2007,
491
+ 2008,
492
+ 2009,
493
+ 2010,
494
+ 2011,
495
+ 2012,
496
+ 2013,
497
+ 2014,
498
+ 2015,
499
+ 2016,
500
+ 2017,
501
+ 2018,
502
+ 2019,
503
+ 2020,
504
+ 2021,
505
+ 2022
506
+ ],
507
+ "values": [
508
+ 257462.71443904075,
509
+ 364568.34609437484,
510
+ 342450.6945785108,
511
+ 390181.7837795042,
512
+ 384219.30548898096,
513
+ 522731.2941396919,
514
+ 605310.0505190449,
515
+ 649519.4363632834,
516
+ 728084.5619057884,
517
+ 877392.6667976497,
518
+ 881095.1483580609,
519
+ 927534.8911650542,
520
+ 1038827.452705864,
521
+ 1139805.9324857604,
522
+ 1158856.0993382828,
523
+ 1385513.943306771,
524
+ 1549922.4096253426,
525
+ 2755373.3559733173
526
+ ]
527
+ },
528
+ "Option Awards": {
529
+ "years": [
530
+ 2005,
531
+ 2006,
532
+ 2007,
533
+ 2008,
534
+ 2009,
535
+ 2010,
536
+ 2011,
537
+ 2012,
538
+ 2013,
539
+ 2014,
540
+ 2015,
541
+ 2016,
542
+ 2017,
543
+ 2018,
544
+ 2019,
545
+ 2020,
546
+ 2021,
547
+ 2022
548
+ ],
549
+ "values": [
550
+ 16318.93467717592,
551
+ 257332.03556384964,
552
+ 265285.73440794717,
553
+ 317073.202879905,
554
+ 249125.46930154617,
555
+ 278463.44663207314,
556
+ 299135.2373469089,
557
+ 287231.7414773121,
558
+ 310626.94743698405,
559
+ 331609.19159934873,
560
+ 325731.7834224121,
561
+ 291325.8242040927,
562
+ 348249.16444862855,
563
+ 548477.4402891283,
564
+ 340850.63640291966,
565
+ 420958.8966968912,
566
+ 405147.68930855923,
567
+ 195372.2425712553
568
+ ]
569
+ },
570
+ "Bonus": {
571
+ "years": [
572
+ 2005,
573
+ 2006,
574
+ 2007,
575
+ 2008,
576
+ 2009,
577
+ 2010,
578
+ 2011,
579
+ 2012,
580
+ 2013,
581
+ 2014,
582
+ 2015,
583
+ 2016,
584
+ 2017,
585
+ 2018,
586
+ 2019,
587
+ 2020,
588
+ 2021,
589
+ 2022
590
+ ],
591
+ "values": [
592
+ 299365.65621331544,
593
+ 150773.79824260197,
594
+ 110241.73782859284,
595
+ 89937.37057746548,
596
+ 89609.6421886363,
597
+ 103225.03954966641,
598
+ 99521.490222678,
599
+ 109648.79798117315,
600
+ 108351.15517967811,
601
+ 109099.96811624606,
602
+ 104108.63122744579,
603
+ 104852.4843184124,
604
+ 112945.94641519392,
605
+ 119220.22958826151,
606
+ 115899.47764120871,
607
+ 130050.21036050007,
608
+ 114257.56031069144,
609
+ 71154.22316555488
610
+ ]
611
+ },
612
+ "Non Equity Incentive": {
613
+ "years": [
614
+ 2005,
615
+ 2006,
616
+ 2007,
617
+ 2008,
618
+ 2009,
619
+ 2010,
620
+ 2011,
621
+ 2012,
622
+ 2013,
623
+ 2014,
624
+ 2015,
625
+ 2016,
626
+ 2017,
627
+ 2018,
628
+ 2019,
629
+ 2020,
630
+ 2021,
631
+ 2022
632
+ ],
633
+ "values": [
634
+ 100987.96521884958,
635
+ 226093.58863663024,
636
+ 223757.90633053746,
637
+ 190112.85877579296,
638
+ 226851.8758529105,
639
+ 306079.17200107075,
640
+ 301605.83027483156,
641
+ 303914.47658302257,
642
+ 346899.14006393455,
643
+ 378831.5632987873,
644
+ 375650.6928487031,
645
+ 386651.91694355774,
646
+ 426414.4797439628,
647
+ 446975.6366412828,
648
+ 403280.6740157929,
649
+ 377522.98870126926,
650
+ 453055.45552340336,
651
+ 462825.1728320194
652
+ ]
653
+ },
654
+ "Other Compensation": {
655
+ "years": [
656
+ 2005,
657
+ 2006,
658
+ 2007,
659
+ 2008,
660
+ 2009,
661
+ 2010,
662
+ 2011,
663
+ 2012,
664
+ 2013,
665
+ 2014,
666
+ 2015,
667
+ 2016,
668
+ 2017,
669
+ 2018,
670
+ 2019,
671
+ 2020,
672
+ 2021,
673
+ 2022
674
+ ],
675
+ "values": [
676
+ 81183.52423612276,
677
+ 86783.46192695282,
678
+ 90989.94723771144,
679
+ 81866.98751645307,
680
+ 76377.20525816549,
681
+ 84142.2375564204,
682
+ 159989.03703047175,
683
+ 100654.2935234504,
684
+ 106955.45783296181,
685
+ 104887.59357985263,
686
+ 118675.87858710029,
687
+ 108747.43084123096,
688
+ 124479.23725355529,
689
+ 121496.66114976474,
690
+ 131167.45834045656,
691
+ 149410.89613852173,
692
+ 90833.88190171591,
693
+ 65212.55548817465
694
+ ]
695
+ }
696
+ }
697
+ }
index.html ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Execcomp-AI Dashboard</title>
7
+ <script src="https://cdn.plot.ly/plotly-2.35.0.min.js"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
10
+ <style>
11
+ /* ── Reset & Base ────────────────────────────────────── */
12
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
13
+ html { scroll-behavior: smooth; }
14
+ body {
15
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
16
+ background: #F8FAFC;
17
+ color: #1E293B;
18
+ line-height: 1.6;
19
+ -webkit-font-smoothing: antialiased;
20
+ }
21
+ a { color: #2563EB; text-decoration: none; }
22
+ a:hover { text-decoration: underline; }
23
+
24
+ /* ── Layout ──────────────────────────────────────────── */
25
+ .container { max-width: 1140px; margin: 0 auto; padding: 0 24px; }
26
+
27
+ /* ── Header ──────────────────────────────────────────── */
28
+ .hero {
29
+ background: #FFFFFF;
30
+ border-bottom: 1px solid #E2E8F0;
31
+ padding: 48px 0 40px;
32
+ margin-bottom: 32px;
33
+ }
34
+ .hero-inner { text-align: center; }
35
+ .hero h1 {
36
+ font-size: 2.5rem;
37
+ font-weight: 800;
38
+ letter-spacing: -0.03em;
39
+ color: #0F172A;
40
+ margin-bottom: 12px;
41
+ }
42
+ .hero p {
43
+ font-size: 1.05rem;
44
+ color: #64748B;
45
+ max-width: 620px;
46
+ margin: 0 auto 24px;
47
+ }
48
+ .badge-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
49
+ .badge {
50
+ display: inline-flex; align-items: center; gap: 6px;
51
+ background: #F1F5F9; border: 1px solid #E2E8F0;
52
+ color: #475569; padding: 8px 18px; border-radius: 99px;
53
+ font-size: 13px; font-weight: 600; transition: all .15s;
54
+ }
55
+ .badge:hover { background: #E2E8F0; color: #1E293B; text-decoration: none; }
56
+ .badge.accent { background: #EFF6FF; border-color: #BFDBFE; color: #1D4ED8; }
57
+
58
+ /* ── Tabs ────────────────────────────────────────────── */
59
+ .tabs { display: flex; gap: 4px; background: #FFFFFF; border: 1px solid #E2E8F0; border-radius: 12px; padding: 4px; margin-bottom: 28px; }
60
+ .tab-btn {
61
+ flex: 1; padding: 12px 8px; border: none; background: transparent;
62
+ font: 600 14px/1 'Inter', sans-serif; color: #64748B;
63
+ border-radius: 8px; cursor: pointer; transition: all .15s;
64
+ white-space: nowrap;
65
+ }
66
+ .tab-btn:hover { color: #1E293B; background: #F8FAFC; }
67
+ .tab-btn.active { background: #2563EB; color: #FFFFFF; box-shadow: 0 1px 3px rgba(37,99,235,.3); }
68
+ .tab-panel { display: none; }
69
+ .tab-panel.active { display: block; }
70
+
71
+ /* ── Cards & KPIs ────────────────────────────────────── */
72
+ .card {
73
+ background: #FFFFFF;
74
+ border: 1px solid #E2E8F0;
75
+ border-radius: 14px;
76
+ padding: 24px;
77
+ margin-bottom: 20px;
78
+ }
79
+ .card-title { font-size: 1rem; font-weight: 700; color: #0F172A; margin-bottom: 16px; }
80
+
81
+ .kpi-grid {
82
+ display: grid;
83
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
84
+ gap: 14px; margin-bottom: 24px;
85
+ }
86
+ .kpi {
87
+ background: #FFFFFF;
88
+ border: 1px solid #E2E8F0;
89
+ border-radius: 14px;
90
+ padding: 20px 16px;
91
+ text-align: center;
92
+ border-top: 4px solid var(--accent, #2563EB);
93
+ transition: transform .15s, box-shadow .15s;
94
+ }
95
+ .kpi:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,.06); }
96
+ .kpi-icon { font-size: 24px; margin-bottom: 6px; }
97
+ .kpi-val { font-size: 1.4rem; font-weight: 800; color: #0F172A; letter-spacing: -0.02em; }
98
+ .kpi-label {
99
+ font-size: 11px; font-weight: 600; color: #94A3B8;
100
+ text-transform: uppercase; letter-spacing: 0.05em; margin-top: 4px;
101
+ }
102
+
103
+ /* ── Chart container ─────────────────────────────────── */
104
+ .chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
105
+ .chart-full { margin-bottom: 20px; }
106
+ @media (max-width: 768px) { .chart-row { grid-template-columns: 1fr; } }
107
+
108
+ /* ── Tables ──────────────────────────────────────────── */
109
+ .data-table {
110
+ width: 100%; border-collapse: collapse; font-size: 14px;
111
+ background: #FFFFFF; border-radius: 12px; overflow: hidden;
112
+ border: 1px solid #E2E8F0;
113
+ }
114
+ .data-table th {
115
+ background: #F8FAFC; color: #64748B; font-weight: 600;
116
+ padding: 14px 18px; text-align: left;
117
+ border-bottom: 2px solid #E2E8F0;
118
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
119
+ }
120
+ .data-table td {
121
+ padding: 14px 18px; border-bottom: 1px solid #F1F5F9;
122
+ color: #334155; vertical-align: middle;
123
+ }
124
+ .data-table tbody tr:hover { background: #F8FAFC; }
125
+ .data-table .total-row { background: #F0FDF4; }
126
+ .data-table .total-row td { font-weight: 700; color: #15803D; border-top: 2px solid #BBF7D0; }
127
+
128
+ .pill {
129
+ display: inline-block;
130
+ background: #EFF6FF; color: #1D4ED8;
131
+ padding: 3px 12px; border-radius: 99px;
132
+ font-size: 12px; font-weight: 600;
133
+ }
134
+
135
+ /* ── Info boxes ──────────────────────────────────────── */
136
+ .info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px; margin-bottom: 20px; }
137
+ .info-stat {
138
+ background: #FFFFFF; border: 1px solid #E2E8F0;
139
+ border-radius: 12px; padding: 20px; text-align: center;
140
+ }
141
+ .info-stat.green { background: #F0FDF4; border-color: #BBF7D0; }
142
+ .info-stat.amber { background: #FFFBEB; border-color: #FDE68A; }
143
+ .info-stat-val { font-size: 1.6rem; font-weight: 800; color: #0F172A; }
144
+ .info-stat-label { font-size: 11px; font-weight: 600; color: #94A3B8; text-transform: uppercase; letter-spacing: .04em; margin-top: 4px; }
145
+ .info-stat.green .info-stat-val { color: #16A34A; }
146
+ .info-stat.amber .info-stat-val { color: #D97706; }
147
+
148
+ .tip-box {
149
+ background: #EFF6FF; border-left: 4px solid #2563EB;
150
+ border-radius: 0 10px 10px 0; padding: 16px 20px; font-size: 14px;
151
+ color: #1E40AF; margin-top: 16px;
152
+ }
153
+ .tip-box b { color: #1E3A5F; }
154
+
155
+ /* ── Pipeline info ───────────────────────────────────── */
156
+ .pipeline-flow {
157
+ background: #F1F5F9; border: 1px solid #E2E8F0; border-radius: 10px;
158
+ padding: 16px 20px; font-family: 'SF Mono', 'Fira Code', monospace;
159
+ font-size: 13px; color: #334155; margin-bottom: 20px;
160
+ overflow-x: auto; white-space: nowrap;
161
+ }
162
+ .steps-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
163
+ .steps-grid b { color: #0F172A; display: block; margin-bottom: 4px; }
164
+ .steps-grid span { color: #64748B; font-size: 13px; }
165
+ @media (max-width: 768px) { .steps-grid { grid-template-columns: 1fr; } }
166
+
167
+ /* ── Footer ──────────────────────────────────────────── */
168
+ .footer { text-align: center; color: #94A3B8; font-size: 12px; padding: 32px 0 48px; }
169
+
170
+ /* ── Plotly overrides ────────────────────────────────── */
171
+ .js-plotly-plot .plotly .main-svg { border-radius: 8px; }
172
+ </style>
173
+ </head>
174
+ <body>
175
+
176
+ <!-- ═══════════════════ HERO ═══════════════════ -->
177
+ <header class="hero">
178
+ <div class="container hero-inner">
179
+ <h1>πŸ“Š Execcomp-AI Dashboard</h1>
180
+ <p>AI-extracted executive compensation from <b id="h-total"></b> SEC DEF 14A proxy statements (2005–2022)</p>
181
+ <div class="badge-row">
182
+ <a href="https://github.com/pierpierpy/Execcomp-AI" target="_blank" class="badge">
183
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
184
+ GitHub
185
+ </a>
186
+ <a href="https://huggingface.co/datasets/pierjoe/execcomp-ai-sample" target="_blank" class="badge">πŸ€— Dataset</a>
187
+ <span class="badge accent">⚑ Qwen-VL-32B · MinerU</span>
188
+ </div>
189
+ </div>
190
+ </header>
191
+
192
+ <main class="container">
193
+
194
+ <!-- ═══════════════════ TABS ═══════════════════ -->
195
+ <div class="tabs" id="tab-bar">
196
+ <button class="tab-btn active" data-tab="pipeline">πŸ“ Pipeline</button>
197
+ <button class="tab-btn" data-tab="compensation">πŸ’° Compensation</button>
198
+ <button class="tab-btn" data-tab="top10">πŸ† Top Earners</button>
199
+ <button class="tab-btn" data-tab="quality">🎯 Data Quality</button>
200
+ </div>
201
+
202
+ <!-- ═══════════ TAB 1: Pipeline ═══════════ -->
203
+ <div class="tab-panel active" id="panel-pipeline">
204
+ <div class="kpi-grid" id="kpi-pipeline"></div>
205
+ <div class="chart-row">
206
+ <div class="card"><div id="chart-donut" style="width:100%;height:380px"></div></div>
207
+ <div class="card"><div id="chart-by-year" style="width:100%;height:380px"></div></div>
208
+ </div>
209
+ <div class="card">
210
+ <div class="card-title">βš™οΈ How the pipeline works</div>
211
+ <div class="pipeline-flow">SEC EDGAR β†’ PDF Download β†’ MinerU Extraction β†’ Qwen3-VL-32B Classification &amp; Parsing β†’ Qwen3-VL-4B Verification β†’ HF Dataset</div>
212
+ <div class="steps-grid">
213
+ <div><b>1 Β· Vision Extraction</b><span>MinerU converts PDFs to structured images preserving table layouts.</span></div>
214
+ <div><b>2 Β· Classification + Parsing</b><span>Qwen3-VL-32B identifies the Summary Compensation Table and parses it into typed JSON.</span></div>
215
+ <div><b>3 Β· Quality Filtering</b><span>Fine-tuned Qwen3-VL-4B assigns a confidence score (0–1) for each extracted table.</span></div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+
220
+ <!-- ═══════════ TAB 2: Compensation ═══════════ -->
221
+ <div class="tab-panel" id="panel-compensation">
222
+ <div class="kpi-grid" id="kpi-comp"></div>
223
+ <div class="card chart-full"><div id="chart-trends" style="width:100%;height:420px"></div></div>
224
+ <div class="chart-row">
225
+ <div class="card"><div id="chart-dist" style="width:100%;height:380px"></div></div>
226
+ <div class="card"><div id="chart-components" style="width:100%;height:380px"></div></div>
227
+ </div>
228
+ <div class="card chart-full"><div id="chart-comp-trends" style="width:100%;height:420px"></div></div>
229
+ <div class="card">
230
+ <div class="card-title">Compensation Breakdown</div>
231
+ <table class="data-table" id="table-breakdown"></table>
232
+ </div>
233
+ </div>
234
+
235
+ <!-- ═══════════ TAB 3: Top 10 ═══════════ -->
236
+ <div class="tab-panel" id="panel-top10">
237
+ <div class="card chart-full"><div id="chart-top10" style="width:100%;height:500px"></div></div>
238
+ <div class="card">
239
+ <table class="data-table" id="table-top10"></table>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- ═══════════ TAB 4: Data Quality ═══════════ -->
244
+ <div class="tab-panel" id="panel-quality">
245
+ <div class="kpi-grid" id="kpi-quality"></div>
246
+ <div class="chart-row">
247
+ <div class="card"><div id="chart-prob-hist" style="width:100%;height:380px"></div></div>
248
+ <div class="card"><div id="chart-prob-pie" style="width:100%;height:380px"></div></div>
249
+ </div>
250
+ <div class="card" id="disambig-card"></div>
251
+ </div>
252
+
253
+ </main>
254
+
255
+ <footer class="footer"><span id="footer-text"></span></footer>
256
+
257
+ <!-- ═══════════════════ DATA + LOGIC ═══════════════════ -->
258
+ <script>
259
+ // Data loaded from file
260
+ let D;
261
+
262
+ // ── Colors ──
263
+ const C = {
264
+ blue:'#2563EB', green:'#16A34A', amber:'#D97706', red:'#DC2626',
265
+ violet:'#7C3AED', teal:'#0D9488', slate:'#64748B', gray:'#94A3B8',
266
+ bg:'#FFFFFF', grid:'#E2E8F0', text:'#1E293B',
267
+ };
268
+ const COLORS = [C.blue, C.teal, C.green, C.violet, C.amber, C.red, '#0EA5E9', '#84CC16'];
269
+ const plotCfg = {displayModeBar: false, responsive: true};
270
+
271
+ function baseLayout(extra={}) {
272
+ return Object.assign({
273
+ font: {family:'Inter, system-ui, sans-serif', size:13, color:C.text},
274
+ paper_bgcolor: C.bg, plot_bgcolor: C.bg,
275
+ margin: {l:55, r:30, t:50, b:50},
276
+ hoverlabel: {bgcolor:'#fff', font:{size:13, family:'Inter'}, bordercolor:C.grid},
277
+ }, extra);
278
+ }
279
+
280
+ // ── Helpers ──
281
+ const fmt = n => n.toLocaleString('en-US');
282
+ const fmtM = n => '$' + (n/1e6).toFixed(1) + 'M';
283
+ const fmtM2 = n => '$' + (n/1e6).toFixed(2) + 'M';
284
+ const fmtK = n => '$' + (n/1e3).toFixed(0) + 'K';
285
+ const fmtVal = v => v < 1e6 ? fmtK(v) : fmtM2(v);
286
+
287
+ function makeKpi(container, items) {
288
+ const el = document.getElementById(container);
289
+ el.innerHTML = items.map(([icon,val,label,color]) => `
290
+ <div class="kpi" style="--accent:${color}">
291
+ <div class="kpi-icon">${icon}</div>
292
+ <div class="kpi-val">${val}</div>
293
+ <div class="kpi-label">${label}</div>
294
+ </div>
295
+ `).join('');
296
+ }
297
+
298
+ // ── Tab switching ──
299
+ document.getElementById('tab-bar').addEventListener('click', e => {
300
+ const btn = e.target.closest('.tab-btn');
301
+ if (!btn) return;
302
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
303
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
304
+ btn.classList.add('active');
305
+ document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
306
+ // Relayout all plotly charts in the newly visible panel for proper sizing
307
+ setTimeout(() => {
308
+ document.querySelectorAll('#panel-' + btn.dataset.tab + ' .js-plotly-plot').forEach(p => {
309
+ Plotly.Plots.resize(p);
310
+ });
311
+ }, 50);
312
+ });
313
+
314
+ // ── Load data and render ──
315
+ fetch('dashboard_data.json')
316
+ .then(r => r.json())
317
+ .then(data => {
318
+ D = data;
319
+ document.getElementById('h-total').textContent = fmt(D.pipeline.total_docs);
320
+ document.getElementById('footer-text').textContent =
321
+ `Indexed on ${D.generated_at.slice(0,10)} Β· Execcomp-AI Β· Pier Paolo Di Pasquale`;
322
+ renderPipeline();
323
+ renderCompensation();
324
+ renderTop10();
325
+ renderQuality();
326
+ });
327
+
328
+ // ═══════════════ 1. PIPELINE ═══════════════
329
+ function renderPipeline() {
330
+ const p = D.pipeline;
331
+ makeKpi('kpi-pipeline', [
332
+ ['πŸ“', fmt(p.total_docs), 'Total Filings', C.blue],
333
+ ['🏒', fmt(p.non_funds), 'Companies', C.teal],
334
+ ['βœ…', fmt(p.with_sct), 'SCT Found', C.green],
335
+ ['πŸ“‹', fmt(p.total_tables), 'SCT Tables', C.violet],
336
+ ['❌', fmt(p.no_sct), 'No SCT', C.red],
337
+ ['⏳', fmt(p.pending), 'Pending', C.amber],
338
+ ]);
339
+
340
+ // Donut
341
+ Plotly.newPlot('chart-donut', [{
342
+ type:'pie', hole:.55,
343
+ labels: ['With SCT','No SCT','Funds (skipped)','Pending'],
344
+ values: [p.with_sct, p.no_sct, p.funds, p.pending],
345
+ marker: { colors:[C.green, C.red, C.slate, C.amber], line:{color:'#fff',width:2} },
346
+ textinfo:'percent', hovertemplate:'<b>%{label}</b><br>%{value:,} docs<br>%{percent}<extra></extra>',
347
+ }], baseLayout({
348
+ title:{text:'Document Breakdown',font:{size:16},x:.5,xanchor:'center'},
349
+ legend:{orientation:'h',y:-.06,x:.5,xanchor:'center'},
350
+ margin:{l:20,r:20,t:55,b:60},
351
+ }), plotCfg);
352
+
353
+ // Bar by year
354
+ const years = Object.keys(D.tables_by_year).sort();
355
+ const counts = years.map(y => D.tables_by_year[y]);
356
+ Plotly.newPlot('chart-by-year', [{
357
+ type:'bar', x:years, y:counts,
358
+ marker:{color:C.blue, line:{color:'#fff',width:1}},
359
+ text:counts, textposition:'outside', textfont:{size:10, color:C.text},
360
+ hovertemplate:'<b>Year %{x}</b><br>Tables: %{y:,}<extra></extra>',
361
+ }], baseLayout({
362
+ title:{text:'SCT Tables by Filing Year',font:{size:16},x:.5,xanchor:'center'},
363
+ xaxis:{title:'Filing Year',tickangle:-45,gridcolor:C.grid,linecolor:C.grid},
364
+ yaxis:{title:'Tables',gridcolor:C.grid,linecolor:C.grid},
365
+ margin:{l:55,r:25,t:55,b:70},
366
+ }), plotCfg);
367
+ }
368
+
369
+ // ═══════════════ 2. COMPENSATION ═══════════════
370
+ function renderCompensation() {
371
+ const c = D.compensation;
372
+ makeKpi('kpi-comp', [
373
+ ['πŸ‘€', fmt(c.total_exec_records), 'Exec Records', C.blue],
374
+ ['🏒', fmt(c.unique_companies), 'Companies', C.teal],
375
+ ['πŸ’°', fmtM2(c.mean_total), 'Mean Comp', C.green],
376
+ ['πŸ“Š', fmtM2(c.median_total), 'Median Comp', C.violet],
377
+ ['πŸ†', fmtM(c.max_total), 'Max Comp', C.amber],
378
+ ]);
379
+
380
+ // Trends (dual axis)
381
+ const t = D.trends;
382
+ const tYears = t.map(r=>r.year);
383
+ const tMeans = t.map(r=>r.mean/1e6);
384
+ const tMedians = t.map(r=>r.median/1e6);
385
+ const tCounts = t.map(r=>r.count);
386
+
387
+ Plotly.newPlot('chart-trends', [
388
+ { type:'bar', x:tYears, y:tCounts, name:'Exec Count', yaxis:'y2',
389
+ marker:{color:C.grid}, opacity:.5, hoverinfo:'skip' },
390
+ { type:'scatter', mode:'lines+markers', x:tYears, y:tMeans, name:'Mean',
391
+ line:{color:C.blue,width:3}, marker:{size:8,color:'#fff',line:{color:C.blue,width:2}},
392
+ hovertemplate:'<b>FY %{x}</b><br>Mean: $%{y:.2f}M<extra></extra>' },
393
+ { type:'scatter', mode:'lines+markers', x:tYears, y:tMedians, name:'Median',
394
+ line:{color:C.amber,width:3,dash:'dot'}, marker:{size:8,color:'#fff',line:{color:C.amber,width:2}},
395
+ hovertemplate:'<b>FY %{x}</b><br>Median: $%{y:.2f}M<extra></extra>' },
396
+ ], baseLayout({
397
+ title:{text:'Total Compensation Trends (2005–2022)',font:{size:16},x:.5,xanchor:'center'},
398
+ xaxis:{title:'Fiscal Year',gridcolor:C.grid,linecolor:C.grid},
399
+ yaxis:{title:'Total Comp ($M)',gridcolor:C.grid,linecolor:C.grid},
400
+ yaxis2:{overlaying:'y',side:'right',showgrid:false,showticklabels:false},
401
+ legend:{orientation:'h',y:1.08,x:.5,xanchor:'center'},
402
+ margin:{l:60,r:40,t:60,b:55},
403
+ }), plotCfg);
404
+
405
+ // Distribution
406
+ const d = D.distribution;
407
+ const mids=[],widths=[];
408
+ for(let i=0;i<d.values.length;i++){
409
+ mids.push((d.edges[i]+d.edges[i+1])/2);
410
+ widths.push(d.edges[i+1]-d.edges[i]);
411
+ }
412
+ Plotly.newPlot('chart-dist', [{
413
+ type:'bar', x:mids, y:d.values, width:widths,
414
+ marker:{color:C.teal,line:{width:0}}, opacity:.85,
415
+ hovertemplate:'<b>$%{x:.1f}M range</b><br>Executives: %{y:,}<extra></extra>',
416
+ }], baseLayout({
417
+ title:{text:`Distribution (≀99th pctl, ${fmt(d.n_outliers)} outliers excluded)`,font:{size:15},x:.5,xanchor:'center'},
418
+ xaxis:{title:'Total Compensation ($M)',gridcolor:C.grid,linecolor:C.grid},
419
+ yaxis:{title:'Executives',gridcolor:C.grid,linecolor:C.grid},
420
+ shapes:[{type:'line',x0:d.median/1e6,x1:d.median/1e6,y0:0,y1:1,yref:'paper',
421
+ line:{color:C.red,width:2,dash:'dash'}}],
422
+ annotations:[{x:d.median/1e6,y:1,yref:'paper',text:'Median $'+((d.median/1e6).toFixed(1))+'M',
423
+ showarrow:false,font:{color:C.red,size:12},xanchor:'left',xshift:6}],
424
+ margin:{l:60,r:25,t:55,b:55},
425
+ }), plotCfg);
426
+
427
+ // Components bar
428
+ const cc = D.comp_components;
429
+ const sorted = Object.entries(cc).sort((a,b)=>a[1]-b[1]);
430
+ Plotly.newPlot('chart-components', [{
431
+ type:'bar', orientation:'h',
432
+ y:sorted.map(([k])=>k), x:sorted.map(([,v])=>v/1e6),
433
+ marker:{color:COLORS.slice(0,sorted.length), line:{width:0}},
434
+ text:sorted.map(([,v])=>'$'+(v/1e6).toFixed(2)+'M'),
435
+ textposition:'outside', textfont:{size:12,color:C.text},
436
+ hovertemplate:'<b>%{y}</b><br>Average: $%{x:.2f}M<extra></extra>',
437
+ }], baseLayout({
438
+ title:{text:'Average Comp by Component',font:{size:16},x:.5,xanchor:'center'},
439
+ xaxis:{title:'Average ($M)',gridcolor:C.grid,linecolor:C.grid,range:[0,sorted[sorted.length-1][1]/1e6*1.25]},
440
+ yaxis:{automargin:true,gridcolor:C.grid,linecolor:C.grid},
441
+ margin:{l:10,r:80,t:55,b:55},
442
+ }), plotCfg);
443
+
444
+ // Component trends
445
+ const ct = D.comp_trends;
446
+ const traces = Object.entries(ct).map(([name,data],i) => ({
447
+ type:'scatter', mode:'lines+markers', name,
448
+ x:data.years, y:data.values.map(v=>v/1e6),
449
+ line:{color:COLORS[i%COLORS.length],width:2}, marker:{size:5},
450
+ hovertemplate:`<b>${name}</b><br>FY %{x}<br>$%{y:.2f}M<extra></extra>`,
451
+ }));
452
+ Plotly.newPlot('chart-comp-trends', traces, baseLayout({
453
+ title:{text:'Compensation Components Over Time',font:{size:16},x:.5,xanchor:'center'},
454
+ xaxis:{title:'Fiscal Year',gridcolor:C.grid,linecolor:C.grid},
455
+ yaxis:{title:'Average ($M)',gridcolor:C.grid,linecolor:C.grid},
456
+ legend:{orientation:'h',y:1.1,x:.5,xanchor:'center'},
457
+ margin:{l:55,r:30,t:70,b:55},
458
+ }), plotCfg);
459
+
460
+ // Breakdown table
461
+ const bd = D.compensation.breakdown;
462
+ let rows = `<thead><tr>
463
+ <th>Component</th><th style="text-align:right">Mean</th>
464
+ <th style="text-align:right">Median</th><th style="text-align:right">Max</th>
465
+ </tr></thead><tbody>`;
466
+ for (const [k,v] of Object.entries(bd)) {
467
+ const label = k.replace(/_/g,' ').replace(/\b\w/g,c=>c.toUpperCase());
468
+ const isTotal = k === 'total';
469
+ rows += `<tr class="${isTotal?'total-row':''}">
470
+ <td>${isTotal?'⭐ ':''}${label}</td>
471
+ <td style="text-align:right">${fmtVal(v.mean)}</td>
472
+ <td style="text-align:right">${fmtVal(v.median)}</td>
473
+ <td style="text-align:right">${fmtVal(v.max)}</td>
474
+ </tr>`;
475
+ }
476
+ rows += '</tbody>';
477
+ document.getElementById('table-breakdown').innerHTML = rows;
478
+ }
479
+
480
+ // ═══════════════ 3. TOP 10 ═══════════════
481
+ function renderTop10() {
482
+ const data = D.top10;
483
+ const rev = [...data].reverse();
484
+ const medals = ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰'];
485
+
486
+ Plotly.newPlot('chart-top10', [{
487
+ type:'bar', orientation:'h',
488
+ y: rev.map(e=>e.name.slice(0,28)),
489
+ x: rev.map(e=>e.total/1e6),
490
+ marker: { color: rev.map((_,i)=> i >= rev.length-3 ? C.violet : C.blue), line:{width:0} },
491
+ text: rev.map(e=>'$'+(e.total/1e6).toFixed(1)+'M'),
492
+ textposition:'outside', textfont:{size:12,color:C.text},
493
+ customdata: rev.map(e=>e.company),
494
+ hovertemplate:'<b>%{y}</b><br>%{customdata}<br>$%{x:.1f}M<extra></extra>',
495
+ }], baseLayout({
496
+ title:{text:'Top 10 Highest Paid Executives (Overall)',font:{size:16},x:.5,xanchor:'center'},
497
+ xaxis:{title:'Total Compensation ($M)',gridcolor:C.grid,linecolor:C.grid,
498
+ range:[0,Math.max(...data.map(e=>e.total))/1e6*1.15]},
499
+ yaxis:{automargin:true,gridcolor:C.grid,linecolor:C.grid},
500
+ margin:{l:10,r:80,t:55,b:55}, height:480,
501
+ }), plotCfg);
502
+
503
+ // Table
504
+ let rows = `<thead><tr>
505
+ <th style="text-align:center;width:50px">#</th>
506
+ <th>Executive</th><th>Company</th><th>Title</th>
507
+ <th style="text-align:center">Year</th>
508
+ <th style="text-align:right">Total</th>
509
+ </tr></thead><tbody>`;
510
+ data.forEach((e,i) => {
511
+ const rank = i<3 ? `<span style="font-size:20px">${medals[i]}</span>` : `<b style="color:${C.gray}">${i+1}</b>`;
512
+ rows += `<tr${i%2?' style="background:#FAFAFA"':''}>
513
+ <td style="text-align:center">${rank}</td>
514
+ <td style="font-weight:700;color:${C.text}">${e.name}</td>
515
+ <td><span class="pill">${e.company}</span></td>
516
+ <td style="color:${C.slate};font-size:13px">${e.title||'-'}</td>
517
+ <td style="text-align:center;font-weight:600">${e.fiscal_year}</td>
518
+ <td style="text-align:right;font-weight:800;color:${C.green};font-size:15px">$${(e.total/1e6).toFixed(1)}M</td>
519
+ </tr>`;
520
+ });
521
+ rows += '</tbody>';
522
+ document.getElementById('table-top10').innerHTML = rows;
523
+ }
524
+
525
+ // ═══════════════ 4. DATA QUALITY ═══════════════
526
+ function renderQuality() {
527
+ const p = D.probability;
528
+ const tot = p.total_tables;
529
+ const pct = n => (n/tot*100).toFixed(0) + '%';
530
+
531
+ makeKpi('kpi-quality', [
532
+ ['πŸ“‹', fmt(tot), 'Tables Analyzed', C.blue],
533
+ ['πŸ“„', fmt(p.unique_docs), 'Unique Documents', C.slate],
534
+ ['βœ…', fmt(p.high_confidence)+' ('+pct(p.high_confidence)+')', 'High β‰₯0.7', C.green],
535
+ ['⚠️', fmt(p.medium_confidence)+' ('+pct(p.medium_confidence)+')', 'Medium', C.amber],
536
+ ['❌', fmt(p.low_confidence)+' ('+pct(p.low_confidence)+')', 'Low <0.3', C.red],
537
+ ]);
538
+
539
+ // Histogram with colored bars
540
+ const mids=[], widths=[], colors=[];
541
+ for(let i=0;i<p.hist_values.length;i++){
542
+ const m = (p.hist_edges[i]+p.hist_edges[i+1])/2;
543
+ mids.push(m); widths.push(p.hist_edges[i+1]-p.hist_edges[i]);
544
+ colors.push(m>=.7 ? C.green : m>=.3 ? C.amber : C.red);
545
+ }
546
+ const ymax = Math.max(...p.hist_values);
547
+ Plotly.newPlot('chart-prob-hist', [{
548
+ type:'bar', x:mids, y:p.hist_values, width:widths,
549
+ marker:{color:colors,line:{width:0}}, opacity:.85,
550
+ hovertemplate:'<b>Score: %{x:.2f}</b><br>Tables: %{y:,}<extra></extra>',
551
+ }], baseLayout({
552
+ title:{text:'Confidence Score Distribution',font:{size:16},x:.5,xanchor:'center'},
553
+ xaxis:{title:'SCT Probability (0–1)',range:[0,1],gridcolor:C.grid,linecolor:C.grid},
554
+ yaxis:{title:'Tables',gridcolor:C.grid,linecolor:C.grid},
555
+ shapes:[
556
+ {type:'line',x0:.7,x1:.7,y0:0,y1:1,yref:'paper',line:{color:C.green,width:2,dash:'dash'}},
557
+ {type:'line',x0:.3,x1:.3,y0:0,y1:1,yref:'paper',line:{color:C.amber,width:2,dash:'dash'}},
558
+ ],
559
+ annotations:[
560
+ {x:.85,y:ymax*.95,text:'Keep',showarrow:false,font:{color:C.green,size:13,weight:600}},
561
+ {x:.5,y:ymax*.95,text:'Review',showarrow:false,font:{color:C.amber,size:13}},
562
+ {x:.15,y:ymax*.95,text:'Filter out',showarrow:false,font:{color:C.red,size:13}},
563
+ ],
564
+ margin:{l:60,r:25,t:55,b:55},
565
+ }), plotCfg);
566
+
567
+ // Pie
568
+ Plotly.newPlot('chart-prob-pie', [{
569
+ type:'pie', hole:.55,
570
+ labels:['High (β‰₯0.7)','Medium (0.3–0.7)','Low (<0.3)'],
571
+ values:[p.high_confidence, p.medium_confidence, p.low_confidence],
572
+ marker:{colors:[C.green, C.amber, C.red], line:{color:'#fff',width:2}},
573
+ textinfo:'percent',
574
+ hovertemplate:'<b>%{label}</b><br>%{value:,} tables<br>%{percent}<extra></extra>',
575
+ }], baseLayout({
576
+ title:{text:'Confidence Breakdown',font:{size:16},x:.5,xanchor:'center'},
577
+ legend:{orientation:'h',y:-.06,x:.5,xanchor:'center'},
578
+ margin:{l:20,r:20,t:55,b:60},
579
+ }), plotCfg);
580
+
581
+ // Disambiguation card
582
+ const disambPct = p.multi_table_docs ? (p.could_disambiguate/p.multi_table_docs*100).toFixed(0) : 0;
583
+ const remaining = p.multi_table_docs - p.could_disambiguate;
584
+ document.getElementById('disambig-card').innerHTML = `
585
+ <div class="card-title">πŸ” Duplicate / False-Positive Resolution</div>
586
+ <p style="color:${C.slate};font-size:14px;margin-bottom:20px">
587
+ Proxy filings often contain multiple tables resembling the Summary Compensation Table
588
+ (Director Compensation, Option Grant tables, etc.). A fine-tuned Qwen3-VL-4B binary classifier
589
+ assigns a confidence score to help filter them out.
590
+ </p>
591
+ <div class="info-grid">
592
+ <div class="info-stat">
593
+ <div class="info-stat-val">${fmt(p.multi_table_docs)}</div>
594
+ <div class="info-stat-label">Docs with duplicates</div>
595
+ </div>
596
+ <div class="info-stat green">
597
+ <div class="info-stat-val">${fmt(p.could_disambiguate)}</div>
598
+ <div class="info-stat-label">Auto-resolved (${disambPct}%)</div>
599
+ </div>
600
+ <div class="info-stat amber">
601
+ <div class="info-stat-val">${fmt(remaining)}</div>
602
+ <div class="info-stat-label">Still ambiguous</div>
603
+ </div>
604
+ </div>
605
+ <div class="tip-box">
606
+ <b>πŸ’‘ Tip:</b> Filter by <code>sct_probability β‰₯ 0.7</code> to keep only high-confidence SCTs.
607
+ </div>`;
608
+ }
609
+ </script>
610
+ </body>
611
+ </html>