Fix remaining Dutch string in DataQualityView
This commit is contained in:
@@ -37,8 +37,36 @@ type ColumnProfile = {
|
||||
top_values?: { value: string; count: number }[]
|
||||
}
|
||||
|
||||
type GxCheck = { suite: string; expectation: string; success: boolean; result: string; column?: string }
|
||||
type SodaCheck = { suite: string; name: string; check: string; outcome: string; detail: string }
|
||||
type Violations = {
|
||||
violation_count?: number
|
||||
violation_pct?: number
|
||||
sample_rows?: { row_index: number; values: Record<string, unknown> }[]
|
||||
location_hint?: string
|
||||
affected_columns?: string[]
|
||||
}
|
||||
|
||||
type DqCheck = {
|
||||
id?: string
|
||||
suite: string
|
||||
success?: boolean
|
||||
status?: string
|
||||
outcome?: string
|
||||
expectation?: string
|
||||
name?: string
|
||||
metric?: string
|
||||
check?: string
|
||||
test?: string
|
||||
rule?: string
|
||||
monitor?: string
|
||||
result?: string
|
||||
detail?: string
|
||||
column?: string
|
||||
violations?: Violations
|
||||
meta?: Record<string, unknown>
|
||||
}
|
||||
|
||||
type GxCheck = DqCheck & { expectation: string; success: boolean; result: string }
|
||||
type SodaCheck = DqCheck & { name: string; check: string; outcome: string; detail: string }
|
||||
|
||||
type DocStructure = {
|
||||
pages: number
|
||||
@@ -67,10 +95,24 @@ type AssessResult = {
|
||||
dimensions: Dimension[]
|
||||
column_profiles: ColumnProfile[]
|
||||
action_items: { priority: string; dimension: string; score: number; action: string }[]
|
||||
checks?: { great_expectations: GxCheck[]; soda_core: SodaCheck[] }
|
||||
checks_summary: {
|
||||
great_expectations: { total: number; passed: number }
|
||||
soda_core: { total: number; warnings: number }
|
||||
checks?: {
|
||||
great_expectations: GxCheck[]
|
||||
soda_core: SodaCheck[]
|
||||
deequ?: DqCheck[]
|
||||
pandera?: DqCheck[]
|
||||
dbt_expectations?: DqCheck[]
|
||||
monte_carlo?: DqCheck[]
|
||||
affirm?: DqCheck[]
|
||||
}
|
||||
checks_summary: Record<string, { total: number; passed?: number; warnings?: number }>
|
||||
executive_summary?: {
|
||||
headline: string
|
||||
dataset: string
|
||||
checks_run: number
|
||||
issues_found: number
|
||||
flagged_columns: number
|
||||
tools: string[]
|
||||
recommendation: string
|
||||
}
|
||||
docling?: { used: boolean; parse_id?: string; document_structure?: DocStructure; stats?: Record<string, number>; images?: DocImage[] }
|
||||
rag_ingest?: { ok: boolean; duplicate?: boolean; chunks?: number; message?: string; error?: string }
|
||||
@@ -140,8 +182,9 @@ export function DataQualityView() {
|
||||
const [reports, setReports] = useState<ReportSummary[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [expandedCol, setExpandedCol] = useState<string | null>(null)
|
||||
const [showGx, setShowGx] = useState(false)
|
||||
const [showSoda, setShowSoda] = useState(false)
|
||||
const [expandedCheck, setExpandedCheck] = useState<string | null>(null)
|
||||
const [expandedDim, setExpandedDim] = useState<string | null>(null)
|
||||
const [activeTool, setActiveTool] = useState<string>('great_expectations')
|
||||
|
||||
const loadMeta = useCallback(async () => {
|
||||
try {
|
||||
@@ -240,7 +283,7 @@ export function DataQualityView() {
|
||||
{caps && (
|
||||
<div className="mt-3 grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<CapCard title="Maturity Engine" items={caps.maturity_dimensions.map((d) => d.label)} icon={Layers} />
|
||||
<CapCard title="Data Quality Tools" items={['Great Expectations', 'Soda Core', 'Pandas Profiling']} icon={CheckCircle2} />
|
||||
<CapCard title="Data Quality Tools" items={['Great Expectations', 'Soda Core', 'Deequ', 'Pandera', 'dbt', 'Monte Carlo', 'Affirm', 'Pandas']} icon={CheckCircle2} />
|
||||
<CapCard title="Document Parsing" items={caps.tools.docling?.capabilities || ['PDF', 'PPTX', 'DOCX']} icon={FileText} />
|
||||
<CapCard title="File formats" items={[...caps.supported_data_formats.slice(0, 4), ...caps.supported_document_formats.slice(0, 3)]} icon={Upload} />
|
||||
</div>
|
||||
@@ -287,8 +330,8 @@ export function DataQualityView() {
|
||||
<StatCard label="Overall Score" value={`${assess.overall_score}`} sub="/100" accent />
|
||||
<StatCard label="Maturity Level" value={assess.maturity_level} sub={assess.maturity_description} />
|
||||
<StatCard label="Dataset" value={`${assess.rows.toLocaleString()}`} sub={`${assess.columns} columns`} />
|
||||
<StatCard label="Great Expectations" value={`${assess.checks_summary.great_expectations.passed}/${assess.checks_summary.great_expectations.total}`} sub="checks passed" />
|
||||
<StatCard label="Soda Core" value={String(assess.checks_summary.soda_core.warnings)} sub="warnings" warn={assess.checks_summary.soda_core.warnings > 0} />
|
||||
<StatCard label="DQ Checks" value={String(assess.executive_summary?.checks_run || Object.values(assess.checks_summary).reduce((s, x) => s + (x.total || 0), 0))} sub="across 7 tools" />
|
||||
<StatCard label="Issues" value={String(assess.executive_summary?.issues_found ?? 0)} sub="failed/warn checks" warn={(assess.executive_summary?.issues_found ?? 0) > 0} />
|
||||
</div>
|
||||
|
||||
{assess.docling?.used && assess.docling.document_structure && (
|
||||
@@ -316,38 +359,69 @@ export function DataQualityView() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{assess.executive_summary && (
|
||||
<section className="rounded-lg border border-docker/30 bg-docker/5 p-4">
|
||||
<h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-docker">Executive Summary — Customer Report</h3>
|
||||
<p className="text-[13px] font-semibold text-foreground">{assess.executive_summary.headline}</p>
|
||||
<p className="mt-1 text-[11px] text-foreground-muted">{assess.executive_summary.dataset}</p>
|
||||
<div className="mt-2 flex flex-wrap gap-3 text-[10px]">
|
||||
<span>{assess.executive_summary.checks_run} checks run</span>
|
||||
<span className={assess.executive_summary.issues_found > 0 ? 'text-warning' : 'text-success'}>{assess.executive_summary.issues_found} issues found</span>
|
||||
<span>{assess.executive_summary.flagged_columns} flagged columns</span>
|
||||
</div>
|
||||
<p className="mt-2 border-t border-border/50 pt-2 text-[11px] text-foreground-muted">
|
||||
<strong className="text-foreground">Recommendation:</strong> {assess.executive_summary.recommendation}
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<a href={assess.report_url} target="_blank" rel="noreferrer" className={cn('rounded-md px-3 py-1.5 text-[11px] font-medium', subTabActive)}>
|
||||
Full HTML report ↗
|
||||
</a>
|
||||
<button type="button" onClick={() => setShowGx(!showGx)} className={cn('rounded-md px-3 py-1.5 text-[11px]', showGx ? subTabActive : subTabIdle)}>
|
||||
GE checks ({assess.checks?.great_expectations.length || 0})
|
||||
</button>
|
||||
<button type="button" onClick={() => setShowSoda(!showSoda)} className={cn('rounded-md px-3 py-1.5 text-[11px]', showSoda ? subTabActive : subTabIdle)}>
|
||||
Soda checks ({assess.checks?.soda_core.length || 0})
|
||||
</button>
|
||||
<a href={assess.report_url + '/json'} target="_blank" rel="noreferrer" className={cn('rounded-md px-3 py-1.5 text-[11px]', subTabIdle)}>
|
||||
JSON export ↗
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{showGx && assess.checks?.great_expectations && (
|
||||
<CheckTable title="Great Expectations" rows={assess.checks.great_expectations.map((c) => ({
|
||||
name: c.column ? `${c.expectation} [${c.column}]` : c.expectation,
|
||||
status: c.success ? 'pass' : 'fail',
|
||||
detail: c.result,
|
||||
}))} />
|
||||
)}
|
||||
{showSoda && assess.checks?.soda_core && (
|
||||
<CheckTable title="Soda Core" rows={assess.checks.soda_core.map((c) => ({
|
||||
name: c.name,
|
||||
status: c.outcome,
|
||||
detail: `${c.check} — ${c.detail}`,
|
||||
}))} />
|
||||
)}
|
||||
<section>
|
||||
<h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">Data Quality Tools — click any row for error details</h3>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{([
|
||||
['great_expectations', 'Great Expectations'],
|
||||
['soda_core', 'Soda Core'],
|
||||
['deequ', 'AWS Deequ'],
|
||||
['pandera', 'Pandera'],
|
||||
['dbt_expectations', 'dbt Expectations'],
|
||||
['monte_carlo', 'Monte Carlo'],
|
||||
['affirm', 'Affirm'],
|
||||
] as const).map(([key, label]) => {
|
||||
const items = assess.checks?.[key] || []
|
||||
if (!items.length) return null
|
||||
const summary = assess.checks_summary?.[key]
|
||||
const badge = summary?.passed !== undefined ? `${summary.passed}/${summary.total}` : `${items.length}`
|
||||
return (
|
||||
<button key={key} type="button" onClick={() => setActiveTool(key)} className={cn('rounded-md px-2.5 py-1 text-[10px]', activeTool === key ? subTabActive : subTabIdle)}>
|
||||
{label} ({badge})
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{assess.checks && (
|
||||
<CheckTable
|
||||
title={activeTool.replace(/_/g, ' ')}
|
||||
checks={normalizeChecks(assess.checks[activeTool as keyof typeof assess.checks] || [])}
|
||||
expandedId={expandedCheck}
|
||||
onToggle={setExpandedCheck}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-3 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">6 Maturity Dimensions</h3>
|
||||
<div className="grid gap-3 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{assess.dimensions.map((d) => (
|
||||
<DimensionCard key={d.id} dimension={d} />
|
||||
<DimensionCard key={d.id} dimension={d} expanded={expandedDim === d.id} onToggle={() => setExpandedDim(expandedDim === d.id ? null : d.id)} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
@@ -383,7 +457,7 @@ export function DataQualityView() {
|
||||
<div className="space-y-5">
|
||||
<p className="text-[12px] leading-relaxed text-foreground-muted">
|
||||
Docling extracts text, tables, images and document structure from PDF, PowerPoint, Word, Excel and images.
|
||||
Resultaat: Markdown, HTML, JSON met pagina's, plaatjes, tabellen en outline.
|
||||
Output: Markdown, HTML, JSON with pages, images, tables and document outline.
|
||||
</p>
|
||||
<UploadZone
|
||||
loading={loading}
|
||||
@@ -398,8 +472,8 @@ export function DataQualityView() {
|
||||
{parse && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<StatCard label="Bestand" value={parse.filename.length > 20 ? parse.filename.slice(0, 18) + '…' : parse.filename} sub={parse.status} />
|
||||
<StatCard label="Verwerking" value={`${(parse.processing_time_sec || 0).toFixed(1)}s`} sub={`Formats: ${parse.formats_available.join(', ')}`} />
|
||||
<StatCard label="File" value={parse.filename.length > 20 ? parse.filename.slice(0, 18) + '…' : parse.filename} sub={parse.status} />
|
||||
<StatCard label="Processing" value={`${(parse.processing_time_sec || 0).toFixed(1)}s`} sub={`Formats: ${parse.formats_available.join(', ')}`} />
|
||||
<StatCard label="Pages" value={String(parse.document_structure?.pages ?? parse.stats.pages ?? 0)} icon={Layers} />
|
||||
<StatCard label="Images" value={String(parse.document_structure?.pictures ?? 0)} icon={Image} accent />
|
||||
<StatCard label="Tables" value={String(parse.document_structure?.tables ?? 0)} icon={Table2} />
|
||||
@@ -498,7 +572,7 @@ function ImageGallery({ images, parseId }: { images: DocImage[]; parseId: string
|
||||
return (
|
||||
<section className="rounded-lg border border-border bg-surface-overlay/30 p-4">
|
||||
<h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
|
||||
Images gedetecteerd ({images.length}) — no embedded export
|
||||
Images detected ({images.length}) — no embedded export
|
||||
</h3>
|
||||
<p className="text-[11px] text-foreground-muted">Re-upload the document to extract images (embedded mode).</p>
|
||||
</section>
|
||||
@@ -508,7 +582,7 @@ function ImageGallery({ images, parseId }: { images: DocImage[]; parseId: string
|
||||
<section className="rounded-lg border border-border bg-surface-overlay/30 p-4">
|
||||
<h3 className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
|
||||
<Image className="h-4 w-4 text-docker" />
|
||||
Images die Docling ziet ({available.length})
|
||||
Images extracted by Docling ({available.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||
{available.map((img) => (
|
||||
@@ -601,9 +675,13 @@ function DocStructurePanel({ structure, title }: { structure: DocStructure; titl
|
||||
)
|
||||
}
|
||||
|
||||
function DimensionCard({ dimension: d }: { dimension: Dimension }) {
|
||||
function normalizeChecks(items: DqCheck[]): DqCheck[] {
|
||||
return items.map((c, i) => ({ ...c, id: c.id || `${c.suite}_${i}` }))
|
||||
}
|
||||
|
||||
function DimensionCard({ dimension: d, expanded, onToggle }: { dimension: Dimension; expanded?: boolean; onToggle?: () => void }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-surface-overlay/40 p-3">
|
||||
<button type="button" onClick={onToggle} className="w-full rounded-lg border border-border bg-surface-overlay/40 p-3 text-left transition-colors hover:border-docker/30">
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<span className="text-[12px] font-semibold">{d.label}</span>
|
||||
<span className={cn('text-base font-bold', SCORE_COLOR(d.score))}>{d.score}</span>
|
||||
@@ -617,10 +695,16 @@ function DimensionCard({ dimension: d }: { dimension: Dimension }) {
|
||||
<li key={f} className="flex gap-1"><span className="text-docker">▸</span>{f}</li>
|
||||
))}
|
||||
</ul>
|
||||
{d.recommended_actions && d.recommended_actions.length > 0 && (
|
||||
<p className="mt-2 border-t border-border pt-2 text-[9px] text-warning">→ {d.recommended_actions[0]}</p>
|
||||
{expanded && d.recommended_actions && d.recommended_actions.length > 0 && (
|
||||
<div className="mt-2 border-t border-border pt-2">
|
||||
<p className="mb-1 text-[9px] font-semibold uppercase text-foreground-faint">Recommended actions</p>
|
||||
{d.recommended_actions.map((a) => <p key={a} className="text-[9px] text-warning">→ {a}</p>)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!expanded && d.recommended_actions && d.recommended_actions.length > 0 && (
|
||||
<p className="mt-2 border-t border-border pt-2 text-[9px] text-foreground-faint">Click for {d.recommended_actions.length} recommended action(s)</p>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -662,24 +746,75 @@ function ColumnTable({ profiles, expandedCol, onToggle }: { profiles: ColumnProf
|
||||
)
|
||||
}
|
||||
|
||||
function CheckTable({ title, rows }: { title: string; rows: { name: string; status: string; detail: string }[] }) {
|
||||
function checkLabel(c: DqCheck): string {
|
||||
const base = c.expectation || c.name || c.metric || c.check || c.test || c.rule || c.monitor || 'check'
|
||||
return c.column ? `${base} [${c.column}]` : base
|
||||
}
|
||||
|
||||
function checkStatus(c: DqCheck): string {
|
||||
return c.status || (c.success === false ? 'fail' : c.success ? 'pass' : c.outcome || 'unknown')
|
||||
}
|
||||
|
||||
function checkDetail(c: DqCheck): string {
|
||||
return c.result || c.detail || ''
|
||||
}
|
||||
|
||||
function CheckTable({ title, checks, expandedId, onToggle }: { title: string; checks: DqCheck[]; expandedId: string | null; onToggle: (id: string | null) => void }) {
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-lg border border-border">
|
||||
<p className="border-b border-border bg-surface-overlay px-3 py-2 text-[11px] font-semibold">{title}</p>
|
||||
<p className="border-b border-border bg-surface-overlay px-3 py-2 text-[11px] font-semibold capitalize">{title}</p>
|
||||
<table className="w-full text-[10px]">
|
||||
<tbody>
|
||||
{rows.map((r, i) => (
|
||||
<tr key={i} className="border-t border-border">
|
||||
<td className="px-3 py-1.5">
|
||||
<span className={cn('mr-2 rounded px-1.5 py-0.5 text-[9px] font-bold uppercase',
|
||||
r.status === 'pass' ? 'bg-success/20 text-success' : r.status === 'warn' ? 'bg-warning/20 text-warning' : 'bg-danger/20 text-danger')}>
|
||||
{r.status}
|
||||
</span>
|
||||
{r.name}
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-foreground-muted">{r.detail}</td>
|
||||
</tr>
|
||||
))}
|
||||
{checks.map((c) => {
|
||||
const id = c.id || checkLabel(c)
|
||||
const status = checkStatus(c)
|
||||
const viol = c.violations
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<tr className="cursor-pointer border-t border-border hover:bg-surface-overlay/50" onClick={() => onToggle(expandedId === id ? null : id)}>
|
||||
<td className="px-3 py-1.5">
|
||||
<span className={cn('mr-2 rounded px-1.5 py-0.5 text-[9px] font-bold uppercase',
|
||||
status === 'pass' ? 'bg-success/20 text-success' : status === 'warn' ? 'bg-warning/20 text-warning' : 'bg-danger/20 text-danger')}>
|
||||
{status}
|
||||
</span>
|
||||
{checkLabel(c)}
|
||||
{viol?.violation_count ? <span className="ml-2 text-[9px] text-danger">{viol.violation_count} violations</span> : null}
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-foreground-muted">{checkDetail(c)}</td>
|
||||
</tr>
|
||||
{expandedId === id && (
|
||||
<tr className="border-t border-border bg-surface-overlay/20">
|
||||
<td colSpan={2} className="px-4 py-3 text-[10px]">
|
||||
<p className="mb-2 font-semibold text-docker">Error location & samples</p>
|
||||
{viol?.violation_count ? (
|
||||
<>
|
||||
<p className="text-foreground-muted">{viol.violation_count} rows affected ({viol.violation_pct}%){viol.location_hint ? ` · ${viol.location_hint}` : ''}</p>
|
||||
{viol.sample_rows && viol.sample_rows.length > 0 && (
|
||||
<div className="mt-2 overflow-x-auto rounded border border-border">
|
||||
<table className="w-full text-[9px]">
|
||||
<thead><tr className="bg-surface-overlay"><th className="px-2 py-1">Row #</th>{Object.keys(viol.sample_rows[0].values).map((k) => <th key={k} className="px-2 py-1">{k}</th>)}</tr></thead>
|
||||
<tbody>
|
||||
{viol.sample_rows.map((s) => (
|
||||
<tr key={s.row_index} className="border-t border-border/50">
|
||||
<td className="px-2 py-1 font-mono text-warning">{s.row_index}</td>
|
||||
{Object.values(s.values).map((v, j) => <td key={j} className="max-w-[120px] truncate px-2 py-1">{v === null ? 'NULL' : String(v)}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-success">No row-level violations — check passed.</p>
|
||||
)}
|
||||
{c.meta && <pre className="mt-2 rounded bg-black/30 p-2 font-mono text-[9px]">{JSON.stringify(c.meta, null, 2)}</pre>}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user