From 1906fc347c9721c196680c2f7faffc7ccaf7e9d7 Mon Sep 17 00:00:00 2001 From: Clive Freeman Date: Thu, 2 Apr 2026 17:40:52 +0100 Subject: [PATCH] Fix aggday gissue 4382 and some quals issues --- package-lock.json | 56 +++++++++++++++++++++++-------------------- package.json | 2 +- quals/basic_test.html | 2 +- quals/claude_quals.js | 6 ++--- src/beebrain.js | 17 ++++++------- src/butil.js | 10 +++++++- 6 files changed, 53 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cea4b4..97d7ce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Beebrain", - "version": "2026.03.12-c", + "version": "2026.03.13-b", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Beebrain", - "version": "2026.03.12-c", + "version": "2026.03.13-b", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -37,7 +37,7 @@ "gulp-rename": "^2.1.0", "gulp-terser": "^2.1.0", "nodemon": "^3.1.14", - "puppeteer": "^24.39.1", + "puppeteer": "^24.40.0", "rimraf": "^6.1.3", "terser": "^5.46.0" }, @@ -951,9 +951,9 @@ "license": "Apache-2.0" }, "node_modules/bare-fs": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", - "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz", + "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -976,9 +976,9 @@ } }, "node_modules/bare-os": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz", - "integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==", + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", + "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", "dev": true, "license": "Apache-2.0", "engines": { @@ -996,20 +996,24 @@ } }, "node_modules/bare-stream": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz", - "integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz", + "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "streamx": "^2.21.0", + "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -1019,9 +1023,9 @@ } }, "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", + "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5828,9 +5832,9 @@ } }, "node_modules/puppeteer": { - "version": "24.39.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.39.1.tgz", - "integrity": "sha512-68Zc9QpcVvfxp2C+3UL88TyUogEAn5tSylXidbEuEXvhiqK1+v65zeBU5ubinAgEHMGr3dcSYqvYrGtdzsPI3w==", + "version": "24.40.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.40.0.tgz", + "integrity": "sha512-IxQbDq93XHVVLWHrAkFP7F7iHvb9o0mgfsSIMlhHb+JM+JjM1V4v4MNSQfcRWJopx9dsNOr9adYv0U5fm9BJBQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -5839,7 +5843,7 @@ "chromium-bidi": "14.0.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1581282", - "puppeteer-core": "24.39.1", + "puppeteer-core": "24.40.0", "typed-query-selector": "^2.12.1" }, "bin": { @@ -5850,9 +5854,9 @@ } }, "node_modules/puppeteer-core": { - "version": "24.39.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.39.1.tgz", - "integrity": "sha512-AMqQIKoEhPS6CilDzw0Gd1brLri3emkC+1N2J6ZCCuY1Cglo56M63S0jOeBZDQlemOiRd686MYVMl9ELJBzN3A==", + "version": "24.40.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.40.0.tgz", + "integrity": "sha512-MWL3XbUCfVgGR0gRsidzT6oKJT2QydPLhMITU6HoVWiiv4gkb6gJi3pcdAa8q4HwjBTbqISOWVP4aJiiyUJvag==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6732,9 +6736,9 @@ "license": "MIT" }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3230743..6cc3f4c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "gulp-rename": "^2.1.0", "gulp-terser": "^2.1.0", "nodemon": "^3.1.14", - "puppeteer": "^24.39.1", + "puppeteer": "^24.40.0", "rimraf": "^6.1.3", "terser": "^5.46.0" } diff --git a/quals/basic_test.html b/quals/basic_test.html index a511424..e0005c2 100644 --- a/quals/basic_test.html +++ b/quals/basic_test.html @@ -35,7 +35,7 @@ showFocusRect: true, showContext: true}) //graph.loadGoal( "/home/saranli/work/privatesuite/data/searchby-bug.bb" ) - graph.loadGoal( "/Users/dreeves/lab/privatesuite/data/searchby-bug.bb" ) + graph.loadGoal( "../automon/data/testroad0.bb" ) diff --git a/quals/claude_quals.js b/quals/claude_quals.js index a3dd275..25e0633 100644 --- a/quals/claude_quals.js +++ b/quals/claude_quals.js @@ -23,9 +23,6 @@ function serve(req, res) { let fpath = req.url.startsWith('/') ? path.join(REPO, req.url) : req.url - // basic_test.html hardcodes an absolute path to the .bb file - if (req.url.startsWith('/Users/')) fpath = req.url - fs.readFile(fpath, (err, data) => { if (err) { res.writeHead(404) @@ -288,6 +285,8 @@ assert(bu.unaryflat([]) === false, 'unaryflat empty') assert(bu.clocky([1,2,6,9]) === 4, 'clocky([1,2,6,9])') assert(bu.clocky([1,2,6]) === 1, 'clocky odd ignores last') assert(bu.clocky([]) === 0, 'clocky empty') +assert(bu.clocky([22.5, 6]) === 7.5, 'clocky midnight crossing (#4382)') +assert(bu.clocky([23, 1]) === 2, 'clocky midnight crossing 23->1') // --- butil: mean, median, mode, trimmean --- assert(bu.mean([1,2,3]) === 2, 'mean([1,2,3])') @@ -398,6 +397,7 @@ assert(br.AGGR.nonzero([0,1]) === true, 'aggday nonzero (alias)') assert(br.AGGR.triangle([3]) === 6, 'aggday triangle 3*(3+1)/2') assert(br.AGGR.square([3]) === 9, 'aggday square 3^2') assert(br.AGGR.clocky([1,2,6,9]) === 4, 'aggday clocky') +assert(br.AGGR.clocky([22.5, 6]) === 7.5, 'aggday clocky midnight (#4382)') assert(br.AGGR.skatesum([1,2,3]) === 0, 'aggday skatesum (rsk8=0)') // --- skatesum FP consistency (github.com/beeminder/road/issues/250) --- diff --git a/src/beebrain.js b/src/beebrain.js index 1fd9815..5bc2507 100644 --- a/src/beebrain.js +++ b/src/beebrain.js @@ -636,13 +636,8 @@ function procData() { let pre = 0 // Current cumulative sum let prevpt - // HACK: aggday=skatesum needs to know rcur which we won't know until we do - // procParams. We do know rfin so we're making do with that for now... - // NB: Operation order should match fillroad's (rate/siru then *SID) to avoid - // floating point mismatch. - // br.rsk8 = gol.rfin / gol.siru * SID // daily rate for skatesum - br.rsk8 = gol.rfin * SID / gol.siru // old version for now - //br.rsk8 = br.rtf(roads, gol.asof) * SID // would this not work? + // Default rsk8 (daily rate for skatesum) from rfin as fallback + br.rsk8 = gol.rfin * SID / gol.siru // Process all datapoints for (i = 0; i <= n; i++) { @@ -652,6 +647,10 @@ function procData() { if (i >= data.length || data[i][0] != ct) { // Done recording all data for today let vlv = vl.map(dval) // extract all values for today + // Update rsk8 per-day for skatesum: use actual road rate for this day + // so weekends-off (and other road rate changes) are respected (#5451) + if (gol.aggday === "skatesum" && roads.length > 0) + br.rsk8 = br.rtf(roads, ct) * SID let ad = br.AGGR[gol.aggday](vlv) // compute aggregated value // Find previous point to record its info in the aggregated point if (newpts.length > 0) prevpt = newpts[newpts.length-1] @@ -1661,11 +1660,13 @@ function genStats(p, d, tm=null) { // after filling in road in procParams. if (bu.listy(gol.road)) gol.road.push([gol.tfin, gol.vfin, gol.rfin]) if (gol.error == "") gol.error = vetParams() - if (gol.error == "") gol.error = procData() // Extract road info into our internal format consisting of road segments: // [ [startt, startv], [endt, endv], slope, autofield ] + // NB: procRoad moved before procData so that skatesum aggregation can use + // per-day road rates (gissue #5451: weekends-off support) if (gol.error == "") gol.error = procRoad(p.road) + if (gol.error == "") gol.error = procData() if (gol.error == "") gol.error = self.reloadRoad() // does procParams here computeRosy() diff --git a/src/butil.js b/src/butil.js index 4e0b9a3..a4bd616 100644 --- a/src/butil.js +++ b/src/butil.js @@ -591,10 +591,18 @@ function unaryflat(a) { return a.some(x => x !== 0) } /** AGGDAY: Sum of differences of pairs, eg, [1,2,6,9] -> 2-1 + 9-6 = 1+3 = 4 If there's an odd number of elements then the last one is ignored. + If a pair's difference is negative, we assume the times span midnight + and add 24 hours, eg, [22.5, 6] -> (6-22.5)+24 = 7.5 + This allows e.g. a deadline of 14:00 to be used to log total time in + hours slept (see beebody gissue #4382) @param {Number[]} a Input list*/ function clocky(a) { let s = 0 - for (let i = 1; i < a.length; i += 2) s += a[i]-a[i-1] + for (let i = 1; i < a.length; i += 2) { + let d = a[i] - a[i-1] + if (d < 0) d += 24 // the pair spans midnight + s += d + } return s }