fable 3.1.61 → 3.1.63
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/package.json +11 -11
- package/source/services/Fable-Service-Math.js +95 -28
- package/test/ExpressionParser_tests.js +32 -8
- package/.babelrc +0 -3
- package/.browserslistrc +0 -1
- package/dist/fable.js +0 -5183
- package/dist/fable.js.map +0 -1
- package/dist/fable.min.js +0 -12
- package/dist/fable.min.js.map +0 -1
- package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +0 -10514
- /package/docs/{cover.md → _cover.md} +0 -0
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fable",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.63",
|
|
4
4
|
"description": "A service dependency injection, configuration and logging library.",
|
|
5
5
|
"main": "source/Fable.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "node source/Fable.js",
|
|
8
|
-
"coverage": "
|
|
9
|
-
"test": "
|
|
8
|
+
"coverage": "npx quack coverage",
|
|
9
|
+
"test": "npx quack test",
|
|
10
10
|
"build": "npx quack build",
|
|
11
11
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t fable-image:local",
|
|
12
12
|
"docker-dev-run": "docker run -it -d --name fable-dev -p 30001:8080 -p 38086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/fable\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" fable-image:local",
|
|
13
13
|
"docker-dev-shell": "docker exec -it fable-dev /bin/bash",
|
|
14
|
-
"tests": "
|
|
14
|
+
"tests": "npx quack test -g"
|
|
15
15
|
},
|
|
16
16
|
"mocha": {
|
|
17
17
|
"diff": true,
|
|
@@ -50,21 +50,21 @@
|
|
|
50
50
|
},
|
|
51
51
|
"homepage": "https://github.com/stevenvelozo/fable",
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"quackage": "^1.0.
|
|
53
|
+
"quackage": "^1.0.58"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"async.eachlimit": "^0.5.2",
|
|
57
57
|
"async.waterfall": "^0.5.2",
|
|
58
58
|
"big.js": "^7.0.1",
|
|
59
|
-
"cachetrax": "^1.0.
|
|
59
|
+
"cachetrax": "^1.0.6",
|
|
60
60
|
"cookie": "^1.1.1",
|
|
61
61
|
"data-arithmatic": "^1.0.7",
|
|
62
62
|
"dayjs": "^1.11.19",
|
|
63
|
-
"fable-log": "^3.0.
|
|
64
|
-
"fable-serviceproviderbase": "^3.0.
|
|
65
|
-
"fable-settings": "^3.0.
|
|
66
|
-
"fable-uuid": "^3.0.
|
|
67
|
-
"manyfest": "^1.0.
|
|
63
|
+
"fable-log": "^3.0.18",
|
|
64
|
+
"fable-serviceproviderbase": "^3.0.19",
|
|
65
|
+
"fable-settings": "^3.0.16",
|
|
66
|
+
"fable-uuid": "^3.0.13",
|
|
67
|
+
"manyfest": "^1.0.48",
|
|
68
68
|
"simple-get": "^4.0.1"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -1864,24 +1864,95 @@ class FableServiceMath extends libFableServiceBase
|
|
|
1864
1864
|
}, pRegressionCoefficients[0]);
|
|
1865
1865
|
}
|
|
1866
1866
|
|
|
1867
|
+
/**
|
|
1868
|
+
* Resolves paired Y/X value arrays from flexible argument patterns.
|
|
1869
|
+
*
|
|
1870
|
+
* The expression parser's SetConcatArray spreads comma-separated arguments
|
|
1871
|
+
* as individual function parameters. This helper normalizes three calling
|
|
1872
|
+
* conventions into a clean paired-value result:
|
|
1873
|
+
*
|
|
1874
|
+
* 1. Two arrays: SLOPE(yArray, xArray)
|
|
1875
|
+
* 2. Spread scalars: SLOPE(y1,y2,...,yN, x1,x2,...,xN) — split in half
|
|
1876
|
+
* 3. One flat array: SLOPE([y1,...,yN,x1,...,xN]) — split in half
|
|
1877
|
+
*
|
|
1878
|
+
* Non-numeric values are filtered; only pairs where both Y and X parse
|
|
1879
|
+
* are kept.
|
|
1880
|
+
*
|
|
1881
|
+
* @param {...*} pArguments - Flexible: (yArr, xArr) | (y1,y2,...,x1,x2,...) | (flatArr)
|
|
1882
|
+
*
|
|
1883
|
+
* @return {Object} { cleanX, cleanY, n }
|
|
1884
|
+
*/
|
|
1885
|
+
cleanPairedValues(...pArguments)
|
|
1886
|
+
{
|
|
1887
|
+
let tmpYValues;
|
|
1888
|
+
let tmpXValues;
|
|
1889
|
+
|
|
1890
|
+
if (pArguments.length === 2 && Array.isArray(pArguments[0]) && Array.isArray(pArguments[1]))
|
|
1891
|
+
{
|
|
1892
|
+
// Two arrays: SLOPE(yArray, xArray)
|
|
1893
|
+
tmpYValues = pArguments[0];
|
|
1894
|
+
tmpXValues = pArguments[1];
|
|
1895
|
+
}
|
|
1896
|
+
else if (pArguments.length === 1 && Array.isArray(pArguments[0]))
|
|
1897
|
+
{
|
|
1898
|
+
// Single flat array — split in half
|
|
1899
|
+
let tmpFlat = pArguments[0];
|
|
1900
|
+
let tmpHalf = Math.floor(tmpFlat.length / 2);
|
|
1901
|
+
tmpYValues = tmpFlat.slice(0, tmpHalf);
|
|
1902
|
+
tmpXValues = tmpFlat.slice(tmpHalf);
|
|
1903
|
+
}
|
|
1904
|
+
else if (pArguments.length > 2)
|
|
1905
|
+
{
|
|
1906
|
+
// Many spread scalars from expression parser — split in half
|
|
1907
|
+
let tmpHalf = Math.floor(pArguments.length / 2);
|
|
1908
|
+
tmpYValues = pArguments.slice(0, tmpHalf);
|
|
1909
|
+
tmpXValues = pArguments.slice(tmpHalf);
|
|
1910
|
+
}
|
|
1911
|
+
else
|
|
1912
|
+
{
|
|
1913
|
+
// Two scalars or other degenerate input
|
|
1914
|
+
tmpYValues = Array.isArray(pArguments[0]) ? pArguments[0] : [pArguments[0]];
|
|
1915
|
+
tmpXValues = Array.isArray(pArguments[1]) ? pArguments[1] : [pArguments[1]];
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
let tmpPairCount = Math.min(tmpYValues.length, tmpXValues.length);
|
|
1919
|
+
|
|
1920
|
+
let tmpCleanX = [];
|
|
1921
|
+
let tmpCleanY = [];
|
|
1922
|
+
|
|
1923
|
+
for (let i = 0; i < tmpPairCount; i++)
|
|
1924
|
+
{
|
|
1925
|
+
let tmpX = this.parsePrecise(tmpXValues[i], NaN);
|
|
1926
|
+
let tmpY = this.parsePrecise(tmpYValues[i], NaN);
|
|
1927
|
+
if (!isNaN(tmpX) && !isNaN(tmpY))
|
|
1928
|
+
{
|
|
1929
|
+
tmpCleanX.push(tmpX);
|
|
1930
|
+
tmpCleanY.push(tmpY);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
return { cleanX: tmpCleanX, cleanY: tmpCleanY, n: tmpCleanX.length };
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1867
1937
|
/**
|
|
1868
1938
|
* Calculates the slope of a linear regression line through paired data points.
|
|
1869
1939
|
* Equivalent to Excel's SLOPE function.
|
|
1870
1940
|
*
|
|
1871
1941
|
* Formula: slope = (n * Σ(xy) - Σx * Σy) / (n * Σ(x²) - (Σx)²)
|
|
1872
1942
|
*
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1943
|
+
* Calling conventions (all work through the expression parser):
|
|
1944
|
+
* SLOPE(yArray, xArray) — two resolved arrays
|
|
1945
|
+
* SLOPE(y1,y2,...,yN, x1,x2,...,xN) — inline scalars, split in half
|
|
1946
|
+
*
|
|
1947
|
+
* @param {...*} pArguments - Y values followed by X values (see cleanPairedValues).
|
|
1875
1948
|
*
|
|
1876
1949
|
* @return {string} The slope of the regression line.
|
|
1877
1950
|
*/
|
|
1878
|
-
slopePrecise(
|
|
1951
|
+
slopePrecise(...pArguments)
|
|
1879
1952
|
{
|
|
1880
|
-
let
|
|
1881
|
-
let tmpXValues = Array.isArray(pXValues) ? pXValues : [pXValues];
|
|
1882
|
-
let tmpN = Math.min(tmpYValues.length, tmpXValues.length);
|
|
1953
|
+
let tmpPaired = this.cleanPairedValues(...pArguments);
|
|
1883
1954
|
|
|
1884
|
-
if (
|
|
1955
|
+
if (tmpPaired.n < 2)
|
|
1885
1956
|
{
|
|
1886
1957
|
return '0';
|
|
1887
1958
|
}
|
|
@@ -1891,23 +1962,17 @@ class FableServiceMath extends libFableServiceBase
|
|
|
1891
1962
|
let tmpSumXY = '0';
|
|
1892
1963
|
let tmpSumX2 = '0';
|
|
1893
1964
|
|
|
1894
|
-
for (let i = 0; i <
|
|
1965
|
+
for (let i = 0; i < tmpPaired.n; i++)
|
|
1895
1966
|
{
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
tmpSumX = this.addPrecise(tmpSumX, tmpX);
|
|
1903
|
-
tmpSumY = this.addPrecise(tmpSumY, tmpY);
|
|
1904
|
-
tmpSumXY = this.addPrecise(tmpSumXY, this.multiplyPrecise(tmpX, tmpY));
|
|
1905
|
-
tmpSumX2 = this.addPrecise(tmpSumX2, this.multiplyPrecise(tmpX, tmpX));
|
|
1967
|
+
tmpSumX = this.addPrecise(tmpSumX, tmpPaired.cleanX[i]);
|
|
1968
|
+
tmpSumY = this.addPrecise(tmpSumY, tmpPaired.cleanY[i]);
|
|
1969
|
+
tmpSumXY = this.addPrecise(tmpSumXY, this.multiplyPrecise(tmpPaired.cleanX[i], tmpPaired.cleanY[i]));
|
|
1970
|
+
tmpSumX2 = this.addPrecise(tmpSumX2, this.multiplyPrecise(tmpPaired.cleanX[i], tmpPaired.cleanX[i]));
|
|
1906
1971
|
}
|
|
1907
1972
|
|
|
1908
1973
|
// slope = (n * Σ(xy) - Σx * Σy) / (n * Σ(x²) - (Σx)²)
|
|
1909
|
-
let tmpNumerator = this.subtractPrecise(this.multiplyPrecise(
|
|
1910
|
-
let tmpDenominator = this.subtractPrecise(this.multiplyPrecise(
|
|
1974
|
+
let tmpNumerator = this.subtractPrecise(this.multiplyPrecise(tmpPaired.n, tmpSumXY), this.multiplyPrecise(tmpSumX, tmpSumY));
|
|
1975
|
+
let tmpDenominator = this.subtractPrecise(this.multiplyPrecise(tmpPaired.n, tmpSumX2), this.multiplyPrecise(tmpSumX, tmpSumX));
|
|
1911
1976
|
|
|
1912
1977
|
if (this.comparePrecise(tmpDenominator, 0) == 0)
|
|
1913
1978
|
{
|
|
@@ -1923,19 +1988,21 @@ class FableServiceMath extends libFableServiceBase
|
|
|
1923
1988
|
*
|
|
1924
1989
|
* Formula: intercept = ȳ - slope * x̄
|
|
1925
1990
|
*
|
|
1926
|
-
*
|
|
1927
|
-
*
|
|
1991
|
+
* Calling conventions (all work through the expression parser):
|
|
1992
|
+
* INTERCEPT(yArray, xArray) — two resolved arrays
|
|
1993
|
+
* INTERCEPT(y1,y2,...,yN, x1,x2,...,xN) — inline scalars, split in half
|
|
1994
|
+
*
|
|
1995
|
+
* @param {...*} pArguments - Y values followed by X values (see cleanPairedValues).
|
|
1928
1996
|
*
|
|
1929
1997
|
* @return {string} The y-intercept of the regression line.
|
|
1930
1998
|
*/
|
|
1931
|
-
interceptPrecise(
|
|
1999
|
+
interceptPrecise(...pArguments)
|
|
1932
2000
|
{
|
|
1933
|
-
let
|
|
1934
|
-
let tmpXValues = Array.isArray(pXValues) ? pXValues : [pXValues];
|
|
2001
|
+
let tmpPaired = this.cleanPairedValues(...pArguments);
|
|
1935
2002
|
|
|
1936
|
-
let tmpSlope = this.slopePrecise(
|
|
1937
|
-
let tmpMeanY = this.meanPrecise(
|
|
1938
|
-
let tmpMeanX = this.meanPrecise(
|
|
2003
|
+
let tmpSlope = this.slopePrecise(tmpPaired.cleanY, tmpPaired.cleanX);
|
|
2004
|
+
let tmpMeanY = this.meanPrecise(tmpPaired.cleanY);
|
|
2005
|
+
let tmpMeanX = this.meanPrecise(tmpPaired.cleanX);
|
|
1939
2006
|
|
|
1940
2007
|
// intercept = ȳ - slope * x̄
|
|
1941
2008
|
return this.subtractPrecise(tmpMeanY, this.multiplyPrecise(tmpSlope, tmpMeanX));
|
|
@@ -1336,10 +1336,38 @@ suite
|
|
|
1336
1336
|
_Parser.solve('InterceptResult = INTERCEPT(AppData.YData, AppData.XData)', testFable, tmpSolverResult, false, tmpSolverResult);
|
|
1337
1337
|
Expect(tmpSolverResult.InterceptResult).to.equal('1');
|
|
1338
1338
|
|
|
1339
|
-
// Solver with inline
|
|
1339
|
+
// Solver with inline scalars (split in half: first 5 = Y, last 5 = X)
|
|
1340
1340
|
let tmpInlineResult = {};
|
|
1341
1341
|
_Parser.solve('InlineSlope = SLOPE(3,5,7,9,11, 1,2,3,4,5)', testFable, tmpInlineResult, false, tmpInlineResult);
|
|
1342
|
-
|
|
1342
|
+
Expect(tmpInlineResult.InlineSlope).to.equal('2');
|
|
1343
|
+
|
|
1344
|
+
_Parser.solve('InlineIntercept = INTERCEPT(3,5,7,9,11, 1,2,3,4,5)', testFable, tmpInlineResult, false, tmpInlineResult);
|
|
1345
|
+
Expect(tmpInlineResult.InlineIntercept).to.equal('1');
|
|
1346
|
+
|
|
1347
|
+
// Solver chain: SERIES produces string arrays, then SLOPE/INTERCEPT consume them
|
|
1348
|
+
let tmpChainData = {};
|
|
1349
|
+
_Parser.solve('XValues = SERIES FROM 1 TO 5 STEP 1 : n + 0', tmpChainData, {}, false, tmpChainData);
|
|
1350
|
+
_Parser.solve('YValues = SERIES FROM 1 TO 5 STEP 1 : 2 * n + 1', tmpChainData, {}, false, tmpChainData);
|
|
1351
|
+
// Verify the arrays are string-typed (expression parser produces strings)
|
|
1352
|
+
Expect(tmpChainData.XValues[0]).to.equal('1');
|
|
1353
|
+
Expect(typeof tmpChainData.XValues[0]).to.equal('string');
|
|
1354
|
+
Expect(tmpChainData.YValues[0]).to.equal('3');
|
|
1355
|
+
Expect(typeof tmpChainData.YValues[0]).to.equal('string');
|
|
1356
|
+
|
|
1357
|
+
_Parser.solve('ChainSlope = SLOPE(YValues, XValues)', tmpChainData, {}, false, tmpChainData);
|
|
1358
|
+
Expect(tmpChainData.ChainSlope).to.equal('2');
|
|
1359
|
+
|
|
1360
|
+
_Parser.solve('ChainIntercept = INTERCEPT(YValues, XValues)', tmpChainData, {}, false, tmpChainData);
|
|
1361
|
+
Expect(tmpChainData.ChainIntercept).to.equal('1');
|
|
1362
|
+
|
|
1363
|
+
// String arrays at AppData addresses
|
|
1364
|
+
testFable.AppData.XStrings = ['1', '2', '3', '4', '5'];
|
|
1365
|
+
testFable.AppData.YStrings = ['3', '5', '7', '9', '11'];
|
|
1366
|
+
_Parser.solve('StringSlope = SLOPE(AppData.YStrings, AppData.XStrings)', testFable, tmpSolverResult, false, tmpSolverResult);
|
|
1367
|
+
Expect(tmpSolverResult.StringSlope).to.equal('2');
|
|
1368
|
+
|
|
1369
|
+
_Parser.solve('StringIntercept = INTERCEPT(AppData.YStrings, AppData.XStrings)', testFable, tmpSolverResult, false, tmpSolverResult);
|
|
1370
|
+
Expect(tmpSolverResult.StringIntercept).to.equal('1');
|
|
1343
1371
|
|
|
1344
1372
|
// Solver with non-perfect data
|
|
1345
1373
|
testFable.AppData.YData2 = [2, 4, 5, 4, 5];
|
|
@@ -1350,12 +1378,8 @@ suite
|
|
|
1350
1378
|
Expect(Number(tmpSolverResult.Intercept2)).to.be.closeTo(2.2, 0.0001);
|
|
1351
1379
|
|
|
1352
1380
|
// Verify slope and intercept reconstruct the regression line
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
{
|
|
1356
|
-
let tmpPredicted = testFable.Math.addPrecise(tmpSolverResult.Intercept2, testFable.Math.multiplyPrecise(tmpSolverResult.Slope2, tmpXValues2[i]));
|
|
1357
|
-
testFable.log.info(`x=${tmpXValues2[i]}, y=${tmpYValues2[i]}, predicted=${tmpPredicted}`);
|
|
1358
|
-
}
|
|
1381
|
+
_Parser.solve('Predicted = ChainIntercept + ChainSlope * 6', tmpChainData, {}, false, tmpChainData);
|
|
1382
|
+
Expect(tmpChainData.Predicted).to.equal('13');
|
|
1359
1383
|
}
|
|
1360
1384
|
);
|
|
1361
1385
|
}
|
package/.babelrc
DELETED
package/.browserslistrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
since 2020
|