-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpython_bootcamp_standalone.html
More file actions
2565 lines (2467 loc) · 188 KB
/
Copy pathpython_bootcamp_standalone.html
File metadata and controls
2565 lines (2467 loc) · 188 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!--
🐍 Python Bootcamp
==================
Created by Clarence Cheung (Octoix AI)
https://github.com/mysnoopy/Python-Bootcamp
MIT License — free to use, fork, and enhance.
If you improve it, a ⭐ on GitHub is always appreciated!
Built with: React + Pyodide + CodeMirror
Version: v2026-03-09f
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>🐍 Python Bootcamp</title>
<script>
window.addEventListener('unhandledrejection', function(event) {
console.error('Unhandled Promise rejection:', event.reason);
var d = document.getElementById('loading-screen');
if (d && d.style.display !== 'none') {
d.innerHTML = '<div style="color:#e06c75;font-family:monospace;padding:30px;white-space:pre-wrap">❌ Async Error: ' + (event.reason && event.reason.message || event.reason) + '</div>';
}
});
</script>
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.23.10/babel.min.js" crossorigin="anonymous"></script>
<!-- CodeMirror -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/eclipse.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Plus Jakarta Sans', -apple-system, 'SF Pro Display', 'Helvetica Neue', system-ui, sans-serif;
height: 100vh; overflow: hidden;
-webkit-font-smoothing: antialiased;
}
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #1E1E35; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #3A3A60; }
input:focus, textarea:focus { outline: none; }
button { font-family: inherit; cursor: pointer; }
.CodeMirror {
height: 100% !important;
font-family: 'Space Mono', 'JetBrains Mono', 'SF Mono', monospace !important;
font-size: 13px !important; line-height: 1.75 !important;
}
.cm-wrap { height: 100%; }
/* ── FORCE SANS-SERIF ON ALL UI TEXT ── */
#root * {
font-family: 'Plus Jakarta Sans', -apple-system, 'Helvetica Neue', system-ui, sans-serif !important;
}
#root .CodeMirror, #root .CodeMirror *, #root code, #root pre {
font-family: 'Space Mono', 'JetBrains Mono', 'SF Mono', monospace !important;
}
/* ── BUTTONS ── */
button { transition: all 0.15s ease !important; }
button:hover { opacity: 0.88 !important; transform: translateY(-1px) !important; }
button:active { transform: translateY(0) !important; opacity: 1 !important; }
/* ── RUN BUTTON GLOW ── */
button[style*="background:#00D084"],
button[style*="background:#00FF87"] {
box-shadow: 0 0 18px rgba(0,255,135,0.35) !important;
font-weight: 700 !important;
}
button[style*="background:#00D084"]:hover,
button[style*="background:#00FF87"]:hover {
box-shadow: 0 4px 28px rgba(0,255,135,0.6) !important;
opacity: 1 !important; transform: translateY(-2px) !important;
}
/* ── CARD HOVER ── */
div[style*="borderRadius:8"][style*="cursor:"pointer""],
div[style*="borderRadius:10"][style*="cursor:"pointer""] {
transition: transform 0.2s ease, box-shadow 0.2s ease !important;
}
div[style*="borderRadius:8"][style*="cursor:"pointer""]:hover,
div[style*="borderRadius:10"][style*="cursor:"pointer""]:hover {
transform: translateY(-3px) !important;
box-shadow: 0 8px 30px rgba(0,0,0,0.45) !important;
}
/* ── LESSON CARDS 2-COL GRID ── */
.phase-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 12px !important;
}
/* ── CONCEPT CHIPS ── */
span[style*="borderRadius:3"] {
font-family: 'Space Mono', monospace !important;
font-size: 11px !important;
border-radius: 20px !important;
padding: 2px 10px !important;
}
/* ── MEM TRICK BOX ── */
div[style*="borderLeft:3px solid"] {
border-radius: 0 8px 8px 0 !important;
}
/* ── CHAT FAB FLOAT ── */
button[style*="borderRadius:50%"][style*="position"] {
box-shadow: 0 4px 20px rgba(0,255,135,0.4) !important;
animation: bc-float 3s ease-in-out infinite !important;
}
button[style*="borderRadius:50%"][style*="position"]:hover {
animation: none !important;
transform: scale(1.1) translateY(-2px) !important;
opacity: 1 !important;
box-shadow: 0 8px 32px rgba(0,255,135,0.6) !important;
}
/* ── PROGRESS BAR ── */
div[style*="height:2"][style*="bgRaised"] + div,
div[style*='width:'][style*='transition:"width'] {
background: linear-gradient(90deg, #00CC6A, #00FF87) !important;
box-shadow: 0 0 8px rgba(0,255,135,0.4) !important;
}
/* ── SUBTLE AMBIENT GLOW ── */
body::before {
content: '';
position: fixed; inset: 0; pointer-events: none; z-index: 0;
background:
radial-gradient(ellipse 70% 35% at 50% 0%, rgba(0,255,135,0.04) 0%, transparent 60%),
radial-gradient(ellipse 50% 40% at 100% 100%, rgba(76,201,240,0.03) 0%, transparent 55%);
}
/* ── ANIMATIONS ── */
@keyframes bc-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
@keyframes bc-spin { to { transform: rotate(360deg); } }
@keyframes bc-load {
0% { width:5%; margin-left:0; }
50% { width:70%; margin-left:0; }
100% { width:5%; margin-left:95%; }
}
@keyframes bc-fade-up {
from { opacity:0; transform:translateY(12px); }
to { opacity:1; transform:translateY(0); }
}
.bc-ring {
width:44px; height:44px; border-radius:50%;
border:3px solid rgba(0,255,135,0.12);
border-top-color:#00FF87;
animation: bc-spin 0.85s linear infinite;
margin-bottom:22px;
}
.bc-t1 { animation: bc-fade-up 0.45s ease 0.05s both; }
.bc-t2 { animation: bc-fade-up 0.45s ease 0.25s both; }
.bc-t3 { animation: bc-fade-up 0.45s ease 0.45s both; }
@media (max-width: 680px) {
.sidebar { display: none !important; }
.lesson-wrap { flex-direction: column !important; }
.vdiv { display: none !important; }
.hdr-sub { display: none !important; }
.phase-grid { grid-template-columns: 1fr !important; }
}
</style>
</head>
<body class="dark">
<div id="root"></div>
<script>
window.onerror = function(msg, src, line, col, err) {
if (err) console.error('Full error:', err);
console.error('Script error details:', msg, 'src:', src, 'line:', line, 'col:', col);
var d = document.getElementById('loading-screen');
if (!d) return;
// If cross-origin "Script error.", try Babel diagnostic
if (msg === 'Script error.' && typeof Babel !== 'undefined') {
var babelSrc = document.querySelector('script[type="text/babel"]');
if (babelSrc) {
try {
Babel.transform(babelSrc.textContent, {presets:['react']});
// No Babel error — show generic message
d.innerHTML = '<div style="color:#e06c75;font-family:monospace;padding:30px">❌ Runtime Error (Script error. — unknown cross-origin error)</div>';
} catch(bErr) {
d.innerHTML = '<div style="color:#e06c75;font-family:monospace;padding:30px;white-space:pre-wrap">'
+ '<div style="font-size:18px;margin-bottom:12px">❌ Babel Parse Error</div>'
+ '<div style="font-size:13px;background:#1e1e2e;padding:12px;border-radius:4px;margin-bottom:8px">' + bErr.message + '</div>'
+ '<div style="font-size:12px;color:#94a3b8">Fix the syntax error above, then reload.</div>'
+ '</div>';
return;
}
}
}
d.innerHTML = '<div style="color:#e06c75;font-family:monospace;padding:30px;max-width:700px;white-space:pre-wrap">'
+ '<div style="font-size:20px;margin-bottom:12px">❌ JavaScript Error</div>'
+ '<div style="font-size:13px;margin-bottom:8px"><b>Message:</b> ' + msg + '</div>'
+ '<div style="font-size:13px;margin-bottom:8px"><b>Line:</b> ' + line + ' Col: ' + col + '</div>'
+ '<div style="font-size:12px;color:#94a3b8">' + (src||'') + '</div>'
+ '</div>';
};
window.addEventListener('unhandledrejection', function(e) {
var d = document.getElementById('loading-screen');
if (d) {
d.innerHTML = '<div style="color:#e06c75;font-family:monospace;padding:30px;max-width:700px;white-space:pre-wrap">'
+ '<div style="font-size:20px;margin-bottom:12px">❌ Unhandled Promise Rejection</div>'
+ '<div style="font-size:13px">' + (e.reason?.message || String(e.reason)) + '</div>'
+ '</div>';
}
});
</script>
<div id="loading-screen" style="position:fixed;inset:0;background:#060609;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:9999;">
<div class="bc-ring"></div>
<div class="bc-t1" style="color:#00FF87;font-size:26px;font-weight:800;letter-spacing:-0.04em;margin-bottom:5px;font-family:-apple-system,sans-serif;">Python Bootcamp</div>
<div class="bc-t2" style="color:#5858A0;font-size:11px;letter-spacing:0.08em;text-transform:uppercase;font-family:-apple-system,sans-serif;">v2026-03-09f · #4c544de3</div>
<div style="animation:bc-fade-up 0.45s ease 0.6s both;margin-bottom:16px;font-size:10px;color:#2A2A50;letter-spacing:0.06em;font-family:-apple-system,sans-serif;">
by Clarence Cheung · Octoix AI · MIT License
</div>
<div class="bc-t3" style="margin-top:26px;width:160px;height:2px;background:#111120;border-radius:2px;overflow:hidden;">
<div style="height:100%;border-radius:2px;background:linear-gradient(90deg,#00CC6A,#00FF87);animation:bc-load 1.8s ease-in-out infinite;"></div>
</div>
</div>
<script type="text/babel">
const { useState, useEffect, useRef, useCallback } = React;
const curriculum = [
{ id:1, phase:"Phase 1", title:"Python Basics", icon:"🐍", color:"#00D084", lessons:[
{ id:"1a", title:"Hello World & Variables", duration:"30 min", project:"🎯 Student Bio Card",
memTrick:"💡 f-strings = f for 'fill in the blank'. {} are blank spaces filled with variables.",
concepts:["print()","variables","f-strings","comments"],
lines:[
{code:'# 🎯 PROJECT: Student Bio Card', exp:"💬 COMMENT — Python ignores lines starting with #. Use them to leave notes."},
{code:'name = "Alex"', exp:'📦 VARIABLE — stores text "Alex". Think of it as a labeled box. = means put this value in.'},
{code:'role = "Python Student"', exp:"📦 VARIABLE — stores a STRING (text in quotes)."},
{code:'city = "New York"', exp:"📦 VARIABLE — stores your city."},
{code:"python_day = 1", exp:"📦 VARIABLE — stores the NUMBER 1. No quotes needed for numbers (INTEGER)."},
{code:"", exp:null},
{code:'print("=" * 40)', exp:'🖨️ PRINT — displays output. "=" * 40 repeats = 40 times to make a divider line.'},
{code:'print(f" 👤 {name}")', exp:"🖨️ F-STRING — f before quotes lets you embed variables inside {}. {name} becomes Alex."},
{code:'print(f" 💼 {role}")', exp:"🖨️ F-STRING — {role} gets replaced with its value."},
{code:'print(f" 📍 {city}")', exp:"🖨️ Prints your city."},
{code:'print(f" 🐍 Python Day #{python_day}")', exp:"🖨️ # inside quotes is just a character, not a comment."},
{code:'print("=" * 40)', exp:"🖨️ Closing divider line."},
]},
{ id:"1b", title:"Conditions & Loops", duration:"45 min", project:"🎯 Student Grade Screener",
memTrick:"💡 if/elif/else = waterfall. First true condition wins. for loop = do this FOR each item.",
concepts:["if/elif/else","for loop","boolean","comparison"],
lines:[
{code:'students = [', exp:"📦 LIST — holds multiple items in order, wrapped in []."},
{code:' {"name": "Alice", "score": 92, "passed": True},', exp:"📦 DICTIONARY inside list — labeled data. True = boolean yes."},
{code:' {"name": "Bob", "score": 65, "passed": False},', exp:"📦 False = boolean no. No quotes — True/False are special Python keywords."},
{code:' {"name": "Carol", "score": 78, "passed": True},', exp:"📦 Third student."},
{code:"]", exp:"📦 Closing bracket — ends the list."},
{code:"", exp:null},
{code:'print("📚 GRADE SCREENER")', exp:"🖨️ Prints the header."},
{code:'print("-" * 35)', exp:"🖨️ Prints 35 dashes as a divider."},
{code:"", exp:null},
{code:"for person in students:", exp:"🔁 FOR LOOP — goes through each item. 'person' holds the current item each time."},
{code:' name = person["name"]', exp:"📦 Gets 'name' value from the dictionary. 4 spaces = INDENTATION (inside the loop)."},
{code:' score = person["score"]', exp:"📦 Gets the student score."},
{code:' passed = person["passed"]', exp:"📦 Gets submission status (True or False)."},
{code:"", exp:null},
{code:" if score >= 80 and passed:", exp:"❓ IF — checks TWO conditions. >= means 'greater than or equal'. 'and' = BOTH must be true."},
{code:' status = "✅ PASS"', exp:"📦 If true, set status. 8 spaces = inside the if block."},
{code:" elif score >= 60 or passed:", exp:"❓ ELIF = 'else if' — checked only if first failed. 'or' = EITHER is enough."},
{code:' status = "🔶 BORDERLINE"', exp:"📦 Meets some criteria."},
{code:" else:", exp:"❓ ELSE — runs if ALL conditions above were false."},
{code:' status = "❌ FAIL"', exp:"📦 Doesn't meet any threshold."},
{code:"", exp:null},
{code:' print(f"{status} {name} | score: {score}")', exp:"🖨️ Prints one line per student with info filled in."},
]},
{ id:"1d", title:"Don't Panic: Reading Errors", duration:"20 min", project:"🚨 Decode 3 Common Errors",
memTrick:"💡 Read errors BOTTOM UP. Last line = what went wrong. Line above = where. TypeError = wrong type. NameError = typo or wrong order. IndexError = list too short.",
concepts:["TypeError","NameError","IndexError","debugging","str()"],
lines:[
{code:"# 🚨 DON'T PANIC: Reading Python Errors", exp:"💬 Errors are normal. Every developer sees them daily. Python's error messages are actually helpful — they tell you exactly what went wrong and where."},
{code:"", exp:null},
{code:"# ── Anatomy of an error ─────────────────────────────────", exp:"💬 Python errors have 3 parts: WHERE it happened (file + line), WHAT the code looked like, and WHY it failed (the error type + message). Always read bottom-up."},
{code:"# TypeError example:", exp:"💬 TypeError = wrong type. You mixed types Python can't combine automatically."},
{code:"age = 25", exp:"📦 age is an int."},
{code:"# print(\"I am \" + age) # ✗ TypeError!", exp:"❌ This would crash: can't add str + int. Python doesn't auto-convert."},
{code:"print(\"I am \" + str(age)) # ✓ Fix: convert", exp:"✅ str() converts age to \"25\" so string + string works."},
{code:"", exp:null},
{code:"# NameError = used a variable before defining it", exp:"💬 NameError = Python doesn't know that name. Usually a typo or wrong order."},
{code:"# print(nmae) # ✗ NameError: name 'nmae' is not defined", exp:"❌ Typo! nmae instead of name. Error message tells you the exact name it couldn't find."},
{code:"name = \"Alice\"", exp:"📦 Define it first."},
{code:"print(name) # ✓ Works now", exp:"✅ Now Python knows what name is."},
{code:"", exp:null},
{code:"# IndexError = list index out of range", exp:"💬 IndexError = you asked for item [n] but the list isn't that long. Remember: lists start at 0."},
{code:"fruits = [\"apple\", \"banana\", \"cherry\"]", exp:"📦 3 items: index 0, 1, 2."},
{code:"# print(fruits[5]) # ✗ IndexError!", exp:"❌ Index 5 doesn't exist. Only 0, 1, 2 are valid."},
{code:"print(fruits[2]) # ✓ Last item = index 2", exp:"✅ fruits[2] = \"cherry\". Or use fruits[-1] for the last item always."},
{code:"", exp:null},
{code:"# ── The golden rule ────────────────────────────────────", exp:"💬 When you see an error: 1️⃣ Read the LAST line first — that's the actual problem. 2️⃣ Look at the line number. 3️⃣ Google the error type + message. You will always find an answer."},
{code:"print(\"Errors are just Python giving you directions 🧭\")", exp:"🖨️ This always runs fine. No error here!"}
]},
]},
{ id:2, phase:"Phase 2", title:"Data Types", icon:"📦", color:"#FF6B6B", lessons:[
{ id:"2a", title:"Lists & Dictionaries", duration:"45 min", project:"🎯 Movie Library Manager",
memTrick:"💡 List = ordered cart [item1, item2]. Dict = labeled box {'key': value}. Access list with [0], dict with ['key'].",
concepts:["list","dict",".append()","list comprehension"],
lines:[
{code:"movies = [", exp:"📦 LIST that holds multiple movie dictionaries."},
{code:' {"title": "Inception", "year": 2010, "genre": "Sci-Fi", "available": True},', exp:"📦 DICTIONARY — one movie with 4 labeled fields."},
{code:' {"title": "The Matrix", "year": 1999, "genre": "Action", "available": True},', exp:"📦 Second movie."},
{code:' {"title": "Interstellar", "year": 2014, "genre": "Sci-Fi", "available": False},', exp:"📦 Unavailable movie — available:False."},
{code:' {"title": "Parasite", "year": 2019, "genre": "Thriller", "available": True},', exp:"📦 Fourth movie."},
{code:"]", exp:"📦 End of list."},
{code:"", exp:null},
{code:'available = [m for m in movies if m["available"]]', exp:'✨ LIST COMPREHENSION — shortcut filter. Read: "give me m FROM movies, only IF available is True".'},
{code:"", exp:null},
{code:'print("🎬 AVAILABLE MOVIES")', exp:"🖨️ Header title."},
{code:'print("-" * 40)', exp:"🖨️ Divider."},
{code:"", exp:null},
{code:"for movie in available:", exp:"🔁 Loop through only available movies."},
{code:" print(movie['title']+'('+str(movie['year'])+') — '+movie['genre'])", exp:"🖨️ Prints title, year, genre."},
{code:" print()", exp:"🖨️ Empty print() = blank line for spacing."},
{code:"", exp:null},
{code:'new_movie = {"title": "Dune", "year": 2021, "genre": "Sci-Fi", "available": True}', exp:"📦 New movie dictionary."},
{code:"movies.append(new_movie)", exp:"➕ .append() ADDS item to END of list."},
{code:"print('✅ Added: '+new_movie['title']+' — now '+str(len(movies))+' total')", exp:"🖨️ len() counts items in a list."},
]},
{ id:"2b", title:"Functions", duration:"40 min", project:"🎯 Shopping Cart Calculator",
memTrick:"💡 def = define a recipe. Parameters = ingredients. return = what the recipe produces. Calling = actually cooking it.",
concepts:["def","parameters","return","default args"],
lines:[
{code:"def calculate_total(price, quantity, discount=0.0):", exp:"🛠️ DEF defines a FUNCTION. price & quantity required. discount optional (defaults to 0.0)."},
{code:' """Calculate total cost with optional discount"""', exp:"📝 DOCSTRING — describes what the function does. Good practice."},
{code:" subtotal = price * quantity", exp:"📦 Calculates subtotal."},
{code:" discount_amt = subtotal * discount", exp:"📦 Calculates discount amount."},
{code:" total = subtotal - discount_amt", exp:"📦 Final total after discount."},
{code:" return round(total, 2)", exp:"↩️ RETURN sends value back. round(total, 2) = 2 decimal places like money."},
{code:"", exp:null},
{code:"def format_receipt(item, price, qty, discount=0.0):", exp:"🛠️ Second function — calls calculate_total() inside it."},
{code:" total = calculate_total(price, qty, discount)", exp:"📦 CALLING the first function! Returns the total."},
{code:" savings = round(price * qty * discount, 2)", exp:"📦 Calculates money saved."},
{code:' print(f" 🛒 {item}")', exp:"🖨️ Prints item name."},
{code:' print(f" {qty} x ${price} | saved: ${savings} | TOTAL: ${total}")', exp:"🖨️ Prints full receipt line."},
{code:"", exp:null},
{code:"cart = [", exp:"📦 List of TUPLES — each holds (item, price, qty, discount)."},
{code:' ("Laptop Stand", 49.99, 2, 0.10),', exp:"📦 2 units at $49.99 with 10% off."},
{code:' ("USB Hub", 29.99, 1, 0.0),', exp:"📦 1 unit, no discount."},
{code:' ("Keyboard", 89.99, 1, 0.15),', exp:"📦 1 unit with 15% off."},
{code:"]", exp:"📦 End of list."},
{code:"", exp:null},
{code:"for item, price, qty, disc in cart:", exp:"🔁 LOOP with UNPACKING — each tuple splits into 4 variables automatically."},
{code:" format_receipt(item, price, qty, disc)", exp:"🖨️ Calls format_receipt() for each cart item."},
]},
{ id:"2e", title:"String Methods", duration:"35 min", project:"🎯 Text Transformer",
memTrick:"💡 Strings are immutable — methods return a NEW string, they don't change the original. Chain them: text.strip().lower().replace('a','b'). .split() = string → list. .join() = list → string.",
concepts:[".strip()",".split()",".join()",".replace()",".find()","in operator"],
lines:[
{code:"# 🎯 PROJECT: Text Transformer", exp:"💬 Strings have dozens of built-in methods. These are the ones you'll use in almost every real project."},
{code:"", exp:null},
{code:"sentence = \" Hello, World! This is Python. \"", exp:"📦 A messy string with extra spaces — typical of real-world data."},
{code:"", exp:null},
{code:"# ── Cleaning ────────────────────────────────────────────", exp:"💬 Always clean user input before using it. .strip() is the most common first step."},
{code:"print(sentence.strip())", exp:"✂️ .strip() removes leading and trailing whitespace. .lstrip() = left only. .rstrip() = right only."},
{code:"print(sentence.strip().lower())", exp:"🔧 Chain methods! .lower() = all lowercase. Useful for comparisons — \"Python\" == \"python\" is False without it."},
{code:"print(sentence.strip().upper())", exp:"📢 .upper() = ALL CAPS."},
{code:"", exp:null},
{code:"# ── Searching ────────────────────────────────────────────", exp:"💬 Find things inside strings."},
{code:"print(\"Python\" in sentence)", exp:"🔍 in operator — True if the substring exists. Fastest way to check if a word appears."},
{code:"print(sentence.find(\"World\"))", exp:"🔢 .find() returns the INDEX of the first match, or -1 if not found. Great for \"does this contain X?\""},
{code:"print(sentence.count(\"i\"))", exp:"🔢 .count() = how many times does this substring appear?"},
{code:"print(sentence.startswith(\" Hello\"))", exp:"✅ .startswith() / .endswith() — check the beginning or end. Common for file extensions, URLs."},
{code:"", exp:null},
{code:"# ── Transforming ─────────────────────────────────────────", exp:"💬 Change strings without modifying the original — strings are immutable in Python."},
{code:"print(sentence.replace(\"Python\", \"🐍 Python\"))", exp:"🔄 .replace(old, new) — swap any substring. Doesn't change the original, returns a new string."},
{code:"print(sentence.strip().replace(\"!\", \"?\"))", exp:"🔄 Chain with strip() first to clean, then replace."},
{code:"", exp:null},
{code:"# ── Splitting & Joining ─────────────────────────────────", exp:"💬 .split() turns a string into a list. .join() turns a list into a string. Two sides of the same coin."},
{code:"words = \"apple,banana,cherry,date\"", exp:"📦 A CSV-style string."},
{code:"fruit_list = words.split(\",\")", exp:"✂️ .split(delimiter) — break string into list at each delimiter. No arg = split on whitespace."},
{code:"print(fruit_list)", exp:"🖨️ Prints: ['apple', 'banana', 'cherry', 'date']"},
{code:"print(\" | \".join(fruit_list))", exp:"🔗 .join(list) — glue a list back into a string with a separator. \" | \".join(...) = \"apple | banana | cherry | date\""}
]},
]},
{ id:3, phase:"Phase 3", title:"OOP", icon:"🏗️", color:"#4ECDC4", lessons:[
{ id:"3a", title:"Classes & Objects", duration:"60 min", project:"🎯 Library Book System",
memTrick:"💡 Class = cookie cutter mold. Object = actual cookie. self = cookie referring to itself. __init__ = recipe that runs when cookie is made.",
concepts:["class","__init__","self","methods","inheritance"],
lines:[
{code:"class Book:", exp:"🏗️ CLASS — a blueprint for creating Book objects."},
{code:' library = "City Public Library"', exp:"📦 CLASS VARIABLE — shared by ALL Book objects."},
{code:"", exp:null},
{code:" def __init__(self, title, author, year):", exp:"🛠️ CONSTRUCTOR — runs automatically when you create a Book. self = this specific object."},
{code:" self.title = title", exp:"📦 INSTANCE VARIABLE — unique to each book."},
{code:" self.author = author", exp:"📦 Stores author on this book."},
{code:" self.year = year", exp:"📦 Stores year published."},
{code:" self.checkouts = []", exp:"📦 Each book starts with empty checkouts list."},
{code:"", exp:null},
{code:" def checkout(self, borrower):", exp:"🛠️ METHOD — a function belonging to the class. self always first."},
{code:" self.checkouts.append(borrower)", exp:"➕ Adds borrower to THIS book's checkouts list."},
{code:' return f"✅ {self.title} checked out by {borrower}"', exp:"↩️ Returns confirmation message."},
{code:"", exp:null},
{code:" def info(self):", exp:"🛠️ Method to generate book info string."},
{code:" times = len(self.checkouts)", exp:"📦 Counts checkouts."},
{code:' return f"📖 {self.title} by {self.author} ({self.year}) | {times}x checked out"', exp:"↩️ Returns formatted info."},
{code:"", exp:null},
{code:"class EBook(Book):", exp:"🏗️ INHERITANCE — EBook extends Book. Gets all Book methods automatically."},
{code:" def __init__(self, title, author, year, file_size_mb):", exp:"🛠️ EBook needs one extra: file_size_mb."},
{code:" super().__init__(title, author, year)", exp:"🔗 super() calls Parent's __init__ — sets up title, author, year first."},
{code:" self.file_size = file_size_mb", exp:"📦 EBook-specific variable — digital file size."},
{code:"", exp:null},
{code:" def info(self):", exp:"🛠️ OVERRIDE — redefines parent's method. EBook version runs instead."},
{code:" base = super().info()", exp:"🔗 Gets parent's info text, then we add file size."},
{code:' return f"{base} | 💾 {self.file_size}MB"', exp:"↩️ Appends file size to base info."},
{code:"", exp:null},
{code:'book1 = Book("Atomic Habits", "James Clear", 2018)', exp:"📦 Creates a Book object. Python calls __init__ automatically."},
{code:'book2 = EBook("Deep Work", "Cal Newport", 2016, file_size_mb=3)', exp:"📦 EBook object — gets all Book features + file_size."},
{code:"", exp:null},
{code:'print(book1.checkout("Alice"))', exp:"🖨️ Calls checkout() on book1."},
{code:'print(book1.checkout("Bob"))', exp:"🖨️ book1.checkouts now has 2 names."},
{code:'print(book2.checkout("Carol"))', exp:"🖨️ Carol uses the inherited checkout method."},
{code:"print(book1.info())", exp:"🖨️ Book's info() — shows checked out 2x."},
{code:"print(book2.info())", exp:"🖨️ EBook's OVERRIDDEN version — includes file size."},
{code:'print(f"Library: {Book.library}")', exp:"🖨️ Accesses CLASS VARIABLE directly from the class."},
]},
]},
{ id:4, phase:"Phase 4", title:"APIs", icon:"🔌", color:"#A78BFA", lessons:[
{ id:"4a", title:"REST APIs & HTTP", duration:"60 min", project:"🎯 Live Weather Fetcher",
memTrick:"💡 API = restaurant. You ORDER from a URL. The server sends back JSON. open_url() is Pyodide's built-in browser-safe HTTP fetcher.",
concepts:["pyodide.http","GET request","JSON","error handling"],
lines:[
{code:"from pyodide.http import open_url", exp:"📦 open_url = Pyodide's browser-safe HTTP fetcher. Built-in — no pip install needed!"},
{code:"import json", exp:"📦 json = built-in parser. Converts JSON text → Python dictionary."},
{code:"", exp:null},
{code:'def fetch_weather(city="New York"):', exp:'🛠️ Function with DEFAULT parameter — uses "New York" if nothing provided.'},
{code:' url = "https://api.open-meteo.com/v1/forecast?latitude=40.71&longitude=-74.01¤t_weather=true"', exp:"📦 Free weather API — no key required!"},
{code:" try:", exp:"🛡️ TRY — run this, catch errors below instead of crashing."},
{code:" response = open_url(url)", exp:"🌐 SENDS the GET request. open_url works in Pyodide's browser sandbox."},
{code:" data = json.loads(response.read())", exp:"📦 Reads response and parses JSON → Python dict."},
{code:' weather = data.get("current_weather", {})', exp:"📦 Gets 'current_weather' safely. {} if key missing."},
{code:"", exp:null},
{code:' print(f"✅ Weather for {city}:")', exp:"🖨️ Prints city header."},
{code:' temp = weather.get("temperature", "N/A")', exp:"📦 Gets temperature value."},
{code:' wind = weather.get("windspeed", "N/A")', exp:"📦 Gets wind speed."},
{code:' wcode = weather.get("weathercode", "?")', exp:"📦 WMO weather code: 0=Clear, 45=Fog, 61=Rain, 71=Snow."},
{code:' print(f" 🌡️ Temp : {temp}°C")', exp:"🖨️ Prints temperature."},
{code:' print(f" 💨 Wind : {wind} km/h")', exp:"🖨️ Prints wind speed."},
{code:' print(f" 🌤 Code : {wcode}")', exp:"🖨️ Prints weather code."},
{code:" except Exception as e:", exp:"🛡️ Catches any error — network, bad JSON, etc."},
{code:' print(f"❌ Error: {e}")', exp:"🖨️ Friendly error instead of crash."},
{code:"", exp:null},
{code:'fetch_weather("New York")', exp:"▶️ Calls the function — fetches live weather and prints it!"},
]},
]},
{ id:5, phase:"Phase 5", title:"Frameworks", icon:"⚙️", color:"#F59E0B", lessons:[
{ id:"5a", title:"FastAPI Backend", isSim:true, duration:"90 min", project:"🎯 Book Store REST API",
memTrick:"💡 @app.get = answer when someone READS. @app.post = answer when someone SENDS. The @ decorator means 'when this URL is called, run the function below'.",
concepts:["FastAPI","routes","Pydantic","decorators","uvicorn"],
lines:[
{code:"from fastapi import FastAPI, HTTPException", exp:"📦 FastAPI = the web framework. HTTPException = send error responses."},
{code:"from pydantic import BaseModel", exp:"📦 BaseModel = defines data shapes with auto-validation."},
{code:"from typing import Optional", exp:"📦 Optional = a field that may or may not be present."},
{code:"", exp:null},
{code:"app = FastAPI()", exp:"🏗️ CREATE the app — like Flask's app = Flask(__name__)."},
{code:"", exp:null},
{code:"class Book(BaseModel):", exp:"🏗️ DATA MODEL — Pydantic validates incoming JSON automatically."},
{code:" title: str", exp:"📦 title must be a string — enforced automatically."},
{code:" price: float", exp:"📦 price must be a number — auto-converted from JSON."},
{code:" category: str", exp:"📦 category must be a string."},
{code:"", exp:null},
{code:"books_db = [", exp:"📦 In-memory database — a simple list of dicts."},
{code:" {'id':1, 'title':'Clean Code', 'price':29.99, 'category':'tech'},", exp:"📦 Seed data — book 1."},
{code:" {'id':2, 'title':'Dune', 'price':14.99, 'category':'sci-fi'},", exp:"📦 Seed data — book 2."},
{code:"]", exp:"📦 Closes the list."},
{code:"", exp:null},
{code:"@app.get('/')", exp:"🔗 ROUTE — @app.get('/') maps HTTP GET / to this function."},
{code:"def root():", exp:"🛠️ Handler function — runs when someone visits /."},
{code:" return {'message': 'Book Store API', 'total': len(books_db)}", exp:"↩️ FastAPI auto-converts dicts to JSON responses."},
{code:"", exp:null},
{code:"@app.get('/books')", exp:"🔗 GET /books — returns all books or filtered by category."},
{code:"def get_books(category: Optional[str] = None):", exp:"🛠️ Query param ?category=tech is auto-parsed by FastAPI."},
{code:" if category:", exp:"❓ Was a category filter provided?"},
{code:" return [b for b in books_db if b['category']==category]", exp:"↩️ Filter and return matching books."},
{code:" return books_db", exp:"↩️ No filter — return everything."},
{code:"", exp:null},
{code:"@app.post('/books')", exp:"🔗 POST /books — creates a new book."},
{code:"def create_book(book: Book):", exp:"🛠️ book: Book auto-validates the JSON body against the model."},
{code:" new_book = {'id': len(books_db)+1, **book.dict()}", exp:"📦 ** unpacks the Pydantic model into a dict."},
{code:" books_db.append(new_book)", exp:"📦 Add to our in-memory DB."},
{code:" return new_book", exp:"↩️ Return the created book with its new ID."},
{code:"", exp:null},
{code:"@app.delete('/books/{book_id}')", exp:"🔗 DELETE /books/1 — path param {book_id} auto-extracted."},
{code:"def delete_book(book_id: int):", exp:"🛠️ book_id: int — FastAPI converts the string to int automatically."},
{code:" book = next((b for b in books_db if b['id']==book_id), None)", exp:"📦 Find the book or return None."},
{code:" if not book:", exp:"❓ Does the book exist?"},
{code:" raise HTTPException(status_code=404, detail='Book not found')", exp:"↩️ HTTPException sends a proper 404 JSON error."},
{code:" books_db.remove(book)", exp:"📦 Remove from DB."},
{code:" return {'deleted': book_id}", exp:"↩️ Confirm deletion."}
]},
]},
{ id:6, phase:"Phase 6", title:"AI / LLMs", icon:"🤖", color:"#EC4899", lessons:[
{ id:"6a", title:"AI API — Movie Recommender", duration:"60 min", project:"🎯 AI Movie Recommender",
memTrick:"💡 LLM call = sending a letter. system = instructions on envelope. messages = conversation history. max_tokens = max words in reply.",
concepts:["REST API","urllib","system prompt","tokens","JSON"],
lines:[
{code:"# ✅ Your API key is injected automatically from AI Tutor settings", exp:"💬 SETUP — key + provider injected automatically."},
{code:"import json", exp:"📦 json parses the AI's JSON response into a Python dict."},
{code:"", exp:null},
{code:"def call_ai(system_prompt, user_message):", exp:"🛠️ Takes a role (system) + task (user) — auto-filled by bootcamp."},
{code:" pass # auto-filled by bootcamp", exp:"💬 Replaced with real API call when lesson loads."},
{code:"", exp:null},
{code:"def clean_json(text):", exp:"🛠️ Strips markdown fences AI sometimes adds around JSON."},
{code:" text = text.strip()", exp:"📦 Remove leading/trailing whitespace."},
{code:" if text.startswith('```'):", exp:"❓ Did AI wrap response in markdown code block?"},
{code:" lines = text.split('\\n')", exp:"📦 Split into lines."},
{code:" text = '\\n'.join(lines[1:-1])", exp:"📦 Drop first and last lines (the fences)."},
{code:" return text.strip()", exp:"↩️ Return clean JSON string."},
{code:"", exp:null},
{code:"def ai_recommend_movie(user_prefs, movies):", exp:"🛠️ Sends prefs + movies to AI, returns recommendation dict."},
{code:" system_prompt = (", exp:"📦 SYSTEM PROMPT — sets the AI role."},
{code:" 'You are a friendly movie expert. '", exp:"📦 The role."},
{code:" 'Return a JSON object with keys: recommendation, score (0-100), reason. '", exp:"📦 Exact output format."},
{code:" 'Be enthusiastic and concise. Return ONLY valid JSON, no extra text.'", exp:"📦 No markdown, just raw JSON."},
{code:" )", exp:"📦 Closes system_prompt string."},
{code:"", exp:null},
{code:" prefs_str = json.dumps(user_prefs, indent=2)", exp:"📦 Convert prefs dict to readable JSON string."},
{code:" movies_str = json.dumps(movies, indent=2)", exp:"📦 Convert movies list to readable JSON string."},
{code:" user_message = 'User Preferences:\\n' + prefs_str + '\\n\\nAvailable Movies:\\n' + movies_str", exp:"📦 USER MESSAGE — the actual task sent to AI."},
{code:"", exp:null},
{code:" raw = call_ai(system_prompt, user_message)", exp:"🌐 CALL AI — sends both prompts, returns text response."},
{code:" return json.loads(clean_json(raw))", exp:"↩️ Strip fences then parse JSON → Python dict."},
{code:"", exp:null},
{code:"user_prefs = {'genres': ['sci-fi', 'thriller'], 'mood': 'mind-bending'}", exp:"📦 Sample user preferences."},
{code:"movies = [", exp:"📦 Movie options to choose from."},
{code:" {'title': 'Inception', 'genre': 'sci-fi'},", exp:"📦 Movie 1."},
{code:" {'title': 'The Matrix', 'genre': 'sci-fi'},", exp:"📦 Movie 2."},
{code:" {'title': 'The Notebook', 'genre': 'romance'},", exp:"📦 Movie 3."},
{code:"]", exp:"📦 Closes movies list."},
{code:"", exp:null},
{code:"result = ai_recommend_movie(user_prefs, movies)", exp:"▶️ Run it — sends to AI, gets recommendation back."},
{code:"print('Pick: ' + result['recommendation'])", exp:"🖨️ Prints AI movie recommendation."},
{code:"print('Score: ' + str(result['score']))", exp:"🖨️ Prints confidence score."},
{code:"print('Why: ' + result['reason'])", exp:"🖨️ Prints AI reasoning."}
]},
]},
{ id:7, phase:"Phase 7", title:"AI Topics", icon:"🧠", color:"#f472b6", lessons:[
{ id:"7a", title:"Prompt Engineering", duration:"45 min", project:"🎯 Smart Prompt Builder",
memTrick:"💡 Prompt = instructions you give the AI. Better instructions = better output. Think like telling a new employee exactly what you want.",
concepts:["system prompts","few-shot examples","chain-of-thought","structured output"],
lines:[
{code:"# ✅ Your API key is injected automatically from AI Tutor settings", exp:"💬 SETUP — key injected from settings."},
{code:"import json", exp:"📦 json needed by call_ai."},
{code:"def call_ai(prompt):", exp:"🛠️ AI function — auto-filled by bootcamp."},
{code:" pass # auto-filled by bootcamp", exp:"▶️ Replaced with real API call automatically."},
{code:"", exp:null},
{code:"# --- TECHNIQUE 1: Specific Prompt ---", exp:"💬 Being specific gets better AI answers."},
{code:"specific = (", exp:"📦 Multi-line string using parentheses — each string auto-concatenates."},
{code:" 'You are a vet writing for new pet owners. '", exp:"📦 Set the role and audience."},
{code:" 'Write a 3-sentence tip about feeding puppies. '", exp:"📦 Exact task."},
{code:" 'Use simple language. End with one actionable tip.'", exp:"📦 Format instructions."},
{code:")", exp:"📦 Closes the parenthesised string."},
{code:"", exp:null},
{code:"# --- TECHNIQUE 2: Few-Shot ---", exp:"💬 Show examples so AI learns the pattern."},
{code:"few_shot = (", exp:"📦 Few-shot string."},
{code:" 'Classify sentiment.\\n'", exp:"📦 Task header. \n = newline."},
{code:" 'Text: I love this! -> Positive\\n'", exp:"📦 Example 1."},
{code:" 'Text: Terrible. -> Negative\\n'", exp:"📦 Example 2."},
{code:" 'Text: Its okay. -> Neutral\\n'", exp:"📦 Example 3."},
{code:" 'Now classify: Text: Best purchase ever!'", exp:"📦 The real question."},
{code:")", exp:"📦 Closes the string."},
{code:"", exp:null},
{code:"# --- TECHNIQUE 3: Chain-of-Thought ---", exp:"💬 Ask AI to reason step-by-step."},
{code:"cot = (", exp:"📦 Chain-of-thought string."},
{code:" 'A store has 50 apples. Sells 30% on Monday, '", exp:"📦 Problem setup."},
{code:" 'receives 20 more on Tuesday. How many now? '", exp:"📦 The question."},
{code:" 'Show your reasoning then give the final answer.'", exp:"📦 Forces step-by-step thinking."},
{code:")", exp:"📦 Closes the string."},
{code:"", exp:null},
{code:"def test_prompt(label, prompt):", exp:"🛠️ Helper to run and print each technique."},
{code:" result = call_ai(prompt)", exp:"🌐 Send prompt to AI."},
{code:" print('=== ' + label + ' ===')", exp:"🖨️ Print section header."},
{code:" print(result)", exp:"🖨️ Print AI response."},
{code:" print()", exp:"🖨️ Blank line separator."},
{code:"", exp:null},
{code:"test_prompt('Specific Prompt', specific)", exp:"▶️ Test technique 1."},
{code:"test_prompt('Few-Shot', few_shot)", exp:"▶️ Test technique 2."},
{code:"test_prompt('Chain-of-Thought', cot)", exp:"▶️ Test technique 3."}
]},
{ id:"7b", title:"RAG — Retrieval Augmented Generation", duration:"60 min", project:"🎯 Knowledge Base Q&A",
memTrick:"💡 RAG = Give AI a reference book before asking. Without RAG, AI only knows training data. With RAG, it answers from YOUR documents.",
concepts:["retrieval","context injection","chunking","keyword search","grounding"],
lines:[
{code:"# ✅ Your API key is injected automatically from AI Tutor settings", exp:"💬 SETUP — key injected from settings."},
{code:"import json", exp:"📦 json needed by call_ai."},
{code:"def call_ai(prompt):", exp:"🛠️ AI function — auto-filled by bootcamp."},
{code:" pass # auto-filled by bootcamp", exp:"▶️ Replaced with real API call."},
{code:"", exp:null},
{code:"knowledge_base = [", exp:"📦 STORE — our mini database of Python facts."},
{code:" {'topic': 'Python Lists', 'content': 'Use append() to add, pop() to remove, len() to count.'},", exp:"📦 Doc 1."},
{code:" {'topic': 'Python Dicts', 'content': 'Use dict[key] to access, .get() for safe access.'},", exp:"📦 Doc 2."},
{code:" {'topic': 'Python Functions', 'content': 'Define with def. Use return for outputs.'},", exp:"📦 Doc 3."},
{code:" {'topic': 'Python Classes', 'content': 'self refers to the instance. __init__ is the constructor.'},", exp:"📦 Doc 4."},
{code:"]", exp:"📦 Closes knowledge_base list."},
{code:"", exp:null},
{code:"def retrieve(query, kb, top_k=2):", exp:"🛠️ RETRIEVE — find most relevant docs by keyword match."},
{code:" words = query.lower().split()", exp:"📦 Split query into words."},
{code:" scores = []", exp:"📦 Will hold (score, doc) pairs."},
{code:" for doc in kb:", exp:"🔁 Check every document."},
{code:" text = (doc['topic'] + ' ' + doc['content']).lower()", exp:"📦 Combine topic + content for matching."},
{code:" score = sum(1 for w in words if w in text)", exp:"📦 Count matching words — simple relevance score."},
{code:" scores.append((score, doc))", exp:"📦 Save score paired with doc."},
{code:" scores.sort(key=lambda x: x[0], reverse=True)", exp:"📦 Sort: highest score first."},
{code:" return [doc for _, doc in scores[:top_k]]", exp:"↩️ Return top_k most relevant docs."},
{code:"", exp:null},
{code:"def rag_answer(question):", exp:"🛠️ FULL RAG PIPELINE — retrieve then generate."},
{code:" docs = retrieve(question, knowledge_base)", exp:"🔍 Step 1: RETRIEVE relevant docs."},
{code:" context = '\\n'.join([d['topic'] + ': ' + d['content'] for d in docs])", exp:"📦 Step 2: AUGMENT — format docs as context."},
{code:" prompt = 'Context:\\n' + context + '\\n\\nQuestion: ' + question + '\\nAnswer concisely.'", exp:"📦 Step 3: Build grounded prompt."},
{code:" answer = call_ai(prompt)", exp:"🌐 Step 4: GENERATE answer from context."},
{code:" print('Q: ' + question)", exp:"🖨️ Print question."},
{code:" print('Retrieved:', [d['topic'] for d in docs])", exp:"🖨️ Show which docs were used."},
{code:" print('A: ' + str(answer))", exp:"🖨️ Print grounded answer."},
{code:" print()", exp:"🖨️ Blank line."},
{code:"", exp:null},
{code:"rag_answer('How do I add items to a list?')", exp:"▶️ Should retrieve Python Lists doc."},
{code:"rag_answer('What is self in a class?')", exp:"▶️ Should retrieve Python Classes doc."}
]},
{ id:"7c", title:"AI Agents & Tools", duration:"60 min", project:"🎯 Personal Assistant Agent",
memTrick:"💡 Agent = AI that can DECIDE and ACT. Normal AI: you ask, it answers. Agent: you give a goal, it picks tools, takes steps, completes autonomously.",
concepts:["tool use","agent loop","function calling","multi-step reasoning"],
lines:[
{code:"# ✅ Your API key is injected automatically from AI Tutor settings", exp:"💬 SETUP — key injected from settings."},
{code:"import json", exp:"📦 json needed by call_ai."},
{code:"from datetime import datetime", exp:"📦 For the get_time tool."},
{code:"def call_ai(prompt):", exp:"🛠️ AI function — auto-filled by bootcamp."},
{code:" pass # auto-filled by bootcamp", exp:"▶️ Replaced with real API call."},
{code:"", exp:null},
{code:"def get_weather(city):", exp:"🛠️ Tool 1 — fake weather lookup."},
{code:" return 'Weather in ' + city + ': 22 degrees, Sunny'", exp:"↩️ Returns fake weather string."},
{code:"", exp:null},
{code:"def calculate(expression):", exp:"🛠️ Tool 2 — calculator."},
{code:" return 'Result: ' + str(eval(expression))", exp:"↩️ eval('25*4') computes to 100."},
{code:"", exp:null},
{code:"def get_time():", exp:"🛠️ Tool 3 — current time."},
{code:" return str(datetime.now())[:16]", exp:"↩️ Returns formatted datetime string."},
{code:"", exp:null},
{code:"tools_desc = 'get_weather(city), calculate(expression), get_time()'", exp:"📦 Tool list as a string — passed to AI."},
{code:"", exp:null},
{code:"def run_agent(user_request):", exp:"🛠️ Main agent — takes a goal and uses tools."},
{code:" print('Request: ' + user_request)", exp:"🖨️ Shows the user goal."},
{code:" prompt = 'You have tools: ' + tools_desc + '\\n'", exp:"📦 Tell AI what tools exist."},
{code:" prompt += 'Request: ' + user_request + '\\n'", exp:"📦 Add the user request."},
{code:" prompt += 'For each tool needed write: TOOL: name(arg)\\n'", exp:"📦 Instruct AI to use TOOL: format."},
{code:" prompt += 'Then write a one-sentence summary.'", exp:"📦 Ask for a final summary."},
{code:" response = call_ai(prompt)", exp:"🌐 AI decides which tools to call."},
{code:" results = []", exp:"📦 Collect tool results here."},
{code:" for line in response.split('\\n'):", exp:"🔁 Scan each line of AI response for TOOL: calls."},
{code:" if 'TOOL: get_weather(' in line:", exp:"❓ Did AI request the weather tool?"},
{code:" city = line.split('(')[1].split(')')[0]", exp:"📦 Extract city from TOOL: get_weather(Tokyo)."},
{code:" results.append(get_weather(city))", exp:"▶️ Run the tool, store result."},
{code:" elif 'TOOL: calculate(' in line:", exp:"❓ Did AI request the calculator?"},
{code:" expr = line.split('(')[1].split(')')[0]", exp:"📦 Extract expression from TOOL: calculate(15*0.85)."},
{code:" results.append(calculate(expr))", exp:"▶️ Run the calculator, store result."},
{code:" elif 'TOOL: get_time' in line:", exp:"❓ Did AI request the time tool?"},
{code:" results.append(get_time())", exp:"▶️ Run get_time(), store result."},
{code:" if results:", exp:"❓ Were any tools called?"},
{code:" print('Tool results: ' + str(results))", exp:"🖨️ Show what the tools returned."},
{code:" non_empty = [l for l in response.split('\\n') if l.strip()]", exp:"📦 Filter out empty lines."},
{code:" if non_empty:", exp:"❓ Did AI respond at all?"},
{code:" print('AI: ' + non_empty[-1])", exp:"🖨️ Print AI final summary line."},
{code:" print()", exp:"🖨️ Blank line separator."},
{code:"", exp:null},
{code:"run_agent('What is the weather in Tokyo and what is 15 percent of 85?')", exp:"▶️ Agent should call weather + calculator."},
{code:"run_agent('What time is it right now?')", exp:"▶️ Agent should use the time tool."}
]},
]},
{ id:8, phase:"Phase 8", title:"Data & Files", icon:"🗄️", color:"#f59e0b", lessons:[
{ id:"8a", title:"File I/O: Read & Write Files", duration:"35 min", project:"🎯 Personal Notes App",
memTrick:"💡 open(file, mode) — 'w'=write, 'r'=read, 'a'=append. Always use with: so the file auto-closes. .read() = whole file. Loop over file = line by line. os.path.exists() = safe guard before reading.",
concepts:["open()","with statement","read","write","append","os.path"],
lines:[
{code:"# 🎯 PROJECT: Personal Notes App", exp:"💬 Reading and writing files is one of the most practical Python skills. Every real app saves data. open() is your gateway to the filesystem."},
{code:"", exp:null},
{code:"import os", exp:"📦 os module lets us check if files exist, delete them, etc."},
{code:"", exp:null},
{code:"# ── WRITE a file ───────────────────────────────────────────", exp:"💬 open(filename, mode) — mode \"w\" = write (creates or overwrites). \"a\" = append. \"r\" = read. Always use with: so the file closes automatically."},
{code:"with open(\"notes.txt\", \"w\") as f:", exp:"📂 WITH STATEMENT — opens the file and auto-closes it when the block ends. f is the file object."},
{code:" f.write(\"Note 1: Python is awesome\\n\")", exp:"✏️ .write() writes a string. \\n = newline. Without it everything runs together on one line."},
{code:" f.write(\"Note 2: Files save data forever\\n\")", exp:"✏️ Write another line."},
{code:" f.write(\"Note 3: with: auto-closes the file\\n\")", exp:"✏️ One more note."},
{code:"print(\"File written!\")", exp:"🖨️ Confirm it worked."},
{code:"", exp:null},
{code:"# ── READ the whole file ──────────────────────────────────────", exp:"💬 mode \"r\" = read. .read() returns the entire file as one string."},
{code:"with open(\"notes.txt\", \"r\") as f:", exp:"📂 Open same file for reading."},
{code:" content = f.read()", exp:"📋 .read() = entire file as one big string."},
{code:"print(\"=== File contents ===\")", exp:"🖨️ Label."},
{code:"print(content)", exp:"🖨️ Prints all 3 notes."},
{code:"", exp:null},
{code:"# ── READ line by line ────────────────────────────────────────", exp:"💬 Loop directly over a file object to read one line at a time. Memory-efficient for large files."},
{code:"print(\"=== Line by line ===\")", exp:"🖨️ Label."},
{code:"with open(\"notes.txt\", \"r\") as f:", exp:"📂 Open for reading again."},
{code:" for i, line in enumerate(f, 1):", exp:"🔁 Loop over file directly — each iteration = one line. enumerate gives us a line number."},
{code:" print(f\" Line {i}: {line.strip()}\")", exp:"🖨️ .strip() removes the \\n at the end of each line."},
{code:"", exp:null},
{code:"# ── APPEND to a file ──────────────────────────────────────────", exp:"💬 mode \"a\" = append. Adds to the end WITHOUT overwriting. Use this to add new entries to a log or notes file."},
{code:"with open(\"notes.txt\", \"a\") as f:", exp:"📂 mode \"a\" = append."},
{code:" f.write(\"Note 4: Append adds without overwriting\\n\")", exp:"✏️ Adds a 4th note."},
{code:"", exp:null},
{code:"# ── Check file exists ────────────────────────────────────────", exp:"💬 Always check before reading so your program doesn't crash on a missing file."},
{code:"if os.path.exists(\"notes.txt\"):", exp:"🔍 os.path.exists() = True if the file is there. Safe guard before reading."},
{code:" print(f\"File has {os.path.getsize('notes.txt')} bytes\")", exp:"📏 .getsize() = file size in bytes. Good for sanity checking."},
{code:"os.remove(\"notes.txt\")", exp:"🗑️ Clean up — delete the file when done. os.remove() deletes permanently."},
{code:"print(\"Done! File deleted.\")", exp:"🖨️ Confirm cleanup."}
]},
{ id:"8b", duration:"40 min", project:"🎯 Sales Report Generator",
memTrick:"💡 CSV = spreadsheet as plain text. JSON = Python dict saved to a file. Both are just text files with rules.",
concepts:["csv.reader","csv.writer","json.load","json.dump","with open()"],
lines:[
{code:"import csv, json", exp:"📦 IMPORT — two built-in modules. csv handles spreadsheet files. json handles data files. No pip needed."},
{code:"", exp:null},
{code:"# ── PART 1: Read a CSV ──", exp:"💬 We'll create a fake sales CSV in memory, then read it back."},
{code:"import io", exp:"📦 io.StringIO lets us fake a file in memory — great for demos without real files."},
{code:"", exp:null},
{code:"csv_data = '''name,sales,region", exp:"📝 RAW STRING — triple quotes let us write multi-line text. This is our fake CSV content."},
{code:"Alice,15000,North", exp:"📋 ROW 1 — Alice sold 15000 in the North region."},
{code:"Bob,22000,South", exp:"📋 ROW 2 — Bob sold 22000 in the South region."},
{code:"Carol,18500,North", exp:"📋 ROW 3 — Carol sold 18500 in the North region."},
{code:"Dave,9800,East'''", exp:"📋 ROW 4 — Dave sold 9800 in the East region. Triple quotes close here."},
{code:"", exp:null},
{code:"reader = csv.reader(io.StringIO(csv_data))", exp:"📖 csv.reader turns the text into rows we can loop over. io.StringIO wraps the string so it acts like a file."},
{code:"headers = next(reader)", exp:"⏭️ next() reads ONE row — the first row is always headers. We skip it so it doesn't appear in our data."},
{code:"print('Headers:', headers)", exp:"🖨️ Print the column names: ['name', 'sales', 'region']"},
{code:"", exp:null},
{code:"total = 0", exp:"🔢 COUNTER — start at 0, we'll add each person's sales."},
{code:"for row in reader:", exp:"🔁 LOOP — reader gives us one row at a time as a list like ['Alice', '15000', 'North']."},
{code:" name, sales, region = row", exp:"📦 UNPACK — split the list into 3 variables at once. Cleaner than row[0], row[1], row[2]."},
{code:" total += int(sales)", exp:"➕ int() converts '15000' (text) to 15000 (number). += adds to total."},
{code:" print(f'{name} ({region}): ${int(sales):,}')", exp:"🖨️ f-string with :, format adds comma separators — 15000 becomes 15,000."},
{code:"", exp:null},
{code:"print(f'\\nTotal Sales: ${total:,}')", exp:"🖨️ Final total with comma formatting. \\n adds a blank line before it."},
{code:"", exp:null},
{code:"# ── PART 2: Save & Load JSON ──", exp:"💬 JSON is perfect for saving structured data like dicts and lists."},
{code:"report = {", exp:"📦 DICT — build a summary report as a Python dictionary."},
{code:" 'total_sales': total,", exp:"🔑 KEY — store the total we calculated above."},
{code:" 'top_performer': 'Bob',", exp:"🔑 KEY — hardcoded for this demo. In real code you'd calculate this."},
{code:" 'regions': ['North', 'South', 'East']", exp:"🔑 KEY — a list of regions as a value."},
{code:"}", exp:"🔒 Close the dict."},
{code:"", exp:null},
{code:"json_str = json.dumps(report, indent=2)", exp:"💾 json.dumps = dict TO string. indent=2 makes it pretty-printed (indented). The 's' in dumps = string."},
{code:"print('\\nJSON Output:')", exp:"🖨️ Label before the JSON output."},
{code:"print(json_str)", exp:"🖨️ Print the formatted JSON — looks like a nicely indented dict."},
{code:"", exp:null},
{code:"loaded = json.loads(json_str)", exp:"📂 json.loads = string TO dict. The 's' means 'from string'. Now loaded is a Python dict again."},
{code:"print('\\nLoaded back:', loaded['top_performer'], 'was top performer')", exp:"✅ Prove we can read back from JSON — access like any dict key."}
]},
{ id:"8c", title:"SQLite Database", duration:"45 min", project:"🎯 Contacts Database",
memTrick:"💡 SQL = ask questions to your data. SELECT = fetch, INSERT = add, DELETE = remove. SQLite = SQL built into Python, no server needed.",
concepts:["sqlite3","CREATE TABLE","INSERT","SELECT","WHERE","cursor"],
lines:[
{code:"import sqlite3", exp:"📦 IMPORT — sqlite3 is built into Python. No pip install. It stores data in a single .db file (or in memory)."},
{code:"", exp:null},
{code:"# Create a database IN MEMORY (no file needed for demo)", exp:"💬 ':memory:' means the database lives in RAM. Perfect for demos and tests. Use 'contacts.db' for a real file."},
{code:"conn = sqlite3.connect(':memory:')", exp:"🔌 CONNECT — creates the database and opens a connection. conn is our link to it."},
{code:"cur = conn.cursor()", exp:"🖱️ CURSOR — our tool for running SQL commands. Think of it as a pointer that executes queries."},
{code:"", exp:null},
{code:"# Create the table", exp:"💬 CREATE TABLE defines the structure — like defining columns in a spreadsheet."},
{code:"cur.execute('''", exp:"▶️ execute() runs one SQL command. Triple quotes let us write multi-line SQL."},
{code:" CREATE TABLE contacts (", exp:"🏗️ TABLE NAME — contacts. Every table needs a name."},
{code:" id INTEGER PRIMARY KEY AUTOINCREMENT,", exp:"🔑 PRIMARY KEY — unique ID for each row. AUTOINCREMENT means Python assigns 1, 2, 3... automatically."},
{code:" name TEXT NOT NULL,", exp:"📝 TEXT column — stores strings. NOT NULL means this field is required."},
{code:" phone TEXT,", exp:"📝 TEXT column — phone number as text (not int) so we keep leading zeros and dashes."},
{code:" city TEXT", exp:"📝 TEXT column — city name."},
{code:" )", exp:"🔒 Close the CREATE TABLE command."},
{code:"''')", exp:"🔒 Close the triple-quote string passed to execute()."},
{code:"", exp:null},
{code:"# Insert contacts", exp:"💬 INSERT INTO adds rows to the table."},
{code:"contacts = [", exp:"📋 LIST of tuples — each tuple is one row of data to insert."},
{code:" ('Alice', '555-1234', 'New York'),", exp:"👤 Contact 1 — name, phone, city."},
{code:" ('Bob', '555-5678', 'London'),", exp:"👤 Contact 2 — note extra spaces for alignment (Python ignores them)."},
{code:" ('Carol', '555-9999', 'New York'),", exp:"👤 Contact 3 — also in New York."},
{code:"]", exp:"🔒 Close the list."},
{code:"", exp:null},
{code:"cur.executemany('INSERT INTO contacts (name,phone,city) VALUES (?,?,?)', contacts)", exp:"⚡ executemany = INSERT multiple rows at once. The ? are placeholders — SQLite fills them safely. Never use f-strings in SQL (security risk)."},
{code:"conn.commit()", exp:"💾 COMMIT — saves the changes permanently. Like Ctrl+S. Without this, inserts are lost."},
{code:"", exp:null},
{code:"# Query all contacts", exp:"💬 SELECT fetches rows. * means all columns."},
{code:"print('All contacts:')", exp:"🖨️ Label."},
{code:"for row in cur.execute('SELECT * FROM contacts'):", exp:"🔁 LOOP — execute returns rows one at a time. Each row is a tuple like (1, 'Alice', '555-1234', 'New York')."},
{code:" print(f' [{row[0]}] {row[1]} — {row[2]} — {row[3]}')", exp:"🖨️ row[0]=id, row[1]=name, row[2]=phone, row[3]=city."},
{code:"", exp:null},
{code:"# Query with WHERE filter", exp:"💬 WHERE filters rows — like a search."},
{code:"print('\\nNew York contacts:')", exp:"🖨️ Label."},
{code:"cur.execute('SELECT name, phone FROM contacts WHERE city = ?', ('New York',))", exp:"🔍 SELECT only name and phone WHERE city matches. The comma after 'New York' makes it a tuple — required by sqlite3."},
{code:"for row in cur.fetchall():", exp:"📂 fetchall() gets all matching rows as a list. Use fetchone() for just the first match."},
{code:" print(f' {row[0]}: {row[1]}')", exp:"🖨️ Print name and phone for each New York contact."},
{code:"", exp:null},
{code:"conn.close()", exp:"🔒 CLOSE — always close the connection when done. Frees up resources."}
]},
{ id:"8d", title:"Data Analysis with Pandas", duration:"50 min", project:"🎯 Student Grade Analyzer",
memTrick:"💡 DataFrame = a spreadsheet in Python. df['column'] = one column. df[df['col'] > 5] = filter rows. groupby = group + calculate.",
concepts:["pandas","DataFrame","read_csv","groupby","describe","filtering"],
lines:[
{code:"# pip install pandas", exp:"📦 This tells the bootcamp to install pandas automatically via micropip. It only runs once."},
{code:"import pandas as pd", exp:"📦 IMPORT — pandas is THE data tool. 'as pd' means we type pd instead of pandas everywhere. Install: pip install pandas"},
{code:"import io", exp:"📦 io — for creating a fake CSV in memory (demo only)."},
{code:"", exp:null},
{code:"# Sample student data", exp:"💬 In real code you'd use pd.read_csv('students.csv'). Here we fake it with a string."},
{code:"csv_data = '''name,math,science,english,grade", exp:"📋 HEADER ROW — column names for our fake CSV."},
{code:"Alice,92,88,95,A", exp:"👤 Student row — name and scores."},
{code:"Bob,74,80,68,B", exp:"👤 Student row."},
{code:"Carol,55,60,72,C", exp:"👤 Student row."},
{code:"Dave,88,91,85,A", exp:"👤 Student row."},
{code:"Eve,61,55,58,D", exp:"👤 Student row."},
{code:"Frank,95,97,93,A'''", exp:"👤 Last student row. Triple quote closes."},
{code:"", exp:null},
{code:"df = pd.read_csv(io.StringIO(csv_data))", exp:"📊 DataFrame — df is the standard variable name. Think of it as a smart spreadsheet. pd.read_csv reads CSV data."},
{code:"print('Shape:', df.shape)", exp:"📐 shape = (rows, columns). (6, 5) means 6 students, 5 columns. Always check shape first."},
{code:"print(df.to_string())", exp:"🖨️ Print the full table. to_string() shows all rows without truncation."},
{code:"", exp:null},
{code:"# Summary statistics", exp:"💬 describe() is magic — one line gives you count, mean, min, max for every number column."},
{code:"print('\\n--- Summary ---')", exp:"🖨️ Section label."},
{code:"print(df[['math','science','english']].describe().round(1))", exp:"📊 [['math','science','english']] selects only those columns. describe() calculates stats. round(1) = 1 decimal place."},
{code:"", exp:null},
{code:"# Add an average column", exp:"💬 You can create new columns from existing ones — like a formula in Excel."},
{code:"df['average'] = df[['math','science','english']].mean(axis=1).round(1)", exp:"➕ mean(axis=1) = average ACROSS columns (per student). axis=0 would average DOWN rows (per subject). round(1) = 1 decimal."},
{code:"print('\\nWith averages:')", exp:"🖨️ Label."},
{code:"print(df[['name','average','grade']].to_string())", exp:"🖨️ Show only name, average, grade columns — df[['col1','col2']] selects multiple columns."},
{code:"", exp:null},
{code:"# Filter: only A students", exp:"💬 FILTERING — like WHERE in SQL. df[condition] returns matching rows."},
{code:"a_students = df[df['grade'] == 'A']", exp:"🔍 df['grade'] == 'A' creates a True/False mask for each row. df[mask] keeps only True rows."},
{code:"print(f'A students: {a_students.name.tolist()}')", exp:"🖨️ .name gets the name column. .tolist() converts Series to a Python list."},
{code:"", exp:null},
{code:"# Group by grade", exp:"💬 groupby = split into groups, then calculate something for each group. Like a pivot table."},
{code:"grouped = df.groupby('grade')['average'].mean().round(1)", exp:"📊 Group by grade → get average column → calculate mean of each group. Chain of operations."},
{code:"print('\\nAverage score by grade:')", exp:"🖨️ Label."},
{code:"print(grouped.to_string())", exp:"🖨️ Shows each grade and its average score."}
]}
]},
{ id:9, phase:"Phase 9", title:"Testing & Debugging", icon:"🧪", color:"#8b5cf6", lessons:[
{ id:"9a", title:"Reading Error Messages", duration:"30 min", project:"🎯 Error Decoder",
memTrick:"💡 Read errors BOTTOM UP. The last line = what went wrong. The lines above = where it happened. Type Error = wrong type. Name Error = typo.",
concepts:["SyntaxError","TypeError","NameError","IndexError","traceback","try/except"],
lines:[
{code:"# 🎯 PROJECT: Learn to read and handle Python errors", exp:"💬 Errors are normal! Every programmer sees them daily. Learning to read them is a superpower."},
{code:"", exp:null},
{code:"# ── TypeError ──", exp:"💬 TypeError = you used the wrong type. Like adding a number to a word."},
{code:"try:", exp:"🛡️ try block — put risky code here. If it crashes, Python jumps to except instead of stopping."},
{code:" result = 'Score: ' + 95", exp:"💥 This crashes! Can't add string + int. Fix: str(95) or f'Score: {95}'"},
{code:"except TypeError as e:", exp:"🎯 Catches TypeError specifically. 'as e' stores the error message in variable e."},
{code:" print(f'TypeError caught: {e}')", exp:"🖨️ Prints: TypeError caught: can only concatenate str (not \"int\") to str"},
{code:"", exp:null},
{code:"# ── NameError ──", exp:"💬 NameError = you used a variable that doesn't exist. Usually a typo."},
{code:"try:", exp:"🛡️ Another try block."},
{code:" print(usernmae)", exp:"💥 Typo! 'usernmae' doesn't exist. Should be 'username'."},
{code:"except NameError as e:", exp:"🎯 Catches the NameError."},
{code:" print(f'NameError caught: {e}')", exp:"🖨️ Prints: name 'usernmae' is not defined — Python tells you exactly which name is wrong."},
{code:"", exp:null},
{code:"# ── IndexError ──", exp:"💬 IndexError = you tried to access a list position that doesn't exist."},
{code:"fruits = ['apple', 'banana', 'cherry']", exp:"📋 List with 3 items. Valid indexes are 0, 1, 2."},
{code:"try:", exp:"🛡️ Try block."},
{code:" print(fruits[10])", exp:"💥 Index 10 doesn't exist! List only has indexes 0-2."},
{code:"except IndexError as e:", exp:"🎯 Catches IndexError."},
{code:" print(f'IndexError caught: {e}')", exp:"🖨️ Prints: list index out of range"},
{code:"", exp:null},
{code:"# ── Catch any error ──", exp:"💬 Use bare 'except Exception' to catch everything. Good for logging unknown errors."},
{code:"def safe_divide(a, b):", exp:"🔧 FUNCTION — wraps division with error handling."},
{code:" try:", exp:"🛡️ Try the division."},
{code:" return a / b", exp:"➗ Normal division."},
{code:" except ZeroDivisionError:", exp:"🎯 ZeroDivisionError = dividing by zero."},
{code:" return 'Error: cannot divide by zero'", exp:"↩️ Return a friendly message instead of crashing."},
{code:" except TypeError:", exp:"🎯 TypeError if a or b aren't numbers."},
{code:" return 'Error: inputs must be numbers'", exp:"↩️ Friendly message for wrong types."},
{code:"", exp:null},
{code:"print(safe_divide(10, 2))", exp:"✅ Returns 5.0 — no error."},
{code:"print(safe_divide(10, 0))", exp:"✅ Returns 'Error: cannot divide by zero' — no crash."},
{code:"print(safe_divide(10, 'x'))", exp:"✅ Returns 'Error: inputs must be numbers' — no crash."}
]},
{ id:"9b", title:"Writing Tests", duration:"40 min", project:"🎯 Test Your Own Functions",
memTrick:"💡 A test = call your function + check the result. assert means 'I promise this is true — crash if it isn't'. Tests = your safety net.",
concepts:["assert","unittest","test functions","assertEqual","setUp","edge cases"],
lines:[
{code:"# 🎯 PROJECT: Write tests for a simple calculator", exp:"💬 Tests check that your code does what you expect. Write them BEFORE you think your code is done."},
{code:"import unittest", exp:"📦 unittest — built into Python. No pip needed. The standard testing framework."},
{code:"", exp:null},
{code:"# ── The code we want to test ──", exp:"💬 First, let's write a simple calculator with a bug we'll catch with tests."},
{code:"def add(a, b): return a + b", exp:"➕ Simple add function."},
{code:"def subtract(a, b): return a - b", exp:"➖ Simple subtract."},
{code:"def multiply(a, b): return a * b", exp:"✖️ Simple multiply."},
{code:"def divide(a, b):", exp:"➗ Divide — needs error handling."},
{code:" if b == 0: raise ValueError('Cannot divide by zero')", exp:"🛡️ raise creates an error on purpose. ValueError = the value is wrong/invalid."},
{code:" return a / b", exp:"➗ Normal division if b is not zero."},
{code:"", exp:null},
{code:"# ── Quick assertions (simplest tests) ──", exp:"💬 assert = 'I promise this is True'. If it's False, Python crashes with AssertionError."},
{code:"assert add(2, 3) == 5, 'add failed'", exp:"✅ 2+3=5. If this ever returns something else, we'll know immediately."},
{code:"assert subtract(10, 4) == 6, 'subtract failed'", exp:"✅ 10-4=6."},
{code:"assert multiply(3, 4) == 12, 'multiply failed'", exp:"✅ 3×4=12."},
{code:"assert add(0, 0) == 0, 'zero case failed'", exp:"✅ Edge case — zero inputs. Always test edge cases!"},
{code:"assert add(-1, 1) == 0, 'negative case failed'", exp:"✅ Edge case — negative numbers."},
{code:"print('All basic assertions passed!')", exp:"🎉 Only prints if ALL assertions above passed. One failure = crash before this line."},
{code:"", exp:null},
{code:"# ── Proper unittest class ──", exp:"💬 unittest.TestCase gives you better error messages and lets you run many tests at once."},
{code:"class TestCalculator(unittest.TestCase):", exp:"🏗️ Class inheriting from TestCase. All test methods inside will be auto-discovered."},
{code:" def test_add_positive(self):", exp:"🧪 Method starting with 'test_' is auto-run. Tests positive numbers."},
{code:" self.assertEqual(add(2, 3), 5)", exp:"✅ assertEqual = assert a == b, but with a better error message if it fails."},
{code:" def test_add_negative(self):", exp:"🧪 Test negative numbers."},
{code:" self.assertEqual(add(-1, -1), -2)", exp:"✅ -1 + -1 = -2."},
{code:" def test_divide_by_zero(self):", exp:"🧪 Test that divide raises an error when b=0."},
{code:" with self.assertRaises(ValueError):", exp:"✅ assertRaises = 'I expect this to raise ValueError'. Test PASSES if the error IS raised."},
{code:" divide(10, 0)", exp:"💥 This should raise ValueError — and our test expects that, so it passes."},
{code:"", exp:null},
{code:"suite = unittest.TestLoader().loadTestsFromTestCase(TestCalculator)", exp:"🔍 Collect all test_ methods from our class."},
{code:"runner = unittest.TextTestRunner(verbosity=2)", exp:"🏃 verbosity=2 prints each test name and result."},
{code:"runner.run(suite)", exp:"▶️ Run all tests and print results."}
]},
{ id:"9c", title:"Debugging Tips", duration:"35 min", project:"🎯 Find & Fix 3 Bugs",
memTrick:"💡 Debugging order: 1) print() the value before the crash. 2) Check the TYPE not just the value. 3) Read the error message BOTTOM UP.",
concepts:["print debugging","type()","breakpoint()","common bugs","defensive coding"],
lines:[
{code:"# 🎯 PROJECT: Debug 3 deliberately broken functions", exp:"💬 The fastest way to learn debugging is to fix real bugs. Let's break things on purpose and fix them."},
{code:"", exp:null},
{code:"# ── Bug 1: Wrong type ──", exp:"💬 Most beginner bugs are type errors — mixing strings and numbers."},
{code:"def calculate_discount(price, discount_pct):", exp:"🔧 FUNCTION — should return price after discount."},
{code:" # BUG: discount_pct might come in as a string '20'", exp:"💬 COMMENT — hints about the bug. In real code, inputs from users are ALWAYS strings."},
{code:" discount_pct = float(discount_pct) # FIX: convert to float first", exp:"🔧 FIX — always convert user inputs. float('20') = 20.0. Now math works."},
{code:" discount = price * (discount_pct / 100)", exp:"🔢 Calculate the discount amount. 20% of price."},
{code:" return round(price - discount, 2)", exp:"↩️ Subtract discount, round to 2 decimal places (money)."},
{code:"", exp:null},
{code:"print(calculate_discount(100, '20'))", exp:"✅ Returns 80.0. Without the float() fix, this would crash with TypeError."},
{code:"print(calculate_discount(49.99, 15))", exp:"✅ Returns 42.49. Works with both string and number input now."},
{code:"", exp:null},
{code:"# ── Bug 2: Off-by-one error ──", exp:"💬 Off-by-one = loop runs one too many or too few times. Very common in list operations."},
{code:"def get_last_three(items):", exp:"🔧 FUNCTION — should return the last 3 items from a list."},
{code:" # BUG: items[-4:-1] misses the last item!", exp:"💬 Common mistake: -1 stops BEFORE the last element."},
{code:" return items[-3:] # FIX: -3: means 'from 3rd-last to END'", exp:"🔧 FIX — items[-3:] with no end = goes all the way to the end. Includes the last item."},
{code:"", exp:null},
{code:"scores = [88, 72, 95, 61, 84, 90, 77]", exp:"📋 7 scores. Last 3 should be [84, 90, 77]."},
{code:"print('Last 3:', get_last_three(scores))", exp:"✅ Prints [84, 90, 77]. The fix works."},
{code:"", exp:null},
{code:"# ── Bug 3: Mutating a list while looping ──", exp:"💬 Never modify a list while you're iterating over it — unpredictable behavior."},
{code:"def remove_negatives(numbers):", exp:"🔧 FUNCTION — should remove all negative numbers."},
{code:" # BUG: removing items while looping skips items", exp:"💬 When you remove item at index 2, item at index 3 shifts to index 2 and gets skipped."},
{code:" return [n for n in numbers if n >= 0] # FIX: list comprehension", exp:"🔧 FIX — build a NEW list with only non-negatives. Don't modify the original while looping."},
{code:"", exp:null},
{code:"data = [3, -1, 7, -5, 2, -9, 8]", exp:"📋 Mixed positive and negative numbers."},
{code:"print('Cleaned:', remove_negatives(data))", exp:"✅ Prints [3, 7, 2, 8] — all negatives removed correctly."},
{code:"", exp:null},
{code:"# ── Debugging tip: use print() + type() ──", exp:"💬 The fastest debugger is print(). Add type() to see what Python thinks something is."},
{code:"mystery = '42'", exp:"📦 Looks like a number but it's a string!"},
{code:"print(f'Value: {mystery}, Type: {type(mystery).__name__}')", exp:"🔍 Prints: Value: 42, Type: str — now you can see the problem."},
{code:"print(f'After int(): {int(mystery)}, Type: {type(int(mystery)).__name__}')", exp:"🔧 int() converts string to integer. Type is now int."}
]}
]},
{ id:10, phase:"Phase 10", title:"Real Projects", icon:"🔄", color:"#ec4899", lessons:[
{ id:"10a", title:"CLI Todo App", duration:"45 min", project:"🎯 Command-Line Todo List",
memTrick:"💡 CLI app = a loop that reads commands. while True + input() = your app running forever. 'break' = quit. Dict or list = your database.",
concepts:["while loop","input()","list operations","functions","CLI design"],
lines:[
{code:"# 🎯 PROJECT: Full command-line Todo app", exp:"💬 This is a COMPLETE app — not just snippets. It runs in a loop and responds to commands. Real software!"},
{code:"", exp:null},
{code:"todos = [] # Our in-memory database", exp:"📋 LIST — stores all todos. In a real app you'd save this to a file with json.dump()."},
{code:"", exp:null},
{code:"def show_todos():", exp:"🔧 FUNCTION — display all todos with numbers. Functions keep code organized."},
{code:" if not todos:", exp:"❓ if not todos = if the list is empty. Empty list is 'falsy' in Python."},
{code:" print(' No todos yet!')", exp:"🖨️ Friendly empty state message."},
{code:" return", exp:"↩️ return early — no point continuing if list is empty."},
{code:" for i, todo in enumerate(todos, 1):", exp:"🔁 enumerate(list, 1) gives (1, item), (2, item)... The 1 means start counting from 1."},
{code:" status = '✅' if todo['done'] else '⬜'", exp:"✅ TERNARY — one-line if/else. If done=True show ✅, else show ⬜."},
{code:" task = todo['task']", exp:"📦 Get the task text from the dict using key 'task'."},
{code:" print(f' {i}. {status} {task}')", exp:"🖨️ Format: '1. ✅ Buy groceries'."},
{code:"", exp:null},
{code:"def add_todo(task):", exp:"🔧 FUNCTION — add a new todo."},
{code:" todos.append({'task': task, 'done': False})", exp:"➕ append adds to end of list. Each todo is a dict with 'task' and 'done' keys."},
{code:" print(f' Added: {task}')", exp:"✅ Confirm the addition."},
{code:"", exp:null},
{code:"def complete_todo(num):", exp:"🔧 FUNCTION — mark a todo as done by its number."},
{code:" if 1 <= num <= len(todos):", exp:"🛡️ VALIDATION — check number is in valid range. len(todos) is the max valid index."},
{code:" todos[num-1]['done'] = True", exp:"✅ num-1 converts from 1-based display to 0-based index. Set done to True."},
{code:" task = todos[num-1]['task']", exp:"📦 Get the task text from the selected todo."},
{code:" print(f' Completed: {task}')", exp:"🖨️ Confirm which task was completed."},
{code:" else:", exp:"❌ Number was out of range."},
{code:" print(' Invalid number')", exp:"🖨️ Friendly error message."},
{code:"", exp:null},
{code:"# ── Demo the app ──", exp:"💬 Pyodide can't use input() interactively, so let's simulate commands."},
{code:"print('=== TODO APP DEMO ===')", exp:"🖨️ Header."},
{code:"add_todo('Learn Python')", exp:"➕ Add first todo."},
{code:"add_todo('Build a project')", exp:"➕ Add second todo."},
{code:"add_todo('Push to GitHub')", exp:"➕ Add third todo."},
{code:"print('\\nYour todos:')", exp:"🖨️ Label."},
{code:"show_todos()", exp:"📋 Display all 3 todos."},
{code:"complete_todo(1)", exp:"✅ Mark first todo as done."},
{code:"print('\\nAfter completing #1:')", exp:"🖨️ Label."},
{code:"show_todos()", exp:"📋 Show updated list — #1 now has ✅."},
{code:"", exp:null},