diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 3b8263d..502a273 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -66,8 +66,8 @@ jobs: - name: Test assembler run: go -C assembler test ./... - # Lint covers ESLint and svelte-check; the tests cover the framework-free - # modules, including the ASCII fold that has to match the Go parser. + # The tests cover the framework-free modules, including the ASCII fold + # that has to match the Go parser. - name: Test web working-directory: web run: npm test diff --git a/CLAUDE.md b/CLAUDE.md index a6b5962..e640750 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,8 @@ Add the web checks when frontend files changed: (cd web && npm test && npm run lint && npm run build) ``` -`npm run lint` is ESLint plus `svelte-check`, so it is the type check too. +`npm run lint` is ESLint. The web app is plain JavaScript — no TypeScript, no +type-check step. **Do not run the crawler suite as part of routine verification.** The crawler is not part of the build — `data//` is committed and a crawl only refreshes it @@ -44,8 +45,8 @@ hashes every real input file. That is the point of it; do not skip it. regenerated.** A mismatch is a reader bug until proven otherwise, never a cue to refresh the file. `parser/cmd/dumpcells` narrows a failure to the cell. - **`ToAscii` filters the literal range U+0300..U+036F, not `unicode.Mn`.** It - must match `toAscii` in `web/src/lib/to-ascii.ts`, or accent-insensitive search - silently misses rows. `to-ascii.test.ts` pins the pairs both sides must agree + must match `toAscii` in `web/src/lib/to-ascii.js`, or accent-insensitive search + silently misses rows. `to-ascii.test.js` pins the pairs both sides must agree on. - **The 2016 files use four different layouts, and detection is per sheet.** Two of them publish scores in one column per subject instead of a `DIEM_THI` diff --git a/README.md b/README.md index f578951..1fcbda7 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Each stage runs on its own and hands its output to the next through the stores. `datasets.json` is the contract between them. It is JSON because Go and the web app both read it and neither needs a dependency to do so; presentation stays in -`web/src/lib/datasets.ts`, keyed by id, which fails loudly if the two disagree. +`web/src/lib/datasets.js`, keyed by id, which fails loudly if the two disagree. The dataset id is one identifier end to end: @@ -86,7 +86,7 @@ Pushing to `main` runs the same steps in 2. Add `parser/configs/.yml` — sheet mode, column indices, validation guards. No SQL; the schema is canonical. 3. Add an entry to `datasets.json` with its expected row count and size -4. Add the matching presentation to `CONTENT` in `web/src/lib/datasets.ts` +4. Add the matching presentation to `CONTENT` in `web/src/lib/datasets.js` Everything else follows: the assembler, the router and the hub all read the registry, and the UI adapts to whichever columns the dataset fills. Steps 3 and 4 diff --git a/docs/data-pipeline.md b/docs/data-pipeline.md index 1bb9567..518df28 100644 --- a/docs/data-pipeline.md +++ b/docs/data-pipeline.md @@ -183,7 +183,7 @@ silently drops 13,720 students** (Hanoi +7,275, HCM +6,445). That is what The databases are written with 1 KiB pages (`PRAGMA page_size` in `parser/internal/writer/writer.go`) because the browser fetches them a page per -HTTP request. `CHUNK_BYTES` in `web/src/lib/sqlite.svelte.ts` must match. +HTTP request. `CHUNK_BYTES` in `web/src/lib/sqlite.svelte.js` must match. | id | Source rows | Skipped | DB rows | | --- | --- | --- | --- | diff --git a/docs/system-architecture.md b/docs/system-architecture.md index 850804a..fd6d5e3 100644 --- a/docs/system-architecture.md +++ b/docs/system-architecture.md @@ -46,7 +46,7 @@ because the assembler is a Go program and the web app is not, and JSON is the only format both parse without a dependency. Presentation — titles, labels, search examples, SQL presets — stays in -`web/src/lib/datasets.ts`, keyed by id. That file cross-checks the two: a registry +`web/src/lib/datasets.js`, keyed by id. That file cross-checks the two: a registry entry with no content, or content for a dataset that was never built, throws at module load rather than rendering a page with no title or a link to a database that does not exist. @@ -125,13 +125,13 @@ No component contains a per-dataset conditional. Two mechanisms do the work: self-exclude wherever those languages were not sat. Anything genuinely per-dataset — title, source, database size, search examples, -SQL presets — lives in `web/src/lib/datasets.ts`. +SQL presets — lives in `web/src/lib/datasets.js`. ## Exam ID formats -`web/src/lib/query-mode.ts` decides whether a query is an exam ID or a name, and +`web/src/lib/query-mode.js` decides whether a query is an exam ID or a name, and is shared by the dataset page and `search-form.svelte` (they previously held -separate copies and had drifted apart on exactly this rule). `query-mode.test.ts` +separate copies and had drifted apart on exactly this rule). `query-mode.test.js` covers every form in the table below. | Form | Example | Where | diff --git a/parser/README.md b/parser/README.md index 23f57e1..3fea335 100644 --- a/parser/README.md +++ b/parser/README.md @@ -41,7 +41,7 @@ reader independently verifiable against a hash oracle. - `ToAscii` strips combining marks in the literal range U+0300–U+036F rather than by Unicode category. That covers every Vietnamese diacritic and must - stay identical to `toAscii` in `web/src/lib/to-ascii.ts`, or accent-insensitive + stay identical to `toAscii` in `web/src/lib/to-ascii.js`, or accent-insensitive search misses rows. - Gender is normalised to `Nam`/`Nữ`; the Cần Thơ files write `0`/`1` instead and are translated. Anything else becomes NULL. diff --git a/web/eslint.config.js b/web/eslint.config.js index 2175062..6378a41 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,12 +1,10 @@ import js from "@eslint/js"; import svelte from "eslint-plugin-svelte"; import globals from "globals"; -import ts from "typescript-eslint"; import svelteConfig from "./svelte.config.js"; -export default ts.config( +export default [ js.configs.recommended, - ...ts.configs.recommended, ...svelte.configs.recommended, { languageOptions: { @@ -15,16 +13,11 @@ export default ts.config( }, { // Svelte files and rune modules are parsed by svelte-eslint-parser, which - // needs the TypeScript parser handed to it for `lang="ts"` blocks. - files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], + // needs the project's svelte.config.js to resolve aliases and runes. + files: ["**/*.svelte", "**/*.svelte.js"], languageOptions: { - parserOptions: { - parser: ts.parser, - projectService: true, - extraFileExtensions: [".svelte"], - svelteConfig, - }, + parserOptions: { svelteConfig }, }, }, { ignores: ["dist/", ".svelte-kit/", "node_modules/"] }, -); +]; diff --git a/web/tsconfig.json b/web/jsconfig.json similarity index 63% rename from web/tsconfig.json rename to web/jsconfig.json index 4344710..21df54b 100644 --- a/web/tsconfig.json +++ b/web/jsconfig.json @@ -1,14 +1,11 @@ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { - "allowJs": true, - "checkJs": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" + "sourceMap": true } } diff --git a/web/package-lock.json b/web/package-lock.json index 5ed552d..6f55121 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -16,15 +16,11 @@ "@sveltejs/kit": "^2.70.2", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.3.3", - "@types/sql.js": "^1.4.11", "eslint": "^9.39.4", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.4.0", "svelte": "^5.56.9", - "svelte-check": "^4.4.3", "tailwindcss": "^4.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.2", "vite": "^7.1.14", "vitest": "^4.1.10" } @@ -1187,16 +1183,6 @@ } } }, - "node_modules/@sveltejs/load-config": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.3.tgz", - "integrity": "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - } - }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", @@ -1818,13 +1804,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/emscripten": { - "version": "1.41.5", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", - "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.9", "dev": true, @@ -1835,27 +1814,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/sql.js": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.11.tgz", - "integrity": "sha512-QXIx38p2ZThJaK9vP5ZdqdlRe1FG9I8SmCZOS7FHfB/2qPAjZwkL7/vlfPg6N/oWHuuOaGg/P/IRwfP2W0kWVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/emscripten": "*", - "@types/node": "*" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1863,301 +1821,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", - "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/type-utils": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.67.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -2401,22 +2064,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3189,16 +2836,6 @@ "node": "*" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -3495,20 +3132,6 @@ "node": ">=6" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "dev": true, @@ -3563,19 +3186,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/set-cookie-parser": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", @@ -3705,31 +3315,6 @@ "node": ">=18" } }, - "node_modules/svelte-check": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.6.tgz", - "integrity": "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "@sveltejs/load-config": "^0.2.3", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.0.0 || ^6.0.0" - } - }, "node_modules/svelte-eslint-parser": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.8.0.tgz", @@ -3847,19 +3432,6 @@ "node": ">=6" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -3871,51 +3443,6 @@ "node": ">= 0.8.0" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", - "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.67.0", - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, "node_modules/uri-js": { "version": "4.4.1", "dev": true, diff --git a/web/package.json b/web/package.json index fbf0a85..646acc1 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,7 @@ "build": "vite build", "preview": "vite preview", "test": "vitest run", - "lint": "eslint . && svelte-check --tsconfig ./tsconfig.json" + "lint": "eslint ." }, "dependencies": { "sql.js-httpvfs": "^0.8.12" @@ -20,15 +20,11 @@ "@sveltejs/kit": "^2.70.2", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.3.3", - "@types/sql.js": "^1.4.11", "eslint": "^9.39.4", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.4.0", "svelte": "^5.56.9", - "svelte-check": "^4.4.3", "tailwindcss": "^4.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.2", "vite": "^7.1.14", "vitest": "^4.1.10" } diff --git a/web/src/lib/admission-blocks.ts b/web/src/lib/admission-blocks.js similarity index 88% rename from web/src/lib/admission-blocks.ts rename to web/src/lib/admission-blocks.js index 98f5142..b99c600 100644 --- a/web/src/lib/admission-blocks.ts +++ b/web/src/lib/admission-blocks.js @@ -1,15 +1,3 @@ -import type { Student, SubjectKey } from "./types"; - -export type AdmissionBlock = { code: string; subjects: SubjectKey[]; label: string }; -export type ComputedBlock = { - code: string; - label: string; - total: number; - parts: { key: SubjectKey; score: number }[]; -}; -export type TierKey = "common" | "uncommon" | "rare" | "epic" | "legendary" | "prismatic"; -export type Tier = { key: TierKey; symbol: string; label: string }; - // Vietnamese university admission-block ("khối thi") definitions, per Circular // 03/2017/TT-BGDĐT. Each block is the sum of exactly 3 subject scores. // @@ -17,7 +5,7 @@ export type Tier = { key: TierKey; symbol: string; label: string }; // skips any block with a missing subject score, so blocks needing GDCD // self-exclude on 2016 rows, and blocks needing a language the candidate did // not sit self-exclude row by row. -export const ADMISSION_BLOCKS: AdmissionBlock[] = [ +export const ADMISSION_BLOCKS = [ { code: "A00", subjects: ["toan", "vat_ly", "hoa_hoc"], label: "Toán + Lý + Hóa" }, { code: "A01", subjects: ["toan", "vat_ly", "tieng_anh"], label: "Toán + Lý + Anh" }, { code: "A02", subjects: ["toan", "vat_ly", "sinh_hoc"], label: "Toán + Lý + Sinh" }, @@ -76,14 +64,13 @@ export const ADMISSION_BLOCKS: AdmissionBlock[] = [ // Returns { code, label, total, parts:[{key,score}] } for every block where // the student has scores for all three subjects, sorted by total desc. -export function computeBlocks(student: Student): ComputedBlock[] { - const out: ComputedBlock[] = []; +export function computeBlocks(student) { + const out = []; for (const b of ADMISSION_BLOCKS) { const parts = b.subjects.map((k) => ({ key: k, score: student[k] })); if (parts.some((p) => p.score === null || p.score === undefined)) continue; - const whole = parts as { key: SubjectKey; score: number }[]; - const total = whole.reduce((s, p) => s + p.score, 0); - out.push({ code: b.code, label: b.label, total, parts: whole }); + const total = parts.reduce((s, p) => s + p.score, 0); + out.push({ code: b.code, label: b.label, total, parts }); } return out.sort((a, b) => b.total - a.total); } @@ -93,7 +80,7 @@ export function computeBlocks(student: Student): ComputedBlock[] { // whatever the other scores are, so that boundary is inclusive while the rest // are exclusive upper bounds. Every tier carries a unicode symbol as well as a // color, so the meaning is never conveyed by color alone. -export function scoreTier(score: number | null | undefined): Tier | null { +export function scoreTier(score) { if (score === null || score === undefined) return null; if (score <= 1) return { key: "common", symbol: "·", label: "Điểm liệt" }; if (score < 5) return { key: "uncommon", symbol: "○", label: "Chưa đạt" }; diff --git a/web/src/lib/admission-blocks.test.ts b/web/src/lib/admission-blocks.test.js similarity index 96% rename from web/src/lib/admission-blocks.test.ts rename to web/src/lib/admission-blocks.test.js index d78f189..8239e6e 100644 --- a/web/src/lib/admission-blocks.test.ts +++ b/web/src/lib/admission-blocks.test.js @@ -1,9 +1,8 @@ import { describe, expect, it } from "vitest"; import { ADMISSION_BLOCKS, computeBlocks, scoreTier } from "./admission-blocks"; -import type { Student } from "./types"; /** A row with every column NULL, so a test only states the scores it needs. */ -function student(scores: Partial): Student { +function student(scores) { return { so_bao_danh: "49008235", ho_ten: "Nguyễn Văn A", diff --git a/web/src/lib/components/custom-query.svelte b/web/src/lib/components/custom-query.svelte index d18df0b..dd0734c 100644 --- a/web/src/lib/components/custom-query.svelte +++ b/web/src/lib/components/custom-query.svelte @@ -1,29 +1,24 @@ - diff --git a/web/src/routes/[dataset]/+page.ts b/web/src/routes/[dataset]/+page.js similarity index 71% rename from web/src/routes/[dataset]/+page.ts rename to web/src/routes/[dataset]/+page.js index 055bd77..b9d5a57 100644 --- a/web/src/routes/[dataset]/+page.ts +++ b/web/src/routes/[dataset]/+page.js @@ -1,15 +1,14 @@ import { error } from "@sveltejs/kit"; import { DATASETS, datasetById } from "$lib/datasets"; -import type { EntryGenerator, PageLoad } from "./$types"; /** * One prerendered page per registered dataset. The registry is the repository * root's datasets.json, so a dataset the assembler builds is a page that exists, * and one it does not is a 404 at build time rather than a blank page. */ -export const entries: EntryGenerator = () => DATASETS.map((d) => ({ dataset: d.id })); +export const entries = () => DATASETS.map((d) => ({ dataset: d.id })); -export const load: PageLoad = ({ params }) => { +export const load = ({ params }) => { const dataset = datasetById(params.dataset); if (!dataset) throw error(404, `Unknown dataset: ${params.dataset}`); return { dataset }; diff --git a/web/src/routes/[dataset]/+page.svelte b/web/src/routes/[dataset]/+page.svelte index ac18283..e77d48e 100644 --- a/web/src/routes/[dataset]/+page.svelte +++ b/web/src/routes/[dataset]/+page.svelte @@ -1,4 +1,4 @@ -