Test-Data-Catalog---Busines.../index.html
Yûki VACHOT 1ee476756d Initial commit: Data Catalog & Business Glossary website
Single-page HTML app with catalog search, filters, dataset detail pages,
business glossary with term detail pages, and cross-linking between both.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:12:11 +01:00

1957 lines
96 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Catalog & Business Glossary</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: #444;
background: #f5f5f5;
}
/* ── Header ── */
.header {
background: #0279B1;
color: #fff;
padding: 0 32px;
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
.header-logo {
font-size: 18px;
font-weight: 700;
letter-spacing: .3px;
display: flex;
align-items: center;
gap: 10px;
}
.header-logo svg { flex-shrink: 0; }
.header-nav { display: flex; gap: 6px; }
.header-nav button {
background: transparent;
color: #fff;
border: none;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background .15s;
}
.header-nav button:hover,
.header-nav button.active { background: rgba(255,255,255,.18); }
/* ── Tab views ── */
.view { display: none; }
.view.active { display: block; }
/* ── Search bar ── */
.search-section {
background: #fff;
border-bottom: 1px solid #ddd;
padding: 20px 32px;
}
.search-wrapper {
max-width: 900px;
margin: 0 auto;
display: flex;
gap: 10px;
}
.search-wrapper input {
flex: 1;
padding: 12px 16px;
border: 2px solid #d9d9d5;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color .15s;
}
.search-wrapper input:focus { border-color: #0279B1; }
.search-wrapper .search-btn {
background: #0279B1;
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 15px;
cursor: pointer;
font-weight: 600;
transition: background .15s;
}
.search-wrapper .search-btn:hover { background: #025f8a; }
.result-count {
max-width: 900px;
margin: 10px auto 0;
font-size: 13px;
color: #888;
}
/* ── Layout: sidebar + cards ── */
.catalog-layout {
display: flex;
max-width: 1320px;
margin: 0 auto;
padding: 24px 32px;
gap: 28px;
}
/* ── Sidebar ── */
.sidebar { width: 260px; flex-shrink: 0; }
.filter-group { margin-bottom: 20px; }
.filter-group-title {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .6px;
color: #666;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 2px solid #0279B1;
}
.filter-group label {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
font-size: 14px;
cursor: pointer;
transition: color .1s;
}
.filter-group label:hover { color: #0279B1; }
.filter-group input[type="checkbox"] { accent-color: #0279B1; width: 16px; height: 16px; }
.filter-count {
margin-left: auto;
background: #e8e8e8;
color: #666;
font-size: 11px;
padding: 1px 7px;
border-radius: 10px;
}
/* Active filter pills */
.active-filters { margin-bottom: 18px; display: flex; flex-wrap: wrap; gap: 6px; }
.active-filters:empty { display: none; }
.pill {
display: inline-flex;
align-items: center;
gap: 5px;
background: #0279B1;
color: #fff;
padding: 4px 10px;
border-radius: 14px;
font-size: 12px;
font-weight: 500;
}
.pill .remove {
cursor: pointer;
font-size: 14px;
opacity: .8;
transition: opacity .1s;
}
.pill .remove:hover { opacity: 1; }
.clear-filters {
background: none;
border: none;
color: #c0392b;
font-size: 12px;
cursor: pointer;
padding: 4px 8px;
font-weight: 600;
}
/* ── Cards grid ── */
.cards-area { flex: 1; min-width: 0; }
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
gap: 18px;
}
.card {
background: #fff;
border: 1px solid #d9d9d5;
border-radius: 10px;
padding: 20px 22px;
transition: border-color .15s, box-shadow .15s;
cursor: pointer;
display: flex;
flex-direction: column;
}
.card:hover {
border-color: #5DA61B;
box-shadow: 0 4px 14px rgba(93,166,27,.12);
}
.card-theme {
display: inline-block;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .5px;
padding: 3px 9px;
border-radius: 4px;
margin-bottom: 10px;
width: fit-content;
}
.card-title {
font-size: 16px;
font-weight: 700;
color: #222;
margin-bottom: 8px;
line-height: 1.3;
}
.card-desc {
font-size: 13px;
color: #666;
line-height: 1.55;
margin-bottom: 14px;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
}
.card-keyword {
font-size: 11px;
background: #f0f4f8;
color: #0279B1;
padding: 2px 8px;
border-radius: 4px;
font-weight: 500;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #999;
border-top: 1px solid #f0f0f0;
padding-top: 10px;
}
.card-owner { font-weight: 600; color: #666; }
/* Theme colors */
.theme-finance { background: #e8f5e9; color: #2e7d32; }
.theme-hr { background: #e3f2fd; color: #1565c0; }
.theme-sales { background: #fff3e0; color: #e65100; }
.theme-operations { background: #f3e5f5; color: #7b1fa2; }
.theme-marketing { background: #fce4ec; color: #c62828; }
.theme-it { background: #e0f2f1; color: #00695c; }
.theme-compliance { background: #fff8e1; color: #f57f17; }
.theme-customer { background: #ede7f6; color: #4527a0; }
/* ── Glossary View ── */
.glossary-search {
max-width: 600px;
margin: 0 auto 24px;
}
.glossary-search input {
width: 100%;
padding: 12px 16px;
border: 2px solid #d9d9d5;
border-radius: 8px;
font-size: 15px;
outline: none;
}
.glossary-search input:focus { border-color: #0279B1; }
.glossary-layout {
max-width: 960px;
margin: 0 auto;
padding: 0 32px 40px;
}
.glossary-letter-nav {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
margin-bottom: 24px;
}
.glossary-letter-nav button {
width: 34px; height: 34px;
border: 1px solid #d9d9d5;
background: #fff;
border-radius: 6px;
font-weight: 700;
font-size: 13px;
cursor: pointer;
color: #444;
transition: all .15s;
}
.glossary-letter-nav button:hover,
.glossary-letter-nav button.active {
background: #0279B1;
color: #fff;
border-color: #0279B1;
}
.glossary-group-title {
font-size: 24px;
font-weight: 800;
color: #0279B1;
border-bottom: 3px solid #0279B1;
padding-bottom: 4px;
margin: 28px 0 14px;
}
.glossary-item {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 10px;
transition: border-color .15s;
}
.glossary-item:hover { border-color: #5DA61B; }
.glossary-term {
font-size: 15px;
font-weight: 700;
color: #222;
margin-bottom: 4px;
}
.glossary-def {
font-size: 13px;
color: #666;
line-height: 1.55;
margin-bottom: 8px;
}
.glossary-meta { display: flex; gap: 12px; flex-wrap: wrap; }
.glossary-meta span {
font-size: 11px;
color: #999;
}
.glossary-meta strong { color: #666; }
/* ── No results ── */
.no-results {
text-align: center;
padding: 60px 20px;
color: #999;
}
.no-results svg { margin-bottom: 16px; }
.no-results p { font-size: 15px; }
/* ═══════════════════════════════════════
DETAIL PAGE — shared
═══════════════════════════════════════ */
.detail-page {
max-width: 960px;
margin: 0 auto;
padding: 28px 32px 48px;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: none;
border: none;
color: #0279B1;
font-size: 14px;
font-weight: 600;
cursor: pointer;
padding: 6px 0;
margin-bottom: 20px;
transition: color .15s;
}
.back-btn:hover { color: #025f8a; }
.detail-header {
margin-bottom: 28px;
}
.detail-header .card-theme {
font-size: 12px;
padding: 4px 12px;
margin-bottom: 12px;
}
.detail-title {
font-size: 28px;
font-weight: 800;
color: #222;
line-height: 1.25;
margin-bottom: 10px;
}
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
}
/* Metadata grid */
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.meta-card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
}
.meta-card-label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .5px;
color: #999;
margin-bottom: 6px;
}
.meta-card-value {
font-size: 15px;
font-weight: 600;
color: #333;
}
/* Section blocks */
.detail-section {
margin-bottom: 32px;
}
.detail-section-title {
font-size: 16px;
font-weight: 700;
color: #222;
margin-bottom: 14px;
padding-bottom: 8px;
border-bottom: 2px solid #0279B1;
display: flex;
align-items: center;
gap: 8px;
}
.detail-section-title svg { flex-shrink: 0; }
/* Schema table */
.schema-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
.schema-table th {
background: #f7f8fa;
text-align: left;
padding: 10px 14px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .4px;
color: #666;
border-bottom: 2px solid #e0e0e0;
}
.schema-table td {
padding: 10px 14px;
font-size: 13px;
border-bottom: 1px solid #f0f0f0;
color: #444;
}
.schema-table tr:last-child td { border-bottom: none; }
.schema-table tr:hover td { background: #f9fbfd; }
.col-type {
font-family: "SF Mono", "Consolas", "Menlo", monospace;
font-size: 12px;
background: #f0f4f8;
color: #0279B1;
padding: 2px 8px;
border-radius: 4px;
}
.col-pk {
font-size: 10px;
background: #fff3e0;
color: #e65100;
padding: 2px 6px;
border-radius: 3px;
font-weight: 700;
margin-left: 6px;
}
.col-nullable {
font-size: 11px;
color: #999;
}
/* Sample data table */
.sample-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
font-size: 13px;
}
.sample-table th {
background: #f7f8fa;
padding: 9px 12px;
text-align: left;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .3px;
color: #666;
border-bottom: 2px solid #e0e0e0;
white-space: nowrap;
}
.sample-table td {
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
color: #444;
white-space: nowrap;
}
.sample-table tr:last-child td { border-bottom: none; }
.sample-table tr:hover td { background: #f9fbfd; }
.table-scroll {
overflow-x: auto;
border-radius: 8px;
}
/* Lineage */
.lineage-flow {
display: flex;
align-items: center;
gap: 0;
flex-wrap: wrap;
padding: 20px;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.lineage-node {
background: #f0f4f8;
border: 1px solid #d0dbe6;
border-radius: 8px;
padding: 12px 18px;
text-align: center;
min-width: 120px;
}
.lineage-node.current {
background: #0279B1;
color: #fff;
border-color: #0279B1;
font-weight: 700;
}
.lineage-node-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: .5px;
opacity: .7;
margin-bottom: 4px;
}
.lineage-node-name {
font-size: 13px;
font-weight: 600;
}
.lineage-arrow {
font-size: 22px;
color: #bbb;
padding: 0 10px;
}
/* Related items */
.related-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.related-card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 14px 16px;
cursor: pointer;
transition: border-color .15s, box-shadow .15s;
}
.related-card:hover {
border-color: #5DA61B;
box-shadow: 0 2px 8px rgba(93,166,27,.1);
}
.related-card-title {
font-size: 14px;
font-weight: 700;
color: #222;
margin-bottom: 4px;
}
.related-card-sub {
font-size: 12px;
color: #999;
}
/* ═══════════════════════════════════════
GLOSSARY DETAIL
═══════════════════════════════════════ */
.glossary-detail-term {
font-size: 28px;
font-weight: 800;
color: #222;
margin-bottom: 14px;
}
.glossary-detail-def {
font-size: 16px;
color: #555;
line-height: 1.7;
margin-bottom: 24px;
padding: 20px;
background: #fff;
border-left: 4px solid #0279B1;
border-radius: 0 8px 8px 0;
}
.glossary-detail-meta {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 14px;
margin-bottom: 32px;
}
.glossary-detail-meta .meta-card-value .status-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
}
.status-approved { background: #e8f5e9; color: #2e7d32; }
.status-draft { background: #fff8e1; color: #f57f17; }
.status-review { background: #fce4ec; color: #c62828; }
/* Usage examples */
.usage-example {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 10px;
}
.usage-context {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .4px;
color: #0279B1;
margin-bottom: 6px;
}
.usage-text {
font-size: 14px;
color: #444;
line-height: 1.6;
font-style: italic;
}
/* ── Responsive ── */
@media (max-width: 860px) {
.catalog-layout { flex-direction: column; }
.sidebar { width: 100%; }
.header { padding: 0 16px; }
.header-nav { display: none; }
.detail-page { padding: 20px 16px 40px; }
.detail-title { font-size: 22px; }
.lineage-flow { flex-direction: column; }
.lineage-arrow { transform: rotate(90deg); }
}
</style>
</head>
<body>
<!-- ── Header ── -->
<header class="header">
<div class="header-logo">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
Enterprise Data Catalog
</div>
<nav class="header-nav">
<button class="active" onclick="switchView('catalog')">Catalog</button>
<button onclick="switchView('glossary')">Business Glossary</button>
</nav>
</header>
<!-- ═══════════ CATALOG VIEW ═══════════ -->
<div id="view-catalog" class="view active">
<div class="search-section">
<div class="search-wrapper">
<input type="text" id="catalog-search" placeholder="Search datasets by name, description, keyword…" oninput="renderCatalog()">
<button class="search-btn" onclick="renderCatalog()">Search</button>
</div>
<div class="result-count" id="result-count"></div>
</div>
<div class="catalog-layout">
<aside class="sidebar">
<div class="active-filters" id="active-filters"></div>
<div id="filter-panels"></div>
</aside>
<main class="cards-area">
<div class="cards-grid" id="cards-grid"></div>
</main>
</div>
</div>
<!-- ═══════════ CATALOG DETAIL VIEW ═══════════ -->
<div id="view-catalog-detail" class="view">
<div class="detail-page" id="catalog-detail-content"></div>
</div>
<!-- ═══════════ GLOSSARY VIEW ═══════════ -->
<div id="view-glossary" class="view">
<div class="search-section">
<div class="glossary-search">
<input type="text" id="glossary-search" placeholder="Search business terms…" oninput="renderGlossary()">
</div>
</div>
<div class="glossary-layout">
<div class="glossary-letter-nav" id="letter-nav"></div>
<div id="glossary-list"></div>
</div>
</div>
<!-- ═══════════ GLOSSARY DETAIL VIEW ═══════════ -->
<div id="view-glossary-detail" class="view">
<div class="detail-page" id="glossary-detail-content"></div>
</div>
<script>
/* ═══════════════════════════════════════
DATA — Catalog Datasets (extended)
═══════════════════════════════════════ */
const datasets = [
{
title: "Employee Master Data",
description: "Comprehensive record of all active and inactive employees including personal details, department assignment, hire date, and employment status.",
theme: "HR",
owner: "HR Analytics Team",
steward: "Sarah Chen",
keywords: ["employee", "headcount", "personnel"],
updated: "2026-02-10",
created: "2021-03-15",
records: "12,458",
frequency: "Daily",
source: "Workday HCM",
format: "Snowflake Table",
classification: "Confidential",
quality: 94,
schema: [
{ name: "employee_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Unique employee identifier" },
{ name: "first_name", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Employee first name" },
{ name: "last_name", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Employee last name" },
{ name: "email", type: "VARCHAR(255)", pk: false, nullable: false, desc: "Corporate email address" },
{ name: "department_id", type: "VARCHAR(10)", pk: false, nullable: false, desc: "FK to department table" },
{ name: "hire_date", type: "DATE", pk: false, nullable: false, desc: "Date of hire" },
{ name: "job_title", type: "VARCHAR(150)", pk: false, nullable: true, desc: "Current job title" },
{ name: "status", type: "VARCHAR(20)", pk: false, nullable: false, desc: "Active / Inactive / On Leave" },
{ name: "manager_id", type: "VARCHAR(20)", pk: false, nullable: true, desc: "FK to employee_id of direct manager" },
{ name: "location", type: "VARCHAR(100)", pk: false, nullable: true, desc: "Primary office location" }
],
sampleData: [
["EMP-10421", "Alice", "Morgan", "alice.morgan@corp.com", "DEPT-05", "2019-06-01", "Sr. Analyst", "Active", "EMP-10200", "New York"],
["EMP-10422", "Brian", "Kato", "brian.kato@corp.com", "DEPT-12", "2021-01-15", "Engineer II", "Active", "EMP-10305", "London"],
["EMP-10423", "Carla", "Diaz", "carla.diaz@corp.com", "DEPT-05", "2018-03-22", "Manager", "Active", null, "New York"],
["EMP-10424", "David", "Liu", "david.liu@corp.com", "DEPT-08", "2023-09-10", "Intern", "Inactive", "EMP-10423", "Toronto"]
],
lineage: [
{ label: "Source", name: "Workday HCM" },
{ label: "Ingestion", name: "Fivetran Sync" },
{ label: "Staging", name: "stg_employees" },
{ label: "This Dataset", name: "Employee Master Data", current: true },
{ label: "Downstream", name: "HR Dashboard" }
],
relatedDatasets: [1, 12], // indexes
relatedTerms: [10, 21] // glossary indexes
},
{
title: "Monthly Revenue Report",
description: "Aggregated revenue figures broken down by business unit, product line, and region. Includes MoM and YoY comparisons.",
theme: "Finance",
owner: "Finance BI Team",
steward: "James Park",
keywords: ["revenue", "income", "financial"],
updated: "2026-02-01",
created: "2020-01-10",
records: "3,640",
frequency: "Monthly",
source: "SAP ERP",
format: "Snowflake View",
classification: "Restricted",
quality: 97,
schema: [
{ name: "report_month", type: "DATE", pk: true, nullable: false, desc: "First day of reporting month" },
{ name: "business_unit", type: "VARCHAR(50)", pk: true, nullable: false, desc: "Business unit name" },
{ name: "product_line", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Product line category" },
{ name: "region", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Geographic region" },
{ name: "gross_revenue", type: "DECIMAL(15,2)", pk: false, nullable: false, desc: "Total gross revenue" },
{ name: "net_revenue", type: "DECIMAL(15,2)", pk: false, nullable: false, desc: "Revenue after deductions" },
{ name: "mom_change_pct", type: "DECIMAL(5,2)", pk: false, nullable: true, desc: "Month-over-month % change" },
{ name: "yoy_change_pct", type: "DECIMAL(5,2)", pk: false, nullable: true, desc: "Year-over-year % change" }
],
sampleData: [
["2026-01-01", "Enterprise", "Cloud Platform", "North America", "4,250,000.00", "3,825,000.00", "+3.2%", "+18.5%"],
["2026-01-01", "Enterprise", "Consulting", "EMEA", "1,120,000.00", "952,000.00", "-1.1%", "+7.3%"],
["2026-01-01", "SMB", "SaaS", "APAC", "780,000.00", "702,000.00", "+5.8%", "+24.1%"]
],
lineage: [
{ label: "Source", name: "SAP ERP" },
{ label: "ETL", name: "dbt Transform" },
{ label: "This Dataset", name: "Monthly Revenue Report", current: true },
{ label: "Downstream", name: "CFO Dashboard" }
],
relatedDatasets: [7, 14],
relatedTerms: [0, 8, 12, 18]
},
{
title: "Customer 360 Profile",
description: "Unified customer view combining CRM, support tickets, purchase history, and web analytics. Golden record for each customer.",
theme: "Customer",
owner: "Data Engineering",
steward: "Maria Gonzalez",
keywords: ["customer", "CRM", "profile"],
updated: "2026-01-28",
created: "2022-06-01",
records: "845,210",
frequency: "Daily",
source: "Salesforce + Zendesk + Shopify",
format: "Snowflake Table",
classification: "Confidential",
quality: 89,
schema: [
{ name: "customer_id", type: "VARCHAR(30)", pk: true, nullable: false, desc: "Unified customer identifier" },
{ name: "full_name", type: "VARCHAR(200)", pk: false, nullable: false, desc: "Customer full name" },
{ name: "email", type: "VARCHAR(255)", pk: false, nullable: true, desc: "Primary email" },
{ name: "segment", type: "VARCHAR(30)", pk: false, nullable: false, desc: "Enterprise / SMB / Consumer" },
{ name: "lifetime_value", type: "DECIMAL(12,2)", pk: false, nullable: true, desc: "Predicted CLV" },
{ name: "total_orders", type: "INTEGER", pk: false, nullable: false, desc: "Cumulative order count" },
{ name: "last_interaction", type: "TIMESTAMP", pk: false, nullable: true, desc: "Most recent touchpoint" },
{ name: "nps_score", type: "INTEGER", pk: false, nullable: true, desc: "Latest NPS response" },
{ name: "churn_risk", type: "VARCHAR(10)", pk: false, nullable: true, desc: "Low / Medium / High" }
],
sampleData: [
["CUST-00291", "Acme Corp", "billing@acme.com", "Enterprise", "284,500.00", "142", "2026-01-27 14:32", "72", "Low"],
["CUST-00292", "BrightStar LLC", "info@brightstar.io", "SMB", "18,320.00", "23", "2026-01-25 09:15", "45", "Medium"],
["CUST-00293", "Jane Smith", "jane.s@email.com", "Consumer", "1,240.00", "8", "2026-01-20 18:44", null, "Low"]
],
lineage: [
{ label: "Source", name: "Salesforce CRM" },
{ label: "Source", name: "Zendesk" },
{ label: "Merge", name: "Identity Resolution" },
{ label: "This Dataset", name: "Customer 360 Profile", current: true },
{ label: "Downstream", name: "Marketing Automation" }
],
relatedDatasets: [13, 3],
relatedTerms: [2, 5, 16, 11]
},
{
title: "Sales Pipeline Tracker",
description: "Real-time view of opportunities across all sales stages, including expected close dates, deal values, and assigned representatives.",
theme: "Sales",
owner: "Sales Operations",
steward: "Tom Bradley",
keywords: ["pipeline", "opportunities", "deals"],
updated: "2026-02-18",
created: "2021-09-01",
records: "5,892",
frequency: "Real-time",
source: "Salesforce CRM",
format: "Snowflake Table",
classification: "Internal",
quality: 91,
schema: [
{ name: "opportunity_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Salesforce opportunity ID" },
{ name: "account_name", type: "VARCHAR(200)", pk: false, nullable: false, desc: "Customer account" },
{ name: "stage", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Pipeline stage" },
{ name: "deal_value", type: "DECIMAL(12,2)", pk: false, nullable: false, desc: "Expected deal amount" },
{ name: "close_date", type: "DATE", pk: false, nullable: true, desc: "Expected close date" },
{ name: "rep_name", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Assigned sales rep" },
{ name: "probability", type: "DECIMAL(5,2)", pk: false, nullable: true, desc: "Win probability %" }
],
sampleData: [
["OPP-4821", "Globex Inc", "Negotiation", "125,000.00", "2026-03-15", "Mike Ross", "75%"],
["OPP-4822", "Initech", "Discovery", "45,000.00", "2026-04-01", "Rachel Zane", "30%"],
["OPP-4823", "Wayne Enterprises", "Closed Won", "310,000.00", "2026-02-10", "Harvey Specter", "100%"]
],
lineage: [
{ label: "Source", name: "Salesforce CRM" },
{ label: "Sync", name: "Fivetran" },
{ label: "This Dataset", name: "Sales Pipeline Tracker", current: true },
{ label: "Downstream", name: "Sales Dashboard" }
],
relatedDatasets: [8, 2],
relatedTerms: [4, 14, 20]
},
{
title: "IT Asset Inventory",
description: "Complete hardware and software inventory across all offices. Tracks lifecycle status, warranty, assigned users, and depreciation.",
theme: "IT",
owner: "IT Service Management",
steward: "Kevin Patel",
keywords: ["assets", "hardware", "software", "inventory"],
updated: "2026-01-15",
created: "2020-07-20",
records: "23,711",
frequency: "Weekly",
source: "ServiceNow CMDB",
format: "Snowflake Table",
classification: "Internal",
quality: 86,
schema: [
{ name: "asset_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Unique asset tag" },
{ name: "asset_type", type: "VARCHAR(30)", pk: false, nullable: false, desc: "Hardware / Software / Peripheral" },
{ name: "make_model", type: "VARCHAR(150)", pk: false, nullable: false, desc: "Manufacturer and model" },
{ name: "assigned_to", type: "VARCHAR(20)", pk: false, nullable: true, desc: "FK to employee_id" },
{ name: "status", type: "VARCHAR(20)", pk: false, nullable: false, desc: "In Use / In Stock / Retired" },
{ name: "purchase_date", type: "DATE", pk: false, nullable: false, desc: "Date of purchase" },
{ name: "warranty_exp", type: "DATE", pk: false, nullable: true, desc: "Warranty expiration date" }
],
sampleData: [
["AST-30012", "Hardware", "Dell Latitude 5540", "EMP-10421", "In Use", "2024-03-10", "2027-03-10"],
["AST-30013", "Software", "Adobe Creative Cloud", "EMP-10422", "In Use", "2025-01-01", "2026-12-31"],
["AST-30014", "Hardware", "Apple MacBook Pro 16\"", null, "In Stock", "2025-11-20", "2028-11-20"]
],
lineage: [
{ label: "Source", name: "ServiceNow CMDB" },
{ label: "ETL", name: "Airflow DAG" },
{ label: "This Dataset", name: "IT Asset Inventory", current: true },
{ label: "Downstream", name: "IT Cost Report" }
],
relatedDatasets: [0, 16],
relatedTerms: [6, 7]
},
{
title: "Marketing Campaign Performance",
description: "Performance metrics for all digital and offline marketing campaigns including impressions, CTR, conversions, and ROI.",
theme: "Marketing",
owner: "Marketing Analytics",
steward: "Lisa Nguyen",
keywords: ["campaigns", "ROI", "advertising"],
updated: "2026-02-20",
created: "2022-01-05",
records: "1,204",
frequency: "Daily",
source: "Google Ads + Meta Ads + HubSpot",
format: "Snowflake View",
classification: "Internal",
quality: 92,
schema: [
{ name: "campaign_id", type: "VARCHAR(30)", pk: true, nullable: false, desc: "Campaign identifier" },
{ name: "campaign_name", type: "VARCHAR(200)", pk: false, nullable: false, desc: "Campaign display name" },
{ name: "channel", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Marketing channel" },
{ name: "start_date", type: "DATE", pk: false, nullable: false, desc: "Campaign launch date" },
{ name: "impressions", type: "BIGINT", pk: false, nullable: false, desc: "Total impressions served" },
{ name: "clicks", type: "INTEGER", pk: false, nullable: false, desc: "Total clicks" },
{ name: "conversions", type: "INTEGER", pk: false, nullable: false, desc: "Conversion events" },
{ name: "spend", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "Total spend in USD" },
{ name: "roi_pct", type: "DECIMAL(6,2)", pk: false, nullable: true, desc: "Return on investment %" }
],
sampleData: [
["CMP-2026-041", "Spring SaaS Launch", "Google Ads", "2026-02-01", "1,240,000", "18,600", "342", "24,500.00", "280%"],
["CMP-2026-042", "Brand Awareness Q1", "Meta Ads", "2026-01-15", "3,800,000", "42,100", "128", "35,000.00", "95%"],
["CMP-2026-043", "Webinar Promo", "LinkedIn", "2026-02-10", "520,000", "7,800", "89", "8,200.00", "210%"]
],
lineage: [
{ label: "Source", name: "Google Ads API" },
{ label: "Source", name: "Meta Ads API" },
{ label: "ETL", name: "Fivetran + dbt" },
{ label: "This Dataset", name: "Campaign Performance", current: true },
{ label: "Downstream", name: "CMO Dashboard" }
],
relatedDatasets: [10, 2],
relatedTerms: [1, 4]
},
{
title: "Supply Chain Shipments",
description: "Tracks all inbound and outbound shipments, carrier information, transit times, and delivery status across warehouses.",
theme: "Operations",
owner: "Supply Chain Team",
steward: "Robert Kim",
keywords: ["logistics", "shipping", "warehouse"],
updated: "2026-02-05",
created: "2021-05-12",
records: "198,340",
frequency: "Real-time",
source: "Oracle SCM Cloud",
format: "Snowflake Table",
classification: "Internal",
quality: 88,
schema: [
{ name: "shipment_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Unique shipment ID" },
{ name: "direction", type: "VARCHAR(10)", pk: false, nullable: false, desc: "Inbound / Outbound" },
{ name: "carrier", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Shipping carrier" },
{ name: "origin", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Origin warehouse/address" },
{ name: "destination", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Destination address" },
{ name: "ship_date", type: "TIMESTAMP", pk: false, nullable: false, desc: "Shipped timestamp" },
{ name: "delivery_date", type: "TIMESTAMP", pk: false, nullable: true, desc: "Actual delivery" },
{ name: "status", type: "VARCHAR(20)", pk: false, nullable: false, desc: "In Transit / Delivered / Delayed" }
],
sampleData: [
["SHP-88201", "Outbound", "FedEx", "Warehouse East", "Chicago, IL", "2026-02-03 08:00", "2026-02-05 14:22", "Delivered"],
["SHP-88202", "Inbound", "DHL", "Shenzhen, CN", "Warehouse West", "2026-01-28 12:00", null, "In Transit"],
["SHP-88203", "Outbound", "UPS", "Warehouse East", "Miami, FL", "2026-02-04 10:30", "2026-02-06 09:15", "Delivered"]
],
lineage: [
{ label: "Source", name: "Oracle SCM Cloud" },
{ label: "Ingestion", name: "Kafka Stream" },
{ label: "This Dataset", name: "Supply Chain Shipments", current: true },
{ label: "Downstream", name: "Ops Dashboard" }
],
relatedDatasets: [11, 8],
relatedTerms: [13, 19]
},
{
title: "General Ledger Transactions",
description: "All financial transactions posted to the general ledger, including journal entries, account codes, cost centers, and posting dates.",
theme: "Finance",
owner: "Finance BI Team",
steward: "James Park",
keywords: ["accounting", "ledger", "transactions"],
updated: "2026-02-12",
created: "2019-08-01",
records: "1,482,009",
frequency: "Daily",
source: "SAP ERP",
format: "Snowflake Table",
classification: "Restricted",
quality: 99,
schema: [
{ name: "journal_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Journal entry ID" },
{ name: "line_no", type: "INTEGER", pk: true, nullable: false, desc: "Line item number" },
{ name: "posting_date", type: "DATE", pk: false, nullable: false, desc: "GL posting date" },
{ name: "account_code", type: "VARCHAR(10)", pk: false, nullable: false, desc: "GL account code" },
{ name: "cost_center", type: "VARCHAR(20)", pk: false, nullable: false, desc: "Cost center" },
{ name: "debit", type: "DECIMAL(15,2)", pk: false, nullable: true, desc: "Debit amount" },
{ name: "credit", type: "DECIMAL(15,2)", pk: false, nullable: true, desc: "Credit amount" },
{ name: "description", type: "VARCHAR(300)", pk: false, nullable: true, desc: "Transaction memo" }
],
sampleData: [
["JE-90201", "1", "2026-02-11", "4100", "CC-SALES", "125,000.00", null, "Q1 software license revenue"],
["JE-90201", "2", "2026-02-11", "1200", "CC-SALES", null, "125,000.00", "Accounts receivable offset"],
["JE-90202", "1", "2026-02-12", "5010", "CC-ENG", "42,000.00", null, "Cloud hosting Feb 2026"]
],
lineage: [
{ label: "Source", name: "SAP ERP" },
{ label: "ETL", name: "Informatica" },
{ label: "This Dataset", name: "General Ledger Transactions", current: true },
{ label: "Downstream", name: "Financial Reports" }
],
relatedDatasets: [1, 14],
relatedTerms: [3, 8, 18]
},
{
title: "Product Catalog Master",
description: "Central repository of all products and SKUs with pricing, category hierarchy, supplier information, and active/discontinued status.",
theme: "Sales",
owner: "Product Management",
steward: "Amy Watanabe",
keywords: ["products", "SKU", "pricing"],
updated: "2026-01-30",
created: "2020-04-15",
records: "34,589",
frequency: "Weekly",
source: "ERP + PIM System",
format: "Snowflake Table",
classification: "Internal",
quality: 93,
schema: [
{ name: "sku", type: "VARCHAR(30)", pk: true, nullable: false, desc: "Stock keeping unit" },
{ name: "product_name", type: "VARCHAR(200)", pk: false, nullable: false, desc: "Product display name" },
{ name: "category", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Product category" },
{ name: "unit_price", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "List price in USD" },
{ name: "supplier", type: "VARCHAR(150)", pk: false, nullable: true, desc: "Primary supplier" },
{ name: "status", type: "VARCHAR(20)", pk: false, nullable: false, desc: "Active / Discontinued" }
],
sampleData: [
["SKU-00142", "CloudSync Pro License", "Software", "299.00", "Internal", "Active"],
["SKU-00143", "DataVault Appliance", "Hardware", "12,499.00", "Dell Technologies", "Active"],
["SKU-00144", "Legacy Connector v2", "Software", "49.00", "Internal", "Discontinued"]
],
lineage: [
{ label: "Source", name: "SAP ERP" },
{ label: "Source", name: "PIM System" },
{ label: "This Dataset", name: "Product Catalog Master", current: true },
{ label: "Downstream", name: "E-commerce Site" }
],
relatedDatasets: [3, 6],
relatedTerms: [12]
},
{
title: "Regulatory Compliance Log",
description: "Audit trail of all compliance-related activities, inspections, certifications, and policy acknowledgments across departments.",
theme: "Compliance",
owner: "Legal & Compliance",
steward: "Diana Okafor",
keywords: ["audit", "regulatory", "compliance"],
updated: "2026-02-22",
created: "2021-11-01",
records: "7,823",
frequency: "Daily",
source: "GRC Platform",
format: "Snowflake Table",
classification: "Restricted",
quality: 96,
schema: [
{ name: "log_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Log entry ID" },
{ name: "activity_type", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Inspection / Certification / Acknowledgment" },
{ name: "regulation", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Applicable regulation" },
{ name: "department", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Department assessed" },
{ name: "status", type: "VARCHAR(20)", pk: false, nullable: false, desc: "Compliant / Non-Compliant / Pending" },
{ name: "assessed_date", type: "DATE", pk: false, nullable: false, desc: "Assessment date" },
{ name: "assessor", type: "VARCHAR(100)", pk: false, nullable: true, desc: "Person who conducted assessment" }
],
sampleData: [
["CL-4001", "Inspection", "SOX Section 404", "Finance", "Compliant", "2026-02-20", "Diana Okafor"],
["CL-4002", "Certification", "ISO 27001", "IT", "Compliant", "2026-02-18", "External Auditor"],
["CL-4003", "Acknowledgment", "Code of Conduct", "Sales", "Pending", "2026-02-22", null]
],
lineage: [
{ label: "Source", name: "GRC Platform" },
{ label: "ETL", name: "Airflow DAG" },
{ label: "This Dataset", name: "Regulatory Compliance Log", current: true },
{ label: "Downstream", name: "Board Report" }
],
relatedDatasets: [17, 0],
relatedTerms: [17]
},
{
title: "Website Analytics",
description: "Daily web traffic data including page views, sessions, bounce rate, referral sources, and user demographics from Google Analytics.",
theme: "Marketing",
owner: "Digital Team",
steward: "Lisa Nguyen",
keywords: ["web", "traffic", "analytics"],
updated: "2026-02-24",
created: "2020-02-01",
records: "2,310,000",
frequency: "Daily",
source: "Google Analytics 4",
format: "BigQuery Export",
classification: "Internal",
quality: 90,
schema: [
{ name: "session_date", type: "DATE", pk: false, nullable: false, desc: "Session date" },
{ name: "session_id", type: "VARCHAR(50)", pk: true, nullable: false, desc: "Unique session ID" },
{ name: "page_path", type: "VARCHAR(500)", pk: false, nullable: false, desc: "URL path visited" },
{ name: "page_views", type: "INTEGER", pk: false, nullable: false, desc: "Pages viewed in session" },
{ name: "bounce", type: "BOOLEAN", pk: false, nullable: false, desc: "Single-page session flag" },
{ name: "source", type: "VARCHAR(100)", pk: false, nullable: true, desc: "Traffic source" },
{ name: "country", type: "VARCHAR(50)", pk: false, nullable: true, desc: "Visitor country" }
],
sampleData: [
["2026-02-24", "GA-9928301", "/products/cloud", "4", "false", "google / organic", "United States"],
["2026-02-24", "GA-9928302", "/blog/data-tips", "1", "true", "twitter / social", "United Kingdom"],
["2026-02-24", "GA-9928303", "/pricing", "3", "false", "direct", "Canada"]
],
lineage: [
{ label: "Source", name: "Google Analytics 4" },
{ label: "Export", name: "BigQuery" },
{ label: "ETL", name: "dbt Transform" },
{ label: "This Dataset", name: "Website Analytics", current: true },
{ label: "Downstream", name: "Marketing Dashboard" }
],
relatedDatasets: [5, 2],
relatedTerms: [1]
},
{
title: "Vendor Master List",
description: "Approved vendor directory with contract terms, payment terms, performance scores, and contact information.",
theme: "Operations",
owner: "Procurement",
steward: "Robert Kim",
keywords: ["vendors", "suppliers", "procurement"],
updated: "2026-01-20",
created: "2020-11-05",
records: "1,842",
frequency: "Weekly",
source: "SAP Ariba",
format: "Snowflake Table",
classification: "Internal",
quality: 91,
schema: [
{ name: "vendor_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Vendor identifier" },
{ name: "vendor_name", type: "VARCHAR(200)", pk: false, nullable: false, desc: "Legal entity name" },
{ name: "category", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Vendor category" },
{ name: "contract_end", type: "DATE", pk: false, nullable: true, desc: "Contract expiration" },
{ name: "payment_terms", type: "VARCHAR(30)", pk: false, nullable: false, desc: "Net 30 / Net 60 etc." },
{ name: "perf_score", type: "DECIMAL(3,1)", pk: false, nullable: true, desc: "Performance rating (1-5)" }
],
sampleData: [
["VND-0201", "AWS", "Cloud Infrastructure", "2027-12-31", "Net 30", "4.8"],
["VND-0202", "Deloitte Consulting", "Professional Services", "2026-06-30", "Net 45", "4.2"],
["VND-0203", "Office Depot", "Office Supplies", "2026-12-31", "Net 30", "3.9"]
],
lineage: [
{ label: "Source", name: "SAP Ariba" },
{ label: "ETL", name: "Informatica" },
{ label: "This Dataset", name: "Vendor Master List", current: true },
{ label: "Downstream", name: "Procurement Dashboard" }
],
relatedDatasets: [6, 9],
relatedTerms: [19]
},
{
title: "Payroll Summary",
description: "Monthly payroll runs with gross pay, deductions, taxes, net pay, and benefits costs aggregated by department and location.",
theme: "HR",
owner: "Payroll Team",
steward: "Sarah Chen",
keywords: ["payroll", "salary", "compensation"],
updated: "2026-02-15",
created: "2020-01-01",
records: "149,400",
frequency: "Monthly",
source: "ADP Workforce Now",
format: "Snowflake Table",
classification: "Restricted",
quality: 98,
schema: [
{ name: "payroll_month", type: "DATE", pk: true, nullable: false, desc: "Payroll period" },
{ name: "employee_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "FK to employee master" },
{ name: "gross_pay", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "Gross pay amount" },
{ name: "deductions", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "Total deductions" },
{ name: "taxes", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "Tax withholding" },
{ name: "net_pay", type: "DECIMAL(10,2)", pk: false, nullable: false, desc: "Take-home pay" },
{ name: "benefits_cost", type: "DECIMAL(10,2)", pk: false, nullable: true, desc: "Employer benefits cost" }
],
sampleData: [
["2026-02-01", "EMP-10421", "8,500.00", "850.00", "2,125.00", "5,525.00", "1,200.00"],
["2026-02-01", "EMP-10422", "7,200.00", "720.00", "1,800.00", "4,680.00", "1,200.00"],
["2026-02-01", "EMP-10423", "12,000.00", "1,200.00", "3,000.00", "7,800.00", "1,500.00"]
],
lineage: [
{ label: "Source", name: "ADP Workforce Now" },
{ label: "ETL", name: "Secure Pipeline" },
{ label: "This Dataset", name: "Payroll Summary", current: true },
{ label: "Downstream", name: "HR Cost Report" }
],
relatedDatasets: [0, 14],
relatedTerms: [10]
},
{
title: "Customer Support Tickets",
description: "All support interactions including issue category, priority, resolution time, assigned agent, and customer satisfaction score.",
theme: "Customer",
owner: "Support Analytics",
steward: "Maria Gonzalez",
keywords: ["support", "tickets", "CSAT"],
updated: "2026-02-23",
created: "2021-02-15",
records: "421,070",
frequency: "Real-time",
source: "Zendesk",
format: "Snowflake Table",
classification: "Internal",
quality: 87,
schema: [
{ name: "ticket_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Zendesk ticket ID" },
{ name: "customer_id", type: "VARCHAR(30)", pk: false, nullable: false, desc: "FK to customer 360" },
{ name: "category", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Issue category" },
{ name: "priority", type: "VARCHAR(10)", pk: false, nullable: false, desc: "Low / Medium / High / Urgent" },
{ name: "created_at", type: "TIMESTAMP", pk: false, nullable: false, desc: "Ticket creation time" },
{ name: "resolved_at", type: "TIMESTAMP", pk: false, nullable: true, desc: "Resolution time" },
{ name: "agent", type: "VARCHAR(100)", pk: false, nullable: true, desc: "Assigned agent" },
{ name: "csat_score", type: "INTEGER", pk: false, nullable: true, desc: "Satisfaction 1-5" }
],
sampleData: [
["TKT-98201", "CUST-00291", "Billing", "Medium", "2026-02-22 09:30", "2026-02-22 14:15", "Alex Murphy", "5"],
["TKT-98202", "CUST-00292", "Technical", "High", "2026-02-23 08:00", null, "Sam Jones", null],
["TKT-98203", "CUST-00293", "General Inquiry", "Low", "2026-02-23 11:45", "2026-02-23 12:00", "Bot", "4"]
],
lineage: [
{ label: "Source", name: "Zendesk" },
{ label: "Sync", name: "Fivetran" },
{ label: "This Dataset", name: "Customer Support Tickets", current: true },
{ label: "Downstream", name: "Support Dashboard" }
],
relatedDatasets: [2, 3],
relatedTerms: [15, 16, 19]
},
{
title: "Budget vs Actuals",
description: "Comparison of planned budget to actual spend by department, project, and GL account. Includes variance analysis and forecasts.",
theme: "Finance",
owner: "FP&A Team",
steward: "James Park",
keywords: ["budget", "forecast", "variance"],
updated: "2026-02-08",
created: "2020-06-01",
records: "18,200",
frequency: "Monthly",
source: "Adaptive Planning",
format: "Snowflake View",
classification: "Restricted",
quality: 95,
schema: [
{ name: "fiscal_month", type: "DATE", pk: true, nullable: false, desc: "Fiscal month" },
{ name: "department", type: "VARCHAR(50)", pk: true, nullable: false, desc: "Department name" },
{ name: "gl_account", type: "VARCHAR(10)", pk: true, nullable: false, desc: "GL account code" },
{ name: "budget_amt", type: "DECIMAL(12,2)", pk: false, nullable: false, desc: "Budgeted amount" },
{ name: "actual_amt", type: "DECIMAL(12,2)", pk: false, nullable: false, desc: "Actual spend" },
{ name: "variance", type: "DECIMAL(12,2)", pk: false, nullable: false, desc: "Budget minus actual" },
{ name: "variance_pct", type: "DECIMAL(5,2)", pk: false, nullable: true, desc: "Variance as %" }
],
sampleData: [
["2026-02-01", "Engineering", "5010", "150,000.00", "142,800.00", "7,200.00", "4.8%"],
["2026-02-01", "Marketing", "6200", "85,000.00", "91,300.00", "-6,300.00", "-7.4%"],
["2026-02-01", "Sales", "6100", "120,000.00", "118,500.00", "1,500.00", "1.3%"]
],
lineage: [
{ label: "Source", name: "Adaptive Planning" },
{ label: "Source", name: "SAP ERP (Actuals)" },
{ label: "ETL", name: "dbt Transform" },
{ label: "This Dataset", name: "Budget vs Actuals", current: true },
{ label: "Downstream", name: "CFO Dashboard" }
],
relatedDatasets: [1, 7],
relatedTerms: [8, 13]
},
{
title: "Data Quality Scorecard",
description: "Automated data quality metrics across all major datasets: completeness, accuracy, timeliness, consistency, and uniqueness scores.",
theme: "IT",
owner: "Data Governance",
steward: "Kevin Patel",
keywords: ["quality", "governance", "metrics"],
updated: "2026-02-19",
created: "2023-04-01",
records: "560",
frequency: "Daily",
source: "Great Expectations + dbt Tests",
format: "Snowflake Table",
classification: "Internal",
quality: 100,
schema: [
{ name: "dataset_name", type: "VARCHAR(200)", pk: true, nullable: false, desc: "Monitored dataset name" },
{ name: "check_date", type: "DATE", pk: true, nullable: false, desc: "Date of quality check" },
{ name: "completeness", type: "DECIMAL(5,2)", pk: false, nullable: false, desc: "% non-null values" },
{ name: "accuracy", type: "DECIMAL(5,2)", pk: false, nullable: false, desc: "% passing accuracy rules" },
{ name: "timeliness", type: "DECIMAL(5,2)", pk: false, nullable: false, desc: "% delivered on time" },
{ name: "consistency", type: "DECIMAL(5,2)", pk: false, nullable: false, desc: "% cross-system consistency" },
{ name: "overall_score", type: "DECIMAL(5,2)", pk: false, nullable: false, desc: "Weighted composite score" }
],
sampleData: [
["Employee Master Data", "2026-02-19", "98.2", "94.1", "100.0", "91.5", "94.0"],
["General Ledger Transactions", "2026-02-19", "99.9", "99.8", "100.0", "99.5", "99.0"],
["Customer 360 Profile", "2026-02-19", "92.1", "87.4", "95.0", "84.8", "89.0"]
],
lineage: [
{ label: "Source", name: "Great Expectations" },
{ label: "Source", name: "dbt Tests" },
{ label: "This Dataset", name: "Data Quality Scorecard", current: true },
{ label: "Downstream", name: "Governance Dashboard" }
],
relatedDatasets: [16, 4],
relatedTerms: [6, 7]
},
{
title: "Incident Management Log",
description: "IT incident records including severity levels, root cause analysis, resolution steps, and mean time to resolution (MTTR).",
theme: "IT",
owner: "IT Service Management",
steward: "Kevin Patel",
keywords: ["incidents", "MTTR", "outages"],
updated: "2026-02-17",
created: "2020-09-01",
records: "4,290",
frequency: "Real-time",
source: "PagerDuty + ServiceNow",
format: "Snowflake Table",
classification: "Internal",
quality: 93,
schema: [
{ name: "incident_id", type: "VARCHAR(20)", pk: true, nullable: false, desc: "Incident identifier" },
{ name: "severity", type: "VARCHAR(10)", pk: false, nullable: false, desc: "SEV1-SEV4" },
{ name: "title", type: "VARCHAR(300)", pk: false, nullable: false, desc: "Incident title" },
{ name: "service", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Affected service" },
{ name: "created_at", type: "TIMESTAMP", pk: false, nullable: false, desc: "Incident start" },
{ name: "resolved_at", type: "TIMESTAMP", pk: false, nullable: true, desc: "Resolution time" },
{ name: "root_cause", type: "VARCHAR(500)", pk: false, nullable: true, desc: "Root cause summary" },
{ name: "mttr_minutes", type: "INTEGER", pk: false, nullable: true, desc: "Minutes to resolve" }
],
sampleData: [
["INC-5501", "SEV2", "API latency spike", "Payment Service", "2026-02-16 03:22", "2026-02-16 04:50", "DB connection pool exhaustion", "88"],
["INC-5502", "SEV3", "Login page 503 errors", "Auth Service", "2026-02-17 09:00", "2026-02-17 09:25", "Expired TLS certificate", "25"],
["INC-5503", "SEV1", "Data pipeline failure", "ETL Platform", "2026-02-17 14:00", null, null, null]
],
lineage: [
{ label: "Source", name: "PagerDuty" },
{ label: "Source", name: "ServiceNow" },
{ label: "ETL", name: "Airflow DAG" },
{ label: "This Dataset", name: "Incident Management Log", current: true },
{ label: "Downstream", name: "SRE Dashboard" }
],
relatedDatasets: [4, 15],
relatedTerms: [15, 19]
},
{
title: "GDPR Consent Records",
description: "Customer consent and preference records for data processing activities under GDPR, including opt-in/out timestamps.",
theme: "Compliance",
owner: "Data Privacy Office",
steward: "Diana Okafor",
keywords: ["GDPR", "privacy", "consent"],
updated: "2026-02-14",
created: "2022-05-25",
records: "623,800",
frequency: "Real-time",
source: "OneTrust CMP",
format: "Snowflake Table",
classification: "Restricted",
quality: 97,
schema: [
{ name: "consent_id", type: "VARCHAR(30)", pk: true, nullable: false, desc: "Consent record ID" },
{ name: "customer_id", type: "VARCHAR(30)", pk: false, nullable: false, desc: "FK to customer" },
{ name: "purpose", type: "VARCHAR(100)", pk: false, nullable: false, desc: "Processing purpose" },
{ name: "consent_given", type: "BOOLEAN", pk: false, nullable: false, desc: "Opt-in status" },
{ name: "consent_date", type: "TIMESTAMP", pk: false, nullable: false, desc: "When consent was given/revoked" },
{ name: "source", type: "VARCHAR(50)", pk: false, nullable: false, desc: "Where consent was collected" },
{ name: "expiry_date", type: "DATE", pk: false, nullable: true, desc: "Consent expiration" }
],
sampleData: [
["CON-220001", "CUST-00291", "Marketing emails", "true", "2025-11-10 08:30", "Website banner", "2026-11-10"],
["CON-220002", "CUST-00292", "Analytics tracking", "false", "2026-01-05 12:00", "Preference center", null],
["CON-220003", "CUST-00293", "Marketing emails", "true", "2026-02-14 09:45", "Signup form", "2027-02-14"]
],
lineage: [
{ label: "Source", name: "OneTrust CMP" },
{ label: "Sync", name: "API Integration" },
{ label: "This Dataset", name: "GDPR Consent Records", current: true },
{ label: "Downstream", name: "Privacy Dashboard" }
],
relatedDatasets: [2, 9],
relatedTerms: [17]
}
];
/* ═══════════════════════════════════════
DATA — Business Glossary
═══════════════════════════════════════ */
const glossary = [
{ term: "Annual Recurring Revenue (ARR)", definition: "The annualized value of active subscription contracts, excluding one-time fees. Used as the primary top-line growth metric.", domain: "Finance", owner: "FP&A Team", status: "Approved", approved: "2024-06-15", formula: "Sum of all active subscription contract values, annualized", usageExamples: [{ context: "Board Report", text: "\"Our ARR grew 24% year-over-year, reaching $48M at end of Q4.\"" }, { context: "Sales Meeting", text: "\"This deal adds $120K to our ARR once the contract is signed.\"" }], relatedTerms: [8, 12, 18], relatedDatasets: [1, 14] },
{ term: "Bounce Rate", definition: "The percentage of website visitors who navigate away after viewing only one page without any further interaction.", domain: "Marketing", owner: "Digital Team", status: "Approved", approved: "2024-03-10", formula: "Single-page sessions / Total sessions x 100", usageExamples: [{ context: "Marketing Review", text: "\"The landing page bounce rate dropped from 68% to 52% after the redesign.\"" }], relatedTerms: [16], relatedDatasets: [10] },
{ term: "Churn Rate", definition: "The percentage of customers or subscribers who stop doing business with the company during a given time period.", domain: "Customer", owner: "CX Analytics", status: "Approved", approved: "2024-04-22", formula: "Customers lost during period / Customers at start of period x 100", usageExamples: [{ context: "Executive Summary", text: "\"Monthly churn rate held steady at 2.1%, below the 3% target.\"" }, { context: "Retention Analysis", text: "\"Customers on annual plans have a churn rate 60% lower than monthly subscribers.\"" }], relatedTerms: [5, 16, 0], relatedDatasets: [2, 13] },
{ term: "Cost of Goods Sold (COGS)", definition: "The direct costs attributable to the production of goods sold, including materials and direct labor.", domain: "Finance", owner: "Accounting", status: "Approved", approved: "2024-01-20", formula: "Beginning inventory + Purchases Ending inventory", usageExamples: [{ context: "Financial Statement", text: "\"COGS for Q1 was $12.4M, representing a 3% increase driven by rising material costs.\"" }], relatedTerms: [12, 8], relatedDatasets: [7, 1] },
{ term: "Customer Acquisition Cost (CAC)", definition: "The total cost of acquiring a new customer, including marketing and sales expenses divided by the number of new customers acquired.", domain: "Sales", owner: "Sales Operations", status: "Approved", approved: "2024-05-18", formula: "(Marketing spend + Sales spend) / New customers acquired", usageExamples: [{ context: "Growth Meeting", text: "\"Our blended CAC is $340 per customer, with enterprise CAC at $2,800 and self-serve at $45.\"" }], relatedTerms: [5, 0, 14], relatedDatasets: [5, 3] },
{ term: "Customer Lifetime Value (CLV)", definition: "The predicted total net profit attributed to the entire future relationship with a customer.", domain: "Customer", owner: "Data Science", status: "Approved", approved: "2024-04-22", formula: "Average revenue per customer x Gross margin % x Average customer lifespan", usageExamples: [{ context: "Strategy Deck", text: "\"Enterprise CLV is $84K with a CLV:CAC ratio of 30:1, well above our 3:1 threshold.\"" }], relatedTerms: [2, 4, 0], relatedDatasets: [2] },
{ term: "Data Lineage", definition: "The complete lifecycle of data from origin to destination, including all transformations and movements between systems.", domain: "IT", owner: "Data Governance", status: "Approved", approved: "2025-01-08", formula: null, usageExamples: [{ context: "Governance Meeting", text: "\"We traced the data lineage and found the discrepancy originates in the staging layer transformation.\"" }], relatedTerms: [7, 9, 11], relatedDatasets: [15] },
{ term: "Data Steward", definition: "An individual responsible for the management, quality, and fitness of data elements both content and metadata.", domain: "IT", owner: "Data Governance", status: "Approved", approved: "2025-01-08", formula: null, usageExamples: [{ context: "Data Governance Policy", text: "\"Each critical dataset must have a designated Data Steward who reviews quality scores weekly.\"" }], relatedTerms: [6, 11], relatedDatasets: [15] },
{ term: "EBITDA", definition: "Earnings Before Interest, Taxes, Depreciation, and Amortization. A measure of operating performance independent of capital structure.", domain: "Finance", owner: "FP&A Team", status: "Approved", approved: "2024-01-20", formula: "Net income + Interest + Taxes + Depreciation + Amortization", usageExamples: [{ context: "Investor Call", text: "\"Adjusted EBITDA margin improved to 28%, up 400 basis points year-over-year.\"" }], relatedTerms: [0, 3, 12], relatedDatasets: [1, 7] },
{ term: "ETL", definition: "Extract, Transform, Load — The process of extracting data from source systems, transforming it to fit business needs, and loading it into a target data store.", domain: "IT", owner: "Data Engineering", status: "Approved", approved: "2025-02-14", formula: null, usageExamples: [{ context: "Architecture Review", text: "\"The ETL pipeline runs nightly at 2 AM UTC and processes approximately 15M rows.\"" }], relatedTerms: [6, 11], relatedDatasets: [7, 0] },
{ term: "Full-Time Equivalent (FTE)", definition: "A unit of measurement equal to the hours worked by one full-time employee. Used to standardize headcount across part-time and full-time workers.", domain: "HR", owner: "HR Analytics", status: "Approved", approved: "2024-07-01", formula: "Total hours worked by all employees / Standard full-time hours", usageExamples: [{ context: "Headcount Report", text: "\"The department has 45 headcount but only 38.5 FTE due to part-time arrangements.\"" }], relatedTerms: [21], relatedDatasets: [0, 12] },
{ term: "Golden Record", definition: "The single, authoritative version of a data entity created by resolving duplicates and conflicts across source systems.", domain: "IT", owner: "MDM Team", status: "Draft", approved: null, formula: null, usageExamples: [{ context: "MDM Project", text: "\"The golden record for each customer is created by merging CRM, billing, and support system data.\"" }], relatedTerms: [6, 7], relatedDatasets: [2] },
{ term: "Gross Margin", definition: "Revenue minus cost of goods sold (COGS), expressed as a percentage of revenue. Indicates production efficiency.", domain: "Finance", owner: "Accounting", status: "Approved", approved: "2024-01-20", formula: "(Revenue COGS) / Revenue x 100", usageExamples: [{ context: "Quarterly Report", text: "\"Gross margin for the SaaS segment is 82%, while hardware sits at 34%.\"" }], relatedTerms: [3, 0, 8], relatedDatasets: [1] },
{ term: "Key Performance Indicator (KPI)", definition: "A measurable value that demonstrates how effectively a company is achieving key business objectives.", domain: "Operations", owner: "Strategy Team", status: "Approved", approved: "2024-02-28", formula: null, usageExamples: [{ context: "All-Hands Meeting", text: "\"Our top 5 KPIs this quarter are ARR growth, NPS, churn rate, CAC payback, and uptime.\"" }], relatedTerms: [0, 16, 2], relatedDatasets: [15] },
{ term: "Lead Conversion Rate", definition: "The percentage of leads that are converted into paying customers over a defined period.", domain: "Sales", owner: "Sales Operations", status: "Approved", approved: "2024-05-18", formula: "Converted leads / Total leads x 100", usageExamples: [{ context: "Pipeline Review", text: "\"Inbound lead conversion rate is 12%, roughly 3x higher than outbound at 4%.\"" }], relatedTerms: [4, 20], relatedDatasets: [3] },
{ term: "Mean Time to Resolution (MTTR)", definition: "The average time taken to fully resolve an incident from the moment it is reported to when service is restored.", domain: "IT", owner: "IT Service Management", status: "Approved", approved: "2025-03-01", formula: "Sum of resolution times / Number of incidents", usageExamples: [{ context: "SRE Report", text: "\"MTTR for SEV1 incidents improved from 4.2 hours to 2.8 hours after on-call restructuring.\"" }], relatedTerms: [19], relatedDatasets: [16] },
{ term: "Net Promoter Score (NPS)", definition: "A customer loyalty metric ranging from -100 to +100, calculated by subtracting the percentage of detractors from promoters.", domain: "Customer", owner: "CX Analytics", status: "Approved", approved: "2024-04-22", formula: "% Promoters (9-10) % Detractors (0-6)", usageExamples: [{ context: "Customer Success Review", text: "\"Company-wide NPS is +42, with enterprise segment at +58 and SMB at +31.\"" }], relatedTerms: [2, 5], relatedDatasets: [2, 13] },
{ term: "Personally Identifiable Information (PII)", definition: "Any data that could potentially identify a specific individual, including name, SSN, email, phone number, or address.", domain: "Compliance", owner: "Data Privacy Office", status: "Approved", approved: "2024-08-12", formula: null, usageExamples: [{ context: "Privacy Policy", text: "\"All PII must be encrypted at rest and masked in non-production environments.\"" }, { context: "Data Classification", text: "\"Fields containing PII are automatically tagged and require elevated access permissions.\"" }], relatedTerms: [11, 6], relatedDatasets: [17, 0] },
{ term: "Revenue Recognition", definition: "The accounting principle that determines when revenue is officially recorded, based on delivery of goods or services.", domain: "Finance", owner: "Accounting", status: "Under Review", approved: null, formula: null, usageExamples: [{ context: "Audit Prep", text: "\"Under ASC 606, we recognize revenue ratably over the subscription term, not at contract signing.\"" }], relatedTerms: [0, 3], relatedDatasets: [1, 7] },
{ term: "Service Level Agreement (SLA)", definition: "A formal commitment between a service provider and client defining expected service standards, response times, and responsibilities.", domain: "Operations", owner: "Service Delivery", status: "Approved", approved: "2024-09-05", formula: null, usageExamples: [{ context: "Contract Review", text: "\"Our SLA guarantees 99.95% uptime with a 15-minute response time for critical issues.\"" }], relatedTerms: [15, 13], relatedDatasets: [16, 13] },
{ term: "Total Addressable Market (TAM)", definition: "The overall revenue opportunity available for a product or service, assuming 100% market share.", domain: "Sales", owner: "Strategy Team", status: "Draft", approved: null, formula: "Total potential customers x Average revenue per customer", usageExamples: [{ context: "Investor Pitch", text: "\"The TAM for our core platform is estimated at $12B, with a serviceable addressable market of $3.2B.\"" }], relatedTerms: [0, 4], relatedDatasets: [3] },
{ term: "Turnover Rate", definition: "The percentage of employees who leave an organization during a specific period, including voluntary and involuntary separations.", domain: "HR", owner: "HR Analytics", status: "Approved", approved: "2024-07-01", formula: "Employees who left / Average total employees x 100", usageExamples: [{ context: "HR Dashboard", text: "\"Annual voluntary turnover rate is 14%, concentrated in the engineering department at 19%.\"" }], relatedTerms: [10], relatedDatasets: [0] },
];
/* ═══════════════════════════════════════
THEME HELPERS
═══════════════════════════════════════ */
const themeClass = {
"Finance": "theme-finance",
"HR": "theme-hr",
"Sales": "theme-sales",
"Operations": "theme-operations",
"Marketing": "theme-marketing",
"IT": "theme-it",
"Compliance": "theme-compliance",
"Customer": "theme-customer"
};
/* ═══════════════════════════════════════
FILTER STATE
═══════════════════════════════════════ */
const filterDefs = [
{ key: "theme", label: "Theme", values: () => [...new Set(datasets.map(d => d.theme))].sort() },
{ key: "owner", label: "Data Owner", values: () => [...new Set(datasets.map(d => d.owner))].sort() },
{ key: "keyword", label: "Keyword", values: () => [...new Set(datasets.flatMap(d => d.keywords))].sort() },
];
let activeFilters = {}; // { theme: Set, owner: Set, keyword: Set }
filterDefs.forEach(f => activeFilters[f.key] = new Set());
/* ═══════════════════════════════════════
CATALOG RENDERING
═══════════════════════════════════════ */
function matchesFilters(d) {
const q = document.getElementById("catalog-search").value.toLowerCase();
const textMatch = !q || d.title.toLowerCase().includes(q) || d.description.toLowerCase().includes(q) || d.keywords.some(k => k.toLowerCase().includes(q));
const themeMatch = activeFilters.theme.size === 0 || activeFilters.theme.has(d.theme);
const ownerMatch = activeFilters.owner.size === 0 || activeFilters.owner.has(d.owner);
const kwMatch = activeFilters.keyword.size === 0 || d.keywords.some(k => activeFilters.keyword.has(k));
return textMatch && themeMatch && ownerMatch && kwMatch;
}
function renderCatalog() {
const filtered = datasets.filter(matchesFilters);
// Cards
const grid = document.getElementById("cards-grid");
if (filtered.length === 0) {
grid.innerHTML = `<div class="no-results" style="grid-column:1/-1">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<p>No datasets match your search or filters.</p></div>`;
} else {
grid.innerHTML = filtered.map(d => {
const idx = datasets.indexOf(d);
return `
<div class="card" onclick="openDatasetDetail(${idx})">
<span class="card-theme ${themeClass[d.theme] || ''}">${d.theme}</span>
<div class="card-title">${highlight(d.title)}</div>
<div class="card-desc">${highlight(d.description)}</div>
<div class="card-meta">${d.keywords.map(k => `<span class="card-keyword">${k}</span>`).join("")}</div>
<div class="card-footer">
<span class="card-owner">${d.owner}</span>
<span>${d.records} records · ${formatDate(d.updated)}</span>
</div>
</div>`;
}).join("");
}
document.getElementById("result-count").textContent = `${filtered.length} dataset${filtered.length !== 1 ? 's' : ''} found`;
// Filter panels
renderFilterPanels(filtered);
renderActivePills();
}
function renderFilterPanels() {
const container = document.getElementById("filter-panels");
container.innerHTML = filterDefs.map(f => {
const vals = f.values();
return `<div class="filter-group">
<div class="filter-group-title">${f.label}</div>
${vals.map(v => {
const count = datasets.filter(d => {
if (f.key === "theme") return d.theme === v;
if (f.key === "owner") return d.owner === v;
return d.keywords.includes(v);
}).length;
const checked = activeFilters[f.key].has(v) ? "checked" : "";
return `<label><input type="checkbox" ${checked} onchange="toggleFilter('${f.key}','${v.replace(/'/g, "\\'")}')"> ${v} <span class="filter-count">${count}</span></label>`;
}).join("")}
</div>`;
}).join("");
}
function renderActivePills() {
const container = document.getElementById("active-filters");
let html = "";
let total = 0;
for (const f of filterDefs) {
for (const v of activeFilters[f.key]) {
html += `<span class="pill">${v} <span class="remove" onclick="toggleFilter('${f.key}','${v.replace(/'/g, "\\'")}')">&times;</span></span>`;
total++;
}
}
if (total > 1) html += `<button class="clear-filters" onclick="clearAllFilters()">Clear all</button>`;
container.innerHTML = html;
}
function toggleFilter(key, value) {
if (activeFilters[key].has(value)) activeFilters[key].delete(value);
else activeFilters[key].add(value);
renderCatalog();
}
function clearAllFilters() {
filterDefs.forEach(f => activeFilters[f.key].clear());
renderCatalog();
}
/* ═══════════════════════════════════════
GLOSSARY RENDERING
═══════════════════════════════════════ */
let activeLetter = null;
function renderGlossary() {
const q = document.getElementById("glossary-search").value.toLowerCase();
let filtered = glossary.filter(g =>
(!q || g.term.toLowerCase().includes(q) || g.definition.toLowerCase().includes(q)) &&
(!activeLetter || g.term[0].toUpperCase() === activeLetter)
);
// Letter nav
const letters = [...new Set(glossary.map(g => g.term[0].toUpperCase()))].sort();
document.getElementById("letter-nav").innerHTML = [
`<button class="${!activeLetter ? 'active' : ''}" onclick="activeLetter=null;renderGlossary()">All</button>`,
...letters.map(l => `<button class="${activeLetter===l?'active':''}" onclick="activeLetter='${l}';renderGlossary()">${l}</button>`)
].join("");
// Group by letter
const groups = {};
filtered.forEach(g => {
const l = g.term[0].toUpperCase();
if (!groups[l]) groups[l] = [];
groups[l].push(g);
});
const statusColors = { "Approved": "#5DA61B", "Draft": "#f39c12", "Under Review": "#e74c3c" };
let html = "";
Object.keys(groups).sort().forEach(l => {
html += `<div class="glossary-group-title">${l}</div>`;
groups[l].forEach(g => {
const gi = glossary.indexOf(g);
html += `<div class="glossary-item" onclick="openGlossaryDetail(${gi})" style="cursor:pointer">
<div class="glossary-term">${highlightGlossary(g.term)}</div>
<div class="glossary-def">${highlightGlossary(g.definition)}</div>
<div class="glossary-meta">
<span><strong>Domain:</strong> ${g.domain}</span>
<span><strong>Owner:</strong> ${g.owner}</span>
<span><strong>Status:</strong> <span style="color:${statusColors[g.status] || '#999'};font-weight:600">${g.status}</span></span>
</div>
</div>`;
});
});
if (!html) {
html = `<div class="no-results">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<p>No glossary terms match your search.</p></div>`;
}
document.getElementById("glossary-list").innerHTML = html;
}
/* ═══════════════════════════════════════
VIEW SWITCHING
═══════════════════════════════════════ */
function showView(viewId) {
document.querySelectorAll(".view").forEach(v => v.classList.remove("active"));
document.getElementById(viewId).classList.add("active");
window.scrollTo(0, 0);
}
function switchView(view) {
showView("view-" + view);
document.querySelectorAll(".header-nav button").forEach(b => b.classList.remove("active"));
event.target.classList.add("active");
if (view === "glossary") renderGlossary();
}
/* ═══════════════════════════════════════
CATALOG DETAIL PAGE
═══════════════════════════════════════ */
function qualityColor(score) {
if (score >= 95) return "#2e7d32";
if (score >= 85) return "#f57f17";
return "#c62828";
}
function classificationBadge(c) {
const colors = { "Public": "#2e7d32", "Internal": "#1565c0", "Confidential": "#e65100", "Restricted": "#c62828" };
return `<span style="background:${colors[c] || '#666'}15;color:${colors[c] || '#666'};padding:3px 10px;border-radius:12px;font-size:12px;font-weight:700">${c}</span>`;
}
function openDatasetDetail(idx) {
const d = datasets[idx];
const el = document.getElementById("catalog-detail-content");
el.innerHTML = `
<button class="back-btn" onclick="showView('view-catalog')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
Back to Catalog
</button>
<div class="detail-header">
<span class="card-theme ${themeClass[d.theme] || ''}">${d.theme}</span>
<h1 class="detail-title">${d.title}</h1>
<p class="detail-desc">${d.description}</p>
</div>
<!-- Metadata grid -->
<div class="meta-grid">
<div class="meta-card">
<div class="meta-card-label">Data Owner</div>
<div class="meta-card-value">${d.owner}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Data Steward</div>
<div class="meta-card-value">${d.steward}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Source System</div>
<div class="meta-card-value">${d.source}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Format</div>
<div class="meta-card-value">${d.format}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Update Frequency</div>
<div class="meta-card-value">${d.frequency}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Records</div>
<div class="meta-card-value">${d.records}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Classification</div>
<div class="meta-card-value">${classificationBadge(d.classification)}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Data Quality Score</div>
<div class="meta-card-value" style="color:${qualityColor(d.quality)}">${d.quality}%</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Created</div>
<div class="meta-card-value">${formatDate(d.created)}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Last Updated</div>
<div class="meta-card-value">${formatDate(d.updated)}</div>
</div>
</div>
<!-- Keywords -->
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
Keywords
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
${d.keywords.map(k => `<span class="card-keyword" style="font-size:13px;padding:4px 12px">${k}</span>`).join("")}
</div>
</div>
<!-- Schema -->
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>
Schema (${d.schema.length} columns)
</div>
<div class="table-scroll">
<table class="schema-table">
<thead><tr><th>Column</th><th>Type</th><th>Nullable</th><th>Description</th></tr></thead>
<tbody>
${d.schema.map(col => `<tr>
<td><strong>${col.name}</strong>${col.pk ? '<span class="col-pk">PK</span>' : ''}</td>
<td><span class="col-type">${col.type}</span></td>
<td class="col-nullable">${col.nullable ? 'Yes' : 'No'}</td>
<td>${col.desc}</td>
</tr>`).join("")}
</tbody>
</table>
</div>
</div>
<!-- Sample Data -->
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>
Sample Data
</div>
<div class="table-scroll">
<table class="sample-table">
<thead><tr>${d.schema.map(col => `<th>${col.name}</th>`).join("")}</tr></thead>
<tbody>
${d.sampleData.map(row => `<tr>${row.map(cell => `<td>${cell === null ? '<span style="color:#ccc">NULL</span>' : cell}</td>`).join("")}</tr>`).join("")}
</tbody>
</table>
</div>
</div>
<!-- Data Lineage -->
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
Data Lineage
</div>
<div class="lineage-flow">
${d.lineage.map((n, i) =>
(i > 0 ? '<span class="lineage-arrow">&#8594;</span>' : '') +
`<div class="lineage-node ${n.current ? 'current' : ''}">
<div class="lineage-node-label">${n.label}</div>
<div class="lineage-node-name">${n.name}</div>
</div>`
).join("")}
</div>
</div>
<!-- Related Datasets -->
${d.relatedDatasets && d.relatedDatasets.length ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
Related Datasets
</div>
<div class="related-grid">
${d.relatedDatasets.map(ri => {
const rd = datasets[ri];
return `<div class="related-card" onclick="openDatasetDetail(${ri})">
<span class="card-theme ${themeClass[rd.theme] || ''}" style="font-size:10px;padding:2px 7px;margin-bottom:6px">${rd.theme}</span>
<div class="related-card-title">${rd.title}</div>
<div class="related-card-sub">${rd.owner} · ${rd.records} records</div>
</div>`;
}).join("")}
</div>
</div>` : ''}
<!-- Related Business Terms -->
${d.relatedTerms && d.relatedTerms.length ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
Related Business Terms
</div>
<div class="related-grid">
${d.relatedTerms.map(ti => {
const gt = glossary[ti];
return `<div class="related-card" onclick="openGlossaryDetail(${ti})">
<div class="related-card-title">${gt.term}</div>
<div class="related-card-sub">${gt.domain} · ${gt.owner}</div>
</div>`;
}).join("")}
</div>
</div>` : ''}
`;
showView("view-catalog-detail");
}
/* ═══════════════════════════════════════
GLOSSARY DETAIL PAGE
═══════════════════════════════════════ */
function statusBadgeClass(s) {
if (s === "Approved") return "status-approved";
if (s === "Draft") return "status-draft";
return "status-review";
}
function openGlossaryDetail(idx) {
const g = glossary[idx];
const el = document.getElementById("glossary-detail-content");
el.innerHTML = `
<button class="back-btn" onclick="showView('view-glossary');renderGlossary()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
Back to Glossary
</button>
<div class="detail-header">
<h1 class="glossary-detail-term">${g.term}</h1>
<div class="glossary-detail-def">${g.definition}</div>
</div>
<!-- Metadata -->
<div class="glossary-detail-meta">
<div class="meta-card">
<div class="meta-card-label">Domain</div>
<div class="meta-card-value"><span class="card-theme ${themeClass[g.domain] || ''}" style="font-size:12px;padding:3px 10px">${g.domain}</span></div>
</div>
<div class="meta-card">
<div class="meta-card-label">Business Owner</div>
<div class="meta-card-value">${g.owner}</div>
</div>
<div class="meta-card">
<div class="meta-card-label">Status</div>
<div class="meta-card-value"><span class="status-badge ${statusBadgeClass(g.status)}">${g.status}</span></div>
</div>
<div class="meta-card">
<div class="meta-card-label">Approved Date</div>
<div class="meta-card-value">${g.approved ? formatDate(g.approved) : '<span style="color:#ccc">Pending</span>'}</div>
</div>
</div>
<!-- Formula / Calculation -->
${g.formula ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Formula / Calculation
</div>
<div style="background:#f7f8fa;border:1px solid #e0e0e0;border-radius:8px;padding:16px 20px;font-family:'SF Mono','Consolas','Menlo',monospace;font-size:14px;color:#333">
${g.formula}
</div>
</div>` : ''}
<!-- Usage Examples -->
${g.usageExamples && g.usageExamples.length ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
Usage Examples
</div>
${g.usageExamples.map(ex => `
<div class="usage-example">
<div class="usage-context">${ex.context}</div>
<div class="usage-text">${ex.text}</div>
</div>
`).join("")}
</div>` : ''}
<!-- Related Terms -->
${g.relatedTerms && g.relatedTerms.length ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
Related Business Terms
</div>
<div class="related-grid">
${g.relatedTerms.map(ti => {
const rt = glossary[ti];
return `<div class="related-card" onclick="openGlossaryDetail(${ti})">
<div class="related-card-title">${rt.term}</div>
<div class="related-card-sub">${rt.domain} · ${rt.owner}</div>
</div>`;
}).join("")}
</div>
</div>` : ''}
<!-- Related Data Assets -->
${g.relatedDatasets && g.relatedDatasets.length ? `
<div class="detail-section">
<div class="detail-section-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0279B1" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
Related Data Assets
</div>
<div class="related-grid">
${g.relatedDatasets.map(di => {
const rd = datasets[di];
return `<div class="related-card" onclick="openDatasetDetail(${di})">
<span class="card-theme ${themeClass[rd.theme] || ''}" style="font-size:10px;padding:2px 7px;margin-bottom:6px">${rd.theme}</span>
<div class="related-card-title">${rd.title}</div>
<div class="related-card-sub">${rd.owner} · ${rd.records} records</div>
</div>`;
}).join("")}
</div>
</div>` : ''}
`;
showView("view-glossary-detail");
}
/* ═══════════════════════════════════════
UTILITIES
═══════════════════════════════════════ */
function formatDate(dateStr) {
const d = new Date(dateStr + "T00:00:00");
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
}
function highlight(text) {
const q = document.getElementById("catalog-search").value.trim();
if (!q) return text;
const re = new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return text.replace(re, '<mark style="background:#fff3cd;padding:0 2px;border-radius:2px">$1</mark>');
}
function highlightGlossary(text) {
const q = document.getElementById("glossary-search").value.trim();
if (!q) return text;
const re = new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return text.replace(re, '<mark style="background:#fff3cd;padding:0 2px;border-radius:2px">$1</mark>');
}
/* ── Init ── */
renderCatalog();
</script>
</body>
</html>