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>
1957 lines
96 KiB
HTML
1957 lines
96 KiB
HTML
<!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, "\\'")}')">×</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">→</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>
|