diff --git a/bun.lock b/bun.lock index a84c02a..8f40768 100644 --- a/bun.lock +++ b/bun.lock @@ -78,6 +78,7 @@ "dependencies": { "@elysiajs/cors": "^1.4.0", "@elysiajs/openapi": "^1.4.0", + "@elysiajs/server-timing": "^1.4.0", "chalk": "^5.6.2", "elysia": "^1.4.0", "zod": "^4.1.11", @@ -185,6 +186,8 @@ "name": "@cvsa/cvsa-temp", "dependencies": { "@elysiajs/eden": "^1.4.1", + "@nivo/core": "^0.99.0", + "@nivo/line": "^0.99.0", "@radix-ui/react-slot": "^1.2.3", "@react-router/node": "^7.7.1", "@react-router/serve": "^7.7.1", @@ -196,6 +199,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-router": "^7.7.1", + "recharts": "^3.2.1", "sonner": "^2.0.7", "swr": "^2.3.6", "tailwind-merge": "^3.3.1", @@ -356,6 +360,8 @@ "@elysiajs/openapi": ["@elysiajs/openapi@1.4.10", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-bEsETp/CGcs1CqH3zW6/CAI2g6d0K/g8wUuH7HwXQm0gtP18s9RnljJESuv4of3ePUoYQgy85t+dha+ABv+L/A=="], + "@elysiajs/server-timing": ["@elysiajs/server-timing@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-vDFdHyi8Q43vgA5MaTQMA9v4/bgKrtqPrpVqVuHlMCRQgfOpvYGXPj3okSttyendG5r2bRHfyPG11lTWWIrzrQ=="], + "@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], @@ -538,6 +544,28 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + "@nivo/annotations": ["@nivo/annotations@0.99.0", "", { "dependencies": { "@nivo/colors": "0.99.0", "@nivo/core": "0.99.0", "@nivo/theming": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0", "lodash": "^4.17.21" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-jCuuXPbvpaqaz4xF7k5dv0OT2ubn5Nt0gWryuTe/8oVsC/9bzSuK8bM9vBty60m9tfO+X8vUYliuaCDwGksC2g=="], + + "@nivo/axes": ["@nivo/axes@0.99.0", "", { "dependencies": { "@nivo/core": "0.99.0", "@nivo/scales": "0.99.0", "@nivo/text": "0.99.0", "@nivo/theming": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0", "@types/d3-format": "^1.4.1", "@types/d3-time-format": "^2.3.1", "d3-format": "^1.4.4", "d3-time-format": "^3.0.0" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-3KschnmEL0acRoa7INSSOSEFwJLm54aZwSev7/r8XxXlkgRBriu6ReZy/FG0wfN+ljZ4GMvx+XyIIf6kxzvrZg=="], + + "@nivo/colors": ["@nivo/colors@0.99.0", "", { "dependencies": { "@nivo/core": "0.99.0", "@nivo/theming": "0.99.0", "@types/d3-color": "^3.0.0", "@types/d3-scale": "^4.0.8", "@types/d3-scale-chromatic": "^3.0.0", "d3-color": "^3.1.0", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.0.0", "lodash": "^4.17.21" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-hyYt4lEFIfXOUmQ6k3HXm3KwhcgoJpocmoGzLUqzk7DzuhQYJo+4d5jIGGU0N/a70+9XbHIdpKNSblHAIASD3w=="], + + "@nivo/core": ["@nivo/core@0.99.0", "", { "dependencies": { "@nivo/theming": "0.99.0", "@nivo/tooltip": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0", "@types/d3-shape": "^3.1.6", "d3-color": "^3.1.0", "d3-format": "^1.4.4", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.0.0", "d3-shape": "^3.2.0", "d3-time-format": "^3.0.0", "lodash": "^4.17.21", "react-virtualized-auto-sizer": "^1.0.26", "use-debounce": "^10.0.4" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-olCItqhPG3xHL5ei+vg52aB6o+6S+xR2idpkd9RormTTUniZb8U2rOdcQojOojPY5i9kVeQyLFBpV4YfM7OZ9g=="], + + "@nivo/legends": ["@nivo/legends@0.99.0", "", { "dependencies": { "@nivo/colors": "0.99.0", "@nivo/core": "0.99.0", "@nivo/text": "0.99.0", "@nivo/theming": "0.99.0", "@types/d3-scale": "^4.0.8", "d3-scale": "^4.0.2" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-P16FjFqNceuTTZphINAh5p0RF0opu3cCKoWppe2aRD9IuVkvRm/wS5K1YwMCxDzKyKh5v0AuTlu9K6o3/hk8hA=="], + + "@nivo/line": ["@nivo/line@0.99.0", "", { "dependencies": { "@nivo/annotations": "0.99.0", "@nivo/axes": "0.99.0", "@nivo/colors": "0.99.0", "@nivo/core": "0.99.0", "@nivo/legends": "0.99.0", "@nivo/scales": "0.99.0", "@nivo/theming": "0.99.0", "@nivo/tooltip": "0.99.0", "@nivo/voronoi": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0", "@types/d3-shape": "^3.1.6", "d3-shape": "^3.2.0" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-bAqTXSjpnpcGMs341qWFUi7hJTqQiNoSeJHsYPuPS3icuXPcp3WETQH+zRZACeEF79ZigeOWCW+dzODgne1y9w=="], + + "@nivo/scales": ["@nivo/scales@0.99.0", "", { "dependencies": { "@types/d3-interpolate": "^3.0.4", "@types/d3-scale": "^4.0.8", "@types/d3-time": "^1.1.1", "@types/d3-time-format": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-time": "^1.0.11", "d3-time-format": "^3.0.0", "lodash": "^4.17.21" } }, "sha512-g/2K4L6L8si6E2BWAHtFVGahtDKbUcO6xHJtlIZMwdzaJc7yB16EpWLK8AfI/A42KadLhJSJqBK3mty+c7YZ+w=="], + + "@nivo/text": ["@nivo/text@0.99.0", "", { "dependencies": { "@nivo/core": "0.99.0", "@nivo/theming": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-ho3oZpAZApsJNjsIL5WJSAdg/wjzTBcwo1KiHBlRGUmD+yUWO8qp7V+mnYRhJchwygtRVALlPgZ/rlcW2Xr/MQ=="], + + "@nivo/theming": ["@nivo/theming@0.99.0", "", { "dependencies": { "lodash": "^4.17.21" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-KvXlf0nqBzh/g2hAIV9bzscYvpq1uuO3TnFN3RDXGI72CrbbZFTGzprPju3sy/myVsauv+Bb+V4f5TZ0jkYKRg=="], + + "@nivo/tooltip": ["@nivo/tooltip@0.99.0", "", { "dependencies": { "@nivo/core": "0.99.0", "@nivo/theming": "0.99.0", "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-weoEGR3xAetV4k2P6k96cdamGzKQ5F2Pq+uyDaHr1P3HYArM879Pl+x+TkU0aWjP6wgUZPx/GOBiV1Hb1JxIqg=="], + + "@nivo/voronoi": ["@nivo/voronoi@0.99.0", "", { "dependencies": { "@nivo/core": "0.99.0", "@nivo/theming": "0.99.0", "@nivo/tooltip": "0.99.0", "@types/d3-delaunay": "^6.0.4", "@types/d3-scale": "^4.0.8", "d3-delaunay": "^6.0.4", "d3-scale": "^4.0.2" }, "peerDependencies": { "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, "sha512-KfmMdidbYzhiUCki1FG4X4nHEFT4loK8G5bMBnmCl9U+S78W+gvkfrgD2Aoqp/Q9yKQvr3Y8UcZKSFZnn3HgjQ=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -650,6 +678,20 @@ "@react-router/serve": ["@react-router/serve@7.9.1", "", { "dependencies": { "@react-router/express": "7.9.1", "@react-router/node": "7.9.1", "compression": "^1.7.4", "express": "^4.19.2", "get-port": "5.1.1", "morgan": "^1.10.0", "source-map-support": "^0.5.21" }, "peerDependencies": { "react-router": "7.9.1" }, "bin": { "react-router-serve": "bin.js" } }, "sha512-yVBSb5KsNCdkSoOk186/M5GJtcIvKE32Ax9LhXySVpM+suCSjucI+p2TXDOJIYsBqr2aKcBl/bNBm5sIJxG/HA=="], + "@react-spring/animated": ["@react-spring/animated@9.4.5", "", { "dependencies": { "@react-spring/shared": "~9.4.5", "@react-spring/types": "~9.4.5" }, "peerDependencies": { "react": "^16.8.0 || >=17.0.0 || >=18.0.0" } }, "sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA=="], + + "@react-spring/core": ["@react-spring/core@9.4.5", "", { "dependencies": { "@react-spring/animated": "~9.4.5", "@react-spring/rafz": "~9.4.5", "@react-spring/shared": "~9.4.5", "@react-spring/types": "~9.4.5" }, "peerDependencies": { "react": "^16.8.0 || >=17.0.0 || >=18.0.0" } }, "sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ=="], + + "@react-spring/rafz": ["@react-spring/rafz@9.4.5", "", {}, "sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ=="], + + "@react-spring/shared": ["@react-spring/shared@9.4.5", "", { "dependencies": { "@react-spring/rafz": "~9.4.5", "@react-spring/types": "~9.4.5" }, "peerDependencies": { "react": "^16.8.0 || >=17.0.0 || >=18.0.0" } }, "sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA=="], + + "@react-spring/types": ["@react-spring/types@9.4.5", "", {}, "sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg=="], + + "@react-spring/web": ["@react-spring/web@9.4.5", "", { "dependencies": { "@react-spring/animated": "~9.4.5", "@react-spring/core": "~9.4.5", "@react-spring/shared": "~9.4.5", "@react-spring/types": "~9.4.5" }, "peerDependencies": { "react": "^16.8.0 || >=17.0.0 || >=18.0.0", "react-dom": "^16.8.0 || >=17.0.0 || >=18.0.0" } }, "sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA=="], + + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.9.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="], "@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.1", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ=="], @@ -752,6 +794,10 @@ "@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="], @@ -818,6 +864,32 @@ "@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-format": ["@types/d3-format@1.4.5", "", {}, "sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], + + "@types/d3-time": ["@types/d3-time@1.1.4", "", {}, "sha512-JIvy2HjRInE+TXOmIGN5LCmeO0hkFZx5f9FZ7kiN+D+YTcc8pptsiLiuHsvwxwC7VVKmJ2ExHUgNlAiV7vQM9g=="], + + "@types/d3-time-format": ["@types/d3-time-format@2.3.4", "", {}, "sha512-xdDXbpVO74EvadI3UDxjxTdR6QIxm1FKzEA/+F8tL4GWWUg/hgvBqf6chql64U5A9ZUGWo7pEu4eNlyLwbKdhg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], @@ -850,7 +922,7 @@ "@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="], - "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="], + "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="], "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], @@ -860,6 +932,8 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.45.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/type-utils": "8.45.0", "@typescript-eslint/utils": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.45.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ=="], @@ -1084,7 +1158,7 @@ "bullmq": ["bullmq@5.58.7", "", { "dependencies": { "cron-parser": "^4.9.0", "ioredis": "^5.4.1", "msgpackr": "^1.11.2", "node-abort-controller": "^3.1.1", "semver": "^7.5.4", "tslib": "^2.0.0", "uuid": "^11.1.0" } }, "sha512-rqsKV/ip76wU90q7Cxpr1vS/6PYIVbhuzqr3wgILgjS6XbsnJtWyYrK23jqWHs9+m6/NXM4+62hyf8CSBpufAw=="], - "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], + "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -1216,6 +1290,32 @@ "culori": ["culori@4.0.2", "", {}, "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw=="], + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-format": ["d3-format@1.4.5", "", {}, "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@3.0.0", "", { "dependencies": { "d3-time": "1 - 2" } }, "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], "dax-sh": ["dax-sh@0.43.2", "", { "dependencies": { "@deno/shim-deno": "~0.19.0", "undici-types": "^5.26" } }, "sha512-uULa1sSIHgXKGCqJ/pA0zsnzbHlVnuq7g8O2fkHokWFNwEGIhh5lAJlxZa1POG5En5ba7AU4KcBAvGQWMMf8rg=="], @@ -1226,6 +1326,8 @@ "decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], @@ -1246,6 +1348,8 @@ "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], @@ -1332,6 +1436,8 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es-toolkit": ["es-toolkit@1.39.10", "", {}, "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], @@ -1590,6 +1696,8 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immer": ["immer@10.1.3", "", {}, "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], @@ -1600,6 +1708,8 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "ioredis": ["ioredis@5.7.0", "", { "dependencies": { "@ioredis/commands": "^1.3.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g=="], "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], @@ -2140,10 +2250,16 @@ "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], + "react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="], + + "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], + "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], "react-router": ["react-router@7.9.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g=="], + "react-virtualized-auto-sizer": ["react-virtualized-auto-sizer@1.0.26", "", { "peerDependencies": { "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A=="], + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -2154,6 +2270,8 @@ "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + "recharts": ["recharts@3.2.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw=="], + "recrawl-sync": ["recrawl-sync@2.2.3", "", { "dependencies": { "@cush/relative": "^1.0.0", "glob-regex": "^0.3.0", "slash": "^3.0.0", "sucrase": "^3.20.3", "tslib": "^1.9.3" } }, "sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ=="], "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], @@ -2162,6 +2280,10 @@ "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + + "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="], + "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], @@ -2190,6 +2312,8 @@ "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -2212,6 +2336,8 @@ "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + "rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="], "rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.3", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw=="], @@ -2454,7 +2580,7 @@ "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.45.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.45.0", "@typescript-eslint/parser": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/utils": "8.45.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg=="], @@ -2532,6 +2658,8 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "use-debounce": ["use-debounce@10.0.6", "", { "peerDependencies": { "react": "*" } }, "sha512-C5OtPyhAZgVoteO9heXMTdW7v/IbFI+8bSVKYCJrSmiWWCLsbUxiBSp4t9v0hNBTGY97bT72ydDIDyGSFWfwXg=="], + "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -2556,6 +2684,8 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], + "vinxi": ["vinxi@0.5.8", "", { "dependencies": { "@babel/core": "^7.22.11", "@babel/plugin-syntax-jsx": "^7.22.5", "@babel/plugin-syntax-typescript": "^7.22.5", "@types/micromatch": "^4.0.2", "@vinxi/listhen": "^1.5.6", "boxen": "^8.0.1", "chokidar": "^4.0.3", "citty": "^0.1.6", "consola": "^3.4.2", "crossws": "^0.3.4", "dax-sh": "^0.43.0", "defu": "^6.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "get-port-please": "^3.1.2", "h3": "1.15.3", "hookable": "^5.5.3", "http-proxy": "^1.18.1", "micromatch": "^4.0.8", "nitropack": "^2.11.10", "node-fetch-native": "^1.6.6", "path-to-regexp": "^6.2.1", "pathe": "^1.1.1", "radix3": "^1.1.2", "resolve": "^1.22.10", "serve-placeholder": "^2.0.1", "serve-static": "^1.15.0", "tinyglobby": "^0.2.14", "ufo": "^1.6.1", "unctx": "^2.4.1", "unenv": "^1.10.0", "unstorage": "^1.16.0", "vite": "^6.3.3", "zod": "^3.24.3" }, "bin": { "vinxi": "bin/cli.mjs" } }, "sha512-1pGA+cU1G9feBQ1sd5FMftPuLUT8NSX880AvELhNWqoqWhe2jeSOQxjDPxlA3f1AC+Bbknl4UPKHyVXmfLZQjw=="], "vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="], @@ -2686,6 +2816,10 @@ "@koshnic/ratelimit/chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="], + "@nivo/scales/@types/d3-time-format": ["@types/d3-time-format@3.0.4", "", {}, "sha512-or9DiDnYI1h38J9hxKEsw513+KVuFbEVhl7qdxcaudoiqWWepapUen+2vAriFGexr6W5+P4l9+HJrB39GG+oRg=="], + + "@nivo/scales/d3-time": ["d3-time@1.1.0", "", {}, "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="], + "@npmcli/git/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], "@npmcli/git/which": ["which@3.0.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg=="], @@ -2734,6 +2868,10 @@ "@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + "@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], + + "@types/d3-scale/@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -2782,6 +2920,8 @@ "boxen/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "bun-types/@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="], + "c12/dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="], "c12/jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="], @@ -2800,6 +2940,8 @@ "crc32-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "d3-time-format/d3-time": ["d3-time@2.1.1", "", { "dependencies": { "d3-array": "2" } }, "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ=="], + "dax-sh/undici-types": ["undici-types@5.28.4", "", {}, "sha512-3OeMF5Lyowe8VW0skf5qaIE7Or3yS9LS7fvMUI0gg4YxpIBVg0L8BxCmROw2CcYhSkpR68Epz7CGc8MPj94Uww=="], "dot-prop/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], @@ -2892,10 +3034,6 @@ "plaette/@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="], - "plaette/@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="], - - "plaette/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "plaette/vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], @@ -2938,6 +3076,8 @@ "untyped/jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="], + "victory-vendor/@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + "vinxi/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], "vinxi/serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="], @@ -3092,12 +3232,16 @@ "boxen/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "bun-types/@types/node/undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="], + "color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "d3-time-format/d3-time/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + "eslint/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], @@ -3186,6 +3330,8 @@ "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "d3-time-format/d3-time/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + "mocha/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "mocha/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], diff --git a/packages/core/db/index.ts b/packages/core/db/index.ts index b38228a..c0021aa 100644 --- a/packages/core/db/index.ts +++ b/packages/core/db/index.ts @@ -1 +1 @@ -export * from "./snapshots" \ No newline at end of file +export * from "./snapshots"; diff --git a/packages/core/db/snapshots/getClosetSnapshot.ts b/packages/core/db/snapshots/getClosetSnapshot.ts index ba9016b..172ef0c 100644 --- a/packages/core/db/snapshots/getClosetSnapshot.ts +++ b/packages/core/db/snapshots/getClosetSnapshot.ts @@ -3,28 +3,28 @@ import { sql } from "drizzle-orm"; export const getClosestSnapshot = async (aid: number, targetTime: Date) => { const closest = await dbMain.execute<{ created_at: Date; views: number }>(sql` - SELECT created_at, views - FROM ( - (SELECT created_at, views, 'later' AS type - FROM video_snapshot - WHERE aid = ${aid} - AND created_at >= ${targetTime.toISOString()} - ORDER BY created_at - LIMIT 1) - UNION ALL - (SELECT created_at, views, 'earlier' AS type - FROM video_snapshot - WHERE aid = ${aid} - AND created_at <= ${targetTime.toISOString()} - ORDER BY created_at DESC - LIMIT 1) - ) AS combined - ORDER BY - CASE - WHEN created_at >= ${targetTime.toISOString()} THEN created_at -${targetTime.toISOString()} - ELSE ${targetTime.toISOString()} - created_at - END - LIMIT 1; - `); + SELECT created_at, views + FROM ( + (SELECT created_at, views, 'later' AS type + FROM video_snapshot + WHERE aid = ${aid} + AND created_at >= ${targetTime.toISOString()} + ORDER BY created_at + LIMIT 1) + UNION ALL + (SELECT created_at, views, 'earlier' AS type + FROM video_snapshot + WHERE aid = ${aid} + AND created_at <= ${targetTime.toISOString()} + ORDER BY created_at DESC + LIMIT 1) + ) AS combined + ORDER BY + CASE + WHEN created_at >= ${targetTime.toISOString()} THEN created_at -${targetTime.toISOString()} + ELSE ${targetTime.toISOString()} - created_at + END + LIMIT 1; + `); return closest[0] || null; }; diff --git a/packages/core/db/snapshots/getLatestSnapshot.ts b/packages/core/db/snapshots/getLatestSnapshot.ts index a4e4253..0036db9 100644 --- a/packages/core/db/snapshots/getLatestSnapshot.ts +++ b/packages/core/db/snapshots/getLatestSnapshot.ts @@ -2,10 +2,10 @@ import { dbMain } from "@core/drizzle"; import { latestVideoSnapshot } from "@core/drizzle/main/schema"; import { eq } from "drizzle-orm"; -export const getLatestSnapshot = async (aid: number) =>{ +export const getLatestSnapshot = async (aid: number) => { const result = await dbMain.select().from(latestVideoSnapshot).where(eq(latestVideoSnapshot.aid, aid)).limit(1); if (result.length === 0) { return null; } return result[0]; -} \ No newline at end of file +}; diff --git a/packages/core/db/snapshots/index.ts b/packages/core/db/snapshots/index.ts index 60906da..8c3e684 100644 --- a/packages/core/db/snapshots/index.ts +++ b/packages/core/db/snapshots/index.ts @@ -1,2 +1,3 @@ export * from "./getLatestSnapshot"; -export * from "./getClosetSnapshot"; \ No newline at end of file +export * from "./getClosetSnapshot"; +export * from "./milestone"; diff --git a/packages/core/db/snapshots/milestone.ts b/packages/core/db/snapshots/milestone.ts index 5178860..c6b6422 100644 --- a/packages/core/db/snapshots/milestone.ts +++ b/packages/core/db/snapshots/milestone.ts @@ -1,30 +1,28 @@ -import { MINUTE, HOUR, getClosetMilestone, getMileStoneETAfactor, truncate } from "@core/lib"; +import { MINUTE, HOUR, getClosetMilestone } from "@core/lib"; import { getLatestSnapshot, getClosestSnapshot } from "@core/db"; -export const getShortTermETA = async (aid: number) => { +export const getShortTermETA = async (aid: number, targetViews?: number): Promise => { const DELTA = 1e-5; let minETAHours = Infinity; const timeIntervals = [20 * MINUTE, HOUR, 3 * HOUR, 6 * HOUR, 24 * HOUR, 72 * HOUR, 168 * HOUR]; const currentTimestamp = new Date().getTime(); const latestSnapshot = await getLatestSnapshot(aid); + const latestSnapshotTime = new Date(latestSnapshot.time).getTime(); for (const timeInterval of timeIntervals) { const date = new Date(currentTimestamp - timeInterval); const snapshot = await getClosestSnapshot(aid, date); if (!snapshot) continue; - const latestSnapshotTime = new Date(latestSnapshot.time).getTime(); const currentSnapshotTime = new Date(snapshot.created_at).getTime(); const hoursDiff = (latestSnapshotTime - currentSnapshotTime) / HOUR; const viewsDiff = latestSnapshot.views - snapshot.views; if (viewsDiff <= 0) continue; const speed = viewsDiff / (hoursDiff + DELTA); - const target = getClosetMilestone(latestSnapshot.views); + const target = targetViews || getClosetMilestone(latestSnapshot.views); const viewsToIncrease = target - latestSnapshot.views; const eta = viewsToIncrease / (speed + DELTA); - let factor = getMileStoneETAfactor(viewsToIncrease); - factor = truncate(factor, 4.5, 100); - const adjustedETA = eta / factor; - if (adjustedETA < minETAHours) { - minETAHours = adjustedETA; + if (eta < minETAHours) { + minETAHours = eta; } } + return minETAHours; }; diff --git a/packages/core/drizzle/main/schema.ts b/packages/core/drizzle/main/schema.ts index af0c099..41efdc1 100644 --- a/packages/core/drizzle/main/schema.ts +++ b/packages/core/drizzle/main/schema.ts @@ -1,22 +1,30 @@ import { pgTable, - uniqueIndex, index, - integer, + uniqueIndex, + bigserial, bigint, - varchar, text, timestamp, - smallint, - boolean, + integer, unique, serial, - bigserial, - uuid, + smallint, + boolean, + varchar, + jsonb, pgSequence } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm"; +export const viewsIncrementRateIdSeq = pgSequence("views_increment_rate_id_seq", { + startWith: "1", + increment: "1", + minValue: "1", + maxValue: "9223372036854775807", + cache: "1", + cycle: false +}); export const allDataIdSeq = pgSequence("all_data_id_seq", { startWith: "1", increment: "1", @@ -49,13 +57,161 @@ export const videoSnapshotIdSeq = pgSequence("video_snapshot_id_seq", { cache: "1", cycle: false }); -export const viewsIncrementRateIdSeq = pgSequence("views_increment_rate_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "9223372036854775807", - cache: "1", - cycle: false + +export const snapshotSchedule = pgTable( + "snapshot_schedule", + { + id: bigserial({ mode: "bigint" }).notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + type: text(), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + startedAt: timestamp("started_at", { withTimezone: true, mode: "string" }), + finishedAt: timestamp("finished_at", { withTimezone: true, mode: "string" }), + status: text().default("pending").notNull() + }, + (table) => [ + index("idx_snapshot_schedule_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_snapshot_schedule_started_at").using( + "btree", + table.startedAt.asc().nullsLast().op("timestamptz_ops") + ), + index("idx_snapshot_schedule_status").using("btree", table.status.asc().nullsLast().op("text_ops")), + index("idx_snapshot_schedule_type").using("btree", table.type.asc().nullsLast().op("text_ops")), + uniqueIndex("snapshot_schedule_pkey").using("btree", table.id.asc().nullsLast().op("int8_ops")) + ] +); + +export const videoSnapshot = pgTable( + "video_snapshot", + { + id: integer() + .default(sql`nextval('video_snapshot_id_seq'::regclass)`) + .notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + views: integer().notNull(), + coins: integer().notNull(), + likes: integer().notNull(), + favorites: integer().notNull(), + shares: integer().notNull(), + danmakus: integer().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + replies: integer().notNull() + }, + (table) => [ + index("idx_vid_snapshot_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_vid_snapshot_aid_created_at").using( + "btree", + table.aid.asc().nullsLast().op("int8_ops"), + table.createdAt.asc().nullsLast().op("int8_ops") + ), + index("idx_vid_snapshot_time").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_vid_snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")), + uniqueIndex("video_snapshot_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")) + ] +); + +export const bilibiliUser = pgTable( + "bilibili_user", + { + id: serial().primaryKey().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + uid: bigint({ mode: "number" }).notNull(), + username: text().notNull(), + desc: text().notNull(), + fans: integer().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull() + }, + (table) => [ + index("idx_bili-user_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), + unique("unq_bili-user_uid").on(table.uid) + ] +); + +export const relations = pgTable( + "relations", + { + id: serial().primaryKey().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + sourceId: bigint("source_id", { mode: "number" }).notNull(), + sourceType: text("source_type").notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + targetId: bigint("target_id", { mode: "number" }).notNull(), + targetType: text("target_type").notNull(), + relation: text().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull() + }, + (table) => [ + index("idx_relations_source_id_source_type_relation").using( + "btree", + table.sourceId.asc().nullsLast().op("int8_ops"), + table.sourceType.asc().nullsLast().op("int8_ops"), + table.relation.asc().nullsLast().op("text_ops") + ), + index("idx_relations_target_id_target_type_relation").using( + "btree", + table.targetId.asc().nullsLast().op("text_ops"), + table.targetType.asc().nullsLast().op("text_ops"), + table.relation.asc().nullsLast().op("text_ops") + ), + unique("unq_relations").on(table.sourceId, table.sourceType, table.targetId, table.targetType, table.relation) + ] +); + +export const songs = pgTable( + "songs", + { + id: integer() + .default(sql`nextval('songs_id_seq'::regclass)`) + .notNull(), + name: text(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }), + publishedAt: timestamp("published_at", { withTimezone: true, mode: "string" }), + duration: integer(), + type: smallint(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + neteaseId: bigint("netease_id", { mode: "number" }), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + deleted: boolean().default(false).notNull(), + image: text(), + producer: text() + }, + (table) => [ + index("idx_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_hash_songs_aid").using("hash", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")), + index("idx_published_at").using("btree", table.publishedAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_type").using("btree", table.type.asc().nullsLast().op("int2_ops")), + uniqueIndex("songs_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), + uniqueIndex("unq_songs_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + uniqueIndex("unq_songs_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")) + ] +); + +export const singer = pgTable("singer", { + id: serial().primaryKey().notNull(), + name: text().notNull() }); export const bilibiliMetadata = pgTable( @@ -88,6 +244,39 @@ export const bilibiliMetadata = pgTable( ] ); +export const humanClassifiedLables = pgTable( + "human_classified_lables", + { + id: serial().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + uid: integer().notNull(), + label: smallint().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull() + }, + (table) => [ + index("idx_classified-labels-human_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_classified-labels-human_author").using("btree", table.uid.asc().nullsLast().op("int4_ops")), + index("idx_classified-labels-human_created-at").using( + "btree", + table.createdAt.asc().nullsLast().op("timestamptz_ops") + ), + index("idx_classified-labels-human_label").using("btree", table.label.asc().nullsLast().op("int2_ops")) + ] +); + +export const history = pgTable("history", { + id: serial().primaryKey().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + objectId: bigint("object_id", { mode: "number" }).notNull(), + changeType: text("change_type").notNull(), + changedAt: timestamp("changed_at", { withTimezone: true, mode: "string" }).notNull(), + changedBy: integer("changed_by").notNull(), + data: jsonb().notNull() +}); + export const labellingResult = pgTable( "labelling_result", { @@ -143,187 +332,3 @@ export const latestVideoSnapshot = pgTable( index("idx_latest-video-snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")) ] ); - -export const videoSnapshot = pgTable( - "video_snapshot", - { - id: integer() - .default(sql`nextval('video_snapshot_id_seq'::regclass)`) - .notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - views: integer().notNull(), - coins: integer().notNull(), - likes: integer().notNull(), - favorites: integer().notNull(), - shares: integer().notNull(), - danmakus: integer().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - replies: integer().notNull() - }, - (table) => [ - index("idx_vid_snapshot_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_vid_snapshot_aid_created_at").using( - "btree", - table.aid.asc().nullsLast().op("timestamptz_ops"), - table.createdAt.asc().nullsLast().op("timestamptz_ops") - ), - index("idx_vid_snapshot_time").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")), - index("idx_vid_snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")), - uniqueIndex("video_snapshot_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")) - ] -); - -export const songs = pgTable( - "songs", - { - id: integer() - .default(sql`nextval('songs_id_seq'::regclass)`) - .notNull(), - name: text(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }), - publishedAt: timestamp("published_at", { withTimezone: true, mode: "string" }), - duration: integer(), - type: smallint(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - neteaseId: bigint("netease_id", { mode: "number" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deleted: boolean().default(false).notNull(), - image: text(), - producer: text() - }, - (table) => [ - index("idx_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_hash_songs_aid").using("hash", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")), - index("idx_published_at").using("btree", table.publishedAt.asc().nullsLast().op("timestamptz_ops")), - index("idx_type").using("btree", table.type.asc().nullsLast().op("int2_ops")), - uniqueIndex("songs_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), - uniqueIndex("unq_songs_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - uniqueIndex("unq_songs_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")) - ] -); - -export const bilibiliUser = pgTable( - "bilibili_user", - { - id: serial().primaryKey().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - uid: bigint({ mode: "number" }).notNull(), - username: text().notNull(), - desc: text().notNull(), - fans: integer().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_bili-user_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), - unique("unq_bili-user_uid").on(table.uid) - ] -); - -export const singer = pgTable("singer", { - id: serial().primaryKey().notNull(), - name: text().notNull() -}); - -export const relations = pgTable( - "relations", - { - id: serial().primaryKey().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - sourceId: bigint("source_id", { mode: "number" }).notNull(), - sourceType: text("source_type").notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - targetId: bigint("target_id", { mode: "number" }).notNull(), - targetType: text("target_type").notNull(), - relation: text().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_relations_source_id_source_type_relation").using( - "btree", - table.sourceId.asc().nullsLast().op("int8_ops"), - table.sourceType.asc().nullsLast().op("int8_ops"), - table.relation.asc().nullsLast().op("text_ops") - ), - index("idx_relations_target_id_target_type_relation").using( - "btree", - table.targetId.asc().nullsLast().op("text_ops"), - table.targetType.asc().nullsLast().op("text_ops"), - table.relation.asc().nullsLast().op("text_ops") - ), - unique("unq_relations").on(table.sourceId, table.sourceType, table.targetId, table.targetType, table.relation) - ] -); - -export const globalKv = pgTable("global_kv", { - key: text().primaryKey().notNull(), - value: text().notNull() -}); - -export const snapshotSchedule = pgTable( - "snapshot_schedule", - { - id: bigserial({ mode: "bigint" }).notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - type: text(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - startedAt: timestamp("started_at", { withTimezone: true, mode: "string" }), - finishedAt: timestamp("finished_at", { withTimezone: true, mode: "string" }), - status: text().default("pending").notNull() - }, - (table) => [ - index("idx_snapshot_schedule_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_snapshot_schedule_started_at").using( - "btree", - table.startedAt.asc().nullsLast().op("timestamptz_ops") - ), - index("idx_snapshot_schedule_status").using("btree", table.status.asc().nullsLast().op("text_ops")), - index("idx_snapshot_schedule_type").using("btree", table.type.asc().nullsLast().op("text_ops")), - uniqueIndex("snapshot_schedule_pkey").using("btree", table.id.asc().nullsLast().op("int8_ops")) - ] -); - -export const classifiedLabelsHuman = pgTable( - "classified_labels_human", - { - id: serial().primaryKey().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - author: uuid().notNull(), - label: smallint().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_classified-labels-human_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_classified-labels-human_author").using("btree", table.author.asc().nullsLast().op("uuid_ops")), - index("idx_classified-labels-human_created-at").using( - "btree", - table.createdAt.asc().nullsLast().op("timestamptz_ops") - ), - index("idx_classified-labels-human_label").using("btree", table.label.asc().nullsLast().op("int2_ops")) - ] -); diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 94c669b..8354600 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -9,6 +9,7 @@ "dependencies": { "@elysiajs/cors": "^1.4.0", "@elysiajs/openapi": "^1.4.0", + "@elysiajs/server-timing": "^1.4.0", "chalk": "^5.6.2", "elysia": "^1.4.0", "zod": "^4.1.11" diff --git a/packages/elysia/routes/song/milestone.ts b/packages/elysia/routes/song/milestone.ts index f944fcb..5390217 100644 --- a/packages/elysia/routes/song/milestone.ts +++ b/packages/elysia/routes/song/milestone.ts @@ -2,13 +2,14 @@ import { Elysia, t } from "elysia"; import { dbMain } from "@core/drizzle"; import { bilibiliMetadata, latestVideoSnapshot } from "@core/drizzle/main/schema"; import { eq, and, gte, lt, desc } from "drizzle-orm"; +import { getShortTermETA } from "@core/db"; type MileStoneType = "dendou" | "densetsu" | "shinwa"; const range = { - dendou: [90000, 99999], - densetsu: [900000, 999999], - shinwa: [5000000, 9999999] + dendou: [90000, 99999, 100000], + densetsu: [900000, 999999, 1000000], + shinwa: [5000000, 9999999, 10000000] }; export const closeMileStoneHandler = new Elysia({ prefix: "/song" }).get( @@ -23,12 +24,21 @@ export const closeMileStoneHandler = new Elysia({ prefix: "/song" }).get( .innerJoin(latestVideoSnapshot, eq(latestVideoSnapshot.aid, bilibiliMetadata.aid)) .where(and(gte(latestVideoSnapshot.views, min), lt(latestVideoSnapshot.views, max))) .orderBy(desc(latestVideoSnapshot.views)); - const aids = data.map((song) => song.bilibili_metadata.aid); - for (const aid of aids) { - - } - - return data; + type Row = (typeof data)[number]; + type Result = Row & { + eta: number; + }; + const result: Result[] = []; + for (let i = 0; i < data.length; i++) { + const aid = data[i].bilibili_metadata.aid; + const eta = await getShortTermETA(aid, range[type as MileStoneType][2]); + result.push({ + ...data[i], + eta + }); + } + result.sort((a, b) => a.eta - b.eta); + return result; }, { response: { diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts index b855b71..cfab212 100644 --- a/packages/elysia/src/index.ts +++ b/packages/elysia/src/index.ts @@ -6,6 +6,8 @@ import { cors } from "@elysiajs/cors"; import { getSongInfoHandler } from "@elysia/routes/song/info"; import { rootHandler } from "@elysia/routes/root"; import { getVideoMetadataHandler } from "@elysia/routes/video/metadata"; +import { closeMileStoneHandler } from "@elysia/routes/song/milestone"; +import { serverTiming } from '@elysiajs/server-timing' const [host, port] = getBindingInfo(); logStartup(host, port); @@ -15,12 +17,14 @@ const app = new Elysia({ hostname: host } }) +.use(serverTiming()) .use(cors()) .use(openapi()) .use(rootHandler) .use(pingHandler) .use(getVideoMetadataHandler) .use(getSongInfoHandler) + .use(closeMileStoneHandler) .listen(15412); export const VERSION = "0.7.0"; diff --git a/packages/palette/src/components/Picker/Slider.tsx b/packages/palette/src/components/Picker/Slider.tsx index c559038..21c76f4 100644 --- a/packages/palette/src/components/Picker/Slider.tsx +++ b/packages/palette/src/components/Picker/Slider.tsx @@ -71,6 +71,7 @@ export const Slider = ({ useP3, channel, color, onChange, i18nProvider }: Slider }; const handleTouchMove = (e: React.TouchEvent) => { + e.preventDefault(); const touch = e.touches[0]; if (touch) { const newValue = getValueFromPosition(touch.clientX); diff --git a/packages/temp_frontend/app/components/Chart.tsx b/packages/temp_frontend/app/components/Chart.tsx new file mode 100644 index 0000000..259dd8c --- /dev/null +++ b/packages/temp_frontend/app/components/Chart.tsx @@ -0,0 +1,480 @@ +import { useState, useRef, useMemo, useCallback, useEffect } from "react"; + +export const TimeSeriesChart = ({ + data = [], + width = "100%", + height = 300, + accentColor = "#007AFF", + showGrid = true, + smoothInterpolation = true, + timeRange = "auto", // '6h', '1d', '7d', '30d', 'auto' +}: { + data: { timestamp: number; value: number }[]; + width?: string; + height?: number; + accentColor?: string; + showGrid?: boolean; + smoothInterpolation?: boolean; + timeRange?: "6h" | "1d" | "7d" | "30d" | "auto"; +}) => { + const svgRef = useRef(null); + const containerRef = useRef(null); + const [currentPosition, setCurrentPosition] = useState<{ + x: number; + y: number; + data: { timestamp: number; value: number } | null; + } | null>(null); + const [isDragging, setIsDragging] = useState(false); + const [dragStartX, setDragStartX] = useState(0); + const [viewBox, setViewBox] = useState({ start: 0, end: 1 }); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + const [isTouchActive, setIsTouchActive] = useState(false); + const [longPressTimer, setLongPressTimer] = useState(null); + const [touchStartPosition, setTouchStartPosition] = useState<{ x: number; y: number } | null>(null); + + // 响应式尺寸处理 + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + const { width: containerWidth, height: containerHeight } = containerRef.current.getBoundingClientRect(); + setDimensions({ width: containerWidth, height: containerHeight }); + } + }; + + updateDimensions(); + window.addEventListener("resize", updateDimensions); + return () => window.removeEventListener("resize", updateDimensions); + }, []); + + // 格式化时间标签 + const formatTimeLabel = useCallback((timestamp: number, range: "6h" | "1d" | "7d" | "30d" | "auto") => { + const date = new Date(timestamp); + const now = new Date(); + const isToday = date.toDateString() === now.toDateString(); + + switch (range) { + case "6h": + return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + case "1d": + return isToday ? "今天" : date.toLocaleDateString([], { month: "short", day: "numeric" }); + case "7d": + return date.toLocaleDateString([], { weekday: "short" }); + case "30d": + return date.toLocaleDateString([], { month: "short", day: "numeric" }); + default: + return date.toLocaleDateString([], { month: "short", day: "numeric" }); + } + }, []); + + // 处理数据范围和时间间隔 + const { xScale, yScale, visibleData, timeTicks, yTicks } = useMemo(() => { + if (!data.length || !dimensions.width) return {}; + + const visibleDataPoints = data.filter((point, index) => { + const progress = index / (data.length - 1); + return progress >= viewBox.start && progress <= viewBox.end; + }); + + if (!visibleDataPoints.length) return {}; + + // 计算Y轴范围(带一些边距) + const values = visibleDataPoints.map((d) => d.value); + const minValue = Math.min(...values); + const maxValue = Math.max(...values); + const valueRange = maxValue - minValue; + const padding = valueRange * 0.1; + + const yScale = (value: number) => { + const chartHeight = dimensions.height - 60; // 为标签留出空间 + return chartHeight - ((value - (minValue - padding)) / (maxValue - minValue + 2 * padding)) * chartHeight + 20; + }; + + // X轴比例尺 + const xScale = (index: number) => { + const totalPoints = visibleDataPoints.length; + return (index / (totalPoints - 1)) * (dimensions.width - 60) + 40; + }; + + // 生成时间刻度 + const generateTimeTicks = () => { + const tickCount = Math.min(6, Math.floor(dimensions.width / 80)); + const ticks = []; + + for (let i = 0; i < tickCount; i++) { + const dataIndex = Math.floor((i / (tickCount - 1)) * (visibleDataPoints.length - 1)); + if (visibleDataPoints[dataIndex]) { + ticks.push({ + x: xScale(dataIndex), + timestamp: visibleDataPoints[dataIndex].timestamp, + label: formatTimeLabel(visibleDataPoints[dataIndex].timestamp, timeRange), + }); + } + } + return ticks; + }; + + // 生成Y轴刻度 + const generateYTicks = () => { + const tickCount = 4; + const ticks = []; + + for (let i = 0; i <= tickCount; i++) { + const value = minValue - padding + (maxValue + padding - (minValue - padding)) * (i / tickCount); + const y = yScale(value); + ticks.push({ + y, + value: Math.round(value * 100) / 100, // 保留两位小数 + }); + } + return ticks; + }; + + return { + xScale, + yScale, + visibleData: visibleDataPoints, + timeTicks: generateTimeTicks(), + yTicks: generateYTicks(), + }; + }, [data, dimensions, viewBox, timeRange]); + + // 生成平滑路径 + const generatePath = useCallback(() => { + if (!visibleData || !xScale || !yScale) return ""; + + if (!smoothInterpolation || visibleData.length < 3) { + // 直线连接 + return visibleData + .map((point, index) => `${index === 0 ? "M" : "L"} ${xScale(index)} ${yScale(point.value)}`) + .join(" "); + } + + // Catmull-Rom 平滑曲线 + const points = visibleData.map((point, index) => ({ + x: xScale(index), + y: yScale(point.value), + })); + + let path = `M ${points[0].x} ${points[0].y}`; + + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[Math.max(0, i - 1)]; + const p1 = points[i]; + const p2 = points[i + 1]; + const p3 = points[Math.min(points.length - 1, i + 2)]; + + const tension = 0.5; + const x1 = p1.x + ((p2.x - p0.x) / 6) * tension; + const y1 = p1.y + ((p2.y - p0.y) / 6) * tension; + const x2 = p2.x - ((p3.x - p1.x) / 6) * tension; + const y2 = p2.y - ((p3.y - p1.y) / 6) * tension; + + path += ` C ${x1} ${y1} ${x2} ${y2} ${p2.x} ${p2.y}`; + } + + return path; + }, [visibleData, xScale, yScale, smoothInterpolation]); + + // 更新光标位置和对应数据点 + const updateCursorPosition = useCallback( + (x: number) => { + if (!visibleData || !xScale) return; + + // 找到最近的数据点 + let closestIndex = 0; + let minDistance = Infinity; + + visibleData.forEach((point, index) => { + const pointX = xScale(index); + const distance = Math.abs(pointX - x); + if (distance < minDistance) { + minDistance = distance; + closestIndex = index; + } + }); + + if (minDistance < 50) { + // 灵敏度阈值 + const point = visibleData[closestIndex]; + setCurrentPosition({ + x: xScale(closestIndex), + y: yScale(point.value), + data: point, + }); + } else { + setCurrentPosition(null); + } + }, + [visibleData, xScale, yScale], + ); + + // 鼠标事件处理 + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + console.log("mouse down"); + if (!svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + + setIsDragging(true); + setDragStartX(x); + updateCursorPosition(x); + }, + [updateCursorPosition], + ); + + const handleMouseMove = useCallback( + (e: React.MouseEvent) => { + console.log("mouse move"); + if (!svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + + if (isDragging) { + // 滚动逻辑 + const deltaX = x - dragStartX; + if (Math.abs(deltaX) > 10) { + const dragSpeed = 0.02; + const newStart = Math.max(0, viewBox.start - deltaX * dragSpeed); + const newEnd = Math.min(1, viewBox.end - deltaX * dragSpeed); + + if (newEnd - newStart === viewBox.end - viewBox.start) { + setViewBox({ start: newStart, end: newEnd }); + } + setDragStartX(x); + } else { + // 光标位置更新 + updateCursorPosition(x); + } + } else { + // 悬停时更新光标位置 + updateCursorPosition(x); + } + }, + [isDragging, dragStartX, viewBox, updateCursorPosition], + ); + + const handleMouseUp = useCallback(() => { + console.log("mouse up"); + setIsDragging(false); + }, []); + + const handleMouseLeave = useCallback(() => { + console.log("mouse leave"); + setIsDragging(false); + setCurrentPosition(null); + }, []); + + // 触摸事件处理 + const handleTouchStart = useCallback( + (e: React.TouchEvent) => { + console.log("touch start"); + if (!svgRef.current) return; + + const touch = e.touches[0]; + const rect = svgRef.current.getBoundingClientRect(); + const x = touch.clientX - rect.left; + + setIsDragging(true); + setDragStartX(x); + updateCursorPosition(x); + }, + [updateCursorPosition], + ); + + const handleTouchMove = useCallback( + (e: React.TouchEvent) => { + console.log("touch move"); + if (!isDragging || !svgRef.current) return; + + const touch = e.touches[0]; + const rect = svgRef.current.getBoundingClientRect(); + const x = touch.clientX - rect.left; + + // 滚动逻辑 + const deltaX = x - dragStartX; + if (Math.abs(deltaX) > 10) { + const dragSpeed = 0.02; + const newStart = Math.max(0, viewBox.start - deltaX * dragSpeed); + const newEnd = Math.min(1, viewBox.end - deltaX * dragSpeed); + + if (newEnd - newStart === viewBox.end - viewBox.start) { + setViewBox({ start: newStart, end: newEnd }); + } + setDragStartX(x); + } else { + // 光标位置更新 + updateCursorPosition(x); + } + }, + [isDragging, dragStartX, viewBox, updateCursorPosition], + ); + + const handleTouchEnd = useCallback(() => { + console.log("touch end"); + setIsDragging(false); + setCurrentPosition(null); + }, []); + + + if (!data.length) { + return ( +
+
+ 暂无数据 +
+
+ ); + } + + return ( +
+ + + {/* Y轴刻度 */} + {yTicks && + yTicks.map((tick, index) => ( + + + + {tick.value.toLocaleString()} + + + ))} + + {/* X轴时间刻度 */} + {timeTicks && + timeTicks.map((tick, index) => ( + + + + {tick.label} + + + ))} + + {/* 折线路径 */} + + + {/* 当前光标指示线 */} + {currentPosition && ( + + {/* 垂直指示线 */} + + {/* 数据点 */} + + + )} + + + {/* 浮动数据标签 */} + {currentPosition && ( +
+
+
+ {currentPosition.data && + new Date(currentPosition.data.timestamp).toLocaleTimeString([], { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })} +
+ +
+ {currentPosition.data && currentPosition.data.value.toLocaleString()} +
+
+
+ )} +
+ ); +}; diff --git a/packages/temp_frontend/app/routes.ts b/packages/temp_frontend/app/routes.ts index da9a953..89eb556 100644 --- a/packages/temp_frontend/app/routes.ts +++ b/packages/temp_frontend/app/routes.ts @@ -1,3 +1,8 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes"; -export default [index("routes/home.tsx"), route("song/:id/info", "routes/song/[id]/info.tsx")] satisfies RouteConfig; +export default [ + index("routes/home.tsx"), + route("song/:id/info", "routes/song/[id]/info.tsx"), + route("song/:id/data", "routes/song/[id]/data.tsx"), + route("chart-demo", "routes/chartDemo.tsx"), +] satisfies RouteConfig; diff --git a/packages/temp_frontend/app/routes/chartDemo.tsx b/packages/temp_frontend/app/routes/chartDemo.tsx new file mode 100644 index 0000000..f7efe33 --- /dev/null +++ b/packages/temp_frontend/app/routes/chartDemo.tsx @@ -0,0 +1,59 @@ +import { TimeSeriesChart } from "@/components/Chart"; +import { useEffect, useState } from "react"; +import useSWR from "swr"; + +const API_URL = "https://api.projectcvsa.com"; + +const App = () => { + const { data, error, isLoading } = useSWR(`${API_URL}/video/av285205499/snapshots`, async (url) => { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to fetch song info"); + } + return response.json(); + }); + + function generateSampleData() { + if (!data || data.length === 0) return []; + const d = []; + for (let i = data.length - 1; i >= 0; i--) { + d.push({ + timestamp: data[i].created_at, + value: data[i].views + }); + } + return d; + } + + const [chartData, setChartData] = useState(generateSampleData()); + + useEffect(() => { + if (data) { + setChartData(generateSampleData()); + } + }, [data]); + + + return ( +
+

健康数据趋势

+ + +
+ ); +}; + +export default App; diff --git a/packages/temp_frontend/package.json b/packages/temp_frontend/package.json index 4afa287..248b8d7 100644 --- a/packages/temp_frontend/package.json +++ b/packages/temp_frontend/package.json @@ -10,6 +10,8 @@ }, "dependencies": { "@elysiajs/eden": "^1.4.1", + "@nivo/core": "^0.99.0", + "@nivo/line": "^0.99.0", "@radix-ui/react-slot": "^1.2.3", "@react-router/node": "^7.7.1", "@react-router/serve": "^7.7.1", @@ -21,6 +23,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-router": "^7.7.1", + "recharts": "^3.2.1", "sonner": "^2.0.7", "swr": "^2.3.6", "tailwind-merge": "^3.3.1"