PageSourceSearch

https://exploremydata.com/assets/csvProfileWorkspace-D4lpLBFi.js

js exploremydata.com collected 2026-09-25 20:06:17 UTC 22,895 bytes, 65 lines download raw bytes

1import{formatCount as c,parseLooseNumeric as P,resolveColumnNames as U,stripExtension as K,textBlob as N,previewFromAoa as I,escapeHtml as S,emptyPreview as O}from"./util-CkZ8KZK2.js";import{readText as Y,parseCsvText as B,delimiterFromOption as J,serializeTable as T}from"./csvShared-8k83eY5V.js";import{c as H}from"./promptDialog-CqM-wAnZ.js";import{profileTable as Q,NUMERIC_LOOKING_LABEL as F,renderProfileReport as X}from"./csvProfile-C3oxAZrn.js";import{p as q,F as Z,f as D}from"./dateShared-X0Yox5Lt.js";import{f as k}from"./statsShared-DnBYzIdE.js";import{v as R,d as ee,e as te,a as ne,b as se,c as oe,w as ie,i as re,r as ae,L as E}from"./qualityShared-6en4hq5n.js";import"./vendor-papaparse-Btpkh8vH.js";import"./cjs-helpers-D6-XlEtG.js";import"./detectTableRegion-B60ufQHb.js";const V="emd-data-contracts",_=.1,j=15;function le(t,r){const s=r.filter(w=>w.trim()!==""),a=new Set,o=new Map;let e=1/0,n=-1/0,d=0,y=0,p=0,g=0;for(const w of s){const l=w.trim();a.size<=j&&a.add(l),o.set(R(l),(o.get(R(l))??0)+1);const f=P(l);f!==null&&(d+=1,Number.isInteger(f)&&(y+=1),f<e&&(e=f),f>n&&(n=f)),q(l)!==null&&(p+=1);const $=l.toLowerCase();($==="true"||$==="false"||$==="yes"||$==="no")&&(g+=1)}const i=s.length;let x="string";i>0&&(g===i?x="boolean":d===i?x=y===i?"integer":"number":p===i&&(x="date"));let v;for(const[w,l]of o)l>=i*.95&&(v=w);return{name:t,filled:i,total:r.length,distinct:a.size,...a.size<=j&&a.size>0&&i>=a.size*3?{values:[...a].sort()}:{},...d>0?{numericMin:e,numericMax:n}:{},...v?{shape:v}:{},type:x}}function ce(t,r,s,a){const o=[];s.forEach(e=>{if(e.filled===e.total&&e.total>0&&o.push({kind:"required",column:e.name}),e.type!=="string"&&o.push({kind:"type",column:e.name,type:e.type,tolerancePercent:0}),e.filled>0&&e.distinct===e.filled&&e.filled>3&&o.push({kind:"unique",column:e.name}),(e.type==="integer"||e.type==="number")&&e.numericMin!==void 0&&e.numericMax!==void 0&&Number.isFinite(e.numericMin)&&Number.isFinite(e.numericMax)){const n=e.numericMax-e.numericMin,d=n===0?Math.max(1,Math.abs(e.numericMax)*_):n*_;o.push({kind:"range",column:e.name,min:e.numericMin>=0?Math.max(0,e.numericMin-d):e.numericMin-d,max:e.numericMax+d})}e.values&&e.type==="string"?o.push({kind:"allowed",column:e.name,values:e.values}):e.shape&&e.type==="string"&&e.distinct>j&&o.push({kind:"pattern",column:e.name,shape:e.shape,tolerancePercent:5})});for(const e of a.slice(0,1))o.push({kind:"key",column:"",columns:[...e]});return{schemaVersion:1,name:t,created:new Date().toISOString().slice(0,10),columns:[...r],rules:o}}function M(t){switch(t.kind){case"required":return`"${t.column}" is never blank`;case"type":return`"${t.column}" holds ${t.type} values`;case"unique":return`"${t.column}" has no repeated value`;case"range":return`"${t.column}" stays between ${t.min===void 0?"any":k(t.min)} and ${t.max===void 0?"any":k(t.max)}`;case"pattern":return`"${t.column}" matches the shape ${t.shape}${t.tolerancePercent?` (up to ${t.tolerancePercent}% may differ)`:""}`;case"allowed":return`"${t.column}" is one of: ${(t.values??[]).join(", ")}`;case"key":return`${(t.columns??[]).map(r=>`"${r}"`).join(" + ")} identifies a row`}}const G=200,z=3;function ue(t,r){switch(r){case"integer":{const s=P(t);return s!==null&&Number.isInteger(s)}case"number":return P(t)!==null;case"date":return q(t)!==null;case"boolean":return["true","false","yes","no","0","1"].includes(t.toLowerCase());default:return!0}}function de(t,r,s){const a=t.columns.filter(n=>!r.includes(n)),o=r.filter(n=>!t.columns.includes(n)),e=[];for(const n of t.rules){const d=(l,f,$,m,h,u=[])=>{e.push({rule:n,passed:l,failures:f,checked:$,failingRows:m,message:h,samples:u})};if(n.kind==="key"){const f=(n.columns??[]).map(u=>r.indexOf(u));if(f.some(u=>u<0)){d(!1,0,0,[],`${M(n)} — one of those columns is not in this file.`);continue}const $=new Map,m=[],h=[];s.forEach((u,b)=>{const C=f.map(W=>String(u[W]??"").trim()).join("\0");if($.get(C)===void 0){$.set(C,b);return}m.length<G&&m.push(b),h.length<z&&h.push(C.replace(/�/g," + "))}),d(m.length===0,m.length,s.length,m,m.length===0?`${M(n)} — every combination is unique.`:`${M(n)} — but ${c(m.length)} row${m.length===1?"":"s"} repeat a combination, first at data row ${c(m[0]+1)}.`,h);continue}const y=r.indexOf(n.column);if(y<0){d(!1,0,0,[],`${M(n)} — but "${n.column}" is not in this file.`);continue}const p=[],g=[];let i=0;const x=new Map;
1s.forEach((l,f)=>{const $=String(l[y]??""),m=$.trim();let h=!1;switch(n.kind){case"required":i+=1,h=m==="";break;case"unique":{if(m==="")return;i+=1,x.has(m)?h=!0:x.set(m,f);break}case"type":if(m==="")return;i+=1,h=!ue(m,n.type??"string");break;case"range":{if(m==="")return;i+=1;const u=P(m);u===null?h=!0:h=n.min!==void 0&&u<n.min||n.max!==void 0&&u>n.max;break}case"pattern":if(m==="")return;i+=1,h=R(m)!==n.shape;break;case"allowed":if(m==="")return;i+=1,h=!(n.values??[]).includes(m);break}h&&(p.length<G&&p.push(f),g.length<z&&!g.includes($)&&g.push($))});const v=i===0?0:(n.tolerancePercent??0)/100*i,w=p.length<=v;d(w,p.length,i,p,p.length===0?`${M(n)} — held on all ${c(i)} value${i===1?"":"s"}.`:`${M(n)} — ${c(p.length)} of ${c(i)} value${i===1?"":"s"} broke it${w?", which is inside the allowed tolerance":""}, first at data row ${c(p[0]+1)}${g.length>0?` (${g.map(l=>`"${l}"`).join(", ")})`:""}.`,g)}return{contract:t,results:e,passed:e.every(n=>n.passed)&&a.length===0,missingColumns:a,extraColumns:o}}function L(){try{if(typeof localStorage>"u")return{};const t=localStorage.getItem(V);if(!t)return{};const r=JSON.parse(t);if(r&&typeof r=="object")return r}catch{}return{}}function me(t){try{if(typeof localStorage>"u")return!1;const r=L();return r[t.name]=t,localStorage.setItem(V,JSON.stringify(r)),!0}catch{return!1}}function fe(t){return L()[t]??null}function he(){return Object.keys(L()).sort()}const pe=20,ge=200;function $e(t,r){const s=Q(t,r),{columns:a}=U(t[0]??[]),o=t.slice(1),e=[],n=ee(o);n.count>0&&e.push({level:n.count>o.length*.05?"high":"review",column:"",kind:"duplicate-rows",text:`${c(n.count)} exact duplicate row${n.count===1?"":"s"}, every column identical to an earlier row. First repeat at data row ${c((n.rows[0]??0)+1)}.`,rows:n.rows});const d=te(o,a);for(const i of d)e.push({level:"note",column:i.columns.length===1?i.columns[0]:"",kind:"candidate-key",text:i.columns.length===1?`"${i.columns[0]}" is a candidate key: all ${c(i.distinct)} of its values are distinct and none is blank.`:`${i.columns.map(x=>`"${x}"`).join(" + ")} together identify a row: ${c(i.distinct)} distinct combinations across ${c(i.covered)} rows.`,rows:[]});a.forEach((i,x)=>{const v=o.map(u=>String(u[x]??"")),w=s.columnProfiles[x];if(!w)return;if(w.filled===0){e.push({level:"review",column:i,kind:"empty-column",text:`"${i}
1" is entirely empty. It carries a name and no data.`,rows:[]});return}if(w.missing>0){const u=w.missingPercent>=pe,b=[];v.forEach((C,A)=>{C.trim()===""&&b.length<ge&&b.push(A)}),e.push({level:u?"high":"review",column:i,kind:"missing",text:`"${i}" is ${w.missingPercent.toFixed(1)}% missing: ${c(w.missing)} of ${c(s.rows)} rows are blank.`,rows:b})}w.distinct===1&&w.filled>1&&e.push({level:"note",column:i,kind:"constant",text:`"${i}" holds the same value in every non-blank row, so it cannot tell any two rows apart.`,rows:[]});const l=ne(v),f=v.filter(u=>u.trim()!=="").length,$=l!==null&&l.parsed+l.invalid>=f*.8;if($&&l)l.invalid>0&&e.push({level:"high",column:i,kind:"invalid-date",text:`"${i}" holds ${c(l.invalid)} value${l.invalid===1?"":"s"} that look like a date and are not one: ${l.invalidSamples.map(u=>`"${u}"`).join(", ")}${l.invalid>l.invalidSamples.length?", and more":""}. A day that does not exist in that month rolls silently over in most tools, so nothing else will tell you.`,rows:l.invalidRows}),l.formats.length>1&&e.push({level:"high",column:i,kind:"mixed-date-format",text:`"${i}" mixes ${c(l.formats.length)} date formats in one column (${l.formats.join(", ")}). Whatever reads this next will pick one and misread the rest.`,rows:[]}),l.parsed>0&&e.push({level:"note",column:i,kind:"date-range",text:`"${i}" runs from ${D(l.min)} to ${D(l.max)}${l.dayGaps>0?`, with ${c(l.dayGaps)} ${Z.day}${l.dayGaps===1?"":"s"} inside that range carrying no row at all`:", with no missing days"}.`,rows:[]});else{const u=se(v);u&&u.offenders>0&&u.dominantCount>=u.total*.6&&e.push({level:"review",column:i,kind:"mixed-shape",text:`"${i}" mixes value shapes: ${c(u.offenders)} of ${c(u.total)} non-blank values differ from the dominant shape ${u.dominant} (${u.samples.map(b=>`"${b}"`).join(", ")}${u.offenders>u.samples.length?", and more":""}).`,rows:u.rows})}const m=oe(v);m.length>0&&e.push({level:"review",column:i,kind:"case-variant",text:`"${i}" has ${c(m.length)} value${m.length===1?"":"s"} that differ only in letter case (${m.slice(0,3).map(u=>u.variants.map(b=>`"${b}"`).join("/")).join(", ")}${m.length>3?", and more":""}). A group-by counts those as separate categories.`,rows:[]});const h=ie(v);if(h&&e.push({level:"review",column:i,kind:"whitespace",text:`${c(h.count)} value${h.count===1?"":"s"} in "${i}" ${h.count===1?"carries":"carry"} a leading or trailing space (${h.samples.join(", ")}). Most tools keep it as part of the value, so it will not match anything.`,rows:h.rows}),(w.kind==="numeric"||w.numericLooking)&&!$){const u=re(v);u&&e.push({level:"note",column:i,kind:"outlier",text:`"${i}" has ${c(u.count)} IQR outlier${u.count===1?"":"s"}: value${u.count===1?"":"s"} outside ${k(u.low)} to ${k(u.high)}, the 1.5x fences around Q1 ${k(u.q1)} and Q3 ${k(u.q3)}${u.samples.length>0?` (${u.samples.map(b=>k(b)).join(", ")})`:""}.`,rows:u.rows})}});const y=ae(e),p=s.rows*s.columns,g=s.columnProfiles.reduce((i,x)=>i+x.missing,0);return{profile:s,tiles:{rows:s.rows,columns:s.columns,completePercent:p===0?100:(p-g)/p*100,missingCells:g,duplicateRows:n.count,findings:y.length},findings:y,keys:d,columns:a}}function we(t){return[["rows",c(t.rows)],["columns",c(t.columns)],["complete",`${t.completePercent.toFixed(1)}%`],["missing cells",c(t.missingCells)],["full-row dupes",c(t.duplicateRows)],["findings",c(t.findings)]].map(([s,a])=>`  ${s.padEnd(16)}${a}`)}function ve(t){const r=[`Profile of ${t.profile.filename}`,"","AT A GLANCE","-----------",...we(t.tiles),""];if(r.push("FINDINGS","--------"),t.findings.length===0)r.push("  Nothing to report. No blanks, no duplicates, no mixed shapes.");else for(const s of t.findings)r.push(`  ${E[s.level].padEnd(7)}${s.text}`);return r.push("","EVERY COLUMN","------------"),r.push(X(t.profile).split(`
2`).slice(3).join(`
3`)),r.join(`
4`)}function ye(t){const r=t.tiles,s=[`# Profile of \`${t.profile.filename}\``,"","| | |","| --- | ---: |",`| Rows | ${c(r.rows)} |`,`| Columns | ${c(r.columns)} |`,`| Complete | ${r.completePercent.toFixed(1)}% |`,`| Missing cells | ${c(r.missingCells)} |`,`| Full-row duplicates | ${c(r.duplicateRows)} |`,`| Findings | ${c(r.findings)} |`,"","## Findings",""];if(t.findings.length===0)s.push("Nothing to report.","");else{for(const a of t.findings)s.push(`- **${E[a.level]}** ${a.text}`);
4s.push("")}s.push("## Columns","","| Column | Type | Missing | Distinct | Min | Median | Max |","| --- | --- | ---: | ---: | ---: | ---: | ---: |");for(const a of t.profile.columnProfiles){const o=a.numeric;s.push(`| ${a.name} | ${a.numericLooking?F:a.kind} | ${c(a.missing)} (${a.missingPercent.toFixed(1)}%) | ${c(a.distinct)} | ${o?k(o.min):"—"} | ${o?k(o.median):"—"} | ${o?k(o.max):"—"} |`)}return s.push("","Generated in the browser by ExploreMyData. The file was not uploaded.",""),s.join(`
5`)}function xe(t){const r=t.tiles,s=(e,n)=>`<div class="tile"><span>${S(e)}</span><strong>${S(n)}</strong></div>`,a=t.findings.map(e=>`<li class="f ${e.level}"><span class="lvl">${E[e.level]}</span> ${S(e.text)}</li>`).join(`
6`),o=t.profile.columnProfiles.map(e=>{const n=e.numeric;return`<tr><th scope="row">${S(e.name)}</th><td>${S(e.numericLooking?F:e.kind)}</td><td>${S(c(e.missing))} (${e.missingPercent.toFixed(1)}%)</td><td>${S(c(e.distinct))}</td><td>${n?S(k(n.min)):"—"}</td><td>${n?S(k(n.median)):"—"}</td><td>${n?S(k(n.max)):"—"}</td></tr>`}).join(`
7`);return`<!DOCTYPE html>
8<html lang="en">
9<head>
10<meta charset="utf-8">
11<meta name="viewport" content="width=device-width, initial-scale=1">
12<meta name="robots" content="noindex, nofollow">
13<meta name="referrer" content="no-referrer">
14<title>Profile of ${S(t.profile.filename)}</title>
15<style>
16  :root { color-scheme: light dark; --ink:#101828; --muted:#667085; --line:#e4e7ec; --bg:#fff; --high:#b42318; --review:#b54708; --note:#475467; }
17  @media (prefers-color-scheme: dark) { :root { --ink:#f2f4f7; --muted:#98a2b3; --line:#344054; --bg:#0c111d; --high:#fda29b; --review:#fec84b; --note:#98a2b3; } }
18  body { margin:0; padding:2.5rem 1.5rem; background:var(--bg); color:var(--ink); font:15px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif; }
19  main { max-width:64rem; margin:0 auto; }
20  h1 { font-size:1.6rem; margin:0 0 1.5rem; }
21  h2 { font-size:1.1rem; margin:2rem 0 .75rem; }
22  .tiles { display:flex; flex-wrap:wrap; gap:1rem; }
23  .tile { border:1px solid var(--line); border-radius:.6rem; padding:.85rem 1.1rem; min-width:9rem; }
24  .tile span { display:block; font-size:.7rem; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
25  .tile strong { font-size:1.35rem; font-weight:650; }
26  ul { list-style:none; padding:0; margin:0; }
27  .f { border-left:3px solid var(--note); padding:.4rem 0 .4rem .75rem; margin-bottom:.5rem; }
28  .f.high { border-color:var(--high); } .f.review { border-color:var(--review); }
29  .lvl { font-size:.68rem; letter-spacing:.06em; font-weight:700; color:var(--muted); margin-right:.4rem; }
30  .f.high .lvl { color:var(--high); } .f.review .lvl { color:var(--review); }
31  table { border-collapse:collapse; width:100%; font-variant-numeric:tabular-nums; }
32  th, td { border-bottom:1px solid var(--line); padding:.45rem .6rem; text-align:right; }
33  th[scope=row], thead th:first-child { text-align:left; }
34  thead th { font-size:.7rem; letter-spacing:.05em; text-transform:uppercase; color:var(--muted); }
35  footer { margin-top:2.5rem; color:var(--muted); font-size:.8rem; }
36</style>
37</head>
38<body>
39<main>
40<h1>Profile of ${S(t.profile.filename)}</h1>
41<div class="tiles">
42${s("Rows",c(r.rows))}
43${s("Columns",c(r.columns))}
44${s("Complete",`${r.completePercent.toFixed(1)}%`)}
45${s("Missing cells",c(r.missingCells))}
46${s("Full-row duplicates",c(r.duplicateRows))}
47${s("Findings",c(r.findings))}
48</div>
49<h2>Findings</h2>
50${t.findings.length===0?"<p>Nothing to report.</p>":`<ul>
51${a}
52</ul>`}
53<h2>Every column</h2>
54<table><thead><tr><th>Column</th><th>Type</th><th>Missing</th><th>Distinct</th><th>Min</th><th>Median</th><th>Max</th></tr></thead><tbody>
55${o}
56</tbody></table>
57<footer>Generated in the browser by ExploreMyData. The file was not uploaded, and this report carries no cell values.</footer>
58</main>
59</body>
60</html>
61`}function be(t){const r=s=>s===void 0||!Number.isFinite(s)?"":String(s);return{columns:["column","type","rows","filled","missing","missing_percent","distinct","min","p25","median","p75","max","mean"],rows:t.profile.columnProfiles.map(s=>{var a,o,e,n,d,y;return[s.name,s.numericLooking?F:s.kind,String(t.profile.rows),String(s.filled),String(s.missing),s.missingPercent.toFixed(2),String(s.distinct),r((a=s.numeric)==null?void 0:a.min),r((o=s.numeric)==null?void 0:o.p25),r((e=s.numeric)==null?void 0:e.median),r((n=s.numeric)==null?void 0:n.p75),r((d=s.numeric)==null?void 0:d.max),r((y=s.numeric)==null?void 0:y.mean)]})}}function ke(t,r){var a;const s=[];for(const o of t.findings){const e=t.columns.indexOf(o.column);for(const n of o.rows)s.push([String(n+1),o.column,o.kind,E[o.level],e>=0?String(((a=r[n])==null?void 0:a[e])??""):"",o.text])}return s.sort((o,e)=>Number(o[0])-Number(e[0])),{columns:["data_row","column","issue","severity","value","detail"],rows:s}}const Se="The issue CSV lists the rows that failed AND the raw values that failed, copied out of your file. Every other export on this page carries counts and column names only. Download it?";function Ce(t,r,s){const a=t.columns.map((n,d)=>le(n,r.map(y=>String(y[d]??"")))),o=ce(s,t.columns,a,t.keys.map(n=>n.columns));return{text:[`Suggested data contract "${o.name}"`,`Generated ${o.created} from ${t.profile.filename}`,"",`${c(o.rules.length)} rule${o.rules.length===1?"":"s"} across ${c(o.columns.length)} columns:`,"",...o.rules.map((n,d)=>`  ${String(d+1).padStart(3)}. ${M(n)}`),"","These are suggestions read off one file, not a specification. A range gets a","10% margin so next month's slightly larger number does not fail, and a column","that is 99% filled is NOT marked required, because a rule that cries wolf gets","ignored. Download the JSON and edit it before you rely on it.","","The contract holds column names, types and thresholds. No values, no filename,","no row count. It is safe to commit to a repository or paste into a ticket.","",'Save it under a name, come back next month, switch to "Check a 
61saved contract"',"and type that name: the new file gets a pass or a fail with the rows that","caused it."].join(`
62`),contract:o}}function Ne(t,r,s){const a=de(t,r.columns,s),o=a.results.filter(d=>!d.passed),e=a.results.filter(d=>d.passed),n=[`Contract check: "${t.name}" against ${r.profile.filename}`,"",a.passed?"PASS. Every rule held.":`FAIL. ${c(o.length)} of ${c(a.results.length)} rules broke.`,""];if(a.missingColumns.length>0&&n.push(`MISSING COLUMNS (${c(a.missingColumns.length)})`,"---------------",`  The contract expects columns this file does not have: ${a.missingColumns.join(", ")}.`,"  That is a schema change rather than a data problem, and it is why the check","  fails before any rule about those columns is run.",""),a.extraColumns.length>0&&n.push(`NEW COLUMNS (${c(a.extraColumns.length)})`,"-----------",`  This file has columns the contract does not mention: ${a.extraColumns.join(", ")}.`,"  Nothing is checked about them. Regenerate the contract to cover them.",""),o.length>0){n.push(`FAILED (${c(o.length)})`,"------");for(const d of o)n.push(`  ${d.message}`);n.push("")}if(e.length>0){n.push(`PASSED (${c(e.length)})`,"------");for(const d of e)n.push(`  ${d.message}`);n.push("")}return{text:n.join(`
63`),contract:t,result:a}}function Me(t,r,s){var o;const a=[];for(const e of t.results){if(e.failingRows.length===0)continue;const n=r.indexOf(e.rule.column);for(const d of e.failingRows)a.push([String(d+1),e.rule.column||(e.rule.columns??[]).join(" + "),e.rule.kind,n>=0?String(((o=s[d])==null?void 0:o[n])??""):"",M(e.rule)])}return a.sort((e,n)=>Number(e[0])-Number(n[0])),{columns:["data_row","column","rule","value","expected"],rows:a}}async function Ee(){return typeof document>"u"?!0:H({title:"Export the failing values?",message:Se,confirmLabel:"Include the values"})}const _e=async t=>{t.ctx.onProgress("Reading the CSV…");const r=await Y(t),s=B(r,J(t.options.delimiter)),a=s.rows.slice(1);t.ctx.onProgress("Profiling every column…");const o=$e(s.rows,t.filename),e=K(t.filename)||"data",n=t.options.mode??"profile",d=[...s.warnings,...o.profile.warnings];if(n==="contract"){const l=(t.options.contractName??"").trim()||e,f=Ce(o,a,l),$=`${JSON.stringify(f.contract,null,2)}
64`;return(t.options.saveContract??"no")==="save"&&d.push(me(f.contract)?`Saved the contract as "${l}" in this browser. Switch to "Check a saved contract" and type that name to apply it to another file.`:"This browser would not let the contract be saved (private mode blocks local storage). The download still works."),{status:"ok",output:{filename:`${e}-contract.json`,blob:N($,"application/json"),text:f.text,copyKind:"text",previewText:f.text,preview:O(),warnings:d,summary:[`${c(f.contract.rules.length)} suggested rules`,`${c(f.contract.columns.length)} columns covered`,"the saved file holds column names and thresholds only, no data"],summaryStyle:"list"}}}if(n==="check"){const l=(t.options.contractName??"").trim(),f=he();if(l==="")throw new Error(f.length===0?'No contract is saved in this browser yet. Switch to "Generate a contract", set Save to Yes, then come back with the next file.':`Type the name of a saved contract to check this file against. Saved: ${f.join(", ")}.`);const $=fe(l);if(!$)throw new Error(f.length===0?`No contract called "${l}" is saved in this browser, and there are none saved at all. Switch to "Generate a contract", set Save to Yes, and try again.`:`No contract called "${l}" is saved in this browser. Saved contracts: ${f.join(", ")}.`);const m=Ne($,o,a),h=Me(m.result,o.columns,a),u=m.result.results.filter(C=>!C.passed);return{status:"ok",output:{filename:`${e}-contract-check.csv`,blob:N(T(h.columns,h.rows),"text/csv"),text:m.text,copyKind:"text",previewText:m.text,preview:I([h.columns,...h.rows],20),warnings:[...d,h.rows.length>0?"The download is the row-level evidence, so it includes the raw values that failed.":""].filter(C=>C!==""),summary:[m.result.passed?"PASS, every rule held":`FAIL, ${c(u.length)} rule${u.length===1?"":"s"} broke`,`${c(m.result.results.length)} rules checked against ${c(a.length)} rows`,...u.slice(0,4).map(C=>C.message)],summaryStyle:"list"}}}const y=ve(o),p=t.options.output??"report";let g,i,x=O();if(p==="html")g=N(xe(o),"text/html"),i=`${e}-profile.html`;else if(p==="markdown")g=N(ye(o),"text/markdown"),i=`${e}-profile.md`;else if(p==="profile-csv"){const l=be(o);g=N(T(l.columns,l.rows),"text/csv"),i=`${e}-profile.csv`,x=I([l.columns,...l.rows],30)}else if(p==="issue-csv")if(!await Ee())d.push("The issue CSV was not produced. The download button hands over the plain-text report instead, which carries no cell values."),g=N(y,"text/plain"),i=`${e}-profile.txt`;else{const l=ke(o,a);g=N(T(l.columns,l.rows),"text/csv"),i=`${e}-issues.csv`,x=I([l.columns,...l.rows],20),d.push(`This download contains ${c(l.rows.length)} row${l.rows.length===1?"":"s"} of raw values copied out of your file. Every other export on this page carries counts and column names only.`)}else p==="json"?(g=N(`${JSON.stringify(o.profile,null,2)}
65`,"application/json"),i=`${e}-profile.json`):(g=N(y,"text/plain"),i=`${e}-profile.txt`);const v=o.tiles;return{status:"ok",output:{filename:i,blob:g,text:y,copyKind:"text",previewText:y,preview:x,warnings:d,summary:[`${c(v.rows)} rows x ${c(v.columns)} columns · ${v.completePercent.toFixed(1)}% complete · ${c(v.missingCells)} missing cells · ${c(v.duplicateRows)} full-row duplicates`,...o.findings.slice(0,6).map(l=>`${E[l.level]} ${l.text}`),...o.findings.length>6?[`and ${c(o.findings.length-6)} more findings in the report`]:[]],summaryStyle:"list"}}};export{pe as HIGH_MISSING_PERCENT,Se as ISSUE_CSV_CONFIRMATION,Ne as buildCheckReport,Ce as buildContractReport,$e as buildWorkspace,Ee as confirmRawValues,Me as contractIssueCsv,_e as default,ke as issueCsv,be as profileCsv,xe as renderWorkspaceHtml,ye as renderWorkspaceMarkdown,ve as renderWorkspaceReport};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.