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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -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 = Math.round(Number(scoringSummary?.overall?.combined?.percent_progress || 0));
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", "Imapct"];
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
- ? (parsedSources as BusinessRuleRow[]).slice().sort((a, b) => (Number(b.weight || 0) - Number(a.weight || 0)))
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 w-1/4'} style={{ color: 'var(--text-secondary)' }}>
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 w-1/4'} style={{ color: 'var(--text-secondary)' }}>{br.category || '—'}</td>
267
- <td className={'px-4 py-4 whitespace-normal text-sm font-medium w-1/4'} style={{ color: 'var(--text-main)' }}>{br.label || '—'}</td>
268
- <td className={'px-4 py-4 whitespace-normal text-sm w-1/4'} style={{ color: 'var(--text-secondary)' }}>{(() => {
269
- const weight = br.weight || 0;
270
- if (weight > 0) return 'Positive';
271
- if (weight < 0) return 'Negative';
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
  }