@bicharts/shape-core 0.5.45 → 0.5.46

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.
package/dist/index.mjs CHANGED
@@ -1,2 +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{a as Lt,b as zt}from"./chunk-5233JUKA.mjs";import{a as pt,b as yt,c as dt,d as gt,e as Mt,f as Tt,g as At,h as St,i as vt,j as kt,k as It}from"./chunk-LDC2C4WC.mjs";import{a as ht}from"./chunk-QEH7WTLB.mjs";import{a as Ct,b as Pt}from"./chunk-2LR73DNS.mjs";import{a as xt,b as bt,c as Rt}from"./chunk-NWDPA7DG.mjs";import{a as K,b as V,c as ut,d as ct,e as ft,f as mt}from"./chunk-SAASSIJ7.mjs";import{a as R,f as M,g,h as T,i as G,j as X,k as tt,l as H,m as p,n as nt,o as et,p as ot,q as A,r as it,s as rt,t as st,u as at,v as _,w as lt}from"./chunk-E6SVTFAW.mjs";import"./chunk-5MRZID36.mjs";import{a as Y,b as J,c as Q}from"./chunk-WBEROMBG.mjs";var w=2,j=400;function N(r){return!!M(r)}function P(r){let n=new Set;for(let e of r){if(e==null)continue;let s=R(String(e));if(!T(s)&&!n.has(s)&&(n.add(s),n.size>=j))break}return Array.from(n)}function C(r){let n=P(r);if(n.length===0||E(r)<95)return!1;let e=n.filter(l=>N(l)&&!p(l)).length,s=n.filter(l=>!N(l)&&!!p(l)).length;return e>=1&&e>=s}function q(r,n){if(!p(r))return!1;let e=M(r);return e?n==="world"?!0:H.has(e):!1}function E(r){let n=P(r);if(n.length===0)return 0;let e=0;for(let s of n)N(s)&&e++;return Math.round(e/n.length*1e3)/10}function S(r){let n=P(r);if(n.length===0)return 0;let e=0;for(let s of n)p(s)&&e++;return Math.round(e/n.length*1e3)/10}function D(r){let n=new Set;for(let e of r){if(e==null)continue;let s=String(e).trim();if(!T(R(s))&&!n.has(s)&&(n.add(s),n.size>=j))break}return Array.from(n)}function O(r){let n=D(r);if(n.length===0)return 0;let e=0;for(let s of n)A(s).length>0&&e++;return Math.round(e/n.length*1e3)/10}function Z(r,n){return V(n,K)?!0:D(r).some(e=>/^\d{5}(-\d{4})?$/.test(e.replace(/\.0+$/,"")))}function Gt(r,n){let e=P(r);if(e.length===0)return 0;let s=0;for(let l of e)_(l,n)&&s++;return Math.round(s/e.length*1e3)/10}function $(r,n){let e=0;for(let s of P(r))n(s)&&e++;return e}function _t(r,n){let e=0;for(let s of D(r))n(s)&&e++;return e}function wt(r,n,e,s){let l=[],c=[],o={},v={},k=new Set(r),u=t=>n.map(i=>i[t]);for(let t of["city","lat","lon","country"]){let i=e?.[t];i&&k.has(i)&&(o[t]=i)}let y=e?.zip;if(y&&k.has(y)){let t=u(y),i=O(t);i<95?c.push(`zip=${y} (only ${i}% of values read as ZIPs)`):Z(t,y)?o.zip=y:c.push(`zip=${y} (all values are 3-4 digits with nothing to say they are postal)`)}let m=e?.state;if(m&&k.has(m)){let t=u(m),i=P(t);i.length>0&&i.every(a=>q(a,s))?c.push(`state=${m} (every value is "CA", which is both Canada and California)`):C(t)?(c.push(`state=${m} (every value is a country, not a state)`),o.country||(o.country=m)):S(t)<95?c.push(`state=${m} (only ${S(t)}% of values are states/provinces)`):o.state=m}let d=new Set(Object.values(o).filter(Boolean)),x=r.filter(t=>!d.has(t)&&!t.startsWith("__"));if(!o.state){let t=null;for(let i of x){let a=u(i);if(C(a))continue;let f=S(a);if(f<95)continue;let z=$(a,b=>!!p(b)),W=$(a,b=>!!p(b)&&!q(b,s));z<w&&W<1||(!t||f>t.pct)&&(t={name:i,pct:f})}t&&(o.state=t.name,d.add(t.name),l.push(`state=${t.name} (${t.pct}% states/provinces)`))}if(!o.zip)for(let t of x){if(d.has(t))continue;let i=u(t),a=O(i);if(!(a<95)&&Z(i,t)&&!(_t(i,f=>A(f).length>0)<w)){o.zip=t,d.add(t),l.push(`zip=${t} (${a}% ZIP codes)`);break}}let h=o.country;if(!h||!C(u(h))){let t=null;for(let i of x){if(d.has(i)||!C(u(i)))continue;let a=E(u(i));(!t||a>t.pct)&&(t={name:i,pct:a})}t&&(h&&(c.push(`country=${h} (only ${E(u(h))}% of values are country identifiers; using ${t.name} instead)`),v.country=h),o.country=t.name,d.add(t.name),l.push(`country=${t.name}`))}if(!o.city){let t=null;for(let i of x){if(d.has(i))continue;let a=u(i),f=Gt(a,s);f<G||$(a,z=>_(z,s))<w||(!t||f>t.pct)&&(t={name:i,pct:f})}t&&(o.city=t.name,l.push(`city=${t.name} (${t.pct}% known cities)`))}let B=o.country?u(o.country):[],I=!!o.country&&C(B),U=I?new Set(B.map(t=>M(t==null?null:String(t))).filter(Boolean)).size:0,F=I&&U>=2,L=!!(o.lat&&o.lon)&&n.some(t=>{let i=+String(t[o.lat]??"").trim(),a=+String(t[o.lon]??"").trim();return isFinite(i)&&isFinite(a)&&i>=-90&&i<=90&&a>=-180&&a<=180&&String(t[o.lat]??"").trim()!==""&&String(t[o.lon]??"").trim()!==""});return o.lat&&o.lon&&!L&&c.push(`lat/lon=${o.lat}/${o.lon} (no row holds a usable coordinate pair)`),o.country&&!F&&!(o.city||o.state||o.zip||L)&&c.push(I?`country=${o.country} (every row resolves to the same country - nothing to place apart)`:`country=${o.country} (values are not country identifiers)`),{bind:!!(o.city||o.state||o.zip||F||L)?o:null,backfilled:l,refused:c,...Object.keys(v).length?{labelFallback:v}:{}}}export{G as CITY_ROLE_MATCH_PCT,Rt as GET_RANDOM,It as IndexedText,g as ROLE_MATCH_PCT,bt as SIMPLE_STRING_HASH,xt as STR,S as admin1MatchPct,mt as buildGeoIsoColumn,st as buildGeoPointColumns,lt as cityMatchPct,at as cityTagsFor,vt as classifyAdditivity,kt as classifyNumericValueNature,At as classifyTemporal,dt as countryRegion,ht as detectFormatSignature,ut as detectGeo,Q as detectOrdinalDomain,Tt as detectTextDatePattern,Lt as engineTypeForSqlType,St as hostAggHint,zt as ingest,T as isBlankLike,tt as isCityTableLoaded,it as isGeoPointAmbiguity,Mt as isIdentifierName,ft as isJoinGeoKind,Y as isOrdinalFriendlyName,C as looksLikeCountryColumn,Pt as monthLookupFor,nt as normalizeCountry,Ct as normalizeMonthKey,R as normalizePlaceName,ot as normalizeZip5,X as registerCityTable,yt as registerIso3Regions,p as resolveAdmin1,rt as resolveGeoPoint,wt as resolvePointRoles,J as safeDistinctValuesToShip,gt as summarizeCountryRegions,pt as summarizeGeoExtent,ct as toGeoIso,O as zipMatchPct,A as zipPrefixCandidates,et as zipToPrefix3};
2
+ import{a as Ht,b as Vt}from"./chunk-5233JUKA.mjs";import{a as Pt,b as It,c as Rt,d as Nt,e as zt,f as Gt,g as Ft,h as $t,i as Dt,j as Kt,k as Bt}from"./chunk-LDC2C4WC.mjs";import{a as Ot}from"./chunk-QEH7WTLB.mjs";import{a as Et,b as wt}from"./chunk-2LR73DNS.mjs";import{a as kt,b as Mt,c as Lt}from"./chunk-NWDPA7DG.mjs";import{a as q,b as j,c as bt,d as Tt,e as _t,f as St}from"./chunk-SAASSIJ7.mjs";import{a as b,f as T,g as y,h as _,i as w,j as ft,k as gt,l as Z,m as g,n as pt,o as mt,p as yt,q as S,r as At,s as ht,t as vt,u as xt,v as k,w as Ct}from"./chunk-E6SVTFAW.mjs";import"./chunk-5MRZID36.mjs";import{a as lt,b as ct,c as dt}from"./chunk-WBEROMBG.mjs";var W=["rate","ratio","pct","percent","percentage","rating","share","score","index","nps","csat","margin","yield","coverage","utilization","z[_-]?score","average","avg","mean","median","stddev","variance","gpa","percentile","quantile","efficiency","accuracy","probability","multiplier","coefficient","correlation","occupancy","age","bmi","temperature","density","pressure","humidity","ph","velocity","speed","elevation","altitude","latency","throughput","bandwidth","iops"],X=["pct","percent","percentage","rate","ratio","rating","share","score","index","nps","csat","margin","yield","coverage","utilization","gpa","percentile","quantile","bmi","density","pressure","humidity","velocity","efficiency","occupancy","latency","throughput","bandwidth","iops"],Y=["latitude","longitude"],J=["latitude","longitude"],Q=/^\s*(sum|count|count\s+distinct|distinct\s+count)\s+of\s+/i,Ut=/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])/g,Zt=new RegExp("\\b("+W.join("|")+")\\b|("+X.join("|")+")$","i"),qt=new RegExp("\\b("+Y.join("|")+")\\b|("+J.join("|")+")$","i");function M(e){return e?String(e).replace(Q,"").trim():""}function tt(e){return!!e&&Q.test(String(e))}function jt(e){let t=M(e);return t.length===0?"":t.replace(Ut," ")}function et(e,t){return t?e.test(jt(t))||e.test(M(t)):!1}function L(e){return et(qt,e)}function nt(e){return et(Zt,e)||L(e)}var it=/int|double|decimal|single|float|number|currency|money/i;function Wt(e){let t=String(e?.valueNature??"").trim().toLowerCase();return t==="categorical"?"Categorical":t==="ordinal"?"Ordinal":t==="continuous"||e?.isMeasure===!0||it.test(String(e?.dataType??""))?"Continuous":"Categorical"}function Xt(e){return e?.isMeasure===!0||it.test(String(e?.dataType??""))}function z(e){let t=e??{},i=Wt(t);if(i!=="Continuous")return{nature:i,additivity:"unknown",basis:"role",suppressed:!1};if(L(t.name))return{nature:i,additivity:"positional",basis:"name",suppressed:!0};let r=String(t.additivity??"").trim().toLowerCase();if(!(r==="additive"&&tt(t.name))){if(r==="additive")return{nature:i,additivity:"additive",basis:"measured",suppressed:!1};if(r==="part_of_whole")return{nature:i,additivity:"part_of_whole",basis:"measured",suppressed:!1};if(r==="intensive_rate")return{nature:i,additivity:"intensive_rate",basis:"measured",suppressed:!1}}return t.discourageAggregationAcrossGroups===!0?{nature:i,additivity:"intensive_rate",basis:"host-flag",suppressed:!1}:nt(t.name)?{nature:i,additivity:"intensive_rate",basis:"name",suppressed:!1}:{nature:i,additivity:"additive",basis:"default",suppressed:!1}}function G(e){let t=z(e),i=e??{};if(t.nature==="Categorical")return["first","distinctcount","count"];if(t.nature==="Ordinal")return Xt(i)?["median","min","max","first","distinctcount","count"]:["first","distinctcount","count"];switch(t.additivity){case"positional":return["min","max","count","distinctcount","first"];case"intensive_rate":return["average","median","min","max","count","distinctcount","first"];default:return["sum","average","median","min","max","count","distinctcount","first"]}}function Yt(e){return G(e)[0]}function Jt(e,t){return G(e).indexOf(t)>=0}function Qt(e,t){if(t!=="sum"&&t!=="count")return!1;if(t==="count")return!0;let i=z(e);return i.additivity==="additive"||i.additivity==="part_of_whole"||i.additivity==="unknown"}var F=2,st=400;function D(e){return!!T(e)}function v(e){let t=new Set;for(let i of e){if(i==null)continue;let r=b(String(i));if(!_(r)&&!t.has(r)&&(t.add(r),t.size>=st))break}return Array.from(t)}function h(e){let t=v(e);if(t.length===0||K(e)<95)return!1;let i=t.filter(u=>D(u)&&!g(u)).length,r=t.filter(u=>!D(u)&&!!g(u)).length;return i>=1&&i>=r}function ot(e,t){if(!g(e))return!1;let i=T(e);return i?t==="world"?!0:Z.has(i):!1}function K(e){let t=v(e);if(t.length===0)return 0;let i=0;for(let r of t)D(r)&&i++;return Math.round(i/t.length*1e3)/10}function P(e){let t=v(e);if(t.length===0)return 0;let i=0;for(let r of t)g(r)&&i++;return Math.round(i/t.length*1e3)/10}function H(e){let t=new Set;for(let i of e){if(i==null)continue;let r=String(i).trim();if(!_(b(r))&&!t.has(r)&&(t.add(r),t.size>=st))break}return Array.from(t)}function B(e){let t=H(e);if(t.length===0)return 0;let i=0;for(let r of t)S(r).length>0&&i++;return Math.round(i/t.length*1e3)/10}function rt(e,t){return j(t,q)?!0:H(e).some(i=>/^\d{5}(-\d{4})?$/.test(i.replace(/\.0+$/,"")))}function te(e,t){let i=v(e);if(i.length===0)return 0;let r=0;for(let u of i)k(u,t)&&r++;return Math.round(r/i.length*1e3)/10}function $(e,t){let i=0;for(let r of v(e))t(r)&&i++;return i}function ee(e,t){let i=0;for(let r of H(e))t(r)&&i++;return i}function ne(e,t,i,r){let u=[],c=[],o={},I={},R=new Set(e),l=n=>t.map(s=>s[n]);for(let n of["city","lat","lon","country"]){let s=i?.[n];s&&R.has(s)&&(o[n]=s)}let p=i?.zip;if(p&&R.has(p)){let n=l(p),s=B(n);s<95?c.push(`zip=${p} (only ${s}% of values read as ZIPs)`):rt(n,p)?o.zip=p:c.push(`zip=${p} (all values are 3-4 digits with nothing to say they are postal)`)}let f=i?.state;if(f&&R.has(f)){let n=l(f),s=v(n);s.length>0&&s.every(a=>ot(a,r))?c.push(`state=${f} (every value is "CA", which is both Canada and California)`):h(n)?(c.push(`state=${f} (every value is a country, not a state)`),o.country||(o.country=f)):P(n)<95?c.push(`state=${f} (only ${P(n)}% of values are states/provinces)`):o.state=f}let m=new Set(Object.values(o).filter(Boolean)),x=e.filter(n=>!m.has(n)&&!n.startsWith("__"));if(!o.state){let n=null;for(let s of x){let a=l(s);if(h(a))continue;let d=P(a);if(d<95)continue;let E=$(a,C=>!!g(C)),ut=$(a,C=>!!g(C)&&!ot(C,r));E<F&&ut<1||(!n||d>n.pct)&&(n={name:s,pct:d})}n&&(o.state=n.name,m.add(n.name),u.push(`state=${n.name} (${n.pct}% states/provinces)`))}if(!o.zip)for(let n of x){if(m.has(n))continue;let s=l(n),a=B(s);if(!(a<95)&&rt(s,n)&&!(ee(s,d=>S(d).length>0)<F)){o.zip=n,m.add(n),u.push(`zip=${n} (${a}% ZIP codes)`);break}}let A=o.country;if(!A||!h(l(A))){let n=null;for(let s of x){if(m.has(s)||!h(l(s)))continue;let a=K(l(s));(!n||a>n.pct)&&(n={name:s,pct:a})}n&&(A&&(c.push(`country=${A} (only ${K(l(A))}% of values are country identifiers; using ${n.name} instead)`),I.country=A),o.country=n.name,m.add(n.name),u.push(`country=${n.name}`))}if(!o.city){let n=null;for(let s of x){if(m.has(s))continue;let a=l(s),d=te(a,r);d<w||$(a,E=>k(E,r))<F||(!n||d>n.pct)&&(n={name:s,pct:d})}n&&(o.city=n.name,u.push(`city=${n.name} (${n.pct}% known cities)`))}let V=o.country?l(o.country):[],N=!!o.country&&h(V),at=N?new Set(V.map(n=>T(n==null?null:String(n))).filter(Boolean)).size:0,U=N&&at>=2,O=!!(o.lat&&o.lon)&&t.some(n=>{let s=+String(n[o.lat]??"").trim(),a=+String(n[o.lon]??"").trim();return isFinite(s)&&isFinite(a)&&s>=-90&&s<=90&&a>=-180&&a<=180&&String(n[o.lat]??"").trim()!==""&&String(n[o.lon]??"").trim()!==""});return o.lat&&o.lon&&!O&&c.push(`lat/lon=${o.lat}/${o.lon} (no row holds a usable coordinate pair)`),o.country&&!U&&!(o.city||o.state||o.zip||O)&&c.push(N?`country=${o.country} (every row resolves to the same country - nothing to place apart)`:`country=${o.country} (values are not country identifiers)`),{bind:!!(o.city||o.state||o.zip||U||O)?o:null,backfilled:u,refused:c,...Object.keys(I).length?{labelFallback:I}:{}}}export{w as CITY_ROLE_MATCH_PCT,Lt as GET_RANDOM,X as INTENSIVE_SUFFIX_TOKENS,W as INTENSIVE_WORD_TOKENS,Bt as IndexedText,J as POSITIONAL_SUFFIX_TOKENS,Y as POSITIONAL_WORD_TOKENS,y as ROLE_MATCH_PCT,Mt as SIMPLE_STRING_HASH,kt as STR,P as admin1MatchPct,G as allowedAggregations,St as buildGeoIsoColumn,vt as buildGeoPointColumns,Ct as cityMatchPct,xt as cityTagsFor,Dt as classifyAdditivity,z as classifyForAggregation,Kt as classifyNumericValueNature,Ft as classifyTemporal,Rt as countryRegion,Yt as defaultAggregation,Ot as detectFormatSignature,bt as detectGeo,dt as detectOrdinalDomain,Gt as detectTextDatePattern,Ht as engineTypeForSqlType,tt as hasDefaultAggPrefix,$t as hostAggHint,Vt as ingest,Jt as isAggregationAllowed,_ as isBlankLike,gt as isCityTableLoaded,At as isGeoPointAmbiguity,zt as isIdentifierName,_t as isJoinGeoKind,lt as isOrdinalFriendlyName,h as looksLikeCountryColumn,wt as monthLookupFor,nt as nameLooksIntensiveRate,L as nameLooksPositional,pt as normalizeCountry,Et as normalizeMonthKey,b as normalizePlaceName,yt as normalizeZip5,ft as registerCityTable,It as registerIso3Regions,g as resolveAdmin1,ht as resolveGeoPoint,ne as resolvePointRoles,ct as safeDistinctValuesToShip,Qt as shareOfTotalIsHonest,M as stripHostAggPrefix,Nt as summarizeCountryRegions,Pt as summarizeGeoExtent,Tt as toGeoIso,B as zipMatchPct,S as zipPrefixCandidates,mt as zipToPrefix3};
@@ -0,0 +1,106 @@
1
+ /** Every aggregation any BIC surface offers. `first` is the one that only makes sense for a
2
+ * dimension — "which value is this?" — and it is why a categorical column can have a line at
3
+ * all. */
4
+ export type AggKind = "sum" | "average" | "median" | "min" | "max" | "count" | "distinctcount" | "first";
5
+ /** Does SUM mean anything over this column?
6
+ * - `additive` revenue, counts — group sums vary with group size.
7
+ * - `part_of_whole` a share: the parts sum to a CONSTANT whole, so it stacks to 100%.
8
+ * - `intensive_rate` margin, NPS, uptime%, latency — mean/median, never a total.
9
+ * - `positional` a COORDINATE. Not a quantity at all; see `suppressed`.
10
+ * - `unknown` no signal; callers treat it as additive and say so. */
11
+ export type AggAdditivity = "additive" | "part_of_whole" | "intensive_rate" | "positional" | "unknown";
12
+ /** WHICH signal decided, so a surface can explain itself and a test can pin the path rather
13
+ * than only the verdict. Ordered by strength: a measured verdict beats a name. */
14
+ export type AggBasis = "measured" | "host-flag" | "name" | "role" | "default";
15
+ export type AggNature = "Categorical" | "Ordinal" | "Continuous";
16
+ export interface AggregationClass {
17
+ nature: AggNature;
18
+ additivity: AggAdditivity;
19
+ basis: AggBasis;
20
+ /** TRUE when a presentation surface should offer NO arithmetic line for this column by
21
+ * default. Set only for `positional` today: a coordinate is not an amount, and the mark's
22
+ * own label already says which place it is. Distinct from `allowed` being empty on
23
+ * purpose — min/max over coordinates IS a bounding box and stays available to a caller
24
+ * that wants one, without re-deriving any of this. */
25
+ suppressed: boolean;
26
+ }
27
+ /** The structural slice of a column this module reads. Deliberately not `LLMColumnWithValue`:
28
+ * chart-host calls this over `buildRenderPayload`'s columns, which are the same objects but
29
+ * reach it through a package boundary that does not ship shape-core's types. */
30
+ export interface AggregationColumn {
31
+ name?: string | null;
32
+ dataType?: string | null;
33
+ isMeasure?: boolean;
34
+ valueNature?: string | null;
35
+ /** As measured by classifyAdditivity — often "unknown", and legitimately so: in a host with
36
+ * no field wells (Excel, MCP) there is no aggregation prefix to read. */
37
+ additivity?: string | null;
38
+ /** Power BI's DataViewMetadataColumn.discourageAggregationAcrossGroups. */
39
+ discourageAggregationAcrossGroups?: boolean;
40
+ }
41
+ /** Tokens that mark an INTENSIVE / averaged / ratio measure — one a SUM would corrupt, because
42
+ * the total scales with row count rather than with the quantity. Matched as a WHOLE WORD
43
+ * anywhere in the name. `z[_-]?score` is a PATTERN, not a literal, and is carried verbatim
44
+ * from the server list. */
45
+ export declare const INTENSIVE_WORD_TOKENS: readonly string[];
46
+ /** ALSO matched as a camelCase SUFFIX ("ProfitMargin", "CreditScore"). A token belongs here
47
+ * ONLY if no common ADDITIVE word ends in it — "age" is word-only because Usage / Storage /
48
+ * Coverage are summable and must not false-match. A false positive forbids a legitimate
49
+ * stack, so both arrays stay to names that are non-additive in nearly every business context. */
50
+ export declare const INTENSIVE_SUFFIX_TOKENS: readonly string[];
51
+ /** COORDINATES, carved out of the intensive list rather than added to it. The server lumps
52
+ * these with intensive rates and is right to — it only ever asks "may this stack". A menu
53
+ * needs more: min/max over coordinates is a bounding box and is useful, the mean of latitudes
54
+ * is a rough centroid, and the mean of LONGITUDES is simply wrong near the antimeridian
55
+ * (mean(+179, -179) = 0, the Gulf of Guinea). So they get their own class and, by default, no
56
+ * line at all.
57
+ *
58
+ * STRICT PARITY, and deliberately no aliases. `lat` / `lon` / `lng` are NOT here: adding them
59
+ * would make this set a superset of the server's and change what the parity test can assert,
60
+ * and adding them to BOTH would move a server verdict (a column named `Lat` would stop
61
+ * stacking), which is a corpus-replay question rather than a presentation one, and is
62
+ * recorded as a follow-up rather than done here. */
63
+ export declare const POSITIONAL_WORD_TOKENS: readonly string[];
64
+ export declare const POSITIONAL_SUFFIX_TOKENS: readonly string[];
65
+ /** "Sum of Revenue" -> "Revenue", so the name tests judge the QUANTITY and not the host's
66
+ * aggregation choice. */
67
+ export declare function stripHostAggPrefix(name: string | null | undefined): string;
68
+ /** True when the name carries a DEFAULT aggregation prefix — the one Power BI applies without
69
+ * anyone choosing it, which is why a resulting "additive" verdict is not conclusive. */
70
+ export declare function hasDefaultAggPrefix(name: string | null | undefined): boolean;
71
+ /** Does this column NAME read as a coordinate? */
72
+ export declare function nameLooksPositional(name: string | null | undefined): boolean;
73
+ /** Does this column NAME read as an intensive rate — a quantity a SUM would corrupt?
74
+ * Coordinates answer TRUE here too, because on the server they are intensive; use
75
+ * `classifyForAggregation` when the two need telling apart. */
76
+ export declare function nameLooksIntensiveRate(name: string | null | undefined): boolean;
77
+ /**
78
+ * ONE verdict per column, both axes, from every signal in priority order.
79
+ *
80
+ * The additivity ladder mirrors the server-side resolver deliberately, including its one
81
+ * subtlety: a MEASURED "additive" is NOT conclusive when it came from a default aggregation
82
+ * prefix, because Power BI applies Sum to every numeric column dropped in a measure well —
83
+ * "Sum of LatencyMs" is evidence about the host, not about the quantity. Every other measured
84
+ * value is trusted outright: part_of_whole and intensive_rate are decided, not defaulted.
85
+ */
86
+ export declare function classifyForAggregation(col: AggregationColumn | null | undefined): AggregationClass;
87
+ /**
88
+ * The MENU: every aggregation that is honest for this column, best first — so `[0]` is also the
89
+ * answer to "what does Auto mean here".
90
+ *
91
+ * `count` and `distinctcount` are legal for everything because they are questions about the
92
+ * ROWS, not about the quantity. `first` likewise, and it is the only one that earns its place on
93
+ * a dimension.
94
+ */
95
+ export declare function allowedAggregations(col: AggregationColumn | null | undefined): AggKind[];
96
+ /** What "Auto — sum amounts, average rates" actually means for THIS column. The settings panel
97
+ * has promised exactly this since the aggregation control shipped; until 2026-09-01 the code
98
+ * resolved the blank to `sum` for every column alike. */
99
+ export declare function defaultAggregation(col: AggregationColumn | null | undefined): AggKind;
100
+ /** Is `agg` honest for this column? The one predicate a surface needs when the user has CHOSEN
101
+ * an aggregation globally and it has to be applied per column. */
102
+ export declare function isAggregationAllowed(col: AggregationColumn | null | undefined, agg: AggKind): boolean;
103
+ /** A SHARE-OF-TOTAL is honest only when the parts sum to the whole — which needs BOTH an
104
+ * additive aggregation AND an additive column. The card had the first half and printed
105
+ * "12.3% of total" beside a total of latitudes for want of the second. */
106
+ export declare function shareOfTotalIsHonest(col: AggregationColumn | null | undefined, agg: AggKind): boolean;
@@ -2,6 +2,8 @@ export { IndexedText } from "./indexedText";
2
2
  export type { IValueCollection } from "./indexedText";
3
3
  export { classifyTemporal, detectTextDatePattern, classifyAdditivity, classifyNumericValueNature, hostAggHint, isIdentifierName, } from "./indexedText";
4
4
  export type { TextDateDetection } from "./indexedText";
5
+ export { classifyForAggregation, allowedAggregations, defaultAggregation, isAggregationAllowed, shareOfTotalIsHonest, nameLooksIntensiveRate, nameLooksPositional, stripHostAggPrefix, hasDefaultAggPrefix, INTENSIVE_WORD_TOKENS, INTENSIVE_SUFFIX_TOKENS, POSITIONAL_WORD_TOKENS, POSITIONAL_SUFFIX_TOKENS, } from "./aggregation";
6
+ export type { AggKind, AggAdditivity, AggBasis, AggNature, AggregationClass, AggregationColumn, } from "./aggregation";
5
7
  export type { LLMColumnWithValue } from "./models";
6
8
  export type { LLMRequestModifier, SimpleColumn, VegaRendererPayload, LLMClientHints, LLMRequestCode, LLMQualifyResult, LLMRequestCodeResult, GetLicenseStatusResult, } from "./models";
7
9
  export { ROLE_MATCH_PCT, CITY_ROLE_MATCH_PCT, isBlankLike } from "./matchQuality";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/shape-core",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
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",