kyd-shared-badge 0.2.31 → 0.2.33
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/SharedBadgeDisplay.tsx +2 -5
- package/src/components/AppendixTables.tsx +43 -12
- package/src/types.ts +5 -0
package/package.json
CHANGED
|
@@ -49,14 +49,11 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
49
49
|
const graphInsights: GraphInsightsPayload = (assessmentResult)?.graph_insights || {} as GraphInsightsPayload;
|
|
50
50
|
const scoringSummary: ScoringSummary = (assessmentResult)?.scoring_summary || {} as ScoringSummary;
|
|
51
51
|
|
|
52
|
-
// const devTrustScore = summary_scores.developer_trust;
|
|
53
|
-
// const riskScore = summary_scores.risk_score;
|
|
54
|
-
// const aiUsageScore = summary_scores.ai_usage;
|
|
55
52
|
|
|
56
53
|
const wrapperMaxWidth = 'max-w-5xl';
|
|
57
54
|
|
|
58
55
|
// Overall and genre scores
|
|
59
|
-
const overallFinalPercent =
|
|
56
|
+
const overallFinalPercent = assessmentResult?.final_percent || 0;
|
|
60
57
|
|
|
61
58
|
// Build category display grouped by genres from scoring_summary.config.genre_mapping
|
|
62
59
|
const genreMapping = (scoringSummary?.config?.genre_mapping) || {};
|
|
@@ -155,7 +152,6 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
155
152
|
className={'rounded-xl shadow-xl p-6 sm:p-8 mt-8 border'}
|
|
156
153
|
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
157
154
|
>
|
|
158
|
-
|
|
159
155
|
<div className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
160
156
|
<div className="pt-8 first:pt-0">
|
|
161
157
|
<Reveal as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
|
|
@@ -483,6 +479,7 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
483
479
|
sources={graphInsights.business_rules_all as any}
|
|
484
480
|
searchedAt={updatedAt}
|
|
485
481
|
developerName={developerName || 'this developer'}
|
|
482
|
+
genreMapping={genreMapping as Record<string, string[]>}
|
|
486
483
|
/>
|
|
487
484
|
</div>
|
|
488
485
|
</Reveal>
|
|
@@ -36,6 +36,8 @@ interface AppendixTableProps {
|
|
|
36
36
|
sources: (string | DomainCSVRow | SanctionSource | BusinessRuleRow)[];
|
|
37
37
|
searchedAt: string;
|
|
38
38
|
developerName: string;
|
|
39
|
+
// Used for business_rules: map genres (e.g., Technical/Risk) to category arrays
|
|
40
|
+
genreMapping?: Record<string, string[]>;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const SanctionsRow = ({
|
|
@@ -125,7 +127,7 @@ const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource
|
|
|
125
127
|
);
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName }) => {
|
|
130
|
+
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName, genreMapping }) => {
|
|
129
131
|
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
130
132
|
const [expanded, setExpanded] = useState<{ [k: number]: boolean }>({});
|
|
131
133
|
|
|
@@ -160,7 +162,22 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
160
162
|
? ["Issuing Entity", "List Name", "List Searched On", "Value", "Findings"]
|
|
161
163
|
: type === 'domains'
|
|
162
164
|
? ["Country", "Entity Type", "Entity/Domain", "Searched On", "Value", "Findings"]
|
|
163
|
-
: ["Platform", "Category", "Observation", "
|
|
165
|
+
: ["Platform", "KYD Pillar", "Category", "Observation", "Weight Impact"];
|
|
166
|
+
|
|
167
|
+
// Build quick lookup: category -> KYD Pillar (e.g., Technical or Risk)
|
|
168
|
+
const categoryToPillar: Record<string, string> = (() => {
|
|
169
|
+
const map: Record<string, string> = {};
|
|
170
|
+
try {
|
|
171
|
+
const gm = genreMapping || {};
|
|
172
|
+
Object.keys(gm || {}).forEach((genre) => {
|
|
173
|
+
const cats = (gm as Record<string, string[]>)[genre] || [];
|
|
174
|
+
cats.forEach((c) => {
|
|
175
|
+
if (typeof c === 'string') map[c] = genre;
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
} catch {}
|
|
179
|
+
return map;
|
|
180
|
+
})();
|
|
164
181
|
|
|
165
182
|
const parsedSources = type === 'sanctions'
|
|
166
183
|
? (sources).map((src) => {
|
|
@@ -179,18 +196,32 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
179
196
|
}))
|
|
180
197
|
: (sources as BusinessRuleRow[]).map(s => ({
|
|
181
198
|
provider: s.provider,
|
|
199
|
+
// Keep original category label; pillar derived from genreMapping
|
|
182
200
|
category: s.category || '',
|
|
183
201
|
label: s.label || '',
|
|
184
202
|
likert_label: s.likert_label || '',
|
|
185
203
|
likert_value: s.likert_value || 0,
|
|
186
204
|
weight: s.weight || 0,
|
|
187
205
|
uid: s.uid || '',
|
|
206
|
+
pillar: categoryToPillar[(s.category || '')] || '',
|
|
188
207
|
}));
|
|
189
208
|
|
|
190
209
|
const sortedParsedSources = (type === 'sanctions')
|
|
191
210
|
? (parsedSources as SanctionSource[]).slice().sort((a, b) => (b.matched === true ? 1 : 0) - (a.matched === true ? 1 : 0))
|
|
192
211
|
: type === 'business_rules'
|
|
193
|
-
? (
|
|
212
|
+
? (() => {
|
|
213
|
+
const orderForPillar = (p: string) => (p === 'Technical' ? 0 : p === 'Risk' ? 1 : 2);
|
|
214
|
+
return (parsedSources as (BusinessRuleRow & { pillar?: string })[]).slice().sort((a, b) => {
|
|
215
|
+
const ap = (a as any).pillar || '';
|
|
216
|
+
const bp = (b as any).pillar || '';
|
|
217
|
+
const cmpPillar = orderForPillar(ap) - orderForPillar(bp);
|
|
218
|
+
if (cmpPillar !== 0) return cmpPillar;
|
|
219
|
+
const ac = (a.category || '').localeCompare(b.category || '');
|
|
220
|
+
if (ac !== 0) return ac;
|
|
221
|
+
// Within same category, sort by descending weight for usefulness
|
|
222
|
+
return Number(b.weight || 0) - Number(a.weight || 0);
|
|
223
|
+
});
|
|
224
|
+
})()
|
|
194
225
|
: parsedSources;
|
|
195
226
|
|
|
196
227
|
const visibleParsedSources = (type === 'business_rules')
|
|
@@ -242,7 +273,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
242
273
|
if (type === 'domains') {
|
|
243
274
|
return <DomainRow key={index} source={source as DomainSource} searchedAt={formattedDate} developerName={developerName} />
|
|
244
275
|
}
|
|
245
|
-
const br = source as BusinessRuleRow;
|
|
276
|
+
const br = source as BusinessRuleRow & { pillar?: string };
|
|
246
277
|
const anchorId = br.uid ? `rule-${br.uid}` : undefined;
|
|
247
278
|
const ProviderIcon = ({ name }: { name?: string }) => {
|
|
248
279
|
const n = (name || '').toLowerCase();
|
|
@@ -258,18 +289,18 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
258
289
|
};
|
|
259
290
|
return (
|
|
260
291
|
<tr id={anchorId} key={index} className={'transition-colors hover:bg-black/5'}>
|
|
261
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
292
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
262
293
|
<span title={br.provider || ''} className={'inline-flex items-center text-4xl'} style={{ color: 'var(--text-main)' }}>
|
|
263
294
|
<ProviderIcon name={br.provider} />
|
|
264
295
|
</span>
|
|
265
296
|
</td>
|
|
266
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
267
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
268
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (weight
|
|
272
|
-
return '
|
|
297
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.pillar || '—'}</td>
|
|
298
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.category || '—'}</td>
|
|
299
|
+
<td className={'px-4 py-4 whitespace-normal text-sm font-medium'} style={{ color: 'var(--text-main)' }}>{br.label || '—'}</td>
|
|
300
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{(() => {
|
|
301
|
+
const weight = Number(br.weight || 0);
|
|
302
|
+
if (!Number.isFinite(weight)) return '—';
|
|
303
|
+
return weight.toFixed(3).replace(/\.000$/, '.0');
|
|
273
304
|
})()}</td>
|
|
274
305
|
</tr>
|
|
275
306
|
);
|
package/src/types.ts
CHANGED
|
@@ -156,6 +156,7 @@ export interface FBIWantedMatch {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export interface AssessmentResult {
|
|
159
|
+
final_percent: number;
|
|
159
160
|
report_summary?: string;
|
|
160
161
|
recommendations: {
|
|
161
162
|
summary?: string;
|
|
@@ -238,6 +239,10 @@ export interface ClientMetadata {
|
|
|
238
239
|
// --- Scoring Summary (from rules engine) ---
|
|
239
240
|
export interface ScoringSummaryConfig {
|
|
240
241
|
coldstart_fraction?: number;
|
|
242
|
+
coldstart_mapping?: {
|
|
243
|
+
default?: number;
|
|
244
|
+
by_category?: { [category: string]: number };
|
|
245
|
+
};
|
|
241
246
|
system_weights?: { [k: string]: number };
|
|
242
247
|
genre_mapping?: { [genre: string]: string[] };
|
|
243
248
|
}
|