# ScoreExplainer

> A 0 to 100 score badge that opens the reasons behind it, one line per dimension with points, source chips, an inferred mark and confidence.

Source: https://docs.nasaqui.com/components/score-explainer

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/score-explainer.json
```

A number people should not have to trust blindly. `ScoreBadge` shows the score in a table cell; pressing it opens the
`ScoreExplainer`: what each dimension contributed and why, which sources back it, whether it was inferred, and how
confident the model is.

## When to use

- Lead, fit or health scores in a list or a record.
- Any AI number that drives a decision.

## When not to use

- A plain metric: use `Meter` or a stat.
- A yes/no verdict.

## Import

```tsx
import { ScoreBadge, ScoreExplainer } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
<ScoreBadge
  score={72}
  confidence={0.8}
  aiGenerated
  dimensions={[
    { id: "size", label: "Company size", points: 30, maxPoints: 40, reason: "51 to 200 employees", sources: [{ label: "LinkedIn", url: "https://example.com" }] },
    { id: "intent", label: "Intent", points: 42, maxPoints: 60, reason: "Visited pricing twice", inferred: true },
  ]}
/>
```

## Anatomy

- Badge: number and band word (High, Medium, Low), a dashed border and "≈" when inferred.
- Explainer: total, summary, confidence meter, dimensions sorted by points, each with a bar, reason, matched words, source chips and an inferred mark. Points that no dimension explains show as "Other".

## API

`ScoreExplainerProps`: `score`, `max` (100), `dimensions`, `summary`, `confidence` (0 to 1), `model`, `aiGenerated`, `compact`, `onSourceClick`, `labels`.

`ScoreDimension`: `{ id, label, points, maxPoints?, reason, matched?, sources?, inferred?, confidence? }`.
`ScoreSource`: `{ label, url?, kind?, inferred? }`.
`ScoreBadge` takes the same props plus `side` and `defaultOpen`.

Pure helpers: `clampScore`, `scoreExplainerBand`, `sortScoreDimensions`, `sumScorePoints`, `scoreRemainder`, `scoreDimensionFill`.

## Examples

Inline (no popover): `<ScoreExplainer score={72} dimensions={dims} />`.

## Accessibility

The badge is a button named "Score 72 of 100, High. Show why". Band is a word, never colour alone. Bars have text values.

## RTL & i18n

English and Arabic ship. Numbers use the locale; source URLs and matched keywords are isolated.

## Styling & tokens

Bands use success, warning and danger tokens. Extend with `className`.

## Do / Don't

- Do mark anything the model guessed as inferred.
- Don't show a score without a way to see why.

## Related

- [`AiGeneratedLabel`, `AiConfidenceMeter`](https://docs.nasaqui.com/components/ai-states)
- [`LeadsInbox`](https://docs.nasaqui.com/components/leads-inbox)

## Lab

https://docs.nasaqui.com/?path=/docs/components-ai-assistant-score-explainer--docs

## Code

### React

```tsx
<ScoreBadge
  score={72}
  confidence={0.8}
  aiGenerated
  dimensions={[
    { id: "size", label: "Company size", points: 30, maxPoints: 40, reason: "51 to 200 employees", sources: [{ label: "LinkedIn", url: "https://example.com" }] },
    { id: "intent", label: "Intent", points: 42, maxPoints: 60, reason: "Visited pricing twice", inferred: true },
  ]}
/>
```

### shadcn

```tsx
<ScoreBadge
  score={72}
  confidence={0.8}
  aiGenerated
  dimensions={[
    { id: "size", label: "Company size", points: 30, maxPoints: 40, reason: "51 to 200 employees", sources: [{ label: "LinkedIn", url: "https://example.com" }] },
    { id: "intent", label: "Intent", points: 42, maxPoints: 60, reason: "Visited pricing twice", inferred: true },
  ]}
/>
```

### Vue

```vue
<script setup lang="ts">
import { NqScoreBadge, type ScoreDimension } from "@fadymondy/nasaq/vue";

const dimensions: ScoreDimension[] = [
  { id: "size", label: "Company size", points: 30, maxPoints: 40, reason: "51 to 200 employees", sources: [{ label: "LinkedIn", url: "https://example.com" }] },
  { id: "intent", label: "Intent", points: 42, maxPoints: 60, reason: "Visited pricing twice", inferred: true },
];
</script>

<template>
  <NqScoreBadge :score="72" :confidence="0.8" ai-generated :dimensions="dimensions" />
</template>
```

### Blade

```blade
<x-nq::score-explainer.badge :score="72" :confidence="0.8" ai-generated :dimensions="[
    ['id' => 'size', 'label' => 'Company size', 'points' => 30, 'maxPoints' => 40, 'reason' => '51 to 200 employees', 'sources' => [['label' => 'LinkedIn', 'url' => 'https://example.com']]],
    ['id' => 'intent', 'label' => 'Intent', 'points' => 42, 'maxPoints' => 60, 'reason' => 'Visited pricing twice', 'inferred' => true],
]" />
```

### HTML + Alpine

```html
<div data-slot="popover" x-data="nqPopover(false)" x-modelable="open" x-id="['nq-popover']" class="contents">
    <button type="button" data-slot="score-badge" data-band="high" aria-label="Score 72 of 100, High. Show why" x-ref="trigger" aria-haspopup="dialog" x-on:click="toggle()" x-bind:aria-expanded="open"
        x-bind:data-popup-open="open ? '' : undefined"
        class="inline-flex h-6 cursor-pointer items-center gap-1.5 rounded-full border px-2 text-caption font-medium outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus hover:bg-nq-hover border-nq-success/40 bg-nq-success-soft text-nq-success-text border-dashed">
        <span aria-hidden="true">≈</span>        <bdi data-slot="num" data-numeric="" class="tabular-nums text-body-sm">72</bdi>
        <span>High</span>
    </button>
    <template x-teleport="body">
    <div data-slot="popover-content" x-bind="popup" x-init="popupEl = $el" x-nq-presence="open" x-anchor.bottom-start.offset.6="$refs.trigger"
        class="z-50 max-w-[var(--available-width)] rounded-floating border border-border bg-popover text-body-sm text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 w-[min(26rem,calc(100vw-2rem))] p-4"><section data-slot="score-explainer" data-band="high" aria-labelledby="nq-score-000001-title" class="flex min-w-0 flex-col gap-4">
    <header class="flex flex-wrap items-center gap-x-4 gap-y-2">
        <div class="flex items-baseline gap-1.5">
            <span class="text-display-sm tabular-nums text-foreground" aria-hidden="true"><bdi data-slot="num" data-numeric="" class="tabular-nums">72</bdi>
</span>
            <span class="text-body-sm text-muted-foreground">out of 100</span>
        </div>
        <div class="flex min-w-0 flex-1 flex-col gap-1">
            <div class="flex flex-wrap items-center gap-1.5">
                <h3 id="nq-score-000001-title" class="text-title-sm text-foreground">Why this score</h3>
                <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-success/40 bg-nq-success-soft text-nq-success-text">High</span>
                <span data-slot="ai-generated-label"     class="inline-flex h-5 shrink-0 items-center whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-accent/40 bg-nq-accent/15 text-nq-accent-text gap-1"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
  <path d="M20 2v4"/>
  <path d="M22 4h-4"/>
  <circle cx="4" cy="20" r="2"/>
</svg>    AI generated</span>
            </div>
                    </div>
    </header>
    <div data-slot="ai-confidence" data-level="high" class="flex items-center gap-2 text-caption text-muted-foreground">
    <span>Confidence</span>
    <div data-slot="progress" data-tone="success" role="progressbar"     aria-valuemin="0" aria-valuemax="100"  aria-valuenow="80"     aria-valuetext="80%" data-progressing
    aria-label="Confidence" class="flex flex-col gap-1.5 w-16">
        <div data-slot="progress-track" data-progressing class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-1">
        <div data-slot="progress-indicator" data-progressing style="inset-inline-start:0;width:80%"
            class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-nq-success"></div>
    </div>
</div>
    <span class="text-foreground">High <bdi data-slot="num" data-numeric="" class="tabular-nums">80%</bdi>
</span>
</div>
        <ul data-slot="score-dimensions" class="flex flex-col gap-4">
                                <li data-slot="score-dimension" class="flex min-w-0 flex-col gap-1.5">
                <div class="flex items-baseline justify-between gap-3">
                    <span class="flex min-w-0 flex-wrap items-center gap-1.5 text-label text-foreground">
                        <span dir="auto">Intent</span>
                        <span data-slot="score-inferred" title="Worked out by the model from other signals. No source states it directly."
    class="inline-flex h-5 items-center gap-1 rounded-[4px] border border-dashed border-nq-line-strong px-1.5 text-caption text-muted-foreground [&_svg]:size-3">
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
  <path d="M20 2v4"/>
  <path d="M22 4h-4"/>
  <circle cx="4" cy="20" r="2"/>
</svg>    Inferred
</span>
                    </span>
                    <span class="shrink-0 text-body-sm text-muted-foreground">
                        42 of 60 points
                    </span>
                </div>
                <div data-slot="progress" data-tone="success" role="progressbar"     aria-valuemin="0" aria-valuemax="100"  aria-valuenow="70"     aria-valuetext="70%" data-progressing
    aria-label="Intent" class="flex w-full flex-col gap-1.5">
        <div data-slot="progress-track" data-progressing class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-1">
        <div data-slot="progress-indicator" data-progressing style="inset-inline-start:0;width:70%"
            class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-nq-success"></div>
    </div>
</div>
                <p dir="auto" class="text-body-sm text-muted-foreground">Visited pricing twice</p>
                                                            </li>
                                <li data-slot="score-dimension" class="flex min-w-0 flex-col gap-1.5">
                <div class="flex items-baseline justify-between gap-3">
                    <span class="flex min-w-0 flex-wrap items-center gap-1.5 text-label text-foreground">
                        <span dir="auto">Company size</span>
                                            </span>
                    <span class="shrink-0 text-body-sm text-muted-foreground">
                        30 of 40 points
                    </span>
                </div>
                <div data-slot="progress" data-tone="success" role="progressbar"     aria-valuemin="0" aria-valuemax="100"  aria-valuenow="75"     aria-valuetext="75%" data-progressing
    aria-label="Company size" class="flex w-full flex-col gap-1.5">
        <div data-slot="progress-track" data-progressing class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-1">
        <div data-slot="progress-indicator" data-progressing style="inset-inline-start:0;width:75%"
            class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-nq-success"></div>
    </div>
</div>
                <p dir="auto" class="text-body-sm text-muted-foreground">51 to 200 employees</p>
                                                    <div class="flex flex-wrap items-center gap-1.5" role="group" aria-label="Sources: Company size">
                        <a data-slot="score-source" href="https://example.com" target="_blank" rel="noopener noreferrer"         class="inline-flex max-w-full items-center gap-1 rounded-full border px-2 py-0.5 text-caption outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus border-border bg-secondary text-foreground hover:bg-nq-hover">
                <span dir="auto" class="truncate">LinkedIn</span>
                <svg aria-hidden="true" class="size-3 shrink-0 text-muted-foreground rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M15 3h6v6"/>
  <path d="M10 14 21 3"/>
  <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
</svg>        <span class="sr-only">opens in a new tab</span>
    </a>
                    </div>
                                            </li>
                    </ul>
</section></div>
</template>
</div>
```
