Spaces:
Sleeping
Sleeping
work-sejal commited on
Commit ·
8ece6f3
1
Parent(s): 84a0358
Add models and dataset
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- data/artifacts/models/answer_scorer/metrics.json +45 -0
- data/artifacts/models/answer_scorer/model.joblib +3 -0
- data/artifacts/models/answer_scorer/model_card.md +51 -0
- data/artifacts/models/answer_scorer/training_config.json +26 -0
- data/artifacts/models/answer_scorer/vectorizer.joblib +3 -0
- data/artifacts/models/bloom_classifier/label_encoder.joblib +3 -0
- data/artifacts/models/bloom_classifier/metrics.json +204 -0
- data/artifacts/models/bloom_classifier/model.joblib +3 -0
- data/artifacts/models/bloom_classifier/model_card.md +47 -0
- data/artifacts/models/bloom_classifier/training_config.json +28 -0
- data/artifacts/models/bloom_classifier/vectorizer.joblib +3 -0
- data/artifacts/models/difficulty_model/encoder.joblib +3 -0
- data/artifacts/models/difficulty_model/feature_columns.json +6 -0
- data/artifacts/models/difficulty_model/metrics.json +38 -0
- data/artifacts/models/difficulty_model/model.joblib +3 -0
- data/artifacts/models/difficulty_model/model_card.md +50 -0
- data/artifacts/models/difficulty_model/training_config.json +33 -0
- data/artifacts/models/lo_tagger/label_encoder.joblib +3 -0
- data/artifacts/models/lo_tagger/metrics.json +0 -0
- data/artifacts/models/lo_tagger/model.joblib +3 -0
- data/artifacts/models/lo_tagger/model_card.md +50 -0
- data/artifacts/models/lo_tagger/training_config.json +27 -0
- data/artifacts/models/lo_tagger/vectorizer.joblib +3 -0
- data/artifacts/models/mastery_model/feature_columns.json +11 -0
- data/artifacts/models/mastery_model/metrics.json +132 -0
- data/artifacts/models/mastery_model/model.joblib +3 -0
- data/artifacts/models/mastery_model/model_card.md +56 -0
- data/artifacts/models/mastery_model/training_config.json +35 -0
- data/artifacts/models/recommender/encoder.joblib +3 -0
- data/artifacts/models/recommender/feature_columns.json +7 -0
- data/artifacts/models/recommender/metrics.json +33 -0
- data/artifacts/models/recommender/model.joblib +3 -0
- data/artifacts/models/recommender/model_card.md +53 -0
- data/artifacts/models/recommender/training_config.json +39 -0
- data/artifacts/models/risk_model/feature_columns.json +21 -0
- data/artifacts/models/risk_model/metrics.json +101 -0
- data/artifacts/models/risk_model/model.joblib +3 -0
- data/artifacts/models/risk_model/model_card.md +71 -0
- data/artifacts/models/risk_model/training_config.json +44 -0
- data/learning_outcome_os_dataset_v2/README.md +134 -0
- data/learning_outcome_os_dataset_v2/ai_prediction_logs.csv +3 -0
- data/learning_outcome_os_dataset_v2/assessment_questions.csv +3 -0
- data/learning_outcome_os_dataset_v2/assessments.csv +3 -0
- data/learning_outcome_os_dataset_v2/chapters.csv +3 -0
- data/learning_outcome_os_dataset_v2/classes.csv +3 -0
- data/learning_outcome_os_dataset_v2/content_catalog.csv +3 -0
- data/learning_outcome_os_dataset_v2/dataset_metadata.json +98 -0
- data/learning_outcome_os_dataset_v2/engagement_logs.csv +3 -0
- data/learning_outcome_os_dataset_v2/generation_script.py +354 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
data/artifacts/models/answer_scorer/metrics.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "answer_scorer",
|
| 3 |
+
"model_version": "answer_scorer_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:12.590667+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 8371,
|
| 9 |
+
"validation": 1820,
|
| 10 |
+
"test": 1809
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"mae": 0.4787,
|
| 15 |
+
"rmse": 0.773,
|
| 16 |
+
"r_squared": 0.718,
|
| 17 |
+
"distribution": {
|
| 18 |
+
"predicted_mean": 1.8303,
|
| 19 |
+
"predicted_std": 0.9736,
|
| 20 |
+
"actual_mean": 2.0455,
|
| 21 |
+
"actual_std": 1.4557
|
| 22 |
+
},
|
| 23 |
+
"pct_above_review_threshold": 16.87
|
| 24 |
+
},
|
| 25 |
+
"test": {
|
| 26 |
+
"mae": 0.5256,
|
| 27 |
+
"rmse": 0.8385,
|
| 28 |
+
"r_squared": 0.6791,
|
| 29 |
+
"distribution": {
|
| 30 |
+
"predicted_mean": 1.8985,
|
| 31 |
+
"predicted_std": 0.9581,
|
| 32 |
+
"actual_mean": 2.174,
|
| 33 |
+
"actual_std": 1.4803
|
| 34 |
+
},
|
| 35 |
+
"pct_above_review_threshold": 19.68
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"limitations": [
|
| 39 |
+
"Trained on synthetic data only.",
|
| 40 |
+
"TF-IDF features do not capture deep semantic similarity.",
|
| 41 |
+
"Predictions clipped to [0, max_marks] range.",
|
| 42 |
+
"teacher_review_required is always True in V2 baseline.",
|
| 43 |
+
"MAE is the primary metric; individual predictions may deviate significantly."
|
| 44 |
+
]
|
| 45 |
+
}
|
data/artifacts/models/answer_scorer/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d79a6024682b2c0c756882dda8ab277c14afba6ebd18c50f76a791655a2046fb
|
| 3 |
+
size 8522
|
data/artifacts/models/answer_scorer/model_card.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Answer Scorer
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** answer_scorer
|
| 6 |
+
- **Model Version:** answer_scorer_v2_baseline_001
|
| 7 |
+
- **Algorithm:** TF-IDF + Ridge Regression
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:12.590667+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Score subjective student answers against a rubric and model answer.
|
| 15 |
+
Produces a predicted marks value clipped to [0, max_marks]. Always sets
|
| 16 |
+
teacher_review_required=True in V2 baseline — predictions are advisory only.
|
| 17 |
+
|
| 18 |
+
## Training Data
|
| 19 |
+
|
| 20 |
+
- **Source:** training_answer_scoring.csv (synthetic dataset v2)
|
| 21 |
+
- **Split Counts:** train=8371, validation=1820, test=1809
|
| 22 |
+
- **Features:** student_answer (TF-IDF, max_features=5000, ngram_range=(1,2)) + rubric_match_score + concept_coverage_score
|
| 23 |
+
- **Target:** teacher_marks (continuous)
|
| 24 |
+
|
| 25 |
+
## Metrics
|
| 26 |
+
|
| 27 |
+
### Validation Set
|
| 28 |
+
- MAE: 0.4787
|
| 29 |
+
- RMSE: 0.773
|
| 30 |
+
- R-squared: 0.718
|
| 31 |
+
- % Above Review Threshold (>1.0): 16.87%
|
| 32 |
+
|
| 33 |
+
### Test Set
|
| 34 |
+
- MAE: 0.5256
|
| 35 |
+
- RMSE: 0.8385
|
| 36 |
+
- R-squared: 0.6791
|
| 37 |
+
- % Above Review Threshold (>1.0): 19.68%
|
| 38 |
+
|
| 39 |
+
## Known Limitations
|
| 40 |
+
|
| 41 |
+
- Trained on synthetic data only — performance on real student answers is unknown.
|
| 42 |
+
- TF-IDF features do not capture deep semantic similarity or paraphrasing.
|
| 43 |
+
- Predictions are clipped to [0, max_marks]; the model may predict outside this range before clipping.
|
| 44 |
+
- teacher_review_required is always True in V2 baseline.
|
| 45 |
+
- MAE is the primary metric; individual predictions may deviate significantly from teacher marks.
|
| 46 |
+
|
| 47 |
+
## Fallback Behavior
|
| 48 |
+
|
| 49 |
+
When the model is not loaded or confidence is below threshold,
|
| 50 |
+
the system falls back to rubric keyword coverage + length heuristic,
|
| 51 |
+
always setting teacher_review_required=True.
|
data/artifacts/models/answer_scorer/training_config.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "answer_scorer",
|
| 3 |
+
"model_version": "answer_scorer_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 8371,
|
| 8 |
+
"validation": 1820,
|
| 9 |
+
"test": 1809
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"tfidf_max_features": 5000,
|
| 13 |
+
"ngram_range": [
|
| 14 |
+
1,
|
| 15 |
+
2
|
| 16 |
+
],
|
| 17 |
+
"ridge_alpha": 1.0
|
| 18 |
+
},
|
| 19 |
+
"feature_columns": [
|
| 20 |
+
"student_answer (TF-IDF)",
|
| 21 |
+
"rubric_match_score",
|
| 22 |
+
"concept_coverage_score"
|
| 23 |
+
],
|
| 24 |
+
"target_column": "teacher_marks",
|
| 25 |
+
"algorithm": "TF-IDF + Ridge regression"
|
| 26 |
+
}
|
data/artifacts/models/answer_scorer/vectorizer.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bf5b83c246aacede1528d6c6b52986e36712363b5e1b85bad1e1b3495134a268
|
| 3 |
+
size 26983
|
data/artifacts/models/bloom_classifier/label_encoder.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49cd11f5861283c0d8da24d21a2f021791eb64ea0d971e21e6d76ce16c3f2b7f
|
| 3 |
+
size 439
|
data/artifacts/models/bloom_classifier/metrics.json
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "bloom_classifier",
|
| 3 |
+
"model_version": "bloom_classifier_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:09.626503+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 3912,
|
| 9 |
+
"validation": 1033,
|
| 10 |
+
"test": 875
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"macro_f1": 0.6434,
|
| 15 |
+
"weighted_f1": 0.6634,
|
| 16 |
+
"per_class": {
|
| 17 |
+
"Analyze": {
|
| 18 |
+
"precision": 0.614,
|
| 19 |
+
"recall": 0.6774,
|
| 20 |
+
"f1": 0.6442,
|
| 21 |
+
"support": 155
|
| 22 |
+
},
|
| 23 |
+
"Apply": {
|
| 24 |
+
"precision": 0.65,
|
| 25 |
+
"recall": 0.5778,
|
| 26 |
+
"f1": 0.6118,
|
| 27 |
+
"support": 270
|
| 28 |
+
},
|
| 29 |
+
"Create": {
|
| 30 |
+
"precision": 0.8947,
|
| 31 |
+
"recall": 1.0,
|
| 32 |
+
"f1": 0.9444,
|
| 33 |
+
"support": 17
|
| 34 |
+
},
|
| 35 |
+
"Evaluate": {
|
| 36 |
+
"precision": 1.0,
|
| 37 |
+
"recall": 0.1522,
|
| 38 |
+
"f1": 0.2642,
|
| 39 |
+
"support": 46
|
| 40 |
+
},
|
| 41 |
+
"Remember": {
|
| 42 |
+
"precision": 0.7438,
|
| 43 |
+
"recall": 0.5732,
|
| 44 |
+
"f1": 0.6475,
|
| 45 |
+
"support": 157
|
| 46 |
+
},
|
| 47 |
+
"Understand": {
|
| 48 |
+
"precision": 0.68,
|
| 49 |
+
"recall": 0.8325,
|
| 50 |
+
"f1": 0.7486,
|
| 51 |
+
"support": 388
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"confusion_matrix": [
|
| 55 |
+
[
|
| 56 |
+
105,
|
| 57 |
+
50,
|
| 58 |
+
0,
|
| 59 |
+
0,
|
| 60 |
+
0,
|
| 61 |
+
0
|
| 62 |
+
],
|
| 63 |
+
[
|
| 64 |
+
27,
|
| 65 |
+
156,
|
| 66 |
+
0,
|
| 67 |
+
0,
|
| 68 |
+
0,
|
| 69 |
+
87
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
0,
|
| 73 |
+
0,
|
| 74 |
+
17,
|
| 75 |
+
0,
|
| 76 |
+
0,
|
| 77 |
+
0
|
| 78 |
+
],
|
| 79 |
+
[
|
| 80 |
+
37,
|
| 81 |
+
0,
|
| 82 |
+
2,
|
| 83 |
+
7,
|
| 84 |
+
0,
|
| 85 |
+
0
|
| 86 |
+
],
|
| 87 |
+
[
|
| 88 |
+
0,
|
| 89 |
+
2,
|
| 90 |
+
0,
|
| 91 |
+
0,
|
| 92 |
+
90,
|
| 93 |
+
65
|
| 94 |
+
],
|
| 95 |
+
[
|
| 96 |
+
2,
|
| 97 |
+
32,
|
| 98 |
+
0,
|
| 99 |
+
0,
|
| 100 |
+
31,
|
| 101 |
+
323
|
| 102 |
+
]
|
| 103 |
+
]
|
| 104 |
+
},
|
| 105 |
+
"test": {
|
| 106 |
+
"macro_f1": 0.6178,
|
| 107 |
+
"weighted_f1": 0.6654,
|
| 108 |
+
"per_class": {
|
| 109 |
+
"Analyze": {
|
| 110 |
+
"precision": 0.7,
|
| 111 |
+
"recall": 0.6853,
|
| 112 |
+
"f1": 0.6926,
|
| 113 |
+
"support": 143
|
| 114 |
+
},
|
| 115 |
+
"Apply": {
|
| 116 |
+
"precision": 0.6411,
|
| 117 |
+
"recall": 0.5678,
|
| 118 |
+
"f1": 0.6022,
|
| 119 |
+
"support": 236
|
| 120 |
+
},
|
| 121 |
+
"Create": {
|
| 122 |
+
"precision": 0.8333,
|
| 123 |
+
"recall": 0.9375,
|
| 124 |
+
"f1": 0.8824,
|
| 125 |
+
"support": 16
|
| 126 |
+
},
|
| 127 |
+
"Evaluate": {
|
| 128 |
+
"precision": 0.6667,
|
| 129 |
+
"recall": 0.0588,
|
| 130 |
+
"f1": 0.1081,
|
| 131 |
+
"support": 34
|
| 132 |
+
},
|
| 133 |
+
"Remember": {
|
| 134 |
+
"precision": 0.7677,
|
| 135 |
+
"recall": 0.608,
|
| 136 |
+
"f1": 0.6786,
|
| 137 |
+
"support": 125
|
| 138 |
+
},
|
| 139 |
+
"Understand": {
|
| 140 |
+
"precision": 0.665,
|
| 141 |
+
"recall": 0.8411,
|
| 142 |
+
"f1": 0.7428,
|
| 143 |
+
"support": 321
|
| 144 |
+
}
|
| 145 |
+
},
|
| 146 |
+
"confusion_matrix": [
|
| 147 |
+
[
|
| 148 |
+
98,
|
| 149 |
+
45,
|
| 150 |
+
0,
|
| 151 |
+
0,
|
| 152 |
+
0,
|
| 153 |
+
0
|
| 154 |
+
],
|
| 155 |
+
[
|
| 156 |
+
13,
|
| 157 |
+
134,
|
| 158 |
+
0,
|
| 159 |
+
0,
|
| 160 |
+
0,
|
| 161 |
+
89
|
| 162 |
+
],
|
| 163 |
+
[
|
| 164 |
+
0,
|
| 165 |
+
0,
|
| 166 |
+
15,
|
| 167 |
+
1,
|
| 168 |
+
0,
|
| 169 |
+
0
|
| 170 |
+
],
|
| 171 |
+
[
|
| 172 |
+
29,
|
| 173 |
+
0,
|
| 174 |
+
3,
|
| 175 |
+
2,
|
| 176 |
+
0,
|
| 177 |
+
0
|
| 178 |
+
],
|
| 179 |
+
[
|
| 180 |
+
0,
|
| 181 |
+
2,
|
| 182 |
+
0,
|
| 183 |
+
0,
|
| 184 |
+
76,
|
| 185 |
+
47
|
| 186 |
+
],
|
| 187 |
+
[
|
| 188 |
+
0,
|
| 189 |
+
28,
|
| 190 |
+
0,
|
| 191 |
+
0,
|
| 192 |
+
23,
|
| 193 |
+
270
|
| 194 |
+
]
|
| 195 |
+
]
|
| 196 |
+
}
|
| 197 |
+
},
|
| 198 |
+
"limitations": [
|
| 199 |
+
"Trained on synthetic data only.",
|
| 200 |
+
"6 classes with imbalanced distribution \u2014 Create (~2%) and Evaluate (~4%) are rare.",
|
| 201 |
+
"Macro F1 is the primary metric; per-class recall may be low for rare classes.",
|
| 202 |
+
"TF-IDF features do not capture semantic similarity beyond n-gram overlap."
|
| 203 |
+
]
|
| 204 |
+
}
|
data/artifacts/models/bloom_classifier/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2cae60751163b66384f69e916fda7c5a302eb3b6e2a6b8117944a47a3fd70dfd
|
| 3 |
+
size 76776
|
data/artifacts/models/bloom_classifier/model_card.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Bloom Classifier
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** bloom_classifier
|
| 6 |
+
- **Model Version:** bloom_classifier_v2_baseline_001
|
| 7 |
+
- **Algorithm:** TF-IDF + LogisticRegression (multinomial)
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:09.626503+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Automatically classify questions by Bloom's taxonomy cognitive level.
|
| 15 |
+
Used in the Bloom classification endpoint to predict one of 6 levels:
|
| 16 |
+
Remember, Understand, Apply, Analyze, Evaluate, Create.
|
| 17 |
+
|
| 18 |
+
## Training Data
|
| 19 |
+
|
| 20 |
+
- **Source:** training_bloom_classification.csv (synthetic dataset v2)
|
| 21 |
+
- **Split Counts:** train=3912, validation=1033, test=875
|
| 22 |
+
- **Feature:** question_text (TF-IDF vectorized, max_features=8000, ngram_range=(1,2))
|
| 23 |
+
- **Target:** bloom_level (6 classes)
|
| 24 |
+
|
| 25 |
+
## Metrics
|
| 26 |
+
|
| 27 |
+
### Validation Set
|
| 28 |
+
- Macro F1: 0.6434
|
| 29 |
+
- Weighted F1: 0.6634
|
| 30 |
+
|
| 31 |
+
### Test Set
|
| 32 |
+
- Macro F1: 0.6178
|
| 33 |
+
- Weighted F1: 0.6654
|
| 34 |
+
|
| 35 |
+
## Known Limitations
|
| 36 |
+
|
| 37 |
+
- Trained on synthetic data only — performance on real classroom questions is unknown.
|
| 38 |
+
- Class imbalance: Create (~2%) and Evaluate (~4%) are rare; recall on these classes may be low.
|
| 39 |
+
- TF-IDF features do not capture semantic similarity beyond n-gram overlap.
|
| 40 |
+
- Macro F1 is the primary metric; accuracy alone would mask poor performance on rare classes.
|
| 41 |
+
|
| 42 |
+
## Fallback Behavior
|
| 43 |
+
|
| 44 |
+
When the model is not loaded or confidence is below the threshold (0.55),
|
| 45 |
+
the system falls back to keyword heuristic classification:
|
| 46 |
+
define/list → Remember; explain → Understand; calculate/use → Apply;
|
| 47 |
+
compare/contrast → Analyze; justify → Evaluate; design → Create.
|
data/artifacts/models/bloom_classifier/training_config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "bloom_classifier",
|
| 3 |
+
"model_version": "bloom_classifier_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 3912,
|
| 8 |
+
"validation": 1033,
|
| 9 |
+
"test": 875
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"tfidf_max_features": 8000,
|
| 13 |
+
"ngram_range": [
|
| 14 |
+
1,
|
| 15 |
+
2
|
| 16 |
+
],
|
| 17 |
+
"sublinear_tf": true,
|
| 18 |
+
"logreg_C": 1.0,
|
| 19 |
+
"logreg_solver": "lbfgs",
|
| 20 |
+
"logreg_max_iter": 1000,
|
| 21 |
+
"logreg_multi_class": "multinomial"
|
| 22 |
+
},
|
| 23 |
+
"feature_columns": [
|
| 24 |
+
"question_text"
|
| 25 |
+
],
|
| 26 |
+
"target_column": "bloom_level",
|
| 27 |
+
"algorithm": "LogisticRegression(multinomial)"
|
| 28 |
+
}
|
data/artifacts/models/bloom_classifier/vectorizer.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ca74184cd96c1a424e09a6e35df2a89d27fd009eb77aa35886666d4a097ab96
|
| 3 |
+
size 31067
|
data/artifacts/models/difficulty_model/encoder.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:781bac207e91e216ba726703b113d6646eabe3f46567205b10088621e6404f47
|
| 3 |
+
size 704
|
data/artifacts/models/difficulty_model/feature_columns.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"bloom_score",
|
| 3 |
+
"grade",
|
| 4 |
+
"subject",
|
| 5 |
+
"question_type"
|
| 6 |
+
]
|
data/artifacts/models/difficulty_model/metrics.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "difficulty_model",
|
| 3 |
+
"model_version": "difficulty_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:09.943332+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 3912,
|
| 9 |
+
"validation": 1033,
|
| 10 |
+
"test": 875
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"mae": 0.3475,
|
| 15 |
+
"r_squared": 0.5003,
|
| 16 |
+
"per_bucket_mae": {
|
| 17 |
+
"easy": 0.3058,
|
| 18 |
+
"medium": 0.2934,
|
| 19 |
+
"hard": 0.6563
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
"test": {
|
| 23 |
+
"mae": 0.3519,
|
| 24 |
+
"r_squared": 0.4685,
|
| 25 |
+
"per_bucket_mae": {
|
| 26 |
+
"easy": 0.325,
|
| 27 |
+
"medium": 0.2885,
|
| 28 |
+
"hard": 0.6797
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"limitations": [
|
| 33 |
+
"Trained on synthetic data only.",
|
| 34 |
+
"difficulty_score distribution may not reflect real-world difficulty.",
|
| 35 |
+
"OrdinalEncoder assumes an ordering that may not be meaningful for subject/question_type.",
|
| 36 |
+
"Per-bucket MAE depends on the quality of the difficulty string labels."
|
| 37 |
+
]
|
| 38 |
+
}
|
data/artifacts/models/difficulty_model/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3d41ef6570c86bc6878a6cbe7de68aba85bebc996f96d2a33198c8de91165f3f
|
| 3 |
+
size 735351
|
data/artifacts/models/difficulty_model/model_card.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Difficulty Model
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** difficulty_model
|
| 6 |
+
- **Model Version:** difficulty_model_v2_baseline_001
|
| 7 |
+
- **Algorithm:** RandomForestRegressor
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:09.943332+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Estimate question difficulty as a continuous score in [0, 1] based on
|
| 15 |
+
question features (bloom_score, grade, subject, question_type). Used in
|
| 16 |
+
the difficulty estimation endpoint to predict how hard a question is for
|
| 17 |
+
a given grade level.
|
| 18 |
+
|
| 19 |
+
## Training Data
|
| 20 |
+
|
| 21 |
+
- **Source:** training_lo_tagging.csv + questions.csv (for question_type)
|
| 22 |
+
- **Split Counts:** train=3912, validation=1033, test=875
|
| 23 |
+
- **Features:** bloom_score (numeric), grade (numeric), subject (OrdinalEncoded), question_type (OrdinalEncoded)
|
| 24 |
+
- **Target:** difficulty_score (continuous [0, 1])
|
| 25 |
+
|
| 26 |
+
## Metrics
|
| 27 |
+
|
| 28 |
+
### Validation Set
|
| 29 |
+
- MAE: 0.3475
|
| 30 |
+
- R-squared: 0.5003
|
| 31 |
+
- Per-bucket MAE: {'easy': 0.3058, 'medium': 0.2934, 'hard': 0.6563}
|
| 32 |
+
|
| 33 |
+
### Test Set
|
| 34 |
+
- MAE: 0.3519
|
| 35 |
+
- R-squared: 0.4685
|
| 36 |
+
- Per-bucket MAE: {'easy': 0.325, 'medium': 0.2885, 'hard': 0.6797}
|
| 37 |
+
|
| 38 |
+
## Known Limitations
|
| 39 |
+
|
| 40 |
+
- Trained on synthetic data only — performance on real questions is unknown.
|
| 41 |
+
- difficulty_score distribution may not reflect real-world difficulty.
|
| 42 |
+
- OrdinalEncoder assumes an ordering that may not be meaningful for subject/question_type.
|
| 43 |
+
- Per-bucket MAE depends on the quality of the difficulty string labels.
|
| 44 |
+
- Limited feature set (4 features); text-based features could improve performance.
|
| 45 |
+
|
| 46 |
+
## Fallback Behavior
|
| 47 |
+
|
| 48 |
+
When the model is not loaded or confidence is below threshold, the system
|
| 49 |
+
falls back to a rule-based difficulty estimation using bloom_score and
|
| 50 |
+
grade-level heuristics.
|
data/artifacts/models/difficulty_model/training_config.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "difficulty_model",
|
| 3 |
+
"model_version": "difficulty_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 3912,
|
| 8 |
+
"validation": 1033,
|
| 9 |
+
"test": 875
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"n_estimators": 100,
|
| 13 |
+
"random_state": 42,
|
| 14 |
+
"algorithm": "RandomForestRegressor",
|
| 15 |
+
"encoder": "OrdinalEncoder"
|
| 16 |
+
},
|
| 17 |
+
"feature_columns": [
|
| 18 |
+
"bloom_score",
|
| 19 |
+
"grade",
|
| 20 |
+
"subject",
|
| 21 |
+
"question_type"
|
| 22 |
+
],
|
| 23 |
+
"categorical_columns": [
|
| 24 |
+
"subject",
|
| 25 |
+
"question_type"
|
| 26 |
+
],
|
| 27 |
+
"numeric_columns": [
|
| 28 |
+
"bloom_score",
|
| 29 |
+
"grade"
|
| 30 |
+
],
|
| 31 |
+
"target_column": "difficulty_score",
|
| 32 |
+
"algorithm": "RandomForestRegressor"
|
| 33 |
+
}
|
data/artifacts/models/lo_tagger/label_encoder.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:42e9faea136d20cf19963170fc609d0f3a02b28b6d2e3e376b79739b3f043e43
|
| 3 |
+
size 1147
|
data/artifacts/models/lo_tagger/metrics.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/artifacts/models/lo_tagger/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b30022c2f5a8b307b13dd7b433f6e0ea112297c0ec4cb69e5d2ed1fc6412996c
|
| 3 |
+
size 7427152
|
data/artifacts/models/lo_tagger/model_card.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: LO Tagger
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** lo_tagger
|
| 6 |
+
- **Model Version:** lo_tagger_v2_baseline_001
|
| 7 |
+
- **Algorithm:** TF-IDF + CalibratedClassifierCV(LinearSVC)
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:08.919894+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Automatically tag questions to their most relevant Learning Outcome (LO).
|
| 15 |
+
Used in the LO tagging endpoint to classify question text into one of 194
|
| 16 |
+
learning outcome categories.
|
| 17 |
+
|
| 18 |
+
## Training Data
|
| 19 |
+
|
| 20 |
+
- **Source:** training_lo_tagging.csv (synthetic dataset v2)
|
| 21 |
+
- **Split Counts:** train=3912, validation=1033, test=875
|
| 22 |
+
- **Feature:** question_text (TF-IDF vectorized, max_features=10000, ngram_range=(1,2))
|
| 23 |
+
- **Target:** lo_id (194 classes)
|
| 24 |
+
|
| 25 |
+
## Metrics
|
| 26 |
+
|
| 27 |
+
### Validation Set
|
| 28 |
+
- Top-1 Accuracy: 0.9245
|
| 29 |
+
- Top-3 Accuracy: 1.0
|
| 30 |
+
- Macro F1: 0.9042
|
| 31 |
+
- Weighted F1: 0.9249
|
| 32 |
+
|
| 33 |
+
### Test Set
|
| 34 |
+
- Top-1 Accuracy: 0.9097
|
| 35 |
+
- Top-3 Accuracy: 1.0
|
| 36 |
+
- Macro F1: 0.8861
|
| 37 |
+
- Weighted F1: 0.9115
|
| 38 |
+
|
| 39 |
+
## Known Limitations
|
| 40 |
+
|
| 41 |
+
- Trained on synthetic data only — performance on real classroom questions is unknown.
|
| 42 |
+
- 194 classes with imbalanced distribution; rare LOs may have low recall.
|
| 43 |
+
- TF-IDF features do not capture semantic similarity beyond n-gram overlap.
|
| 44 |
+
- Top-3 accuracy is the primary metric; top-1 may be low for ambiguous questions.
|
| 45 |
+
|
| 46 |
+
## Fallback Behavior
|
| 47 |
+
|
| 48 |
+
When the model is not loaded or confidence is below the threshold (0.55),
|
| 49 |
+
the system falls back to keyword/embedding match against learning_outcomes.embedding_text
|
| 50 |
+
filtered by grade and subject, returning top-3 with rule-based confidence.
|
data/artifacts/models/lo_tagger/training_config.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "lo_tagger",
|
| 3 |
+
"model_version": "lo_tagger_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 3912,
|
| 8 |
+
"validation": 1033,
|
| 9 |
+
"test": 875
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"tfidf_max_features": 10000,
|
| 13 |
+
"ngram_range": [
|
| 14 |
+
1,
|
| 15 |
+
2
|
| 16 |
+
],
|
| 17 |
+
"sublinear_tf": true,
|
| 18 |
+
"svc_max_iter": 5000,
|
| 19 |
+
"calibration_cv": 3,
|
| 20 |
+
"calibration_method": "sigmoid"
|
| 21 |
+
},
|
| 22 |
+
"feature_columns": [
|
| 23 |
+
"question_text"
|
| 24 |
+
],
|
| 25 |
+
"target_column": "lo_id",
|
| 26 |
+
"algorithm": "CalibratedClassifierCV(LinearSVC)"
|
| 27 |
+
}
|
data/artifacts/models/lo_tagger/vectorizer.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aa3bb1d853a64bbb2147a6f4039771b7bacddef6c99e730a59cc28f00fe1657c
|
| 3 |
+
size 31067
|
data/artifacts/models/mastery_model/feature_columns.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"attempt_count",
|
| 3 |
+
"accuracy",
|
| 4 |
+
"average_marks_ratio",
|
| 5 |
+
"average_time_seconds",
|
| 6 |
+
"hint_usage_rate",
|
| 7 |
+
"attendance_percentage",
|
| 8 |
+
"assignment_completion_rate",
|
| 9 |
+
"average_login_per_week",
|
| 10 |
+
"inactive_days_last_14"
|
| 11 |
+
]
|
data/artifacts/models/mastery_model/metrics.json
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "mastery_model",
|
| 3 |
+
"model_version": "mastery_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:11.764140+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 24515,
|
| 9 |
+
"validation": 5218,
|
| 10 |
+
"test": 5187
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"macro_f1": 0.8649,
|
| 15 |
+
"weighted_f1": 0.8952,
|
| 16 |
+
"per_class": {
|
| 17 |
+
"weak": {
|
| 18 |
+
"precision": 0.9661,
|
| 19 |
+
"recall": 0.9626,
|
| 20 |
+
"f1": 0.9643,
|
| 21 |
+
"support": 2247
|
| 22 |
+
},
|
| 23 |
+
"developing": {
|
| 24 |
+
"precision": 0.8884,
|
| 25 |
+
"recall": 0.905,
|
| 26 |
+
"f1": 0.8966,
|
| 27 |
+
"support": 1337
|
| 28 |
+
},
|
| 29 |
+
"proficient": {
|
| 30 |
+
"precision": 0.7831,
|
| 31 |
+
"recall": 0.7385,
|
| 32 |
+
"f1": 0.7601,
|
| 33 |
+
"support": 826
|
| 34 |
+
},
|
| 35 |
+
"mastered": {
|
| 36 |
+
"precision": 0.8234,
|
| 37 |
+
"recall": 0.854,
|
| 38 |
+
"f1": 0.8384,
|
| 39 |
+
"support": 808
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"confusion_matrix": [
|
| 43 |
+
[
|
| 44 |
+
2163,
|
| 45 |
+
84,
|
| 46 |
+
0,
|
| 47 |
+
0
|
| 48 |
+
],
|
| 49 |
+
[
|
| 50 |
+
76,
|
| 51 |
+
1210,
|
| 52 |
+
51,
|
| 53 |
+
0
|
| 54 |
+
],
|
| 55 |
+
[
|
| 56 |
+
0,
|
| 57 |
+
68,
|
| 58 |
+
610,
|
| 59 |
+
148
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
0,
|
| 63 |
+
0,
|
| 64 |
+
118,
|
| 65 |
+
690
|
| 66 |
+
]
|
| 67 |
+
]
|
| 68 |
+
},
|
| 69 |
+
"test": {
|
| 70 |
+
"macro_f1": 0.8754,
|
| 71 |
+
"weighted_f1": 0.9029,
|
| 72 |
+
"per_class": {
|
| 73 |
+
"weak": {
|
| 74 |
+
"precision": 0.9667,
|
| 75 |
+
"recall": 0.9717,
|
| 76 |
+
"f1": 0.9692,
|
| 77 |
+
"support": 2120
|
| 78 |
+
},
|
| 79 |
+
"developing": {
|
| 80 |
+
"precision": 0.9155,
|
| 81 |
+
"recall": 0.9089,
|
| 82 |
+
"f1": 0.9122,
|
| 83 |
+
"support": 1394
|
| 84 |
+
},
|
| 85 |
+
"proficient": {
|
| 86 |
+
"precision": 0.7814,
|
| 87 |
+
"recall": 0.7555,
|
| 88 |
+
"f1": 0.7683,
|
| 89 |
+
"support": 814
|
| 90 |
+
},
|
| 91 |
+
"mastered": {
|
| 92 |
+
"precision": 0.8395,
|
| 93 |
+
"recall": 0.865,
|
| 94 |
+
"f1": 0.8521,
|
| 95 |
+
"support": 859
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
"confusion_matrix": [
|
| 99 |
+
[
|
| 100 |
+
2060,
|
| 101 |
+
60,
|
| 102 |
+
0,
|
| 103 |
+
0
|
| 104 |
+
],
|
| 105 |
+
[
|
| 106 |
+
71,
|
| 107 |
+
1267,
|
| 108 |
+
56,
|
| 109 |
+
0
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
0,
|
| 113 |
+
57,
|
| 114 |
+
615,
|
| 115 |
+
142
|
| 116 |
+
],
|
| 117 |
+
[
|
| 118 |
+
0,
|
| 119 |
+
0,
|
| 120 |
+
116,
|
| 121 |
+
743
|
| 122 |
+
]
|
| 123 |
+
]
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"limitations": [
|
| 127 |
+
"Trained on synthetic data only.",
|
| 128 |
+
"4-class mastery labels derived from synthetic mastery_score thresholds.",
|
| 129 |
+
"All features are numeric; no text or contextual features used.",
|
| 130 |
+
"Class distribution may not reflect real-world mastery patterns."
|
| 131 |
+
]
|
| 132 |
+
}
|
data/artifacts/models/mastery_model/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:51ba404df9382d85b82d30964ed4e1d2e58c5a98948ffc2427a7b105886e9ca5
|
| 3 |
+
size 6856082
|
data/artifacts/models/mastery_model/model_card.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Mastery Model
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** mastery_model
|
| 6 |
+
- **Model Version:** mastery_model_v2_baseline_001
|
| 7 |
+
- **Algorithm:** RandomForestClassifier
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:11.764140+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Predict per-student per-LO mastery label (weak, developing, proficient, mastered)
|
| 15 |
+
based on behavioral and performance features. Used in the mastery prediction
|
| 16 |
+
endpoint to classify student mastery level for a given learning outcome.
|
| 17 |
+
|
| 18 |
+
## Training Data
|
| 19 |
+
|
| 20 |
+
- **Source:** training_mastery_prediction.csv (synthetic dataset v2)
|
| 21 |
+
- **Split Counts:** train=24515, validation=5218, test=5187
|
| 22 |
+
- **Features:** attempt_count, accuracy, average_marks_ratio, average_time_seconds, hint_usage_rate, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14 (all numeric)
|
| 23 |
+
- **Target:** mastery_label (integer 0-3, mapped to weak/developing/proficient/mastered)
|
| 24 |
+
|
| 25 |
+
## Metrics
|
| 26 |
+
|
| 27 |
+
### Validation Set
|
| 28 |
+
- Macro F1: 0.8649
|
| 29 |
+
- Weighted F1: 0.8952
|
| 30 |
+
|
| 31 |
+
### Test Set
|
| 32 |
+
- Macro F1: 0.8754
|
| 33 |
+
- Weighted F1: 0.9029
|
| 34 |
+
|
| 35 |
+
## Per-Class Performance (Test Set)
|
| 36 |
+
|
| 37 |
+
| Class | Precision | Recall | F1 | Support |
|
| 38 |
+
|-------|-----------|--------|-----|---------|
|
| 39 |
+
| weak | 0.9667 | 0.9717 | 0.9692 | 2120 |
|
| 40 |
+
| developing | 0.9155 | 0.9089 | 0.9122 | 1394 |
|
| 41 |
+
| proficient | 0.7814 | 0.7555 | 0.7683 | 814 |
|
| 42 |
+
| mastered | 0.8395 | 0.865 | 0.8521 | 859 |
|
| 43 |
+
|
| 44 |
+
## Known Limitations
|
| 45 |
+
|
| 46 |
+
- Trained on synthetic data only — performance on real student data is unknown.
|
| 47 |
+
- 4-class mastery labels derived from synthetic mastery_score thresholds.
|
| 48 |
+
- All features are numeric; no text or contextual features used.
|
| 49 |
+
- Class distribution may not reflect real-world mastery patterns.
|
| 50 |
+
- No encoding needed since all features are already numeric.
|
| 51 |
+
|
| 52 |
+
## Fallback Behavior
|
| 53 |
+
|
| 54 |
+
When the model is not loaded or confidence is below the threshold (0.55),
|
| 55 |
+
the system falls back to rule-based mastery estimation using mastery_score
|
| 56 |
+
thresholds: <0.4 weak, <0.6 developing, <0.8 proficient, else mastered.
|
data/artifacts/models/mastery_model/training_config.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "mastery_model",
|
| 3 |
+
"model_version": "mastery_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 24515,
|
| 8 |
+
"validation": 5218,
|
| 9 |
+
"test": 5187
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"n_estimators": 100,
|
| 13 |
+
"random_state": 42,
|
| 14 |
+
"algorithm": "RandomForestClassifier"
|
| 15 |
+
},
|
| 16 |
+
"feature_columns": [
|
| 17 |
+
"attempt_count",
|
| 18 |
+
"accuracy",
|
| 19 |
+
"average_marks_ratio",
|
| 20 |
+
"average_time_seconds",
|
| 21 |
+
"hint_usage_rate",
|
| 22 |
+
"attendance_percentage",
|
| 23 |
+
"assignment_completion_rate",
|
| 24 |
+
"average_login_per_week",
|
| 25 |
+
"inactive_days_last_14"
|
| 26 |
+
],
|
| 27 |
+
"target_column": "mastery_label",
|
| 28 |
+
"label_map": {
|
| 29 |
+
"0": "weak",
|
| 30 |
+
"1": "developing",
|
| 31 |
+
"2": "proficient",
|
| 32 |
+
"3": "mastered"
|
| 33 |
+
},
|
| 34 |
+
"algorithm": "RandomForestClassifier"
|
| 35 |
+
}
|
data/artifacts/models/recommender/encoder.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:417a9e093b8090b171f8383bc3772bfca90127b02d17a5b543ec3f5517200d2d
|
| 3 |
+
size 699
|
data/artifacts/models/recommender/feature_columns.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"priority",
|
| 3 |
+
"ai_confidence",
|
| 4 |
+
"recommendation_type",
|
| 5 |
+
"grade",
|
| 6 |
+
"subject"
|
| 7 |
+
]
|
data/artifacts/models/recommender/metrics.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "recommender",
|
| 3 |
+
"model_version": "recommender_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:13.148420+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 5671,
|
| 9 |
+
"validation": 1214,
|
| 10 |
+
"test": 1215
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"roc_auc_clicked": 0.5439,
|
| 15 |
+
"lift_at_10_clicked": 1.1392,
|
| 16 |
+
"roc_auc_is_completed": 0.5303,
|
| 17 |
+
"lift_at_10_is_completed": 1.1056
|
| 18 |
+
},
|
| 19 |
+
"test": {
|
| 20 |
+
"roc_auc_clicked": 0.5486,
|
| 21 |
+
"lift_at_10_clicked": 1.0471,
|
| 22 |
+
"roc_auc_is_completed": 0.5424,
|
| 23 |
+
"lift_at_10_is_completed": 1.0366
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"limitations": [
|
| 27 |
+
"Trained on synthetic data only.",
|
| 28 |
+
"Two separate GBC models \u2014 no joint optimization of clicked + is_completed.",
|
| 29 |
+
"OrdinalEncoder assumes an ordering for priority/recommendation_type/subject.",
|
| 30 |
+
"Lift@10 depends on the distribution of positive labels in the dataset.",
|
| 31 |
+
"No user-level features (e.g., engagement history) included in baseline."
|
| 32 |
+
]
|
| 33 |
+
}
|
data/artifacts/models/recommender/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1917a4e2cb2387cfd5df82b416ea2794d1943bea3d82e47a8e38a5a6bbbe7db4
|
| 3 |
+
size 201534
|
data/artifacts/models/recommender/model_card.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Recommender
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** recommender
|
| 6 |
+
- **Model Version:** recommender_v2_baseline_001
|
| 7 |
+
- **Algorithm:** GradientBoostingClassifier (two models: clicked, is_completed)
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:13.148420+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Predict whether a student will click on a recommendation and whether they will
|
| 15 |
+
complete the recommended content. Used in the recommendation engine to rank
|
| 16 |
+
content by predicted engagement. Two separate binary classifiers are trained:
|
| 17 |
+
one for `clicked` and one for `is_completed`.
|
| 18 |
+
|
| 19 |
+
## Training Data
|
| 20 |
+
|
| 21 |
+
- **Source:** training_recommendation_outcomes.csv (synthetic dataset v2)
|
| 22 |
+
- **Split Counts:** train=5671, validation=1214, test=1215
|
| 23 |
+
- **Features:** priority (OrdinalEncoded), ai_confidence (numeric), recommendation_type (OrdinalEncoded), grade (numeric), subject (OrdinalEncoded)
|
| 24 |
+
- **Targets:** clicked (binary), is_completed (binary)
|
| 25 |
+
|
| 26 |
+
## Metrics
|
| 27 |
+
|
| 28 |
+
### Validation Set
|
| 29 |
+
- ROC-AUC (clicked): 0.5439
|
| 30 |
+
- ROC-AUC (is_completed): 0.5303
|
| 31 |
+
- Lift@10 (clicked): 1.1392
|
| 32 |
+
- Lift@10 (is_completed): 1.1056
|
| 33 |
+
|
| 34 |
+
### Test Set
|
| 35 |
+
- ROC-AUC (clicked): 0.5486
|
| 36 |
+
- ROC-AUC (is_completed): 0.5424
|
| 37 |
+
- Lift@10 (clicked): 1.0471
|
| 38 |
+
- Lift@10 (is_completed): 1.0366
|
| 39 |
+
|
| 40 |
+
## Known Limitations
|
| 41 |
+
|
| 42 |
+
- Trained on synthetic data only — performance on real recommendation data is unknown.
|
| 43 |
+
- Two separate GBC models — no joint optimization of clicked + is_completed.
|
| 44 |
+
- OrdinalEncoder assumes an ordering for priority/recommendation_type/subject.
|
| 45 |
+
- Lift@10 depends on the distribution of positive labels in the dataset.
|
| 46 |
+
- No user-level features (e.g., engagement history) included in baseline.
|
| 47 |
+
- Limited feature set (5 features); adding student history could improve performance.
|
| 48 |
+
|
| 49 |
+
## Fallback Behavior
|
| 50 |
+
|
| 51 |
+
When the model is not loaded or confidence is below threshold, the system
|
| 52 |
+
falls back to knowledge-graph weakest-prerequisite + content_catalog filtered
|
| 53 |
+
by LO + difficulty, ranked by estimated_mastery_gain.
|
data/artifacts/models/recommender/training_config.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "recommender",
|
| 3 |
+
"model_version": "recommender_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 5671,
|
| 8 |
+
"validation": 1214,
|
| 9 |
+
"test": 1215
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"n_estimators": 100,
|
| 13 |
+
"max_depth": 4,
|
| 14 |
+
"random_state": 42,
|
| 15 |
+
"algorithm": "GradientBoostingClassifier",
|
| 16 |
+
"encoder": "OrdinalEncoder"
|
| 17 |
+
},
|
| 18 |
+
"feature_columns": [
|
| 19 |
+
"priority",
|
| 20 |
+
"ai_confidence",
|
| 21 |
+
"recommendation_type",
|
| 22 |
+
"grade",
|
| 23 |
+
"subject"
|
| 24 |
+
],
|
| 25 |
+
"categorical_columns": [
|
| 26 |
+
"priority",
|
| 27 |
+
"recommendation_type",
|
| 28 |
+
"subject"
|
| 29 |
+
],
|
| 30 |
+
"numeric_columns": [
|
| 31 |
+
"ai_confidence",
|
| 32 |
+
"grade"
|
| 33 |
+
],
|
| 34 |
+
"target_columns": [
|
| 35 |
+
"clicked",
|
| 36 |
+
"is_completed"
|
| 37 |
+
],
|
| 38 |
+
"algorithm": "GradientBoostingClassifier"
|
| 39 |
+
}
|
data/artifacts/models/risk_model/feature_columns.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"avg_mastery_score",
|
| 3 |
+
"weak_lo_count",
|
| 4 |
+
"developing_lo_count",
|
| 5 |
+
"mastered_lo_count",
|
| 6 |
+
"avg_confidence",
|
| 7 |
+
"avg_accuracy",
|
| 8 |
+
"avg_marks_ratio",
|
| 9 |
+
"avg_time_seconds",
|
| 10 |
+
"hint_usage_rate",
|
| 11 |
+
"total_attempts",
|
| 12 |
+
"attendance_percentage",
|
| 13 |
+
"assignment_completion_rate",
|
| 14 |
+
"average_login_per_week",
|
| 15 |
+
"inactive_days_last_14",
|
| 16 |
+
"avg_active_minutes",
|
| 17 |
+
"total_logins",
|
| 18 |
+
"avg_video_watch_ratio",
|
| 19 |
+
"total_content_completed",
|
| 20 |
+
"total_quiz_attempts"
|
| 21 |
+
]
|
data/artifacts/models/risk_model/metrics.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "risk_model",
|
| 3 |
+
"model_version": "risk_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"trained_at": "2026-05-21T05:59:12.289643+00:00",
|
| 6 |
+
"seed": 42,
|
| 7 |
+
"split_counts": {
|
| 8 |
+
"train": 1133,
|
| 9 |
+
"validation": 243,
|
| 10 |
+
"test": 244
|
| 11 |
+
},
|
| 12 |
+
"metrics": {
|
| 13 |
+
"validation": {
|
| 14 |
+
"recall_positive": 0.8333,
|
| 15 |
+
"precision_positive": 0.9459,
|
| 16 |
+
"f1_positive": 0.8861,
|
| 17 |
+
"roc_auc": 0.99,
|
| 18 |
+
"per_class": {
|
| 19 |
+
"not_at_risk": {
|
| 20 |
+
"precision": 0.966,
|
| 21 |
+
"recall": 0.99,
|
| 22 |
+
"f1": 0.9779,
|
| 23 |
+
"support": 201
|
| 24 |
+
},
|
| 25 |
+
"at_risk": {
|
| 26 |
+
"precision": 0.9459,
|
| 27 |
+
"recall": 0.8333,
|
| 28 |
+
"f1": 0.8861,
|
| 29 |
+
"support": 42
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"confusion_matrix": [
|
| 33 |
+
[
|
| 34 |
+
199,
|
| 35 |
+
2
|
| 36 |
+
],
|
| 37 |
+
[
|
| 38 |
+
7,
|
| 39 |
+
35
|
| 40 |
+
]
|
| 41 |
+
],
|
| 42 |
+
"risk_level_recall": {
|
| 43 |
+
"high": {
|
| 44 |
+
"recall": 0.8158,
|
| 45 |
+
"support": 38
|
| 46 |
+
},
|
| 47 |
+
"critical": {
|
| 48 |
+
"recall": 1.0,
|
| 49 |
+
"support": 4
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"test": {
|
| 54 |
+
"recall_positive": 0.7812,
|
| 55 |
+
"precision_positive": 0.9259,
|
| 56 |
+
"f1_positive": 0.8475,
|
| 57 |
+
"roc_auc": 0.9899,
|
| 58 |
+
"per_class": {
|
| 59 |
+
"not_at_risk": {
|
| 60 |
+
"precision": 0.9677,
|
| 61 |
+
"recall": 0.9906,
|
| 62 |
+
"f1": 0.979,
|
| 63 |
+
"support": 212
|
| 64 |
+
},
|
| 65 |
+
"at_risk": {
|
| 66 |
+
"precision": 0.9259,
|
| 67 |
+
"recall": 0.7812,
|
| 68 |
+
"f1": 0.8475,
|
| 69 |
+
"support": 32
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"confusion_matrix": [
|
| 73 |
+
[
|
| 74 |
+
210,
|
| 75 |
+
2
|
| 76 |
+
],
|
| 77 |
+
[
|
| 78 |
+
7,
|
| 79 |
+
25
|
| 80 |
+
]
|
| 81 |
+
],
|
| 82 |
+
"risk_level_recall": {
|
| 83 |
+
"high": {
|
| 84 |
+
"recall": 0.7407,
|
| 85 |
+
"support": 27
|
| 86 |
+
},
|
| 87 |
+
"critical": {
|
| 88 |
+
"recall": 1.0,
|
| 89 |
+
"support": 5
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
"limitations": [
|
| 95 |
+
"Trained on synthetic data only.",
|
| 96 |
+
"Binary risk_label derived from synthetic risk_score thresholds.",
|
| 97 |
+
"All features are numeric; no text or contextual features used.",
|
| 98 |
+
"Class imbalance (~16% positive) addressed via class_weight='balanced'.",
|
| 99 |
+
"Critical class (~2%) recall should be monitored separately."
|
| 100 |
+
]
|
| 101 |
+
}
|
data/artifacts/models/risk_model/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6545f4d8f57225e6a77bcc4578307b425be3d3cbdad6ac4c9dd02fa7e636cb28
|
| 3 |
+
size 150104
|
data/artifacts/models/risk_model/model_card.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: Risk Model
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** risk_model
|
| 6 |
+
- **Model Version:** risk_model_v2_baseline_001
|
| 7 |
+
- **Algorithm:** RandomForestClassifier (class_weight="balanced")
|
| 8 |
+
- **Framework:** scikit-learn
|
| 9 |
+
- **Trained At:** 2026-05-21T05:59:12.289643+00:00
|
| 10 |
+
- **Seed:** 42
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
Predict whether a student is at-risk (binary: 0=not at-risk, 1=at-risk) based on
|
| 15 |
+
mastery, performance, and engagement features. Used in the risk prediction endpoint
|
| 16 |
+
to identify students who may need intervention. Primary optimization target is
|
| 17 |
+
recall on the positive class to minimize missed at-risk students.
|
| 18 |
+
|
| 19 |
+
## Training Data
|
| 20 |
+
|
| 21 |
+
- **Source:** training_risk_prediction.csv (synthetic dataset v2)
|
| 22 |
+
- **Split Counts:** train=1133, validation=243, test=244
|
| 23 |
+
- **Features:** avg_mastery_score, weak_lo_count, developing_lo_count, mastered_lo_count, avg_confidence, avg_accuracy, avg_marks_ratio, avg_time_seconds, hint_usage_rate, total_attempts, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, avg_active_minutes, total_logins, avg_video_watch_ratio, total_content_completed, total_quiz_attempts (all numeric, 19 features)
|
| 24 |
+
- **Target:** risk_label (binary 0/1)
|
| 25 |
+
- **Class Imbalance:** ~16% positive class, addressed via class_weight="balanced"
|
| 26 |
+
|
| 27 |
+
## Metrics
|
| 28 |
+
|
| 29 |
+
### Validation Set
|
| 30 |
+
- Recall (positive): 0.8333
|
| 31 |
+
- Precision (positive): 0.9459
|
| 32 |
+
- F1 (positive): 0.8861
|
| 33 |
+
- ROC-AUC: 0.99
|
| 34 |
+
|
| 35 |
+
### Test Set
|
| 36 |
+
- Recall (positive): 0.7812
|
| 37 |
+
- Precision (positive): 0.9259
|
| 38 |
+
- F1 (positive): 0.8475
|
| 39 |
+
- ROC-AUC: 0.9899
|
| 40 |
+
|
| 41 |
+
## Per-Class Performance (Test Set)
|
| 42 |
+
|
| 43 |
+
| Class | Precision | Recall | F1 | Support |
|
| 44 |
+
|-------|-----------|--------|-----|---------|
|
| 45 |
+
| not_at_risk | 0.9677 | 0.9906 | 0.979 | 212 |
|
| 46 |
+
| at_risk | 0.9259 | 0.7812 | 0.8475 | 32 |
|
| 47 |
+
|
| 48 |
+
## Risk Level Recall (Test Set)
|
| 49 |
+
|
| 50 |
+
| Risk Level | Recall | Support |
|
| 51 |
+
|------------|--------|---------|
|
| 52 |
+
| high | 0.7407 | 27 |
|
| 53 |
+
| critical | 1.0 | 5 |
|
| 54 |
+
|
| 55 |
+
## Known Limitations
|
| 56 |
+
|
| 57 |
+
- Trained on synthetic data only — performance on real student data is unknown.
|
| 58 |
+
- Binary risk_label derived from synthetic risk_score thresholds.
|
| 59 |
+
- All features are numeric; no text or contextual features used.
|
| 60 |
+
- Class imbalance (~16% positive) addressed via class_weight="balanced".
|
| 61 |
+
- Critical class (~2%) is very rare; recall on critical should be monitored.
|
| 62 |
+
- No temporal features (trend over time) included in this baseline.
|
| 63 |
+
|
| 64 |
+
## Fallback Behavior
|
| 65 |
+
|
| 66 |
+
When the model is not loaded or confidence is below the threshold (0.55),
|
| 67 |
+
the system falls back to rule-based risk estimation using:
|
| 68 |
+
- inactive_days_last_14 > 7 → high risk
|
| 69 |
+
- attendance_percentage < 60% → high risk
|
| 70 |
+
- avg_mastery_score < 0.4 → medium risk
|
| 71 |
+
- Otherwise → low risk
|
data/artifacts/models/risk_model/training_config.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "risk_model",
|
| 3 |
+
"model_version": "risk_model_v2_baseline_001",
|
| 4 |
+
"dataset_version": "2.0.0",
|
| 5 |
+
"seed": 42,
|
| 6 |
+
"split_counts": {
|
| 7 |
+
"train": 1133,
|
| 8 |
+
"validation": 243,
|
| 9 |
+
"test": 244
|
| 10 |
+
},
|
| 11 |
+
"hyperparameters": {
|
| 12 |
+
"n_estimators": 100,
|
| 13 |
+
"class_weight": "balanced",
|
| 14 |
+
"random_state": 42,
|
| 15 |
+
"algorithm": "RandomForestClassifier"
|
| 16 |
+
},
|
| 17 |
+
"feature_columns": [
|
| 18 |
+
"avg_mastery_score",
|
| 19 |
+
"weak_lo_count",
|
| 20 |
+
"developing_lo_count",
|
| 21 |
+
"mastered_lo_count",
|
| 22 |
+
"avg_confidence",
|
| 23 |
+
"avg_accuracy",
|
| 24 |
+
"avg_marks_ratio",
|
| 25 |
+
"avg_time_seconds",
|
| 26 |
+
"hint_usage_rate",
|
| 27 |
+
"total_attempts",
|
| 28 |
+
"attendance_percentage",
|
| 29 |
+
"assignment_completion_rate",
|
| 30 |
+
"average_login_per_week",
|
| 31 |
+
"inactive_days_last_14",
|
| 32 |
+
"avg_active_minutes",
|
| 33 |
+
"total_logins",
|
| 34 |
+
"avg_video_watch_ratio",
|
| 35 |
+
"total_content_completed",
|
| 36 |
+
"total_quiz_attempts"
|
| 37 |
+
],
|
| 38 |
+
"target_column": "risk_label",
|
| 39 |
+
"label_map": {
|
| 40 |
+
"0": "not_at_risk",
|
| 41 |
+
"1": "at_risk"
|
| 42 |
+
},
|
| 43 |
+
"algorithm": "RandomForestClassifier"
|
| 44 |
+
}
|
data/learning_outcome_os_dataset_v2/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Learning Outcome OS AI-Ready Expanded Dataset v2
|
| 2 |
+
|
| 3 |
+
Expanded synthetic, CBSE/NCERT LO-aligned, model-ready dataset for LO tagging, Bloom classification, mastery prediction, recommendation, risk prediction, subjective answer scoring, teacher feedback, and digital twin modelling.
|
| 4 |
+
|
| 5 |
+
All names are synthetic. No real PII is included. CSVs are pre-cleaned with stable IDs, target labels, and train/validation/test split columns.
|
| 6 |
+
|
| 7 |
+
## Tables
|
| 8 |
+
### schools.csv
|
| 9 |
+
- Rows: 3
|
| 10 |
+
- Columns: school_id, school_name, board, state, city, region_type, academic_year, is_synthetic
|
| 11 |
+
|
| 12 |
+
### classes.csv
|
| 13 |
+
- Rows: 18
|
| 14 |
+
- Columns: class_id, school_id, grade, section, class_name, academic_year, class_teacher_id
|
| 15 |
+
|
| 16 |
+
### subjects.csv
|
| 17 |
+
- Rows: 9
|
| 18 |
+
- Columns: subject_id, grade, subject, subject_code
|
| 19 |
+
|
| 20 |
+
### chapters.csv
|
| 21 |
+
- Rows: 52
|
| 22 |
+
- Columns: chapter_id, grade, subject, chapter, chapter_order, lo_count, is_active
|
| 23 |
+
|
| 24 |
+
### teachers.csv
|
| 25 |
+
- Rows: 54
|
| 26 |
+
- Columns: teacher_id, school_id, class_id, teacher_name, grade, section, subject, email, experience_years, ai_feedback_participation_rate
|
| 27 |
+
|
| 28 |
+
### student_profiles.csv
|
| 29 |
+
- Rows: 540
|
| 30 |
+
- Columns: student_id, school_id, class_id, grade, section, roll_number, student_name, learning_style, learner_archetype, baseline_level, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, parent_contact_available, is_synthetic, train_split
|
| 31 |
+
|
| 32 |
+
### learning_outcomes.csv
|
| 33 |
+
- Rows: 194
|
| 34 |
+
- Columns: lo_id, grade, subject, chapter, competency, title, description, difficulty, bloom_level, source_framework, source_pdf, source_pages, alignment_confidence, difficulty_score, bloom_score, embedding_text, is_active, train_split
|
| 35 |
+
|
| 36 |
+
### lo_dependencies.csv
|
| 37 |
+
- Rows: 174
|
| 38 |
+
- Columns: lo_id, prerequisite_lo_id, relationship_type, strength
|
| 39 |
+
|
| 40 |
+
### questions.csv
|
| 41 |
+
- Rows: 5,820
|
| 42 |
+
- Columns: question_id, lo_id, grade, subject, chapter, question_text, question_type, difficulty, difficulty_score, bloom_level, bloom_score, correct_answer, rubric, max_marks, source, source_lo_pdf, alignment_confidence, embedding_text, train_split
|
| 43 |
+
|
| 44 |
+
### question_options.csv
|
| 45 |
+
- Rows: 7,760
|
| 46 |
+
- Columns: question_id, option_label, option_text, is_correct
|
| 47 |
+
|
| 48 |
+
### content_catalog.csv
|
| 49 |
+
- Rows: 1,552
|
| 50 |
+
- Columns: content_id, lo_id, grade, subject, chapter, title, content_type, target_use, difficulty, duration_minutes, language, description, estimated_mastery_gain, embedding_text, is_active, train_split
|
| 51 |
+
|
| 52 |
+
### assessments.csv
|
| 53 |
+
- Rows: 216
|
| 54 |
+
- Columns: assessment_id, school_id, class_id, grade, section, subject, assessment_name, assessment_type, scheduled_date, max_marks, question_count, academic_year, train_split
|
| 55 |
+
|
| 56 |
+
### assessment_questions.csv
|
| 57 |
+
- Rows: 2,592
|
| 58 |
+
- Columns: assessment_id, question_id, question_order
|
| 59 |
+
|
| 60 |
+
### student_attempts.csv
|
| 61 |
+
- Rows: 77,760
|
| 62 |
+
- Columns: attempt_id, assessment_id, school_id, class_id, student_id, question_id, lo_id, grade, section, subject, question_type, difficulty_score, bloom_score, is_correct, marks_obtained, max_marks, marks_ratio, time_taken_seconds, hint_used, attempt_number, submitted_at, train_split
|
| 63 |
+
|
| 64 |
+
### initial_mastery_profiles.csv
|
| 65 |
+
- Rows: 34,920
|
| 66 |
+
- Columns: student_id, lo_id, grade, section, subject, chapter, train_split, attempt_count, accuracy, average_marks_ratio, average_time_seconds, hint_usage_rate, mastery_score, status, confidence, last_updated
|
| 67 |
+
|
| 68 |
+
### mastery_profiles.csv
|
| 69 |
+
- Rows: 34,920
|
| 70 |
+
- Columns: student_id, lo_id, grade, section, subject, chapter, train_split, attempt_count, accuracy, average_marks_ratio, average_time_seconds, hint_usage_rate, mastery_score, status, confidence, last_updated
|
| 71 |
+
|
| 72 |
+
### engagement_logs.csv
|
| 73 |
+
- Rows: 108,000
|
| 74 |
+
- Columns: engagement_id, student_id, school_id, class_id, activity_date, login_count, active_minutes, content_completed_count, quiz_attempt_count, recommendation_click_count, video_watch_ratio, discussion_posts, device_type, train_split
|
| 75 |
+
|
| 76 |
+
### risk_profiles.csv
|
| 77 |
+
- Rows: 1,620
|
| 78 |
+
- Columns: risk_prediction_id, student_id, school_id, class_id, grade, section, subject, risk_score, risk_level, risk_label, primary_reasons, recommended_intervention, model_version, generated_at, confidence, train_split
|
| 79 |
+
|
| 80 |
+
### recommendations.csv
|
| 81 |
+
- Rows: 8,100
|
| 82 |
+
- Columns: recommendation_id, student_id, school_id, class_id, lo_id, content_id, grade, section, subject, recommendation_type, priority, reason, ai_confidence, generated_at, shown_to_student, clicked, is_completed, observed_mastery_gain, model_version, train_split
|
| 83 |
+
|
| 84 |
+
### teacher_interventions.csv
|
| 85 |
+
- Rows: 162
|
| 86 |
+
- Columns: intervention_id, school_id, class_id, teacher_id, grade, section, subject, lo_id, intervention_type, affected_students, avg_mastery_before, suggested_action, scheduled_week, status, expected_mastery_gain, generated_by_ai, train_split
|
| 87 |
+
|
| 88 |
+
### subjective_answers.csv
|
| 89 |
+
- Rows: 12,000
|
| 90 |
+
- Columns: answer_id, attempt_id, student_id, question_id, lo_id, grade, subject, question_type, student_answer, model_answer, rubric, max_marks, teacher_marks, ai_predicted_marks, absolute_error, rubric_match_score, concept_coverage_score, feedback_text, teacher_review_required, train_split
|
| 91 |
+
|
| 92 |
+
### teacher_feedback.csv
|
| 93 |
+
- Rows: 5,986
|
| 94 |
+
- Columns: feedback_id, teacher_id, student_id, related_entity_type, related_entity_id, feedback_type, teacher_rating, correction_required, correction_text, created_at, train_split
|
| 95 |
+
|
| 96 |
+
### student_digital_twins.csv
|
| 97 |
+
- Rows: 540
|
| 98 |
+
- Columns: digital_twin_id, student_id, school_id, class_id, grade, section, overall_mastery_score, strongest_subject, weakest_subject, top_weak_lo_ids, learning_speed_score, consistency_score, preferred_content_type, current_risk_level, current_risk_score, recommended_next_action, last_updated, train_split
|
| 99 |
+
|
| 100 |
+
### ai_prediction_logs.csv
|
| 101 |
+
- Rows: 16,620
|
| 102 |
+
- Columns: prediction_log_id, model_name, model_version, entity_type, entity_id, prediction_output, confidence, latency_ms, created_at, train_split
|
| 103 |
+
|
| 104 |
+
### ml_features_student_subject.csv
|
| 105 |
+
- Rows: 1,620
|
| 106 |
+
- Columns: feature_row_id, student_id, school_id, class_id, grade, section, subject, avg_mastery_score, weak_lo_count, developing_lo_count, mastered_lo_count, avg_confidence, avg_accuracy, avg_marks_ratio, avg_time_seconds, hint_usage_rate, total_attempts, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, avg_active_minutes, total_logins, avg_video_watch_ratio, total_content_completed, total_quiz_attempts, risk_score, risk_level, risk_label, train_split
|
| 107 |
+
|
| 108 |
+
### ml_features_student_lo.csv
|
| 109 |
+
- Rows: 34,920
|
| 110 |
+
- Columns: feature_row_id, student_id, lo_id, grade, section, subject, chapter, train_split, attempt_count, accuracy, average_marks_ratio, average_time_seconds, hint_usage_rate, mastery_score, status, confidence, last_updated, school_id, class_id, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, mastery_label
|
| 111 |
+
|
| 112 |
+
### training_lo_tagging.csv
|
| 113 |
+
- Rows: 5,820
|
| 114 |
+
- Columns: question_id, question_text, embedding_text, lo_id, grade, subject, chapter, difficulty, difficulty_score, bloom_level, bloom_score, train_split
|
| 115 |
+
|
| 116 |
+
### training_bloom_classification.csv
|
| 117 |
+
- Rows: 5,820
|
| 118 |
+
- Columns: question_id, question_text, embedding_text, bloom_level, bloom_score, grade, subject, question_type, train_split
|
| 119 |
+
|
| 120 |
+
### training_risk_prediction.csv
|
| 121 |
+
- Rows: 1,620
|
| 122 |
+
- Columns: feature_row_id, student_id, school_id, class_id, grade, section, subject, avg_mastery_score, weak_lo_count, developing_lo_count, mastered_lo_count, avg_confidence, avg_accuracy, avg_marks_ratio, avg_time_seconds, hint_usage_rate, total_attempts, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, avg_active_minutes, total_logins, avg_video_watch_ratio, total_content_completed, total_quiz_attempts, risk_score, risk_level, risk_label, train_split
|
| 123 |
+
|
| 124 |
+
### training_mastery_prediction.csv
|
| 125 |
+
- Rows: 34,920
|
| 126 |
+
- Columns: feature_row_id, student_id, lo_id, grade, section, subject, chapter, train_split, attempt_count, accuracy, average_marks_ratio, average_time_seconds, hint_usage_rate, mastery_score, status, confidence, last_updated, school_id, class_id, attendance_percentage, assignment_completion_rate, average_login_per_week, inactive_days_last_14, mastery_label
|
| 127 |
+
|
| 128 |
+
### training_answer_scoring.csv
|
| 129 |
+
- Rows: 12,000
|
| 130 |
+
- Columns: answer_id, question_id, student_id, lo_id, grade, subject, question_type, student_answer, model_answer, rubric, max_marks, teacher_marks, ai_predicted_marks, rubric_match_score, concept_coverage_score, teacher_review_required, train_split
|
| 131 |
+
|
| 132 |
+
### training_recommendation_outcomes.csv
|
| 133 |
+
- Rows: 8,100
|
| 134 |
+
- Columns: recommendation_id, student_id, lo_id, content_id, grade, subject, recommendation_type, priority, ai_confidence, clicked, is_completed, observed_mastery_gain, train_split
|
data/learning_outcome_os_dataset_v2/ai_prediction_logs.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f54c6a4a70c988f7098903b9204509644a410207bd61b3c75b0b08b5cb7c6984
|
| 3 |
+
size 3227380
|
data/learning_outcome_os_dataset_v2/assessment_questions.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:87f6069f6e47175b4f9a1dfe479026d4482e48906f63c978c72d8bda6fab3330
|
| 3 |
+
size 49937
|
data/learning_outcome_os_dataset_v2/assessments.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3358b494742424fd733167015ae1eada0a6e767d079d0bdad647df3a9383bfb
|
| 3 |
+
size 23609
|
data/learning_outcome_os_dataset_v2/chapters.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b8d84f61d2f51c609b22888a25cfb92c1a3becdec9fd50bdd1bd25b0869a5cb0
|
| 3 |
+
size 2838
|
data/learning_outcome_os_dataset_v2/classes.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aadcf230ada60fd703e3147447c2acfa1df778223cda012eb8c86cb76d99992c
|
| 3 |
+
size 939
|
data/learning_outcome_os_dataset_v2/content_catalog.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0b7c6666bba870a5f7c8562b9bc2b98a1e86b41eae1e0db8b93a2a2b434aea42
|
| 3 |
+
size 847643
|
data/learning_outcome_os_dataset_v2/dataset_metadata.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset_name": "Learning Outcome OS AI-Ready Expanded Dataset",
|
| 3 |
+
"version": "2.0.0",
|
| 4 |
+
"generated_at": "2026-05-20T11:24:37Z",
|
| 5 |
+
"seed": 20260520,
|
| 6 |
+
"scope": {
|
| 7 |
+
"schools": 3,
|
| 8 |
+
"classes": 18,
|
| 9 |
+
"grades": [
|
| 10 |
+
6,
|
| 11 |
+
7,
|
| 12 |
+
8
|
| 13 |
+
],
|
| 14 |
+
"sections": [
|
| 15 |
+
"A",
|
| 16 |
+
"B"
|
| 17 |
+
],
|
| 18 |
+
"subjects": [
|
| 19 |
+
"Mathematics",
|
| 20 |
+
"Science",
|
| 21 |
+
"Social Science"
|
| 22 |
+
],
|
| 23 |
+
"students_per_class_section": 30,
|
| 24 |
+
"total_students": 540,
|
| 25 |
+
"source_alignment": [
|
| 26 |
+
"mathematics_LO.pdf",
|
| 27 |
+
"science_LO.pdf",
|
| 28 |
+
"social_science_LO.pdf"
|
| 29 |
+
],
|
| 30 |
+
"source_base_dataset": "cbse_lo_aligned_synthetic_dataset_classes_6_8.zip"
|
| 31 |
+
},
|
| 32 |
+
"model_ready_design": {
|
| 33 |
+
"no_null_values": true,
|
| 34 |
+
"stable_primary_keys": true,
|
| 35 |
+
"foreign_keys_valid": true,
|
| 36 |
+
"train_validation_test_splits": true,
|
| 37 |
+
"numeric_features_available": true,
|
| 38 |
+
"nlp_embedding_text_fields_available": true,
|
| 39 |
+
"human_feedback_tables_available": true,
|
| 40 |
+
"target_labels_available": [
|
| 41 |
+
"lo_id",
|
| 42 |
+
"bloom_level",
|
| 43 |
+
"difficulty_score",
|
| 44 |
+
"mastery_score",
|
| 45 |
+
"mastery_label",
|
| 46 |
+
"risk_label",
|
| 47 |
+
"teacher_marks",
|
| 48 |
+
"clicked",
|
| 49 |
+
"is_completed"
|
| 50 |
+
]
|
| 51 |
+
},
|
| 52 |
+
"table_counts": {
|
| 53 |
+
"schools.csv": 3,
|
| 54 |
+
"classes.csv": 18,
|
| 55 |
+
"subjects.csv": 9,
|
| 56 |
+
"chapters.csv": 52,
|
| 57 |
+
"teachers.csv": 54,
|
| 58 |
+
"student_profiles.csv": 540,
|
| 59 |
+
"learning_outcomes.csv": 194,
|
| 60 |
+
"lo_dependencies.csv": 174,
|
| 61 |
+
"questions.csv": 5820,
|
| 62 |
+
"question_options.csv": 7760,
|
| 63 |
+
"content_catalog.csv": 1552,
|
| 64 |
+
"assessments.csv": 216,
|
| 65 |
+
"assessment_questions.csv": 2592,
|
| 66 |
+
"student_attempts.csv": 77760,
|
| 67 |
+
"initial_mastery_profiles.csv": 34920,
|
| 68 |
+
"mastery_profiles.csv": 34920,
|
| 69 |
+
"engagement_logs.csv": 108000,
|
| 70 |
+
"risk_profiles.csv": 1620,
|
| 71 |
+
"recommendations.csv": 8100,
|
| 72 |
+
"teacher_interventions.csv": 162,
|
| 73 |
+
"subjective_answers.csv": 12000,
|
| 74 |
+
"teacher_feedback.csv": 5986,
|
| 75 |
+
"student_digital_twins.csv": 540,
|
| 76 |
+
"ai_prediction_logs.csv": 16620,
|
| 77 |
+
"ml_features_student_subject.csv": 1620,
|
| 78 |
+
"ml_features_student_lo.csv": 34920,
|
| 79 |
+
"training_lo_tagging.csv": 5820,
|
| 80 |
+
"training_bloom_classification.csv": 5820,
|
| 81 |
+
"training_risk_prediction.csv": 1620,
|
| 82 |
+
"training_mastery_prediction.csv": 34920,
|
| 83 |
+
"training_answer_scoring.csv": 12000,
|
| 84 |
+
"training_recommendation_outcomes.csv": 8100
|
| 85 |
+
},
|
| 86 |
+
"status_labels": [
|
| 87 |
+
"weak",
|
| 88 |
+
"developing",
|
| 89 |
+
"proficient",
|
| 90 |
+
"mastered"
|
| 91 |
+
],
|
| 92 |
+
"risk_labels": [
|
| 93 |
+
"low",
|
| 94 |
+
"medium",
|
| 95 |
+
"high",
|
| 96 |
+
"critical"
|
| 97 |
+
]
|
| 98 |
+
}
|
data/learning_outcome_os_dataset_v2/engagement_logs.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:18ba724e544dffd9b0b50cfd4a17d5f0cec1acb5448e8164335d99aa73a3f9db
|
| 3 |
+
size 8509736
|
data/learning_outcome_os_dataset_v2/generation_script.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
import json, random, math, shutil, zipfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
from collections import defaultdict
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
SEED=20260520
|
| 10 |
+
random.seed(SEED); np.random.seed(SEED)
|
| 11 |
+
BASE=Path('/mnt/data/cbse_ds')
|
| 12 |
+
OUT=Path('/mnt/data/learning_outcome_os_ai_dataset_v2')
|
| 13 |
+
ZIP=Path('/mnt/data/learning_outcome_os_ai_ready_expanded_dataset_v2.zip')
|
| 14 |
+
if OUT.exists(): shutil.rmtree(OUT)
|
| 15 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 16 |
+
if ZIP.exists(): ZIP.unlink()
|
| 17 |
+
|
| 18 |
+
def sigmoid(x): return 1/(1+math.exp(-x))
|
| 19 |
+
def clip(x,a,b): return max(a,min(b,x))
|
| 20 |
+
def clean(s): return ' '.join(str(s).replace('\n',' ').replace('\r',' ').split()).strip()
|
| 21 |
+
def split_id(x):
|
| 22 |
+
h=sum((i+1)*ord(c) for i,c in enumerate(str(x)))%100
|
| 23 |
+
return 'train' if h<70 else ('validation' if h<85 else 'test')
|
| 24 |
+
def dstr(dt): return dt.strftime('%Y-%m-%d')
|
| 25 |
+
def tstr(dt): return dt.strftime('%Y-%m-%dT%H:%M:%S')
|
| 26 |
+
from time import time
|
| 27 |
+
_t0=time()
|
| 28 |
+
def mark(x):
|
| 29 |
+
with open('/mnt/data/fast_progress.txt','a') as f: f.write(f'{time()-_t0:.1f}s {x}\n')
|
| 30 |
+
mark('start')
|
| 31 |
+
|
| 32 |
+
diff_map={'Easy':1,'Medium':2,'Hard':3}; diff_rev={1:'Easy',2:'Medium',3:'Hard'}
|
| 33 |
+
bloom_map={'Remember':1,'Understand':2,'Apply':3,'Analyze':4,'Evaluate':5,'Create':6}; bloom_rev={v:k for k,v in bloom_map.items()}
|
| 34 |
+
subjects_list=['Mathematics','Science','Social Science']
|
| 35 |
+
|
| 36 |
+
# Source LO layer
|
| 37 |
+
lo=pd.read_csv(BASE/'learning_outcomes.csv').fillna('')
|
| 38 |
+
lo['title']=lo['title'].map(clean); lo['description']=lo['description'].map(clean); lo['chapter']=lo['chapter'].map(clean); lo['competency']=lo['competency'].map(clean)
|
| 39 |
+
lo['difficulty_score']=lo['difficulty'].map(diff_map).fillna(2).astype(int)
|
| 40 |
+
lo['bloom_score']=lo['bloom_level'].map(bloom_map).fillna(2).astype(int)
|
| 41 |
+
lo['embedding_text']=(lo['subject'].astype(str)+' | Grade '+lo['grade'].astype(str)+' | '+lo['chapter']+' | '+lo['competency']+' | '+lo['title']+' | '+lo['description']).map(clean)
|
| 42 |
+
lo['is_active']=1; lo['train_split']=lo['lo_id'].map(split_id)
|
| 43 |
+
mark('lo')
|
| 44 |
+
learning_outcomes=lo.copy()
|
| 45 |
+
lo_map=learning_outcomes.set_index('lo_id').to_dict('index')
|
| 46 |
+
los_by_grade={g: learning_outcomes[learning_outcomes.grade==g].to_dict('records') for g in [6,7,8]}
|
| 47 |
+
los_by_grade_subject={(g,s): learning_outcomes[(learning_outcomes.grade==g)&(learning_outcomes.subject==s)].to_dict('records') for g in [6,7,8] for s in subjects_list}
|
| 48 |
+
lo_dependencies=pd.read_csv(BASE/'lo_dependencies.csv').fillna('')
|
| 49 |
+
chapters=pd.read_csv(BASE/'chapters.csv').fillna(''); chapters['is_active']=1
|
| 50 |
+
|
| 51 |
+
# Dimensions
|
| 52 |
+
school_defs=[('SCH001','Nirmal Vidya Public School','CBSE','Chhattisgarh','Bhilai','Urban'),('SCH002','Pragati Central School','CBSE','Chhattisgarh','Durg','Semi-Urban'),('SCH003','Saraswati Learning Academy','CBSE','Maharashtra','Nagpur','Urban')]
|
| 53 |
+
schools=pd.DataFrame([{'school_id':a,'school_name':b,'board':c,'state':d,'city':e,'region_type':f,'academic_year':'2026-27','is_synthetic':1} for a,b,c,d,e,f in school_defs])
|
| 54 |
+
classes=[]
|
| 55 |
+
for sid, *_ in school_defs:
|
| 56 |
+
for grade in [6,7,8]:
|
| 57 |
+
for sec in ['A','B']:
|
| 58 |
+
classes.append({'class_id':f'{sid}_G{grade}{sec}','school_id':sid,'grade':grade,'section':sec,'class_name':f'Class {grade}-{sec}','academic_year':'2026-27'})
|
| 59 |
+
classes=pd.DataFrame(classes)
|
| 60 |
+
subjects=pd.DataFrame([{'subject_id':f'{code}{g}','grade':g,'subject':subj,'subject_code':code} for g in [6,7,8] for subj,code in [('Mathematics','MATH'),('Science','SCI'),('Social Science','SOC')]])
|
| 61 |
+
|
| 62 |
+
first=['Aarav','Vivaan','Aditya','Ishaan','Kabir','Arjun','Rohan','Karan','Priya','Ananya','Isha','Riya','Neha','Pooja','Kavita','Meera','Sneha','Aditi','Nisha','Vikas','Manish','Deepak','Suman','Rakesh','Naveen','Sanjay','Anita','Swati']
|
| 63 |
+
last=['Sharma','Verma','Sahu','Patel','Gupta','Singh','Yadav','Mishra','Tiwari','Choudhary','Jain','Rao','Das','Nair','Khan','Joshi','Dubey','Agrawal']
|
| 64 |
+
|
| 65 |
+
teachers=[]; tid=1
|
| 66 |
+
for c in classes.to_dict('records'):
|
| 67 |
+
for subj in subjects_list:
|
| 68 |
+
fn,ln=random.choice(first),random.choice(last)
|
| 69 |
+
teachers.append({'teacher_id':f'TCH{tid:04d}','school_id':c['school_id'],'class_id':c['class_id'],'teacher_name':f'{fn} {ln}','grade':c['grade'],'section':c['section'],'subject':subj,'email':f'{fn.lower()}.{ln.lower()}.{tid}@school.example','experience_years':random.randint(2,18),'ai_feedback_participation_rate':round(random.uniform(.55,.95),2)})
|
| 70 |
+
tid+=1
|
| 71 |
+
mark('teachers')
|
| 72 |
+
teachers=pd.DataFrame(teachers)
|
| 73 |
+
classes['class_teacher_id']=classes['class_id'].map(teachers.groupby('class_id')['teacher_id'].first().to_dict())
|
| 74 |
+
teacher_lookup={(r.class_id,r.subject):r.teacher_id for _,r in teachers.iterrows()}
|
| 75 |
+
|
| 76 |
+
# Students
|
| 77 |
+
archetypes={
|
| 78 |
+
'high_performer':(1.1,(90,98),(88,99),(5,7),.10),'consistent_average':(.25,(82,94),(72,90),(3,6),.07),'conceptually_weak_active':(-.45,(82,94),(70,88),(4,7),.12),'low_engagement':(-.25,(68,84),(40,68),(1,4),.03),'at_risk':(-1.0,(55,76),(25,55),(0,3),.01),'fast_improver':(-.15,(84,96),(76,94),(4,7),.20)}
|
| 79 |
+
weights=[.14,.32,.18,.14,.10,.12]; styles=['visual','auditory','reading_writing','kinesthetic','mixed']
|
| 80 |
+
students=[]; ability={}; sidn=1; students_by_class=defaultdict(list)
|
| 81 |
+
for c in classes.to_dict('records'):
|
| 82 |
+
for roll in range(1,31):
|
| 83 |
+
arch=random.choices(list(archetypes),weights=weights,k=1)[0]
|
| 84 |
+
mean,att_rng,comp_rng,login_rng,growth=archetypes[arch]
|
| 85 |
+
base=np.random.normal(mean,.35); subj_offsets={s:np.random.normal(0,.35) for s in subjects_list}
|
| 86 |
+
sid=f'STU{sidn:05d}'; sidn+=1
|
| 87 |
+
att=random.randint(*att_rng); comp=random.randint(*comp_rng); login=random.randint(*login_rng)
|
| 88 |
+
inactive=int(clip(round(np.random.normal(3+(100-att)/10+(5-login),2)),0,14))
|
| 89 |
+
ability[sid]={s:base+subj_offsets[s] for s in subjects_list}; ability[sid]['growth']=growth; ability[sid]['arch']=arch
|
| 90 |
+
row={'student_id':sid,'school_id':c['school_id'],'class_id':c['class_id'],'grade':c['grade'],'section':c['section'],'roll_number':roll,'student_name':f'{random.choice(first)} {random.choice(last)}','learning_style':random.choice(styles),'learner_archetype':arch,'baseline_level':'high' if base>.65 else ('medium' if base>-.45 else 'low'),'attendance_percentage':att,'assignment_completion_rate':comp,'average_login_per_week':login,'inactive_days_last_14':inactive,'parent_contact_available':1,'is_synthetic':1,'train_split':split_id(sid)}
|
| 91 |
+
students.append(row); students_by_class[c['class_id']].append(sid)
|
| 92 |
+
mark('students')
|
| 93 |
+
student_profiles=pd.DataFrame(students); student_meta=student_profiles.set_index('student_id').to_dict('index')
|
| 94 |
+
|
| 95 |
+
# Questions
|
| 96 |
+
q_plan=[('MCQ',10),('SHORT_ANSWER',8),('CASE_BASED',6),('LONG_ANSWER',4),('ORAL_PROMPT',2)]
|
| 97 |
+
q_templates={
|
| 98 |
+
'MCQ':['Which option best demonstrates the learning outcome: {title}?','Which choice correctly shows how to {title_lc}?','Which is the best example of {title_lc}?'],
|
| 99 |
+
'SHORT_ANSWER':['Answer briefly: how can a learner {title_lc} in {chapter}?','Write two points to show: {title}.','State the key idea behind: {title}.'],
|
| 100 |
+
'CASE_BASED':['A student faces a real-life situation from {chapter}. Describe how the student can {title_lc}.','In a classroom activity on {chapter}, how would you demonstrate: {title}?'],
|
| 101 |
+
'LONG_ANSWER':['Explain in detail with an example: {title}.','Discuss the process, example, and application related to: {title}.'],
|
| 102 |
+
'ORAL_PROMPT':['Oral prompt: explain aloud how you would {title_lc}.','Class discussion prompt: what does this outcome mean - {title}?']}
|
| 103 |
+
questions=[]; options=[]; qid=1; q_by_grade_subj_type=defaultdict(list)
|
| 104 |
+
for l in learning_outcomes.to_dict('records'):
|
| 105 |
+
for qtype,count in q_plan:
|
| 106 |
+
for _ in range(count):
|
| 107 |
+
ds=int(clip(l['difficulty_score']+np.random.choice([-1,0,1],p=[.12,.72,.16]),1,3)); bs=int(clip(l['bloom_score']+np.random.choice([-1,0,1],p=[.12,.68,.20]),1,6))
|
| 108 |
+
title=clean(l['title']); title_lc=title[:1].lower()+title[1:]
|
| 109 |
+
text=random.choice(q_templates[qtype]).format(title=title,title_lc=title_lc,chapter=l['chapter'])
|
| 110 |
+
qid_str=f'Q{qid:06d}'; max_marks={'MCQ':1,'SHORT_ANSWER':2,'CASE_BASED':3,'LONG_ANSWER':5,'ORAL_PROMPT':2}[qtype]
|
| 111 |
+
row={'question_id':qid_str,'lo_id':l['lo_id'],'grade':int(l['grade']),'subject':l['subject'],'chapter':l['chapter'],'question_text':clean(text),'question_type':qtype,'difficulty':diff_rev[ds],'difficulty_score':ds,'bloom_level':bloom_rev[bs],'bloom_score':bs,'correct_answer':'A response that accurately demonstrates the learning outcome.','rubric':f'Award marks for accurate concept use, relevant example, and alignment with LO {l["lo_id"]}.','max_marks':max_marks,'source':'synthetic_cbse_lo_aligned_v2','source_lo_pdf':l.get('source_pdf','unknown'),'alignment_confidence':round(random.uniform(.88,.97),2),'embedding_text':clean(f'{l["subject"]} Grade {l["grade"]} {l["chapter"]} {text} {title}'),'train_split':split_id(qid_str)}
|
| 112 |
+
questions.append(row); q_by_grade_subj_type[(int(l['grade']),l['subject'],qtype)].append(qid_str)
|
| 113 |
+
if qtype=='MCQ':
|
| 114 |
+
correct=random.choice(['A','B','C','D'])
|
| 115 |
+
for lab in ['A','B','C','D']:
|
| 116 |
+
options.append({'question_id':qid_str,'option_label':lab,'option_text':'Correctly demonstrates the stated learning outcome.' if lab==correct else random.choice(['Partially related but misses the key concept.','Uses an incorrect example.','Focuses on an unrelated concept.','Shows a common misconception.']),'is_correct':1 if lab==correct else 0})
|
| 117 |
+
qid+=1
|
| 118 |
+
mark('questions')
|
| 119 |
+
questions=pd.DataFrame(questions); question_options=pd.DataFrame(options); q_map=questions.set_index('question_id').to_dict('index')
|
| 120 |
+
|
| 121 |
+
# Content
|
| 122 |
+
ctypes=[('video',8,.07,'practice'),('worksheet',18,.09,'practice'),('interactive_activity',14,.11,'practice'),('flashcard_set',6,.05,'remediation'),('diagnostic_quiz',12,.08,'practice'),('remedial_notes',10,.10,'remediation'),('practice_quiz',15,.09,'practice'),('advanced_challenge',20,.08,'enrichment')]
|
| 123 |
+
content=[]; cid=1; content_by_lo=defaultdict(list)
|
| 124 |
+
for l in learning_outcomes.to_dict('records'):
|
| 125 |
+
for ctype,dur,gain,target in ctypes:
|
| 126 |
+
row={'content_id':f'CNT{cid:06d}','lo_id':l['lo_id'],'grade':int(l['grade']),'subject':l['subject'],'chapter':l['chapter'],'title':clean(f'{l["title"]} - {ctype.replace("_"," ").title()}'),'content_type':ctype,'target_use':target,'difficulty':'Hard' if target=='enrichment' else ('Easy' if target=='remediation' else l['difficulty']),'duration_minutes':max(4,int(np.random.normal(dur,3))),'language':random.choice(['English','English','English','Hindi Support']),'description':clean(f'{ctype.replace("_"," ").title()} aligned to {l["lo_id"]}: {l["description"]}'),'estimated_mastery_gain':round(clip(np.random.normal(gain,.025),.03,.18),2),'embedding_text':clean(f'{l["subject"]} {l["chapter"]} {l["title"]} {ctype} {l["description"]}'),'is_active':1,'train_split':split_id(f'CNT{cid:06d}')}
|
| 127 |
+
content.append(row); content_by_lo[l['lo_id']].append(row); cid+=1
|
| 128 |
+
mark('content')
|
| 129 |
+
content_catalog=pd.DataFrame(content)
|
| 130 |
+
|
| 131 |
+
# Assessments and attempts
|
| 132 |
+
assessments=[]; assessment_questions=[]; attempts=[]; agg=defaultdict(lambda:{'cnt':0,'correct':0,'marks':0.0,'time':0,'hint':0,'last':'2026-06-01'}); non_mcq_attempts=[]
|
| 133 |
+
start=datetime(2026,6,15); aid=1; attid=1
|
| 134 |
+
atype_seq=['diagnostic','formative','unit_test','summative']
|
| 135 |
+
for c in classes.to_dict('records'):
|
| 136 |
+
for subj in subjects_list:
|
| 137 |
+
for idx,atype in enumerate(atype_seq,1):
|
| 138 |
+
aid_str=f'ASM{aid:05d}'; dt=start+timedelta(days=idx*28+random.randint(-2,2)+(c['grade']-6)*3)
|
| 139 |
+
qids=[]
|
| 140 |
+
for qt,n in [('MCQ',5),('SHORT_ANSWER',3),('CASE_BASED',2),('LONG_ANSWER',2)]:
|
| 141 |
+
pool=q_by_grade_subj_type[(c['grade'],subj,qt)]
|
| 142 |
+
qids.extend(random.sample(pool,n))
|
| 143 |
+
max_marks=sum(q_map[q]['max_marks'] for q in qids)
|
| 144 |
+
assessments.append({'assessment_id':aid_str,'school_id':c['school_id'],'class_id':c['class_id'],'grade':c['grade'],'section':c['section'],'subject':subj,'assessment_name':f'{subj} {atype.title()} {idx}','assessment_type':atype,'scheduled_date':dstr(dt),'max_marks':max_marks,'question_count':len(qids),'academic_year':'2026-27','train_split':split_id(aid_str)})
|
| 145 |
+
for order,q in enumerate(qids,1): assessment_questions.append({'assessment_id':aid_str,'question_id':q,'question_order':order})
|
| 146 |
+
progress=(dt-start).days/140
|
| 147 |
+
for sid in students_by_class[c['class_id']]:
|
| 148 |
+
sp=student_meta[sid]; subj_ability=ability[sid][subj]+ability[sid]['growth']*progress
|
| 149 |
+
eng_bonus=(sp['average_login_per_week']-3)*.08+(sp['assignment_completion_rate']-70)*.01+(sp['attendance_percentage']-80)*.008
|
| 150 |
+
for qid_ in qids:
|
| 151 |
+
q=q_map[qid_]; p=clip(sigmoid(subj_ability+eng_bonus-.55*(q['difficulty_score']-1)-.10*(q['bloom_score']-2)+np.random.normal(0,.22)),.02,.98)
|
| 152 |
+
correct=1 if random.random()<p else 0
|
| 153 |
+
partial=0 if correct or q['question_type']=='MCQ' else clip(np.random.normal(p,.18),0,.85)
|
| 154 |
+
mark_ratio=1.0 if correct else partial; marks=round(mark_ratio*q['max_marks'],2)
|
| 155 |
+
base_time=28+q['difficulty_score']*18+(q['bloom_score']-1)*5+{'MCQ':0,'SHORT_ANSWER':22,'CASE_BASED':38,'LONG_ANSWER':70,'ORAL_PROMPT':20}[q['question_type']]
|
| 156 |
+
time_taken=int(clip(np.random.normal(base_time*(1.15-p*.25),16),12,420)); hint=1 if random.random()<clip(.36+.12*q['difficulty_score']-.32*p,.02,.78) else 0
|
| 157 |
+
submitted=dt+timedelta(hours=random.randint(8,17),minutes=random.randint(0,59),seconds=random.randint(0,59)); att_str=f'ATT{attid:07d}'
|
| 158 |
+
row={'attempt_id':att_str,'assessment_id':aid_str,'school_id':c['school_id'],'class_id':c['class_id'],'student_id':sid,'question_id':qid_,'lo_id':q['lo_id'],'grade':c['grade'],'section':c['section'],'subject':subj,'question_type':q['question_type'],'difficulty_score':q['difficulty_score'],'bloom_score':q['bloom_score'],'is_correct':correct,'marks_obtained':marks,'max_marks':q['max_marks'],'marks_ratio':round(mark_ratio,3),'time_taken_seconds':time_taken,'hint_used':hint,'attempt_number':1,'submitted_at':tstr(submitted),'train_split':split_id(att_str)}
|
| 159 |
+
attempts.append(row)
|
| 160 |
+
if q['question_type']!='MCQ': non_mcq_attempts.append(row)
|
| 161 |
+
a=agg[(sid,q['lo_id'])]; a['cnt']+=1; a['correct']+=correct; a['marks']+=mark_ratio; a['time']+=time_taken; a['hint']+=hint; a['last']=max(a['last'],dstr(submitted))
|
| 162 |
+
attid+=1
|
| 163 |
+
aid+=1
|
| 164 |
+
mark('attempts')
|
| 165 |
+
assessments=pd.DataFrame(assessments); assessment_questions=pd.DataFrame(assessment_questions); student_attempts=pd.DataFrame(attempts)
|
| 166 |
+
|
| 167 |
+
# Mastery and initial mastery
|
| 168 |
+
initial=[]; mastery=[]; mastery_by_ss=defaultdict(list)
|
| 169 |
+
for sp in students:
|
| 170 |
+
for l in los_by_grade[sp['grade']]:
|
| 171 |
+
init=clip(sigmoid(ability[sp['student_id']][l['subject']]-.38*(l['difficulty_score']-1)+np.random.normal(0,.2))*.75,.05,.85)
|
| 172 |
+
init_status='weak' if init<.40 else ('developing' if init<.65 else ('proficient' if init<.85 else 'mastered'))
|
| 173 |
+
base={'student_id':sp['student_id'],'lo_id':l['lo_id'],'grade':sp['grade'],'section':sp['section'],'subject':l['subject'],'chapter':l['chapter'],'train_split':split_id(sp['student_id']+l['lo_id'])}
|
| 174 |
+
initial.append({**base,'attempt_count':0,'accuracy':0.0,'average_marks_ratio':0.0,'average_time_seconds':0.0,'hint_usage_rate':0.0,'mastery_score':round(init,3),'status':init_status,'confidence':.35,'last_updated':'2026-06-01'})
|
| 175 |
+
a=agg.get((sp['student_id'],l['lo_id']))
|
| 176 |
+
if a:
|
| 177 |
+
acc=a['correct']/a['cnt']; mr=a['marks']/a['cnt']; tm=a['time']/a['cnt']; hr=a['hint']/a['cnt']; eff=clip(1-(tm-55)/180,0,1)
|
| 178 |
+
score=clip(.18*init+.42*acc+.27*mr+.08*eff+.05*(1-hr)+np.random.normal(0,.025),.02,.99); conf=clip(.45+.12*a['cnt'],.35,.96); lastd=a['last']; cnt=a['cnt']
|
| 179 |
+
else:
|
| 180 |
+
acc=mr=tm=hr=0.0; score=init*.92; conf=.25; lastd='2026-06-01'; cnt=0
|
| 181 |
+
status='weak' if score<.40 else ('developing' if score<.65 else ('proficient' if score<.85 else 'mastered'))
|
| 182 |
+
row={**base,'attempt_count':cnt,'accuracy':round(acc,3),'average_marks_ratio':round(mr,3),'average_time_seconds':round(tm,1),'hint_usage_rate':round(hr,3),'mastery_score':round(score,3),'status':status,'confidence':round(conf,3),'last_updated':lastd}
|
| 183 |
+
mastery.append(row); mastery_by_ss[(sp['student_id'],l['subject'])].append(row)
|
| 184 |
+
mark('mastery')
|
| 185 |
+
initial_mastery_profiles=pd.DataFrame(initial); mastery_profiles=pd.DataFrame(mastery)
|
| 186 |
+
|
| 187 |
+
# Engagement logs + agg
|
| 188 |
+
eng=[]; eng_agg=defaultdict(lambda:{'active':0,'logins':0,'video':0,'content':0,'quiz':0,'days':0}); eid=1
|
| 189 |
+
for sp in students:
|
| 190 |
+
base_login=sp['average_login_per_week']/7
|
| 191 |
+
for day in range(200):
|
| 192 |
+
dt=datetime(2026,6,1)+timedelta(days=day); weekday=.45 if dt.weekday()>=5 else 1.0
|
| 193 |
+
p=clip(base_login*weekday+(.12 if sp['learner_archetype'] in ['high_performer','fast_improver'] else 0)-(.08 if sp['learner_archetype']=='at_risk' else 0),.02,.98)
|
| 194 |
+
logged=random.random()<p; login=int(np.random.poisson(1.2))+1 if logged else 0; active=int(clip(np.random.normal(35+sp['assignment_completion_rate']*.28,18),5,180)) if logged else 0
|
| 195 |
+
content_done=int(np.random.poisson(max(.1,active/45))) if logged else 0; quiz=int(np.random.binomial(2,.18+.002*sp['assignment_completion_rate'])) if logged else 0; rec_click=int(np.random.binomial(2,.18+.003*sp['assignment_completion_rate'])) if logged else 0; video=round(clip(np.random.normal(.58+sp['assignment_completion_rate']/300,.18),0,1),2) if logged else 0.0
|
| 196 |
+
eng.append({'engagement_id':f'ENG{eid:07d}','student_id':sp['student_id'],'school_id':sp['school_id'],'class_id':sp['class_id'],'activity_date':dstr(dt),'login_count':login,'active_minutes':active,'content_completed_count':content_done,'quiz_attempt_count':quiz,'recommendation_click_count':rec_click,'video_watch_ratio':video,'discussion_posts':int(np.random.binomial(2,.05)) if logged else 0,'device_type':random.choice(['mobile','mobile','tablet','desktop']) if logged else 'none','train_split':split_id(f'ENG{eid:07d}')})
|
| 197 |
+
ea=eng_agg[sp['student_id']]; ea['active']+=active; ea['logins']+=login; ea['video']+=video; ea['content']+=content_done; ea['quiz']+=quiz; ea['days']+=1; eid+=1
|
| 198 |
+
mark('engagement')
|
| 199 |
+
engagement_logs=pd.DataFrame(eng)
|
| 200 |
+
|
| 201 |
+
# Subjective answer scoring sample
|
| 202 |
+
subjective=[]
|
| 203 |
+
sub_sample=random.sample(non_mcq_attempts, min(12000,len(non_mcq_attempts)))
|
| 204 |
+
for i,att in enumerate(sub_sample,1):
|
| 205 |
+
q=q_map[att['question_id']]; quality=clip(att['marks_ratio']+np.random.normal(0,.1),0,1)
|
| 206 |
+
ans=f"The answer {'clearly explains' if quality>.78 else ('partially explains' if quality>.45 else 'shows limited understanding of')} {lo_map[att['lo_id']]['title']} in {q['chapter']}."
|
| 207 |
+
teacher_marks=round(att['marks_obtained'],2); ai_marks=round(clip(teacher_marks+np.random.normal(0,.25+.05*q['difficulty_score']),0,q['max_marks']),2); err=round(abs(ai_marks-teacher_marks),2)
|
| 208 |
+
subjective.append({'answer_id':f'ANS{i:06d}','attempt_id':att['attempt_id'],'student_id':att['student_id'],'question_id':att['question_id'],'lo_id':att['lo_id'],'grade':att['grade'],'subject':att['subject'],'question_type':att['question_type'],'student_answer':ans,'model_answer':q['correct_answer'],'rubric':q['rubric'],'max_marks':q['max_marks'],'teacher_marks':teacher_marks,'ai_predicted_marks':ai_marks,'absolute_error':err,'rubric_match_score':round(clip(quality+np.random.normal(0,.08),0,1),3),'concept_coverage_score':round(clip(quality+np.random.normal(0,.08),0,1),3),'feedback_text':'Good concept coverage.' if quality>.7 else ('Revise the key concept and add a clear example.' if quality>.4 else 'Needs remedial support on the prerequisite concept.'),'teacher_review_required':1 if err>.7 or quality<.35 else 0,'train_split':split_id(f'ANS{i:06d}')})
|
| 209 |
+
mark('subjective')
|
| 210 |
+
subjective_answers=pd.DataFrame(subjective)
|
| 211 |
+
|
| 212 |
+
# Features + Risk
|
| 213 |
+
risk=[]; feat_sub=[]; rid=1
|
| 214 |
+
for sp in students:
|
| 215 |
+
ea=eng_agg[sp['student_id']]
|
| 216 |
+
for subj in subjects_list:
|
| 217 |
+
rows=mastery_by_ss[(sp['student_id'],subj)]
|
| 218 |
+
avg_m=sum(r['mastery_score'] for r in rows)/len(rows); weak=sum(1 for r in rows if r['status']=='weak'); dev=sum(1 for r in rows if r['status']=='developing'); mastered=sum(1 for r in rows if r['status']=='mastered'); conf=sum(r['confidence'] for r in rows)/len(rows)
|
| 219 |
+
# Use aggregate dictionaries from generated attempts; no full attempts scan needed.
|
| 220 |
+
lo_attempts=[agg.get((sp['student_id'],r['lo_id'])) for r in rows if agg.get((sp['student_id'],r['lo_id']))]
|
| 221 |
+
total=sum(a['cnt'] for a in lo_attempts) or 1; acc=sum(a['correct'] for a in lo_attempts)/total; mr=sum(a['marks'] for a in lo_attempts)/total; tm=sum(a['time'] for a in lo_attempts)/total; hr=sum(a['hint'] for a in lo_attempts)/total
|
| 222 |
+
score=clip(.32*(1-avg_m)+.14*(weak/25)+.14*(1-acc)+.10*(1-sp['attendance_percentage']/100)+.10*(1-sp['assignment_completion_rate']/100)+.08*(sp['inactive_days_last_14']/14)+.06*hr+.06*(1-min(sp['average_login_per_week'],7)/7)+np.random.normal(0,.025),0,1)
|
| 223 |
+
lvl='critical' if score>=.75 else ('high' if score>=.58 else ('medium' if score>=.38 else 'low'))
|
| 224 |
+
reasons=[]
|
| 225 |
+
if avg_m<.45: reasons.append('low_mastery')
|
| 226 |
+
if weak>=8: reasons.append('multiple_weak_learning_outcomes')
|
| 227 |
+
if sp['attendance_percentage']<75: reasons.append('low_attendance')
|
| 228 |
+
if sp['assignment_completion_rate']<60: reasons.append('low_assignment_completion')
|
| 229 |
+
if sp['inactive_days_last_14']>=5: reasons.append('recent_inactivity')
|
| 230 |
+
if acc<.45: reasons.append('low_assessment_accuracy')
|
| 231 |
+
if not reasons: reasons=['stable_learning_pattern']
|
| 232 |
+
rrow={'risk_prediction_id':f'RISK{rid:06d}','student_id':sp['student_id'],'school_id':sp['school_id'],'class_id':sp['class_id'],'grade':sp['grade'],'section':sp['section'],'subject':subj,'risk_score':round(score,3),'risk_level':lvl,'risk_label':1 if lvl in ['high','critical'] else 0,'primary_reasons':'|'.join(reasons[:4]),'recommended_intervention':'urgent_teacher_intervention' if lvl=='critical' else ('small_group_remediation' if lvl=='high' else ('targeted_practice' if lvl=='medium' else 'continue_current_path')),'model_version':'synthetic-risk-label-v2.0','generated_at':'2026-09-30T08:00:00','confidence':round(.62+abs(score-.5)*.55,3),'train_split':sp['train_split']}
|
| 233 |
+
risk.append(rrow)
|
| 234 |
+
feat_sub.append({'feature_row_id':f'FSUB{rid:06d}','student_id':sp['student_id'],'school_id':sp['school_id'],'class_id':sp['class_id'],'grade':sp['grade'],'section':sp['section'],'subject':subj,'avg_mastery_score':round(avg_m,3),'weak_lo_count':weak,'developing_lo_count':dev,'mastered_lo_count':mastered,'avg_confidence':round(conf,3),'avg_accuracy':round(acc,3),'avg_marks_ratio':round(mr,3),'avg_time_seconds':round(tm,1),'hint_usage_rate':round(hr,3),'total_attempts':total,'attendance_percentage':sp['attendance_percentage'],'assignment_completion_rate':sp['assignment_completion_rate'],'average_login_per_week':sp['average_login_per_week'],'inactive_days_last_14':sp['inactive_days_last_14'],'avg_active_minutes':round(ea['active']/ea['days'],1),'total_logins':ea['logins'],'avg_video_watch_ratio':round(ea['video']/ea['days'],3),'total_content_completed':ea['content'],'total_quiz_attempts':ea['quiz'],'risk_score':round(score,3),'risk_level':lvl,'risk_label':1 if lvl in ['high','critical'] else 0,'train_split':split_id(f'FSUB{rid:06d}')})
|
| 235 |
+
rid+=1
|
| 236 |
+
mark('risk')
|
| 237 |
+
risk_profiles=pd.DataFrame(risk); ml_features_student_subject=pd.DataFrame(feat_sub)
|
| 238 |
+
|
| 239 |
+
# ML LO features
|
| 240 |
+
ml_features_student_lo=mastery_profiles.merge(student_profiles[['student_id','school_id','class_id','attendance_percentage','assignment_completion_rate','average_login_per_week','inactive_days_last_14']],on='student_id',how='left')
|
| 241 |
+
ml_features_student_lo['mastery_label']=ml_features_student_lo['status'].map({'weak':0,'developing':1,'proficient':2,'mastered':3})
|
| 242 |
+
ml_features_student_lo.insert(0,'feature_row_id',[f'FLO{i:07d}' for i in range(1,len(ml_features_student_lo)+1)])
|
| 243 |
+
|
| 244 |
+
# Recommendations
|
| 245 |
+
recommendations=[]; recid=1
|
| 246 |
+
for sp in students:
|
| 247 |
+
for subj in subjects_list:
|
| 248 |
+
rows=sorted(mastery_by_ss[(sp['student_id'],subj)], key=lambda x:(x['mastery_score'],x['attempt_count']))[:5]
|
| 249 |
+
for m in rows:
|
| 250 |
+
pool=content_by_lo[m['lo_id']]
|
| 251 |
+
if m['status']=='weak': cands=[c for c in pool if c['target_use']=='remediation']; rtype='remedial_content'; pr='high'
|
| 252 |
+
elif m['status']=='developing': cands=[c for c in pool if c['target_use']=='practice']; rtype='practice_content'; pr='medium'
|
| 253 |
+
else: cands=[c for c in pool if c['target_use']=='enrichment']; rtype='enrichment_content'; pr='low'
|
| 254 |
+
c=random.choice(cands or pool); cp=.72 if sp['learner_archetype'] in ['high_performer','fast_improver'] else (.45 if sp['learner_archetype'] in ['low_engagement','at_risk'] else .6); clicked=1 if random.random()<cp+.15 else 0; done=1 if clicked and random.random()<cp else 0
|
| 255 |
+
recommendations.append({'recommendation_id':f'REC{recid:07d}','student_id':sp['student_id'],'school_id':sp['school_id'],'class_id':sp['class_id'],'lo_id':m['lo_id'],'content_id':c['content_id'],'grade':sp['grade'],'section':sp['section'],'subject':subj,'recommendation_type':rtype,'priority':pr,'reason':f"{m['status']} mastery in {m['lo_id']} with score {m['mastery_score']}",'ai_confidence':round(clip(.55+(1-float(m['mastery_score']))*.35+random.uniform(-.04,.04),.5,.95),3),'generated_at':'2026-09-30T09:00:00','shown_to_student':1,'clicked':clicked,'is_completed':done,'observed_mastery_gain':round(random.uniform(.01,.12) if done else random.uniform(0,.03),3),'model_version':'hybrid-recommender-synthetic-label-v2.0','train_split':split_id(f'REC{recid:07d}')}); recid+=1
|
| 256 |
+
mark('recommendations')
|
| 257 |
+
recommendations=pd.DataFrame(recommendations)
|
| 258 |
+
|
| 259 |
+
# Teacher interventions
|
| 260 |
+
interventions=[]; iid=1
|
| 261 |
+
for c in classes.to_dict('records'):
|
| 262 |
+
class_sids=set(students_by_class[c['class_id']])
|
| 263 |
+
for subj in subjects_list:
|
| 264 |
+
d=defaultdict(lambda:{'scores':[],'weak':0})
|
| 265 |
+
for sid in class_sids:
|
| 266 |
+
for m in mastery_by_ss[(sid,subj)]:
|
| 267 |
+
d[m['lo_id']]['scores'].append(m['mastery_score']); d[m['lo_id']]['weak']+=1 if m['status']=='weak' else 0
|
| 268 |
+
weakest=sorted([(lo_id, sum(v['scores'])/len(v['scores']), v['weak']) for lo_id,v in d.items()], key=lambda x:(-x[2],x[1]))[:3]
|
| 269 |
+
for lo_id,avg,weak in weakest:
|
| 270 |
+
if weak==0: continue
|
| 271 |
+
level='whole_class_reteach' if weak>=14 else ('small_group_remediation' if weak>=6 else 'individual_support')
|
| 272 |
+
interventions.append({'intervention_id':f'INT{iid:05d}','school_id':c['school_id'],'class_id':c['class_id'],'teacher_id':teacher_lookup.get((c['class_id'],subj),'TCH0000'),'grade':c['grade'],'section':c['section'],'subject':subj,'lo_id':lo_id,'intervention_type':level,'affected_students':weak,'avg_mastery_before':round(avg,3),'suggested_action':f'Use remedial activity and 10-question practice quiz for {lo_id}.','scheduled_week':'2026-W40','status':random.choice(['planned','in_progress','completed']),'expected_mastery_gain':round(random.uniform(.06,.18),3),'generated_by_ai':1,'train_split':split_id(f'INT{iid:05d}')}); iid+=1
|
| 273 |
+
mark('interventions')
|
| 274 |
+
teacher_interventions=pd.DataFrame(interventions)
|
| 275 |
+
|
| 276 |
+
# Feedback, digital twins, logs
|
| 277 |
+
teacher_feedback=[]; fbid=1
|
| 278 |
+
review=subjective_answers[subjective_answers.teacher_review_required==1].to_dict('records')+subjective_answers.sample(n=min(2500,len(subjective_answers)), random_state=SEED).to_dict('records')
|
| 279 |
+
seen=set()
|
| 280 |
+
for ans in review:
|
| 281 |
+
if ans['answer_id'] in seen or len(teacher_feedback)>=5000: continue
|
| 282 |
+
seen.add(ans['answer_id']); sp=student_meta[ans['student_id']]; tid=teacher_lookup.get((sp['class_id'],ans['subject']),'TCH0000')
|
| 283 |
+
teacher_feedback.append({'feedback_id':f'FB{fbid:06d}','teacher_id':tid,'student_id':ans['student_id'],'related_entity_type':'subjective_answer','related_entity_id':ans['answer_id'],'feedback_type':'score_review','teacher_rating':random.choice([3,4,4,5]) if ans['absolute_error']<=.7 else random.choice([1,2,3]),'correction_required':1 if ans['absolute_error']>.7 else 0,'correction_text':'AI score acceptable.' if ans['absolute_error']<=.7 else 'Teacher adjusted marks due to rubric nuance.','created_at':'2026-09-30T12:00:00','train_split':split_id(f'FB{fbid:06d}')}); fbid+=1
|
| 284 |
+
for r in risk_profiles.sample(n=1000, random_state=SEED).to_dict('records'):
|
| 285 |
+
sp=student_meta[r['student_id']]; teacher_feedback.append({'feedback_id':f'FB{fbid:06d}','teacher_id':teacher_lookup.get((sp['class_id'],r['subject']),'TCH0000'),'student_id':r['student_id'],'related_entity_type':'risk_prediction','related_entity_id':r['risk_prediction_id'],'feedback_type':'risk_review','teacher_rating':random.choice([3,4,4,5]),'correction_required':random.choice([0,0,0,1]),'correction_text':'Risk alert reviewed by teacher.','created_at':'2026-09-30T12:00:00','train_split':split_id(f'FB{fbid:06d}')}); fbid+=1
|
| 286 |
+
mark('feedback')
|
| 287 |
+
teacher_feedback=pd.DataFrame(teacher_feedback)
|
| 288 |
+
|
| 289 |
+
student_digital_twins=[]
|
| 290 |
+
risk_by_student=defaultdict(list)
|
| 291 |
+
for r in risk_profiles.to_dict('records'): risk_by_student[r['student_id']].append(r)
|
| 292 |
+
for i,sp in enumerate(students,1):
|
| 293 |
+
rows=[m for subj in subjects_list for m in mastery_by_ss[(sp['student_id'],subj)]]; bysubj={subj:np.mean([m['mastery_score'] for m in mastery_by_ss[(sp['student_id'],subj)]]) for subj in subjects_list}; weak=sorted(rows,key=lambda x:x['mastery_score'])[:5]; top=max(risk_by_student[sp['student_id']], key=lambda x:x['risk_score'])
|
| 294 |
+
pref='video' if sp['learning_style']=='visual' else ('audio_explanation' if sp['learning_style']=='auditory' else ('notes' if sp['learning_style']=='reading_writing' else 'interactive_activity'))
|
| 295 |
+
student_digital_twins.append({'digital_twin_id':f'DT{i:05d}','student_id':sp['student_id'],'school_id':sp['school_id'],'class_id':sp['class_id'],'grade':sp['grade'],'section':sp['section'],'overall_mastery_score':round(np.mean([m['mastery_score'] for m in rows]),3),'strongest_subject':max(bysubj,key=bysubj.get),'weakest_subject':min(bysubj,key=bysubj.get),'top_weak_lo_ids':'|'.join([m['lo_id'] for m in weak]),'learning_speed_score':round(clip((sp['assignment_completion_rate']/100)*.45+(sp['average_login_per_week']/7)*.35+(np.mean([m['attempt_count'] for m in rows])/4)*.20,0,1),3),'consistency_score':round(clip(sp['attendance_percentage']/100*.55+sp['assignment_completion_rate']/100*.45,0,1),3),'preferred_content_type':pref,'current_risk_level':top['risk_level'],'current_risk_score':top['risk_score'],'recommended_next_action':'teacher_intervention' if top['risk_level'] in ['high','critical'] else ('targeted_practice' if min(bysubj.values())<.65 else 'advanced_enrichment'),'last_updated':'2026-09-30','train_split':split_id(f'DT{i:05d}')})
|
| 296 |
+
mark('digital')
|
| 297 |
+
student_digital_twins=pd.DataFrame(student_digital_twins)
|
| 298 |
+
|
| 299 |
+
pred=[]; pid=1
|
| 300 |
+
for r in risk_profiles.to_dict('records'):
|
| 301 |
+
pred.append({'prediction_log_id':f'PRED{pid:07d}','model_name':'risk_prediction','model_version':r['model_version'],'entity_type':'student_subject','entity_id':f"{r['student_id']}:{r['subject']}",'prediction_output':json.dumps({'risk_level':r['risk_level'],'risk_score':r['risk_score']}),'confidence':r['confidence'],'latency_ms':random.randint(18,75),'created_at':r['generated_at'],'train_split':r['train_split']}); pid+=1
|
| 302 |
+
for r in recommendations.sample(n=min(6000,len(recommendations)), random_state=SEED).to_dict('records'):
|
| 303 |
+
pred.append({'prediction_log_id':f'PRED{pid:07d}','model_name':'recommendation_engine','model_version':r['model_version'],'entity_type':'recommendation','entity_id':r['recommendation_id'],'prediction_output':json.dumps({'content_id':r['content_id'],'priority':r['priority'],'type':r['recommendation_type']}),'confidence':r['ai_confidence'],'latency_ms':random.randint(25,110),'created_at':r['generated_at'],'train_split':r['train_split']}); pid+=1
|
| 304 |
+
for r in subjective_answers.sample(n=min(6000,len(subjective_answers)), random_state=SEED+5).to_dict('records'):
|
| 305 |
+
pred.append({'prediction_log_id':f'PRED{pid:07d}','model_name':'subjective_answer_scoring','model_version':'rubric-semantic-v2.0','entity_type':'subjective_answer','entity_id':r['answer_id'],'prediction_output':json.dumps({'ai_marks':r['ai_predicted_marks'],'review_required':r['teacher_review_required']}),'confidence':round(1/(1+r['absolute_error']),3),'latency_ms':random.randint(90,420),'created_at':'2026-09-30T10:00:00','train_split':r['train_split']}); pid+=1
|
| 306 |
+
for r in questions.sample(n=min(3000,len(questions)), random_state=SEED+2).to_dict('records'):
|
| 307 |
+
pred.append({'prediction_log_id':f'PRED{pid:07d}','model_name':'lo_tagging','model_version':'embedding-classifier-v2.0','entity_type':'question','entity_id':r['question_id'],'prediction_output':json.dumps({'lo_id':r['lo_id'],'bloom_level':r['bloom_level'],'difficulty':r['difficulty']}),'confidence':r['alignment_confidence'],'latency_ms':random.randint(12,65),'created_at':'2026-09-30T10:00:00','train_split':r['train_split']}); pid+=1
|
| 308 |
+
mark('pred')
|
| 309 |
+
ai_prediction_logs=pd.DataFrame(pred)
|
| 310 |
+
|
| 311 |
+
# Training tables
|
| 312 |
+
training_lo_tagging=questions[['question_id','question_text','embedding_text','lo_id','grade','subject','chapter','difficulty','difficulty_score','bloom_level','bloom_score','train_split']]
|
| 313 |
+
training_bloom_classification=questions[['question_id','question_text','embedding_text','bloom_level','bloom_score','grade','subject','question_type','train_split']]
|
| 314 |
+
training_risk_prediction=ml_features_student_subject.copy()
|
| 315 |
+
training_mastery_prediction=ml_features_student_lo.copy()
|
| 316 |
+
training_answer_scoring=subjective_answers[['answer_id','question_id','student_id','lo_id','grade','subject','question_type','student_answer','model_answer','rubric','max_marks','teacher_marks','ai_predicted_marks','rubric_match_score','concept_coverage_score','teacher_review_required','train_split']]
|
| 317 |
+
training_recommendation_outcomes=recommendations[['recommendation_id','student_id','lo_id','content_id','grade','subject','recommendation_type','priority','ai_confidence','clicked','is_completed','observed_mastery_gain','train_split']]
|
| 318 |
+
|
| 319 |
+
tables={'schools.csv':schools,'classes.csv':classes,'subjects.csv':subjects,'chapters.csv':chapters,'teachers.csv':teachers,'student_profiles.csv':student_profiles,'learning_outcomes.csv':learning_outcomes,'lo_dependencies.csv':lo_dependencies,'questions.csv':questions,'question_options.csv':question_options,'content_catalog.csv':content_catalog,'assessments.csv':assessments,'assessment_questions.csv':assessment_questions,'student_attempts.csv':student_attempts,'initial_mastery_profiles.csv':initial_mastery_profiles,'mastery_profiles.csv':mastery_profiles,'engagement_logs.csv':engagement_logs,'risk_profiles.csv':risk_profiles,'recommendations.csv':recommendations,'teacher_interventions.csv':teacher_interventions,'subjective_answers.csv':subjective_answers,'teacher_feedback.csv':teacher_feedback,'student_digital_twins.csv':student_digital_twins,'ai_prediction_logs.csv':ai_prediction_logs,'ml_features_student_subject.csv':ml_features_student_subject,'ml_features_student_lo.csv':ml_features_student_lo,'training_lo_tagging.csv':training_lo_tagging,'training_bloom_classification.csv':training_bloom_classification,'training_risk_prediction.csv':training_risk_prediction,'training_mastery_prediction.csv':training_mastery_prediction,'training_answer_scoring.csv':training_answer_scoring,'training_recommendation_outcomes.csv':training_recommendation_outcomes}
|
| 320 |
+
|
| 321 |
+
# Save + validate
|
| 322 |
+
issues=[]
|
| 323 |
+
for name,df in list(tables.items()):
|
| 324 |
+
df=df.copy()
|
| 325 |
+
for col in df.columns:
|
| 326 |
+
if pd.api.types.is_numeric_dtype(df[col]): df[col]=df[col].fillna(0)
|
| 327 |
+
else: df[col]=df[col].fillna('unknown').astype(str).map(clean)
|
| 328 |
+
df.to_csv(OUT/name,index=False); tables[name]=df
|
| 329 |
+
if df.isnull().any().any(): issues.append(f'Nulls in {name}')
|
| 330 |
+
|
| 331 |
+
def fk(child,col,parent,pcol):
|
| 332 |
+
missing=set(tables[child][col])-set(tables[parent][pcol])
|
| 333 |
+
if missing: issues.append(f'{child}.{col} missing {len(missing)} refs to {parent}.{pcol}')
|
| 334 |
+
for child,col,parent,pcol in [('classes.csv','school_id','schools.csv','school_id'),('teachers.csv','class_id','classes.csv','class_id'),('student_profiles.csv','class_id','classes.csv','class_id'),('questions.csv','lo_id','learning_outcomes.csv','lo_id'),('content_catalog.csv','lo_id','learning_outcomes.csv','lo_id'),('assessments.csv','class_id','classes.csv','class_id'),('assessment_questions.csv','assessment_id','assessments.csv','assessment_id'),('assessment_questions.csv','question_id','questions.csv','question_id'),('student_attempts.csv','student_id','student_profiles.csv','student_id'),('student_attempts.csv','question_id','questions.csv','question_id'),('mastery_profiles.csv','student_id','student_profiles.csv','student_id'),('mastery_profiles.csv','lo_id','learning_outcomes.csv','lo_id'),('recommendations.csv','content_id','content_catalog.csv','content_id')]: fk(child,col,parent,pcol)
|
| 335 |
+
metadata={'dataset_name':'Learning Outcome OS AI-Ready Expanded Dataset','version':'2.0.0','generated_at':datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),'seed':SEED,'scope':{'schools':len(schools),'classes':len(classes),'grades':[6,7,8],'sections':['A','B'],'subjects':subjects_list,'students_per_class_section':30,'total_students':len(student_profiles),'source_alignment':['mathematics_LO.pdf','science_LO.pdf','social_science_LO.pdf'],'source_base_dataset':'cbse_lo_aligned_synthetic_dataset_classes_6_8.zip'},'model_ready_design':{'no_null_values':len([i for i in issues if 'Nulls' in i])==0,'stable_primary_keys':True,'foreign_keys_valid':len([i for i in issues if 'refs' in i])==0,'train_validation_test_splits':True,'numeric_features_available':True,'nlp_embedding_text_fields_available':True,'human_feedback_tables_available':True,'target_labels_available':['lo_id','bloom_level','difficulty_score','mastery_score','mastery_label','risk_label','teacher_marks','clicked','is_completed']},'table_counts':{name:len(df) for name,df in tables.items()},'status_labels':['weak','developing','proficient','mastered'],'risk_labels':['low','medium','high','critical']}
|
| 336 |
+
(OUT/'dataset_metadata.json').write_text(json.dumps(metadata,indent=2),encoding='utf-8')
|
| 337 |
+
(OUT/'validation_report.json').write_text(json.dumps({'valid':len(issues)==0,'total_issues':len(issues),'issues':issues,'checks':['null_value_absence','foreign_key_integrity','target_label_presence','split_presence'],'table_counts':metadata['table_counts']},indent=2),encoding='utf-8')
|
| 338 |
+
readme=['# Learning Outcome OS AI-Ready Expanded Dataset v2','','Expanded synthetic, CBSE/NCERT LO-aligned, model-ready dataset for LO tagging, Bloom classification, mastery prediction, recommendation, risk prediction, subjective answer scoring, teacher feedback, and digital twin modelling.','','All names are synthetic. No real PII is included. CSVs are pre-cleaned with stable IDs, target labels, and train/validation/test split columns.','','## Tables']
|
| 339 |
+
for name,df in tables.items(): readme += [f'### {name}',f'- Rows: {len(df):,}',f'- Columns: {", ".join(df.columns)}','']
|
| 340 |
+
(OUT/'README.md').write_text('\n'.join(readme),encoding='utf-8')
|
| 341 |
+
schema=['-- Flexible PostgreSQL import schema for Learning Outcome OS AI-ready dataset v2','']
|
| 342 |
+
for name,df in tables.items():
|
| 343 |
+
table=name.replace('.csv',''); schema.append(f'DROP TABLE IF EXISTS {table};')
|
| 344 |
+
cols=[]
|
| 345 |
+
for col in df.columns:
|
| 346 |
+
typ='DOUBLE PRECISION' if pd.api.types.is_float_dtype(df[col]) else ('INTEGER' if pd.api.types.is_integer_dtype(df[col]) else 'TEXT')
|
| 347 |
+
cols.append(f' {col} {typ}')
|
| 348 |
+
schema.append(f'CREATE TABLE {table} (\n'+',\n'.join(cols)+'\n);')
|
| 349 |
+
schema.append(f"-- COPY {table} FROM '/path/{name}' WITH CSV HEADER;\n")
|
| 350 |
+
(OUT/'postgres_schema.sql').write_text('\n'.join(schema),encoding='utf-8')
|
| 351 |
+
mark('zip')
|
| 352 |
+
with zipfile.ZipFile(ZIP,'w',compression=zipfile.ZIP_DEFLATED,compresslevel=6) as z:
|
| 353 |
+
for p in sorted(OUT.iterdir()): z.write(p,arcname=p.name)
|
| 354 |
+
print(json.dumps({'zip':str(ZIP),'valid':len(issues)==0,'issues':issues,'counts':metadata['table_counts']},indent=2))
|