headless_lms_server/programs/seed/seed_courses/seed_lock_chapter_course.rs
1use crate::programs::seed::builder::chapter::ChapterBuilder;
2use crate::programs::seed::builder::context::SeedContext;
3use crate::programs::seed::builder::course::{CourseBuilder, CourseInstanceConfig};
4use crate::programs::seed::builder::exercise::{ExerciseBuilder, ExerciseIds};
5use crate::programs::seed::builder::json_source::JsonSource;
6use crate::programs::seed::builder::module::ModuleBuilder;
7use crate::programs::seed::builder::page::PageBuilder;
8use crate::programs::seed::seed_courses::CommonCourseData;
9use anyhow::Result;
10use headless_lms_models::roles::UserRole;
11use headless_lms_utils::{attributes, document_schema_processor::GutenbergBlock};
12use serde_json::json;
13use tracing::info;
14use uuid::Uuid;
15
16pub async fn seed_lock_chapter_course(
17 course_id: Uuid,
18 course_name: &str,
19 course_slug: &str,
20 common_course_data: CommonCourseData,
21) -> Result<Uuid> {
22 let CommonCourseData {
23 db_pool,
24 organization_id: org,
25 teacher_user_id,
26 student_user_id: _student,
27 langs_user_id: _langs_user_id,
28 example_normal_user_ids: _users,
29 jwt_key: _jwt_key,
30 base_url: _base_url,
31 } = common_course_data;
32
33 let mut conn = db_pool.acquire().await?;
34 let cx = SeedContext {
35 teacher: teacher_user_id,
36 org,
37 base_course_ns: course_id,
38 };
39
40 info!("inserting lock chapter course {}", course_name);
41
42 let course = CourseBuilder::new(course_name, course_slug)
43 .desc("Course for testing chapter locking feature.")
44 .course_id(course_id)
45 .chapter_locking_enabled(true)
46 .instance(CourseInstanceConfig {
47 name: None,
48 description: None,
49 support_email: None,
50 teacher_in_charge_name: "admin".to_string(),
51 teacher_in_charge_email: "admin@example.com".to_string(),
52 opening_time: None,
53 closing_time: None,
54 instance_id: Some(cx.v5(b"a1b2c3d4-e5f6-7890-abcd-ef1234567890")),
55 })
56 .role(teacher_user_id, UserRole::Teacher)
57 .module(
58 ModuleBuilder::new()
59 .order(0)
60 .chapter(
61 ChapterBuilder::new(1, "Chapter 1 - Lockable")
62 .fixed_ids(
63 cx.v5(b"b2c3d4e5-f6a7-8901-bcde-f12345678901"),
64 cx.v5(b"c3d4e5f6-a7b8-9012-cdef-123456789012"),
65 )
66 .page(
67 PageBuilder::new("/chapter-1/lock-page", "Lock Chapter Page")
68 .block(
69 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
70 "moocfi/lock-chapter",
71 attributes! {},
72 vec![
73 GutenbergBlock::block_with_name_and_attributes(
74 "core/heading",
75 attributes! {
76 "content": "Model Solution",
77 "level": 2,
78 "anchor": "model-solution-title"
79 },
80 )
81 .with_id(cx.v5(b"f1a2b3c4-d5e6-7890-abcd-ef1234567890")),
82 GutenbergBlock::paragraph("Congratulations on completing Chapter 1! Here's a model solution for the Customer Behavior Analysis Project.")
83 .with_id(cx.v5(b"f2a3b4c5-d6e7-8901-bcde-f1234567891")),
84 GutenbergBlock::paragraph("The project involved analyzing customer purchase data to identify patterns and segment customers. The first step was data cleaning, which included handling missing values using appropriate imputation methods and removing outliers using the IQR method.")
85 .with_id(cx.v5(b"f3a4b5c6-d7e8-9012-cdef-1234567892")),
86 GutenbergBlock::block_with_name_and_attributes(
87 "core/heading",
88 attributes! {
89 "content": "Data Cleaning",
90 "level": 3,
91 "anchor": "data-cleaning"
92 },
93 )
94 .with_id(cx.v5(b"f3b4c5d6-e7f8-9012-def3-3456789012")),
95 GutenbergBlock::block_with_name_and_attributes(
96 "core/code",
97 attributes! {
98 "content": "import pandas as pd
99import numpy as np
100from sklearn.cluster import KMeans
101
102# Load and clean data
103df = pd.read_csv('customer_data.csv')
104df['age'].fillna(df['age'].median(), inplace=True)
105df['income'].fillna(df['income'].mean(), inplace=True)
106
107# Remove outliers
108Q1 = df['purchase_amount'].quantile(0.25)
109Q3 = df['purchase_amount'].quantile(0.75)
110IQR = Q3 - Q1
111df = df[(df['purchase_amount'] >= Q1 - 1.5*IQR) &
112 (df['purchase_amount'] <= Q3 + 1.5*IQR)]"
113 },
114 )
115 .with_id(cx.v5(b"f4a5b6c7-d8e9-0123-def4-2345678903")),
116 GutenbergBlock::block_with_name_and_attributes(
117 "core/heading",
118 attributes! {
119 "content": "Key Findings",
120 "level": 3,
121 "anchor": "key-findings"
122 },
123 )
124 .with_id(cx.v5(b"f4b5c6d7-e8f9-0123-ef45-4567890123")),
125 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
126 "core/list",
127 attributes! {
128 "ordered": false
129 },
130 vec![
131 GutenbergBlock::block_with_name_and_attributes(
132 "core/list-item",
133 attributes! {
134 "content": "Strong positive correlation (r=0.72) between income and purchase amount"
135 },
136 )
137 .with_id(cx.v5(b"f5a6b7c8-d9e0-1234-ef45-3456789014")),
138 GutenbergBlock::block_with_name_and_attributes(
139 "core/list-item",
140 attributes! {
141 "content": "Electronics category generates 35% more revenue than other categories"
142 },
143 )
144 .with_id(cx.v5(b"f6a7b8c9-d0e1-2345-f456-4567890125")),
145 GutenbergBlock::block_with_name_and_attributes(
146 "core/list-item",
147 attributes! {
148 "content": "High-value customers represent 18% of customers but 42% of total revenue"
149 },
150 )
151 .with_id(cx.v5(b"f7a8b9c0-d1e2-3456-5678-5678901236")),
152 ],
153 )
154 .with_id(cx.v5(b"f8a9b0c1-d2e3-4567-6789-6789012347")),
155 GutenbergBlock::block_with_name_and_attributes(
156 "core/quote",
157 attributes! {
158 "value": "The key insight from this analysis is that customer segmentation reveals distinct purchasing behaviors that can inform targeted marketing strategies."
159 },
160 )
161 .with_id(cx.v5(b"f0a1b2c3-d4e5-6789-8901-8901234569")),
162 GutenbergBlock::block_with_name_and_attributes(
163 "core/heading",
164 attributes! {
165 "content": "Recommendations",
166 "level": 3,
167 "anchor": "recommendations"
168 },
169 )
170 .with_id(cx.v5(b"f1b2c3d4-e5f6-7890-f012-0123456789")),
171 GutenbergBlock::paragraph("Based on the analysis, recommendations include developing a VIP program for high-value customers and increasing marketing focus on the Electronics category during peak seasons. The methodology demonstrated here provides a solid framework for data-driven decision making.")
172 .with_id(cx.v5(b"f1a2b3c4-d5e6-7890-9012-9012345670")),
173 ],
174 )
175 .with_id(cx.v5(b"d4e5f6a7-b8c9-0123-def4-234567890123")),
176 )
177 .block(
178 GutenbergBlock::paragraph("This is Chapter 1. You can lock this chapter when you're done.")
179 .with_id(cx.v5(b"e5f6a7b8-c9d0-1234-ef45-345678901234")),
180 ),
181 )
182 .page(
183 PageBuilder::new("/chapter-1/exercise-page", "Exercise in Chapter 1")
184 .exercise(ExerciseBuilder::quizzes(
185 "Chapter 1 Exercise",
186 ExerciseIds {
187 exercise_id: cx.v5(b"f6a7b8c9-d0e1-2345-f456-456789012345"),
188 slide_id: cx.v5(b"a7b8c9d0-e1f2-3456-5678-567890123456"),
189 task_id: cx.v5(b"b8c9d0e1-f2a3-4567-6789-678901234567"),
190 block_id: cx.v5(b"c9d0e1f2-a3b4-5678-7890-789012345678"),
191 },
192 false,
193 None,
194 JsonSource::Inline(json!({
195 "version": "2",
196 "title": "Chapter 1 Exercise",
197 "body": "Select the correct answer.",
198 "awardPointsEvenIfWrong": false,
199 "grantPointsPolicy": "grant_whenever_possible",
200 "quizItemDisplayDirection": "vertical",
201 "submitMessage": "",
202 "items": [
203 {
204 "type": "multiple-choice",
205 "id": "d0e1f2a3-b4c5-6789-8901-890123456789",
206 "failureMessage": "",
207 "options": [
208 {
209 "order": 1,
210 "additionalCorrectnessExplanationOnModelSolution": "",
211 "body": "",
212 "correct": true,
213 "id": "e1f2a3b4-c5d6-7890-9012-901234567890",
214 "messageAfterSubmissionWhenSelected": "",
215 "title": "Correct answer"
216 },
217 {
218 "order": 2,
219 "additionalCorrectnessExplanationOnModelSolution": "",
220 "body": "",
221 "correct": false,
222 "id": "f2a3b4c5-d6e7-8901-0123-012345678901",
223 "messageAfterSubmissionWhenSelected": "",
224 "title": "Wrong answer"
225 }
226 ],
227 "order": 0,
228 "successMessage": "",
229 "title": "What is the correct answer?",
230 "body": ""
231 }
232 ]
233 })),
234 vec![],
235 )),
236 ),
237 )
238 .chapter(
239 ChapterBuilder::new(2, "Chapter 2 - Add Lock Later")
240 .fixed_ids(
241 cx.v5(b"z1y2x3w4-v5u6-7890-tsrq-pq9876543210"),
242 cx.v5(b"y2x3w4v5-u6t7-8901-srqp-op8765432109"),
243 )
244 .page(
245 PageBuilder::new("/chapter-2/lock-page", "Lock Chapter Page")
246 .block(
247 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
248 "moocfi/lock-chapter",
249 attributes! {},
250 vec![
251 GutenbergBlock::block_with_name_and_attributes(
252 "core/heading",
253 attributes! {
254 "content": "Model Solution",
255 "level": 2,
256 "anchor": "model-solution-title"
257 },
258 )
259 .with_id(cx.v5(b"g1h2i3j4-k5l6-7890-mnop-qr1234567890")),
260 GutenbergBlock::paragraph("Congratulations on completing Chapter 2! Here's a model solution for the exercises.")
261 .with_id(cx.v5(b"g2h3i4j5-k6l7-8901-nopq-rs2345678901")),
262 ],
263 )
264 .with_id(cx.v5(b"g3h4i5j6-k7l8-9012-opqr-st3456789012")),
265 )
266 .block(
267 GutenbergBlock::paragraph("This is Chapter 2. You can lock this chapter when you're done.")
268 .with_id(cx.v5(b"g4h5i6j7-k8l9-0123-pqrs-tu4567890123")),
269 ),
270 )
271 .page(
272 PageBuilder::new("/chapter-2/exercise-page", "Exercise in Chapter 2")
273 .block(
274 GutenbergBlock::paragraph("This is Chapter 2.")
275 .with_id(cx.v5(b"x3w4v5u6-t7s8-9012-rqpo-no7654321098")),
276 )
277 .exercise(ExerciseBuilder::quizzes(
278 "Chapter 2 Exercise",
279 ExerciseIds {
280 exercise_id: cx.v5(b"w4v5u6t7-s8r9-0123-qpon-mn6543210987"),
281 slide_id: cx.v5(b"v5u6t7s8-r9q0-1234-ponm-lm5432109876"),
282 task_id: cx.v5(b"u6t7s8r9-q0p1-2345-onml-kl4321098765"),
283 block_id: cx.v5(b"t7s8r9q0-p1o2-3456-nmlk-jk3210987654"),
284 },
285 false,
286 None,
287 JsonSource::Inline(json!({
288 "version": "2",
289 "title": "Chapter 2 Exercise",
290 "body": "Select the correct answer.",
291 "awardPointsEvenIfWrong": false,
292 "grantPointsPolicy": "grant_whenever_possible",
293 "quizItemDisplayDirection": "vertical",
294 "submitMessage": "",
295 "items": [
296 {
297 "type": "multiple-choice",
298 "id": "b8c9d0e1-f2a3-4567-6789-678901234567",
299 "failureMessage": "",
300 "options": [
301 {
302 "order": 1,
303 "additionalCorrectnessExplanationOnModelSolution": "",
304 "body": "",
305 "correct": true,
306 "id": "c9d0e1f2-a3b4-5678-7890-789012345678",
307 "messageAfterSubmissionWhenSelected": "",
308 "title": "Correct answer"
309 },
310 {
311 "order": 2,
312 "additionalCorrectnessExplanationOnModelSolution": "",
313 "body": "",
314 "correct": false,
315 "id": "d0e1f2a3-b4c5-6789-8901-890123456789",
316 "messageAfterSubmissionWhenSelected": "",
317 "title": "Wrong answer"
318 }
319 ],
320 "order": 0,
321 "successMessage": "",
322 "title": "What is the correct answer?",
323 "body": ""
324 }
325 ]
326 })),
327 vec![],
328 )),
329 ),
330 ),
331 );
332
333 let (course, _default_instance, _last_module) = course.seed(&mut conn, &cx).await?;
334
335 Ok(course.id)
336}