@bicharts/shape-core 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/chunk-2LR73DNS.mjs +2 -0
  2. package/dist/chunk-3UOCZ2D6.mjs +2 -0
  3. package/dist/chunk-GY54KQLP.mjs +2 -0
  4. package/dist/chunk-NWDPA7DG.mjs +2 -0
  5. package/dist/chunk-QEH7WTLB.mjs +2 -0
  6. package/dist/chunk-WBEROMBG.mjs +2 -0
  7. package/dist/chunk-XWCQ7BJB.mjs +2 -0
  8. package/dist/formatDetector.mjs +1 -7
  9. package/dist/geoDetector.mjs +1 -14
  10. package/dist/geoPoint.mjs +1 -23
  11. package/dist/index.mjs +1 -250
  12. package/dist/indexedText.mjs +1 -23
  13. package/dist/models.mjs +0 -1
  14. package/dist/monthNames.mjs +1 -9
  15. package/dist/ordinalDetector.mjs +1 -11
  16. package/dist/util.mjs +1 -11
  17. package/package.json +2 -2
  18. package/dist/chunk-ICDJLWEM.mjs +0 -83
  19. package/dist/chunk-ICDJLWEM.mjs.map +0 -7
  20. package/dist/chunk-NGV56PGT.mjs +0 -787
  21. package/dist/chunk-NGV56PGT.mjs.map +0 -7
  22. package/dist/chunk-QTEE53OQ.mjs +0 -205
  23. package/dist/chunk-QTEE53OQ.mjs.map +0 -7
  24. package/dist/chunk-RZAZ7PZK.mjs +0 -31
  25. package/dist/chunk-RZAZ7PZK.mjs.map +0 -7
  26. package/dist/chunk-VHWC3THS.mjs +0 -572
  27. package/dist/chunk-VHWC3THS.mjs.map +0 -7
  28. package/dist/chunk-VLMGGCN4.mjs +0 -36
  29. package/dist/chunk-VLMGGCN4.mjs.map +0 -7
  30. package/dist/chunk-YJVU7G63.mjs +0 -559
  31. package/dist/chunk-YJVU7G63.mjs.map +0 -7
  32. package/dist/formatDetector.mjs.map +0 -7
  33. package/dist/geoDetector.mjs.map +0 -7
  34. package/dist/geoPoint.mjs.map +0 -7
  35. package/dist/index.mjs.map +0 -7
  36. package/dist/indexedText.mjs.map +0 -7
  37. package/dist/models.mjs.map +0 -7
  38. package/dist/monthNames.mjs.map +0 -7
  39. package/dist/ordinalDetector.mjs.map +0 -7
  40. package/dist/util.mjs.map +0 -7
package/dist/index.mjs CHANGED
@@ -1,251 +1,2 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- import {
3
- IndexedText,
4
- classifyAdditivity,
5
- classifyNumericValueNature,
6
- classifyTemporal,
7
- hostAggHint,
8
- isIdentifierName
9
- } from "./chunk-NGV56PGT.mjs";
10
- import {
11
- GET_RANDOM,
12
- SIMPLE_STRING_HASH,
13
- STR
14
- } from "./chunk-VLMGGCN4.mjs";
15
- import {
16
- buildGeoIsoColumn,
17
- detectGeo,
18
- isJoinGeoKind,
19
- toGeoIso
20
- } from "./chunk-YJVU7G63.mjs";
21
- import {
22
- buildGeoPointColumns,
23
- cityMatchPct,
24
- isKnownCity,
25
- normalizeCountry,
26
- normalizePlaceName,
27
- resolveAdmin1,
28
- resolveGeoPoint,
29
- zipPrefixCandidates,
30
- zipToPrefix3
31
- } from "./chunk-QTEE53OQ.mjs";
32
- import {
33
- detectOrdinalDomain,
34
- isOrdinalFriendlyName,
35
- safeDistinctValuesToShip
36
- } from "./chunk-VHWC3THS.mjs";
37
- import {
38
- detectFormatSignature
39
- } from "./chunk-ICDJLWEM.mjs";
40
- import {
41
- monthLookupFor,
42
- normalizeMonthKey
43
- } from "./chunk-RZAZ7PZK.mjs";
44
-
45
- // src/geoPointRoles.ts
46
- var THRESHOLD_PCT = 85;
47
- var MIN_DISTINCT_BACKFILL = 2;
48
- var ZIP_THRESHOLD_PCT = 100;
49
- var MAX_DISTINCT_SCAN = 400;
50
- var COUNTRY_IDS = /* @__PURE__ */ new Set([
51
- "us",
52
- "usa",
53
- "u s",
54
- "u s a",
55
- "united states",
56
- "united states of america",
57
- "america",
58
- "ca",
59
- "can",
60
- "canada",
61
- "mx",
62
- "mex",
63
- "mexico",
64
- "estados unidos mexicanos"
65
- ]);
66
- function distinctNormalized(values) {
67
- const seen = /* @__PURE__ */ new Set();
68
- for (const v of values) {
69
- if (v === null || v === void 0) continue;
70
- const k = normalizePlaceName(String(v));
71
- if (!k) continue;
72
- if (!seen.has(k)) {
73
- seen.add(k);
74
- if (seen.size >= MAX_DISTINCT_SCAN) break;
75
- }
76
- }
77
- return Array.from(seen);
78
- }
79
- function looksLikeCountryColumn(values) {
80
- const d = distinctNormalized(values);
81
- if (d.length === 0) return false;
82
- if (!d.every((v) => COUNTRY_IDS.has(v))) return false;
83
- return d.some((v) => !resolveAdmin1(v));
84
- }
85
- function isAmbiguousCountryState(normalized) {
86
- return COUNTRY_IDS.has(normalized) && !!resolveAdmin1(normalized);
87
- }
88
- function admin1MatchPct(values) {
89
- const d = distinctNormalized(values);
90
- if (d.length === 0) return 0;
91
- let n = 0;
92
- for (const v of d) if (resolveAdmin1(v)) n++;
93
- return Math.round(n / d.length * 1e3) / 10;
94
- }
95
- function distinctRaw(values) {
96
- const seen = /* @__PURE__ */ new Set();
97
- for (const v of values) {
98
- if (v === null || v === void 0) continue;
99
- const s = String(v).trim();
100
- if (!s) continue;
101
- if (!seen.has(s)) {
102
- seen.add(s);
103
- if (seen.size >= MAX_DISTINCT_SCAN) break;
104
- }
105
- }
106
- return Array.from(seen);
107
- }
108
- function zipMatchPct(values) {
109
- const d = distinctRaw(values);
110
- if (d.length === 0) return 0;
111
- let n = 0;
112
- for (const v of d) if (zipPrefixCandidates(v).length > 0) n++;
113
- return Math.round(n / d.length * 1e3) / 10;
114
- }
115
- function cityPct(values) {
116
- const d = distinctNormalized(values);
117
- if (d.length === 0) return 0;
118
- let n = 0;
119
- for (const v of d) if (isKnownCity(v)) n++;
120
- return Math.round(n / d.length * 1e3) / 10;
121
- }
122
- function distinctMatches(values, pred) {
123
- let n = 0;
124
- for (const v of distinctNormalized(values)) if (pred(v)) n++;
125
- return n;
126
- }
127
- function distinctRawMatches(values, pred) {
128
- let n = 0;
129
- for (const v of distinctRaw(values)) if (pred(v)) n++;
130
- return n;
131
- }
132
- function resolvePointRoles(columns, rows, hint) {
133
- const backfilled = [];
134
- const refused = [];
135
- const out = {};
136
- const colSet = new Set(columns);
137
- const valuesOf = (name) => rows.map((r) => r[name]);
138
- for (const role of ["city", "zip", "lat", "lon", "country"]) {
139
- const name = hint?.[role];
140
- if (name && colSet.has(name)) out[role] = name;
141
- }
142
- const hintedState = hint?.state;
143
- if (hintedState && colSet.has(hintedState)) {
144
- const vals = valuesOf(hintedState);
145
- const dvals = distinctNormalized(vals);
146
- if (dvals.length > 0 && dvals.every(isAmbiguousCountryState)) {
147
- refused.push(`state=${hintedState} (every value is "CA", which is both Canada and California)`);
148
- } else if (looksLikeCountryColumn(vals)) {
149
- refused.push(`state=${hintedState} (every value is a country, not a state)`);
150
- if (!out.country) out.country = hintedState;
151
- } else if (admin1MatchPct(vals) < THRESHOLD_PCT) {
152
- refused.push(`state=${hintedState} (only ${admin1MatchPct(vals)}% of values are states/provinces)`);
153
- } else {
154
- out.state = hintedState;
155
- }
156
- }
157
- const taken = new Set(Object.values(out).filter(Boolean));
158
- const candidates = columns.filter((c) => !taken.has(c) && !c.startsWith("__"));
159
- if (!out.state) {
160
- let best = null;
161
- for (const c of candidates) {
162
- const vals = valuesOf(c);
163
- if (looksLikeCountryColumn(vals)) continue;
164
- const pct = admin1MatchPct(vals);
165
- if (pct < THRESHOLD_PCT) continue;
166
- const hits = distinctMatches(vals, (v) => !!resolveAdmin1(v));
167
- const unambiguous = distinctMatches(vals, (v) => !!resolveAdmin1(v) && !isAmbiguousCountryState(v));
168
- if (hits < MIN_DISTINCT_BACKFILL && unambiguous < 1) continue;
169
- if (!best || pct > best.pct) best = { name: c, pct };
170
- }
171
- if (best) {
172
- out.state = best.name;
173
- taken.add(best.name);
174
- backfilled.push(`state=${best.name} (${best.pct}% states/provinces)`);
175
- }
176
- }
177
- if (!out.zip) {
178
- for (const c of candidates) {
179
- if (taken.has(c)) continue;
180
- const vals = valuesOf(c);
181
- const pct = zipMatchPct(vals);
182
- if (pct < ZIP_THRESHOLD_PCT) continue;
183
- if (distinctRawMatches(vals, (v) => zipPrefixCandidates(v).length > 0) < MIN_DISTINCT_BACKFILL) continue;
184
- out.zip = c;
185
- taken.add(c);
186
- backfilled.push(`zip=${c} (${pct}% ZIP codes)`);
187
- break;
188
- }
189
- }
190
- if (!out.country) {
191
- for (const c of candidates) {
192
- if (taken.has(c)) continue;
193
- if (!looksLikeCountryColumn(valuesOf(c))) continue;
194
- out.country = c;
195
- taken.add(c);
196
- backfilled.push(`country=${c}`);
197
- break;
198
- }
199
- }
200
- if (!out.city) {
201
- let best = null;
202
- for (const c of candidates) {
203
- if (taken.has(c)) continue;
204
- const vals = valuesOf(c);
205
- const pct = cityPct(vals);
206
- if (pct < THRESHOLD_PCT) continue;
207
- if (distinctMatches(vals, (v) => isKnownCity(v)) < MIN_DISTINCT_BACKFILL) continue;
208
- if (!best || pct > best.pct) best = { name: c, pct };
209
- }
210
- if (best) {
211
- out.city = best.name;
212
- backfilled.push(`city=${best.name} (${best.pct}% known cities)`);
213
- }
214
- }
215
- const any = !!(out.city || out.state || out.zip || out.lat && out.lon);
216
- return { bind: any ? out : null, backfilled, refused };
217
- }
218
- export {
219
- GET_RANDOM,
220
- IndexedText,
221
- SIMPLE_STRING_HASH,
222
- STR,
223
- admin1MatchPct,
224
- buildGeoIsoColumn,
225
- buildGeoPointColumns,
226
- cityMatchPct,
227
- classifyAdditivity,
228
- classifyNumericValueNature,
229
- classifyTemporal,
230
- detectFormatSignature,
231
- detectGeo,
232
- detectOrdinalDomain,
233
- hostAggHint,
234
- isIdentifierName,
235
- isJoinGeoKind,
236
- isOrdinalFriendlyName,
237
- looksLikeCountryColumn,
238
- monthLookupFor,
239
- normalizeCountry,
240
- normalizeMonthKey,
241
- normalizePlaceName,
242
- resolveAdmin1,
243
- resolveGeoPoint,
244
- resolvePointRoles,
245
- safeDistinctValuesToShip,
246
- toGeoIso,
247
- zipMatchPct,
248
- zipPrefixCandidates,
249
- zipToPrefix3
250
- };
251
- //# sourceMappingURL=index.mjs.map
2
+ import{a as U,b as X,c as Y,d as tt,e as nt,f as et}from"./chunk-XWCQ7BJB.mjs";import{a as j,b as J,c as Q}from"./chunk-NWDPA7DG.mjs";import{a as K,b as V,c as E,d as F}from"./chunk-GY54KQLP.mjs";import{a as C,b as f,c as $,d as D,e as g,f as H,g as O,h as b,i as B}from"./chunk-3UOCZ2D6.mjs";import{a as G,b as N,c as _}from"./chunk-WBEROMBG.mjs";import{a as q}from"./chunk-QEH7WTLB.mjs";import{a as W,b as Z}from"./chunk-2LR73DNS.mjs";var R=85,S=2,ot=100,L=400,w=new Set(["us","usa","u s","u s a","united states","united states of america","america","ca","can","canada","mx","mex","mexico","estados unidos mexicanos"]);function m(i){let n=new Set;for(let e of i){if(e==null)continue;let o=C(String(e));if(o&&!n.has(o)&&(n.add(o),n.size>=L))break}return Array.from(n)}function P(i){let n=m(i);return n.length===0||!n.every(e=>w.has(e))?!1:n.some(e=>!f(e))}function M(i){return w.has(i)&&!!f(i)}function x(i){let n=m(i);if(n.length===0)return 0;let e=0;for(let o of n)f(o)&&e++;return Math.round(e/n.length*1e3)/10}function z(i){let n=new Set;for(let e of i){if(e==null)continue;let o=String(e).trim();if(o&&!n.has(o)&&(n.add(o),n.size>=L))break}return Array.from(n)}function I(i){let n=z(i);if(n.length===0)return 0;let e=0;for(let o of n)g(o).length>0&&e++;return Math.round(e/n.length*1e3)/10}function it(i){let n=m(i);if(n.length===0)return 0;let e=0;for(let o of n)b(o)&&e++;return Math.round(e/n.length*1e3)/10}function k(i,n){let e=0;for(let o of m(i))n(o)&&e++;return e}function st(i,n){let e=0;for(let o of z(i))n(o)&&e++;return e}function rt(i,n,e){let o=[],p=[],s={},A=new Set(i),d=t=>n.map(r=>r[t]);for(let t of["city","zip","lat","lon","country"]){let r=e?.[t];r&&A.has(r)&&(s[t]=r)}let u=e?.state;if(u&&A.has(u)){let t=d(u),r=m(t);r.length>0&&r.every(M)?p.push(`state=${u} (every value is "CA", which is both Canada and California)`):P(t)?(p.push(`state=${u} (every value is a country, not a state)`),s.country||(s.country=u)):x(t)<R?p.push(`state=${u} (only ${x(t)}% of values are states/provinces)`):s.state=u}let l=new Set(Object.values(s).filter(Boolean)),y=i.filter(t=>!l.has(t)&&!t.startsWith("__"));if(!s.state){let t=null;for(let r of y){let a=d(r);if(P(a))continue;let c=x(a);if(c<R)continue;let v=k(a,h=>!!f(h)),T=k(a,h=>!!f(h)&&!M(h));v<S&&T<1||(!t||c>t.pct)&&(t={name:r,pct:c})}t&&(s.state=t.name,l.add(t.name),o.push(`state=${t.name} (${t.pct}% states/provinces)`))}if(!s.zip)for(let t of y){if(l.has(t))continue;let r=d(t),a=I(r);if(!(a<ot)&&!(st(r,c=>g(c).length>0)<S)){s.zip=t,l.add(t),o.push(`zip=${t} (${a}% ZIP codes)`);break}}if(!s.country){for(let t of y)if(!l.has(t)&&P(d(t))){s.country=t,l.add(t),o.push(`country=${t}`);break}}if(!s.city){let t=null;for(let r of y){if(l.has(r))continue;let a=d(r),c=it(a);c<R||k(a,v=>b(v))<S||(!t||c>t.pct)&&(t={name:r,pct:c})}t&&(s.city=t.name,o.push(`city=${t.name} (${t.pct}% known cities)`))}return{bind:!!(s.city||s.state||s.zip||s.lat&&s.lon)?s:null,backfilled:o,refused:p}}export{Q as GET_RANDOM,et as IndexedText,J as SIMPLE_STRING_HASH,j as STR,x as admin1MatchPct,F as buildGeoIsoColumn,O as buildGeoPointColumns,B as cityMatchPct,tt as classifyAdditivity,nt as classifyNumericValueNature,X as classifyTemporal,q as detectFormatSignature,K as detectGeo,_ as detectOrdinalDomain,Y as hostAggHint,U as isIdentifierName,E as isJoinGeoKind,G as isOrdinalFriendlyName,P as looksLikeCountryColumn,Z as monthLookupFor,$ as normalizeCountry,W as normalizeMonthKey,C as normalizePlaceName,f as resolveAdmin1,H as resolveGeoPoint,rt as resolvePointRoles,N as safeDistinctValuesToShip,V as toGeoIso,I as zipMatchPct,g as zipPrefixCandidates,D as zipToPrefix3};
@@ -1,24 +1,2 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- import {
3
- IndexedText,
4
- classifyAdditivity,
5
- classifyNumericValueNature,
6
- classifyTemporal,
7
- hostAggHint,
8
- isIdentifierName
9
- } from "./chunk-NGV56PGT.mjs";
10
- import "./chunk-VLMGGCN4.mjs";
11
- import "./chunk-YJVU7G63.mjs";
12
- import "./chunk-QTEE53OQ.mjs";
13
- import "./chunk-VHWC3THS.mjs";
14
- import "./chunk-ICDJLWEM.mjs";
15
- import "./chunk-RZAZ7PZK.mjs";
16
- export {
17
- IndexedText,
18
- classifyAdditivity,
19
- classifyNumericValueNature,
20
- classifyTemporal,
21
- hostAggHint,
22
- isIdentifierName
23
- };
24
- //# sourceMappingURL=indexedText.mjs.map
2
+ import{a,b,c,d,e,f}from"./chunk-XWCQ7BJB.mjs";import"./chunk-NWDPA7DG.mjs";import"./chunk-GY54KQLP.mjs";import"./chunk-3UOCZ2D6.mjs";import"./chunk-WBEROMBG.mjs";import"./chunk-QEH7WTLB.mjs";import"./chunk-2LR73DNS.mjs";export{f as IndexedText,d as classifyAdditivity,e as classifyNumericValueNature,b as classifyTemporal,c as hostAggHint,a as isIdentifierName};
package/dist/models.mjs CHANGED
@@ -1,2 +1 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- //# sourceMappingURL=models.mjs.map
@@ -1,10 +1,2 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- import {
3
- monthLookupFor,
4
- normalizeMonthKey
5
- } from "./chunk-RZAZ7PZK.mjs";
6
- export {
7
- monthLookupFor,
8
- normalizeMonthKey
9
- };
10
- //# sourceMappingURL=monthNames.mjs.map
2
+ import{a,b}from"./chunk-2LR73DNS.mjs";export{b as monthLookupFor,a as normalizeMonthKey};
@@ -1,12 +1,2 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- import {
3
- detectOrdinalDomain,
4
- isOrdinalFriendlyName,
5
- safeDistinctValuesToShip
6
- } from "./chunk-VHWC3THS.mjs";
7
- export {
8
- detectOrdinalDomain,
9
- isOrdinalFriendlyName,
10
- safeDistinctValuesToShip
11
- };
12
- //# sourceMappingURL=ordinalDetector.mjs.map
2
+ import{a,b,c}from"./chunk-WBEROMBG.mjs";export{c as detectOrdinalDomain,a as isOrdinalFriendlyName,b as safeDistinctValuesToShip};
package/dist/util.mjs CHANGED
@@ -1,12 +1,2 @@
1
1
  /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
- import {
3
- GET_RANDOM,
4
- SIMPLE_STRING_HASH,
5
- STR
6
- } from "./chunk-VLMGGCN4.mjs";
7
- export {
8
- GET_RANDOM,
9
- SIMPLE_STRING_HASH,
10
- STR
11
- };
12
- //# sourceMappingURL=util.mjs.map
2
+ import{a,b,c}from"./chunk-NWDPA7DG.mjs";export{c as GET_RANDOM,b as SIMPLE_STRING_HASH,a as STR};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/shape-core",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Host-agnostic data-shape profiler: ingest a dataset's rows through IValueCollection.addRow and read back the measured shape as an LLMColumnWithValue[] payload — types, cardinality, temporal/ordinal detection, geographic region and point resolution, cross-column signals. MEASUREMENT only: the policy that decides what a measured shape means for chart selection lives server-side.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -78,7 +78,7 @@
78
78
  },
79
79
  "scripts": {
80
80
  "build": "node build.mjs",
81
- "prepack": "node build.mjs",
81
+ "prepack": "node build.mjs --minify",
82
82
  "typecheck": "tsc -p tsconfig.json --noEmit"
83
83
  }
84
84
  }
@@ -1,83 +0,0 @@
1
- /*! @bicharts/shape-core — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
-
3
- // src/formatDetector.ts
4
- var SIGNATURE_FLOOR = 0.7;
5
- function classifyOne(v) {
6
- if (v === null || v === void 0) return null;
7
- const s = String(v);
8
- if (s.length === 0) return null;
9
- const len = s.length;
10
- if (len === 36 && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s)) {
11
- return "UUID";
12
- }
13
- if (/^[^\s@]+@[^\s@]+\.[A-Za-z]{2,}$/.test(s)) return "EMAIL_LIKE";
14
- if (/^https?:\/\//i.test(s) || /^www\./i.test(s)) return "URL_LIKE";
15
- if (/^[+\d][\d\s().\-]{5,17}\d$/.test(s)) {
16
- const digitCount = (s.match(/\d/g) || []).length;
17
- if (digitCount >= 7 && digitCount <= 15) return "PHONE_LIKE";
18
- }
19
- if (/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?Z?)?$/.test(s)) return "DATE_STR";
20
- if (/^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}$/.test(s)) return "DATE_STR";
21
- if (/^(true|false|yes|no|t|f|y|n)$/i.test(s)) return "BOOLEAN";
22
- if (/^\d+$/.test(s)) {
23
- if (len >= 6) return "NUMERIC_ID";
24
- return null;
25
- }
26
- if (len >= 8 && /^[0-9a-f]+$/i.test(s)) return "HEX_ID";
27
- if (/^[A-Z][A-Z0-9]*$/.test(s) && !/^\d+$/.test(s)) {
28
- return "ALL_UPPER";
29
- }
30
- if (len >= 6 && /^[A-Za-z0-9_-]+$/.test(s) && /\d/.test(s) && /[A-Za-z]/.test(s)) {
31
- return "OPAQUE_ID_ALPHANUMERIC";
32
- }
33
- const wordCount = s.trim().split(/\s+/).length;
34
- const hasInternalPunct = /[.!?,;:]/.test(s);
35
- if (wordCount >= 2) {
36
- if (hasInternalPunct || /[a-z][A-Z]/.test(s) || s.length > 40) return "SENTENCE_TEXT";
37
- if (/^[A-Z]/.test(s) && /\b[A-Z][a-z]+/.test(s)) return "TITLE_CASE_WORDS";
38
- if (/^[a-z]/.test(s) && !/[A-Z]/.test(s)) return "LOWER_WORDS";
39
- return "SENTENCE_TEXT";
40
- }
41
- if (/^[A-Z][a-z]+$/.test(s)) return "TITLE_CASE_WORDS";
42
- if (/^[a-z]+$/.test(s)) return "LOWER_WORDS";
43
- return null;
44
- }
45
- function detectFormatSignature(values) {
46
- if (!values || values.length === 0) return "OTHER";
47
- const counts = /* @__PURE__ */ new Map();
48
- const lengthBySig = /* @__PURE__ */ new Map();
49
- let classified = 0;
50
- for (const v of values) {
51
- const sig = classifyOne(v);
52
- if (!sig) continue;
53
- classified++;
54
- counts.set(sig, (counts.get(sig) || 0) + 1);
55
- if (!lengthBySig.has(sig)) lengthBySig.set(sig, /* @__PURE__ */ new Set());
56
- lengthBySig.get(sig).add(String(v).length);
57
- }
58
- if (classified === 0) return "OTHER";
59
- let topSig = null;
60
- let topCount = 0;
61
- for (const [sig, count] of counts.entries()) {
62
- if (count > topCount) {
63
- topCount = count;
64
- topSig = sig;
65
- }
66
- }
67
- if (!topSig) return "OTHER";
68
- const dominance = topCount / values.length;
69
- if (dominance < SIGNATURE_FLOOR) return "MIXED";
70
- if (topSig === "ALL_UPPER") {
71
- const lens = lengthBySig.get("ALL_UPPER");
72
- if (lens.size === 1) {
73
- const n = lens.values().next().value;
74
- return `ALL_UPPER_FIXED_${n}`;
75
- }
76
- }
77
- return topSig;
78
- }
79
-
80
- export {
81
- detectFormatSignature
82
- };
83
- //# sourceMappingURL=chunk-ICDJLWEM.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/formatDetector.ts"],
4
- "sourcesContent": ["// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Client-side format-signature detector\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Replaces `topCategoryValues` (obfuscated string samples) for String-dataType\n// columns. Instead of shipping 5 class-preserving random tokens for the LLM\n// to infer format-class from, we classify ONCE on the client (where the raw\n// values are visible) and ship a single enum-like signature.\n//\n// Genesis (2026-05-27): the server's DataShapeAdvisor.IsLikelyIdColumn was\n// already walking each character of the obfuscated tokens to infer \"id-like\"\n// \u2014 that's a format-classifier in the wrong place. Obfuscation preserves\n// character classes by design, so the server-side inference accidentally\n// works, but the logic belongs on the client where it sees real data. Side\n// benefit: no obfuscated values ship for Strings \u2192 the LLM can't hardcode\n// one as a runtime filter (image-bug 2026-05-26).\n//\n// Signature catalog (returned values):\n// ALL_UPPER_FIXED_<n> \u2014 every value is uppercase letters, length exactly n\n// ALL_UPPER \u2014 uppercase-only, mixed length\n// TITLE_CASE_WORDS \u2014 proper-name shape (e.g. \"Delta Air Lines\")\n// LOWER_WORDS \u2014 lowercase-only words, often slugs / tags\n// SENTENCE_TEXT \u2014 multi-word strings with mixed casing / punctuation\n// EMAIL_LIKE \u2014 contains @ + a domain suffix\n// URL_LIKE \u2014 starts with http(s):// or contains www.\n// PHONE_LIKE \u2014 digits + parens/dashes/spaces, length 7-15\n// DATE_STR \u2014 ISO-ish or slash-formatted dates\n// NUMERIC_ID \u2014 all-digit strings of length \u2265 6\n// HEX_ID \u2014 hex characters [0-9a-f]+ of length \u2265 8\n// UUID \u2014 8-4-4-4-12 hex with dashes\n// OPAQUE_ID_ALPHANUMERIC \u2014 alphanumeric + dashes/underscores, no whitespace,\n// mixed letters+digits, length \u2265 6\n// BOOLEAN \u2014 Boolean-rendered text (True/False, Yes/No)\n// MIXED \u2014 distinct values don't agree on a single signature\n// OTHER \u2014 no signature matched (short categorical labels)\n//\n// Threshold: a signature is returned if \u2265 70% of the sampled distinct values\n// match it. The 70% floor tolerates a few stragglers (e.g. one \"TBD\" in an\n// otherwise ID-like column) without dropping to OTHER unnecessarily.\n\nconst SIGNATURE_FLOOR = 0.70;\n\n// Per-value classifier. Returns the highest-specificity signature that fits\n// a single value, or null if none.\nfunction classifyOne(v: string): string | null {\n if (v === null || v === undefined) return null;\n const s = String(v);\n if (s.length === 0) return null;\n const len = s.length;\n\n // UUID \u2014 most specific, check first.\n if (len === 36 && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s)) {\n return \"UUID\";\n }\n\n // EMAIL \u2014 @ and a TLD-shaped suffix.\n if (/^[^\\s@]+@[^\\s@]+\\.[A-Za-z]{2,}$/.test(s)) return \"EMAIL_LIKE\";\n\n // URL \u2014 http(s):// or www.\n if (/^https?:\\/\\//i.test(s) || /^www\\./i.test(s)) return \"URL_LIKE\";\n\n // PHONE \u2014 digits with separators, total digit count 7-15.\n if (/^[+\\d][\\d\\s().\\-]{5,17}\\d$/.test(s)) {\n const digitCount = (s.match(/\\d/g) || []).length;\n if (digitCount >= 7 && digitCount <= 15) return \"PHONE_LIKE\";\n }\n\n // DATE \u2014 ISO-ish or common slash/dash formats.\n if (/^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2}(:\\d{2})?(\\.\\d+)?Z?)?$/.test(s)) return \"DATE_STR\";\n if (/^\\d{1,2}[\\/\\-]\\d{1,2}[\\/\\-]\\d{2,4}$/.test(s)) return \"DATE_STR\";\n\n // BOOLEAN \u2014 common text renderings.\n if (/^(true|false|yes|no|t|f|y|n)$/i.test(s)) return \"BOOLEAN\";\n\n // NUMERIC_ID \u2014 long all-digit (short ones are codes / years / ordinals).\n if (/^\\d+$/.test(s)) {\n if (len >= 6) return \"NUMERIC_ID\";\n return null; // short numeric \u2192 falls through to OTHER\n }\n\n // HEX_ID \u2014 hex characters only, length \u2265 8.\n if (len >= 8 && /^[0-9a-f]+$/i.test(s)) return \"HEX_ID\";\n\n // ALL_UPPER \u2014 uppercase letters and digits, no spaces (codes / abbreviations).\n if (/^[A-Z][A-Z0-9]*$/.test(s) && !/^\\d+$/.test(s)) {\n // Caller aggregates by length to decide ALL_UPPER_FIXED_n vs ALL_UPPER.\n return \"ALL_UPPER\";\n }\n\n // OPAQUE_ID \u2014 alphanumeric + dashes/underscores, no whitespace, has BOTH\n // letters and digits. Length \u2265 6 to skip short tokens like \"A1\" / \"Q2\".\n if (len >= 6 && /^[A-Za-z0-9_-]+$/.test(s) && /\\d/.test(s) && /[A-Za-z]/.test(s)) {\n return \"OPAQUE_ID_ALPHANUMERIC\";\n }\n\n // Words-and-text classifiers (whitespace OK).\n // SENTENCE_TEXT \u2014 multi-word with mixed casing OR punctuation.\n const wordCount = s.trim().split(/\\s+/).length;\n const hasInternalPunct = /[.!?,;:]/.test(s);\n if (wordCount >= 2) {\n if (hasInternalPunct || /[a-z][A-Z]/.test(s) || s.length > 40) return \"SENTENCE_TEXT\";\n // Multi-word, no internal punct, \u2264 40 chars \u2014 check casing.\n if (/^[A-Z]/.test(s) && /\\b[A-Z][a-z]+/.test(s)) return \"TITLE_CASE_WORDS\";\n if (/^[a-z]/.test(s) && !/[A-Z]/.test(s)) return \"LOWER_WORDS\";\n return \"SENTENCE_TEXT\";\n }\n\n // Single word.\n if (/^[A-Z][a-z]+$/.test(s)) return \"TITLE_CASE_WORDS\";\n if (/^[a-z]+$/.test(s)) return \"LOWER_WORDS\";\n\n return null;\n}\n\nexport function detectFormatSignature(values: string[]): string {\n if (!values || values.length === 0) return \"OTHER\";\n\n const counts = new Map<string, number>();\n const lengthBySig = new Map<string, Set<number>>();\n let classified = 0;\n\n for (const v of values) {\n const sig = classifyOne(v);\n if (!sig) continue;\n classified++;\n counts.set(sig, (counts.get(sig) || 0) + 1);\n if (!lengthBySig.has(sig)) lengthBySig.set(sig, new Set());\n lengthBySig.get(sig)!.add(String(v).length);\n }\n\n if (classified === 0) return \"OTHER\";\n\n // Pick the dominant signature.\n let topSig: string | null = null;\n let topCount = 0;\n for (const [sig, count] of counts.entries()) {\n if (count > topCount) {\n topCount = count;\n topSig = sig;\n }\n }\n if (!topSig) return \"OTHER\";\n\n const dominance = topCount / values.length;\n if (dominance < SIGNATURE_FLOOR) return \"MIXED\";\n\n // ALL_UPPER_FIXED_n specialization. If every classified-as-ALL_UPPER value\n // shares the same length, emit ALL_UPPER_FIXED_<n>. Otherwise plain\n // ALL_UPPER. The product spec (2026-05-27): \"use n instead of hardcoding 2/3\".\n if (topSig === \"ALL_UPPER\") {\n const lens = lengthBySig.get(\"ALL_UPPER\")!;\n if (lens.size === 1) {\n const n = lens.values().next().value;\n return `ALL_UPPER_FIXED_${n}`;\n }\n }\n\n return topSig;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }