masterytrace-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. masterytrace_cli-0.1.0/.gitignore +9 -0
  2. masterytrace_cli-0.1.0/LICENSE +21 -0
  3. masterytrace_cli-0.1.0/PKG-INFO +362 -0
  4. masterytrace_cli-0.1.0/README.md +326 -0
  5. masterytrace_cli-0.1.0/examples/README.md +27 -0
  6. masterytrace_cli-0.1.0/pyproject.toml +66 -0
  7. masterytrace_cli-0.1.0/src/masterytrace/__init__.py +68 -0
  8. masterytrace_cli-0.1.0/src/masterytrace/adapters/__init__.py +0 -0
  9. masterytrace_cli-0.1.0/src/masterytrace/adapters/generic_adapter.py +134 -0
  10. masterytrace_cli-0.1.0/src/masterytrace/cli/__init__.py +0 -0
  11. masterytrace_cli-0.1.0/src/masterytrace/cli/commands/__init__.py +0 -0
  12. masterytrace_cli-0.1.0/src/masterytrace/cli/commands/init.py +57 -0
  13. masterytrace_cli-0.1.0/src/masterytrace/cli/commands/record.py +51 -0
  14. masterytrace_cli-0.1.0/src/masterytrace/cli/commands/report.py +102 -0
  15. masterytrace_cli-0.1.0/src/masterytrace/cli/commands/score.py +63 -0
  16. masterytrace_cli-0.1.0/src/masterytrace/cli/format.py +17 -0
  17. masterytrace_cli-0.1.0/src/masterytrace/cli/index.py +177 -0
  18. masterytrace_cli-0.1.0/src/masterytrace/cli/json_encode.py +52 -0
  19. masterytrace_cli-0.1.0/src/masterytrace/cli/types.py +21 -0
  20. masterytrace_cli-0.1.0/src/masterytrace/core/__init__.py +0 -0
  21. masterytrace_cli-0.1.0/src/masterytrace/core/config.py +62 -0
  22. masterytrace_cli-0.1.0/src/masterytrace/core/engine.py +54 -0
  23. masterytrace_cli-0.1.0/src/masterytrace/core/event_schema.py +131 -0
  24. masterytrace_cli-0.1.0/src/masterytrace/core/scoring_model.py +68 -0
  25. masterytrace_cli-0.1.0/src/masterytrace/data/__init__.py +0 -0
  26. masterytrace_cli-0.1.0/src/masterytrace/data/sample_events.py +78 -0
  27. masterytrace_cli-0.1.0/src/masterytrace/models/__init__.py +0 -0
  28. masterytrace_cli-0.1.0/src/masterytrace/models/bkt.py +275 -0
  29. masterytrace_cli-0.1.0/src/masterytrace/models/irt.py +285 -0
  30. masterytrace_cli-0.1.0/src/masterytrace/py.typed +0 -0
  31. masterytrace_cli-0.1.0/tests/__init__.py +0 -0
  32. masterytrace_cli-0.1.0/tests/cli/__init__.py +0 -0
  33. masterytrace_cli-0.1.0/tests/cli/test_init.py +50 -0
  34. masterytrace_cli-0.1.0/tests/cli/test_record.py +45 -0
  35. masterytrace_cli-0.1.0/tests/cli/test_report.py +48 -0
  36. masterytrace_cli-0.1.0/tests/cli/test_score.py +41 -0
  37. masterytrace_cli-0.1.0/tests/test_bkt.py +159 -0
  38. masterytrace_cli-0.1.0/tests/test_cli_e2e.py +50 -0
  39. masterytrace_cli-0.1.0/tests/test_config.py +12 -0
  40. masterytrace_cli-0.1.0/tests/test_engine.py +49 -0
  41. masterytrace_cli-0.1.0/tests/test_event_schema.py +69 -0
  42. masterytrace_cli-0.1.0/tests/test_generic_adapter.py +108 -0
  43. masterytrace_cli-0.1.0/tests/test_irt.py +165 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .mypy_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rudrendu Paul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,362 @@
1
+ Metadata-Version: 2.4
2
+ Name: masterytrace-cli
3
+ Version: 0.1.0
4
+ Summary: Mastery Measurement API and CLI: fits Bayesian Knowledge Tracing (BKT) and Item Response Theory (2PL IRT) models to learner response logs and reports per-learner, per-skill mastery estimates.
5
+ Project-URL: Homepage, https://github.com/RudrenduPaul/MasteryTrace
6
+ Project-URL: Repository, https://github.com/RudrenduPaul/MasteryTrace
7
+ Project-URL: Bug Tracker, https://github.com/RudrenduPaul/MasteryTrace/issues
8
+ Project-URL: Changelog, https://github.com/RudrenduPaul/MasteryTrace/blob/main/CHANGELOG.md
9
+ Project-URL: Documentation, https://github.com/RudrenduPaul/MasteryTrace/blob/main/docs/getting-started.md
10
+ Project-URL: Author - Rudrendu Paul, https://github.com/RudrenduPaul
11
+ Project-URL: Author - Sourav Nandy, https://github.com/Sourav-nandy-ai
12
+ Author: Rudrendu Paul, Sourav Nandy
13
+ License-Expression: MIT
14
+ License-File: LICENSE
15
+ Keywords: agent-native,bayesian-knowledge-tracing,bkt,cli,edtech,education,irt,item-response-theory,learning-analytics,psychometrics
16
+ Classifier: Development Status :: 3 - Alpha
17
+ Classifier: Environment :: Console
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Intended Audience :: Education
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Programming Language :: Python :: 3
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Topic :: Education
29
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
30
+ Requires-Python: >=3.9
31
+ Provides-Extra: dev
32
+ Requires-Dist: build<2,>=1.0; extra == 'dev'
33
+ Requires-Dist: pytest<9,>=7.0; extra == 'dev'
34
+ Requires-Dist: twine<7,>=5.0; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # masterytrace-cli (Python)
38
+
39
+ Mastery Measurement API and CLI: fits Bayesian Knowledge Tracing (BKT) and
40
+ Item Response Theory (2-parameter logistic IRT) models to learner response
41
+ logs and reports per-learner, per-skill mastery estimates.
42
+
43
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/RudrenduPaul/MasteryTrace/blob/main/LICENSE)
44
+ [![CI](https://github.com/RudrenduPaul/MasteryTrace/actions/workflows/ci.yml/badge.svg)](https://github.com/RudrenduPaul/MasteryTrace/actions/workflows/ci.yml)
45
+
46
+ **PyPI status**: publishing this package as `masterytrace-cli` is in
47
+ progress. The first publish attempt hit PyPI's account-level
48
+ `429 Too many new projects created` anti-abuse rate limit -- a
49
+ platform-side throttle on new package names, unrelated to this code or
50
+ to any account security issue -- so `pip install masterytrace-cli` is not
51
+ live yet. The PyPI version/Python-versions badges above will be restored
52
+ once the package is live; install from source below in the meantime.
53
+
54
+ ## Why this exists
55
+
56
+ Most tutoring products and AI tutoring agents track raw percent-correct,
57
+ which conflates a lucky guess with real mastery and never says how
58
+ confident the estimate actually is. BKT and IRT are the two psychometric
59
+ models built to fix that, but they live almost entirely as Python-only
60
+ libraries with no CLI a non-Python tool (or an agent shelling out to a
61
+ subprocess) can call directly. MasteryTrace is a CLI and library that
62
+ turns a log of `{learnerId, skillId, correct, timestamp}` response events
63
+ into a posterior mastery probability (BKT) and an ability estimate (IRT)
64
+ per learner per skill. This package is the Python distribution -- a
65
+ genuine, independent port of the npm package's TypeScript source, not a
66
+ wrapper around a Node binary.
67
+
68
+ ## Install
69
+
70
+ Once published:
71
+
72
+ ```bash
73
+ pip install masterytrace-cli
74
+ ```
75
+
76
+ or with [uv](https://docs.astral.sh/uv/):
77
+
78
+ ```bash
79
+ uv add masterytrace-cli
80
+ ```
81
+
82
+ Until then, install from source (this checkout builds and installs
83
+ identically to how the PyPI package will):
84
+
85
+ ```bash
86
+ git clone https://github.com/RudrenduPaul/MasteryTrace.git
87
+ cd MasteryTrace/python
88
+ pip install -e .
89
+ ```
90
+
91
+ This installs a `masterytrace` console script and the `masterytrace`
92
+ importable package. No runtime dependencies beyond the Python standard
93
+ library -- the forward-recursion and gradient-ascent math in this port
94
+ are both plain scalar/list arithmetic, exactly what the TypeScript
95
+ original does with `Float64Array` and plain numbers, so a numpy/scipy
96
+ dependency would not simplify anything here.
97
+
98
+ **Honest note on the npm side:** the complementary JS/TS distribution
99
+ (`masterytrace-cli` on npm) is not yet published -- that is a deliberate,
100
+ unrelated decision by the maintainer, not a reflection of the npm
101
+ package's readiness (it passes CI and has a working `dist/` build). This
102
+ PyPI package is independent of that decision and installs and runs on its
103
+ own.
104
+
105
+ ## Quickstart
106
+
107
+ ```bash
108
+ masterytrace init
109
+ masterytrace record events.json
110
+ masterytrace score
111
+ masterytrace report
112
+ ```
113
+
114
+ `init` scaffolds a sample `events.json` (3 learners, 3 skills, several
115
+ responses each) and a default `masterytrace.config.json` in the current
116
+ directory. Real output from that flow:
117
+
118
+ ```
119
+ $ masterytrace init
120
+ Created: events.json, masterytrace.config.json
121
+ Next: run 'masterytrace record events.json' to load it, then 'masterytrace score'.
122
+
123
+ $ masterytrace record events.json
124
+ Stored 58 event(s) to /path/to/.masterytrace/events.json
125
+ (record replaces any previously stored event log; see --help for details.)
126
+
127
+ $ masterytrace score
128
+ Scored 58 event(s) with model(s): both
129
+ Wrote /path/to/.masterytrace/scores.json
130
+
131
+ $ masterytrace report
132
+ learner skill model metric value responses
133
+ ------------- --------------------- ----- ----------------------------- ------- ---------
134
+ learner-ada fractions bkt posterior_mastery_probability 0.9994 6
135
+ learner-ada fractions irt ability_theta 0.7349 6
136
+ learner-ada linear-equations bkt posterior_mastery_probability 0.9746 7
137
+ learner-brook fractions bkt posterior_mastery_probability 0.0612 6
138
+ learner-cyrus reading-comprehension bkt posterior_mastery_probability 0.9947 7
139
+ ...
140
+ ```
141
+
142
+ `report` also takes `--format markdown` or `--format json`, and every
143
+ command accepts a global `--json` flag for machine-readable output on
144
+ stdout, with a real exit code contract (`0` success, `1` general/usage
145
+ error, `2` bad event data) so a script or agent invoking this CLI can
146
+ branch on the result without parsing text.
147
+
148
+ Your own event log is a JSON array of `{learnerId, skillId, correct,
149
+ timestamp}` objects, or a CSV with header
150
+ `learner_id,skill_id,correct,timestamp`. `timestamp` must be ISO 8601;
151
+ `correct` is a boolean (JSON) or `true`/`false`/`1`/`0` (CSV), and any
152
+ other value in a CSV `correct` cell is rejected as a validation error
153
+ rather than silently treated as false. Event log files over 100 MB are
154
+ rejected up front with a clear error.
155
+
156
+ ## CLI command reference
157
+
158
+ | Command | Arguments | Options | Does |
159
+ | --- | --- | --- | --- |
160
+ | `masterytrace init` | | `--force` | Scaffolds a sample `events.json` and `masterytrace.config.json` in the current directory. Skips files that already exist unless `--force` is passed. |
161
+ | `masterytrace record <path>` | `<path>`: JSON or CSV event log | | Validates an event log and stores it to `.masterytrace/events.json`. Always replaces any previously stored log. |
162
+ | `masterytrace score` | | `--model <bkt\|irt\|both>` (default `both`) | Fits and scores the stored event log, writing the result to `.masterytrace/scores.json`. |
163
+ | `masterytrace report` | | `--format <table\|json\|markdown>` (default `table`) | Reads `.masterytrace/scores.json` and prints a per-learner, per-skill mastery table. |
164
+
165
+ Global option: `--json` forces machine-readable JSON on stdout for any
166
+ command, overriding `--format` on `report`.
167
+
168
+ Exit codes: `0` success, `1` general or usage error (bad flag, missing
169
+ file), `2` validation error (the event log itself is malformed).
170
+
171
+ ## Library API
172
+
173
+ ```python
174
+ from masterytrace import (
175
+ # Event schema and validation
176
+ ResponseEvent, parse_response_events, EventValidationError,
177
+
178
+ # Engine: runs one or both models
179
+ run_scoring, EngineConfig, EngineResult,
180
+
181
+ # BKT
182
+ BktModel, BKT_DEFAULT_PARAMS, run_forward_recursion, fit_skill_params_by_grid_search,
183
+ BktParams, BktConfig, BktFittedModel,
184
+
185
+ # IRT
186
+ IrtModel, probability_correct,
187
+ IrtItemParams, IrtLearnerResult, IrtConfig, IrtFittedModel,
188
+
189
+ # Generic JSON/CSV event log adapter
190
+ generic_adapter, parse_csv, GenericAdapter,
191
+ )
192
+
193
+ events = parse_response_events([
194
+ {"learnerId": "l1", "skillId": "fractions", "correct": True, "timestamp": "2026-01-01T00:00:00Z"},
195
+ {"learnerId": "l1", "skillId": "fractions", "correct": False, "timestamp": "2026-01-02T00:00:00Z"},
196
+ ])
197
+
198
+ result = run_scoring(events, "both")
199
+ # result.reports[0].model == "bkt", result.reports[1].model == "irt"
200
+ # each learner's report.learners[i].skills[j].value is the mastery estimate
201
+ ```
202
+
203
+ `BktModel` and `IrtModel` both implement the same `fit(events)` then
204
+ `score(fitted_model)` two-step interface, so the engine, and your own
205
+ code, can treat them interchangeably.
206
+
207
+ **A deliberate naming divergence from the npm package**: this Python
208
+ port's JSON output and dataclass field names are `snake_case`
209
+ (`learner_id`, `response_count`, `item_discrimination`) rather than the
210
+ npm CLI's `camelCase` (`learnerId`, `responseCount`,
211
+ `itemDiscrimination`), matching each language's own convention. The data
212
+ shape -- which fields exist and what they mean -- is identical.
213
+
214
+ ## How BKT and IRT work
215
+
216
+ MasteryTrace implements two independent psychometric models. They answer
217
+ different questions and produce different kinds of numbers, so
218
+ `masterytrace score --model both` runs them side by side rather than
219
+ picking one.
220
+
221
+ ### Bayesian Knowledge Tracing (BKT)
222
+
223
+ BKT models one learner's mastery of one skill as a hidden binary state
224
+ (knows it / does not know it yet) and updates a probability of "knows it"
225
+ after every response, using four parameters: `p_init` (prior probability
226
+ the learner already knows the skill), `p_transit` (probability of
227
+ learning the skill between one attempt and the next), `p_slip`
228
+ (probability of an incorrect answer despite knowing the skill), and
229
+ `p_guess` (probability of a correct answer despite not knowing the
230
+ skill). For each response, the forward recursion first updates the belief
231
+ given the observed outcome (Bayes' rule), then advances it for possible
232
+ learning before the next attempt:
233
+
234
+ ```
235
+ after correct: P(know | obs) = P(know) * (1 - p_slip) / [P(know) * (1 - p_slip) + (1 - P(know)) * p_guess]
236
+ after incorrect: P(know | obs) = P(know) * p_slip / [P(know) * p_slip + (1 - P(know)) * (1 - p_guess)]
237
+ P(know)_next = P(know | obs) + (1 - P(know | obs)) * p_transit
238
+ ```
239
+
240
+ MasteryTrace runs this recursion per learner per skill, in chronological
241
+ order, and reports the final posterior as that learner's mastery
242
+ probability for that skill. If you set `"bkt": {"fit": true}` in
243
+ `masterytrace.config.json`, each skill's four parameters are fit from
244
+ your own data by a coarse grid search (7 x 7 x 5 x 5 candidate
245
+ combinations) that minimizes squared error between predicted and observed
246
+ correctness, instead of using the textbook defaults (`p_init=0.4,
247
+ p_transit=0.3, p_slip=0.1, p_guess=0.2`). This is the same coarse,
248
+ dependency-free grid search the TypeScript original implements -- not a
249
+ full EM/Baum-Welch fit -- ported line-for-line, including its parameter
250
+ grid values.
251
+
252
+ ### Item Response Theory (2PL IRT)
253
+
254
+ IRT models one continuous learner ability (`theta`) per learner and two
255
+ parameters per skill treated as an "item": discrimination (`a`, how
256
+ sharply the item separates high- and low-ability learners) and difficulty
257
+ (`b`). The probability of a correct response under the 2-parameter
258
+ logistic model is:
259
+
260
+ ```
261
+ P(correct) = sigmoid(a * (theta - b))
262
+ ```
263
+
264
+ MasteryTrace fits all of these jointly by gradient ascent on the
265
+ log-likelihood (joint MLE), with a small L2 penalty pulling `theta`/`b`
266
+ toward 0 and `a` toward 1. That penalty is what keeps the fit finite for
267
+ a learner or skill with an all-correct or all-incorrect record, where the
268
+ unregularized likelihood would otherwise be maximized at infinity.
269
+ Because the 2PL model is only identified up to a shift and scale of
270
+ `theta` (shifting `theta` and `b` by the same constant, or scaling
271
+ `theta`/`b` while dividing `a` accordingly, leaves every predicted
272
+ probability unchanged), the fit re-centers `theta` to mean 0 and standard
273
+ deviation 1 after every iteration -- the standard way to pin down a
274
+ single solution. This port mirrors the TypeScript original's hand-rolled
275
+ batch gradient ascent line for line (same update rule, same per-iteration
276
+ gauge fix, same default 500 iterations / 0.5 learning rate / 0.01
277
+ regularization), rather than calling into an external optimizer -- there
278
+ is no scipy.optimize equivalent in play to port to, since the original
279
+ does not use one either.
280
+
281
+ ### A real recovery check
282
+
283
+ `tests/test_irt.py` fits the model against a synthetic dataset built from
284
+ known ground-truth `theta`/`a`/`b` values (4,000 responses across 5
285
+ learners and 4 skills, same synthetic setup as the TypeScript suite's own
286
+ recovery test) and checks that the recovered parameters land close to the
287
+ true ones once put through the same gauge normalization, within the same
288
+ 0.3 absolute-error tolerance the TypeScript test uses.
289
+
290
+ ## Benchmark
291
+
292
+ Run locally against synthetic event logs (single core, in-process library
293
+ calls, no subprocess/CLI startup overhead):
294
+
295
+ | Dataset | Events | BKT fit+score | IRT fit+score (500 iterations) |
296
+ | --- | --- | --- | --- |
297
+ | Small | 10,000 (50 learners x 20 skills x 10 responses) | 0.05s | 2.86s |
298
+ | Large | 100,000 (100 learners x 50 skills x 20 responses) | 0.09s | 14.42s |
299
+
300
+ BKT with grid-search fitting (`"bkt": {"fit": true}`, a 1,225-combination
301
+ grid search per skill) on the 10,000-event dataset took 5.20s.
302
+
303
+ **Honest comparison with the TypeScript package**: the npm package's own
304
+ documented benchmark reports the 100,000-event IRT fit at 0.54s (as part
305
+ of a `--model both` subprocess run) -- roughly 27x faster than this
306
+ Python port's 14.42s for the same fit alone. This gap is expected and
307
+ disclosed, not a porting bug: IRT's gradient-ascent fit is a tight
308
+ numeric loop, and V8's JIT compiles that loop far more aggressively than
309
+ CPython's interpreter executes the equivalent Python loop. The fitted
310
+ *values* are what this port keeps faithful (see the recovery check
311
+ above, and the identical-to-4-decimal-places output in the Quickstart
312
+ section); wall-clock fit time on large datasets is not. See
313
+ [SECURITY.md](https://github.com/RudrenduPaul/MasteryTrace/blob/main/SECURITY.md)'s
314
+ "known limitation" section for what this means for very large,
315
+ untrusted event logs.
316
+
317
+ ## CI integration
318
+
319
+ ```yaml
320
+ - uses: actions/checkout@v4
321
+ - uses: actions/setup-python@v5
322
+ with:
323
+ python-version: '3.12'
324
+ - run: pip install masterytrace-cli
325
+ - run: masterytrace init
326
+ - run: masterytrace record events.json
327
+ - run: masterytrace score --model both
328
+ - run: masterytrace report --format json > mastery-report.json
329
+ ```
330
+
331
+ Full walkthrough in
332
+ [docs/integrations/ci.md](https://github.com/RudrenduPaul/MasteryTrace/blob/main/docs/integrations/ci.md).
333
+
334
+ ## Security
335
+
336
+ MasteryTrace never `eval()`s or `exec()`s anything read from an event
337
+ log; event data is only ever parsed as JSON/CSV and pattern-matched
338
+ against the validation schema. The generic adapter refuses symlinked
339
+ paths and files over 100 MB before reading them, closing the two most
340
+ likely ways a maliciously crafted event-log path could cause unexpected
341
+ behavior. **Honest note**: this project does not currently publish SLSA
342
+ provenance, Sigstore signatures, or an SBOM, and has no OpenSSF Scorecard
343
+ badge set up -- none of that infrastructure exists yet for either
344
+ distribution, so it isn't claimed here. See
345
+ [SECURITY.md](https://github.com/RudrenduPaul/MasteryTrace/blob/main/SECURITY.md)
346
+ for the vulnerability disclosure process.
347
+
348
+ ## Contributing
349
+
350
+ See [CONTRIBUTING.md](https://github.com/RudrenduPaul/MasteryTrace/blob/main/CONTRIBUTING.md)
351
+ for the full guide, covering both the TypeScript and Python codebases.
352
+
353
+ ```bash
354
+ cd python
355
+ python3 -m venv .venv && source .venv/bin/activate
356
+ pip install -e ".[dev]"
357
+ pytest
358
+ ```
359
+
360
+ ## License
361
+
362
+ MIT, see [LICENSE](https://github.com/RudrenduPaul/MasteryTrace/blob/main/LICENSE).