kyd-shared-badge 0.3.34 → 0.3.36
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 +608 -0
- package/src/SharedBadgeDisplay.tsx +514 -396
- package/src/components/CategoryBars.tsx +45 -2
- package/src/components/Skills.tsx +13 -4
- package/src/components/SkillsAppendixTable.tsx +65 -42
- package/src/index.ts +1 -0
- package/src/types.ts +3 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { FiAlertTriangle } from 'react-icons/fi';
|
|
4
|
-
import { PublicBadgeData, GraphInsightsPayload, ScoringSummary, BusinessRule, TopBusinessRule } from './types';
|
|
4
|
+
import { PublicBadgeData, GraphInsightsPayload, ScoringSummary, BusinessRule, TopBusinessRule, EnterpriseMatch } from './types';
|
|
5
5
|
// import ShareButton from './components/ShareButton';
|
|
6
6
|
import ReportHeader from './components/ReportHeader';
|
|
7
7
|
import EnterpriseCoaching from './components/EnterpriseCoaching';
|
|
@@ -25,6 +25,7 @@ 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 { useEffect, useMemo, useState } from 'react';
|
|
28
29
|
type ChatWidgetProps = Partial<{
|
|
29
30
|
api: string;
|
|
30
31
|
title: string;
|
|
@@ -52,7 +53,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
52
53
|
} = badgeData;
|
|
53
54
|
|
|
54
55
|
const {
|
|
55
|
-
report_summary,
|
|
56
|
+
// report_summary,
|
|
56
57
|
// developer_trust_explanation,
|
|
57
58
|
screening_sources,
|
|
58
59
|
// industry_considerations
|
|
@@ -72,6 +73,14 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
72
73
|
const genreMapping = (scoringSummary?.config?.genre_mapping) || {};
|
|
73
74
|
const categoryScores = (scoringSummary?.category_scores) || {};
|
|
74
75
|
const categoryTopByGraph = (graphInsights?.categoryTopBusiness) || {};
|
|
76
|
+
const roleTargets: Record<string, number> | undefined = (() => {
|
|
77
|
+
try {
|
|
78
|
+
const em: EnterpriseMatch | undefined = assessmentResult?.enterprise_match;
|
|
79
|
+
return (em && em.role && em.role.categoryTargets) ? (em.role.categoryTargets as Record<string, number>) : undefined;
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
75
84
|
|
|
76
85
|
const topBusinessForGenre = (genre: string) => {
|
|
77
86
|
const cats: string[] = (genreMapping)?.[genre] || [];
|
|
@@ -145,23 +154,88 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
145
154
|
|
|
146
155
|
const barColor = (pct: number) => pct >= 60 ? green : pct >= 40 ? '#ffbb54' : '#EC6662';
|
|
147
156
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
// Determine Role Fit & Coaching visibility
|
|
158
|
+
const hasEnterpriseMatch = !!assessmentResult?.enterprise_match;
|
|
159
|
+
const hasCoaching = !!(assessmentResult?.enterprise_match?.coaching && Array.isArray(assessmentResult.enterprise_match.coaching.items) && assessmentResult.enterprise_match.coaching.items.length > 0);
|
|
160
|
+
const hasUseCases = !!(assessmentResult?.enterprise_use_cases?.items && assessmentResult.enterprise_use_cases.items.length > 0);
|
|
161
|
+
const showRoleFit = hasEnterpriseMatch || hasCoaching || hasUseCases;
|
|
162
|
+
|
|
163
|
+
// Simple tabs for non-headless view
|
|
164
|
+
const tabs = useMemo(() => {
|
|
165
|
+
const arr = [
|
|
166
|
+
{ key: 'overview', label: 'Overview' },
|
|
167
|
+
...(showRoleFit ? [{ key: 'role', label: 'Role Fit & Coaching' }] : []),
|
|
168
|
+
{ key: 'technical', label: 'KYD Technical' },
|
|
169
|
+
{ key: 'risk', label: 'KYD Risk' },
|
|
170
|
+
{ key: 'ai', label: 'KYD AI' },
|
|
171
|
+
{ key: 'appendix', label: 'Appendix' }
|
|
172
|
+
];
|
|
173
|
+
return arr;
|
|
174
|
+
}, [showRoleFit]);
|
|
175
|
+
|
|
176
|
+
const [activeTab, setActiveTab] = useState<string>('overview');
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
const handleHashChange = () => {
|
|
180
|
+
try {
|
|
181
|
+
const hash = (typeof window !== 'undefined' && window.location.hash ? window.location.hash.substring(1) : '').toLowerCase();
|
|
182
|
+
const keys = new Set(tabs.map(t => t.key));
|
|
183
|
+
if (hash && keys.has(hash)) setActiveTab(hash);
|
|
184
|
+
// If the hash targets a skills appendix anchor, switch to Appendix
|
|
185
|
+
if (hash && (hash.startsWith('appendix-skills') || hash.includes('appendix-skills-cat-'))) {
|
|
186
|
+
setActiveTab('appendix');
|
|
187
|
+
}
|
|
188
|
+
} catch {}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
handleHashChange(); // Run on mount and when tabs change
|
|
192
|
+
|
|
193
|
+
if (typeof window !== 'undefined') {
|
|
194
|
+
window.addEventListener('hashchange', handleHashChange);
|
|
195
|
+
return () => {
|
|
196
|
+
window.removeEventListener('hashchange', handleHashChange);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}, [tabs]);
|
|
200
|
+
|
|
201
|
+
const computeStickyTop = (): string | number => {
|
|
202
|
+
if (isHeadless) return 0;
|
|
203
|
+
const ho = chatProps?.headerOffset;
|
|
204
|
+
if (ho === 'none') return 0;
|
|
205
|
+
if (typeof ho === 'number') return ho;
|
|
206
|
+
if (ho === 'auto') return 'var(--kyd-tabs-offset, var(--kyd-header-offset, 64px))';
|
|
207
|
+
return 'var(--kyd-tabs-offset, 0px)';
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const TabNav = () => (
|
|
211
|
+
<div className={'sticky z-10'} style={{ top: computeStickyTop(), background: 'var(--background)', borderBottom: '1px solid var(--icon-button-secondary)' }}>
|
|
212
|
+
<div className={`${wrapperMaxWidth} mx-auto px-2 sm:px-0 overflow-x-auto`}>
|
|
213
|
+
<div className="flex gap-1 sm:gap-2">
|
|
214
|
+
{tabs.map(t => (
|
|
215
|
+
<button
|
|
216
|
+
key={t.key}
|
|
217
|
+
onClick={() => {
|
|
218
|
+
setActiveTab(t.key);
|
|
219
|
+
try { if (typeof window !== 'undefined') window.history.replaceState(null, '', `#${t.key}`); } catch {}
|
|
220
|
+
}}
|
|
221
|
+
className={`px-3 sm:px-4 py-2 text-sm rounded-t ${activeTab === t.key ? 'font-semibold' : ''}`}
|
|
222
|
+
style={{
|
|
223
|
+
color: 'var(--text-main)',
|
|
224
|
+
background: activeTab === t.key ? 'var(--content-card-background)' : 'transparent',
|
|
225
|
+
border: activeTab === t.key ? '1px solid var(--icon-button-secondary)' : '1px solid transparent',
|
|
226
|
+
borderBottomColor: 'transparent'
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
{t.label}
|
|
230
|
+
</button>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const OverviewSection = () => (
|
|
238
|
+
<div className={`${wrapperMaxWidth} mx-auto mt-6`}>
|
|
165
239
|
<Reveal headless={isHeadless} offsetY={8} durationMs={500}>
|
|
166
240
|
<ReportHeader
|
|
167
241
|
badgeId={badgeId}
|
|
@@ -170,7 +244,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
170
244
|
score={overallFinalPercent || 0}
|
|
171
245
|
isPublic={true}
|
|
172
246
|
badgeImageUrl={badgeData.badgeImageUrl || ''}
|
|
173
|
-
summary={report_summary}
|
|
247
|
+
summary={assessmentResult?.report_summary || ''}
|
|
174
248
|
enterpriseMatch={(() => {
|
|
175
249
|
const em = assessmentResult?.enterprise_match;
|
|
176
250
|
if (!em) return null;
|
|
@@ -180,403 +254,408 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
180
254
|
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
181
255
|
/>
|
|
182
256
|
</Reveal>
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const ai_usage_summary = assessmentResult?.ai_usage_summary;
|
|
245
|
-
const label = 'AI Transparency'// TODO: calculate label frontend
|
|
246
|
-
const topMovers = ai_usage_summary?.key_findings || []
|
|
247
|
-
return (
|
|
248
|
-
<GaugeCard
|
|
249
|
-
key={'ai-card'}
|
|
250
|
-
title={'KYD AI (Beta)'}
|
|
251
|
-
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
252
|
-
percent={ai_usage_summary?.transparency_score}
|
|
253
|
-
label={label}
|
|
254
|
-
// id non-functional
|
|
255
|
-
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
256
|
-
topMoversTitle={'Key Findings'}
|
|
257
|
-
/>
|
|
258
|
-
);
|
|
259
|
-
})()}
|
|
260
|
-
</div>
|
|
261
|
-
</Reveal>
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
{/* Enterprise Use Cases (directly under the three summary cards) */}
|
|
265
|
-
{assessmentResult?.enterprise_use_cases?.items && assessmentResult.enterprise_use_cases.items.length > 0 && (
|
|
266
|
-
<UseCases useCases={assessmentResult.enterprise_use_cases} headless={isHeadless} badgeId={badgeId} />
|
|
267
|
-
)}
|
|
268
|
-
|
|
269
|
-
{/* Technical Scores */}
|
|
270
|
-
<div className="mt-8" >
|
|
271
|
-
<div key={'Technical'} className='pt-8 space-y-8 kyd-avoid-break' style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
272
|
-
<Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical</Reveal>
|
|
273
|
-
{/* technical graph insights */}
|
|
274
|
-
<Reveal headless={isHeadless}>
|
|
275
|
-
<div className="">
|
|
276
|
-
<GraphInsights
|
|
277
|
-
graphInsights={graphInsights}
|
|
278
|
-
categories={genreMapping?.['Technical'] as string[]}
|
|
279
|
-
genre={'Technical'}
|
|
280
|
-
scoringSummary={scoringSummary}
|
|
281
|
-
headless={isHeadless}
|
|
282
|
-
/>
|
|
283
|
-
</div>
|
|
284
|
-
</Reveal>
|
|
285
|
-
|
|
286
|
-
{/* category bars and contributing factors */}
|
|
287
|
-
<div className="grid grid-cols-1 lg:grid-cols-12 w-full gap-8 items-stretch py-8 border-t" style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
288
|
-
|
|
289
|
-
{/* Left: Bars */}
|
|
290
|
-
<Reveal headless={isHeadless} className="lg:col-span-8 h-full">
|
|
291
|
-
<CategoryBars
|
|
292
|
-
title={'Technical Category Contributions'}
|
|
293
|
-
categories={genreMapping?.['Technical'] as string[]}
|
|
294
|
-
categoryScores={categoryScores}
|
|
295
|
-
barColor={barColor}
|
|
296
|
-
getCategoryTooltipCopy={getCategoryTooltipCopy}
|
|
297
|
-
barHeight={16}
|
|
298
|
-
/>
|
|
299
|
-
</Reveal>
|
|
257
|
+
<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
|
+
<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
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 *:min-h-full">
|
|
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>
|
|
312
|
+
<div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
|
|
313
|
+
Report Completed: {formatLocalDateTime(updatedAt)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
300
318
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
319
|
+
const RoleFitSection = () => (
|
|
320
|
+
!showRoleFit ? null : (
|
|
321
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
322
|
+
<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)' }}>
|
|
323
|
+
<div className="space-y-10">
|
|
324
|
+
{hasEnterpriseMatch && (
|
|
325
|
+
<div>
|
|
326
|
+
<Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Role Alignment</Reveal>
|
|
327
|
+
<Reveal headless={isHeadless}>
|
|
328
|
+
<div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
329
|
+
{(() => {
|
|
330
|
+
const em = assessmentResult?.enterprise_match;
|
|
331
|
+
if (!em) return null;
|
|
332
|
+
const role = em.role || {};
|
|
308
333
|
return (
|
|
309
|
-
<div
|
|
310
|
-
<div className={'
|
|
311
|
-
<div
|
|
312
|
-
{topRules.map((r, idx: number) => (
|
|
313
|
-
<div key={idx} className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
314
|
-
<span className={'relative inline-flex items-center group'} style={{ color: 'var(--text-secondary)' }}>
|
|
315
|
-
<ProviderIcon name={r.provider} />
|
|
316
|
-
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
317
|
-
<div style={{ background: 'var(--content-card-background)', border: '1px solid var(--icon-button-secondary)', color: 'var(--text-main)', padding: 10, borderRadius: 6 }}>
|
|
318
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
319
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
320
|
-
</div>
|
|
321
|
-
</div>
|
|
322
|
-
</span>
|
|
323
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
324
|
-
</div>
|
|
325
|
-
))}
|
|
326
|
-
</div>
|
|
334
|
+
<div>
|
|
335
|
+
<div className={'font-medium mb-1'} style={{ color: 'var(--text-main)' }}>{em.label} {role?.name ? `— ${role.name}` : ''}</div>
|
|
336
|
+
{em.description ? <div>{em.description}</div> : null}
|
|
327
337
|
</div>
|
|
328
338
|
);
|
|
329
|
-
})}
|
|
339
|
+
})()}
|
|
330
340
|
</div>
|
|
331
341
|
</Reveal>
|
|
332
342
|
</div>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
)}
|
|
344
|
+
{hasCoaching && (() => {
|
|
345
|
+
const em = assessmentResult?.enterprise_match;
|
|
346
|
+
const coaching = em?.coaching;
|
|
347
|
+
if (!coaching || !(Array.isArray(coaching.items) && coaching.items.length)) return null;
|
|
348
|
+
return (
|
|
349
|
+
<div>
|
|
350
|
+
<Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Role Alignment Coaching</Reveal>
|
|
351
|
+
<Reveal headless={isHeadless}>
|
|
352
|
+
<EnterpriseCoaching roleName={em?.role?.name} matchLabel={em?.label} coaching={coaching} />
|
|
353
|
+
</Reveal>
|
|
340
354
|
</div>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
355
|
+
);
|
|
356
|
+
})()}
|
|
357
|
+
{hasUseCases && (
|
|
358
|
+
<div>
|
|
359
|
+
<UseCases useCases={assessmentResult?.enterprise_use_cases} headless={isHeadless} badgeId={badgeId} />
|
|
360
|
+
</div>
|
|
361
|
+
)}
|
|
344
362
|
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
)
|
|
366
|
+
);
|
|
345
367
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
<
|
|
364
|
-
{
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
<div className=
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<div
|
|
388
|
-
<
|
|
389
|
-
<
|
|
390
|
-
<div
|
|
391
|
-
|
|
392
|
-
<div style={{ fontWeight: 600 }}>{getProviderDisplayName(r.provider)}</div>
|
|
393
|
-
<div style={{ marginTop: 6, fontSize: 12, color: 'var(--text-secondary)' }}>{getProviderTooltipCopy(r.provider)}</div>
|
|
394
|
-
</div>
|
|
395
|
-
</div>
|
|
396
|
-
</span>
|
|
397
|
-
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
368
|
+
const TechnicalSection = () => (
|
|
369
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
370
|
+
<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)' }}>
|
|
371
|
+
<Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical</Reveal>
|
|
372
|
+
<Reveal headless={isHeadless}>
|
|
373
|
+
<div className="">
|
|
374
|
+
<GraphInsights
|
|
375
|
+
graphInsights={graphInsights}
|
|
376
|
+
categories={genreMapping?.['Technical'] as string[]}
|
|
377
|
+
genre={'Technical'}
|
|
378
|
+
scoringSummary={scoringSummary}
|
|
379
|
+
headless={isHeadless}
|
|
380
|
+
/>
|
|
381
|
+
</div>
|
|
382
|
+
</Reveal>
|
|
383
|
+
<div className="grid grid-cols-1 lg:grid-cols-12 w-full gap-8 items-stretch py-8">
|
|
384
|
+
<Reveal headless={isHeadless} className="lg:col-span-8 h-full">
|
|
385
|
+
<CategoryBars
|
|
386
|
+
title={'Technical Category Contributions'}
|
|
387
|
+
categories={genreMapping?.['Technical'] as string[]}
|
|
388
|
+
categoryScores={categoryScores}
|
|
389
|
+
barColor={barColor}
|
|
390
|
+
getCategoryTooltipCopy={getCategoryTooltipCopy}
|
|
391
|
+
barHeight={16}
|
|
392
|
+
categoryTargets={roleTargets}
|
|
393
|
+
/>
|
|
394
|
+
</Reveal>
|
|
395
|
+
<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
|
+
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
397
|
+
<div className="space-y-4">
|
|
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>
|
|
398
414
|
</div>
|
|
399
|
-
|
|
415
|
+
</span>
|
|
416
|
+
<BusinessRuleLink uid={r.uid} label={r.label} />
|
|
400
417
|
</div>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
418
|
+
))}
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
})}
|
|
423
|
+
</div>
|
|
424
|
+
</Reveal>
|
|
425
|
+
</div>
|
|
426
|
+
<Reveal headless={isHeadless}>
|
|
427
|
+
<div className="pt-8 border-t kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
428
|
+
<h3 className={'text-xl font-bold mb-3 kyd-keep-with-next'} style={{ color: 'var(--text-main)' }}>KYD Technical - Skills Insights</h3>
|
|
429
|
+
<div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
|
|
430
|
+
<Skills skillsMatrix={skillsMatrix} skillsCategoryRadar={graphInsights?.skillsCategoryRadar} headless={isHeadless} />
|
|
406
431
|
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</Reveal>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
);
|
|
407
437
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
438
|
+
const RiskSection = () => (
|
|
439
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
440
|
+
<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)' }}>
|
|
441
|
+
<Reveal headless={isHeadless} as={'h3'} offsetY={8} className={'text-2xl font-bold'} style={{ color: 'var(--text-main)' }}>KYD Risk</Reveal>
|
|
442
|
+
<Reveal headless={isHeadless}>
|
|
443
|
+
<div className="mt-4">
|
|
444
|
+
<GraphInsights
|
|
445
|
+
graphInsights={graphInsights}
|
|
446
|
+
categories={genreMapping?.['Risk'] as string[]}
|
|
447
|
+
genre={'Risk'}
|
|
448
|
+
scoringSummary={scoringSummary}
|
|
449
|
+
headless={isHeadless}
|
|
450
|
+
/>
|
|
451
|
+
</div>
|
|
452
|
+
</Reveal>
|
|
453
|
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 w-full items-stretch py-8 kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
454
|
+
<Reveal headless={isHeadless} className="lg:col-span-8 h-full">
|
|
455
|
+
<CategoryBars
|
|
456
|
+
title={'KYD Risk - Category Insights'}
|
|
457
|
+
categories={genreMapping?.['Risk'] as string[]}
|
|
458
|
+
categoryScores={categoryScores}
|
|
459
|
+
barColor={barColor}
|
|
460
|
+
getCategoryTooltipCopy={getCategoryTooltipCopy}
|
|
461
|
+
barHeight={16}
|
|
462
|
+
categoryTargets={roleTargets}
|
|
463
|
+
/>
|
|
464
|
+
</Reveal>
|
|
465
|
+
<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
|
+
<div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
|
|
467
|
+
<div className="space-y-4">
|
|
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>
|
|
421
490
|
</div>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
491
|
+
);
|
|
492
|
+
})}
|
|
493
|
+
</div>
|
|
494
|
+
</Reveal>
|
|
495
|
+
</div>
|
|
496
|
+
{badgeData.optOutScreening ? (
|
|
497
|
+
<Reveal headless={isHeadless} className={'p-4 rounded-lg border mt-6'} style={{ backgroundColor: 'var(--icon-button-secondary)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
498
|
+
<div className="flex items-start">
|
|
499
|
+
<span className="h-5 w-5 mr-3 mt-0.5 flex-shrink-0" style={{ color: yellow }}>
|
|
500
|
+
<FiAlertTriangle size={20} />
|
|
501
|
+
</span>
|
|
502
|
+
<div>
|
|
503
|
+
<h4 className={'font-bold'} style={{ color: 'var(--text-main)' }}>User Opted Out of Screening</h4>
|
|
504
|
+
<p className={'text-sm mt-1'} style={{ color: 'var(--text-secondary)' }}>
|
|
505
|
+
The user chose not to participate in the automated sanctions and risk screening process. The risk score reflects this decision and is for informational purposes only.
|
|
506
|
+
</p>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
</Reveal>
|
|
510
|
+
) : (
|
|
511
|
+
(() => {
|
|
512
|
+
const ss = assessmentResult?.screening_sources;
|
|
513
|
+
const ofacMatches = ss?.ofac_screen?.matches && (ss.ofac_screen.matches.length > 0);
|
|
514
|
+
const cslDetails = ss?.csl_details && (Array.isArray(ss.csl_details) ? ss.csl_details.length > 0 : Object.keys(ss.csl_details).length > 0);
|
|
515
|
+
const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
|
|
516
|
+
if (!(ofacMatches || cslDetails || fbiMatches)) return null;
|
|
517
|
+
return (
|
|
518
|
+
<Reveal headless={isHeadless} className={'mb-0 mt-6 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
519
|
+
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Sanctions Matches</h4>
|
|
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>
|
|
458
540
|
</div>
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
</div>
|
|
488
|
-
))}
|
|
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}
|
|
489
569
|
</div>
|
|
490
570
|
</div>
|
|
491
|
-
)}
|
|
492
|
-
</
|
|
493
|
-
);
|
|
494
|
-
})()}
|
|
495
|
-
<Reveal headless={isHeadless}>
|
|
496
|
-
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
497
|
-
</Reveal>
|
|
498
|
-
</>
|
|
499
|
-
)}
|
|
500
|
-
|
|
501
|
-
</div>
|
|
502
|
-
|
|
503
|
-
{/* Connected Platforms */}
|
|
504
|
-
<Reveal headless={isHeadless}>
|
|
505
|
-
<ConnectedPlatforms accounts={connected} />
|
|
506
|
-
</Reveal>
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
<div className="pt-8">
|
|
510
|
-
<h3 className={`text-2xl font-bold mb-4 ${isHeadless ? 'kyd-break-before' : ''}`} style={{ color: 'var(--text-main)' }}>Appendix</h3>
|
|
511
|
-
<div className="space-y-8">
|
|
512
|
-
|
|
513
|
-
{/* Skills */}
|
|
514
|
-
<Reveal headless={isHeadless}>
|
|
515
|
-
<div>
|
|
516
|
-
<h4 id="appendix-skills" className={'text-lg font-bold mb-1'} style={{ color: 'var(--text-main)' }}>Skills</h4>
|
|
517
|
-
<div className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
|
|
518
|
-
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.
|
|
571
|
+
))}
|
|
572
|
+
</div>
|
|
519
573
|
</div>
|
|
520
|
-
|
|
521
|
-
</div>
|
|
574
|
+
)}
|
|
522
575
|
</Reveal>
|
|
576
|
+
);
|
|
577
|
+
})()
|
|
578
|
+
)}
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
);
|
|
523
582
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
547
|
-
{(() => {
|
|
548
|
-
const ofacProvided = screening_sources.ofac_screen?.sources || [];
|
|
549
|
-
const lists = [...(screening_sources.ofac_lists || []), ...ofacProvided];
|
|
550
|
-
const seen: { [k: string]: boolean } = {};
|
|
551
|
-
const dedup: string[] = [];
|
|
552
|
-
for (let i = 0; i < lists.length; i++) {
|
|
553
|
-
const val = lists[i];
|
|
554
|
-
if (!seen[val]) { seen[val] = true; dedup.push(val); }
|
|
555
|
-
}
|
|
556
|
-
const detailed = screening_sources.sanctions_sources_detailed || [];
|
|
557
|
-
const useDetailed = detailed && detailed.length > 0;
|
|
558
|
-
return (
|
|
559
|
-
<AppendixTables
|
|
560
|
-
type="sanctions"
|
|
561
|
-
sources={useDetailed ? detailed : dedup}
|
|
562
|
-
searchedAt={updatedAt}
|
|
563
|
-
developerName={developerName || 'this developer'}
|
|
564
|
-
headless={isHeadless}
|
|
565
|
-
/>
|
|
566
|
-
);
|
|
567
|
-
})()}
|
|
568
|
-
</div>
|
|
569
|
-
</Reveal>
|
|
570
|
-
)}
|
|
583
|
+
const AiSection = () => (
|
|
584
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
585
|
+
<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
|
+
{(() => {
|
|
587
|
+
const ai_usage_summary = assessmentResult?.ai_usage_summary;
|
|
588
|
+
const label = 'AI Transparency';
|
|
589
|
+
const topMovers = ai_usage_summary?.key_findings || [];
|
|
590
|
+
return (
|
|
591
|
+
<GaugeCard
|
|
592
|
+
key={'ai-card'}
|
|
593
|
+
title={'KYD AI (Beta)'}
|
|
594
|
+
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
595
|
+
percent={ai_usage_summary?.transparency_score}
|
|
596
|
+
label={label}
|
|
597
|
+
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
598
|
+
topMoversTitle={'Key Findings'}
|
|
599
|
+
/>
|
|
600
|
+
);
|
|
601
|
+
})()}
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
);
|
|
571
605
|
|
|
606
|
+
const AppendixSection = () => (
|
|
607
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
608
|
+
<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)' }}>
|
|
609
|
+
<div className="space-y-10">
|
|
610
|
+
<div>
|
|
611
|
+
<h4 id="appendix-skills" className={'text-lg font-bold mb-1'} style={{ color: 'var(--text-main)' }}>Skills</h4>
|
|
612
|
+
<div className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
|
|
613
|
+
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.
|
|
572
614
|
</div>
|
|
615
|
+
<SkillsAppendixTable skillsAll={skillsAll} />
|
|
573
616
|
</div>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
617
|
+
{Array.isArray(graphInsights?.business_rules_all) && graphInsights.business_rules_all.length > 0 && (
|
|
618
|
+
<div>
|
|
619
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
|
|
620
|
+
<AppendixTables
|
|
621
|
+
type="business_rules"
|
|
622
|
+
sources={graphInsights.business_rules_all}
|
|
623
|
+
searchedAt={updatedAt}
|
|
624
|
+
developerName={developerName || 'this developer'}
|
|
625
|
+
genreMapping={genreMapping as Record<string, string[]>}
|
|
626
|
+
headless={isHeadless}
|
|
627
|
+
/>
|
|
578
628
|
</div>
|
|
579
|
-
|
|
629
|
+
)}
|
|
630
|
+
{!badgeData.optOutScreening && screening_sources && (
|
|
631
|
+
<div>
|
|
632
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
633
|
+
{(() => {
|
|
634
|
+
const ofacProvided = screening_sources.ofac_screen?.sources || [];
|
|
635
|
+
const lists = [...(screening_sources.ofac_lists || []), ...ofacProvided];
|
|
636
|
+
const seen: { [k: string]: boolean } = {};
|
|
637
|
+
const dedup: string[] = [];
|
|
638
|
+
for (let i = 0; i < lists.length; i++) {
|
|
639
|
+
const val = lists[i];
|
|
640
|
+
if (!seen[val]) { seen[val] = true; dedup.push(val); }
|
|
641
|
+
}
|
|
642
|
+
const detailed = screening_sources.sanctions_sources_detailed || [];
|
|
643
|
+
const useDetailed = detailed && detailed.length > 0;
|
|
644
|
+
return (
|
|
645
|
+
<AppendixTables
|
|
646
|
+
type="sanctions"
|
|
647
|
+
sources={useDetailed ? detailed : dedup}
|
|
648
|
+
searchedAt={updatedAt}
|
|
649
|
+
developerName={developerName || 'this developer'}
|
|
650
|
+
headless={isHeadless}
|
|
651
|
+
/>
|
|
652
|
+
);
|
|
653
|
+
})()}
|
|
654
|
+
</div>
|
|
655
|
+
)}
|
|
656
|
+
<div>
|
|
657
|
+
<ConnectedPlatforms accounts={connected} />
|
|
658
|
+
</div>
|
|
580
659
|
</div>
|
|
581
660
|
</div>
|
|
582
661
|
<Reveal headless={isHeadless}>
|
|
@@ -586,11 +665,50 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
586
665
|
</p>
|
|
587
666
|
</footer>
|
|
588
667
|
</Reveal>
|
|
589
|
-
{/* Floating chat widget */}
|
|
590
|
-
{!headless && (
|
|
591
|
-
<ChatWidget api={chatProps?.api || '/api/chat'} badgeId={badgeId} title={chatProps?.title} hintText={chatProps?.hintText} loginPath={chatProps?.loginPath} headerOffset={chatProps?.headerOffset} developerName={developerName} />
|
|
592
|
-
)}
|
|
593
668
|
</div>
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<BusinessRulesProvider items={graphInsights?.business_rules_all}>
|
|
673
|
+
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
674
|
+
{isHeadless ? (
|
|
675
|
+
<>
|
|
676
|
+
<style>
|
|
677
|
+
{`@page { margin: 0; }
|
|
678
|
+
html, body { margin: 0 !important; padding: 0 !important; background: #fff !important; }
|
|
679
|
+
#__next, main { margin: 0 !important; padding: 0 !important; }
|
|
680
|
+
@media print {
|
|
681
|
+
.kyd-break-before { break-before: page; page-break-before: always; }
|
|
682
|
+
.kyd-break-after { break-after: page; page-break-after: always; }
|
|
683
|
+
.kyd-avoid-break { break-inside: avoid; page-break-inside: avoid; }
|
|
684
|
+
.kyd-keep-with-next { break-after: avoid; page-break-after: avoid; }
|
|
685
|
+
}`}
|
|
686
|
+
</style>
|
|
687
|
+
{/* Long-form original layout retained for headless/print */}
|
|
688
|
+
{OverviewSection()}
|
|
689
|
+
{showRoleFit ? RoleFitSection() : null}
|
|
690
|
+
{TechnicalSection()}
|
|
691
|
+
{RiskSection()}
|
|
692
|
+
{AiSection()}
|
|
693
|
+
{AppendixSection()}
|
|
694
|
+
</>
|
|
695
|
+
) : (
|
|
696
|
+
<>
|
|
697
|
+
<TabNav />
|
|
698
|
+
<div className={'px-2 sm:px-0 pb-2'}>
|
|
699
|
+
{activeTab === 'overview' && OverviewSection()}
|
|
700
|
+
{activeTab === 'role' && RoleFitSection()}
|
|
701
|
+
{activeTab === 'technical' && TechnicalSection()}
|
|
702
|
+
{activeTab === 'risk' && RiskSection()}
|
|
703
|
+
{activeTab === 'ai' && AiSection()}
|
|
704
|
+
{activeTab === 'appendix' && AppendixSection()}
|
|
705
|
+
</div>
|
|
706
|
+
</>
|
|
707
|
+
)}
|
|
708
|
+
{!headless && (
|
|
709
|
+
<ChatWidget api={chatProps?.api || '/api/chat'} badgeId={badgeId} title={chatProps?.title} hintText={chatProps?.hintText} loginPath={chatProps?.loginPath} headerOffset={chatProps?.headerOffset} developerName={developerName} />
|
|
710
|
+
)}
|
|
711
|
+
</div>
|
|
594
712
|
</BusinessRulesProvider>
|
|
595
713
|
);
|
|
596
714
|
};
|