kyd-shared-badge 0.3.40 → 0.3.42
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 +1 -1
- package/src/PrintableBadgeDisplay.tsx +14 -295
- package/src/SharedBadgeDisplay.tsx +20 -304
- package/src/components/AiUsageBody.tsx +91 -0
- package/src/components/AppendixContent.tsx +76 -0
- package/src/components/SanctionsMatches.tsx +78 -0
- package/src/components/SummaryCards.tsx +60 -0
- package/src/components/TopContributingFactors.tsx +46 -0
- package/src/utils/provider.tsx +65 -0
package/package.json
CHANGED
|
@@ -25,6 +25,12 @@ import Reveal from './components/Reveal';
|
|
|
25
25
|
import { formatLocalDateTime } from './utils/date';
|
|
26
26
|
import ChatWidget from './chat/ChatWidget';
|
|
27
27
|
import UseCases from './components/UseCases';
|
|
28
|
+
import SummaryCards from './components/SummaryCards';
|
|
29
|
+
import TopContributingFactors from './components/TopContributingFactors';
|
|
30
|
+
import AiUsageBody from './components/AiUsageBody';
|
|
31
|
+
import SanctionsMatches from './components/SanctionsMatches';
|
|
32
|
+
import AppendixContent from './components/AppendixContent';
|
|
33
|
+
import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy, getCategoryTooltipCopy, barColor } from './utils/provider';
|
|
28
34
|
type ChatWidgetProps = Partial<{
|
|
29
35
|
api: string;
|
|
30
36
|
title: string;
|
|
@@ -95,63 +101,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
95
101
|
const skillsMatrix = assessmentResult?.skills_matrix || { skills: [] };
|
|
96
102
|
const skillsAll = assessmentResult?.skills_all || { skills: [] };
|
|
97
103
|
const connected = badgeData?.connectedAccounts || [];
|
|
98
|
-
|
|
99
|
-
const n = (name || '').toLowerCase();
|
|
100
|
-
if (n.includes('github')) return <FaGithub />;
|
|
101
|
-
if (n.includes('gitlab')) return <FaGitlab />;
|
|
102
|
-
if (n.includes('stack')) return <FaStackOverflow />;
|
|
103
|
-
if (n.includes('credly')) return <SiCredly />;
|
|
104
|
-
if (n.includes('fiverr')) return <SiFiverr />;
|
|
105
|
-
if (n.includes('kaggle')) return <FaKaggle />;
|
|
106
|
-
if (n.includes('google')) return <FaGoogle />;
|
|
107
|
-
if (n.includes('linkedin')) return <FaLinkedin />;
|
|
108
|
-
return <span className="inline-block w-3 h-3 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const getProviderDisplayName = (name?: string): string => {
|
|
112
|
-
const n = (name || '').toLowerCase();
|
|
113
|
-
if (n.includes('github')) return 'GitHub';
|
|
114
|
-
if (n.includes('gitlab')) return 'GitLab';
|
|
115
|
-
if (n.includes('stack')) return 'Stack Overflow';
|
|
116
|
-
if (n.includes('credly')) return 'Credly';
|
|
117
|
-
if (n.includes('fiverr')) return 'Fiverr';
|
|
118
|
-
if (n.includes('kaggle')) return 'Kaggle';
|
|
119
|
-
if (n.includes('google')) return 'Google Scholar';
|
|
120
|
-
if (n.includes('linkedin')) return 'LinkedIn';
|
|
121
|
-
return name || 'Provider';
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const getProviderTooltipCopy = (provider?: string): string => {
|
|
125
|
-
const n = (provider || '').toLowerCase();
|
|
126
|
-
if (n.includes('github')) return 'Signals from open-source activity: commits, repos, stars, and collaboration patterns.';
|
|
127
|
-
if (n.includes('gitlab')) return 'Signals from GitLab projects and contributions that indicate delivery and collaboration.';
|
|
128
|
-
if (n.includes('stack')) return 'Signals from Q&A participation such as answers, reputation, and accepted solutions.';
|
|
129
|
-
if (n.includes('credly')) return 'Verified badges and certifications that validate specific skills or achievements.';
|
|
130
|
-
if (n.includes('fiverr')) return 'Client reviews, job history, and delivery consistency across freelance engagements.';
|
|
131
|
-
if (n.includes('kaggle')) return 'Competition results, notebooks, and dataset contributions that reflect analytical skill.';
|
|
132
|
-
if (n.includes('google')) return 'Publications, citations, and scholarly presence indicating research impact.';
|
|
133
|
-
if (n.includes('linkedin')) return 'Professional history, endorsements, and network signals indicating credibility.';
|
|
134
|
-
return 'Signals contributed from this provider relevant to capability and trust.';
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const getCategoryTooltipCopy = (category: string): string => {
|
|
138
|
-
const name = (category || '').toLowerCase();
|
|
139
|
-
if (/network|connection|collab|peer/.test(name)) return 'Signals from the developer’s professional connections, collaborations, and peer recognition.';
|
|
140
|
-
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.';
|
|
141
|
-
if (/skill|cert|assessment|endorse/.test(name)) return 'Signals tied to specific technical abilities, such as verified certifications, assessments, or endorsements.';
|
|
142
|
-
if (/experience|tenure|history/.test(name)) return 'Signals of tenure and diversity of professional or project involvement over time.';
|
|
143
|
-
if (/activity|recency|frequency|engage/.test(name)) return 'Signals of recency and frequency of developer engagement in professional or technical platforms.';
|
|
144
|
-
if (/sanction|legal|criminal|regulatory|ofac|fbi|watchlist/.test(name)) return 'Signals of legal, criminal, or regulatory red flags linked to an identity.';
|
|
145
|
-
if (/identity|authentic|consisten/.test(name)) return 'Signals that indicate whether a developer’s identity is genuine and consistent across platforms.';
|
|
146
|
-
if (/reputation|review|rating|feedback|perceive|peer/.test(name)) return 'Signals of how peers, clients, and communities perceive the developer.';
|
|
147
|
-
if (/geo|jurisdiction|country|region|ip|location/.test(name)) return 'Signals tied to a developer’s geographic or jurisdictional context.';
|
|
148
|
-
if (/security|cyber/.test(name)) return 'Signals of security posture and potential cyber-risk exposure.';
|
|
149
|
-
if (/risk/.test(name)) return 'KYD Risk surfaces signals of authenticity, reputation, and environmental telemetry that indicate potential risks in engaging with a developer.';
|
|
150
|
-
if (/tech|technical/.test(name)) return 'KYD Technical surfaces signals from a developer’s portfolio, skills, experience, activity, and network to indicate the likelihood of technical capability.';
|
|
151
|
-
return 'Share of overall contribution by category based on applied weights.';
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const barColor = (pct: number) => pct >= 60 ? green : pct >= 40 ? '#ffbb54' : '#EC6662';
|
|
104
|
+
|
|
155
105
|
|
|
156
106
|
return (
|
|
157
107
|
<BusinessRulesProvider items={graphInsights?.business_rules_all}>
|
|
@@ -207,65 +157,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
207
157
|
<div className="pt-8 first:pt-0 kyd-avoid-break">
|
|
208
158
|
<Reveal headless={isHeadless} as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
|
|
209
159
|
<Reveal headless={isHeadless} as={'div'} offsetY={8} durationMs={500} className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
210
|
-
<
|
|
211
|
-
{/* Technical semicircle gauge (refactored) */}
|
|
212
|
-
{(() => {
|
|
213
|
-
const ui = graphInsights?.uiSummary?.technical || {};
|
|
214
|
-
const pct = Math.round(Number(ui?.percent ?? 0));
|
|
215
|
-
const label = ui?.label || 'EVIDENCE';
|
|
216
|
-
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Technical');
|
|
217
|
-
return (
|
|
218
|
-
<GaugeCard
|
|
219
|
-
key={'technical-card'}
|
|
220
|
-
title={'KYD Technical'}
|
|
221
|
-
description={'The gauge visualization shows a weighted composite of technical evidence, with rightward movement indicating stronger indications of developer capability'}
|
|
222
|
-
percent={pct}
|
|
223
|
-
label={label}
|
|
224
|
-
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
225
|
-
topMoversTitle={'Top Score Movers'}
|
|
226
|
-
/>
|
|
227
|
-
);
|
|
228
|
-
})()}
|
|
229
|
-
|
|
230
|
-
{/* Risk descending bars card (abstracted) */}
|
|
231
|
-
{(() => {
|
|
232
|
-
const ui = graphInsights?.uiSummary?.risk || {};
|
|
233
|
-
const pctGood = Math.round(Number(ui?.percent_good ?? 0));
|
|
234
|
-
const label = ui?.label || 'RISK';
|
|
235
|
-
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Risk');
|
|
236
|
-
const tooltip = 'Higher bar filled indicates lower overall risk; movement to the right reflects improved risk posture.';
|
|
237
|
-
return (
|
|
238
|
-
<RiskCard
|
|
239
|
-
title={'KYD Risk'}
|
|
240
|
-
description={'The bar chart visualizes relative risk levels, where shorter bars denote lower risk and taller bars indicate greater exposure.'}
|
|
241
|
-
percentGood={pctGood}
|
|
242
|
-
label={label}
|
|
243
|
-
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
244
|
-
topMoversTitle={'Top Score Movers'}
|
|
245
|
-
tooltipText={tooltip}
|
|
246
|
-
/>
|
|
247
|
-
);
|
|
248
|
-
})()}
|
|
249
|
-
|
|
250
|
-
{/* AI transparency semicircle gauge */}
|
|
251
|
-
{(() => {
|
|
252
|
-
const ai_usage_summary = assessmentResult?.ai_usage_summary;
|
|
253
|
-
const label = 'AI Transparency'// TODO: calculate label frontend
|
|
254
|
-
const topMovers = ai_usage_summary?.key_findings || []
|
|
255
|
-
return (
|
|
256
|
-
<GaugeCard
|
|
257
|
-
key={'ai-card'}
|
|
258
|
-
title={'KYD AI (Beta)'}
|
|
259
|
-
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
260
|
-
percent={ai_usage_summary?.transparency_score}
|
|
261
|
-
label={label}
|
|
262
|
-
// id non-functional
|
|
263
|
-
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
264
|
-
topMoversTitle={'Key Findings'}
|
|
265
|
-
/>
|
|
266
|
-
);
|
|
267
|
-
})()}
|
|
268
|
-
</div>
|
|
160
|
+
<SummaryCards graphInsights={graphInsights} assessmentResult={assessmentResult} topBusinessForGenre={topBusinessForGenre} />
|
|
269
161
|
</Reveal>
|
|
270
162
|
</div>
|
|
271
163
|
|
|
@@ -310,33 +202,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
310
202
|
{/* Right: Contributing Factors (hidden on small screens) */}
|
|
311
203
|
<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}>
|
|
312
204
|
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
313
|
-
<
|
|
314
|
-
{(genreMapping?.['Technical'] || []).map((cat: string) => {
|
|
315
|
-
const topRules = (categoryTopByGraph?.[cat] || []).slice(0, 3) as BusinessRule[];
|
|
316
|
-
if (!topRules || topRules.length === 0) return null;
|
|
317
|
-
return (
|
|
318
|
-
<div key={cat} className="pt-3 first:pt-0">
|
|
319
|
-
<div className={'text-xs font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>{cat}</div>
|
|
320
|
-
<div className="space-y-1">
|
|
321
|
-
{topRules.map((r, idx: number) => (
|
|
322
|
-
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
323
|
-
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
324
|
-
<ProviderIcon name={r.provider} />
|
|
325
|
-
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
326
|
-
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
327
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
328
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
329
|
-
</div>
|
|
330
|
-
</div>
|
|
331
|
-
</span>
|
|
332
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
333
|
-
</div>
|
|
334
|
-
))}
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
);
|
|
338
|
-
})}
|
|
339
|
-
</div>
|
|
205
|
+
<TopContributingFactors categories={genreMapping?.['Technical'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
|
|
340
206
|
</Reveal>
|
|
341
207
|
</div>
|
|
342
208
|
|
|
@@ -385,33 +251,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
385
251
|
{/* Right: Contributing Factors (hidden on small screens) */}
|
|
386
252
|
<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}>
|
|
387
253
|
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
388
|
-
<
|
|
389
|
-
{genreMapping?.['Risk']?.map((cat: string) => {
|
|
390
|
-
const topRules = (categoryTopByGraph?.[cat] || []).slice(0, 3) as BusinessRule[];
|
|
391
|
-
if (!topRules || topRules.length === 0) return null;
|
|
392
|
-
return (
|
|
393
|
-
<div key={cat} className="pt-3 first:pt-0">
|
|
394
|
-
<div className={'text-xs font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>{cat}</div>
|
|
395
|
-
<div className="space-y-1">
|
|
396
|
-
{topRules.map((r, idx: number) => (
|
|
397
|
-
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
398
|
-
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
399
|
-
<ProviderIcon name={r.provider} />
|
|
400
|
-
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
401
|
-
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
402
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
403
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
404
|
-
</div>
|
|
405
|
-
</div>
|
|
406
|
-
</span>
|
|
407
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
408
|
-
</div>
|
|
409
|
-
))}
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
);
|
|
413
|
-
})}
|
|
414
|
-
</div>
|
|
254
|
+
<TopContributingFactors categories={genreMapping?.['Risk'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
|
|
415
255
|
</Reveal>
|
|
416
256
|
</div>
|
|
417
257
|
|
|
@@ -432,76 +272,9 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
432
272
|
</Reveal>
|
|
433
273
|
) : (
|
|
434
274
|
<>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const cslDetails = ss?.csl_details && (Array.isArray(ss.csl_details) ? ss.csl_details.length > 0 : Object.keys(ss.csl_details).length > 0);
|
|
439
|
-
const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
|
|
440
|
-
if (!(ofacMatches || cslDetails || fbiMatches)) return null;
|
|
441
|
-
return (
|
|
442
|
-
<Reveal headless={isHeadless} className={'mb-8 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
443
|
-
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>3A. Sanctions Matches</h4>
|
|
444
|
-
{/* OFAC matches */}
|
|
445
|
-
{ofacMatches && (
|
|
446
|
-
<div className={'mb-4'}>
|
|
447
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>OFAC API Matches</h5>
|
|
448
|
-
<div>
|
|
449
|
-
{ss!.ofac_screen!.matches!.map((m, i: number) => {
|
|
450
|
-
const s = m?.sanction;
|
|
451
|
-
const title = s?.name || 'Unknown';
|
|
452
|
-
const programs = (s?.programs && s?.programs.length) ? ` — Programs: ${s?.programs.join(', ')}` : '';
|
|
453
|
-
return (
|
|
454
|
-
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
455
|
-
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
456
|
-
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
457
|
-
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{title}</span>
|
|
458
|
-
<span>{programs}</span>
|
|
459
|
-
{s?.entityLink ? (
|
|
460
|
-
<>
|
|
461
|
-
{' '}— <a href={s.entityLink} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a>
|
|
462
|
-
</>
|
|
463
|
-
) : null}
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
);
|
|
467
|
-
})}
|
|
468
|
-
</div>
|
|
469
|
-
<details className={'mt-2'}>
|
|
470
|
-
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View OFAC Raw JSON</summary>
|
|
471
|
-
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.ofac_screen!.raw, null, 2)}</pre>
|
|
472
|
-
</details>
|
|
473
|
-
</div>
|
|
474
|
-
)}
|
|
475
|
-
{/* CSL details */}
|
|
476
|
-
{cslDetails && (
|
|
477
|
-
<div className={'mb-4'}>
|
|
478
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>U.S. CSL Details</h5>
|
|
479
|
-
<details>
|
|
480
|
-
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View CSL Raw JSON</summary>
|
|
481
|
-
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.csl_details, null, 2)}</pre>
|
|
482
|
-
</details>
|
|
483
|
-
</div>
|
|
484
|
-
)}
|
|
485
|
-
{/* FBI matches */}
|
|
486
|
-
{fbiMatches && (
|
|
487
|
-
<div className={'mb-2'}>
|
|
488
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>FBI Wanted List Matches</h5>
|
|
489
|
-
<div>
|
|
490
|
-
{ss!.fbi_matches!.map((f, i: number) => (
|
|
491
|
-
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
492
|
-
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
493
|
-
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
494
|
-
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{f.title || 'Match'}</span>
|
|
495
|
-
{f.url ? <> — <a href={f.url} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a></> : null}
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
))}
|
|
499
|
-
</div>
|
|
500
|
-
</div>
|
|
501
|
-
)}
|
|
502
|
-
</Reveal>
|
|
503
|
-
);
|
|
504
|
-
})()}
|
|
275
|
+
<Reveal headless={isHeadless}>
|
|
276
|
+
<SanctionsMatches ss={assessmentResult?.screening_sources} title={'3A. Sanctions Matches'} />
|
|
277
|
+
</Reveal>
|
|
505
278
|
<Reveal headless={isHeadless}>
|
|
506
279
|
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
507
280
|
</Reveal>
|
|
@@ -522,63 +295,9 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
522
295
|
|
|
523
296
|
{/* Skills */}
|
|
524
297
|
<Reveal headless={isHeadless}>
|
|
525
|
-
<
|
|
526
|
-
<h4 id="appendix-skills" className={'text-lg font-bold mb-1'} style={{ color: 'var(--text-main)' }}>Skills</h4>
|
|
527
|
-
<div className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
|
|
528
|
-
Skills are grouped by evidence: Observed when demonstrated in code, Self-reported when declared by the developer without independent verification, and Certified when confirmed through a credential. These categories distinguish between use, claim, and third-party validation.
|
|
529
|
-
</div>
|
|
530
|
-
<SkillsAppendixTable skillsAll={skillsAll} />
|
|
531
|
-
</div>
|
|
298
|
+
<AppendixContent graphInsights={graphInsights} updatedAt={updatedAt} developerName={developerName} genreMapping={genreMapping as Record<string, string[]>} screeningSources={!badgeData.optOutScreening ? screening_sources : undefined} headless={isHeadless} skillsAll={skillsAll} />
|
|
532
299
|
</Reveal>
|
|
533
300
|
|
|
534
|
-
{/* Observations */}
|
|
535
|
-
{Array.isArray(graphInsights?.business_rules_all) && graphInsights.business_rules_all.length > 0 && (
|
|
536
|
-
<Reveal headless={isHeadless}>
|
|
537
|
-
<div>
|
|
538
|
-
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
|
|
539
|
-
<AppendixTables
|
|
540
|
-
type="business_rules"
|
|
541
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
542
|
-
sources={graphInsights.business_rules_all as any}
|
|
543
|
-
searchedAt={updatedAt}
|
|
544
|
-
developerName={developerName || 'this developer'}
|
|
545
|
-
genreMapping={genreMapping as Record<string, string[]>}
|
|
546
|
-
headless={isHeadless}
|
|
547
|
-
/>
|
|
548
|
-
</div>
|
|
549
|
-
</Reveal>
|
|
550
|
-
)}
|
|
551
|
-
|
|
552
|
-
{/* Sanctions & Watchlists */}
|
|
553
|
-
{!badgeData.optOutScreening && screening_sources && (
|
|
554
|
-
<Reveal headless={isHeadless}>
|
|
555
|
-
<div>
|
|
556
|
-
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
557
|
-
{(() => {
|
|
558
|
-
const ofacProvided = screening_sources.ofac_screen?.sources || [];
|
|
559
|
-
const lists = [...(screening_sources.ofac_lists || []), ...ofacProvided];
|
|
560
|
-
const seen: { [k: string]: boolean } = {};
|
|
561
|
-
const dedup: string[] = [];
|
|
562
|
-
for (let i = 0; i < lists.length; i++) {
|
|
563
|
-
const val = lists[i];
|
|
564
|
-
if (!seen[val]) { seen[val] = true; dedup.push(val); }
|
|
565
|
-
}
|
|
566
|
-
const detailed = screening_sources.sanctions_sources_detailed || [];
|
|
567
|
-
const useDetailed = detailed && detailed.length > 0;
|
|
568
|
-
return (
|
|
569
|
-
<AppendixTables
|
|
570
|
-
type="sanctions"
|
|
571
|
-
sources={useDetailed ? detailed : dedup}
|
|
572
|
-
searchedAt={updatedAt}
|
|
573
|
-
developerName={developerName || 'this developer'}
|
|
574
|
-
headless={isHeadless}
|
|
575
|
-
/>
|
|
576
|
-
);
|
|
577
|
-
})()}
|
|
578
|
-
</div>
|
|
579
|
-
</Reveal>
|
|
580
|
-
)}
|
|
581
|
-
|
|
582
301
|
</div>
|
|
583
302
|
</div>
|
|
584
303
|
|
|
@@ -25,6 +25,12 @@ import Reveal from './components/Reveal';
|
|
|
25
25
|
import { formatLocalDateTime } from './utils/date';
|
|
26
26
|
import ChatWidget from './chat/ChatWidget';
|
|
27
27
|
import UseCases from './components/UseCases';
|
|
28
|
+
import SummaryCards from './components/SummaryCards';
|
|
29
|
+
import TopContributingFactors from './components/TopContributingFactors';
|
|
30
|
+
import AiUsageBody from './components/AiUsageBody';
|
|
31
|
+
import SanctionsMatches from './components/SanctionsMatches';
|
|
32
|
+
import AppendixContent from './components/AppendixContent';
|
|
33
|
+
import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy, getCategoryTooltipCopy, barColor } from './utils/provider';
|
|
28
34
|
import { useEffect, useMemo, useState } from 'react';
|
|
29
35
|
type ChatWidgetProps = Partial<{
|
|
30
36
|
api: string;
|
|
@@ -96,63 +102,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
96
102
|
const skillsMatrix = assessmentResult?.skills_matrix || { skills: [] };
|
|
97
103
|
const skillsAll = assessmentResult?.skills_all || { skills: [] };
|
|
98
104
|
const connected = badgeData?.connectedAccounts || [];
|
|
99
|
-
|
|
100
|
-
const n = (name || '').toLowerCase();
|
|
101
|
-
if (n.includes('github')) return <FaGithub />;
|
|
102
|
-
if (n.includes('gitlab')) return <FaGitlab />;
|
|
103
|
-
if (n.includes('stack')) return <FaStackOverflow />;
|
|
104
|
-
if (n.includes('credly')) return <SiCredly />;
|
|
105
|
-
if (n.includes('fiverr')) return <SiFiverr />;
|
|
106
|
-
if (n.includes('kaggle')) return <FaKaggle />;
|
|
107
|
-
if (n.includes('google')) return <FaGoogle />;
|
|
108
|
-
if (n.includes('linkedin')) return <FaLinkedin />;
|
|
109
|
-
return <span className="inline-block w-3 h-3 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const getProviderDisplayName = (name?: string): string => {
|
|
113
|
-
const n = (name || '').toLowerCase();
|
|
114
|
-
if (n.includes('github')) return 'GitHub';
|
|
115
|
-
if (n.includes('gitlab')) return 'GitLab';
|
|
116
|
-
if (n.includes('stack')) return 'Stack Overflow';
|
|
117
|
-
if (n.includes('credly')) return 'Credly';
|
|
118
|
-
if (n.includes('fiverr')) return 'Fiverr';
|
|
119
|
-
if (n.includes('kaggle')) return 'Kaggle';
|
|
120
|
-
if (n.includes('google')) return 'Google Scholar';
|
|
121
|
-
if (n.includes('linkedin')) return 'LinkedIn';
|
|
122
|
-
return name || 'Provider';
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const getProviderTooltipCopy = (provider?: string): string => {
|
|
126
|
-
const n = (provider || '').toLowerCase();
|
|
127
|
-
if (n.includes('github')) return 'Signals from open-source activity: commits, repos, stars, and collaboration patterns.';
|
|
128
|
-
if (n.includes('gitlab')) return 'Signals from GitLab projects and contributions that indicate delivery and collaboration.';
|
|
129
|
-
if (n.includes('stack')) return 'Signals from Q&A participation such as answers, reputation, and accepted solutions.';
|
|
130
|
-
if (n.includes('credly')) return 'Verified badges and certifications that validate specific skills or achievements.';
|
|
131
|
-
if (n.includes('fiverr')) return 'Client reviews, job history, and delivery consistency across freelance engagements.';
|
|
132
|
-
if (n.includes('kaggle')) return 'Competition results, notebooks, and dataset contributions that reflect analytical skill.';
|
|
133
|
-
if (n.includes('google')) return 'Publications, citations, and scholarly presence indicating research impact.';
|
|
134
|
-
if (n.includes('linkedin')) return 'Professional history, endorsements, and network signals indicating credibility.';
|
|
135
|
-
return 'Signals contributed from this provider relevant to capability and trust.';
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const getCategoryTooltipCopy = (category: string): string => {
|
|
139
|
-
const name = (category || '').toLowerCase();
|
|
140
|
-
if (/network|connection|collab|peer/.test(name)) return 'Signals from the developer’s professional connections, collaborations, and peer recognition.';
|
|
141
|
-
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.';
|
|
142
|
-
if (/skill|cert|assessment|endorse/.test(name)) return 'Signals tied to specific technical abilities, such as verified certifications, assessments, or endorsements.';
|
|
143
|
-
if (/experience|tenure|history/.test(name)) return 'Signals of tenure and diversity of professional or project involvement over time.';
|
|
144
|
-
if (/activity|recency|frequency|engage/.test(name)) return 'Signals of recency and frequency of developer engagement in professional or technical platforms.';
|
|
145
|
-
if (/sanction|legal|criminal|regulatory|ofac|fbi|watchlist/.test(name)) return 'Signals of legal, criminal, or regulatory red flags linked to an identity.';
|
|
146
|
-
if (/identity|authentic|consisten/.test(name)) return 'Signals that indicate whether a developer’s identity is genuine and consistent across platforms.';
|
|
147
|
-
if (/reputation|review|rating|feedback|perceive|peer/.test(name)) return 'Signals of how peers, clients, and communities perceive the developer.';
|
|
148
|
-
if (/geo|jurisdiction|country|region|ip|location/.test(name)) return 'Signals tied to a developer’s geographic or jurisdictional context.';
|
|
149
|
-
if (/security|cyber/.test(name)) return 'Signals of security posture and potential cyber-risk exposure.';
|
|
150
|
-
if (/risk/.test(name)) return 'KYD Risk surfaces signals of authenticity, reputation, and environmental telemetry that indicate potential risks in engaging with a developer.';
|
|
151
|
-
if (/tech|technical/.test(name)) return 'KYD Technical surfaces signals from a developer’s portfolio, skills, experience, activity, and network to indicate the likelihood of technical capability.';
|
|
152
|
-
return 'Share of overall contribution by category based on applied weights.';
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const barColor = (pct: number) => pct >= 60 ? green : pct >= 40 ? '#ffbb54' : '#EC6662';
|
|
105
|
+
|
|
156
106
|
|
|
157
107
|
// Determine Role Fit & Coaching visibility
|
|
158
108
|
const hasEnterpriseMatch = !!assessmentResult?.enterprise_match;
|
|
@@ -256,59 +206,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
256
206
|
</Reveal>
|
|
257
207
|
<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)' }}>
|
|
258
208
|
<Reveal headless={isHeadless} as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-4'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
|
|
259
|
-
<
|
|
260
|
-
{(() => {
|
|
261
|
-
const ui = graphInsights?.uiSummary?.technical || {};
|
|
262
|
-
const pct = Math.round(Number(ui?.percent ?? 0));
|
|
263
|
-
const label = ui?.label || 'EVIDENCE';
|
|
264
|
-
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Technical');
|
|
265
|
-
return (
|
|
266
|
-
<GaugeCard
|
|
267
|
-
key={'technical-card'}
|
|
268
|
-
title={'KYD Technical'}
|
|
269
|
-
description={'The gauge visualization shows a weighted composite of technical evidence, with rightward movement indicating stronger indications of developer capability'}
|
|
270
|
-
percent={pct}
|
|
271
|
-
label={label}
|
|
272
|
-
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
273
|
-
topMoversTitle={'Top Score Movers'}
|
|
274
|
-
/>
|
|
275
|
-
);
|
|
276
|
-
})()}
|
|
277
|
-
{(() => {
|
|
278
|
-
const ui = graphInsights?.uiSummary?.risk || {};
|
|
279
|
-
const pctGood = Math.round(Number(ui?.percent_good ?? 0));
|
|
280
|
-
const label = ui?.label || 'RISK';
|
|
281
|
-
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Risk');
|
|
282
|
-
const tooltip = 'Higher bar filled indicates lower overall risk; movement to the right reflects improved risk posture.';
|
|
283
|
-
return (
|
|
284
|
-
<RiskCard
|
|
285
|
-
title={'KYD Risk'}
|
|
286
|
-
description={'The bar chart visualizes relative risk levels, where shorter bars denote lower risk and taller bars indicate greater exposure.'}
|
|
287
|
-
percentGood={pctGood}
|
|
288
|
-
label={label}
|
|
289
|
-
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
290
|
-
topMoversTitle={'Top Score Movers'}
|
|
291
|
-
tooltipText={tooltip}
|
|
292
|
-
/>
|
|
293
|
-
);
|
|
294
|
-
})()}
|
|
295
|
-
{(() => {
|
|
296
|
-
const ai_usage_summary = assessmentResult?.ai_usage_summary;
|
|
297
|
-
const label = 'AI Transparency';
|
|
298
|
-
const topMovers = ai_usage_summary?.key_findings || [];
|
|
299
|
-
return (
|
|
300
|
-
<GaugeCard
|
|
301
|
-
key={'ai-card'}
|
|
302
|
-
title={'KYD AI (Beta)'}
|
|
303
|
-
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
304
|
-
percent={ai_usage_summary?.transparency_score}
|
|
305
|
-
label={label}
|
|
306
|
-
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
307
|
-
topMoversTitle={'Key Findings'}
|
|
308
|
-
/>
|
|
309
|
-
);
|
|
310
|
-
})()}
|
|
311
|
-
</div>
|
|
209
|
+
<SummaryCards graphInsights={graphInsights} assessmentResult={assessmentResult} topBusinessForGenre={topBusinessForGenre} />
|
|
312
210
|
<div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
|
|
313
211
|
Report Completed: {formatLocalDateTime(updatedAt)}
|
|
314
212
|
</div>
|
|
@@ -394,33 +292,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
394
292
|
</Reveal>
|
|
395
293
|
<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}>
|
|
396
294
|
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
397
|
-
<
|
|
398
|
-
{(genreMapping?.['Technical'] || []).map((cat: string) => {
|
|
399
|
-
const topRules = (categoryTopByGraph?.[cat] || []).slice(0, 3) as BusinessRule[];
|
|
400
|
-
if (!topRules || topRules.length === 0) return null;
|
|
401
|
-
return (
|
|
402
|
-
<div key={cat} className="pt-3 first:pt-0">
|
|
403
|
-
<div className={'text-xs font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>{cat}</div>
|
|
404
|
-
<div className="space-y-1">
|
|
405
|
-
{topRules.map((r, idx: number) => (
|
|
406
|
-
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
407
|
-
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
408
|
-
<ProviderIcon name={r.provider} />
|
|
409
|
-
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
410
|
-
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
411
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
412
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
413
|
-
</div>
|
|
414
|
-
</div>
|
|
415
|
-
</span>
|
|
416
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
417
|
-
</div>
|
|
418
|
-
))}
|
|
419
|
-
</div>
|
|
420
|
-
</div>
|
|
421
|
-
);
|
|
422
|
-
})}
|
|
423
|
-
</div>
|
|
295
|
+
<TopContributingFactors categories={genreMapping?.['Technical'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
|
|
424
296
|
</Reveal>
|
|
425
297
|
</div>
|
|
426
298
|
<Reveal headless={isHeadless}>
|
|
@@ -464,33 +336,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
464
336
|
</Reveal>
|
|
465
337
|
<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}>
|
|
466
338
|
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
467
|
-
<
|
|
468
|
-
{genreMapping?.['Risk']?.map((cat: string) => {
|
|
469
|
-
const topRules = (categoryTopByGraph?.[cat] || []).slice(0, 3) as BusinessRule[];
|
|
470
|
-
if (!topRules || topRules.length === 0) return null;
|
|
471
|
-
return (
|
|
472
|
-
<div key={cat} className="pt-3 first:pt-0">
|
|
473
|
-
<div className={'text-xs font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>{cat}</div>
|
|
474
|
-
<div className="space-y-1">
|
|
475
|
-
{topRules.map((r, idx: number) => (
|
|
476
|
-
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
477
|
-
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
478
|
-
<ProviderIcon name={r.provider} />
|
|
479
|
-
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
480
|
-
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
481
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
482
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
483
|
-
</div>
|
|
484
|
-
</div>
|
|
485
|
-
</span>
|
|
486
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
487
|
-
</div>
|
|
488
|
-
))}
|
|
489
|
-
</div>
|
|
490
|
-
</div>
|
|
491
|
-
);
|
|
492
|
-
})}
|
|
493
|
-
</div>
|
|
339
|
+
<TopContributingFactors categories={genreMapping?.['Risk'] as string[] || []} categoryTopByGraph={categoryTopByGraph as any} />
|
|
494
340
|
</Reveal>
|
|
495
341
|
</div>
|
|
496
342
|
{badgeData.optOutScreening ? (
|
|
@@ -508,73 +354,15 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
508
354
|
</div>
|
|
509
355
|
</Reveal>
|
|
510
356
|
) : (
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
{ofacMatches && (
|
|
521
|
-
<div className={'mb-4'}>
|
|
522
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>OFAC API Matches</h5>
|
|
523
|
-
<div>
|
|
524
|
-
{ss!.ofac_screen!.matches!.map((m, i: number) => {
|
|
525
|
-
const s = m?.sanction;
|
|
526
|
-
const title = s?.name || 'Unknown';
|
|
527
|
-
const programs = (s?.programs && s?.programs.length) ? ` — Programs: ${s?.programs.join(', ')}` : '';
|
|
528
|
-
return (
|
|
529
|
-
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
530
|
-
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
531
|
-
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
532
|
-
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{title}</span>
|
|
533
|
-
<span>{programs}</span>
|
|
534
|
-
{s?.entityLink ? (
|
|
535
|
-
<>
|
|
536
|
-
{' '}— <a href={s.entityLink} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a>
|
|
537
|
-
</>
|
|
538
|
-
) : null}
|
|
539
|
-
</div>
|
|
540
|
-
</div>
|
|
541
|
-
);
|
|
542
|
-
})}
|
|
543
|
-
</div>
|
|
544
|
-
<details className={'mt-2'}>
|
|
545
|
-
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View OFAC Raw JSON</summary>
|
|
546
|
-
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.ofac_screen!.raw, null, 2)}</pre>
|
|
547
|
-
</details>
|
|
548
|
-
</div>
|
|
549
|
-
)}
|
|
550
|
-
{cslDetails && (
|
|
551
|
-
<div className={'mb-4'}>
|
|
552
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>U.S. CSL Details</h5>
|
|
553
|
-
<details>
|
|
554
|
-
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View CSL Raw JSON</summary>
|
|
555
|
-
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.csl_details, null, 2)}</pre>
|
|
556
|
-
</details>
|
|
557
|
-
</div>
|
|
558
|
-
)}
|
|
559
|
-
{fbiMatches && (
|
|
560
|
-
<div className={'mb-2'}>
|
|
561
|
-
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>FBI Wanted List Matches</h5>
|
|
562
|
-
<div>
|
|
563
|
-
{ss!.fbi_matches!.map((f, i: number) => (
|
|
564
|
-
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
565
|
-
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
566
|
-
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
567
|
-
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{f.title || 'Match'}</span>
|
|
568
|
-
{f.url ? <> — <a href={f.url} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a></> : null}
|
|
569
|
-
</div>
|
|
570
|
-
</div>
|
|
571
|
-
))}
|
|
572
|
-
</div>
|
|
573
|
-
</div>
|
|
574
|
-
)}
|
|
575
|
-
</Reveal>
|
|
576
|
-
);
|
|
577
|
-
})()
|
|
357
|
+
|
|
358
|
+
<>
|
|
359
|
+
<Reveal headless={isHeadless} className={'mb-0 mt-6'}>
|
|
360
|
+
<SanctionsMatches ss={assessmentResult?.screening_sources} />
|
|
361
|
+
</Reveal>
|
|
362
|
+
<Reveal headless={isHeadless}>
|
|
363
|
+
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
364
|
+
</Reveal>
|
|
365
|
+
</>
|
|
578
366
|
)}
|
|
579
367
|
</div>
|
|
580
368
|
</div>
|
|
@@ -584,79 +372,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
584
372
|
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
585
373
|
<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)' }}>
|
|
586
374
|
<Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD AI (Beta)</Reveal>
|
|
587
|
-
{
|
|
588
|
-
const ai = assessmentResult?.ai_usage_summary;
|
|
589
|
-
if (!ai) return null;
|
|
590
|
-
const techGauge = {
|
|
591
|
-
percent: Math.max(0, Math.min(100, Number(ai.originality_score ?? 0))),
|
|
592
|
-
label: 'Originality',
|
|
593
|
-
};
|
|
594
|
-
const riskGauge = {
|
|
595
|
-
percent: Math.max(0, Math.min(100, Number(ai.transparency_score ?? 0))),
|
|
596
|
-
label: ai.transparency_descriptor ? `Transparency — ${ai.transparency_descriptor}` : 'Transparency',
|
|
597
|
-
};
|
|
598
|
-
const stats: Array<{ label: string; value: string }>= [
|
|
599
|
-
{ label: 'Files analyzed', value: String(ai.files_analyzed ?? 0) },
|
|
600
|
-
{ label: 'Files with AI findings', value: String(ai.files_with_ai_findings ?? 0) },
|
|
601
|
-
{ label: 'Files with disclosure', value: String(ai.files_with_disclosure ?? 0) },
|
|
602
|
-
{ label: 'Repos analyzed', value: String(ai.repos_analyzed ?? 0) },
|
|
603
|
-
{ label: 'Repos with AI findings', value: String(ai.repos_with_ai_findings ?? 0) },
|
|
604
|
-
];
|
|
605
|
-
const findings = Array.isArray(ai.evidence) ? ai.evidence.slice(0, 5) : [];
|
|
606
|
-
const topMovers = Array.isArray(ai.key_findings) ? ai.key_findings : [];
|
|
607
|
-
return (
|
|
608
|
-
<div className="space-y-8">
|
|
609
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 *:min-h-full">
|
|
610
|
-
<GaugeCard
|
|
611
|
-
key={'ai-originality'}
|
|
612
|
-
title={'Originality'}
|
|
613
|
-
description={'Estimated share of human-authored code based on linguistic signals in comments.'}
|
|
614
|
-
percent={techGauge.percent}
|
|
615
|
-
label={techGauge.label}
|
|
616
|
-
topMovers={[]}
|
|
617
|
-
/>
|
|
618
|
-
<GaugeCard
|
|
619
|
-
key={'ai-transparency'}
|
|
620
|
-
title={'Transparency'}
|
|
621
|
-
description={'Proportion of AI-influenced files that include an explicit disclosure.'}
|
|
622
|
-
percent={riskGauge.percent}
|
|
623
|
-
label={riskGauge.label}
|
|
624
|
-
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
625
|
-
topMoversTitle={'Key Findings'}
|
|
626
|
-
/>
|
|
627
|
-
</div>
|
|
628
|
-
<div className="grid grid-cols-2 sm:grid-cols-5 gap-4">
|
|
629
|
-
{stats.map((s, i) => (
|
|
630
|
-
<div key={i} className={'rounded-md p-3 border'} style={{ borderColor: 'var(--icon-button-secondary)', background: 'var(--content-card-background)' }}>
|
|
631
|
-
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>{s.label}</div>
|
|
632
|
-
<div className={'text-lg font-semibold'} style={{ color: 'var(--text-main)' }}>{s.value}</div>
|
|
633
|
-
</div>
|
|
634
|
-
))}
|
|
635
|
-
</div>
|
|
636
|
-
{findings.length > 0 && (
|
|
637
|
-
<div>
|
|
638
|
-
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Sample Findings</div>
|
|
639
|
-
<div className="space-y-3">
|
|
640
|
-
{findings.map((f, idx) => (
|
|
641
|
-
<div key={idx} className={'rounded-md p-3 border'} style={{ borderColor: 'var(--icon-button-secondary)', background: 'var(--content-card-background)' }}>
|
|
642
|
-
<div className={'text-xs mb-1'} style={{ color: 'var(--text-secondary)' }}>{f.file_path || 'file'}</div>
|
|
643
|
-
{f.snippet ? (
|
|
644
|
-
<pre className={'text-xs overflow-auto p-2 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{f.snippet}</pre>
|
|
645
|
-
) : null}
|
|
646
|
-
{f.reason ? (
|
|
647
|
-
<div className={'text-xs mt-2'} style={{ color: 'var(--text-secondary)' }}>{f.reason}</div>
|
|
648
|
-
) : null}
|
|
649
|
-
</div>
|
|
650
|
-
))}
|
|
651
|
-
</div>
|
|
652
|
-
</div>
|
|
653
|
-
)}
|
|
654
|
-
{ai.explanation && (
|
|
655
|
-
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{ai.explanation}</div>
|
|
656
|
-
)}
|
|
657
|
-
</div>
|
|
658
|
-
);
|
|
659
|
-
})()}
|
|
375
|
+
<AiUsageBody ai={assessmentResult?.ai_usage_summary} />
|
|
660
376
|
</div>
|
|
661
377
|
</div>
|
|
662
378
|
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import GaugeCard from './GaugeCard';
|
|
5
|
+
|
|
6
|
+
export default function AiUsageBody({ ai }: { ai?: {
|
|
7
|
+
explanation?: string;
|
|
8
|
+
key_findings?: string[];
|
|
9
|
+
originality_score?: number;
|
|
10
|
+
transparency_score?: number;
|
|
11
|
+
transparency_descriptor?: string;
|
|
12
|
+
files_with_ai_findings?: number;
|
|
13
|
+
files_analyzed?: number;
|
|
14
|
+
files_with_disclosure?: number;
|
|
15
|
+
repos_analyzed?: number;
|
|
16
|
+
repos_with_ai_findings?: number;
|
|
17
|
+
evidence?: Array<{ file_path?: string; snippet?: string; reason?: string }>;
|
|
18
|
+
} }) {
|
|
19
|
+
if (!ai) return null;
|
|
20
|
+
const techGauge = {
|
|
21
|
+
percent: Math.max(0, Math.min(100, Number(ai.originality_score ?? 0))),
|
|
22
|
+
label: 'Originality',
|
|
23
|
+
};
|
|
24
|
+
const riskGauge = {
|
|
25
|
+
percent: Math.max(0, Math.min(100, Number(ai.transparency_score ?? 0))),
|
|
26
|
+
label: ai.transparency_descriptor ? `Transparency — ${ai.transparency_descriptor}` : 'Transparency',
|
|
27
|
+
};
|
|
28
|
+
const stats: Array<{ label: string; value: string }>= [
|
|
29
|
+
{ label: 'Files analyzed', value: String(ai.files_analyzed ?? 0) },
|
|
30
|
+
{ label: 'Files with AI findings', value: String(ai.files_with_ai_findings ?? 0) },
|
|
31
|
+
{ label: 'Files with disclosure', value: String(ai.files_with_disclosure ?? 0) },
|
|
32
|
+
{ label: 'Repos analyzed', value: String(ai.repos_analyzed ?? 0) },
|
|
33
|
+
{ label: 'Repos with AI findings', value: String(ai.repos_with_ai_findings ?? 0) },
|
|
34
|
+
];
|
|
35
|
+
const findings = Array.isArray(ai.evidence) ? ai.evidence.slice(0, 5) : [];
|
|
36
|
+
const topMovers = Array.isArray(ai.key_findings) ? ai.key_findings : [];
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="space-y-8">
|
|
40
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 *:min-h-full">
|
|
41
|
+
<GaugeCard
|
|
42
|
+
key={'ai-originality'}
|
|
43
|
+
title={'Originality'}
|
|
44
|
+
description={'Estimated share of human-authored code based on linguistic signals in comments.'}
|
|
45
|
+
percent={techGauge.percent}
|
|
46
|
+
label={techGauge.label}
|
|
47
|
+
topMovers={[]}
|
|
48
|
+
/>
|
|
49
|
+
<GaugeCard
|
|
50
|
+
key={'ai-transparency'}
|
|
51
|
+
title={'Transparency'}
|
|
52
|
+
description={'Proportion of AI-influenced files that include an explicit disclosure.'}
|
|
53
|
+
percent={riskGauge.percent}
|
|
54
|
+
label={riskGauge.label}
|
|
55
|
+
topMovers={[]}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="grid grid-cols-2 sm:grid-cols-5 gap-4">
|
|
59
|
+
{stats.map((s, i) => (
|
|
60
|
+
<div key={i} className={'rounded-md p-3 border'} style={{ borderColor: 'var(--icon-button-secondary)', background: 'var(--content-card-background)' }}>
|
|
61
|
+
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>{s.label}</div>
|
|
62
|
+
<div className={'text-lg font-semibold'} style={{ color: 'var(--text-main)' }}>{s.value}</div>
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
{findings.length > 0 && (
|
|
67
|
+
<div>
|
|
68
|
+
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Sample Findings</div>
|
|
69
|
+
<div className="space-y-3">
|
|
70
|
+
{findings.map((f, idx) => (
|
|
71
|
+
<div key={idx} className={'rounded-md p-3 border'} style={{ borderColor: 'var(--icon-button-secondary)', background: 'var(--content-card-background)' }}>
|
|
72
|
+
<div className={'text-xs mb-1'} style={{ color: 'var(--text-secondary)' }}>{f.file_path || 'file'}</div>
|
|
73
|
+
{f.snippet ? (
|
|
74
|
+
<pre className={'text-xs overflow-auto p-2 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{f.snippet}</pre>
|
|
75
|
+
) : null}
|
|
76
|
+
{f.reason ? (
|
|
77
|
+
<div className={'text-xs mt-2'} style={{ color: 'var(--text-secondary)' }}>{f.reason}</div>
|
|
78
|
+
) : null}
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
{ai.explanation && (
|
|
85
|
+
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{ai.explanation}</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import AppendixTables from './AppendixTables';
|
|
5
|
+
import SkillsAppendixTable from './SkillsAppendixTable';
|
|
6
|
+
import { GraphInsightsPayload, SkillsAll } from '../types';
|
|
7
|
+
|
|
8
|
+
export default function AppendixContent({
|
|
9
|
+
graphInsights,
|
|
10
|
+
updatedAt,
|
|
11
|
+
developerName,
|
|
12
|
+
genreMapping,
|
|
13
|
+
screeningSources,
|
|
14
|
+
headless,
|
|
15
|
+
skillsAll,
|
|
16
|
+
}: {
|
|
17
|
+
graphInsights: GraphInsightsPayload;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
developerName?: string;
|
|
20
|
+
genreMapping: Record<string, string[]>;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
screeningSources?: any;
|
|
23
|
+
headless?: boolean;
|
|
24
|
+
skillsAll: SkillsAll;
|
|
25
|
+
}) {
|
|
26
|
+
const ofacProvided = screeningSources?.ofac_screen?.sources || [];
|
|
27
|
+
const lists = [...(screeningSources?.ofac_lists || []), ...ofacProvided];
|
|
28
|
+
const seen: { [k: string]: boolean } = {};
|
|
29
|
+
const dedup: string[] = [];
|
|
30
|
+
for (let i = 0; i < lists.length; i++) {
|
|
31
|
+
const val = lists[i];
|
|
32
|
+
if (!seen[val]) { seen[val] = true; dedup.push(val); }
|
|
33
|
+
}
|
|
34
|
+
const detailed = screeningSources?.sanctions_sources_detailed || [];
|
|
35
|
+
const useDetailed = detailed && detailed.length > 0;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="space-y-10">
|
|
39
|
+
<div>
|
|
40
|
+
<h4 id="appendix-skills" className={'text-lg font-bold mb-1'} style={{ color: 'var(--text-main)' }}>Skills</h4>
|
|
41
|
+
<div className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
|
|
42
|
+
Skills are grouped by evidence: Observed when demonstrated in code, Self-reported when declared by the developer without independent verification, and Certified when confirmed through a credential.
|
|
43
|
+
</div>
|
|
44
|
+
<SkillsAppendixTable skillsAll={skillsAll} />
|
|
45
|
+
</div>
|
|
46
|
+
{Array.isArray(graphInsights?.business_rules_all) && graphInsights.business_rules_all.length > 0 && (
|
|
47
|
+
<div>
|
|
48
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
|
|
49
|
+
<AppendixTables
|
|
50
|
+
type="business_rules"
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
sources={graphInsights.business_rules_all as any}
|
|
53
|
+
searchedAt={updatedAt}
|
|
54
|
+
developerName={developerName || 'this developer'}
|
|
55
|
+
genreMapping={genreMapping as Record<string, string[]>}
|
|
56
|
+
headless={headless}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
{screeningSources && (
|
|
61
|
+
<div>
|
|
62
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
63
|
+
<AppendixTables
|
|
64
|
+
type="sanctions"
|
|
65
|
+
sources={useDetailed ? detailed : dedup}
|
|
66
|
+
searchedAt={updatedAt}
|
|
67
|
+
developerName={developerName || 'this developer'}
|
|
68
|
+
headless={headless}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
export default function SanctionsMatches({ ss, title }: {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
ss?: any;
|
|
8
|
+
title?: string;
|
|
9
|
+
}) {
|
|
10
|
+
if (!ss) return null;
|
|
11
|
+
const ofacMatches = ss?.ofac_screen?.matches && (ss.ofac_screen.matches.length > 0);
|
|
12
|
+
const cslDetails = ss?.csl_details && (Array.isArray(ss.csl_details) ? ss.csl_details.length > 0 : Object.keys(ss.csl_details || {}).length > 0);
|
|
13
|
+
const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
|
|
14
|
+
if (!(ofacMatches || cslDetails || fbiMatches)) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className={'rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
18
|
+
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>{title || 'Sanctions Matches'}</h4>
|
|
19
|
+
{ofacMatches && (
|
|
20
|
+
<div className={'mb-4'}>
|
|
21
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>OFAC API Matches</h5>
|
|
22
|
+
<div>
|
|
23
|
+
{ss!.ofac_screen!.matches!.map((m: any, i: number) => {
|
|
24
|
+
const s = m?.sanction;
|
|
25
|
+
const t = s?.name || 'Unknown';
|
|
26
|
+
const programs = (s?.programs && s?.programs.length) ? ` — Programs: ${s?.programs.join(', ')}` : '';
|
|
27
|
+
return (
|
|
28
|
+
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
29
|
+
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
30
|
+
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
31
|
+
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{t}</span>
|
|
32
|
+
<span>{programs}</span>
|
|
33
|
+
{s?.entityLink ? (
|
|
34
|
+
<>
|
|
35
|
+
{' '}— <a href={s.entityLink} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a>
|
|
36
|
+
</>
|
|
37
|
+
) : null}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</div>
|
|
43
|
+
<details className={'mt-2'}>
|
|
44
|
+
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View OFAC Raw JSON</summary>
|
|
45
|
+
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.ofac_screen!.raw, null, 2)}</pre>
|
|
46
|
+
</details>
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
{cslDetails && (
|
|
50
|
+
<div className={'mb-4'}>
|
|
51
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>U.S. CSL Details</h5>
|
|
52
|
+
<details>
|
|
53
|
+
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View CSL Raw JSON</summary>
|
|
54
|
+
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.csl_details, null, 2)}</pre>
|
|
55
|
+
</details>
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
{fbiMatches && (
|
|
59
|
+
<div className={'mb-2'}>
|
|
60
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>FBI Wanted List Matches</h5>
|
|
61
|
+
<div>
|
|
62
|
+
{ss!.fbi_matches!.map((f: any, i: number) => (
|
|
63
|
+
<div key={i} style={{ display: 'grid', gridTemplateColumns: '12px 1fr', columnGap: 8, alignItems: 'start', marginBottom: 6 }}>
|
|
64
|
+
<div aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 6, backgroundColor: 'var(--text-secondary)' }} />
|
|
65
|
+
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
66
|
+
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{f.title || 'Match'}</span>
|
|
67
|
+
{f.url ? <> — <a href={f.url} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a></> : null}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import GaugeCard from './GaugeCard';
|
|
5
|
+
import RiskCard from './RiskCard';
|
|
6
|
+
import { GraphInsightsPayload, AssessmentResult, TopBusinessRule } from '../types';
|
|
7
|
+
|
|
8
|
+
export default function SummaryCards({ graphInsights, assessmentResult, topBusinessForGenre }: {
|
|
9
|
+
graphInsights: GraphInsightsPayload;
|
|
10
|
+
assessmentResult: AssessmentResult;
|
|
11
|
+
topBusinessForGenre: (genre: string) => TopBusinessRule[];
|
|
12
|
+
}) {
|
|
13
|
+
const uiTech = graphInsights?.uiSummary?.technical || {};
|
|
14
|
+
const techPct = Math.round(Number(uiTech?.percent ?? 0));
|
|
15
|
+
const techLabel = uiTech?.label || 'EVIDENCE';
|
|
16
|
+
const techTop = uiTech?.top_movers && uiTech.top_movers.length > 0 ? uiTech.top_movers : topBusinessForGenre('Technical');
|
|
17
|
+
|
|
18
|
+
const uiRisk = graphInsights?.uiSummary?.risk || {};
|
|
19
|
+
const riskPctGood = Math.round(Number(uiRisk?.percent_good ?? 0));
|
|
20
|
+
const riskLabel = uiRisk?.label || 'RISK';
|
|
21
|
+
const riskTop = uiRisk?.top_movers && uiRisk.top_movers.length > 0 ? uiRisk.top_movers : topBusinessForGenre('Risk');
|
|
22
|
+
|
|
23
|
+
const ai = assessmentResult?.ai_usage_summary;
|
|
24
|
+
const aiLabel = 'AI Transparency';
|
|
25
|
+
const aiTopMovers = ai?.key_findings || [];
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 *:min-h-full">
|
|
29
|
+
<GaugeCard
|
|
30
|
+
key={'technical-card'}
|
|
31
|
+
title={'KYD Technical'}
|
|
32
|
+
description={'The gauge visualization shows a weighted composite of technical evidence, with rightward movement indicating stronger indications of developer capability'}
|
|
33
|
+
percent={techPct}
|
|
34
|
+
label={techLabel}
|
|
35
|
+
topMovers={techTop?.map(t => ({ label: (t as any)?.label, uid: (t as any)?.uid }))}
|
|
36
|
+
topMoversTitle={'Top Score Movers'}
|
|
37
|
+
/>
|
|
38
|
+
<RiskCard
|
|
39
|
+
title={'KYD Risk'}
|
|
40
|
+
description={'The bar chart visualizes relative risk levels, where shorter bars denote lower risk and taller bars indicate greater exposure.'}
|
|
41
|
+
percentGood={riskPctGood}
|
|
42
|
+
label={riskLabel}
|
|
43
|
+
topMovers={riskTop?.map(t => ({ label: (t as any)?.label, uid: (t as any)?.uid }))}
|
|
44
|
+
topMoversTitle={'Top Score Movers'}
|
|
45
|
+
tooltipText={'Higher bar filled indicates lower overall risk; movement to the right reflects improved risk posture.'}
|
|
46
|
+
/>
|
|
47
|
+
<GaugeCard
|
|
48
|
+
key={'ai-card'}
|
|
49
|
+
title={'KYD AI (Beta)'}
|
|
50
|
+
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
51
|
+
percent={ai?.transparency_score}
|
|
52
|
+
label={aiLabel}
|
|
53
|
+
topMovers={(aiTopMovers).map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
54
|
+
topMoversTitle={'Key Findings'}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { BusinessRule } from '../types';
|
|
5
|
+
import BusinessRuleLink from './BusinessRuleLink';
|
|
6
|
+
import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy } from '../utils/provider';
|
|
7
|
+
|
|
8
|
+
export default function TopContributingFactors({
|
|
9
|
+
categories,
|
|
10
|
+
categoryTopByGraph,
|
|
11
|
+
}: {
|
|
12
|
+
categories: string[];
|
|
13
|
+
categoryTopByGraph: { [category: string]: BusinessRule[] };
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-4">
|
|
17
|
+
{(categories || []).map((cat: string) => {
|
|
18
|
+
const topRules = (categoryTopByGraph?.[cat] || []).slice(0, 3) as BusinessRule[];
|
|
19
|
+
if (!topRules || topRules.length === 0) return null;
|
|
20
|
+
return (
|
|
21
|
+
<div key={cat} className="pt-3 first:pt-0">
|
|
22
|
+
<div className={'text-xs font-semibold mb-1'} style={{ color: 'var(--text-main)' }}>{cat}</div>
|
|
23
|
+
<div className="space-y-1">
|
|
24
|
+
{topRules.map((r, idx: number) => (
|
|
25
|
+
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
26
|
+
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
27
|
+
<ProviderIcon name={r.provider} />
|
|
28
|
+
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
29
|
+
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
30
|
+
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
31
|
+
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</span>
|
|
35
|
+
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
36
|
+
</div>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } from 'react-icons/fa';
|
|
5
|
+
import { SiCredly, SiFiverr } from 'react-icons/si';
|
|
6
|
+
|
|
7
|
+
export const ProviderIcon = ({ name }: { name?: string }) => {
|
|
8
|
+
const n = (name || '').toLowerCase();
|
|
9
|
+
if (n.includes('github')) return <FaGithub />;
|
|
10
|
+
if (n.includes('gitlab')) return <FaGitlab />;
|
|
11
|
+
if (n.includes('stack')) return <FaStackOverflow />;
|
|
12
|
+
if (n.includes('credly')) return <SiCredly />;
|
|
13
|
+
if (n.includes('fiverr')) return <SiFiverr />;
|
|
14
|
+
if (n.includes('kaggle')) return <FaKaggle />;
|
|
15
|
+
if (n.includes('google')) return <FaGoogle />;
|
|
16
|
+
if (n.includes('linkedin')) return <FaLinkedin />;
|
|
17
|
+
return <span className="inline-block w-3 h-3 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getProviderDisplayName = (name?: string): string => {
|
|
21
|
+
const n = (name || '').toLowerCase();
|
|
22
|
+
if (n.includes('github')) return 'GitHub';
|
|
23
|
+
if (n.includes('gitlab')) return 'GitLab';
|
|
24
|
+
if (n.includes('stack')) return 'Stack Overflow';
|
|
25
|
+
if (n.includes('credly')) return 'Credly';
|
|
26
|
+
if (n.includes('fiverr')) return 'Fiverr';
|
|
27
|
+
if (n.includes('kaggle')) return 'Kaggle';
|
|
28
|
+
if (n.includes('google')) return 'Google Scholar';
|
|
29
|
+
if (n.includes('linkedin')) return 'LinkedIn';
|
|
30
|
+
return name || 'Provider';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getProviderTooltipCopy = (provider?: string): string => {
|
|
34
|
+
const n = (provider || '').toLowerCase();
|
|
35
|
+
if (n.includes('github')) return 'Signals from open-source activity: commits, repos, stars, and collaboration patterns.';
|
|
36
|
+
if (n.includes('gitlab')) return 'Signals from GitLab projects and contributions that indicate delivery and collaboration.';
|
|
37
|
+
if (n.includes('stack')) return 'Signals from Q&A participation such as answers, reputation, and accepted solutions.';
|
|
38
|
+
if (n.includes('credly')) return 'Verified badges and certifications that validate specific skills or achievements.';
|
|
39
|
+
if (n.includes('fiverr')) return 'Client reviews, job history, and delivery consistency across freelance engagements.';
|
|
40
|
+
if (n.includes('kaggle')) return 'Competition results, notebooks, and dataset contributions that reflect analytical skill.';
|
|
41
|
+
if (n.includes('google')) return 'Publications, citations, and scholarly presence indicating research impact.';
|
|
42
|
+
if (n.includes('linkedin')) return 'Professional history, endorsements, and network signals indicating credibility.';
|
|
43
|
+
return 'Signals contributed from this provider relevant to capability and trust.';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const getCategoryTooltipCopy = (category: string): string => {
|
|
47
|
+
const name = (category || '').toLowerCase();
|
|
48
|
+
if (/network|connection|collab|peer/.test(name)) return 'Signals from the developer’s professional connections, collaborations, and peer recognition.';
|
|
49
|
+
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.';
|
|
50
|
+
if (/skill|cert|assessment|endorse/.test(name)) return 'Signals tied to specific technical abilities, such as verified certifications, assessments, or endorsements.';
|
|
51
|
+
if (/experience|tenure|history/.test(name)) return 'Signals of tenure and diversity of professional or project involvement over time.';
|
|
52
|
+
if (/activity|recency|frequency|engage/.test(name)) return 'Signals of recency and frequency of developer engagement in professional or technical platforms.';
|
|
53
|
+
if (/sanction|legal|criminal|regulatory|ofac|fbi|watchlist/.test(name)) return 'Signals of legal, criminal, or regulatory red flags linked to an identity.';
|
|
54
|
+
if (/identity|authentic|consisten/.test(name)) return 'Signals that indicate whether a developer’s identity is genuine and consistent across platforms.';
|
|
55
|
+
if (/reputation|review|rating|feedback|perceive|peer/.test(name)) return 'Signals of how peers, clients, and communities perceive the developer.';
|
|
56
|
+
if (/geo|jurisdiction|country|region|ip|location/.test(name)) return 'Signals tied to a developer’s geographic or jurisdictional context.';
|
|
57
|
+
if (/security|cyber/.test(name)) return 'Signals of security posture and potential cyber-risk exposure.';
|
|
58
|
+
if (/risk/.test(name)) return 'KYD Risk surfaces signals of authenticity, reputation, and environmental telemetry that indicate potential risks in engaging with a developer.';
|
|
59
|
+
if (/tech|technical/.test(name)) return 'KYD Technical surfaces signals from a developer’s portfolio, skills, experience, activity, and network to indicate the likelihood of technical capability.';
|
|
60
|
+
return 'Share of overall contribution by category based on applied weights.';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const barColor = (pct: number) => (pct >= 60 ? '#19C37D' : pct >= 40 ? '#ffbb54' : '#EC6662');
|
|
64
|
+
|
|
65
|
+
|