YASC is a clean and offline-first web app designed for computing scores in any kind of point-based system. It provides an interactive feedback experience driven by a declarative rule engine.
Currently, it only has one ruleset for calculating your CRS (Comprehensive Ranking System) score, original built since the author was not satisfied with the UX of the official tools and existing third-party options.
What is CRS?
The Comprehensive Ranking System is the points-based framework used by IRCC in Canada to rank candidates in the Express Entry immigration pool. Your CRS score determines your chances of receiving an Invitation to Apply (ITA) for Canadian permanent residence. YASC currently ships with one built-in ruleset covering the standard CRS profile score, though the engine is built to go beyond that over time.Disclaimer: YASC is an independent project with no affiliation with and not endorsed by Immigration, Refugees and Citizenship Canada (IRCC). Scores calculated here are solely for personal reference.
- π¨ Handcrafted UI with light and dark themes, with separate layouts designed specifically for both desktop and mobile.
- π Live scoring breakdown: on desktop, the input form sits on the left and a scoring table on the right updates in real time as you fill in your profile. On mobile, a bottom bar shows your running total and expands into the full scoring table as a sheet.
- π±οΈ Button-first inputs: every question in the form uses simple clickable buttons, keeping interaction fast and clean.
- π Hover-to-highlight (desktop): hovering a criterion in the scoring table highlights the form questions that feed into it, so the relationship between inputs and points is always clear.
βοΈ Fully offline: no download is required, nothing leaves your browser, no account required. Can be installed as a PWA if you prefer.- βοΈ YAML rule engine: all scoring logic lives in a declarative ruleset file, keeping the engine reusable across different scoring systems.
Note: Normal users can access a working app on my GitHub Page.
Prerequisites: Node.js and npm.
git clone https://github.com/boreashe/yasc.git
cd yasc
npm i
npm run devIf you have Vite+ on your machine, you can also use the Vite+ CLI directly:
vp devor via the npm script alias:
npm run vp:devVite+ (VitePlus) is an optional unified toolchain by VoidZero. YASC's
package.jsonhas been adjusted so that a global Vite+ installation is not a hard requirement for running or forking the project.
YASC's scoring logic is fully data-driven. The engine processes two files per scoring system:
- π A ruleset YAML describing the scoring system
- π A localization YAML for that ruleset
Each ruleset moves through four stages in sequence.
A short header declaring the ruleset's identity and version. The engine uses this to manage ruleset loading and (eventually) switching.
meta:
id: "canada-crs"
version: v2025.03.25Defines every question the user sees. Each question has a key, a type, and a list of values that become the clickable buttons in the UI. Two features keep the form definition compact:
prerequisitesmakes a question visible only when a parent question matches a specific value, so the full tree of language sub-questions only appears after the user picks their test type.uselets a question reuse thevalueslist from another question entirely, avoiding repetition when multiple questions share the same option set.
# A top-level question
- key: english
type: value
values:
- "no"
- "CELPIP-G"
- "IELTS"
- "PTE Core"
# Only visible when english == "CELPIP-G".
# Reuses the options defined on english_celpip_speaking_score.
- key: english_celpip_listening_score
type: value
prerequisites:
english: "CELPIP-G"
use: "english_celpip_speaking_score"Transforms raw form values into intermediate variables that the scoring stage can work with. This is where test-specific scores get normalised into a common scale (e.g. CLB for English, NCLC for French), minimums are computed across the four language skills, and the engine determines which language ranks as the primary and which as the secondary.
Three operations are currently supported: map_values for lookup-based translation, min/max for reducing a set of variables to a single value, and conditional_assignment for if/else branching.
# Translate a raw CELPIP score to a CLB level
- key: english_celpip_speaking_clb_translation
operation: map_values
config:
sources: ["english_celpip_speaking_score"]
outputs: ["english_speaking_clb"]
mapping:
"10-12": "10"
"9": "9"
"4": "4"
"M, 0-3": "0"
# Find the weakest of the four English skill scores
- key: lowest_english_score
operation: min
config:
sources:
- "english_speaking_clb"
- "english_listening_clb"
- "english_reading_clb"
- "english_writing_clb"
output: "english_lowest_clb"
fallback: "0"
# Assign primary and secondary language based on which overall score is higher
- key: primary_secondary_official_languages
operation: conditional_assignment
config:
if:
operation: gte
left: "english_lowest_clb"
right: "french_lowest_nclc"
then:
- source: "english_lowest_clb"
output: "first_official_language_lowest"
# ...
else:
- source: "french_lowest_nclc"
output: "first_official_language_lowest"
# ...Calculates the final score. Rules are organized as a tree of named sub-keys, each containing a list of clauses. A clause matches when all its conditions are satisfied and contributes its score value. Categories can carry a max cap, and sub-scores marked aggregate: true are summed across their children.
A template directive handles cases where the same scoring logic applies to multiple variables (e.g. speaking, listening, reading, writing). It expands into one clause set per substitution at evaluation time, which keeps the ruleset from becoming four times as long as it needs to be.
- key: first_official_language
aggregate: true
sub:
- key: first_official_language_aspect
template:
find: "first_official_language_aspect"
replace: "aspect"
with: ["speaking", "writing", "reading", "listening"]
clauses:
- if:
first_official_language_aspect: "10"
with_spouse: "no"
score: 34
- if:
first_official_language_aspect: "10"
with_spouse: "yes"
score: 32
# ...
# Skill transferability caps the whole category and each sub-group independently
- key: skill_transferability_factors
max: 100
sub:
- key: education
max: 50
sub:
- key: education.language_proficiency
clauses:
- if:
education: ["multiple_degrees", "master_or_professional_degree", "doctor"]
first_official_language_lowest: ["9", "10"]
score: 50
# ...The current CRS ruleset at src/assets/rules/canada-crs.yaml is a useful real-world reference, though it may sit at the complex end of what the engine handles. CRS involves a lot of arbitrary two-dimensional scoring tables and non-linear point scales, which is part of why the file is as long as it is. A formal syntax guide is planned once the format stabilizes.
A few things in my thought, in no particular order:
- π‘ CRS draw history: pull recent draw data from a community-maintained open source API and let users benchmark their score against historical cutoffs. This would make the app not entirely offline by definition (still it won't submit any of your data), but only as an opt-in feature.
β οΈ Ruleset-driven alerts: surface warnings when a profile has a logical issue, such as missing language scores that Express Entry requires. The rule engine currently has no mechanism for conditional validation of this kind.- π‘ What-If analysis: a fifth pipeline stage that suggests concrete steps to raise your score based on your current profile. Rule-based, no AI involved.
- π± Better mobile interactivity: bring UX features like the scoring table highlight to mobile users, which currently only work on desktop.
- π Ruleset I/O: allow users to upload and switch between rulesets directly in the UI.
- ποΈ Ruleset library: accept community-submitted rulesets via PR, with the possibility of bundling well-tested ones into the main repo.
- π§ͺ Testing: add test cases.
Issues are very welcome. Bug reports, questions, and feature suggestions are all fair game.
Pull requests are not expected at this stage. If you have something in mind, opening an issue first is the best way to start a conversation.
Discord: boreashe
YASC is released under the GNU Affero General Public License v3.0.