kyd-shared-badge 0.3.208 → 0.3.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.208",
3
+ "version": "0.3.210",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -1,38 +1,41 @@
1
1
  'use client';
2
2
 
3
3
  import { FiAlertTriangle } from 'react-icons/fi';
4
- import { PublicBadgeData, GraphInsightsPayload, ScoringSummary, BusinessRule, TopBusinessRule, EnterpriseMatch } from './types';
4
+ import { EnterpriseMatch, GraphInsightsPayload, PublicBadgeData, ScoringSummary, TopBusinessRule } from './types';
5
5
  // import ShareButton from './components/ShareButton';
6
- import ReportHeader from './components/ReportHeader';
7
6
  import EnterpriseCoaching from './components/EnterpriseCoaching';
8
- import AppendixTables from './components/AppendixTables';
9
- import BusinessRuleLink from './components/BusinessRuleLink';
10
7
  import IpRiskAnalysisDisplay from './components/IpRiskAnalysisDisplay';
8
+ import ReportHeader from './components/ReportHeader';
11
9
  // import Image from 'next/image';
12
- import GraphInsights from './components/GraphInsights';
13
10
  import ConnectedPlatforms from './components/ConnectedPlatforms';
14
- import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } from 'react-icons/fa';
15
- import { SiCredly, SiFiverr } from 'react-icons/si';
16
11
  import GaugeCard from './components/GaugeCard';
12
+ import GraphInsights from './components/GraphInsights';
17
13
  import RiskCard from './components/RiskCard';
18
14
  import RoleOverviewCard from './components/RoleOverviewCard';
19
15
 
20
- import { yellow, green } from './colors';
21
- import SkillsValidation from './components/SkillsValidation';
22
- import SkillsBubble from './components/SkillsBubble';
23
- import CategoryBars from './components/CategoryBars';
24
- import SkillsAppendixTable from './components/SkillsAppendixTable';
16
+ import ChatWidget from './chat/ChatWidget';
17
+ import { yellow } from './colors';
25
18
  import { BusinessRulesProvider } from './components/BusinessRulesContext';
19
+ import CategoryBars from './components/CategoryBars';
26
20
  import Reveal from './components/Reveal';
27
- import { formatLocalDateTime } from './utils/date';
28
- import ChatWidget from './chat/ChatWidget';
29
- import UseCases from './components/UseCases';
30
- import SummaryCards from './components/SummaryCards';
21
+ import SkillsBubble from './components/SkillsBubble';
22
+ import SkillsValidation from './components/SkillsValidation';
31
23
  import TopContributingFactors from './components/TopContributingFactors';
24
+ import UseCases from './components/UseCases';
25
+ import { formatLocalDateTime } from './utils/date';
32
26
  // import AiUsageBody from './components/AiUsageBody';
33
- import SanctionsMatches from './components/SanctionsMatches';
34
27
  import AppendixContent from './components/AppendixContent';
35
- import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy, getCategoryTooltipCopy } from './utils/provider';
28
+ import { getCategoryTooltipCopy } from './utils/provider';
29
+
30
+ const DEFAULT_AI_CATEGORY_ORDER = [
31
+ 'Ecosystems & Providers',
32
+ 'Orchestration & Dev',
33
+ 'Specialized AI',
34
+ 'Machine Learning',
35
+ 'Infrastructure',
36
+ 'Governance',
37
+ ] as const;
38
+
36
39
  type ChatWidgetProps = Partial<{
37
40
  api: string;
38
41
  title: string;
@@ -80,6 +83,12 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
80
83
  const genreMapping = (scoringSummary?.config?.genre_mapping) || {};
81
84
  const categoryScores = (scoringSummary?.category_scores) || {};
82
85
  const categoryTopByGraph = (graphInsights?.categoryTopBusiness) || {};
86
+ const aiCategoryOrder =
87
+ graphInsights?.aiCategoryOrder && graphInsights.aiCategoryOrder.length > 0
88
+ ? graphInsights.aiCategoryOrder
89
+ : [...DEFAULT_AI_CATEGORY_ORDER];
90
+ const aiCategoryScores = (graphInsights?.aiCategoryScores) || {};
91
+ const aiCategoryTopByGraph = (graphInsights?.aiCategoryTopBusiness) || {};
83
92
  const roleTargets: Record<string, number> | undefined = (() => {
84
93
  try {
85
94
  const em: EnterpriseMatch | undefined = assessmentResult?.enterprise_match;
@@ -258,7 +267,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
258
267
  </Reveal>
259
268
 
260
269
  {/* Right: Contributing Factors (hidden on small screens) */}
261
- <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
270
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
262
271
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
263
272
  <TopContributingFactors categories={genreMapping?.['Technical'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
264
273
  </Reveal>
@@ -306,7 +315,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
306
315
  />
307
316
  </Reveal>
308
317
  {/* Right: Contributing Factors (hidden on small screens) */}
309
- <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
318
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
310
319
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
311
320
  <TopContributingFactors categories={genreMapping?.['Risk'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
312
321
  </Reveal>
@@ -343,6 +352,43 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
343
352
 
344
353
  </div>
345
354
 
355
+ <div className="pt-8 space-y-8">
356
+ <Reveal headless={isHeadless} as={'h3'} offsetY={8} className={`${isHeadless ? 'kyd-break-before' : ''} text-2xl font-bold`} style={{ color: 'var(--text-main)' }}>KYD AI</Reveal>
357
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 w-full items-stretch py-8 border-y kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)' }}>
358
+ <Reveal headless={isHeadless} className="lg:col-span-8 h-full">
359
+ <CategoryBars
360
+ title={'AI Category Contributions'}
361
+ description={'Each bar represents AI evidence grouped into the major AI capability areas surfaced from GitHub business rules. Bar length reflects the strength of evidence for that AI domain.'}
362
+ categories={aiCategoryOrder as string[]}
363
+ categoryScores={aiCategoryScores}
364
+ getCategoryTooltipCopy={getCategoryTooltipCopy}
365
+ barHeight={16}
366
+ linkToAppendix={false}
367
+ tracksClassName={'min-h-[22rem] sm:min-h-[26rem] lg:min-h-[32rem]'}
368
+ />
369
+ </Reveal>
370
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
371
+ <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
372
+ <TopContributingFactors categories={aiCategoryOrder as string[] || []} categoryTopByGraph={aiCategoryTopByGraph as any} />
373
+ </Reveal>
374
+ </div>
375
+ <Reveal headless={isHeadless}>
376
+ <div className="pt-8 border-t kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)' }}>
377
+ <h3 className={'text-xl font-bold mb-3 kyd-keep-with-next'} style={{ color: 'var(--text-main)' }}>KYD AI - Skills Insights</h3>
378
+ <div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
379
+ <SkillsValidation
380
+ skillsCategoryRadar={graphInsights?.aiSkillsCategoryRadar}
381
+ headless={isHeadless}
382
+ title={'AI Skills by Subcategory'}
383
+ description={'The bar chart shows AI evidence strength by subcategory.'}
384
+ marginLeft={60}
385
+ marginRight={60}
386
+ />
387
+ </div>
388
+ </div>
389
+ </Reveal>
390
+ </div>
391
+
346
392
  {/* Connected Platforms */}
347
393
  <Reveal headless={isHeadless}>
348
394
  <ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
@@ -9,7 +9,7 @@ import EnterpriseCoaching from './components/EnterpriseCoaching';
9
9
  import GraphInsights from './components/GraphInsights';
10
10
  import IpRiskAnalysisDisplay from './components/IpRiskAnalysisDisplay';
11
11
  import ReportHeader from './components/ReportHeader';
12
- // import AiIcon from './components/icons/ai';
12
+ import AiIcon from './components/icons/ai';
13
13
  import CodeIcon from './components/icons/code';
14
14
  import RiskIcon from './components/icons/risk';
15
15
 
@@ -23,7 +23,6 @@ import ResumeView from './components/ResumeView';
23
23
  import Reveal from './components/Reveal';
24
24
  import RiskCard from './components/RiskCard';
25
25
  import RoleOverviewCard from './components/RoleOverviewCard';
26
- import SanctionsMatches from './components/SanctionsMatches';
27
26
  import SkillsAppendixTable from './components/SkillsAppendixTable';
28
27
  import SkillsBubble from './components/SkillsBubble';
29
28
  import SkillsValidation from './components/SkillsValidation';
@@ -32,6 +31,15 @@ import UseCases from './components/UseCases';
32
31
  import { formatLocalDateTime } from './utils/date';
33
32
  import { getCategoryTooltipCopy } from './utils/provider';
34
33
 
34
+ const DEFAULT_AI_CATEGORY_ORDER = [
35
+ 'Ecosystems & Providers',
36
+ 'Orchestration & Dev',
37
+ 'Specialized AI',
38
+ 'Machine Learning',
39
+ 'Infrastructure',
40
+ 'Governance',
41
+ ] as const;
42
+
35
43
  type ChatWidgetProps = Partial<{
36
44
  api: string;
37
45
  title: string;
@@ -79,6 +87,12 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
79
87
  const genreMapping = (scoringSummary?.config?.genre_mapping) || {};
80
88
  const categoryScores = (scoringSummary?.category_scores) || {};
81
89
  const categoryTopByGraph = (graphInsights?.categoryTopBusiness) || {};
90
+ const aiCategoryOrder =
91
+ graphInsights?.aiCategoryOrder && graphInsights.aiCategoryOrder.length > 0
92
+ ? graphInsights.aiCategoryOrder
93
+ : [...DEFAULT_AI_CATEGORY_ORDER];
94
+ const aiCategoryScores = (graphInsights?.aiCategoryScores) || {};
95
+ const aiCategoryTopByGraph = (graphInsights?.aiCategoryTopBusiness) || {};
82
96
  const roleTargets: Record<string, number> | undefined = (() => {
83
97
  try {
84
98
  const em: EnterpriseMatch | undefined = assessmentResult?.enterprise_match;
@@ -116,7 +130,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
116
130
  { key: 'overview', label: 'Overview' },
117
131
  { key: 'technical', label: 'KYD Technical' },
118
132
  { key: 'risk', label: 'KYD Risk' },
119
- // { key: 'ai', label: 'KYD AI' },
133
+ { key: 'ai', label: 'KYD AI' },
120
134
  { key: 'role', label: showRoleFit ? 'Role Fit & Coaching' : 'Coaching' },
121
135
  { key: 'resume', label: 'KYD Resume' },
122
136
  { key: 'appendix', label: 'Appendix' }
@@ -165,7 +179,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
165
179
  {tabs.map(t => {
166
180
  // Map tab key to icon component
167
181
  const IconComp = (() => {
168
- // if (t.key === 'ai') return AiIcon;
182
+ if (t.key === 'ai') return AiIcon;
169
183
  if (t.key === 'technical') return CodeIcon;
170
184
  if (t.key === 'risk') return RiskIcon;
171
185
  return null;
@@ -315,6 +329,20 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
315
329
  </div>
316
330
  </Reveal>
317
331
 
332
+ {/* AI Skills bar chart above Skills Footprint */}
333
+ <Reveal headless={isHeadless}>
334
+ <div className={'mt-6'}>
335
+ <SkillsValidation
336
+ skillsCategoryRadar={graphInsights?.aiSkillsCategoryRadar}
337
+ headless={isHeadless}
338
+ title={'AI Skills by Subcategory'}
339
+ description={'The bar chart shows AI evidence strength by subcategory.'}
340
+ marginLeft={60}
341
+ marginRight={60}
342
+ />
343
+ </div>
344
+ </Reveal>
345
+
318
346
  {/* Full-width Skills bubble chart below quadrant */}
319
347
  <div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
320
348
  <Reveal headless={isHeadless}>
@@ -335,6 +363,10 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
335
363
  // Local state for expanding long role descriptions
336
364
  const [showFullRoleDescription, setShowFullRoleDescription] = useState(false);
337
365
  const rec = assessmentResult?.recommendations;
366
+ const recommendationRoleName = (assessmentResult?.enterprise_match?.role?.name || '').trim();
367
+ const recommendationsTitle = recommendationRoleName
368
+ ? `Evidence Recommendations for ${recommendationRoleName}`
369
+ : 'Evidence Recommendations';
338
370
 
339
371
  return (
340
372
  <div className={`${wrapperMaxWidth} mx-auto`}>
@@ -419,14 +451,14 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
419
451
  </div>
420
452
  )}
421
453
  <div>
422
- <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Score Improvement Recommendations</Reveal>
454
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>{recommendationsTitle}</Reveal>
423
455
  <Reveal headless={isHeadless}>
424
456
  <div className={'space-y-3'}>
425
457
  {rec?.summary ? (
426
458
  <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{rec.summary}</div>
427
459
  ) : null}
428
460
  {Array.isArray(rec?.bullet_points) && (rec?.bullet_points?.length || 0) > 0 ? (
429
- <ul className={'list-disc pl-5 text-sm'} style={{ color: 'var(--text-secondary)' }}>
461
+ <ul className={'list-disc pl-5 space-y-3 text-sm'} style={{ color: 'var(--text-secondary)' }}>
430
462
  {rec?.bullet_points?.map((bp, idx) => (
431
463
  <li key={idx}>{bp}</li>
432
464
  ))}
@@ -489,7 +521,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
489
521
  categoryTargets={roleTargets}
490
522
  />
491
523
  </Reveal>
492
- <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
524
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
493
525
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
494
526
  <TopContributingFactors categories={genreMapping?.['Technical'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
495
527
  </Reveal>
@@ -506,6 +538,48 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
506
538
  </div>
507
539
  );
508
540
 
541
+ const AiSection = () => (
542
+ <div className={`${wrapperMaxWidth} mx-auto`}>
543
+ <div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
544
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD AI</Reveal>
545
+ <div className="grid grid-cols-1 lg:grid-cols-12 w-full gap-8 items-stretch py-8">
546
+ <Reveal headless={isHeadless} className="lg:col-span-8 h-full">
547
+ <CategoryBars
548
+ title={'AI Category Contributions'}
549
+ description={'Each bar represents AI evidence grouped into the major AI capability areas surfaced from GitHub business rules. Bar length reflects the strength of evidence for that AI domain.'}
550
+ categories={aiCategoryOrder as string[]}
551
+ categoryScores={aiCategoryScores}
552
+ getCategoryTooltipCopy={getCategoryTooltipCopy}
553
+ barHeight={16}
554
+ linkToAppendix={false}
555
+ tracksClassName={'min-h-[22rem] sm:min-h-[26rem] lg:min-h-[32rem]'}
556
+ />
557
+ </Reveal>
558
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
559
+ <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
560
+ <TopContributingFactors categories={aiCategoryOrder as string[] || []} categoryTopByGraph={aiCategoryTopByGraph as any} />
561
+ </Reveal>
562
+ </div>
563
+ <Reveal headless={isHeadless}>
564
+ <div className="pt-8 border-t kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)' }}>
565
+ <h3 className={'text-xl font-bold mb-3 kyd-keep-with-next'} style={{ color: 'var(--text-main)' }}>KYD AI - Skills Insights</h3>
566
+ <div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
567
+ <SkillsValidation
568
+ skillsCategoryRadar={graphInsights?.aiSkillsCategoryRadar}
569
+ headless={isHeadless}
570
+ title={'AI Skills by Subcategory'}
571
+ description={'The bar chart shows AI evidence strength by subcategory'}
572
+ marginLeft={60}
573
+ marginRight={60}
574
+ tickFontSize={12}
575
+ />
576
+ </div>
577
+ </div>
578
+ </Reveal>
579
+ </div>
580
+ </div>
581
+ );
582
+
509
583
  const RiskSection = () => {
510
584
  const adjustedCategoryScores = (() => {
511
585
  if (!badgeData.optOutScreening) return categoryScores;
@@ -557,7 +631,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
557
631
  categoryTargets={roleTargets}
558
632
  />
559
633
  </Reveal>
560
- <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
634
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full hidden lg:flex flex-col items-start justify-start" delayMs={80}>
561
635
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
562
636
  <TopContributingFactors categories={genreMapping?.['Risk'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
563
637
  </Reveal>
@@ -594,15 +668,6 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
594
668
  );
595
669
  };
596
670
 
597
- // const AiSection = () => (
598
- // <div className={`${wrapperMaxWidth} mx-auto`}>
599
- // <div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
600
- // <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD AI (Beta)</Reveal>
601
- // <AiUsageBody ai={assessmentResult?.ai_usage_summary} />
602
- // </div>
603
- // </div>
604
- // );
605
-
606
671
  const AppendixSection = () => (
607
672
  <div className={`${wrapperMaxWidth} mx-auto`}>
608
673
  <div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
@@ -697,7 +762,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
697
762
  <RoleFitSection />
698
763
  {TechnicalSection()}
699
764
  {RiskSection()}
700
- {/* {AiSection()} */}
765
+ {AiSection()}
701
766
  {AppendixSection()}
702
767
  </>
703
768
  ) : (
@@ -709,7 +774,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
709
774
  {activeTab === 'overview' && OverviewSection()}
710
775
  {activeTab === 'technical' && TechnicalSection()}
711
776
  {activeTab === 'risk' && RiskSection()}
712
- {/* {activeTab === 'ai' && AiSection()} */}
777
+ {activeTab === 'ai' && AiSection()}
713
778
  {activeTab === 'role' && <RoleFitSection />}
714
779
  {activeTab === 'resume' && <ResumeSection />}
715
780
  {activeTab === 'appendix' && AppendixSection()}
@@ -9,8 +9,12 @@ type CategoryBarsProps = {
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  categoryScores: Record<string, any>;
11
11
  getCategoryTooltipCopy: (category: string) => string;
12
+ description?: string;
12
13
  barHeight?: number; // px height for the filled bar
13
14
  categoryTargets?: Record<string, number>; // desired target lengths (0-100)
15
+ linkToAppendix?: boolean;
16
+ /** Extra classes for the flex column that lays out rows with justify-between (taller column = more space between bars). */
17
+ tracksClassName?: string;
14
18
  };
15
19
 
16
20
  const CategoryBars: React.FC<CategoryBarsProps> = ({
@@ -18,21 +22,24 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
18
22
  categories,
19
23
  categoryScores,
20
24
  getCategoryTooltipCopy,
25
+ description,
21
26
  barHeight = 6,
22
27
  categoryTargets,
28
+ linkToAppendix = true,
29
+ tracksClassName,
23
30
  }) => {
24
31
  return (
25
32
  <div className="relative flex flex-col h-full">
26
33
  <div className="font-semibold text-xl mb-2" style={{ color: 'var(--text-main)' }}>{title}</div>
27
34
  <div className="text-sm mb-6" style={{ color: 'var(--text-secondary)' }}>
28
- Each bar represents a category's net evidence: positive values extend right in green, negative values extend left, and bar length denotes contribution magnitude.
35
+ {description || "Each bar represents a category's net evidence: positive values extend right in green, negative values extend left, and bar length denotes contribution magnitude."}
29
36
  {categoryTargets && Object.keys(categoryTargets).length > 0 ? (
30
37
  <>
31
38
  {' '}Target overlays indicate desired evidence lengths for this role.
32
39
  </>
33
40
  ) : null}
34
41
  </div>
35
- <div className="flex-1 flex flex-col justify-between relative">
42
+ <div className={`flex-1 flex flex-col justify-between relative${tracksClassName ? ` ${tracksClassName}` : ''}`}>
36
43
  <div
37
44
  className="absolute top-0 bottom-0 w-px"
38
45
  style={{
@@ -76,6 +83,7 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
76
83
  const targetClamp = hasTarget ? Math.max(0, Math.min(100, Math.round(targetRaw as number))) : 0;
77
84
  const targetWidth = targetClamp / 2; // same scale as fillWidth
78
85
  const handleClick = (e: React.MouseEvent) => {
86
+ if (!linkToAppendix) return;
79
87
  // Navigate to Appendix -> Skills category anchor
80
88
  try {
81
89
  if (typeof window !== 'undefined') {
@@ -87,7 +95,7 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
87
95
  };
88
96
  return (
89
97
  <div key={category} className="first:pt-0 group relative">
90
- <button type="button" onClick={handleClick} className={'font-semibold mb-1 underline-offset-2 hover:underline text-left'} style={{ color: 'var(--text-main)' }}>
98
+ <button type="button" onClick={handleClick} className={`font-semibold mb-1 underline-offset-2 text-left ${linkToAppendix ? 'hover:underline' : ''}`} style={{ color: 'var(--text-main)' }}>
91
99
  {category}
92
100
  </button>
93
101
  <div className="relative">
@@ -99,8 +107,8 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
99
107
  outline: '1px solid var(--icon-button-secondary)',
100
108
  }}
101
109
  onClick={handleClick}
102
- role={'button'}
103
- aria-label={`Jump to ${category} in Appendix`}
110
+ role={linkToAppendix ? 'button' : undefined}
111
+ aria-label={linkToAppendix ? `Jump to ${category} in Appendix` : undefined}
104
112
  >
105
113
  {/* signed fill originating from center, or full grey fill when no data */}
106
114
  {!hasData ? (
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
4
- import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
3
+ import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
5
4
  import { green1, green2, green5 } from '../colors';
6
5
 
7
6
  type SkillsRadarPoint = {
@@ -11,15 +10,31 @@ type SkillsRadarPoint = {
11
10
  certified?: number;
12
11
  };
13
12
 
14
- export default function SkillsValidation({ skillsCategoryRadar, headless }: { skillsCategoryRadar?: SkillsRadarPoint[]; headless?: boolean }) {
15
- const skillsRadarLimited = (skillsCategoryRadar || []).slice(0, 8);
13
+ export default function SkillsValidation({
14
+ skillsCategoryRadar,
15
+ headless,
16
+ title = 'Skills by Validation Type',
17
+ description = 'The bar chart shows how each skill is supported by self-attested claims, observed practice, or certified evidence.',
18
+ tickFontSize = 12,
19
+ marginLeft = 8,
20
+ marginRight = 8,
21
+ }: {
22
+ skillsCategoryRadar?: SkillsRadarPoint[];
23
+ headless?: boolean;
24
+ title?: string;
25
+ description?: string;
26
+ tickFontSize?: number;
27
+ marginLeft?: number;
28
+ marginRight?: number;
29
+ }) {
30
+ const skillsRadarLimited = skillsCategoryRadar || [];
16
31
  const hasNoData = skillsRadarLimited.length === 0 || skillsRadarLimited.every(p => !p.observed && !p.self_reported && !p.certified);
17
32
  const xAxisBottomOffset = 50 + 36; // XAxis height + BarChart margin.bottom
18
33
 
19
34
  return (
20
35
  <div className={'rounded-lg p-4 border kyd-avoid-break'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
21
- <h4 className={'font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Skills by Validation Type</h4>
22
- <p className={'text-sm mb-4'} style={{ color: 'var(--text-secondary)' }}>The bar chart shows how each skill is supported by self-attested claims, observed practice, or certified evidence.</p>
36
+ <h4 className={'font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>{title}</h4>
37
+ <p className={'text-sm mb-4'} style={{ color: 'var(--text-secondary)' }}>{description}</p>
23
38
  <div style={{ height: 360, position: 'relative' }}>
24
39
  {hasNoData && (
25
40
  <div
@@ -40,15 +55,16 @@ export default function SkillsValidation({ skillsCategoryRadar, headless }: { sk
40
55
  </div>
41
56
  )}
42
57
  <ResponsiveContainer>
43
- <BarChart data={skillsRadarLimited} margin={{ top: 8, right: 8, left: 8, bottom: 36 }}>
58
+ <BarChart data={skillsRadarLimited} margin={{ top: 8, right: marginRight, left: marginLeft, bottom: 8 }}>
44
59
  <CartesianGrid strokeDasharray="3 3" stroke={'var(--icon-button-secondary)'} />
45
- <XAxis dataKey="axis" tick={{ fill: 'var(--text-secondary)', fontSize: 12 }} interval={0} angle={-20} textAnchor="end" height={50} />
60
+ <XAxis dataKey="axis" tick={{ fill: 'var(--text-secondary)', fontSize: tickFontSize }} interval={0} angle={-20} textAnchor="end" height={100}/>
46
61
  <YAxis domain={[0, 100]} tick={{ fill: 'var(--text-secondary)' }} />
47
62
  <Tooltip contentStyle={{ background: 'var(--content-card-background)', border: `1px solid var(--icon-button-secondary)`, color: 'var(--text-main)' }} />
48
63
  <Bar dataKey="observed" name="Observed" fill={green2} isAnimationActive={!headless} stroke={'var(--icon-button-secondary)'} strokeWidth={1} />
49
64
  <Bar dataKey="self_reported" name="Self-reported" fill={green5} isAnimationActive={!headless} stroke={'var(--icon-button-secondary)'} strokeWidth={1} />
50
65
  <Bar dataKey="certified" name="Certified" fill={green1} isAnimationActive={!headless} stroke={'var(--icon-button-secondary)'} strokeWidth={1} />
51
66
  </BarChart>
67
+
52
68
  </ResponsiveContainer>
53
69
  </div>
54
70
  </div>
package/src/types.ts CHANGED
@@ -207,14 +207,30 @@ export interface EnterpriseUseCases {
207
207
  note?: string;
208
208
  }
209
209
 
210
+ export type RecommendationPriority = 'High' | 'Medium' | 'Low';
211
+
212
+ export type RecommendationItem = {
213
+ title: string;
214
+ priority: RecommendationPriority;
215
+ source: string;
216
+ recommendation: string;
217
+ evidence: string;
218
+ why_this_matters: string;
219
+ what_to_do_next: string;
220
+ expected_outcome: string;
221
+ };
222
+
223
+ export type Recommendations = {
224
+ summary?: string;
225
+ bullet_points?: string[];
226
+ items?: RecommendationItem[];
227
+ };
228
+
210
229
 
211
230
  export interface AssessmentResult {
212
231
  final_percent: number;
213
232
  report_summary?: string;
214
- recommendations: {
215
- summary?: string;
216
- bullet_points?: string[];
217
- };
233
+ recommendations: Recommendations;
218
234
  developer_trust_explanation?: string;
219
235
  industry_considerations?: string;
220
236
 
@@ -364,6 +380,27 @@ export type CategoryTopBusinessItem = {
364
380
  rule_label?: string;
365
381
  provider?: string;
366
382
  weight?: number;
383
+ uid?: string;
384
+ };
385
+
386
+ export type CategoryScoreValue = {
387
+ percent_progress?: number;
388
+ percent_progress_signed?: number;
389
+ applied?: number;
390
+ theoretical?: number;
391
+ };
392
+
393
+ export type AiCategoryScoreEntry = {
394
+ business?: CategoryScoreValue;
395
+ combined?: CategoryScoreValue;
396
+ };
397
+
398
+ export type AiSkillsCategoryRadarPoint = {
399
+ axis: string;
400
+ observed?: number;
401
+ self_reported?: number;
402
+ certified?: number;
403
+ matched_rules?: number;
367
404
  };
368
405
 
369
406
  export interface GraphInsightsPayload {
@@ -436,6 +473,14 @@ export interface GraphInsightsPayload {
436
473
  top_movers?: Array<{ label?: string; uid?: string }>;
437
474
  };
438
475
  };
476
+ aiCategoryOrder?: string[];
477
+ aiCategoryScores?: Record<string, AiCategoryScoreEntry>;
478
+ aiCategoryTopBusiness?: { [category: string]: CategoryTopBusinessItem[] };
479
+ aiSkillsCategoryRadar?: AiSkillsCategoryRadarPoint[];
480
+ aiSummary?: {
481
+ percent?: number;
482
+ label?: string;
483
+ };
439
484
  ai_usage_summary?: {
440
485
  explanation: string;
441
486
  key_findings: string[];
@@ -60,6 +60,12 @@ export const getProviderTooltipCopy = (provider?: string): string => {
60
60
 
61
61
  export const getCategoryTooltipCopy = (category: string): string => {
62
62
  const name = (category || '').toLowerCase();
63
+ if (/ecosystems?\s*&\s*providers|model provider ecosystems|cross-provider interaction/.test(name)) return 'Signals showing breadth across model vendors and whether the developer works across multiple provider ecosystems.';
64
+ if (/orchestration\s*&\s*dev|orchestration|interaction frameworks|model development frameworks/.test(name)) return 'Signals showing how the developer structures, chains, and develops AI systems and workflows.';
65
+ if (/specialized ai|vision|image|audio|speech|multimodal/.test(name)) return 'Signals showing hands-on work with specialized AI modalities such as vision, speech, audio, and multimodal systems.';
66
+ if (/machine learning|classical|applied ml|training|optimization|mto/.test(name)) return 'Signals showing practical machine-learning knowledge, experimentation, and model optimization work.';
67
+ if (/infrastructure|embedding|retrieval|vector/.test(name)) return 'Signals showing AI infrastructure work including embeddings, retrieval systems, and vector database usage.';
68
+ if (/governance|evaluation|experimentation|safety|control/.test(name)) return 'Signals showing evaluation rigor, safety controls, and governance practices around AI systems.';
63
69
  if (/network|connection|collab|peer/.test(name)) return 'Signals from the developer\'s professional connections, collaborations, and peer recognition.';
64
70
  if (/project|repo|portfolio|work/.test(name)) return 'Signals from a developer\'s visible projects, repositories, or published work that indicate breadth and quality of output.';
65
71
  if (/skill|cert|assessment|endorse/.test(name)) return 'Signals tied to specific technical abilities, such as verified certifications, assessments, or endorsements.';