{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "data-quality-metrics",
  "title": "Data Quality Metrics",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/data-quality-metrics/DataQualityMetrics.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport {\n  Activity,\n  AlertCircle,\n  AlertTriangle,\n  Check,\n  CheckCircle2,\n  Clock,\n  Copy,\n  Database,\n  Download,\n  FileCode2,\n  Filter,\n  GitBranch,\n  Layers,\n  RefreshCw,\n  Search,\n  ShieldCheck,\n} from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Progress } from '@/components/ui/progress'\nimport { Separator } from '@/components/ui/separator'\nimport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n} from '@/components/ui/sheet'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\nexport type AssertionStatus = 'Passing' | 'Warning' | 'Failed'\nexport type AssertionCategory = 'Completeness' | 'Uniqueness' | 'Validity' | 'Volume' | 'Schema'\n\nexport interface AnomalyRecord {\n  recordId: string\n  column: string\n  value: string\n  reason: string\n}\n\nexport interface AssertionItem {\n  id: string\n  assertionType: string\n  columnName: string\n  category: AssertionCategory\n  ruleDefinition: string\n  targetThreshold: string\n  observedValue: string\n  observedPercent: number\n  failedCount: number\n  totalEvaluated: number\n  status: AssertionStatus\n  statusVariant: 'success' | 'warning' | 'destructive'\n  executionTime: string\n  expectationConfig: Record<string, unknown>\n  remediationQuery: string\n  anomalies: AnomalyRecord[]\n}\n\nexport interface DataQualityMetricsProps {\n  datasetTitle?: string\n  suiteName?: string\n  qualityScore?: number\n  lastRun?: string\n  passedAssertions?: string\n  failedWarnings?: string\n  schemaDriftStatus?: string\n  totalRowsAudited?: string\n  assertions?: AssertionItem[]\n  class?: HTMLAttributes['class']\n}\n\nconst defaultAssertions: AssertionItem[] = [\n  {\n    id: 'dq-assert-1',\n    assertionType: 'expect_column_values_to_not_be_null',\n    columnName: 'user_id',\n    category: 'Completeness',\n    ruleDefinition: 'Primary key non-null identity constraint verification across all partition shards.',\n    targetThreshold: '100.0%',\n    observedValue: '100.0%',\n    observedPercent: 100.0,\n    failedCount: 0,\n    totalEvaluated: 1480000,\n    status: 'Passing',\n    statusVariant: 'success',\n    executionTime: '142ms',\n    expectationConfig: {\n      expectation_type: 'expect_column_values_to_not_be_null',\n      kwargs: {\n        column: 'user_id',\n        mostly: 1.0,\n      },\n    },\n    remediationQuery: `-- Primary key check passed cleanly. No quarantine needed.\\nSELECT COUNT(*) FROM production_analytics.dim_users WHERE user_id IS NULL;`,\n    anomalies: [],\n  },\n  {\n    id: 'dq-assert-2',\n    assertionType: 'expect_column_values_to_be_unique',\n    columnName: 'email',\n    category: 'Uniqueness',\n    ruleDefinition: 'Global case-insensitive deduplication check across customer directory.',\n    targetThreshold: '100.0%',\n    observedValue: '100.0%',\n    observedPercent: 100.0,\n    failedCount: 0,\n    totalEvaluated: 1480000,\n    status: 'Passing',\n    statusVariant: 'success',\n    executionTime: '380ms',\n    expectationConfig: {\n      expectation_type: 'expect_column_values_to_be_unique',\n      kwargs: {\n        column: 'email',\n        mostly: 1.0,\n      },\n    },\n    remediationQuery: `-- Zero duplicate emails detected in active partition.\\nSELECT LOWER(email), COUNT(*) FROM production_analytics.dim_users GROUP BY 1 HAVING COUNT(*) > 1;`,\n    anomalies: [],\n  },\n  {\n    id: 'dq-assert-3',\n    assertionType: 'expect_column_values_to_match_regex',\n    columnName: 'phone_e164',\n    category: 'Validity',\n    ruleDefinition: 'International telecommunications ITU-T E.164 format pattern matching (^\\\\+[1-9]\\\\d{1,14}$).',\n    targetThreshold: '≥ 98.0%',\n    observedValue: '98.8%',\n    observedPercent: 98.8,\n    failedCount: 17760,\n    totalEvaluated: 1480000,\n    status: 'Warning',\n    statusVariant: 'warning',\n    executionTime: '890ms',\n    expectationConfig: {\n      expectation_type: 'expect_column_values_to_match_regex',\n      kwargs: {\n        column: 'phone_e164',\n        regex: '^\\\\+[1-9]\\\\d{1,14}$',\n        mostly: 0.98,\n      },\n    },\n    remediationQuery: `-- Quarantine 17,760 legacy unformatted phone numbers for libphonenumber normalization:\\nCREATE OR REPLACE TABLE staging.unformatted_phones AS\\nSELECT user_id, email, phone_e164, 'MALFORMED_E164' AS failure_reason\\nFROM production_analytics.dim_users\\nWHERE phone_e164 NOT RLIKE '^\\\\+[1-9]\\\\d{1,14}$';`,\n    anomalies: [\n      {\n        recordId: 'usr_9941a8',\n        column: 'phone_e164',\n        value: '+1-555-019',\n        reason: 'Invalid E.164 length (incomplete national number)',\n      },\n      {\n        recordId: 'usr_1082fc',\n        column: 'phone_e164',\n        value: '0784910293',\n        reason: 'Missing international country dial prefix (+)',\n      },\n      {\n        recordId: 'usr_7729de',\n        column: 'phone_e164',\n        value: '+44 (0)20 7946',\n        reason: 'Unstripped parentheses and whitespace delimiter',\n      },\n      {\n        recordId: 'usr_4019ab',\n        column: 'phone_e164',\n        value: 'NULL',\n        reason: 'Unexpected empty field on SMS-enrolled user',\n      },\n    ],\n  },\n  {\n    id: 'dq-assert-4',\n    assertionType: 'expect_table_row_count_to_be_between',\n    columnName: 'table: dim_users',\n    category: 'Volume',\n    ruleDefinition: 'Daily snapshot table row count volumetric boundary expectation between 1.0M and 2.0M rows.',\n    targetThreshold: '1.0M .. 2.0M',\n    observedValue: '1,480,000',\n    observedPercent: 100.0,\n    failedCount: 0,\n    totalEvaluated: 1480000,\n    status: 'Passing',\n    statusVariant: 'success',\n    executionTime: '95ms',\n    expectationConfig: {\n      expectation_type: 'expect_table_row_count_to_be_between',\n      kwargs: {\n        min_value: 1000000,\n        max_value: 2000000,\n      },\n    },\n    remediationQuery: `-- Row count volumetric validation passed within safety boundaries (1.48M in [1.0M, 2.0M]).\\nSELECT COUNT(*) AS total_rows FROM production_analytics.dim_users;`,\n    anomalies: [],\n  },\n  {\n    id: 'dq-assert-5',\n    assertionType: 'expect_column_values_to_be_between',\n    columnName: 'age',\n    category: 'Validity',\n    ruleDefinition: 'Demographic account age boundary distribution constraint between 18 and 120 years.',\n    targetThreshold: '100.0%',\n    observedValue: '100.0%',\n    observedPercent: 100.0,\n    failedCount: 0,\n    totalEvaluated: 1480000,\n    status: 'Passing',\n    statusVariant: 'success',\n    executionTime: '210ms',\n    expectationConfig: {\n      expectation_type: 'expect_column_values_to_be_between',\n      kwargs: {\n        column: 'age',\n        min_value: 18,\n        max_value: 120,\n        mostly: 1.0,\n      },\n    },\n    remediationQuery: `-- All verified user ages lie cleanly between min 18 and max 94 (100.0% compliant).\\nSELECT MIN(age), MAX(age), AVG(age) FROM production_analytics.dim_users;`,\n    anomalies: [],\n  },\n]\n\nconst props = withDefaults(defineProps<DataQualityMetricsProps>(), {\n  datasetTitle: 'production_analytics.dim_users',\n  suiteName: 'user_profile_integrity_v3',\n  qualityScore: 98.4,\n  lastRun: 'Ran 14m ago · 48 assertions evaluated',\n  passedAssertions: '47 / 48 Passing · 98%',\n  failedWarnings: '1 Warning · Null phone numbers 1.2%',\n  schemaDriftStatus: '0 breaking schema changes',\n  totalRowsAudited: '1,480,000 rows',\n})\n\nconst activeAssertions = computed(() => props.assertions ?? defaultAssertions)\n\nconst searchQuery = ref('')\nconst selectedStatus = ref<string>('all')\nconst selectedCategory = ref<string>('all')\nconst isRunningSuite = ref(false)\nconst currentLastRun = ref(props.lastRun)\nconst selectedAssertion = ref<AssertionItem | null>(null)\nconst isDrawerOpen = ref(false)\nconst copiedSnippetKey = ref<string | null>(null)\nconst exportSuccess = ref(false)\nconst anomalyExportSuccess = ref(false)\n\nconst availableCategories = computed(() => {\n  const categories = new Set<string>()\n  activeAssertions.value.forEach((item) => categories.add(item.category))\n  return ['all', ...Array.from(categories)]\n})\n\nconst filteredAssertions = computed(() => {\n  const query = searchQuery.value.trim().toLowerCase()\n  const statusFilter = selectedStatus.value\n  const categoryFilter = selectedCategory.value\n\n  return activeAssertions.value.filter((item) => {\n    const matchesStatus = statusFilter === 'all' || item.status.toLowerCase() === statusFilter.toLowerCase()\n    const matchesCategory = categoryFilter === 'all' || item.category.toLowerCase() === categoryFilter.toLowerCase()\n    const matchesQuery =\n      !query ||\n      item.assertionType.toLowerCase().includes(query) ||\n      item.columnName.toLowerCase().includes(query) ||\n      item.ruleDefinition.toLowerCase().includes(query) ||\n      item.category.toLowerCase().includes(query)\n\n    return matchesStatus && matchesCategory && matchesQuery\n  })\n})\n\nfunction handleRunSuite() {\n  if (isRunningSuite.value) return\n  isRunningSuite.value = true\n\n  setTimeout(() => {\n    isRunningSuite.value = false\n    currentLastRun.value = 'Ran just now · 48 assertions evaluated'\n  }, 750)\n}\n\nfunction handleInspectAnomalies(assertion: AssertionItem) {\n  selectedAssertion.value = assertion\n  isDrawerOpen.value = true\n}\n\nfunction copyCodeSnippet(key: string, content: string) {\n  if (typeof navigator !== 'undefined' && navigator.clipboard) {\n    navigator.clipboard.writeText(content)\n    copiedSnippetKey.value = key\n    setTimeout(() => {\n      if (copiedSnippetKey.value === key) {\n        copiedSnippetKey.value = null\n      }\n    }, 2000)\n  }\n}\n\nfunction exportQualityReport() {\n  const report = {\n    dataset: props.datasetTitle,\n    suite: props.suiteName,\n    overallQualityScore: props.qualityScore,\n    evaluatedAt: new Date().toISOString(),\n    engine: 'Great Expectations v0.18 / SodaCL',\n    summary: {\n      passed: props.passedAssertions,\n      warnings: props.failedWarnings,\n      schemaDrift: props.schemaDriftStatus,\n      totalRows: props.totalRowsAudited,\n    },\n    assertions: activeAssertions.value,\n  }\n\n  const jsonStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(report, null, 2))\n  if (typeof document !== 'undefined') {\n    const link = document.createElement('a')\n    link.setAttribute('href', jsonStr)\n    link.setAttribute('download', `dq-report-${props.suiteName}-${new Date().toISOString().slice(0, 10)}.json`)\n    document.body.appendChild(link)\n    link.click()\n    link.remove()\n\n    exportSuccess.value = true\n    setTimeout(() => {\n      exportSuccess.value = false\n    }, 2000)\n  }\n}\n\nfunction exportAnomalyCsv() {\n  if (!selectedAssertion.value || selectedAssertion.value.anomalies.length === 0) return\n\n  const headers = ['Record ID', 'Column', 'Observed Value', 'Failure Reason']\n  const rows = selectedAssertion.value.anomalies.map((a) => [a.recordId, a.column, `\"${a.value}\"`, `\"${a.reason}\"`])\n  const csvContent = [headers.join(','), ...rows.map((r) => r.join(','))].join('\\n')\n\n  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })\n  const url = URL.createObjectURL(blob)\n  const link = document.createElement('a')\n  link.setAttribute('href', url)\n  link.setAttribute('download', `anomalies-${selectedAssertion.value.columnName}.csv`)\n  document.body.appendChild(link)\n  link.click()\n  link.remove()\n\n  anomalyExportSuccess.value = true\n  setTimeout(() => {\n    anomalyExportSuccess.value = false\n  }, 2000)\n}\n</script>\n\n<template>\n  <div data-slot=\"data-quality-metrics\" :class=\"cn('w-full space-y-6', props.class)\">\n    <!-- Header Section -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardContent class=\"p-5 sm:p-6\">\n        <div class=\"flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between\">\n          <!-- Left: Dataset and Suite Metadata -->\n          <div class=\"space-y-2\">\n            <div class=\"flex flex-wrap items-center gap-2.5\">\n              <div\n                class=\"bg-muted text-foreground border-border flex size-9 shrink-0 items-center justify-center rounded-lg border shadow-xs\"\n              >\n                <Database class=\"text-success size-4.5\" />\n              </div>\n              <div>\n                <div class=\"flex flex-wrap items-center gap-2\">\n                  <h1 class=\"text-foreground font-mono text-lg font-bold tracking-tight break-all sm:text-xl\">\n                    {{ props.datasetTitle }}\n                  </h1>\n                  <Badge wrap variant=\"secondary\" class=\"font-mono text-xs\">\n                    {{ props.suiteName }}\n                  </Badge>\n                </div>\n                <div class=\"text-muted-foreground flex flex-wrap items-center gap-2 pt-1 text-xs\">\n                  <span class=\"flex items-center gap-1\">\n                    <Clock class=\"size-3.5\" />\n                    {{ currentLastRun }}\n                  </span>\n                  <span class=\"opacity-40\">·</span>\n                  <span class=\"font-mono\">Snowflake DW</span>\n                  <span class=\"opacity-40\">·</span>\n                  <span class=\"font-mono\">dbt Core v1.8</span>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <!-- Right: Overall Score Card & Action Buttons -->\n          <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n            <!-- Overall Score Pill Card -->\n            <div\n              class=\"border-success/30 bg-success/10 flex flex-wrap items-center gap-3.5 rounded-xl border p-3.5 shadow-xs\"\n            >\n              <div class=\"bg-success/20 text-success flex size-11 shrink-0 items-center justify-center rounded-lg\">\n                <CheckCircle2 class=\"size-6\" />\n              </div>\n              <div class=\"min-w-0 space-y-0.5\">\n                <div class=\"flex flex-wrap items-baseline gap-2\">\n                  <span class=\"text-success text-success text-2xl font-bold tracking-tight tabular-nums sm:text-3xl\">\n                    {{ props.qualityScore }}%\n                  </span>\n                  <Badge wrap variant=\"success\" class=\"gap-1 px-2 text-xs font-semibold\">\n                    <ShieldCheck class=\"size-3\" />\n                    Quality Score\n                  </Badge>\n                </div>\n                <p class=\"text-muted-foreground text-xs\">Automated Sodacl / GX Engine</p>\n              </div>\n            </div>\n\n            <!-- Action Buttons -->\n            <div class=\"flex flex-wrap gap-2.5 sm:flex-col\">\n              <Button\n                variant=\"default\"\n                size=\"sm\"\n                class=\"gap-2 shadow-xs\"\n                :disabled=\"isRunningSuite\"\n                @click=\"handleRunSuite\"\n              >\n                <RefreshCw :class=\"['size-4', isRunningSuite ? 'animate-spin' : '']\" />\n                <span>{{ isRunningSuite ? 'Evaluating Suite...' : 'Run Assertions Suite' }}</span>\n              </Button>\n\n              <Button variant=\"outline\" size=\"sm\" class=\"gap-2 shadow-xs\" @click=\"exportQualityReport\">\n                <Check v-if=\"exportSuccess\" class=\"text-success size-4\" />\n                <Download v-else class=\"size-4\" />\n                <span>{{ exportSuccess ? 'Report Downloaded!' : 'Export Quality Report' }}</span>\n              </Button>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n\n      <!-- Sub-bar Contract Guarantee -->\n      <div\n        class=\"bg-muted/40 border-border text-muted-foreground flex flex-col gap-2 border-t px-5 py-2.5 text-xs sm:flex-row sm:items-center sm:justify-between\"\n      >\n        <div class=\"flex items-center gap-2 font-mono\">\n          <span class=\"bg-success size-2 rounded-full\" />\n          <span>Schema Contract: Locked v3.2 · Zero Breaking Schema Drift</span>\n        </div>\n        <div class=\"flex items-center gap-3\">\n          <span>Engine SLA: <strong class=\"text-foreground font-medium\">Strict Production</strong></span>\n          <Separator orientation=\"vertical\" class=\"hidden h-3 sm:block\" />\n          <span>48 assertions evaluated</span>\n        </div>\n      </div>\n    </Card>\n\n    <!-- 4 Assertion Metric Cards -->\n    <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n      <!-- Card 1: Passed Assertions -->\n      <Card class=\"border-border bg-card hover:border-border shadow-xs transition-colors\">\n        <CardHeader class=\"p-4 pb-2\">\n          <div class=\"flex items-start justify-between gap-2\">\n            <p class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Passed Assertions</p>\n            <div\n              class=\"border-success/20 bg-success/10 text-success flex size-7 items-center justify-center rounded-md border\"\n            >\n              <CheckCircle2 class=\"size-4\" />\n            </div>\n          </div>\n          <div class=\"flex items-baseline gap-2 pt-1\">\n            <span class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">47 / 48</span>\n            <Badge wrap variant=\"success\" class=\"text-xs font-medium\">98% Passing</Badge>\n          </div>\n        </CardHeader>\n        <CardContent class=\"space-y-2 p-4 pt-1\">\n          <Progress :model-value=\"98\" class=\"h-1.5 w-full\" />\n          <p class=\"text-muted-foreground text-xs\">{{ props.passedAssertions }}</p>\n        </CardContent>\n      </Card>\n\n      <!-- Card 2: Failed / Warnings -->\n      <Card class=\"border-border bg-card hover:border-border shadow-xs transition-colors\">\n        <CardHeader class=\"p-4 pb-2\">\n          <div class=\"flex items-start justify-between gap-2\">\n            <p class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Failed / Warnings</p>\n            <div\n              class=\"border-warning/20 bg-warning/10 text-warning flex size-7 items-center justify-center rounded-md border\"\n            >\n              <AlertTriangle class=\"size-4\" />\n            </div>\n          </div>\n          <div class=\"flex items-baseline gap-2 pt-1\">\n            <span class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">1 Warning</span>\n            <Badge wrap variant=\"warning\" class=\"text-xs font-medium\">1.2% Non-conformant</Badge>\n          </div>\n        </CardHeader>\n        <CardContent class=\"space-y-2 p-4 pt-1\">\n          <Progress :model-value=\"1.2\" class=\"h-1.5 w-full\" />\n          <p class=\"text-muted-foreground text-xs\">{{ props.failedWarnings }}</p>\n        </CardContent>\n      </Card>\n\n      <!-- Card 3: Schema Drift Status -->\n      <Card class=\"border-border bg-card hover:border-border shadow-xs transition-colors\">\n        <CardHeader class=\"p-4 pb-2\">\n          <div class=\"flex items-start justify-between gap-2\">\n            <p class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Schema Drift Status</p>\n            <div class=\"border-info/20 bg-info/10 text-info flex size-7 items-center justify-center rounded-md border\">\n              <GitBranch class=\"size-4\" />\n            </div>\n          </div>\n          <div class=\"flex items-baseline gap-2 pt-1\">\n            <span class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">0 Breaking</span>\n            <Badge wrap variant=\"outline\" class=\"text-success text-xs font-medium\"> No Drift </Badge>\n          </div>\n        </CardHeader>\n        <CardContent class=\"space-y-2 p-4 pt-1\">\n          <div class=\"bg-muted/40 flex items-center justify-between gap-x-2 rounded-md px-2 py-1 text-xs\">\n            <span class=\"text-muted-foreground\">Columns Synced</span>\n            <span class=\"text-foreground font-mono font-medium\">14 / 14 matched</span>\n          </div>\n          <p class=\"text-muted-foreground text-xs\">{{ props.schemaDriftStatus }}</p>\n        </CardContent>\n      </Card>\n\n      <!-- Card 4: Total Rows Audited -->\n      <Card class=\"border-border bg-card hover:border-border shadow-xs transition-colors\">\n        <CardHeader class=\"p-4 pb-2\">\n          <div class=\"flex items-start justify-between gap-2\">\n            <p class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Total Rows Audited</p>\n            <div\n              class=\"bg-muted text-foreground border-border flex size-7 items-center justify-center rounded-md border\"\n            >\n              <Layers class=\"size-4\" />\n            </div>\n          </div>\n          <div class=\"flex items-baseline gap-2 pt-1\">\n            <span class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">1,480,000</span>\n            <Badge wrap variant=\"outline\" class=\"font-mono text-xs font-normal\">1.48M Rows</Badge>\n          </div>\n        </CardHeader>\n        <CardContent class=\"space-y-2 p-4 pt-1\">\n          <div class=\"bg-muted/40 flex items-center justify-between gap-x-2 rounded-md px-2 py-1 text-xs\">\n            <span class=\"text-muted-foreground\">Partition Range</span>\n            <span class=\"text-foreground font-mono font-medium\">1.0M .. 2.0M</span>\n          </div>\n          <p class=\"text-muted-foreground text-xs\">{{ props.totalRowsAudited }}</p>\n        </CardContent>\n      </Card>\n    </div>\n\n    <!-- Data Quality Test Assertions Table Card -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardHeader class=\"flex flex-col gap-4 pb-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div>\n          <div class=\"flex items-center gap-2\">\n            <CardTitle class=\"text-base font-semibold\">Data Quality Test Assertions</CardTitle>\n            <Badge wrap variant=\"secondary\" class=\"font-mono text-xs\">\n              {{ filteredAssertions.length }} of {{ activeAssertions.length }} Assertions\n            </Badge>\n          </div>\n          <CardDescription class=\"text-xs\">\n            Automated expectations, boundary validations, regex format rules, and volume anomaly checks.\n          </CardDescription>\n        </div>\n\n        <!-- Filter Pills by Status -->\n        <div class=\"flex flex-wrap items-center gap-1.5\">\n          <button\n            type=\"button\"\n            :class=\"[\n              'focus-visible:ring-ring inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n              selectedStatus === 'all'\n                ? 'border-primary bg-primary text-primary-foreground shadow-xs'\n                : 'border-border bg-card text-muted-foreground hover:bg-accent hover:text-foreground',\n            ]\"\n            @click=\"selectedStatus = 'all'\"\n          >\n            All Assertions ({{ activeAssertions.length }})\n          </button>\n          <button\n            type=\"button\"\n            :class=\"[\n              'focus-visible:ring-ring inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n              selectedStatus === 'passing'\n                ? 'border-success bg-success bg-success text-white shadow-xs'\n                : 'border-border bg-card text-muted-foreground hover:bg-accent hover:text-foreground',\n            ]\"\n            @click=\"selectedStatus = 'passing'\"\n          >\n            Passing (4)\n          </button>\n          <button\n            type=\"button\"\n            :class=\"[\n              'focus-visible:ring-ring inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n              selectedStatus === 'warning'\n                ? 'border-warning bg-warning bg-warning text-white shadow-xs'\n                : 'border-border bg-card text-muted-foreground hover:bg-accent hover:text-foreground',\n            ]\"\n            @click=\"selectedStatus = 'warning'\"\n          >\n            Warnings (1)\n          </button>\n        </div>\n      </CardHeader>\n\n      <!-- Filter & Search Toolbar -->\n      <div class=\"border-border/60 border-t p-4 pt-3 pb-3\">\n        <div class=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n          <div class=\"relative flex-1\">\n            <Search\n              class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2\"\n            />\n            <input\n              v-model=\"searchQuery\"\n              type=\"text\"\n              placeholder=\"Search by assertion type, column name, or rule definition...\"\n              class=\"border-border bg-background placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border px-3 pl-8 text-xs focus-visible:ring-2 focus-visible:outline-none\"\n            />\n          </div>\n\n          <div class=\"flex flex-wrap items-center gap-1.5\">\n            <span class=\"text-muted-foreground mr-1 flex items-center gap-1 text-xs font-medium\">\n              <Filter class=\"size-3\" />\n              Category:\n            </span>\n            <button\n              v-for=\"cat in availableCategories\"\n              :key=\"cat\"\n              type=\"button\"\n              :class=\"[\n                'focus-visible:ring-ring inline-flex items-center rounded-md border px-2 py-1 font-mono text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                selectedCategory === cat\n                  ? 'border-foreground bg-foreground text-background shadow-xs'\n                  : 'border-border bg-card text-muted-foreground hover:bg-accent hover:text-foreground',\n              ]\"\n              @click=\"selectedCategory = cat\"\n            >\n              {{ cat === 'all' ? 'All Categories' : cat }}\n            </button>\n          </div>\n        </div>\n      </div>\n\n      <!-- Table Container -->\n      <CardContent class=\"p-0\">\n        <div class=\"border-border/60 overflow-x-auto border-t\">\n          <Table>\n            <TableHeader class=\"bg-muted/50\">\n              <TableRow>\n                <TableHead class=\"min-w-[260px] text-xs\">Assertion & Target Column</TableHead>\n                <TableHead class=\"min-w-[100px] text-xs\">Category</TableHead>\n                <TableHead class=\"min-w-[120px] text-xs\">Target Threshold</TableHead>\n                <TableHead class=\"min-w-[120px] text-xs\">Observed Value</TableHead>\n                <TableHead class=\"min-w-[110px] text-xs\">Status</TableHead>\n                <TableHead class=\"min-w-[130px] text-right text-xs\">Actions</TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <TableRow\n                v-for=\"assertion in filteredAssertions\"\n                :key=\"assertion.id\"\n                class=\"hover:bg-muted/20 transition-colors\"\n              >\n                <!-- Assertion Type & Target Column -->\n                <TableCell class=\"py-3\">\n                  <div class=\"space-y-1\">\n                    <div class=\"flex flex-wrap items-center gap-2\">\n                      <Badge wrap variant=\"outline\" class=\"border-border font-mono text-xs font-semibold\">\n                        {{ assertion.columnName }}\n                      </Badge>\n                      <span class=\"text-foreground font-mono text-xs font-medium\">{{ assertion.assertionType }}</span>\n                    </div>\n                    <p class=\"text-muted-foreground max-w-sm text-xs leading-relaxed sm:max-w-md\">\n                      {{ assertion.ruleDefinition }}\n                    </p>\n                  </div>\n                </TableCell>\n\n                <!-- Category -->\n                <TableCell class=\"py-3\">\n                  <Badge wrap variant=\"secondary\" class=\"font-normal\">\n                    {{ assertion.category }}\n                  </Badge>\n                </TableCell>\n\n                <!-- Target Threshold -->\n                <TableCell class=\"py-3\">\n                  <span class=\"text-muted-foreground font-mono text-xs font-medium\">\n                    {{ assertion.targetThreshold }}\n                  </span>\n                </TableCell>\n\n                <!-- Observed Value -->\n                <TableCell class=\"py-3\">\n                  <div class=\"space-y-0.5 font-mono text-xs\">\n                    <span\n                      :class=\"[\n                        'font-bold tabular-nums',\n                        assertion.status === 'Passing' ? 'text-success' : 'text-warning',\n                      ]\"\n                    >\n                      {{ assertion.observedValue }}\n                    </span>\n                    <span class=\"text-muted-foreground/70 block text-xs\"> {{ assertion.executionTime }} eval </span>\n                  </div>\n                </TableCell>\n\n                <!-- Status Badge -->\n                <TableCell class=\"py-3\">\n                  <Badge wrap :variant=\"assertion.statusVariant\" class=\"gap-1 text-xs font-medium\">\n                    <CheckCircle2 v-if=\"assertion.status === 'Passing'\" class=\"size-3\" />\n                    <AlertTriangle v-else-if=\"assertion.status === 'Warning'\" class=\"size-3\" />\n                    <AlertCircle v-else class=\"size-3\" />\n                    {{ assertion.status }}\n                  </Badge>\n                </TableCell>\n\n                <!-- Action Button -->\n                <TableCell class=\"py-3 text-right\">\n                  <Button\n                    variant=\"outline\"\n                    size=\"xs\"\n                    class=\"h-7 gap-1 text-xs font-medium shadow-xs\"\n                    @click=\"handleInspectAnomalies(assertion)\"\n                  >\n                    <Activity class=\"size-3\" />\n                    <span>Inspect Anomalies</span>\n                  </Button>\n                </TableCell>\n              </TableRow>\n\n              <TableRow v-if=\"filteredAssertions.length === 0\">\n                <TableCell colspan=\"6\" class=\"py-8 text-center\">\n                  <div class=\"flex flex-col items-center justify-center gap-1.5\">\n                    <ShieldCheck class=\"text-success size-7\" />\n                    <p class=\"text-foreground text-sm font-medium\">No assertions match your filter query</p>\n                    <p class=\"text-muted-foreground text-xs\">All assertions meet standard quality constraints.</p>\n                  </div>\n                </TableCell>\n              </TableRow>\n            </TableBody>\n          </Table>\n        </div>\n\n        <!-- Table Footer -->\n        <div\n          class=\"border-border/60 bg-muted/20 text-muted-foreground flex flex-col gap-2 border-t px-4 py-2.5 text-xs sm:flex-row sm:items-center sm:justify-between\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <span class=\"bg-success size-2 rounded-full\" />\n            <span>Great Expectations Assertions Runner: 100% evaluated</span>\n          </div>\n          <span class=\"font-mono text-xs\"> Dataset: {{ props.datasetTitle }} · Last Batch: 1,480,000 rows </span>\n        </div>\n      </CardContent>\n    </Card>\n\n    <!-- Anomaly Inspector Drawer / Sheet -->\n    <Sheet v-model:open=\"isDrawerOpen\">\n      <SheetContent class=\"w-full space-y-6 overflow-y-auto p-6 sm:max-w-xl md:max-w-2xl\">\n        <SheetHeader v-if=\"selectedAssertion\" class=\"space-y-2 p-0 text-left\">\n          <div class=\"flex items-center justify-between gap-3 pr-6\">\n            <div class=\"flex items-center gap-2\">\n              <Badge wrap :variant=\"selectedAssertion.statusVariant\" class=\"gap-1 font-mono text-xs font-semibold\">\n                <CheckCircle2 v-if=\"selectedAssertion.status === 'Passing'\" class=\"size-3\" />\n                <AlertTriangle v-else class=\"size-3\" />\n                {{ selectedAssertion.status.toUpperCase() }}\n              </Badge>\n              <Badge wrap variant=\"outline\" class=\"font-mono text-xs font-semibold\">\n                {{ selectedAssertion.category }}\n              </Badge>\n            </div>\n\n            <span class=\"text-muted-foreground font-mono text-xs\">\n              Latency: {{ selectedAssertion.executionTime }}\n            </span>\n          </div>\n\n          <SheetTitle class=\"text-lg font-bold tracking-tight\">\n            {{ selectedAssertion.assertionType }}\n          </SheetTitle>\n          <SheetDescription class=\"text-foreground/90 font-mono text-xs\">\n            Target Column: <strong class=\"text-foreground font-semibold\">{{ selectedAssertion.columnName }}</strong>\n          </SheetDescription>\n        </SheetHeader>\n\n        <div v-if=\"selectedAssertion\" class=\"space-y-5 text-xs\">\n          <!-- Summary Metrics Cards Grid -->\n          <div class=\"grid grid-cols-2 gap-2.5 sm:grid-cols-4\">\n            <div class=\"bg-muted/40 border-border/80 rounded-lg border p-3\">\n              <span class=\"text-muted-foreground text-xs\">Target Threshold</span>\n              <p class=\"text-foreground mt-1 font-mono text-sm font-bold\">{{ selectedAssertion.targetThreshold }}</p>\n            </div>\n            <div class=\"bg-muted/40 border-border/80 rounded-lg border p-3\">\n              <span class=\"text-muted-foreground text-xs\">Observed Pass Rate</span>\n              <p\n                :class=\"[\n                  'mt-1 font-mono text-sm font-bold tabular-nums',\n                  selectedAssertion.status === 'Passing' ? 'text-success' : 'text-warning',\n                ]\"\n              >\n                {{ selectedAssertion.observedValue }}\n              </p>\n            </div>\n            <div class=\"bg-muted/40 border-border/80 rounded-lg border p-3\">\n              <span class=\"text-muted-foreground text-xs\">Audited Records</span>\n              <p class=\"text-foreground mt-1 font-mono text-sm font-bold tabular-nums\">\n                {{ selectedAssertion.totalEvaluated.toLocaleString() }}\n              </p>\n            </div>\n            <div class=\"bg-muted/40 border-border/80 rounded-lg border p-3\">\n              <span class=\"text-muted-foreground text-xs\">Failed Anomalies</span>\n              <p\n                :class=\"[\n                  'mt-1 font-mono text-sm font-bold tabular-nums',\n                  selectedAssertion.failedCount > 0 ? 'text-warning' : 'text-success',\n                ]\"\n              >\n                {{ selectedAssertion.failedCount.toLocaleString() }}\n              </p>\n            </div>\n          </div>\n\n          <!-- Expectation Configuration DSL Box -->\n          <div class=\"space-y-2\">\n            <div class=\"flex items-center justify-between gap-x-2\">\n              <h3 class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Great Expectations Specification\n              </h3>\n              <Button\n                variant=\"ghost\"\n                size=\"xs\"\n                class=\"h-6 gap-1 px-2 text-xs\"\n                @click=\"copyCodeSnippet('dsl', JSON.stringify(selectedAssertion.expectationConfig, null, 2))\"\n              >\n                <Check v-if=\"copiedSnippetKey === 'dsl'\" class=\"text-success size-3\" />\n                <Copy v-else class=\"size-3\" />\n                <span>{{ copiedSnippetKey === 'dsl' ? 'Copied' : 'Copy Config' }}</span>\n              </Button>\n            </div>\n            <div class=\"overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner\">\n              <div\n                class=\"text-muted-foreground flex items-center gap-2 border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5 text-xs\"\n              >\n                <FileCode2 class=\"size-3.5\" />\n                <span>expectation_suite.json</span>\n              </div>\n              <pre class=\"overflow-x-auto p-3 text-xs leading-relaxed text-zinc-300 select-text\">{{\n                JSON.stringify(selectedAssertion.expectationConfig, null, 2)\n              }}</pre>\n            </div>\n          </div>\n\n          <!-- Anomalous Samples Table or Clean Pass State -->\n          <div class=\"space-y-2\">\n            <div class=\"flex items-center justify-between gap-x-2\">\n              <h3 class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Anomalous Sample Records\n              </h3>\n              <span v-if=\"selectedAssertion.anomalies.length > 0\" class=\"text-muted-foreground font-mono text-xs\">\n                Showing {{ selectedAssertion.anomalies.length }} failure samples\n              </span>\n            </div>\n\n            <div\n              v-if=\"selectedAssertion.anomalies.length > 0\"\n              class=\"border-border/80 overflow-hidden rounded-md border shadow-xs\"\n            >\n              <Table>\n                <TableHeader class=\"bg-muted/40\">\n                  <TableRow>\n                    <TableHead class=\"text-xs\">Record ID</TableHead>\n                    <TableHead class=\"text-xs\">Column</TableHead>\n                    <TableHead class=\"text-xs\">Observed Value</TableHead>\n                    <TableHead class=\"text-xs\">Diagnostic Reason</TableHead>\n                  </TableRow>\n                </TableHeader>\n                <TableBody>\n                  <TableRow v-for=\"anomaly in selectedAssertion.anomalies\" :key=\"anomaly.recordId\">\n                    <TableCell class=\"font-mono text-xs font-medium\">{{ anomaly.recordId }}</TableCell>\n                    <TableCell class=\"font-mono text-xs\">{{ anomaly.column }}</TableCell>\n                    <TableCell class=\"text-destructive font-mono text-xs font-semibold\">\n                      {{ anomaly.value }}\n                    </TableCell>\n                    <TableCell class=\"text-muted-foreground text-xs\">{{ anomaly.reason }}</TableCell>\n                  </TableRow>\n                </TableBody>\n              </Table>\n            </div>\n\n            <div\n              v-else\n              class=\"bg-muted/20 border-border/80 flex flex-col items-center justify-center gap-1.5 rounded-md border p-6 text-center\"\n            >\n              <CheckCircle2 class=\"text-success size-6\" />\n              <p class=\"text-foreground text-xs font-semibold\">Zero Anomalies Detected</p>\n              <p class=\"text-muted-foreground text-xs\">\n                All {{ selectedAssertion.totalEvaluated.toLocaleString() }} audited records strictly satisfied the\n                expectation rule.\n              </p>\n            </div>\n          </div>\n\n          <!-- Quarantine SQL Script -->\n          <div class=\"space-y-2\">\n            <div class=\"flex items-center justify-between gap-x-2\">\n              <h3 class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Quarantine & Remediation SQL\n              </h3>\n              <Button\n                variant=\"ghost\"\n                size=\"xs\"\n                class=\"h-6 gap-1 px-2 text-xs\"\n                @click=\"copyCodeSnippet('sql', selectedAssertion.remediationQuery)\"\n              >\n                <Check v-if=\"copiedSnippetKey === 'sql'\" class=\"text-success size-3\" />\n                <Copy v-else class=\"size-3\" />\n                <span>{{ copiedSnippetKey === 'sql' ? 'Copied' : 'Copy SQL' }}</span>\n              </Button>\n            </div>\n            <div class=\"border-border bg-muted/60 rounded-md border p-3 font-mono text-xs leading-relaxed select-text\">\n              <pre class=\"text-foreground overflow-x-auto whitespace-pre-wrap\">{{\n                selectedAssertion.remediationQuery\n              }}</pre>\n            </div>\n          </div>\n        </div>\n\n        <SheetFooter\n          v-if=\"selectedAssertion\"\n          class=\"border-border/80 flex flex-row items-center justify-end gap-2 border-t pt-4\"\n        >\n          <SheetClose as-child>\n            <Button variant=\"outline\" size=\"sm\" class=\"text-xs\"> Close </Button>\n          </SheetClose>\n\n          <Button\n            v-if=\"selectedAssertion.anomalies.length > 0\"\n            variant=\"default\"\n            size=\"sm\"\n            class=\"gap-1.5 text-xs font-medium shadow-xs\"\n            @click=\"exportAnomalyCsv\"\n          >\n            <Check v-if=\"anomalyExportSuccess\" class=\"text-success size-3.5\" />\n            <Download v-else class=\"size-3.5\" />\n            <span>{{ anomalyExportSuccess ? 'Anomalies Exported!' : 'Export Anomaly CSV' }}</span>\n          </Button>\n        </SheetFooter>\n      </SheetContent>\n    </Sheet>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/DataQualityMetrics.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/progress.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/sheet.json",
    "https://uipkge.dev/r/vue/table.json"
  ],
  "description": "Great Expectations and Soda style automated data quality assertions, schema validation, and drift detector dashboard: top header with overall quality score and run triggers, 4 assertion metric cards, data quality assertions table with category filters, and an interactive anomaly inspector drawer with SQL quarantine remediation.",
  "categories": [
    "devops",
    "dashboard",
    "app",
    "data"
  ]
}