Find Duplicates (01_deduplicator): - Delete the redundant outer Options wrapper; surface threshold + survivor rule directly, push the rest behind a single Advanced pane. - Disambiguate competing primaries: top result is an auto-resolved preview (secondary download), review decisions are the single primary. - Plain-English match labels (exact / approximate); clarify the third. - Lift the match-card caption to a one-time instruction; note delimiter is delimited-text-only. Quality Check (08_validator_reporter) — stub: - Remove the dead disabled "Load rules file (JSON)" uploader so the stub invites a single action; keep the informative feature list. Map Columns (05_column_mapper): - Regroup schema -> mapping -> strategy/advanced (core task contiguous). - Make preset-vs-Advanced precedence legible (Custom + modified marker). - Adopt the compact file-intake banner; drop the duplicate resolved- mapping table; fix the add-row gutter style. Combine Files (07_multi_file_merger) — stub: - Actually disable the Merge CTA (add the disabled attribute). PDF to CSV (10_pdf_extractor): - Drop page/raw from the default preview to match export + fix the horizontal clip; surface raw via per-row affordance + overflow-x. - Move the column selector above the download button; give auto-excluded rows a reason; align the files card to Home; de-dupe the row count. Automated Workflows (09_pipeline_runner): - Replace hand-edited JSON step config with per-step control expanders; JSON moved behind Advanced import/export. - Editing the table marks the mode modified; fold the empty error column into the status pill; render summaries as plain English; collapse the explainer by default. Cross-cutting items (stub standardization on page 10, shared disabled- field token, remaining intake rollout) deferred to a holistic pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
210 lines
12 KiB
HTML
210 lines
12 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Layout review — Map Columns</title>
|
|
<link rel="stylesheet" href="app.css">
|
|
</head>
|
|
<body data-page="05_column_mapper">
|
|
<div class="dt-app">
|
|
<aside class="dt-sidebar" id="dt-sidebar"></aside>
|
|
<main class="dt-main">
|
|
<div class="dt-review-banner">
|
|
<span class="dt-mi">visibility</span>
|
|
<span>Static layout preview of <strong>Map Columns</strong>, shown with a file imported, an interactive target schema + mapping configured, and a completed run (results + mapped preview). <a href="index.html">All pages →</a></span>
|
|
</div>
|
|
<div class="dt-main-inner">
|
|
|
|
<!-- Tool header -->
|
|
<div class="dt-tool-header">
|
|
<h1>Map Columns</h1>
|
|
<button class="dt-help-btn"><span class="dt-mi">help_outline</span> Help</button>
|
|
</div>
|
|
<p class="dt-tool-caption">Rename columns, change their order, and set each one as text, number, or date.</p>
|
|
|
|
<div class="dt-spacer"></div>
|
|
|
|
<!-- File pickup banner (using file from upload screen) -->
|
|
<div class="dt-alert info">
|
|
<span class="dt-mi">description</span>
|
|
<span>Using <strong>crm_contacts_raw.csv</strong> from the upload screen.</span>
|
|
</div>
|
|
<button class="dt-btn" style="margin-bottom:4px">Use a different file</button>
|
|
|
|
<!-- Preview expander (collapsed after a result exists) -->
|
|
<details class="dt-expander">
|
|
<summary>Preview: crm_contacts_raw.csv</summary>
|
|
<div class="dt-expander-body">
|
|
<p class="dt-caption">4,210 rows, 6 columns</p>
|
|
<div class="dt-table-wrap">
|
|
<table class="dt-table">
|
|
<thead><tr><th class="idx"></th><th>Full Name</th><th>EmailAddr</th><th>Phone #</th><th>Signup</th><th>Amount Spent</th><th>Notes</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="idx">0</td><td>Jane Doe</td><td>jane@acme.io</td><td>512-555-0190</td><td>01/04/2024</td><td>$1,204.50</td><td>VIP</td></tr>
|
|
<tr><td class="idx">1</td><td>Bob Smith</td><td>bob@globex.com</td><td>720-555-7781</td><td>02/11/2024</td><td>$88.00</td><td></td></tr>
|
|
<tr><td class="idx">2</td><td>Carla Reyes</td><td>carla@initech.net</td><td>415-555-3322</td><td>03/02/2024</td><td>$612.10</td><td>renewal</td></tr>
|
|
<tr><td class="idx">3</td><td>Dev Patel</td><td>dev@umbrella.co</td><td>206-555-9043</td><td>03/19/2024</td><td>$0.00</td><td></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<hr class="dt-divider">
|
|
|
|
<!-- Options expander (open — heart of the tool) -->
|
|
<details class="dt-expander" open>
|
|
<summary>Options</summary>
|
|
<div class="dt-expander-body">
|
|
|
|
<!-- ===== Target schema ===== -->
|
|
<h3 style="margin-top:0">Target schema</h3>
|
|
<div class="dt-field">
|
|
<label class="dt-label">How would you like to define the target schema?</label>
|
|
<div class="dt-radio-row" style="flex-direction:column; gap:8px">
|
|
<span class="dt-radio on"><span class="dot"></span> Build interactively (start from current columns)</span>
|
|
<span class="dt-radio"><span class="dot"></span> Import schema JSON</span>
|
|
<span class="dt-radio"><span class="dot"></span> Skip (rename / coerce only — no schema)</span>
|
|
</div>
|
|
<div class="dt-help-text">An interactive build is fastest for one-off cleanup. Import a JSON when you have a fixed contract (a CRM import format, db schema). Skip when you only want to rename or coerce specific columns.</div>
|
|
</div>
|
|
|
|
<p class="dt-caption">Edit the table to define your target schema. Add rows for fields the input doesn't have yet (with a default), or remove rows for columns you want to drop.</p>
|
|
|
|
<!-- Schema editor (st.data_editor, num_rows=dynamic) -->
|
|
<div class="dt-table-wrap">
|
|
<table class="dt-table">
|
|
<thead><tr><th>Target name</th><th>Type</th><th>Required</th><th>Default (for added cols)</th><th>Aliases (comma-sep, helps fuzzy-match)</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>full_name</td><td>string</td><td>✗</td><td></td><td>Full Name, name</td></tr>
|
|
<tr><td>email</td><td>string</td><td>✓</td><td></td><td>EmailAddr, email_address</td></tr>
|
|
<tr><td>phone</td><td>string</td><td>✗</td><td></td><td>Phone #, tel</td></tr>
|
|
<tr><td>signup_date</td><td>date</td><td>✗</td><td></td><td>Signup</td></tr>
|
|
<tr><td>amount_spent</td><td>float</td><td>✗</td><td>0.0</td><td>Amount Spent</td></tr>
|
|
<tr><td>source</td><td>string</td><td>✗</td><td>crm-import</td><td></td></tr>
|
|
<tr><td style="color:var(--ink-tertiary)"><span class="dt-mi" style="font-size:16px;vertical-align:-3px">add</span> add row</td><td></td><td></td><td></td><td></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="dt-caption">6 target fields · 1 added field (<code>source</code>) not present in the input.</p>
|
|
|
|
<hr class="dt-divider">
|
|
|
|
<!-- ===== Mapping ===== -->
|
|
<!-- Mapping follows the schema directly: define the schema, then map sources onto it. -->
|
|
<h3>Mapping</h3>
|
|
<!-- schema is set → source→target selectbox editor with auto-suggested flag -->
|
|
<div class="dt-table-wrap">
|
|
<table class="dt-table">
|
|
<thead><tr><th>Source</th><th>Target</th><th>Auto-suggested</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>Full Name</td><td>full_name</td><td>✓</td></tr>
|
|
<tr><td>EmailAddr</td><td>email</td><td>✓</td></tr>
|
|
<tr><td>Phone #</td><td>phone</td><td>✓</td></tr>
|
|
<tr><td>Signup</td><td>signup_date</td><td>✓</td></tr>
|
|
<tr><td>Amount Spent</td><td>amount_spent</td><td>✓</td></tr>
|
|
<tr><td>Notes</td><td>(unmapped)</td><td>✗</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="dt-caption">Pick a target for each source column. <code>Notes</code> stays unmapped — with the keep-extras strategy it is kept as-is. <code>source</code> is added from the schema default.</p>
|
|
|
|
<hr class="dt-divider">
|
|
|
|
<!-- ===== Strategy ===== -->
|
|
<!-- Strategy is a modifier on the mapping above (strictness: keep/drop extras, coerce, reorder), so it comes after the user can see what it acts on. -->
|
|
<h3>Strategy</h3>
|
|
<div class="dt-field">
|
|
<label class="dt-label">Preset</label>
|
|
<div class="dt-radio-row" style="flex-direction:column; gap:8px">
|
|
<span class="dt-radio"><span class="dot"></span> rename-only (just rename, leave types alone, keep extras)</span>
|
|
<span class="dt-radio"><span class="dot"></span> lenient-schema (rename + coerce + reorder, keep extras)</span>
|
|
<span class="dt-radio"><span class="dot"></span> strict-schema (rename + coerce + reorder, drop extras) <span class="dt-count-pill info" style="margin-left:4px">base</span></span>
|
|
<span class="dt-radio on"><span class="dot"></span> Custom — based on <strong>strict-schema</strong>, 1 control changed <span class="dt-count-pill warn" style="margin-left:4px">modified</span></span>
|
|
</div>
|
|
<div class="dt-precedence" style="margin-top:10px">
|
|
<span class="dt-mi">rule</span>
|
|
<span>Individual Advanced controls win over the preset. You started from <strong>strict-schema</strong>, then changed <strong>Unmapped source columns</strong> to <strong>keep</strong> below — so the preset is now <strong>Custom</strong>. The controls' current values are what actually run.</span>
|
|
</div>
|
|
<div class="dt-help-text">Pick a strategy as the baseline. Every Advanced toggle below is still individually overridable; overriding any one switches the preset to Custom.</div>
|
|
</div>
|
|
|
|
<!-- Advanced options expander -->
|
|
<details class="dt-expander" open>
|
|
<summary>Advanced options</summary>
|
|
<div class="dt-expander-body">
|
|
<div class="dt-cols-2">
|
|
<div>
|
|
<div class="dt-field">
|
|
<label class="dt-label">Unmapped source columns <span class="dt-count-pill warn" style="margin-left:4px">changed</span></label>
|
|
<div class="dt-select">keep</div>
|
|
<div class="dt-help-text">Winning value: <strong>keep</strong>. Overrides the strict-schema base (drop) — so <code>Notes</code> survives into the output.</div>
|
|
</div>
|
|
<div class="dt-check on"><span class="box"><span class="dt-mi">check</span></span> Coerce types per schema</div>
|
|
<div class="dt-check on"><span class="box"><span class="dt-mi">check</span></span> Reorder to schema order</div>
|
|
</div>
|
|
<div>
|
|
<div class="dt-check on"><span class="box"><span class="dt-mi">check</span></span> Auto-infer mapping (fuzzy match)</div>
|
|
<div class="dt-field">
|
|
<label class="dt-label">Fuzzy match threshold</label>
|
|
<div class="dt-slider"><div class="track"><div class="fill" style="width:80%"></div><div class="knob" style="left:80%"></div></div><div class="val">0.80</div></div>
|
|
</div>
|
|
<div class="dt-check on"><span class="box"><span class="dt-mi">check</span></span> Enforce required fields</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
</div>
|
|
</details>
|
|
|
|
<hr class="dt-divider">
|
|
<button class="dt-btn dt-btn-primary dt-btn-block">Apply Column Mapping</button>
|
|
|
|
<hr class="dt-divider">
|
|
|
|
<!-- ===== Results ===== -->
|
|
<div id="colmap-results-anchor" style="height:1px"></div>
|
|
<h2>Results</h2>
|
|
<div class="dt-metrics">
|
|
<div class="dt-metric"><div class="label">Renamed</div><div class="value">5</div></div>
|
|
<div class="dt-metric"><div class="label">Dropped</div><div class="value">0</div></div>
|
|
<div class="dt-metric"><div class="label">Added</div><div class="value">1</div></div>
|
|
<div class="dt-metric"><div class="label">Coerce fails</div><div class="value">3</div></div>
|
|
</div>
|
|
|
|
<div class="dt-alert info"><span class="dt-mi">info</span><span>Added (with defaults): <code>source</code></span></div>
|
|
<div class="dt-alert warn"><span class="dt-mi">warning</span><span>Some cells could not be coerced and were left as NaN: amount_spent (3)</span></div>
|
|
|
|
<p><strong>Mapped preview (first 10 rows)</strong></p>
|
|
<div class="dt-table-wrap">
|
|
<table class="dt-table">
|
|
<thead><tr><th class="idx"></th><th class="dt-cell-add">full_name</th><th>email</th><th>phone</th><th>signup_date</th><th>amount_spent</th><th class="dt-cell-add">source</th><th>Notes</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="idx">0</td><td>Jane Doe</td><td>jane@acme.io</td><td>512-555-0190</td><td>2024-01-04</td><td>1204.5</td><td>crm-import</td><td>VIP</td></tr>
|
|
<tr><td class="idx">1</td><td>Bob Smith</td><td>bob@globex.com</td><td>720-555-7781</td><td>2024-02-11</td><td>88.0</td><td>crm-import</td><td></td></tr>
|
|
<tr><td class="idx">2</td><td>Carla Reyes</td><td>carla@initech.net</td><td>415-555-3322</td><td>2024-03-02</td><td>612.1</td><td>crm-import</td><td>renewal</td></tr>
|
|
<tr><td class="idx">3</td><td>Dev Patel</td><td>dev@umbrella.co</td><td>206-555-9043</td><td>2024-03-19</td><td>0.0</td><td>crm-import</td><td></td></tr>
|
|
<tr><td class="idx">4</td><td>Mei Lin</td><td>mei@hooli.com</td><td>503-555-1188</td><td>2024-04-07</td><td class="dt-cell-flag">NaN</td><td>crm-import</td><td>trial</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<hr class="dt-divider">
|
|
|
|
<!-- Downloads (3 columns) -->
|
|
<div class="dt-cols-3">
|
|
<button class="dt-btn dt-btn-primary">Download mapped CSV</button>
|
|
<button class="dt-btn">Download mapping audit</button>
|
|
<button class="dt-btn">Download config JSON</button>
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<footer class="dt-footer" id="dt-footer"></footer>
|
|
<script src="shell.js"></script>
|
|
</body>
|
|
</html>
|