[
  {
    "id": "notation",
    "file": "/notes/notation.md",
    "title": "Mathematical Notation for Data Science",
    "blurb": "Read the notation that appears most often in statistics and machine learning, and translate each expression into plain language.",
    "domain": "Data Science",
    "section": "Reference",
    "minutes": 35,
    "standfirst": "Identify the object's type first — scalar, vector, matrix, set, event, random variable, parameter, estimate — then interpret the symbol.",
    "notion": "https://app.notion.com/p/3b0072057ae38126aee3d6b3ecd45507",
    "primarySubject": "subject-mathematical-foundations",
    "catalogType": "reference"
  },
  {
    "id": "functions",
    "file": "/notes/functions.md",
    "title": "Functions",
    "blurb": "Domains, ranges, transformations, composition, invertibility, and how all of it underpins linear and logistic regression.",
    "domain": "Data Science",
    "section": "Foundations",
    "minutes": 16,
    "standfirst": "A function is a rule together with its allowed inputs and declared outputs; those sets control composition, surjectivity, and invertibility.",
    "notion": "https://app.notion.com/p/3b0072057ae3816e93b8ebd74412b47c",
    "primarySubject": "subject-mathematical-foundations",
    "catalogType": "lesson"
  },
  {
    "id": "equations",
    "file": "/notes/equations.md",
    "title": "Equations, Inequalities, Summations & Products",
    "blurb": "Solve and debug equations and inequalities, reindex sums and products, and see the four structures inside ML objectives.",
    "domain": "Data Science",
    "section": "Foundations",
    "minutes": 15,
    "standfirst": "Equations locate matches, inequalities locate regions, sums combine additive evidence, and products combine multiplicative evidence.",
    "notion": "https://app.notion.com/p/3b0072057ae3810794b1de3b8db01337",
    "primarySubject": "subject-mathematical-foundations",
    "catalogType": "lesson"
  },
  {
    "id": "combinatorics",
    "file": "/notes/combinatorics.md",
    "title": "Factorials, Permutations & Combinations",
    "blurb": "Choose the right counting rule from order and replacement assumptions, and size the search spaces ML actually explores.",
    "domain": "Data Science",
    "section": "Foundations",
    "minutes": 13,
    "standfirst": "Define what counts as a distinct outcome first; order, replacement, stages, and distinguishability then determine the formula.",
    "notion": "https://app.notion.com/p/3b0072057ae38176995add2c6f3cc901",
    "primarySubject": "subject-mathematical-foundations",
    "catalogType": "lesson"
  },
  {
    "id": "calculus",
    "file": "/notes/calculus.md",
    "title": "Single-Variable Calculus",
    "blurb": "Limits, the difference quotient, differentiation rules, critical points, Taylor approximation, and the integral–expectation bridge.",
    "domain": "Data Science",
    "section": "Calculus & optimization",
    "minutes": 18,
    "standfirst": "Every tool here is one limit in disguise — the derivative is the limit of secant slopes, Taylor polynomials repackage derivatives into local models, and the definite integral is the limit of weighted sums.",
    "notion": "https://app.notion.com/p/3c0072057ae38117a389c27d4c195e82",
    "primarySubject": "subject-calculus",
    "catalogType": "lesson"
  },
  {
    "id": "multivariable",
    "file": "/notes/multivariable.md",
    "title": "Multivariable Calculus & Optimization",
    "blurb": "Contour plots, partials, gradients, the Hessian, convexity, gradient descent with its learning-rate limit, and Lagrange multipliers.",
    "domain": "Data Science",
    "section": "Calculus & optimization",
    "minutes": 18,
    "standfirst": "The gradient collects every partial slope into the steepest-ascent direction and meets contours at right angles; the Hessian records curvature; each optimization idea here is a statement about those two objects.",
    "notion": "https://app.notion.com/p/3c0072057ae381d3a45ffc4affa25cb3",
    "primarySubject": "subject-calculus",
    "catalogType": "lesson"
  },
  {
    "id": "vectors",
    "file": "/notes/vectors.md",
    "title": "Vectors, Norms, Dot Product & Cosine Similarity",
    "blurb": "Feature vectors and their algebra: the norms and distances behind k-NN, the dot product's angle identity, projections, and when cosine beats Euclidean.",
    "domain": "Data Science",
    "section": "Linear algebra",
    "minutes": 15,
    "standfirst": "One engine powers the whole page — the dot product yields lengths, angles, projections, and cosine similarity.",
    "notion": "https://app.notion.com/p/3c0072057ae38103bb7ec88a3fda5ad9",
    "primarySubject": "subject-linear-algebra",
    "catalogType": "lesson"
  },
  {
    "id": "matrices",
    "file": "/notes/matrices.md",
    "title": "Matrices, Span, Rank & Linear Systems",
    "blurb": "Four readings of the matrix product, span and independence, rank–nullity, the three outcomes of a linear system, and why collinear features wreck coefficients but not predictions.",
    "domain": "Data Science",
    "section": "Linear algebra",
    "minutes": 20,
    "standfirst": "Read a matrix as a verb: its columns are where the basis vectors land, rank counts the dimensions it keeps, and the null space counts the ones it destroys — those two numbers settle whether Ax = b has one solution, none, or infinitely many before you compute anything.",
    "notion": "https://app.notion.com/p/3c1072057ae38171bef7fa5a66206162",
    "primarySubject": "subject-linear-algebra",
    "catalogType": "lesson"
  },
  {
    "id": "orthogonality",
    "file": "/notes/orthogonality.md",
    "title": "Orthogonality, Least Squares & Decompositions",
    "blurb": "One requirement — that the residual be orthogonal to the column space — gives the normal equations, the projection matrix, the pseudoinverse, and by way of the singular value decomposition, PCA twice over.",
    "domain": "Data Science",
    "section": "Linear algebra",
    "minutes": 40,
    "standfirst": "Least squares is a projection wearing statistics: the residual must be orthogonal to everything the model can reach, and that single requirement produces the normal equations with no calculus anywhere. Carry it forward and it hands you the projection matrix, the pseudoinverse for dependent columns, the spectral decomposition of a symmetric matrix, the singular value decomposition of any matrix at all, and PCA derived twice.",
    "notion": "https://app.notion.com/p/3c1072057ae381b49688e72dea157e63",
    "primarySubject": "subject-linear-algebra",
    "catalogType": "lesson"
  },
  {
    "id": "probability",
    "file": "/notes/probability.md",
    "title": "Probability Foundations",
    "blurb": "The probability strand opens: events and conditioning, Bayes with numbers that surprise, distributions in one and two variables, and expectation through to the conditional mean.",
    "domain": "Data Science",
    "section": "Probability",
    "minutes": 40,
    "standfirst": "Events, conditioning and independence, then Bayes on a medical test where a 99 percent sensitive result still leaves you only 7 percent likely to be ill. Densities that exceed one, joint tables whose margins agree while their interiors do not, and the conditional mean treated as a random variable in its own right.",
    "notion": "https://app.notion.com/p/3c1072057ae381af863bda210b6283f7",
    "primarySubject": "subject-statistics",
    "catalogType": "lesson"
  },
  {
    "id": "distributions",
    "file": "/notes/distributions.md",
    "title": "Essential Probability Distributions",
    "blurb": "A Level 5 tour of the essential probability distributions, each derived from its generative story and threaded together into a single family map.",
    "domain": "Data Science",
    "section": "Probability",
    "minutes": 48,
    "standfirst": "Eleven named distributions turn out to be one small family: a binomial is added Bernoulli trials, a Poisson is that binomial pushed to a rare-event limit, an exponential is the same stream asked how long rather than how many, and a normal is what averaging produces whenever the variance is finite. Every formula here is derived from the experiment behind it rather than quoted, every number was checked twice before it was written down, and each module closes with the boundary where the model stops being true and the symptom that shows up in data.",
    "notion": "https://app.notion.com/p/3c2072057ae38184be34f1c418ab69fb",
    "primarySubject": "subject-statistics",
    "catalogType": "lesson"
  },
  {
    "id": "inference",
    "file": "/notes/inference.md",
    "title": "Sampling & Statistical Inference",
    "blurb": "Sampling distributions and standard errors, the law of large numbers and the central limit theorem with their failure cases, bias against variance, confidence intervals and their real coverage, and maximum likelihood through to ridge.",
    "domain": "Data Science",
    "section": "Statistics & inference",
    "minutes": 52,
    "standfirst": "Almost every claim in statistics is secretly a claim about one object: the distribution a statistic takes across repeated samples, which is neither the population nor the histogram of the data in front of you. This lesson builds that object from the sampling scheme upward, through the standard error, the two limit theorems and the populations where they fail, bias traded against variance, confidence intervals and the coverage they actually deliver, and maximum likelihood through to the log-prior that turns it into ridge regression.",
    "notion": "https://app.notion.com/p/3c2072057ae3817597e3f93281f188d4",
    "primarySubject": "subject-statistics",
    "catalogType": "lesson"
  },
  {
    "id": "testing",
    "file": "/notes/testing.md",
    "title": "Testing & Experimentation",
    "blurb": "Seven modules on hypothesis testing, running from null distributions and p-values through power and effect size, the named tests and their conditions, multiplicity control, and permutation and bootstrap methods.",
    "domain": "Data Science",
    "section": "Statistics & inference",
    "minutes": 56,
    "standfirst": "A hypothesis test is a sampling distribution used backwards: the null fixes a distribution for the statistic, the critical value is one of its quantiles, and the significance level is therefore built in by construction rather than hoped for. This note derives exact power and sample size, audits what a violated condition does to the error rate, separates family-wise error from false discovery rate, and builds null distributions by relabelling and resampling when no formula can be trusted.",
    "notion": "https://app.notion.com/p/3c2072057ae381169d4ec1c4ea088ae6",
    "primarySubject": "subject-statistics",
    "catalogType": "lesson"
  },
  {
    "id": "mlbridge",
    "file": "/notes/mlbridge.md",
    "title": "The Bridge Into Machine Learning",
    "blurb": "How a loss, a hypothesis class and a penalty assemble every classical method — and how to measure whether the result means anything.",
    "domain": "Data Science",
    "section": "ML bridge",
    "minutes": 56,
    "standfirst": "Every classical method is three choices — a loss, a hypothesis class, a penalty — and this note derives least squares and logistic regression as maximum likelihood under two different noise models. It then splits prediction error into irreducible noise, squared bias and variance, explains where lasso zeros come from geometrically, and builds a measurement protocol that survives leakage and class imbalance.",
    "notion": "https://app.notion.com/p/3c2072057ae38159b1d9ff149c09b2aa",
    "primarySubject": "subject-mathematical-foundations",
    "catalogType": "lesson"
  },
  {
    "id": "go-001",
    "file": "/notes/go-001.md",
    "title": "GO-001 — Go Versions and the Language Contract",
    "blurb": "Separate the selected toolchain, module language floor, maintainer preference, and selection policy—then prove the contract across Go 1.26 and Go 1.27.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 1,
    "minutes": 80,
    "standfirst": "“This project uses Go 1.27” is incomplete: name the toolchain, the module’s go directive, the optional toolchain suggestion, and the GOTOOLCHAIN policy.",
    "notion": "https://app.notion.com/p/3c2072057ae381b0bdb4d8121e6f0e17",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-002",
    "file": "/notes/go-002.md",
    "title": "GO-002 — Source text, tokens, and literals",
    "blurb": "Read Go source the way the lexer and parser do: UTF-8 code points, longest-match tokens, semicolon insertion, comments, identifiers, and every literal family.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 2,
    "minutes": 35,
    "standfirst": "Go processes UTF-8 code points into the longest valid lexical tokens, then inserts semicolons before grammar is applied.",
    "notion": "https://app.notion.com/p/3c3072057ae3815697d3de40ad0b861f",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-003",
    "file": "/notes/go-003.md",
    "title": "GO-003 — Packages, files, imports, declarations, and scope",
    "blurb": "Trace how files form packages, imports bind names, declarations span files, scopes resolve identifiers, and import cycles or main-package mistakes fail.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 3,
    "minutes": 45,
    "standfirst": "Files declare package membership; imports bind names only in the importing file; package declarations span the package; identifiers resolve through nested lexical scope; only exported package-block names cross package boundaries.",
    "notion": "https://app.notion.com/p/3c3072057ae3810b92f2d9511b6c2ff8",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-004",
    "file": "/notes/go-004.md",
    "title": "GO-004 — Variables, constants, nil, and type-system foundations",
    "blurb": "Separate typed storage from exact constants, zero values from nil, and type identity from assignability, representability, and explicit conversion.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 4,
    "minutes": 45,
    "standfirst": "A variable is typed storage that always holds a value; constants are exact compile-time values whose type may be deferred; nil has no default type; identity, underlying type, assignability, representability, and conversion are different relations.",
    "notion": "https://app.notion.com/p/3c3072057ae381d8b7b6daa735fafbbb",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-005",
    "file": "/notes/go-005.md",
    "title": "GO-005 — Expressions, assignment, and control flow",
    "blurb": "Reason precisely about precedence, specified and unspecified evaluation order, simultaneous assignment, lexical blocks, loops, switches, labels, and goto.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 5,
    "minutes": 45,
    "standfirst": "Evaluate operands under Go’s partly specified order, commit assignment writes under assignment rules, then transfer control through lexical blocks; never invent evaluation order from source appearance.",
    "notion": "https://app.notion.com/p/3c3072057ae381ffbc48f9faffe95bca",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-006",
    "file": "/notes/go-006.md",
    "title": "GO-006 — Functions, closures, and defer",
    "blurb": "Build and trace functions, variadics, recursion, higher-order values, shared closure capture, and defer’s registration-time versus return-time behavior.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 6,
    "minutes": 40,
    "standfirst": "A call evaluates arguments into parameter and result storage; closures share captured variables; each executed defer saves its function value and arguments immediately, then deferred calls run LIFO on return.",
    "notion": "https://app.notion.com/p/3c3072057ae381538ac7fdb6bc07c390",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-007",
    "file": "/notes/go-007.md",
    "title": "GO-007 — Pointers, arrays, slices, and shared storage",
    "blurb": "Draw storage and aliasing explicitly while working with pointers, value-copy arrays, slice descriptors, capacity, append, copy, clear, and retention.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 7,
    "minutes": 60,
    "standfirst": "Arrays are self-contained values, pointers designate variables, and slices are descriptors over arrays; mutations alias while storage is shared, but append may reuse or replace the backing array.",
    "notion": "https://app.notion.com/p/3c3072057ae38193bd8ced2e93492c4b",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-008",
    "file": "/notes/go-008.md",
    "title": "GO-008 — Strings, Unicode, and maps",
    "blurb": "Choose byte, rune, or text semantics deliberately, then make map presence, comparability, nil behavior, and observable ordering explicit.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 1 · Language foundations",
    "phaseOrder": 1,
    "order": 8,
    "minutes": 45,
    "standfirst": "A string is immutable bytes and may contain invalid UTF-8; a map is an unordered association with comparable keys, missing reads return the element zero value, presence requires comma-ok, and writes to a nil map panic.",
    "notion": "https://app.notion.com/p/3c3072057ae381c4a99dd45b3f01c376",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-009",
    "file": "/notes/go-009.md",
    "title": "GO-009 — Structs, composite literals, and embedding",
    "blurb": "Model struct storage, identity, comparison, tags, keyed literals, selector promotion, ambiguity, and Go 1.27 promoted-field keys without inheritance myths.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 9,
    "minutes": 45,
    "standfirst": "A struct is fixed field storage; embedding changes selector resolution and promotion, not storage or inheritance; Go 1.27 promoted keys obey uniqueness, pointer-path, and overlap constraints.",
    "notion": "https://app.notion.com/p/3c3072057ae38166ac6eec077697cdab",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-010",
    "file": "/notes/go-010.md",
    "title": "GO-010 — Methods, interfaces, dynamic values, and typed nil",
    "blurb": "Derive method sets, distinguish call adjustment from interface satisfaction, and reason safely about dynamic interface values, assertions, switches, comparison, and typed nil.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 10,
    "minutes": 60,
    "standfirst": "Interface satisfaction follows static method sets; runtime interfaces carry a dynamic type/value pair and equal nil only when both are absent; call adjustment does not enlarge a value type’s method set.",
    "notion": "https://app.notion.com/p/3c3072057ae38169a6e7f1500d2b45f8",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-011",
    "file": "/notes/go-011.md",
    "title": "GO-011 — Errors, panics, recovery, and API boundaries",
    "blurb": "Design error identity and trees deliberately, trace panic unwinding, and recover only at an owned same-goroutine boundary that preserves foreign failure policy.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 11,
    "minutes": 45,
    "standfirst": "Expected failures cross APIs as errors with deliberately exposed or hidden machine-readable identity; panic unwinds one goroutine while running defers, and only a direct deferred recover in that goroutine can stop it.",
    "notion": "https://app.notion.com/p/3c3072057ae3810a80fcdcad342de534",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-012",
    "file": "/notes/go-012.md",
    "title": "GO-012 — Generics from constraints to inference and methods",
    "blurb": "Build generic functions, types, and Go 1.27 generic methods from type-set constraints, permitted operations, instantiation, and inference equations.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 12,
    "minutes": 75,
    "standfirst": "A generic declaration is checked using only operations guaranteed by each type parameter’s constraint; instantiation substitutes type arguments, inference solves available type equations, and Go 1.27 generic methods introduce method-scoped type parameters without making generic interface methods legal.",
    "notion": "https://app.notion.com/p/3c3072057ae381c6af8bdd1b664cdd01",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-013",
    "file": "/notes/go-013.md",
    "title": "GO-013 — Reflection, unsafe, layout, and pointer boundaries",
    "blurb": "Inspect runtime types and values through reflection’s validity gates, then cross unsafe pointer and layout boundaries only with explicit provenance, lifetime, and portability proofs.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 13,
    "minutes": 60,
    "standfirst": "Reflection operations are permitted by a Value’s validity, dynamic type, kind, addressability, and settability; unsafe operations are valid only for documented conversion patterns whose alignment, bounds, lifetime, and pointer-liveness obligations remain with the programmer.",
    "notion": "https://app.notion.com/p/3c3072057ae381a593bee0194d8ed3f4",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-014",
    "file": "/notes/go-014.md",
    "title": "GO-014 — Modules, MVS, semantic import versioning, and workspaces",
    "blurb": "Compute module build lists with MVS, use semantic import versioning correctly, and distinguish local replace or workspace overlays from a released dependency graph.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 14,
    "minutes": 45,
    "standfirst": "The build list selects one version per module path by minimum version selection over requirements; major-version import paths define compatibility identity, while replacements and workspaces alter the current build context without becoming a consumer’s released dependency graph.",
    "notion": "https://app.notion.com/p/3c3072057ae3811da8d2dfbf0148c33f",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-015",
    "file": "/notes/go-015.md",
    "title": "GO-015 — Build constraints, portability, cgo, and source-to-binary",
    "blurb": "Trace file selection, targets, tags, cgo, compiler, cache, and linker inputs from portable source to a verified artifact.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 15,
    "minutes": 75,
    "standfirst": "A build includes only files selected by filename and build-constraint rules for the active target and tags; the go command then orchestrates compilation, assembly, cgo, and linking, so artifact portability is a property of the complete selected input and build mode rather than source syntax alone.",
    "notion": "https://app.notion.com/p/3c3072057ae3813c8340c31e8e6823d4",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "go-016",
    "file": "/notes/go-016.md",
    "title": "GO-016 — Initialization, Go tooling, package APIs, generation, and ASTs",
    "blurb": "Trace package startup, choose Go tooling deliberately, design durable APIs, and make generation, embedding, AST, token, and type-information contracts reproducible.",
    "domain": "Programming",
    "section": "Go",
    "phase": "Phase 2 · Types, abstraction, and the toolchain",
    "phaseOrder": 2,
    "order": 16,
    "minutes": 60,
    "standfirst": "Imported packages initialize before their importers, package variables follow their dependency graph before init functions run, and reproducible package and tooling behavior requires explicit API, generation, embedding, syntax-tree, and input contracts rather than reliance on incidental file or tool order.",
    "notion": "https://app.notion.com/p/3c3072057ae381bfab12e2ac0fa1f781",
    "primarySubject": "subject-go",
    "catalogType": "lesson"
  },
  {
    "id": "python-001",
    "file": "/notes/python-001.md",
    "title": "PY-001 — The data question behind the code",
    "blurb": "Northstar Market has seven daily sales totals.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 0 · Start with a data question",
    "phaseOrder": 0,
    "order": 1,
    "minutes": 35,
    "standfirst": "Northstar Market has seven daily sales totals. The manager asks: Was Saturday the strongest sales day? You could scan the numbers once, but tomorrow the values will change. Python lets us write the question as a repeatable recipe, run it, and inspect evidence.",
    "notion": "https://app.notion.com/p/3d1072057ae38199b1a0cf987209defa?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-002",
    "file": "/notes/python-002.md",
    "title": "PY-002 — How Python reads and runs a cell",
    "blurb": "A City Sensor analyst receives a Fahrenheit reading and wants a Celsius label.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 0 · Start with a data question",
    "phaseOrder": 0,
    "order": 2,
    "minutes": 30,
    "standfirst": "A City Sensor analyst receives a Fahrenheit reading and wants a Celsius label. The result is easy to run, but trustworthy work requires another skill: reading the cell in the same order Python does.",
    "notion": "https://app.notion.com/p/3d1072057ae3814a93dee50aaeabac44?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-003",
    "file": "/notes/python-003.md",
    "title": "PY-003 — Notebook state: run order, reset, and trust",
    "blurb": "An Experiment Bench notebook evaluates three scores against a decision threshold.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 0 · Start with a data question",
    "phaseOrder": 0,
    "order": 3,
    "minutes": 35,
    "standfirst": "An Experiment Bench notebook evaluates three scores against a decision threshold. Two analysts look at the same visible cells and report different accuracy values. Neither cell contains random numbers. How can both outputs have appeared?",
    "notion": "https://app.notion.com/p/3d1072057ae38171a14bf255c8d9029b?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-004",
    "file": "/notes/python-004.md",
    "title": "PY-004 — Names, values, and objects",
    "blurb": "Northstar Market tracks today's sales and campaign tags.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 1,
    "minutes": 35,
    "standfirst": "Northstar Market tracks today's sales and campaign tags. We want names that make the analysis readable—and a mental model that predicts what happens when two names refer to the same list.",
    "notion": "https://app.notion.com/p/3d1072057ae381179d00e61c5278a738?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-005",
    "file": "/notes/python-005.md",
    "title": "PY-005 — Numbers that describe the world",
    "blurb": "Northstar Market sold 18 bags of coffee at $12.50 each and issued $27.50 in refunds.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 2,
    "minutes": 35,
    "standfirst": "Northstar Market sold 18 bags of coffee at $12.50 each and issued $27.50 in refunds. What was net revenue, and what percentage of gross sales was refunded?",
    "notion": "https://app.notion.com/p/3d1072057ae381c7a086d5ac25c9944e?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-006",
    "file": "/notes/python-006.md",
    "title": "PY-006 — Text as data",
    "blurb": "Movie Night's catalog receives the product label \" Café Beans - LARGE \".",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 3,
    "minutes": 40,
    "standfirst": "Movie Night's catalog receives the product label \" Café Beans - LARGE \". The same product also arrives as \"café beans - large\". If labels remain inconsistent, grouping and lookup will treat them as different categories.",
    "notion": "https://app.notion.com/p/3d1072057ae381999d79f4b26c28930a?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-007",
    "file": "/notes/python-007.md",
    "title": "PY-007 — True, false, and missing",
    "blurb": "Northstar promises delivery within 48 hours.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 4,
    "minutes": 35,
    "standfirst": "Northstar promises delivery within 48 hours. An order arriving after 48 hours is late. But one order has no arrival time yet. Calling that order “not late” would turn missing evidence into a confident answer.",
    "notion": "https://app.notion.com/p/3d1072057ae381bca53ef299eb54dcd0?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-008",
    "file": "/notes/python-008.md",
    "title": "PY-008 — Lists and tuples: ordered observations",
    "blurb": "City Sensor Lab records one temperature for each day of a week.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 5,
    "minutes": 40,
    "standfirst": "City Sensor Lab records one temperature for each day of a week. The order is essential: moving 18.9 from Saturday to Monday changes the story even though the same values remain.",
    "notion": "https://app.notion.com/p/3d1072057ae38186b249d690c9a7dca0?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-009",
    "file": "/notes/python-009.md",
    "title": "PY-009 — Dictionaries and sets: records and membership",
    "blurb": "Northstar has a customer ID, region, visit count, and membership tier.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 6,
    "minutes": 40,
    "standfirst": "Northstar has a customer ID, region, visit count, and membership tier. A list could hold those values, but record[2] does not tell a reader what 2 means. A dictionary retrieves values by meaningful keys.",
    "notion": "https://app.notion.com/p/3d1072057ae381d4810fcb4a3cd9edfb?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-010",
    "file": "/notes/python-010.md",
    "title": "PY-010 — Nested data, mutation, and copies",
    "blurb": "Two Northstar analysts begin with what look like separate order records.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 7,
    "minutes": 45,
    "standfirst": "Two Northstar analysts begin with what look like separate order records. One changes a quantity for a what-if report. The other analyst's record changes too.",
    "notion": "https://app.notion.com/p/3d1072057ae381a9ba5ddcf9e9e632a9?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-studio-a",
    "file": "/notes/python-studio-a.md",
    "title": "PY-S-A — Northstar basket detective",
    "blurb": "Northstar Market prepared a baseline basket and a promotion scenario from one template.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 1 · Values, objects, and data containers",
    "phaseOrder": 1,
    "order": 8,
    "minutes": 100,
    "standfirst": "Northstar Market prepared a baseline basket and a promotion scenario from one template. The promotion analyst changed one quantity. The baseline total changed too.",
    "notion": "https://app.notion.com/p/3d1072057ae381a2b6e5e5d5652ee36b?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "studio"
  },
  {
    "id": "python-011",
    "file": "/notes/python-011.md",
    "title": "PY-011 — Decisions with conditions",
    "blurb": "Northstar wants one action for each customer: urgent outreach, a standard follow-up, no action, or manual review when the data is missing.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 1,
    "minutes": 35,
    "standfirst": "Northstar wants one action for each customer: urgent outreach, a standard follow-up, no action, or manual review when the data is missing. The thresholds are easy to state. The harder part is making Python test them in an order that preserves their meaning.",
    "notion": "https://app.notion.com/p/3d1072057ae3810ea689e1cf899b77eb?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-012",
    "file": "/notes/python-012.md",
    "title": "PY-012 — Repeating work with loops",
    "blurb": "A city sensor sends several readings. We want to inspect each available value, skip missing measurements, and stop as soon as a dangerous temperature appears.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 2,
    "minutes": 40,
    "standfirst": "A city sensor sends several readings. We want to inspect each available value, skip missing measurements, and stop as soon as a dangerous temperature appears. Copying the same conditional five times would be fragile, especially when tomorrow brings fifty readings.",
    "notion": "https://app.notion.com/p/3d1072057ae38135a263c5a03087abe7?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-013",
    "file": "/notes/python-013.md",
    "title": "PY-013 — Transform, filter, count, and accumulate",
    "blurb": "Northstar has four transactions. The analyst needs four different answers: signed amounts, refund records, counts by region, and net revenue.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar has four transactions. The analyst needs four different answers: signed amounts, refund records, counts by region, and net revenue. Each task visits the same input, but each preserves different state.",
    "notion": "https://app.notion.com/p/3d1072057ae381bea60dc17bf55d81d9?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-014",
    "file": "/notes/python-014.md",
    "title": "PY-014 — Comprehensions without cleverness",
    "blurb": "Movie Night has records with untidy genres, ratings, and IDs.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 4,
    "minutes": 35,
    "standfirst": "Movie Night has records with untidy genres, ratings, and IDs. You already know the loop patterns that transform, filter, and collect values. Now the question is narrower: when can one compact expression state that pattern more clearly?",
    "notion": "https://app.notion.com/p/3d1072057ae381fa8691e3099cbccca4?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-015",
    "file": "/notes/python-015.md",
    "title": "PY-015 — Functions: inputs, outputs, and contracts",
    "blurb": "City Sensor Lab receives Celsius and Fahrenheit readings.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 5,
    "minutes": 45,
    "standfirst": "City Sensor Lab receives Celsius and Fahrenheit readings. Copying the same conversion and range checks into every analysis would invite drift: one copy gets fixed while another does not. A function gives the transformation a name and a boundary.",
    "notion": "https://app.notion.com/p/3d1072057ae3815398e0d34d9b2c1595?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-016",
    "file": "/notes/python-016.md",
    "title": "PY-016 — Scope, defaults, and side effects",
    "blurb": "Experiment Bench keeps prediction scores above a confidence threshold.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 6,
    "minutes": 40,
    "standfirst": "Experiment Bench keeps prediction scores above a confidence threshold. The first draft seems reusable, yet two calls share history and a later edit to a global name changes the rule. The syntax is valid; the state boundary is not.",
    "notion": "https://app.notion.com/p/3d1072057ae381df9b46d5f563ae1fec?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-017",
    "file": "/notes/python-017.md",
    "title": "PY-017 — Iterables, iterators, and useful generators",
    "blurb": "City Sensor Lab may receive millions of rows.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 2 · Decisions, repetition, and reusable transformations",
    "phaseOrder": 2,
    "order": 7,
    "minutes": 40,
    "standfirst": "City Sensor Lab may receive millions of rows. Building every intermediate list before checking the first row delays feedback and can increase memory use. Python can instead expose a stream: the consumer asks for the next item only when it is ready.",
    "notion": "https://app.notion.com/p/3d1072057ae381cbbe1cda4ac1571bcf?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-018",
    "file": "/notes/python-018.md",
    "title": "PY-018 — Reading tracebacks and debugging",
    "blurb": "Northstar Market receives four order amounts.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 1,
    "minutes": 45,
    "standfirst": "Northstar Market receives four order amounts. One is the text \"N/A\". The cleaner should reject that record, preserve the other three, and make the rejection visible. Instead, the first version crashes; a hurried “fix” then catches every error and silently changes the total.",
    "notion": "https://app.notion.com/p/3d1072057ae381028a2fed9e6579bc5b?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-019",
    "file": "/notes/python-019.md",
    "title": "PY-019 — Paths, files, and safe resource handling",
    "blurb": "City Sensor Lab keeps raw field notes in data/raw and generated summaries in reports.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 2,
    "minutes": 40,
    "standfirst": "City Sensor Lab keeps raw field notes in data/raw and generated summaries in reports. A notebook works when launched from one folder, then fails when a scheduler launches it elsewhere. The data did not move; the program changed the directory from which it interpreted a relative path.",
    "notion": "https://app.notion.com/p/3d1072057ae381ad9ec6ca5c6bb8ba14?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-020",
    "file": "/notes/python-020.md",
    "title": "PY-020 — CSV, JSON, text, and encodings",
    "blurb": "Northstar Market receives flat order rows in CSV and nested customer preferences in JSON.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar Market receives flat order rows in CSV and nested customer preferences in JSON. Both files parse successfully. Yet an order amount is still text, one record lacks an ID, and a product named \"Café press\" must survive a write/read round trip.",
    "notion": "https://app.notion.com/p/3d1072057ae3811b9da8ff4af170caf8?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-021",
    "file": "/notes/python-021.md",
    "title": "PY-021 — Modules, imports, and the standard library",
    "blurb": "Northstar's analyst has copied the same date parser and order-summary function into three notebooks.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 4,
    "minutes": 40,
    "standfirst": "Northstar's analyst has copied the same date parser and order-summary function into three notebooks. One copy fixes a bug; the others do not. Moving reusable logic into cleaning.py creates one named place for it, while report.py remains responsible for the current analysis.",
    "notion": "https://app.notion.com/p/3d1072057ae381218f97d19ba46d5023?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-022",
    "file": "/notes/python-022.md",
    "title": "PY-022 — Environments, packages, and reproducible installs",
    "blurb": "A teammate's analysis imports pandas in a terminal but raises ModuleNotFoundError in a notebook.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 5,
    "minutes": 45,
    "standfirst": "A teammate's analysis imports pandas in a terminal but raises ModuleNotFoundError in a notebook. Both observations can be true: the terminal command and notebook kernel may run different Python interpreters, each with a different package set.",
    "notion": "https://app.notion.com/p/3d1072057ae3819f877dec6b96f1cd25?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-023",
    "file": "/notes/python-023.md",
    "title": "PY-023 — HTTP APIs, pagination, and defensive parsing",
    "blurb": "City Sensor Lab needs public air-quality observations from an HTTP API.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 6,
    "minutes": 45,
    "standfirst": "City Sensor Lab needs public air-quality observations from an HTTP API. The first response has only two rows and a cursor for the next page. Another response is rate-limited. A third says 200 OK but contains a record with no sensorid.",
    "notion": "https://app.notion.com/p/3d1072057ae381b386d4decf5ee8dd2c?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-024",
    "file": "/notes/python-024.md",
    "title": "PY-024 — Assertions, tests, logging, and reliable scripts",
    "blurb": "Northstar's daily order cleaner should accept valid non-negative amounts, record rejected rows, preserve unique order IDs, and write the same result when rerun with the same declared inputs.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 7,
    "minutes": 45,
    "standfirst": "Northstar's daily order cleaner should accept valid non-negative amounts, record rejected rows, preserve unique order IDs, and write the same result when rerun with the same declared inputs. A happy-path notebook cell is not enough: tomorrow's schema may change, and a silent partial output can feed a model before anyone notices.",
    "notion": "https://app.notion.com/p/3d1072057ae381f0a3e9e58203cd751d?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-studio-b",
    "file": "/notes/python-studio-b.md",
    "title": "PY-S-B — City Sensor ingest",
    "blurb": "Riverton's morning sensor job receives three CSV rows from a field laptop and four JSON items across two API pages.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 3 · Reliable Python with real data",
    "phaseOrder": 3,
    "order": 8,
    "minutes": 75,
    "standfirst": "Riverton's morning sensor job receives three CSV rows from a field laptop and four JSON items across two API pages. Transport and parsing success are not enough: malformed values must not enter the accepted dataset, and dropping them silently would hide a population change.",
    "notion": "https://app.notion.com/p/3d1072057ae381c18ffccb078cd1f804?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "studio"
  },
  {
    "id": "python-025",
    "file": "/notes/python-025.md",
    "title": "PY-025 — Unpacking, enumerate, zip, and key-based sorting",
    "blurb": "The Cricket Match Desk needs to label innings, align model predictions with match IDs, and rank batters by strike rate.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 4 · Python fluency for data work",
    "phaseOrder": 4,
    "order": 1,
    "minutes": 35,
    "standfirst": "The Cricket Match Desk needs to label innings, align model predictions with match IDs, and rank batters by strike rate. Manual indexes can express all three tasks, but they hide the relationship we care about and create opportunities for off-by-one or misalignment errors.",
    "notion": "https://app.notion.com/p/3d1072057ae381f3aa7feec07c5b80b6?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-026",
    "file": "/notes/python-026.md",
    "title": "PY-026 — Functions as values and reusable pipelines",
    "blurb": "Experiment Bench compares the same predictions under accuracy, error rate, and a cost rule where missing a true positive is more expensive than a false alarm.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 4 · Python fluency for data work",
    "phaseOrder": 4,
    "order": 2,
    "minutes": 40,
    "standfirst": "Experiment Bench compares the same predictions under accuracy, error rate, and a cost rule where missing a true positive is more expensive than a false alarm. Copying the evaluation loop three times invites drift. Instead, the experiment can receive the scoring function as a strategy.",
    "notion": "https://app.notion.com/p/3d1072057ae38109b6d6f6a8c6ef3763?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-027",
    "file": "/notes/python-027.md",
    "title": "PY-027 — Classes and dataclasses—just enough",
    "blurb": "An experiment has settings, results, and operations that only make sense for those values.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 4 · Python fluency for data work",
    "phaseOrder": 4,
    "order": 3,
    "minutes": 45,
    "standfirst": "An experiment has settings, results, and operations that only make sense for those values. Passing five loose dictionaries between functions works for a while, but names drift and invalid combinations become easy to create. An object gives that related state a stable home and exposes the operations that use it.",
    "notion": "https://app.notion.com/p/3d1072057ae381878216e081685bdb9e?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-028",
    "file": "/notes/python-028.md",
    "title": "PY-028 — From exploration notebook to readable module",
    "blurb": "Notebooks are excellent for asking a question, looking at evidence, and changing direction.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 4 · Python fluency for data work",
    "phaseOrder": 4,
    "order": 4,
    "minutes": 45,
    "standfirst": "Notebooks are excellent for asking a question, looking at evidence, and changing direction. Trouble begins when the useful result depends on “the cells I happened to run.” A teammate cannot see yesterday's kernel state, and a scheduled job cannot click cells in your preferred order.",
    "notion": "https://app.notion.com/p/3d1072057ae381cbbed8efcef0a1dd36?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-029",
    "file": "/notes/python-029.md",
    "title": "PY-029 — Measure first: performance and the vectorization bridge",
    "blurb": "A sensor report feels slow, so someone rewrites the clearest loop into a dense one-liner.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 4 · Python fluency for data work",
    "phaseOrder": 4,
    "order": 5,
    "minutes": 45,
    "standfirst": "A sensor report feels slow, so someone rewrites the clearest loop into a dense one-liner. The report is still slow: file parsing was the bottleneck. Performance belongs to a workload in an environment, not to line count or style folklore.",
    "notion": "https://app.notion.com/p/3d1072057ae38135a5a4db4010b2b804?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-030",
    "file": "/notes/python-030.md",
    "title": "PY-030 — The NumPy array mental model",
    "blurb": "A list can hold almost anything, including rows of different lengths and values of unrelated types.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 1,
    "minutes": 40,
    "standfirst": "A list can hold almost anything, including rows of different lengths and values of unrelated types. That flexibility is useful, but numerical code needs a stronger promise. Before converting temperatures or fitting a model, you want to know how many directions the data has, what each direction means, and how every value is represented.",
    "notion": "https://app.notion.com/p/3d1072057ae3817d8f9fef6fec019114?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-031",
    "file": "/notes/python-031.md",
    "title": "PY-031 — Create, inspect, reshape, and transpose",
    "blurb": "A logger exported twelve values in one line, but the model contract expects rows of stations and columns of hours.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 2,
    "minutes": 40,
    "standfirst": "A logger exported twelve values in one line, but the model contract expects rows of stations and columns of hours. No value needs to be calculated; the same observations need a different grid interpretation.",
    "notion": "https://app.notion.com/p/3d1072057ae381928b7bef24533ddc6f?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-032",
    "file": "/notes/python-032.md",
    "title": "PY-032 — Indexing, masks, views, and copies",
    "blurb": "The lab needs two different actions: correct a contiguous block in the source readings, and export a filtered subset that must be independent.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 3,
    "minutes": 45,
    "standfirst": "The lab needs two different actions: correct a contiguous block in the source readings, and export a filtered subset that must be independent. Both begin with square brackets. Their memory behavior is different.",
    "notion": "https://app.notion.com/p/3d1072057ae381039180c9a7ed651166?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-033",
    "file": "/notes/python-033.md",
    "title": "PY-033 — Vectorization, ufuncs, and broadcasting",
    "blurb": "The lab has three stations and four hourly readings.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 4,
    "minutes": 45,
    "standfirst": "The lab has three stations and four hourly readings. A Python loop can convert each temperature, but the data already carries a regular array contract. NumPy can apply an operation across that whole contract—provided the shapes say which values align.",
    "notion": "https://app.notion.com/p/3d1072057ae3812388addb73b7e31960?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-034",
    "file": "/notes/python-034.md",
    "title": "PY-034 — Aggregation, missing values, and axes",
    "blurb": "The same station × hour grid can answer two different questions: “What was the city average at each hour?” and “What was each station's range across the day?” The values are identical, but…",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 5,
    "minutes": 40,
    "standfirst": "The same station × hour grid can answer two different questions: “What was the city average at each hour?” and “What was each station's range across the day?” The values are identical, but the direction we collapse is not.",
    "notion": "https://app.notion.com/p/3d1072057ae3811ab4a8d02f8178b772?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-035",
    "file": "/notes/python-035.md",
    "title": "PY-035 — Randomness, simulation, and shape-safe linear algebra",
    "blurb": "An experiment randomly assigns groups and computes one score per participant.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 6,
    "minutes": 45,
    "standfirst": "An experiment randomly assigns groups and computes one score per participant. Debugging needs two contracts at once: controlled pseudorandom state and compatible numerical shapes. “It looked random” and “the numbers multiplied” are not enough.",
    "notion": "https://app.notion.com/p/3d1072057ae38152b498f341bcef4133?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-studio-c",
    "file": "/notes/python-studio-c.md",
    "title": "PY-S-C — Sensor array lab",
    "blurb": "City Sensor Lab has two days of readings from three stations at four hourly slots.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 5 · NumPy: think in shape, dtype, and axis",
    "phaseOrder": 5,
    "order": 7,
    "minutes": 75,
    "standfirst": "City Sensor Lab has two days of readings from three stations at four hourly slots. Three values are unusable: one is missing and two lie outside the instrument's accepted range. Your job is to clean and calibrate the cube without losing its axis meanings, then prove that the result can be replayed.",
    "notion": "https://app.notion.com/p/3d1072057ae38198a7eecb551b8dd61d?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "studio"
  },
  {
    "id": "python-036",
    "file": "/notes/python-036.md",
    "title": "PY-036 — The DataFrame mental model",
    "blurb": "Northstar Market has an order table and two shipping-cost estimates.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar Market has an order table and two shipping-cost estimates. Can you add the estimates safely when their rows appear in different orders?",
    "notion": "https://app.notion.com/p/3d1072057ae38111bb7ffa495411881d?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-037",
    "file": "/notes/python-037.md",
    "title": "PY-037 — Load, inspect, select, and filter",
    "blurb": "Northstar Market received a five-row sales extract.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 2,
    "minutes": 45,
    "standfirst": "Northstar Market received a five-row sales extract. One amount is the token unknown, and one order ID appears twice. Before anybody computes revenue, can you say what loaded, what one row claims to represent, and which rows a filter will retain?",
    "notion": "https://app.notion.com/p/3d1072057ae38130a9d5e3b348cca744?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-038",
    "file": "/notes/python-038.md",
    "title": "PY-038 — Clean types, text, dates, and categories",
    "blurb": "Northstar's export contains $84.00, oops, several spellings of “Mug,” timestamps with different UTC offsets, and a region outside the reporting vocabulary.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar's export contains $84.00, oops, several spellings of “Mug,” timestamps with different UTC offsets, and a region outside the reporting vocabulary. How can we create useful typed columns without pretending every source value succeeded?",
    "notion": "https://app.notion.com/p/3d1072057ae381828c1bcc62473b53dd?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-039",
    "file": "/notes/python-039.md",
    "title": "PY-039 — Missing rows, duplicates, outliers, and validation",
    "blurb": "Northstar's transaction extract contains two rows for one order, two absent regions, a negative quantity, a high but plausible price, and several zero discounts.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 4,
    "minutes": 45,
    "standfirst": "Northstar's transaction extract contains two rows for one order, two absent regions, a negative quantity, a high but plausible price, and several zero discounts. A blanket dropna() or “remove outliers” rule would treat different mechanisms as if they meant the same thing.",
    "notion": "https://app.notion.com/p/3d1072057ae381e0aaa9de830d6a405c?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-040",
    "file": "/notes/python-040.md",
    "title": "PY-040 — Create columns: vectorized transform, map, and apply",
    "blurb": "Northstar needs net revenue, a reporting region, and a review flag for each accepted order.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 5,
    "minutes": 45,
    "standfirst": "Northstar needs net revenue, a reporting region, and a review flag for each accepted order. You could write a Python function that visits every row, but the formulas already operate column by column. pandas can preserve row labels while expressing that lineage directly.",
    "notion": "https://app.notion.com/p/3d1072057ae381fab5cee2318447d119?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-041",
    "file": "/notes/python-041.md",
    "title": "PY-041 — Group, aggregate, transform, and roll",
    "blurb": "Northstar wants three related answers: one summary row per customer and region, each order's customer's mean spend, and each customer's rolling seven-day revenue demand.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 6,
    "minutes": 45,
    "standfirst": "Northstar wants three related answers: one summary row per customer and region, each order's customer's mean spend, and each customer's rolling seven-day revenue demand. Northstar uses booked revenue as a bounded demand proxy in this exercise. All three answers begin with the same order rows, but they do not have the same output grain.",
    "notion": "https://app.notion.com/p/3d1072057ae381358df1c1ddf6aef5a4?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-042",
    "file": "/notes/python-042.md",
    "title": "PY-042 — Combine tables without changing the question",
    "blurb": "Northstar wants to enrich four orders with product and customer attributes.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 7,
    "minutes": 45,
    "standfirst": "Northstar wants to enrich four orders with product and customer attributes. The expected result still has one row per order. Yet the product table contains P-2 twice, one order references an unknown product, and another references an unknown customer. A left join can therefore multiply rows and preserve unmatched rows.",
    "notion": "https://app.notion.com/p/3d1072057ae3817bb204d6e5fdd5b102?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-043",
    "file": "/notes/python-043.md",
    "title": "PY-043 — Reshape and build auditable pandas pipelines",
    "blurb": "Northstar stores one row per customer-product pair, with a separate revenue column for each month.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 6 · pandas: reason about labels, grain, and transformations",
    "phaseOrder": 6,
    "order": 8,
    "minutes": 45,
    "standfirst": "Northstar stores one row per customer-product pair, with a separate revenue column for each month. Analysis needs one customer-product-month observation per row; modeling later needs one feature row per customer. Can you move between those forms while proving that keys, totals, and match coverage survive?",
    "notion": "https://app.notion.com/p/3d1072057ae3818e8fc1cbbb249af4b1?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-044",
    "file": "/notes/python-044.md",
    "title": "PY-044 — Choose a chart from the question",
    "blurb": "Northstar’s delivery table contains region, delivery days, order date, and order value.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 7 · Visualization and exploratory data analysis",
    "phaseOrder": 7,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar’s delivery table contains region, delivery days, order date, and order value. A manager asks, “Which region has the longest typical delivery?” You could draw a histogram, scatterplot, line, or bars. All four can accept numbers, but only some make the requested comparison easy.",
    "notion": "https://app.notion.com/p/3d1072057ae38140be42c5fe232b3fcf?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-045",
    "file": "/notes/python-045.md",
    "title": "PY-045 — Show distributions and comparisons",
    "blurb": "The regional medians in PY-044 answered a narrow comparison.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 7 · Visualization and exploratory data analysis",
    "phaseOrder": 7,
    "order": 2,
    "minutes": 45,
    "standfirst": "The regional medians in PY-044 answered a narrow comparison. They did not show whether most Coastal orders are slow, whether a few delays pull attention, or whether one region has far fewer orders. Northstar needs the distribution: the range, concentration, shape, and unusual values among individual deliveries.",
    "notion": "https://app.notion.com/p/3d1072057ae3814b912afc5a6de00c5a?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-046",
    "file": "/notes/python-046.md",
    "title": "PY-046 — Show relationships and many variables",
    "blurb": "Northstar has one row per customer, with visits in the last 30 days, spending in dollars, days since the last visit, region, and whether the customer later churned.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 7 · Visualization and exploratory data analysis",
    "phaseOrder": 7,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar has one row per customer, with visits in the last 30 days, spending in dollars, days since the last visit, region, and whether the customer later churned. The immediate question is modest: do customers with more visits tend to spend more in this sample?",
    "notion": "https://app.notion.com/p/3d1072057ae3812b8183d2c774e8fee5?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-047",
    "file": "/notes/python-047.md",
    "title": "PY-047 — A disciplined EDA loop and honest communication",
    "blurb": "A weekly chart shows average sensor temperature rising from 17.0°C to 21.5°C.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 7 · Visualization and exploratory data analysis",
    "phaseOrder": 7,
    "order": 4,
    "minutes": 45,
    "standfirst": "A weekly chart shows average sensor temperature rising from 17.0°C to 21.5°C. That is visually dramatic. Before anyone writes “the city warmed sharply,” we need to ask which sensors contributed to each mean. One cool-location sensor stopped reporting in week 2.",
    "notion": "https://app.notion.com/p/3d1072057ae381abae42cdbfc4a1a49b?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-studio-d",
    "file": "/notes/python-studio-d.md",
    "title": "PY-S-D — Northstar analysis",
    "blurb": "Northstar wants to compare matched August revenue by customer region and prepare one feature row per customer.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 7 · Visualization and exploratory data analysis",
    "phaseOrder": 7,
    "order": 5,
    "minutes": 75,
    "standfirst": "Northstar wants to compare matched August revenue by customer region and prepare one feature row per customer. The extracts contain an exact duplicate, an invalid quantity, an unknown product, and an unknown customer. A polished chart would be easy to make—and easy to mistrust—unless every transformation preserves a declared grain and records population changes.",
    "notion": "https://app.notion.com/p/3d1072057ae381b2b2c8c7a3c80512c4?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "studio"
  },
  {
    "id": "python-048",
    "file": "/notes/python-048.md",
    "title": "PY-048 — From a table to X and y",
    "blurb": "Northstar wants to predict whether an active customer will churn within the next 60 days.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 1,
    "minutes": 45,
    "standfirst": "Northstar wants to predict whether an active customer will churn within the next 60 days. Before choosing any algorithm, we must define one example row, the information available at prediction time, and the answer the model is meant to learn.",
    "notion": "https://app.notion.com/p/3d1072057ae381a9af5cc9954dad9fae?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-049",
    "file": "/notes/python-049.md",
    "title": "PY-049 — The estimator contract: fit, predict, evaluate",
    "blurb": "In PY-048, Northstar created one row per customer, X with feature columns, aligned target y, a fixed train/test boundary, and a majority baseline.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 2,
    "minutes": 45,
    "standfirst": "In PY-048, Northstar created one row per customer, X with feature columns, aligned target y, a fixed train/test boundary, and a majority baseline. Now we will fit one deliberately small classifier—not to crown an algorithm, but to understand the interface shared by scikit-learn estimators.",
    "notion": "https://app.notion.com/p/3d1072057ae381c5942df06e2ff54d64?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-050",
    "file": "/notes/python-050.md",
    "title": "PY-050 — Preprocessing pipelines without leakage",
    "blurb": "Northstar’s customer rows contain missing spending and recency values plus a categorical region.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar’s customer rows contain missing spending and recency values plus a categorical region. A model needs a numeric representation, so we must learn medians, scaling values, and a category vocabulary. If those quantities are calculated before the train/test split, held-out customers have already influenced the representation.",
    "notion": "https://app.notion.com/p/3d1072057ae381d0bc67cb298b38da59?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-051",
    "file": "/notes/python-051.md",
    "title": "PY-051 — Compare experiments with CV, metrics, and seeds",
    "blurb": "Northstar’s majority baseline and fitted pipeline tied at 0.75 accuracy on four test rows.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 4,
    "minutes": 45,
    "standfirst": "Northstar’s majority baseline and fitted pipeline tied at 0.75 accuracy on four test rows. That number is fragile and misaligned with the next teaching decision. For this fixture, assume that missing a likely churner costs five times as much as contacting a customer who would stay; the ratio is illustrative, not an estimate from Northstar operations.",
    "notion": "https://app.notion.com/p/3d1072057ae381bdaef0cef8de0b29f7?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-052",
    "file": "/notes/python-052.md",
    "title": "PY-052 — Ship a reproducible prediction workflow",
    "blurb": "A notebook contains a fitted pipeline and a promising score.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 5,
    "minutes": 45,
    "standfirst": "A notebook contains a fitted pipeline and a promising score. Another process receives tomorrow’s customers. Saving one Python object is not enough: the new process also needs column meaning and order, dependency versions, training/evaluation provenance, an input validator, and a policy for values the training data never saw.",
    "notion": "https://app.notion.com/p/3d1072057ae3811981cbd4814189581b?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "python-studio-e",
    "file": "/notes/python-studio-e.md",
    "title": "PY-S-E — Reproducible churn experiment",
    "blurb": "Northstar wants a small churn-risk experiment.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Phase 8 · ML-ready Python and reproducible experiments",
    "phaseOrder": 8,
    "order": 6,
    "minutes": 75,
    "standfirst": "Northstar wants a small churn-risk experiment. Each row is one customer; churned says whether that customer left in the following period. The business cares about finding likely churners, but a single high score is not a deliverable. You must preserve the sample/feature/target boundary, learn preprocessing only inside training folds, compare against a naive baseline, and replay prediction from a recorded bundle.",
    "notion": "https://app.notion.com/p/3d1072057ae381a6853ffc7096a5300d?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "studio"
  },
  {
    "id": "python-b01",
    "file": "/notes/python-b01.md",
    "title": "PY-B01 — From arrays to tensors, batches, and devices",
    "blurb": "Your image array already has a shape, dtype, and axis meaning.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Optional bridges",
    "phaseOrder": 9,
    "order": 1,
    "minutes": 40,
    "standfirst": "Your image array already has a shape, dtype, and axis meaning. A tensor API keeps those questions and adds: where is the data located? Some APIs also expose gradient-tracking state, named here only.",
    "notion": "https://app.notion.com/p/3d1072057ae3810bb21de259099abc0f?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "bridge"
  },
  {
    "id": "python-b02",
    "file": "/notes/python-b02.md",
    "title": "PY-B02 — Calling model APIs as experiments",
    "blurb": "You want to label four support messages as account, billing, or delivery.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Optional bridges",
    "phaseOrder": 9,
    "order": 2,
    "minutes": 40,
    "standfirst": "You want to label four support messages as account, billing, or delivery. The fixture provider rate-limits one attempt, later returns three schema-valid predictions, makes one valid-but-wrong prediction, and returns one item without a label.",
    "notion": "https://app.notion.com/p/3d1072057ae38167b0b7f62208f9c2d9?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "bridge"
  },
  {
    "id": "python-b03",
    "file": "/notes/python-b03.md",
    "title": "PY-B03 — When data belongs in SQL",
    "blurb": "Northstar stores years of orders in a database.",
    "domain": "Programming",
    "section": "Python for DS & AI/ML",
    "phase": "Optional bridges",
    "phaseOrder": 9,
    "order": 3,
    "minutes": 55,
    "standfirst": "Northstar stores years of orders in a database. An analyst needs one month of revenue totals by region. Downloading every order and then filtering in pandas would move detail rows that the question never uses. A better boundary is: send the selection and aggregation to the database, receive a bounded result, then continue labeled analysis in pandas.",
    "notion": "https://app.notion.com/p/3d1072057ae381509873f1fb180546d1?pvs=204",
    "primarySubject": "subject-python",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-001",
    "file": "/notes/pyspark-001.md",
    "title": "PS-001 — When pandas stops being the whole answer",
    "blurb": "Northstar Market must identify hubs with late parcels before the morning refund run.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 0 · Why distributed data changes the rules",
    "phaseOrder": 0,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar Market must identify hubs with late parcels before the morning refund run. Yesterday's 80,000-row extract fits comfortably in pandas. The retained scan history, however, contains 240 million rows across many files. It may exceed one laptop's memory, takes too long to reread after a failure, and is already stored near a cluster. The destination is still modest: count late delivery scans by hub.",
    "curriculumId": "PS-001",
    "contentType": "core",
    "requires": [
      "PY-036"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-002",
    "file": "/notes/pyspark-002.md",
    "title": "PS-002 — The first PySpark question-to-answer loop",
    "blurb": "Northstar Market's fulfillment lead asks: Which sales channel produced at least two paid orders, and how much revenue did each qualifying channel produce?",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 0 · Why distributed data changes the rules",
    "phaseOrder": 0,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar Market's fulfillment lead asks: Which sales channel produced at least two paid orders, and how much revenue did each qualifying channel produce? The answer will become a morning check, so the code must preserve field meaning, expose its action boundary, and return a deterministic display. This page completes one small PySpark loop.",
    "curriculumId": "PS-002",
    "contentType": "core",
    "requires": [
      "PS-001"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-003",
    "file": "/notes/pyspark-003.md",
    "title": "PS-003 — Local mode, notebook, cluster, and Spark Connect",
    "blurb": "A Northstar analyst has a PySpark notebook that counts paid orders correctly on a laptop.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 0 · Why distributed data changes the rules",
    "phaseOrder": 0,
    "order": 3,
    "minutes": 40,
    "standfirst": "A Northstar analyst has a PySpark notebook that counts paid orders correctly on a laptop. The scheduled cluster job times out, while a teammate's Spark Connect session succeeds. “It works in the notebook” does not identify which processes ran, where the data lived, or whether the test resembled production.",
    "curriculumId": "PS-003",
    "contentType": "core",
    "requires": [
      "PS-002"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-004",
    "file": "/notes/pyspark-004.md",
    "title": "PS-004 — From pandas and SQL to Spark expressions",
    "blurb": "Northstar already has a trusted SQL query and a pandas prototype for: paid revenue by region for regions with at least two paid orders.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 0 · Why distributed data changes the rules",
    "phaseOrder": 0,
    "order": 4,
    "minutes": 40,
    "standfirst": "Northstar already has a trusted SQL query and a pandas prototype for: paid revenue by region for regions with at least two paid orders. The team needs a Spark version without smuggling all rows into one Python process. The goal is not character-for-character translation. It is to preserve the data contract and express the work so Spark can analyze, optimize, partition, and execute it.",
    "curriculumId": "PS-004",
    "contentType": "core",
    "requires": [
      "PS-002",
      "PY-043"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-005",
    "file": "/notes/pyspark-005.md",
    "title": "PS-005 — Driver, cluster manager, executors, and workers",
    "blurb": "Northstar schedules a job to count late parcel scans by hub.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar schedules a job to count late parcel scans by hub. When it slows down, four explanations appear: “the worker is overloaded,” “the driver needs more cores,” “the cluster manager is doing the aggregation,” and “an executor was lost.” Those statements refer to different roles; treating them as synonyms sends diagnosis to the wrong place.",
    "curriculumId": "PS-005",
    "contentType": "core",
    "requires": [
      "PS-003"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-006",
    "file": "/notes/pyspark-006.md",
    "title": "PS-006 — Partitions, tasks, stages, and jobs",
    "blurb": "Northstar's late-scan aggregation appears as six lines of DataFrame code, yet the Spark UI shows jobs, stages, and many task attempts.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar's late-scan aggregation appears as six lines of DataFrame code, yet the Spark UI shows jobs, stages, and many task attempts. The code is not being repeated arbitrarily. Spark has divided an action into schedulable work around data dependencies.",
    "curriculumId": "PS-006",
    "contentType": "core",
    "requires": [
      "PS-005"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-007",
    "file": "/notes/pyspark-007.md",
    "title": "PS-007 — Lazy transformations and actions",
    "blurb": "Northstar's notebook creates latescans, byhub, and priorityhubs in seconds.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar's notebook creates latescans, byhub, and priorityhubs in seconds. The analyst concludes that the 240-million-row scan table has already been processed. Then show() takes six minutes. The variable assignments built a plan; the action asked Spark to execute it.",
    "curriculumId": "PS-007",
    "contentType": "core",
    "requires": [
      "PS-006"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-008",
    "file": "/notes/pyspark-008.md",
    "title": "PS-008 — Lineage, recomputation, persistence, and fault recovery",
    "blurb": "Northstar creates a cleaned scan DataFrame, counts its rows, then computes late-rate by hub.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 4,
    "minutes": 40,
    "standfirst": "Northstar creates a cleaned scan DataFrame, counts its rows, then computes late-rate by hub. The source is read twice. A teammate calls cache() everywhere; another says Spark never recomputes anything because it “remembers the lineage.” Both miss the distinction between remembering how to compute data and retaining computed partition blocks.",
    "curriculumId": "PS-008",
    "contentType": "core",
    "requires": [
      "PS-007"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-009",
    "file": "/notes/pyspark-009.md",
    "title": "PS-009 — Read the Spark UI as evidence",
    "blurb": "“Spark was slow” is not yet a diagnosis. Northstar’s daily revenue summary finishes, but one part of the work takes much longer than the rest.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 5,
    "minutes": 40,
    "standfirst": "“Spark was slow” is not yet a diagnosis. Northstar’s daily revenue summary finishes, but one part of the work takes much longer than the rest. The Spark UI lets you move from an application-level symptom to a job, then a stage, then its tasks and executors. Your destination is a short evidence statement: which stage was uneven, what moved or spilled, and where to investigate next?",
    "curriculumId": "PS-009",
    "contentType": "core",
    "requires": [
      "PS-006",
      "PS-008"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-01",
    "file": "/notes/pyspark-studio-01.md",
    "title": "PS-S01 — Parcel the work",
    "blurb": "Northstar Commerce has twelve parcel scans.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 1 · The distributed execution mental model",
    "phaseOrder": 1,
    "order": 6,
    "minutes": 90,
    "standfirst": "Northstar Commerce has twelve parcel scans. Operations needs delivered parcel counts by depot, but first you must explain where the work happens. Your deliverable is not only a result table: it is a defensible execution sketch, evidence from two actions, and one persistence decision.",
    "curriculumId": "PS-S01",
    "contentType": "studio",
    "requires": [
      "PS-005",
      "PS-006",
      "PS-007",
      "PS-008",
      "PS-009"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-010",
    "file": "/notes/pyspark-010.md",
    "title": "PS-010 — SparkSession, DataFrame, Column, Row, and schema",
    "blurb": "Northstar needs the value of each order line, but PySpark objects live at different layers.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar needs the value of each order line, but PySpark objects live at different layers. A SparkSession is the application’s structured-data entry point. A DataFrame describes a distributed table. A Column describes an expression over rows. A Row is one local result record. A schema is the typed contract joining the plan to the data.",
    "curriculumId": "PS-010",
    "contentType": "core",
    "requires": [
      "PS-007"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-011",
    "file": "/notes/pyspark-011.md",
    "title": "PS-011 — Explicit schemas and Spark SQL types",
    "blurb": "A Northstar price arriving as \"12.50\", 12.5, or a decimal may look equivalent, but those representations do not promise the same arithmetic.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 2,
    "minutes": 40,
    "standfirst": "A Northstar price arriving as \"12.50\", 12.5, or a decimal may look equivalent, but those representations do not promise the same arithmetic. A schema gives every field a name, SQL type, nullability flag, and position before business logic begins. Your destination is an ingestion boundary that rejects incompatible records early and preserves money, dates, timestamps, and nested shipping data deliberately.",
    "curriculumId": "PS-011",
    "contentType": "core",
    "requires": [
      "PS-010",
      "PY-020"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-012",
    "file": "/notes/pyspark-012.md",
    "title": "PS-012 — Create, inspect, and sample without collecting everything",
    "blurb": "Northstar’s production order table can be much larger than client memory.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar’s production order table can be much larger than client memory. During development you still need to confirm its schema, prove it is nonempty, and inspect a few recognizable records. The safe habit is not “never look at data”; it is to choose the smallest evidence for the question and state the return bound.",
    "curriculumId": "PS-012",
    "contentType": "core",
    "requires": [
      "PS-011"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-013",
    "file": "/notes/pyspark-013.md",
    "title": "PS-013 — Select and name expressions",
    "blurb": "Northstar’s source stores quantity and unit price, while a downstream report needs line ID, normalized status, and gross USD.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 4,
    "minutes": 40,
    "standfirst": "Northstar’s source stores quantity and unit price, while a downstream report needs line ID, normalized status, and gross USD. PySpark does not ask you to loop through local records. You compose Column expressions, give derived fields stable names, and project only the fields the next step needs. Your destination is a projection whose code, schema, and plan all tell the same story.",
    "curriculumId": "PS-013",
    "contentType": "core",
    "requires": [
      "PS-010",
      "PS-012"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-014",
    "file": "/notes/pyspark-014.md",
    "title": "PS-014 — Filter, sort, limit, and null-aware conditions",
    "blurb": "Northstar wants review candidates whose status is paid or pending and whose unit price lies from $10 through $50.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 5,
    "minutes": 40,
    "standfirst": "Northstar wants review candidates whose status is paid or pending and whose unit price lies from $10 through $50. Two records contain missing values. A filter does not treat missing as ordinary false data; Spark SQL evaluates boolean expressions with TRUE, FALSE, and NULL (unknown), and only TRUE survives. Your destination is a condition whose treatment of missingness and output order is visible in code.",
    "curriculumId": "PS-014",
    "contentType": "core",
    "requires": [
      "PS-013"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-015",
    "file": "/notes/pyspark-015.md",
    "title": "PS-015 — Built-in functions and SQL parity",
    "blurb": "Northstar analysts write SQL while application engineers use the DataFrame API.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 2 · DataFrames, schemas, columns, and expressions",
    "phaseOrder": 2,
    "order": 6,
    "minutes": 40,
    "standfirst": "Northstar analysts write SQL while application engineers use the DataFrame API. Both need the same normalized status, calendar date, and gross USD. Spark’s built-in functions provide one expression system from two interfaces: Python constructs a logical expression tree, while SQL parses text into one.",
    "curriculumId": "PS-015",
    "contentType": "core",
    "requires": [
      "PS-013",
      "PS-014"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-016",
    "file": "/notes/pyspark-016.md",
    "title": "PS-016 — Add, replace, and derive columns",
    "blurb": "Northstar’s curated order lines need gross value, missing-price evidence, a handling fee, net value, and normalized status.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar’s curated order lines need gross value, missing-price evidence, a handling fee, net value, and normalized status. Some fields are independent; others depend on newly derived fields. PySpark returns a new DataFrame at each transformation, so the order of dependency layers matters even though no data runs yet. Your destination is a before/after contract where every output can be traced to source columns.",
    "curriculumId": "PS-016",
    "contentType": "core",
    "requires": [
      "PS-013",
      "PS-015"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-017",
    "file": "/notes/pyspark-017.md",
    "title": "PS-017 — Conditional logic and categorical rules",
    "blurb": "Northstar needs a servicerisk category on every parcel scan.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar needs a servicerisk category on every parcel scan. A heavy parcel at a delayed hub is critical; any delayed scan is watch; an on-time scan is normal. Missing delay evidence must remain unknown, not quietly become normal. In Spark, this is an expression-building problem: create one Column, let executors evaluate it, and measure whether every row matched the intended rule.",
    "curriculumId": "PS-017",
    "contentType": "core",
    "requires": [
      "PS-014",
      "PS-016"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-018",
    "file": "/notes/pyspark-018.md",
    "title": "PS-018 — Strings, dates, timestamps, intervals, and time zones",
    "blurb": "Northstar hubs send local timestamp text plus an IANA time-zone name.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar hubs send local timestamp text plus an IANA time-zone name. Comparing those strings would confuse local wall-clock time with a global instant. We will parse the text, interpret it in the row's named zone, normalize to UTC, compute transit duration, and keep invalid inputs visible. You will be able to: The fixture grain is one hub event; eventid is unique.",
    "curriculumId": "PS-018",
    "contentType": "core",
    "requires": [
      "PS-015",
      "PY-020"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-019",
    "file": "/notes/pyspark-019.md",
    "title": "PS-019 — Arrays, maps, structs, JSON, and explode",
    "blurb": "A Northstar scan contains an array of item codes, a map of device attributes, a location struct, and a raw JSON status payload.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 4,
    "minutes": 40,
    "standfirst": "A Northstar scan contains an array of item codes, a map of device attributes, a location struct, and a raw JSON status payload. Flattening everything immediately can multiply rows or erase the distinction between parcel, item, and attribute. We will keep nested values typed, parse JSON with an explicit schema, and cross the explode grain boundary deliberately.",
    "curriculumId": "PS-019",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-016"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-020",
    "file": "/notes/pyspark-020.md",
    "title": "PS-020 — Missingness, duplicates, invalid rows, and quality ledgers",
    "blurb": "Northstar receives repeated hub readings.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 5,
    "minutes": 40,
    "standfirst": "Northstar receives repeated hub readings. Some values are missing, one temperature is impossible, and a scanner retransmits an earlier observation. A bare dropna() or dropDuplicates() produces fewer rows without explaining why. We will classify every input, select a deterministic winner for duplicates, and reconcile accepted plus rejected records to the source.",
    "curriculumId": "PS-020",
    "contentType": "core",
    "requires": [
      "PS-014",
      "PS-016",
      "PY-039"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-021",
    "file": "/notes/pyspark-021.md",
    "title": "PS-021 — Group, aggregate, pivot, and approximate summaries",
    "blurb": "Northstar's operations board needs one row per hub: parcel count, measured weight, late parcels, distinct routes, and a median delay.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 6,
    "minutes": 40,
    "standfirst": "Northstar's operations board needs one row per hub: parcel count, measured weight, late parcels, distinct routes, and a median delay. Every aggregation changes grain, and several familiar functions treat nulls differently. We will predict the output population, keep units visible, and label approximate answers as approximate. You will learn to: Input grain is one parcel, keyed by parcelid.",
    "curriculumId": "PS-021",
    "contentType": "core",
    "requires": [
      "PS-015",
      "PS-020",
      "PY-041"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-022",
    "file": "/notes/pyspark-022.md",
    "title": "PS-022 — Window functions without losing rows",
    "blurb": "An aggregate can tell Northstar a hub's daily total, but it removes individual scan rows.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 7,
    "minutes": 40,
    "standfirst": "An aggregate can tell Northstar a hub's daily total, but it removes individual scan rows. Operations also needs each scan, its previous scan time, arrival position, and running parcel weight. A window computes across related rows while returning one output row per input row. You will learn to: The fixture grain is one scan event, keyed by eventid. eventts is UTC event time; weightkg is non-null.",
    "curriculumId": "PS-022",
    "contentType": "core",
    "requires": [
      "PS-018",
      "PS-021",
      "PY-041"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-02",
    "file": "/notes/pyspark-studio-02.md",
    "title": "PS-S02 — Clean the sensor grid",
    "blurb": "The City Sensor Grid receives JSON envelopes containing several measurements.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 3 · Transform structured data correctly",
    "phaseOrder": 3,
    "order": 8,
    "minutes": 90,
    "standfirst": "The City Sensor Grid receives JSON envelopes containing several measurements. A dashboard wants one hourly temperature row per station, while operations needs every rejected measurement with a reason. Your mission is to parse, normalize, quarantine, aggregate, and prove every population and grain change. You will be able to: citysensorenvelopes@1 has one row per received envelope. envelopeid is unique.",
    "curriculumId": "PS-S02",
    "contentType": "studio",
    "requires": [
      "PS-016",
      "PS-017",
      "PS-018",
      "PS-019",
      "PS-020",
      "PS-021",
      "PS-022"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-023",
    "file": "/notes/pyspark-023.md",
    "title": "PS-023 — Read CSV, JSON, text, and binary sources defensively",
    "blurb": "Northstar receives CSV manifests, JSON event records, device logs, and proof-of-delivery images.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 4 · Read, write, and organize data",
    "phaseOrder": 4,
    "order": 1,
    "minutes": 45,
    "standfirst": "Northstar receives CSV manifests, JSON event records, device logs, and proof-of-delivery images. The formats do not share a row model: CSV/JSON readers parse records, text reads one line per row, and binary files produce one row per file. We will declare schemas and options, preserve source provenance, and measure malformed data before it enters trusted tables.",
    "curriculumId": "PS-023",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-020",
      "PY-020"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-024",
    "file": "/notes/pyspark-024.md",
    "title": "PS-024 — Parquet and columnar execution",
    "blurb": "Northstar's parcel fact table has dozens of columns, but an alert needs only parcel ID, hub, and delay for H1 rows above ten minutes.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 4 · Read, write, and organize data",
    "phaseOrder": 4,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar's parcel fact table has dozens of columns, but an alert needs only parcel ID, hub, and delay for H1 rows above ten minutes. Parquet stores typed values by column with metadata that readers may use to avoid unnecessary I/O. The optimization is conditional evidence, not magic: inspect the physical plan and actual metrics for your files. You will learn to: Fixture grain is one parcel, keyed by parcelid.",
    "curriculumId": "PS-024",
    "contentType": "core",
    "requires": [
      "PS-013",
      "PS-023"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-025",
    "file": "/notes/pyspark-025.md",
    "title": "PS-025 — Partition discovery and data layout",
    "blurb": "Northstar stores Parquet under eventdate=YYYY-MM-DD/hubid=.../.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 4 · Read, write, and organize data",
    "phaseOrder": 4,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar stores Parquet under eventdate=YYYY-MM-DD/hubid=.../. When Spark reads from the table root, it can discover those directory values as columns and prune directories for matching filters. But a partitioned directory is not a Spark in-memory partition, and excessive partition cardinality creates tiny files and metadata work.",
    "curriculumId": "PS-025",
    "contentType": "core",
    "requires": [
      "PS-006",
      "PS-024"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-026",
    "file": "/notes/pyspark-026.md",
    "title": "PS-026 — Write modes, file counts, and idempotent outputs",
    "blurb": "Northstar publishes a daily parcel snapshot.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 4 · Read, write, and organize data",
    "phaseOrder": 4,
    "order": 4,
    "minutes": 45,
    "standfirst": "Northstar publishes a daily parcel snapshot. A failed orchestration step may retry the same batch. Blind append duplicates it; broad overwrite may remove unrelated days; coalesce(1) serializes output for cosmetic convenience. We will separate logical idempotency, physical file count, and storage commit behavior.",
    "curriculumId": "PS-026",
    "contentType": "core",
    "requires": [
      "PS-025"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-027",
    "file": "/notes/pyspark-027.md",
    "title": "PS-027 — Views, catalogs, namespaces, and managed/external tables",
    "blurb": "Northstar analysts want to query a verified DataFrame from SQL, then publish a durable table.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 4 · Read, write, and organize data",
    "phaseOrder": 4,
    "order": 5,
    "minutes": 40,
    "standfirst": "Northstar analysts want to query a verified DataFrame from SQL, then publish a durable table. A temporary view is session-scoped logical state; a catalog table has a persistent identifier and metadata; a storage location contains files. Treating those as interchangeable leads to missing views, orphaned files, or destructive drops.",
    "curriculumId": "PS-027",
    "contentType": "core",
    "requires": [
      "PS-015",
      "PS-026"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-028",
    "file": "/notes/pyspark-028.md",
    "title": "PS-028 — Join types and explicit key contracts",
    "blurb": "Northstar parcel facts carry hubid; a hub directory supplies region and capacity.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar parcel facts carry hubid; a hub directory supplies region and capacity. An inner join answers “matched parcels,” while a left join answers “all parcels plus available hub data.” We will declare the relationship, use aliases and explicit conditions, and make unmatched rows a first-class result. You will learn to: Parcel grain is one parcel, keyed by parcelid.",
    "curriculumId": "PS-028",
    "contentType": "core",
    "requires": [
      "PS-013",
      "PS-014",
      "PY-042"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-029",
    "file": "/notes/pyspark-029.md",
    "title": "PS-029 — Join cardinality, uniqueness, and row-count bounds",
    "blurb": "Northstar expects many parcels to one hub.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar expects many parcels to one hub. A dimension accidentally contains two current H1 rows. A left join still runs, but every H1 parcel appears twice and totals inflate. Spark's DataFrame join does not provide pandas-style relationship validation, so we will build explicit preconditions and post-join bounds.",
    "curriculumId": "PS-029",
    "contentType": "core",
    "requires": [
      "PS-020",
      "PS-028",
      "PY-042"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-030",
    "file": "/notes/pyspark-030.md",
    "title": "PS-030 — What a shuffle actually moves",
    "blurb": "Northstar parcels for H1 are scattered across input partitions.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar parcels for H1 are scattered across input partitions. To compute one total per hub, Spark must bring all H1 contributions to a compatible downstream partition. That redistribution is a shuffle. It is not “Spark moving the entire DataFrame to the driver”; executors write partitioned shuffle blocks and downstream tasks fetch the blocks they need.",
    "curriculumId": "PS-030",
    "contentType": "core",
    "requires": [
      "PS-006",
      "PS-021",
      "PS-028"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-031",
    "file": "/notes/pyspark-031.md",
    "title": "PS-031 — Physical join strategies",
    "blurb": "The logical requirement is a left join from parcels to a unique hub dimension.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 4,
    "minutes": 40,
    "standfirst": "The logical requirement is a left join from parcels to a unique hub dimension. Spark can implement compatible joins using different physical strategies based on join type, equi-keys, statistics, configuration, and runtime observations. A strategy change must preserve the defined result population.",
    "curriculumId": "PS-031",
    "contentType": "core",
    "requires": [
      "PS-029",
      "PS-030"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-032",
    "file": "/notes/pyspark-032.md",
    "title": "PS-032 — Detect and repair skew",
    "blurb": "Northstar normalizes missing hub IDs to UNKNOWN.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 5,
    "minutes": 40,
    "standfirst": "Northstar normalizes missing hub IDs to UNKNOWN. Forty percent of parcel rows now share that key. Hash partitioning sends equal keys together, so one join/shuffle task receives far more data and finishes long after its peers. More partitions do not split a single equal key by themselves. You will learn to: The simulation has 100 rows across four downstream partitions.",
    "curriculumId": "PS-032",
    "contentType": "core",
    "requires": [
      "PS-030",
      "PS-031"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-033",
    "file": "/notes/pyspark-033.md",
    "title": "PS-033 — Union, schema alignment, and evolving records",
    "blurb": "Northstar's old scanner exports (parcelid, hubid, tempc).",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 6,
    "minutes": 40,
    "standfirst": "Northstar's old scanner exports (parcelid, hubid, tempc). A new scanner orders fields differently and adds batterypct. A positional union can place hub IDs under temperature or fail on incompatible types. We will normalize each source to a canonical contract, use name-based alignment, and keep schema-version provenance. You will learn to: Both inputs have one scan-event row per unique eventid.",
    "curriculumId": "PS-033",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-020"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-03",
    "file": "/notes/pyspark-studio-03.md",
    "title": "PS-S03 — Reconcile Northstar",
    "blurb": "Northstar Commerce receives one Parquet folder per order date.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 5 · Joins, unions, shuffles, and skew",
    "phaseOrder": 5,
    "order": 7,
    "minutes": 90,
    "standfirst": "Northstar Commerce receives one Parquet folder per order date. Tuesday added couponcode; a guest checkout key became hot; and a duplicated product record threatens to multiply revenue. Finance needs a rerunnable customer-day table, while platform engineers need evidence that the join and shuffle plan is understood. Your result must be correct before it is fast.",
    "curriculumId": "PS-S03",
    "contentType": "studio",
    "requires": [
      "PS-023",
      "PS-024",
      "PS-025",
      "PS-026",
      "PS-028",
      "PS-029",
      "PS-030",
      "PS-031",
      "PS-032",
      "PS-033"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-034",
    "file": "/notes/pyspark-034.md",
    "title": "PS-034 — Parsed, analyzed, optimized, and physical plans",
    "blurb": "Northstar needs the value of paid orders above $50.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar needs the value of paid orders above $50. The DataFrame expression is short, but a cluster cannot execute Python syntax directly. Spark first represents the request, resolves names and types, rewrites equivalent expressions, and then chooses executable operators. Reading those layers lets you separate a spelling problem from a data problem or an execution problem.",
    "curriculumId": "PS-034",
    "contentType": "core",
    "requires": [
      "PS-007",
      "PS-015"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-035",
    "file": "/notes/pyspark-035.md",
    "title": "PS-035 — Read explain, statistics, and runtime metrics",
    "blurb": "Northstar's west-region revenue report is late.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 2,
    "minutes": 40,
    "standfirst": "Northstar's west-region revenue report is late. One engineer sees a short physical plan and calls it efficient; another sees a large shuffle metric and calls it expensive. Both are looking at evidence, but at different moments. A useful diagnosis separates the intended operators, optimizer estimates, and measurements from the completed action.",
    "curriculumId": "PS-035",
    "contentType": "core",
    "requires": [
      "PS-009",
      "PS-034"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-036",
    "file": "/notes/pyspark-036.md",
    "title": "PS-036 — Narrow vs wide operations and stage boundaries",
    "blurb": "Northstar receives delivery events in four partitions.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar receives delivery events in four partitions. Normalizing a status can be done independently for every row. Computing one count per depot cannot: all rows for the same depot must meet. That difference—not whether code looks short—explains much of Spark's stage structure.",
    "curriculumId": "PS-036",
    "contentType": "core",
    "requires": [
      "PS-006",
      "PS-030",
      "PS-034"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-037",
    "file": "/notes/pyspark-037.md",
    "title": "PS-037 — Partition sizing, repartition, coalesce, and rebalance",
    "blurb": "“More” is not a defensible answer. Northstar has 12 million delivery rows: 180 input files produce many short tasks, while one forced output partition leaves most executors idle.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 4,
    "minutes": 40,
    "standfirst": "“More” is not a defensible answer. Northstar has 12 million delivery rows: 180 input files produce many short tasks, while one forced output partition leaves most executors idle. Partition count must serve a particular boundary—scan, shuffle, join, or write—and be judged using bytes and task distributions.",
    "curriculumId": "PS-037",
    "contentType": "core",
    "requires": [
      "PS-025",
      "PS-030",
      "PS-036"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-038",
    "file": "/notes/pyspark-038.md",
    "title": "PS-038 — Cache, persist, unpersist, and checkpoint by evidence",
    "blurb": "Two dashboards reuse an expensive parse-and-quality pipeline.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 5,
    "minutes": 45,
    "standfirst": "Two dashboards reuse an expensive parse-and-quality pipeline. Caching might avoid recomputation—or add serialization, memory pressure, and an extra materialization pass for data used once. “Cache because it is expensive” is incomplete; reuse, storage capacity, lineage cost, and measured actions determine the answer.",
    "curriculumId": "PS-038",
    "contentType": "core",
    "requires": [
      "PS-008",
      "PS-035",
      "PS-036"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-039",
    "file": "/notes/pyspark-039.md",
    "title": "PS-039 — Adaptive Query Execution and statistics",
    "blurb": "Northstar joins orders to a region table.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 6,
    "minutes": 40,
    "standfirst": "Northstar joins orders to a region table. Before the action, the optimizer has incomplete size estimates and plans a shuffle-heavy join. After shuffle stages produce real statistics, the region side is far smaller than expected. Adaptive Query Execution (AQE) can revise eligible downstream choices while the query is running.",
    "curriculumId": "PS-039",
    "contentType": "core",
    "requires": [
      "PS-031",
      "PS-032",
      "PS-035"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-040",
    "file": "/notes/pyspark-040.md",
    "title": "PS-040 — A systematic performance diagnosis",
    "blurb": "Northstar's depot summary sometimes takes four minutes and sometimes twelve.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 7,
    "minutes": 45,
    "standfirst": "Northstar's depot summary sometimes takes four minutes and sometimes twelve. The team has proposed more executors, caching, 2,000 shuffle partitions, and a Python rewrite—all before defining the action or locating the slow stage. A diagnosis should make one falsifiable claim at a time.",
    "curriculumId": "PS-040",
    "contentType": "core",
    "requires": [
      "PS-035",
      "PS-036",
      "PS-037",
      "PS-038",
      "PS-039"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-04",
    "file": "/notes/pyspark-studio-04.md",
    "title": "PS-S04 — Make it faster without guessing",
    "blurb": "Northstar's daily risk summary is correct but misses its service window.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 6 · Plans, partitions, memory, and performance",
    "phaseOrder": 6,
    "order": 8,
    "minutes": 90,
    "standfirst": "Northstar's daily risk summary is correct but misses its service window. One teammate wants more executors, another wants caching, and another wants a broadcast hint. Your mission is to replace guesses with a baseline, two hypotheses, one-factor experiments, and an evidence-backed recommendation. You are not trying to produce the fastest number on a laptop.",
    "curriculumId": "PS-S04",
    "contentType": "studio",
    "requires": [
      "PS-034",
      "PS-035",
      "PS-036",
      "PS-037",
      "PS-038",
      "PS-039",
      "PS-040"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-041",
    "file": "/notes/pyspark-041.md",
    "title": "PS-041 — Prefer Spark expressions before Python UDFs",
    "blurb": "Northstar labels a delivery late, watch, or ontime from nullable delay minutes.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 7 · The Python boundary, UDFs, and pandas interop",
    "phaseOrder": 7,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar labels a delivery late, watch, or ontime from nullable delay minutes. A Python function feels familiar, but Spark already has typed conditional expressions. Keeping the rule in Spark's expression tree preserves SQL parity, optimizer visibility, null behavior, and cross-language execution.",
    "curriculumId": "PS-041",
    "contentType": "core",
    "requires": [
      "PS-015",
      "PS-034"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-042",
    "file": "/notes/pyspark-042.md",
    "title": "PS-042 — Python UDF execution and contracts",
    "blurb": "Northstar has a vetted Python library that converts an opaque route token into a one-character canonical zone code.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 7 · The Python boundary, UDFs, and pandas interop",
    "phaseOrder": 7,
    "order": 2,
    "minutes": 45,
    "standfirst": "Northstar has a vetted Python library that converts an opaque route token into a one-character canonical zone code. No Spark built-in implements the algorithm. A UDF is warranted—but the Python body is only one part of its contract. Spark must ship inputs to Python workers, receive values matching a declared Spark SQL type, and surface errors across task boundaries.",
    "curriculumId": "PS-042",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-041",
      "PY-015"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-043",
    "file": "/notes/pyspark-043.md",
    "title": "PS-043 — Pandas UDFs, Arrow UDFs, and batch-shaped thinking",
    "blurb": "Northstar's approved scoring formula uses pandas vector operations.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 7 · The Python boundary, UDFs, and pandas interop",
    "phaseOrder": 7,
    "order": 3,
    "minutes": 45,
    "standfirst": "Northstar's approved scoring formula uses pandas vector operations. A pandas UDF transfers column batches with Arrow and invokes Python on pandas.Series objects. That reduces per-row conversion overhead, but it creates a new contract: every output batch must match the declared type and, for scalar forms, preserve input length.",
    "curriculumId": "PS-043",
    "contentType": "core",
    "requires": [
      "PS-042",
      "PY-030",
      "PY-043"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-044",
    "file": "/notes/pyspark-044.md",
    "title": "PS-044 — mapInPandas, grouped pandas operations, and escape hatches",
    "blurb": "Northstar stores a route as an encoded string such as A>B>C.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 7 · The Python boundary, UDFs, and pandas interop",
    "phaseOrder": 7,
    "order": 4,
    "minutes": 45,
    "standfirst": "Northstar stores a route as an encoded string such as A>B>C. A scalar pandas UDF must return one value for each input row, so it cannot honestly represent “one row becomes many route legs.” mapInPandas is a DataFrame-level escape hatch: it receives an iterator of pandas batches and may emit a different number of rows under an explicit output schema.",
    "curriculumId": "PS-044",
    "contentType": "core",
    "requires": [
      "PS-021",
      "PS-043"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-045",
    "file": "/notes/pyspark-045.md",
    "title": "PS-045 — Closures, broadcasts, accumulators, and serialization traps",
    "blurb": "A Northstar UDF closes over a 40 MB zone dictionary.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 7 · The Python boundary, UDFs, and pandas interop",
    "phaseOrder": 7,
    "order": 5,
    "minutes": 45,
    "standfirst": "A Northstar UDF closes over a 40 MB zone dictionary. The driver can access it, so the code looks valid; Spark must nevertheless serialize the closure and make the referenced data available to tasks. A second bug increments a driver list inside the UDF and expects a final count. Executors do not share that Python object.",
    "curriculumId": "PS-045",
    "contentType": "core",
    "requires": [
      "PS-005",
      "PS-006",
      "PS-042"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-046",
    "file": "/notes/pyspark-046.md",
    "title": "PS-046 — Pure DataFrame transformation functions",
    "blurb": "Northstar's order cleaner depends on a global spark, changes session configuration, calls count() for logging, and writes a table midway through the function.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 1,
    "minutes": 40,
    "standfirst": "Northstar's order cleaner depends on a global spark, changes session configuration, calls count() for logging, and writes a table midway through the function. It is difficult to test and triggers hidden jobs. A reliable transformation should describe a lazy DataFrame-to-DataFrame plan; orchestration owns reads, actions, logging, and writes.",
    "curriculumId": "PS-046",
    "contentType": "core",
    "requires": [
      "PS-016",
      "PS-020",
      "PY-024"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-047",
    "file": "/notes/pyspark-047.md",
    "title": "PS-047 — Unit-test data, schema, and behavior",
    "blurb": "A Northstar test checks three displayed rows and passes.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 2,
    "minutes": 45,
    "standfirst": "A Northstar test checks three displayed rows and passes. Production later fails because netusd changed from non-null decimal to nullable double and duplicate order IDs slipped through. Values, schema, grain/key behavior, null semantics, and ordering policy are separate assertions.",
    "curriculumId": "PS-047",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-046",
      "PY-024"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-048",
    "file": "/notes/pyspark-048.md",
    "title": "PS-048 — Data quality checks and quarantine patterns",
    "blurb": "Northstar's daily orders contain missing IDs, duplicate IDs, negative amounts, and an unsupported currency.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 3,
    "minutes": 40,
    "standfirst": "Northstar's daily orders contain missing IDs, duplicate IDs, negative amounts, and an unsupported currency. Dropping them hides operational risk; failing the whole job discards usable rows. A quarantine pattern annotates every input row, routes blocking failures aside, and produces a ledger that reconciles to the input.",
    "curriculumId": "PS-048",
    "contentType": "core",
    "requires": [
      "PS-020",
      "PS-047"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-049",
    "file": "/notes/pyspark-049.md",
    "title": "PS-049 — Exceptions, structured logging, and job diagnostics",
    "blurb": "A Northstar cleaner reports “Py4J error” and retries.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 4,
    "minutes": 45,
    "standfirst": "A Northstar cleaner reports “Py4J error” and retries. The message omits the run ID and data snapshot; executor logs contain a Python stack trace; the output path may already contain partial files. Useful diagnostics connect application context to Spark's structured error condition and the exact job/stage/task—without leaking customer rows or credentials.",
    "curriculumId": "PS-049",
    "contentType": "core",
    "requires": [
      "PS-009",
      "PS-047",
      "PS-048"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-050",
    "file": "/notes/pyspark-050.md",
    "title": "PS-050 — Package, configure, and submit a PySpark application",
    "blurb": "Northstar's cleaner works in one notebook because its session, package imports, paths, and configuration already exist.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 5,
    "minutes": 45,
    "standfirst": "Northstar's cleaner works in one notebook because its session, package imports, paths, and configuration already exist. A production application needs an explicit entry point, packaged modules, versioned dependencies, validated arguments, launch-time Spark settings, and observable exit behavior. Otherwise “the same code” is not the same runtime.",
    "curriculumId": "PS-050",
    "contentType": "core",
    "requires": [
      "PS-003",
      "PS-046",
      "PS-049",
      "PY-022"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-05",
    "file": "/notes/pyspark-studio-05.md",
    "title": "PS-S05 — Productionize the cleaner",
    "blurb": "The City Sensor cleaner works in one notebook because the session, paths, imports, and temporary variables already exist.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 8 · Reliable PySpark transformations and applications",
    "phaseOrder": 8,
    "order": 6,
    "minutes": 90,
    "standfirst": "The City Sensor cleaner works in one notebook because the session, paths, imports, and temporary variables already exist. Operations needs a repeatable application: declared inputs and outputs, pure DataFrame transformations, behavior tests, external configuration, structured evidence, and an entry point that runs from a clean environment. Your output is a small deployable package, not a longer notebook.",
    "curriculumId": "PS-S05",
    "contentType": "studio",
    "requires": [
      "PS-046",
      "PS-047",
      "PS-048",
      "PS-049",
      "PS-050"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-051",
    "file": "/notes/pyspark-051.md",
    "title": "PS-051 — A stream as an unbounded table",
    "blurb": "Rajasthan Mobility receives vehicle pings throughout the day.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 1,
    "minutes": 45,
    "standfirst": "Rajasthan Mobility receives vehicle pings throughout the day. Operations wants every valid ping above 80 km/h routed to review. A batch job can answer “which pings are fast in this file?” A streaming query must keep answering as new files or messages arrive, survive periods with no data, and deliver results through a declared sink. By the end, you can: The fixture grain is one row per vehicle ping.",
    "curriculumId": "PS-051",
    "contentType": "core",
    "requires": [
      "PS-007",
      "PS-010",
      "PS-018"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-052",
    "file": "/notes/pyspark-052.md",
    "title": "PS-052 — Sources, sinks, triggers, and output modes",
    "blurb": "The mobility desk wants live counts per route.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 2,
    "minutes": 45,
    "standfirst": "The mobility desk wants live counts per route. “Write the stream every minute” leaves four unanswered questions: where new rows come from, which table changes are emitted, when Spark attempts work, and what the destination can accept. A query is safe only when those choices agree. You will be able to: The fixture remains one row per vehicle ping keyed by eventid, with UTC eventtime.",
    "curriculumId": "PS-052",
    "contentType": "core",
    "requires": [
      "PS-026",
      "PS-051"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-053",
    "file": "/notes/pyspark-053.md",
    "title": "PS-053 — Processing time, event time, windows, and late data",
    "blurb": "A network outage delays a bus ping from 08:04 until 08:17.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 3,
    "minutes": 45,
    "standfirst": "A network outage delays a bus ping from 08:04 until 08:17. An operations report asks, “How many pings occurred in each ten-minute travel window?” Grouping by arrival or processing time would put that ping in the wrong window. Streaming correctness starts by naming clocks.",
    "curriculumId": "PS-053",
    "contentType": "core",
    "requires": [
      "PS-018",
      "PS-022",
      "PS-051"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-054",
    "file": "/notes/pyspark-054.md",
    "title": "PS-054 — Watermarks and bounded state",
    "blurb": "Keeping every route-window count forever accommodates any delay but grows state without a bound.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 4,
    "minutes": 45,
    "standfirst": "Keeping every route-window count forever accommodates any delay but grows state without a bound. Closing a window immediately produces small state and wrong late-arrival behavior. A watermark expresses the operational compromise using event-time progress. By the end, you can: The fixture is one row per UTC vehicle ping, keyed by eventid. The query counts pings per (routeid, ten-minute window).",
    "curriculumId": "PS-054",
    "contentType": "core",
    "requires": [
      "PS-052",
      "PS-053"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-055",
    "file": "/notes/pyspark-055.md",
    "title": "PS-055 — Deduplication and stream joins",
    "blurb": "Mobile gateways retry when acknowledgements are delayed, so one vehicle ping can arrive twice.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 5,
    "minutes": 45,
    "standfirst": "Mobile gateways retry when acknowledgements are delayed, so one vehicle ping can arrive twice. A second live feed contains safety alerts that should match pings for the same vehicle within five minutes. Global deduplication and unconstrained stream-stream joins can both retain keys indefinitely. Correctness needs a key, event-time limits, and an explicit policy for conflicting payloads.",
    "curriculumId": "PS-055",
    "contentType": "core",
    "requires": [
      "PS-028",
      "PS-029",
      "PS-054"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-056",
    "file": "/notes/pyspark-056.md",
    "title": "PS-056 — Checkpoints, restart, idempotency, and monitoring",
    "blurb": "A route-alert query commits batch 17, begins batch 18, and the driver dies during the external write.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 6,
    "minutes": 45,
    "standfirst": "A route-alert query commits batch 17, begins batch 18, and the driver dies during the external write. Restarting “from the latest file” can skip or duplicate incidents. Recovery requires a stable checkpoint and a sink operation that can recognize a replay. You will be able to: The fixture contains immutable ping files and route alerts keyed by eventid. Each input file is closed before discovery.",
    "curriculumId": "PS-056",
    "contentType": "core",
    "requires": [
      "PS-026",
      "PS-049",
      "PS-052",
      "PS-054",
      "PS-055"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-06",
    "file": "/notes/pyspark-studio-06.md",
    "title": "PS-S06 — Mobility late-arrival lab",
    "blurb": "Rajasthan Mobility receives vehicle events out of order.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 9 · Structured Streaming and event time",
    "phaseOrder": 9,
    "order": 7,
    "minutes": 90,
    "standfirst": "Rajasthan Mobility receives vehicle events out of order. Network retries duplicate an event; one record arrives after the chosen lateness policy; and the query process restarts between file batches. Operations wants ten-minute route counts with an explicit statement of what is accepted, dropped, emitted, and recoverable.",
    "curriculumId": "PS-S06",
    "contentType": "studio",
    "requires": [
      "PS-051",
      "PS-052",
      "PS-053",
      "PS-054",
      "PS-055",
      "PS-056"
    ],
    "runtimeTier": "streaming",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-057",
    "file": "/notes/pyspark-057.md",
    "title": "PS-057 — Feature columns, vectors, transformers, and estimators",
    "blurb": "A support team predicts escalation from ticket channel, waiting time, and message length.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 1,
    "minutes": 45,
    "standfirst": "A support team predicts escalation from ticket channel, waiting time, and message length. Spark ML estimators expect a label column and usually one vector-valued feature column—not a Python list assembled on the driver. Category vocabulary and model coefficients are learned state that must travel together. You will be able to: The fixture grain is one row per ticket at triage time.",
    "curriculumId": "PS-057",
    "contentType": "core",
    "requires": [
      "PS-011",
      "PS-016",
      "PY-049"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-058",
    "file": "/notes/pyspark-058.md",
    "title": "PS-058 — Leakage-safe pipeline fitting and evaluation",
    "blurb": "Experiment Bench adds a test cohort containing a channel absent from training.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 2,
    "minutes": 45,
    "standfirst": "Experiment Bench adds a test cohort containing a channel absent from training. If category indexing, imputation, scaling, or feature selection sees all rows before the split, the test population influences the representation. A high metric can then describe the workflow's access to held-out information rather than expected performance.",
    "curriculumId": "PS-058",
    "contentType": "core",
    "requires": [
      "PS-057",
      "PY-050"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-059",
    "file": "/notes/pyspark-059.md",
    "title": "PS-059 — Cross-validation, train-validation split, and tuning cost",
    "blurb": "Experiment Bench proposes two regularization values, two elastic-net values, and three folds.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 3,
    "minutes": 45,
    "standfirst": "Experiment Bench proposes two regularization values, two elastic-net values, and three folds. That small-looking grid requests 12 candidate-fold fits, then a final refit of the selected configuration on all tuning input. A pipeline with learned feature stages repeats their fit work inside those boundaries too. You will be able to: The fixture grain is one ticket at triage time.",
    "curriculumId": "PS-059",
    "contentType": "core",
    "requires": [
      "PS-058",
      "PY-051"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-060",
    "file": "/notes/pyspark-060.md",
    "title": "PS-060 — Algorithm scale, sparsity, imbalance, and interpretation",
    "blurb": "Experiment Bench now has many one-hot categories and only 10% escalations.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 4,
    "minutes": 45,
    "standfirst": "Experiment Bench now has many one-hot categories and only 10% escalations. “Use a bigger Spark model” is not a decision rule. Vector density, label prevalence, iteration/shuffle shape, inference contract, and explanation needs can matter more than row count alone. You will be able to: The fixture grain remains one ticket at triage time. Key ticketid is excluded from features.",
    "curriculumId": "PS-060",
    "contentType": "core",
    "requires": [
      "PS-057",
      "PS-058"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-061",
    "file": "/notes/pyspark-061.md",
    "title": "PS-061 — Persist models and replay the feature contract",
    "blurb": "Saving only logistic-regression coefficients loses channel indexing, one-hot width, feature order, and invalid-category policy.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 5,
    "minutes": 45,
    "standfirst": "Saving only logistic-regression coefficients loses channel indexing, one-hot width, feature order, and invalid-category policy. Saving a full pipeline helps, but it still lacks the surrounding raw schema, label definition, dependency versions, data lineage, metrics, and replay test needed to promote safely.",
    "curriculumId": "PS-061",
    "contentType": "core",
    "requires": [
      "PS-050",
      "PS-058",
      "PY-052"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-07",
    "file": "/notes/pyspark-studio-07.md",
    "title": "PS-S07 — Experiment Bench at scale",
    "blurb": "Northstar wants to predict whether a support case will breach its SLA.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 10 · Distributed ML and analytical workflows",
    "phaseOrder": 10,
    "order": 6,
    "minutes": 90,
    "standfirst": "Northstar wants to predict whether a support case will breach its SLA. A notebook achieved a promising score, but preprocessing was fitted before the holdout split, the tuning budget is unknown, and only the classifier was saved. Your mission is to build a leakage-safe baseline, bound the search, save the complete fitted pipeline, and reproduce predictions from raw rows.",
    "curriculumId": "PS-S07",
    "contentType": "studio",
    "requires": [
      "PS-057",
      "PS-058",
      "PS-059",
      "PS-060",
      "PS-061"
    ],
    "runtimeTier": "mllib",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-062",
    "file": "/notes/pyspark-062.md",
    "title": "PS-062 — Deployment modes and cluster managers",
    "blurb": "Support Signal's cleaner works in local mode.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 1,
    "minutes": 40,
    "standfirst": "Support Signal's cleaner works in local mode. Production requires a cluster manager to allocate executors, but “run on Kubernetes” still does not tell us where the driver runs, whether the submit machine may disconnect, or where logs and dependencies live. Driver placement changes failure, networking, and operations. You will be able to: The workload reads one row per support event and writes one row per ticket-day.",
    "curriculumId": "PS-062",
    "contentType": "core",
    "requires": [
      "PS-005",
      "PS-050"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-063",
    "file": "/notes/pyspark-063.md",
    "title": "PS-063 — Resources, concurrency, and dynamic allocation",
    "blurb": "Support Signal asks for six executors with four cores and 8 GiB heap each.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 2,
    "minutes": 40,
    "standfirst": "Support Signal asks for six executors with four cores and 8 GiB heap each. The largest stage has 18 partitions. Calling that “24-way parallel” ignores task CPU requirements, executor availability, stragglers, memory overhead, and the fact that a stage cannot use more simultaneous tasks than it has runnable partitions.",
    "curriculumId": "PS-063",
    "contentType": "core",
    "requires": [
      "PS-006",
      "PS-037",
      "PS-040",
      "PS-062"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-064",
    "file": "/notes/pyspark-064.md",
    "title": "PS-064 — Data locality, object storage, dependencies, and configuration",
    "blurb": "Support Signal's driver can import a ticket parser installed on the gateway, but executor Python workers cannot.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 3,
    "minutes": 40,
    "standfirst": "Support Signal's driver can import a ticket parser installed on the gateway, but executor Python workers cannot. Meanwhile, input moved from node-adjacent distributed storage to a remote object store, so adding executors does not create local copies or remove request/listing costs. Production success requires two journeys: data blocks/objects to tasks, and code/config/identity to every process.",
    "curriculumId": "PS-064",
    "contentType": "core",
    "requires": [
      "PS-023",
      "PS-050",
      "PS-062"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-065",
    "file": "/notes/pyspark-065.md",
    "title": "PS-065 — Security and multi-user boundaries",
    "blurb": "Build an accurate mental model of security and multi-user boundaries with traced PySpark evidence and a practical production boundary.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 4,
    "minutes": 40,
    "standfirst": "A Spark job is general-purpose code. If it runs beside credentials, host mounts, internal network routes, or another user's files, a learner can access those resources regardless of a Python syntax filter. Spark authentication and UI ACLs protect important channels, but they do not turn arbitrary user code into a sandbox.",
    "curriculumId": "PS-065",
    "contentType": "core",
    "requires": [
      "PS-049",
      "PS-062",
      "PS-064"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-066",
    "file": "/notes/pyspark-066.md",
    "title": "PS-066 — Observability, cost, upgrades, and Spark Connect applications",
    "blurb": "A correct one-off result is still unready without backlog alerts, cost attribution, bounded driver results, client/server compatibility, and rollback replay.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 5,
    "minutes": 40,
    "standfirst": "A correct one-off result is still unready without backlog alerts, cost attribution, bounded driver results, client/server compatibility, and rollback replay. Support Signal joins those release facts under one run identity. You will be able to: The fixture represents a daily batch plus bounded streaming extension at key (ticketid, servicedate UTC).",
    "curriculumId": "PS-066",
    "contentType": "core",
    "requires": [
      "PS-009",
      "PS-040",
      "PS-062",
      "PS-063",
      "PS-065"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "lesson"
  },
  {
    "id": "pyspark-studio-08",
    "file": "/notes/pyspark-studio-08.md",
    "title": "PS-S08 — Support Signal capstone",
    "blurb": "Support leadership needs a daily supportcasesignal table for staffing and SLA triage.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Phase 11 · Deploy, secure, observe, and evolve Spark",
    "phaseOrder": 11,
    "order": 6,
    "minutes": 90,
    "standfirst": "Support leadership needs a daily supportcasesignal table for staffing and SLA triage. Raw cases arrive as partitioned files; customer/account dimensions evolve; duplicate ticket events and malformed timestamps occur; and a later streaming extension should refresh open-case counts. Your job is to design, implement, test, tune, package, and hand over a pipeline whose assumptions are visible.",
    "curriculumId": "PS-S08",
    "contentType": "studio",
    "requires": [
      "PS-S05",
      "PS-S06",
      "PS-S07",
      "PS-062",
      "PS-063",
      "PS-064",
      "PS-065",
      "PS-066"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "studio"
  },
  {
    "id": "pyspark-bridge-rdds",
    "file": "/notes/pyspark-bridge-rdds.md",
    "title": "PS-B01 — RDDs for architecture and legacy code",
    "blurb": "RDDs expose Spark's partitioned collection and lineage model directly.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 1,
    "minutes": 45,
    "standfirst": "RDDs expose Spark's partitioned collection and lineage model directly. They help you read legacy pipelines, understand architectural foundations, and handle rare workloads that do not fit the structured engine. They are not the recommended default for tabular business transformations: DataFrames carry schema and expressions that Spark SQL can analyze and optimize.",
    "curriculumId": "PS-B01",
    "contentType": "bridge",
    "requires": [
      "PS-008",
      "PS-030"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-bridge-pandas-api",
    "file": "/notes/pyspark-bridge-pandas-api.md",
    "title": "PS-B02 — pandas API on Spark",
    "blurb": "The pandas API on Spark offers a pandas-like surface backed by Spark execution.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 2,
    "minutes": 45,
    "standfirst": "The pandas API on Spark offers a pandas-like surface backed by Spark execution. It can reduce migration effort for compatible analytical code, but it does not turn a distributed DataFrame into an eager in-memory object. Plans remain lazy, data remains partitioned, shuffles remain costly, and index operations gain distributed consequences.",
    "curriculumId": "PS-B02",
    "contentType": "bridge",
    "requires": [
      "PS-040",
      "PS-043",
      "PY-043"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-bridge-lakehouse",
    "file": "/notes/pyspark-bridge-lakehouse.md",
    "title": "PS-B03 — Lakehouse table formats",
    "blurb": "Build an accurate mental model of lakehouse table formats with traced PySpark evidence and a practical production boundary.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 3,
    "minutes": 45,
    "standfirst": "Parquet defines columnar files. A lakehouse table format adds metadata and commit protocols that can describe a table snapshot across many files, track evolution, and support operations such as atomic commits or row-level changes according to that format's implementation and catalog/storage configuration.",
    "curriculumId": "PS-B03",
    "contentType": "bridge",
    "requires": [
      "PS-026",
      "PS-027",
      "PS-048"
    ],
    "runtimeTier": "full-spark",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-bridge-connect",
    "file": "/notes/pyspark-bridge-connect.md",
    "title": "PS-B04 — Spark Connect application engineering",
    "blurb": "Spark Connect separates the client from the Spark driver.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 4,
    "minutes": 45,
    "standfirst": "Spark Connect separates the client from the Spark driver. The Python client builds an unresolved logical plan and sends it through the Connect protocol; the server analyzes and executes it. Results and errors return across the connection. This makes thin applications and remote development possible, but introduces compatibility, network, session, artifact, cancellation, and multi-user boundaries.",
    "curriculumId": "PS-B04",
    "contentType": "bridge",
    "requires": [
      "PS-003",
      "PS-050",
      "PS-065",
      "PS-066"
    ],
    "runtimeTier": "connect",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-bridge-declarative",
    "file": "/notes/pyspark-bridge-declarative.md",
    "title": "PS-B05 — Declarative pipelines and orchestration",
    "blurb": "Apache Spark 4.2 includes Spark Declarative Pipelines (SDP).",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 5,
    "minutes": 45,
    "standfirst": "Apache Spark 4.2 includes Spark Declarative Pipelines (SDP). An SDP project declares datasets and flows; SDP analyzes their dependencies and orchestrates order and parallelism within a pipeline run, while Spark builds and executes the underlying batch and streaming plans. An external scheduler may still decide when to launch that pipeline, coordinate cross-system readiness, enforce quotas, and promote outputs.",
    "curriculumId": "PS-B05",
    "contentType": "bridge",
    "requires": [
      "PS-050",
      "PS-056",
      "PS-066"
    ],
    "runtimeTier": "simulated",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "pyspark-bridge-interview",
    "file": "/notes/pyspark-bridge-interview.md",
    "title": "PS-B06 — Interview and system-design practice",
    "blurb": "Strong PySpark interviews test whether you can turn an ambiguous workload into explicit assumptions, predict distributed behavior, protect correctness, and choose evidence.",
    "domain": "Programming",
    "section": "PySpark · Distributed Data with Python",
    "phase": "Optional bridges",
    "phaseOrder": 12,
    "order": 6,
    "minutes": 45,
    "standfirst": "Strong PySpark interviews test whether you can turn an ambiguous workload into explicit assumptions, predict distributed behavior, protect correctness, and choose evidence. The exact function name matters less than a traceable chain: contract → plan → data movement → risk → measurement → decision. Use this bridge as deliberate practice.",
    "curriculumId": "PS-B06",
    "contentType": "bridge",
    "requires": [
      "PS-S08"
    ],
    "runtimeTier": "static",
    "sparkVersion": "4.2.x",
    "status": "published",
    "primarySubject": "subject-pyspark",
    "catalogType": "bridge"
  },
  {
    "id": "c-m00-l01",
    "file": "/notes/c-m00-l01.md",
    "title": "What does a program do?",
    "blurb": "Trace data through a receipt and a temperature conversion.",
    "standfirst": "How do input data, instructions, state and output work together?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 00 · Computers and programs, from zero",
    "phaseOrder": 1,
    "order": 1,
    "minutes": 20,
    "curriculumId": "C-00.1",
    "contentType": "lesson",
    "requires": [],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 0,
    "cLab": "computer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m00-l02",
    "file": "/notes/c-m00-l02.md",
    "title": "CPU, memory, storage, and the operating system",
    "blurb": "Distinguish working state, saved files and running processes.",
    "standfirst": "Why can an unsaved edit disappear while the original file remains?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 00 · Computers and programs, from zero",
    "phaseOrder": 1,
    "order": 2,
    "minutes": 20,
    "curriculumId": "C-00.2",
    "contentType": "lesson",
    "requires": [
      "c-m00-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 0,
    "cLab": "computer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m00-l03",
    "file": "/notes/c-m00-l03.md",
    "title": "Files, folders, and extensions",
    "blurb": "Identify a file by its saved path and verify its contents.",
    "standfirst": "How can I establish which saved file a tool actually reads?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 00 · Computers and programs, from zero",
    "phaseOrder": 1,
    "order": 3,
    "minutes": 20,
    "curriculumId": "C-00.3",
    "contentType": "lesson",
    "requires": [
      "c-m00-l02"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 0,
    "cLab": "computer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m00-l04",
    "file": "/notes/c-m00-l04.md",
    "title": "What is C, and how is it different from C++?",
    "blurb": "Distinguish requested C17 configuration from highlighting and runtime evidence.",
    "standfirst": "What settings establish that I am following the course in C17?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 00 · Computers and programs, from zero",
    "phaseOrder": 1,
    "order": 4,
    "minutes": 20,
    "curriculumId": "C-00.4",
    "contentType": "lesson",
    "requires": [
      "c-m00-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 0,
    "cLab": "computer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m00-l05",
    "file": "/notes/c-m00-l05.md",
    "title": "How to use the C Workshop",
    "blurb": "Record a prediction, observation, explanation and changed test.",
    "standfirst": "How can I turn an incorrect prediction into useful learning evidence?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 00 · Computers and programs, from zero",
    "phaseOrder": 1,
    "order": 5,
    "minutes": 20,
    "curriculumId": "C-00.5",
    "contentType": "lesson",
    "requires": [
      "c-m00-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 0,
    "cLab": "computer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m01-l01",
    "file": "/notes/c-m01-l01.md",
    "title": "Algorithms through everyday instructions",
    "blurb": "Use counterexamples to distinguish a reusable algorithm from a memorized answer.",
    "standfirst": "How can one procedure handle all the inputs in a stated task?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 01 · Thinking in steps",
    "phaseOrder": 2,
    "order": 6,
    "minutes": 20,
    "curriculumId": "C-01.1",
    "contentType": "lesson",
    "requires": [
      "c-m00-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 1,
    "cLab": "algorithm",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m01-l02",
    "file": "/notes/c-m01-l02.md",
    "title": "Inputs, outputs, and assumptions",
    "blurb": "Specify accepted inputs, required results and rejection behavior.",
    "standfirst": "How do units, valid ranges and rejection rules make a calculation unambiguous?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 01 · Thinking in steps",
    "phaseOrder": 2,
    "order": 7,
    "minutes": 20,
    "curriculumId": "C-01.2",
    "contentType": "lesson",
    "requires": [
      "c-m01-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 1,
    "cLab": "algorithm",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m01-l03",
    "file": "/notes/c-m01-l03.md",
    "title": "Sequence, decisions, repetition, and state",
    "blurb": "Count loop checks and updates and diagnose a non-progressing body.",
    "standfirst": "How do state changes and condition checks determine whether a loop stops?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 01 · Thinking in steps",
    "phaseOrder": 2,
    "order": 8,
    "minutes": 20,
    "curriculumId": "C-01.3",
    "contentType": "lesson",
    "requires": [
      "c-m01-l02"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 1,
    "cLab": "algorithm",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m01-l04",
    "file": "/notes/c-m01-l04.md",
    "title": "Pseudocode, flowcharts, and hand tracing",
    "blurb": "Trace an accumulator and compare the first divergent state.",
    "standfirst": "How can a trace reveal the first wrong update behind a wrong total?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 01 · Thinking in steps",
    "phaseOrder": 2,
    "order": 9,
    "minutes": 20,
    "curriculumId": "C-01.4",
    "contentType": "lesson",
    "requires": [
      "c-m01-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 1,
    "cLab": "algorithm",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m01-l05",
    "file": "/notes/c-m01-l05.md",
    "title": "Decomposition and first test cases",
    "blurb": "Derive normal, boundary and invalid test expectations independently of the implementation.",
    "standfirst": "How can I choose tests that challenge the required behavior?",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 01 · Thinking in steps",
    "phaseOrder": 2,
    "order": 10,
    "minutes": 20,
    "curriculumId": "C-01.5",
    "contentType": "lesson",
    "requires": [
      "c-m01-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 1,
    "cLab": "algorithm",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m02-l01",
    "file": "/notes/c-m02-l01.md",
    "title": "Editor, terminal, compiler, linker, executable",
    "blurb": "The editor changes source text.",
    "standfirst": "The editor changes source text.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 02 · Your programming workspace",
    "phaseOrder": 3,
    "order": 11,
    "minutes": 20,
    "curriculumId": "C-02.1",
    "contentType": "lesson",
    "requires": [
      "c-m01-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 2,
    "cLab": "build",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m02-l02",
    "file": "/notes/c-m02-l02.md",
    "title": "Save and compile a first source file",
    "blurb": "Create hello.c in your course folder and save the complete example below.",
    "standfirst": "Create hello.c in your course folder and save the complete example below.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 02 · Your programming workspace",
    "phaseOrder": 3,
    "order": 12,
    "minutes": 20,
    "curriculumId": "C-02.2",
    "contentType": "lesson",
    "requires": [
      "c-m02-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 2,
    "cLab": "build",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m02-l03",
    "file": "/notes/c-m02-l03.md",
    "title": "Choose a local setup for your operating system",
    "blurb": "On Windows, a GCC environment such as MSYS2 UCRT64 supplies a C compiler and a matching terminal.",
    "standfirst": "On Windows, a GCC environment such as MSYS2 UCRT64 supplies a C compiler and a matching terminal.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 02 · Your programming workspace",
    "phaseOrder": 3,
    "order": 13,
    "minutes": 20,
    "curriculumId": "C-02.3",
    "contentType": "lesson",
    "requires": [
      "c-m02-l02"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 2,
    "cLab": "build",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m02-l04",
    "file": "/notes/c-m02-l04.md",
    "title": "Working folders, rebuilds, and exit status",
    "blurb": "A relative path is interpreted from the terminal’s current working folder.",
    "standfirst": "A relative path is interpreted from the terminal’s current working folder.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 02 · Your programming workspace",
    "phaseOrder": 3,
    "order": 14,
    "minutes": 20,
    "curriculumId": "C-02.4",
    "contentType": "lesson",
    "requires": [
      "c-m02-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 2,
    "cLab": "build",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m02-l05",
    "file": "/notes/c-m02-l05.md",
    "title": "Read your first diagnostic",
    "blurb": "A diagnostic usually names a source file and location, describes a problem, and may suggest a correction.",
    "standfirst": "A diagnostic usually names a source file and location, describes a problem, and may suggest a correction.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 02 · Your programming workspace",
    "phaseOrder": 3,
    "order": 15,
    "minutes": 20,
    "curriculumId": "C-02.5",
    "contentType": "lesson",
    "requires": [
      "c-m02-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 2,
    "cLab": "build",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m03-l01",
    "file": "/notes/c-m03-l01.md",
    "title": "Where a hosted C program begins",
    "blurb": "A function groups instructions under a name.",
    "standfirst": "A function groups instructions under a name.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 03 · Understand your first C program",
    "phaseOrder": 4,
    "order": 16,
    "minutes": 20,
    "curriculumId": "C-03.1",
    "contentType": "lesson",
    "requires": [
      "c-m02-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 3,
    "cLab": "tokens",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m03-l02",
    "file": "/notes/c-m03-l02.md",
    "title": "Statements, braces, comments, and whitespace",
    "blurb": "Braces group declarations and statements into a block.",
    "standfirst": "Braces group declarations and statements into a block.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 03 · Understand your first C program",
    "phaseOrder": 4,
    "order": 17,
    "minutes": 20,
    "curriculumId": "C-03.2",
    "contentType": "lesson",
    "requires": [
      "c-m03-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 3,
    "cLab": "tokens",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m03-l03",
    "file": "/notes/c-m03-l03.md",
    "title": "Headers and calling library functions",
    "blurb": "The standard library contains reusable facilities.",
    "standfirst": "The standard library contains reusable facilities.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 03 · Understand your first C program",
    "phaseOrder": 4,
    "order": 18,
    "minutes": 20,
    "curriculumId": "C-03.3",
    "contentType": "lesson",
    "requires": [
      "c-m03-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 3,
    "cLab": "tokens",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m03-l04",
    "file": "/notes/c-m03-l04.md",
    "title": "Strings, escapes, and newlines",
    "blurb": "Double quotes delimit a string literal in source code.",
    "standfirst": "Double quotes delimit a string literal in source code.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 03 · Understand your first C program",
    "phaseOrder": 4,
    "order": 19,
    "minutes": 20,
    "curriculumId": "C-03.4",
    "contentType": "lesson",
    "requires": [
      "c-m03-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 3,
    "cLab": "tokens",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m03-l05",
    "file": "/notes/c-m03-l05.md",
    "title": "First program repair workshop",
    "blurb": "Debugging begins with a clear intended result.",
    "standfirst": "Debugging begins with a clear intended result.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 03 · Understand your first C program",
    "phaseOrder": 4,
    "order": 20,
    "minutes": 20,
    "curriculumId": "C-03.5",
    "contentType": "lesson",
    "requires": [
      "c-m03-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 3,
    "cLab": "tokens",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m04-l01",
    "file": "/notes/c-m04-l01.md",
    "title": "Declarations and initialization",
    "blurb": "A declaration introduces a name and a type.",
    "standfirst": "A declaration introduces a name and a type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 04 · Values, variables, and types",
    "phaseOrder": 5,
    "order": 21,
    "minutes": 20,
    "curriculumId": "C-04.1",
    "contentType": "lesson",
    "requires": [
      "c-m03-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 4,
    "cLab": "values",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m04-l02",
    "file": "/notes/c-m04-l02.md",
    "title": "Assignment changes state",
    "blurb": "In an assignment, the right-hand expression is evaluated to obtain a value, then that value is stored in the object designated on the left.",
    "standfirst": "In an assignment, the right-hand expression is evaluated to obtain a value, then that value is stored in the object designated on the left.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 04 · Values, variables, and types",
    "phaseOrder": 5,
    "order": 22,
    "minutes": 20,
    "curriculumId": "C-04.2",
    "contentType": "lesson",
    "requires": [
      "c-m04-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 4,
    "cLab": "values",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m04-l03",
    "file": "/notes/c-m04-l03.md",
    "title": "Integers, real-number approximations, characters, and Booleans",
    "blurb": "int represents a range of whole numbers.",
    "standfirst": "int represents a range of whole numbers.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 04 · Values, variables, and types",
    "phaseOrder": 5,
    "order": 23,
    "minutes": 20,
    "curriculumId": "C-04.3",
    "contentType": "lesson",
    "requires": [
      "c-m04-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 4,
    "cLab": "values",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m04-l04",
    "file": "/notes/c-m04-l04.md",
    "title": "Ranges, sizeof, and representation",
    "blurb": "Every object type has a size measured in C bytes.",
    "standfirst": "Every object type has a size measured in C bytes.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 04 · Values, variables, and types",
    "phaseOrder": 5,
    "order": 24,
    "minutes": 20,
    "curriculumId": "C-04.4",
    "contentType": "lesson",
    "requires": [
      "c-m04-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 4,
    "cLab": "values",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m04-l05",
    "file": "/notes/c-m04-l05.md",
    "title": "Constants and readable names",
    "blurb": "A literal is a value written directly in source, such as 12.",
    "standfirst": "A literal is a value written directly in source, such as 12.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 04 · Values, variables, and types",
    "phaseOrder": 5,
    "order": 25,
    "minutes": 20,
    "curriculumId": "C-04.5",
    "contentType": "lesson",
    "requires": [
      "c-m04-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 4,
    "cLab": "values",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m05-l01",
    "file": "/notes/c-m05-l01.md",
    "title": "Integer division and remainder",
    "blurb": "The arithmetic operators +, -, *, /, and % perform addition, subtraction, multiplication, division, and integer remainder.",
    "standfirst": "The arithmetic operators +, -, *, /, and % perform addition, subtraction, multiplication, division, and integer remainder.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 05 · Expressions and calculations",
    "phaseOrder": 6,
    "order": 26,
    "minutes": 20,
    "curriculumId": "C-05.1",
    "contentType": "lesson",
    "requires": [
      "c-m04-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 5,
    "cLab": "expressions",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m05-l02",
    "file": "/notes/c-m05-l02.md",
    "title": "Precedence, associativity, and parentheses",
    "blurb": "Precedence determines how operators group when parentheses are absent.",
    "standfirst": "Precedence determines how operators group when parentheses are absent.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 05 · Expressions and calculations",
    "phaseOrder": 6,
    "order": 27,
    "minutes": 20,
    "curriculumId": "C-05.2",
    "contentType": "lesson",
    "requires": [
      "c-m05-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 5,
    "cLab": "expressions",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m05-l03",
    "file": "/notes/c-m05-l03.md",
    "title": "Conversions, promotions, and casts",
    "blurb": "Operators often convert operands to a common type.",
    "standfirst": "Operators often convert operands to a common type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 05 · Expressions and calculations",
    "phaseOrder": 6,
    "order": 28,
    "minutes": 20,
    "curriculumId": "C-05.3",
    "contentType": "lesson",
    "requires": [
      "c-m05-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 5,
    "cLab": "expressions",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m05-l04",
    "file": "/notes/c-m05-l04.md",
    "title": "Updating variables without sequencing puzzles",
    "blurb": "The compound assignment x += 3 updates x using its current value.",
    "standfirst": "The compound assignment x += 3 updates x using its current value.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 05 · Expressions and calculations",
    "phaseOrder": 6,
    "order": 29,
    "minutes": 20,
    "curriculumId": "C-05.4",
    "contentType": "lesson",
    "requires": [
      "c-m05-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 5,
    "cLab": "expressions",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m05-l05",
    "file": "/notes/c-m05-l05.md",
    "title": "Finite numbers and arithmetic limits",
    "blurb": "Floating-point types represent a finite set of values.",
    "standfirst": "Floating-point types represent a finite set of values.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 05 · Expressions and calculations",
    "phaseOrder": 6,
    "order": 30,
    "minutes": 20,
    "curriculumId": "C-05.5",
    "contentType": "lesson",
    "requires": [
      "c-m05-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 5,
    "cLab": "expressions",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m06-l01",
    "file": "/notes/c-m06-l01.md",
    "title": "Match printf formats to values",
    "blurb": "A printf format string is a small description of how arguments should be presented.",
    "standfirst": "A printf format string is a small description of how arguments should be presented.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 06 · Output and first input",
    "phaseOrder": 7,
    "order": 31,
    "minutes": 20,
    "curriculumId": "C-06.1",
    "contentType": "lesson",
    "requires": [
      "c-m05-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 6,
    "cLab": "stream",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m06-l02",
    "file": "/notes/c-m06-l02.md",
    "title": "Width, precision, and readable output",
    "blurb": "A minimum field width aligns values without changing them.",
    "standfirst": "A minimum field width aligns values without changing them.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 06 · Output and first input",
    "phaseOrder": 7,
    "order": 32,
    "minutes": 20,
    "curriculumId": "C-06.2",
    "contentType": "lesson",
    "requires": [
      "c-m06-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 6,
    "cLab": "stream",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m06-l03",
    "file": "/notes/c-m06-l03.md",
    "title": "Standard streams and buffering",
    "blurb": "A stream is a sequence of input or output data.",
    "standfirst": "A stream is a sequence of input or output data.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 06 · Output and first input",
    "phaseOrder": 7,
    "order": 33,
    "minutes": 20,
    "curriculumId": "C-06.3",
    "contentType": "lesson",
    "requires": [
      "c-m06-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 6,
    "cLab": "stream",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m06-l04",
    "file": "/notes/c-m06-l04.md",
    "title": "Read and echo one character",
    "blurb": "getchar reads the next input character from stdin.",
    "standfirst": "getchar reads the next input character from stdin.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 06 · Output and first input",
    "phaseOrder": 7,
    "order": 34,
    "minutes": 20,
    "curriculumId": "C-06.4",
    "contentType": "lesson",
    "requires": [
      "c-m06-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 6,
    "cLab": "stream",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m06-l05",
    "file": "/notes/c-m06-l05.md",
    "title": "Text is not automatically a number",
    "blurb": "Keyboard input arrives as characters.",
    "standfirst": "Keyboard input arrives as characters.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 06 · Output and first input",
    "phaseOrder": 7,
    "order": 35,
    "minutes": 20,
    "curriculumId": "C-06.5",
    "contentType": "lesson",
    "requires": [
      "c-m06-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 6,
    "cLab": "stream",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m07-l01",
    "file": "/notes/c-m07-l01.md",
    "title": "Comparisons and zero/nonzero truth",
    "blurb": "The relational operators <, <=, >, and >= compare ordered values.",
    "standfirst": "The relational operators <, <=, >, and >= compare ordered values.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 07 · Decisions and logic",
    "phaseOrder": 8,
    "order": 36,
    "minutes": 20,
    "curriculumId": "C-07.1",
    "contentType": "lesson",
    "requires": [
      "c-m06-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 7,
    "cLab": "branch",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m07-l02",
    "file": "/notes/c-m07-l02.md",
    "title": "if, else, and blocks",
    "blurb": "An if statement evaluates a condition and executes its controlled statement when true.",
    "standfirst": "An if statement evaluates a condition and executes its controlled statement when true.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 07 · Decisions and logic",
    "phaseOrder": 8,
    "order": 37,
    "minutes": 20,
    "curriculumId": "C-07.2",
    "contentType": "lesson",
    "requires": [
      "c-m07-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 7,
    "cLab": "branch",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m07-l03",
    "file": "/notes/c-m07-l03.md",
    "title": "Logical operators and short-circuit evaluation",
    "blurb": "Logical AND, &&, is true when both conditions are true.",
    "standfirst": "Logical AND, &&, is true when both conditions are true.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 07 · Decisions and logic",
    "phaseOrder": 8,
    "order": 38,
    "minutes": 20,
    "curriculumId": "C-07.3",
    "contentType": "lesson",
    "requires": [
      "c-m07-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 7,
    "cLab": "branch",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m07-l04",
    "file": "/notes/c-m07-l04.md",
    "title": "Nested decisions and boundary policies",
    "blurb": "An else-if chain chooses the first satisfied branch.",
    "standfirst": "An else-if chain chooses the first satisfied branch.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 07 · Decisions and logic",
    "phaseOrder": 8,
    "order": 39,
    "minutes": 20,
    "curriculumId": "C-07.4",
    "contentType": "lesson",
    "requires": [
      "c-m07-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 7,
    "cLab": "branch",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m07-l05",
    "file": "/notes/c-m07-l05.md",
    "title": "switch, break, and concise choices",
    "blurb": "switch selects among integer-valued case labels.",
    "standfirst": "switch selects among integer-valued case labels.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 07 · Decisions and logic",
    "phaseOrder": 8,
    "order": 40,
    "minutes": 20,
    "curriculumId": "C-07.5",
    "contentType": "lesson",
    "requires": [
      "c-m07-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 7,
    "cLab": "branch",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m08-l01",
    "file": "/notes/c-m08-l01.md",
    "title": "while and the four parts of a loop",
    "blurb": "while checks a condition before each iteration.",
    "standfirst": "while checks a condition before each iteration.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 08 · Repetition",
    "phaseOrder": 9,
    "order": 41,
    "minutes": 20,
    "curriculumId": "C-08.1",
    "contentType": "lesson",
    "requires": [
      "c-m07-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 8,
    "cLab": "loop",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m08-l02",
    "file": "/notes/c-m08-l02.md",
    "title": "for and bounded repetition",
    "blurb": "A for loop brings initialization, condition, and update together.",
    "standfirst": "A for loop brings initialization, condition, and update together.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 08 · Repetition",
    "phaseOrder": 9,
    "order": 42,
    "minutes": 20,
    "curriculumId": "C-08.2",
    "contentType": "lesson",
    "requires": [
      "c-m08-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 8,
    "cLab": "loop",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m08-l03",
    "file": "/notes/c-m08-l03.md",
    "title": "do...while and choosing a loop",
    "blurb": "do..while executes its body before testing the condition, so the body runs at least once.",
    "standfirst": "do..while executes its body before testing the condition, so the body runs at least once.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 08 · Repetition",
    "phaseOrder": 9,
    "order": 43,
    "minutes": 20,
    "curriculumId": "C-08.3",
    "contentType": "lesson",
    "requires": [
      "c-m08-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 8,
    "cLab": "loop",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m08-l04",
    "file": "/notes/c-m08-l04.md",
    "title": "break, continue, and sentinel values",
    "blurb": "break exits the nearest enclosing loop or switch.",
    "standfirst": "break exits the nearest enclosing loop or switch.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 08 · Repetition",
    "phaseOrder": 9,
    "order": 44,
    "minutes": 20,
    "curriculumId": "C-08.4",
    "contentType": "lesson",
    "requires": [
      "c-m08-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 8,
    "cLab": "loop",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m08-l05",
    "file": "/notes/c-m08-l05.md",
    "title": "Nested loops and termination",
    "blurb": "A nested loop runs one complete inner traversal for each relevant outer iteration.",
    "standfirst": "A nested loop runs one complete inner traversal for each relevant outer iteration.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 08 · Repetition",
    "phaseOrder": 9,
    "order": 45,
    "minutes": 20,
    "curriculumId": "C-08.5",
    "contentType": "lesson",
    "requires": [
      "c-m08-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 8,
    "cLab": "loop",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m09-l01",
    "file": "/notes/c-m09-l01.md",
    "title": "Counters, accumulators, minimums, and maximums",
    "blurb": "A counter records how many qualifying events occurred.",
    "standfirst": "A counter records how many qualifying events occurred.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 09 · Solving small problems",
    "phaseOrder": 10,
    "order": 46,
    "minutes": 20,
    "curriculumId": "C-09.1",
    "contentType": "lesson",
    "requires": [
      "c-m08-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 9,
    "cLab": "accumulator",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m09-l02",
    "file": "/notes/c-m09-l02.md",
    "title": "Digits and divisibility",
    "blurb": "For a nonnegative integer in base ten, remainder by ten gives the last decimal digit and division by ten removes that digit.",
    "standfirst": "For a nonnegative integer in base ten, remainder by ten gives the last decimal digit and division by ten removes that digit.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 09 · Solving small problems",
    "phaseOrder": 10,
    "order": 47,
    "minutes": 20,
    "curriculumId": "C-09.2",
    "contentType": "lesson",
    "requires": [
      "c-m09-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 9,
    "cLab": "accumulator",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m09-l03",
    "file": "/notes/c-m09-l03.md",
    "title": "From a written problem to a program",
    "blurb": "Translate a specification through intermediate representations.",
    "standfirst": "Translate a specification through intermediate representations.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 09 · Solving small problems",
    "phaseOrder": 10,
    "order": 48,
    "minutes": 20,
    "curriculumId": "C-09.3",
    "contentType": "lesson",
    "requires": [
      "c-m09-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 9,
    "cLab": "accumulator",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m09-l04",
    "file": "/notes/c-m09-l04.md",
    "title": "Test boundaries and prove progress",
    "blurb": "A loop invariant is a fact that remains true at a chosen point in every iteration.",
    "standfirst": "A loop invariant is a fact that remains true at a chosen point in every iteration.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 09 · Solving small problems",
    "phaseOrder": 10,
    "order": 49,
    "minutes": 20,
    "curriculumId": "C-09.4",
    "contentType": "lesson",
    "requires": [
      "c-m09-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 9,
    "cLab": "accumulator",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m09-l05",
    "file": "/notes/c-m09-l05.md",
    "title": "Mini-project: a character-menu calculator",
    "blurb": "Build a small calculator with fixed operands first.",
    "standfirst": "Build a small calculator with fixed operands first.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 09 · Solving small problems",
    "phaseOrder": 10,
    "order": 50,
    "minutes": 20,
    "curriculumId": "C-09.5",
    "contentType": "lesson",
    "requires": [
      "c-m09-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 9,
    "cLab": "accumulator",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m10-l01",
    "file": "/notes/c-m10-l01.md",
    "title": "Give one operation a name",
    "blurb": "A function definition gives a name, return type, parameter list, and body to a reusable operation.",
    "standfirst": "A function definition gives a name, return type, parameter list, and body to a reusable operation.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 10 · Functions and decomposition",
    "phaseOrder": 11,
    "order": 51,
    "minutes": 20,
    "curriculumId": "C-10.1",
    "contentType": "lesson",
    "requires": [
      "c-m09-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 10,
    "cLab": "calls",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m10-l02",
    "file": "/notes/c-m10-l02.md",
    "title": "Declarations, prototypes, and definitions",
    "blurb": "A declaration tells the compiler a function’s name and type.",
    "standfirst": "A declaration tells the compiler a function’s name and type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 10 · Functions and decomposition",
    "phaseOrder": 11,
    "order": 52,
    "minutes": 20,
    "curriculumId": "C-10.2",
    "contentType": "lesson",
    "requires": [
      "c-m10-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 10,
    "cLab": "calls",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m10-l03",
    "file": "/notes/c-m10-l03.md",
    "title": "Parameters are separate objects",
    "blurb": "Arguments are expressions supplied by the caller.",
    "standfirst": "Arguments are expressions supplied by the caller.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 10 · Functions and decomposition",
    "phaseOrder": 11,
    "order": 53,
    "minutes": 20,
    "curriculumId": "C-10.3",
    "contentType": "lesson",
    "requires": [
      "c-m10-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 10,
    "cLab": "calls",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m10-l04",
    "file": "/notes/c-m10-l04.md",
    "title": "Return values and early exits",
    "blurb": "return ends the current function call.",
    "standfirst": "return ends the current function call.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 10 · Functions and decomposition",
    "phaseOrder": 11,
    "order": 54,
    "minutes": 20,
    "curriculumId": "C-10.4",
    "contentType": "lesson",
    "requires": [
      "c-m10-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 10,
    "cLab": "calls",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m10-l05",
    "file": "/notes/c-m10-l05.md",
    "title": "Refactor and test small functions",
    "blurb": "Refactoring changes structure while preserving intended behavior.",
    "standfirst": "Refactoring changes structure while preserving intended behavior.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 10 · Functions and decomposition",
    "phaseOrder": 11,
    "order": 55,
    "minutes": 20,
    "curriculumId": "C-10.5",
    "contentType": "lesson",
    "requires": [
      "c-m10-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 10,
    "cLab": "calls",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m11-l01",
    "file": "/notes/c-m11-l01.md",
    "title": "Blocks and scope",
    "blurb": "Scope is the region of source where a declaration’s name can be used.",
    "standfirst": "Scope is the region of source where a declaration’s name can be used.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 11 · Scope, lifetime, and recursion",
    "phaseOrder": 12,
    "order": 56,
    "minutes": 20,
    "curriculumId": "C-11.1",
    "contentType": "lesson",
    "requires": [
      "c-m10-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 11,
    "cLab": "recursion",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m11-l02",
    "file": "/notes/c-m11-l02.md",
    "title": "Storage duration and local static state",
    "blurb": "Lifetime is the period when an object exists and can be accessed according to the language rules.",
    "standfirst": "Lifetime is the period when an object exists and can be accessed according to the language rules.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 11 · Scope, lifetime, and recursion",
    "phaseOrder": 12,
    "order": 57,
    "minutes": 20,
    "curriculumId": "C-11.2",
    "contentType": "lesson",
    "requires": [
      "c-m11-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 11,
    "cLab": "recursion",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m11-l03",
    "file": "/notes/c-m11-l03.md",
    "title": "Trace function calls and returns",
    "blurb": "A call-frame model groups a call’s parameters, local values, and return location.",
    "standfirst": "A call-frame model groups a call’s parameters, local values, and return location.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 11 · Scope, lifetime, and recursion",
    "phaseOrder": 12,
    "order": 58,
    "minutes": 20,
    "curriculumId": "C-11.3",
    "contentType": "lesson",
    "requires": [
      "c-m11-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 11,
    "cLab": "recursion",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m11-l04",
    "file": "/notes/c-m11-l04.md",
    "title": "Recursion needs a base case and progress",
    "blurb": "A recursive function calls itself on a smaller or otherwise simpler problem.",
    "standfirst": "A recursive function calls itself on a smaller or otherwise simpler problem.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 11 · Scope, lifetime, and recursion",
    "phaseOrder": 12,
    "order": 59,
    "minutes": 20,
    "curriculumId": "C-11.4",
    "contentType": "lesson",
    "requires": [
      "c-m11-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 11,
    "cLab": "recursion",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m11-l05",
    "file": "/notes/c-m11-l05.md",
    "title": "Recursion versus iteration",
    "blurb": "Many recursive algorithms can be expressed with loops.",
    "standfirst": "Many recursive algorithms can be expressed with loops.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 11 · Scope, lifetime, and recursion",
    "phaseOrder": 12,
    "order": 60,
    "minutes": 20,
    "curriculumId": "C-11.5",
    "contentType": "lesson",
    "requires": [
      "c-m11-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 11,
    "cLab": "recursion",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m12-l01",
    "file": "/notes/c-m12-l01.md",
    "title": "Declare, initialize, and index an array",
    "blurb": "An array type specifies an element type and number of elements.",
    "standfirst": "An array type specifies an element type and number of elements.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 12 · Arrays and collections",
    "phaseOrder": 13,
    "order": 61,
    "minutes": 20,
    "curriculumId": "C-12.1",
    "contentType": "lesson",
    "requires": [
      "c-m11-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 12,
    "cLab": "array",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m12-l02",
    "file": "/notes/c-m12-l02.md",
    "title": "Traverse and update safely",
    "blurb": "A traversal visits elements in an intended order.",
    "standfirst": "A traversal visits elements in an intended order.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 12 · Arrays and collections",
    "phaseOrder": 13,
    "order": 62,
    "minutes": 20,
    "curriculumId": "C-12.2",
    "contentType": "lesson",
    "requires": [
      "c-m12-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 12,
    "cLab": "array",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m12-l03",
    "file": "/notes/c-m12-l03.md",
    "title": "Capacity, logical length, and partial initialization",
    "blurb": "Capacity is how many elements storage can hold.",
    "standfirst": "Capacity is how many elements storage can hold.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 12 · Arrays and collections",
    "phaseOrder": 13,
    "order": 63,
    "minutes": 20,
    "curriculumId": "C-12.3",
    "contentType": "lesson",
    "requires": [
      "c-m12-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 12,
    "cLab": "array",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m12-l04",
    "file": "/notes/c-m12-l04.md",
    "title": "Search and shift a fixed collection",
    "blurb": "Linear search examines each active element until a match is found or the range is exhausted.",
    "standfirst": "Linear search examines each active element until a match is found or the range is exhausted.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 12 · Arrays and collections",
    "phaseOrder": 13,
    "order": 64,
    "minutes": 20,
    "curriculumId": "C-12.4",
    "contentType": "lesson",
    "requires": [
      "c-m12-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 12,
    "cLab": "array",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m12-l05",
    "file": "/notes/c-m12-l05.md",
    "title": "Two-dimensional arrays and rows",
    "blurb": "A two-dimensional array is an array whose elements are arrays.",
    "standfirst": "A two-dimensional array is an array whose elements are arrays.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 12 · Arrays and collections",
    "phaseOrder": 13,
    "order": 65,
    "minutes": 20,
    "curriculumId": "C-12.5",
    "contentType": "lesson",
    "requires": [
      "c-m12-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 12,
    "cLab": "array",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m13-l01",
    "file": "/notes/c-m13-l01.md",
    "title": "A character is not a string",
    "blurb": "A character constant such as 'A' represents a character code as an integer constant in C.",
    "standfirst": "A character constant such as 'A' represents a character code as an integer constant in C.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 13 · Characters and strings",
    "phaseOrder": 14,
    "order": 66,
    "minutes": 20,
    "curriculumId": "C-13.1",
    "contentType": "lesson",
    "requires": [
      "c-m12-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 13,
    "cLab": "string",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m13-l02",
    "file": "/notes/c-m13-l02.md",
    "title": "The invisible null terminator",
    "blurb": "A C string ends at the first null character, written \\0.",
    "standfirst": "A C string ends at the first null character, written \\0.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 13 · Characters and strings",
    "phaseOrder": 14,
    "order": 67,
    "minutes": 20,
    "curriculumId": "C-13.2",
    "contentType": "lesson",
    "requires": [
      "c-m13-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 13,
    "cLab": "string",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m13-l03",
    "file": "/notes/c-m13-l03.md",
    "title": "Length and capacity are different",
    "blurb": "String length counts characters before the first terminator; it excludes the terminator itself.",
    "standfirst": "String length counts characters before the first terminator; it excludes the terminator itself.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 13 · Characters and strings",
    "phaseOrder": 14,
    "order": 68,
    "minutes": 20,
    "curriculumId": "C-13.3",
    "contentType": "lesson",
    "requires": [
      "c-m13-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 13,
    "cLab": "string",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m13-l04",
    "file": "/notes/c-m13-l04.md",
    "title": "Traverse, compare, and copy within bounds",
    "blurb": "Traversing a string means examining characters up to its terminator while respecting accessible bounds.",
    "standfirst": "Traversing a string means examining characters up to its terminator while respecting accessible bounds.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 13 · Characters and strings",
    "phaseOrder": 14,
    "order": 69,
    "minutes": 20,
    "curriculumId": "C-13.4",
    "contentType": "lesson",
    "requires": [
      "c-m13-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 13,
    "cLab": "string",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m13-l05",
    "file": "/notes/c-m13-l05.md",
    "title": "String literals and modifiable arrays",
    "blurb": "An array initialized from a string literal owns modifiable character elements.",
    "standfirst": "An array initialized from a string literal owns modifiable character elements.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 13 · Characters and strings",
    "phaseOrder": 14,
    "order": 70,
    "minutes": 20,
    "curriculumId": "C-13.5",
    "contentType": "lesson",
    "requires": [
      "c-m13-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 13,
    "cLab": "string",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m14-l01",
    "file": "/notes/c-m14-l01.md",
    "title": "Bits, bytes, objects, and values",
    "blurb": "An object is a region of data storage whose contents can represent a value.",
    "standfirst": "An object is a region of data storage whose contents can represent a value.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 14 · Memory before pointers",
    "phaseOrder": 15,
    "order": 71,
    "minutes": 25,
    "curriculumId": "C-14.1",
    "contentType": "lesson",
    "requires": [
      "c-m13-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 14,
    "cLab": "memory",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m14-l02",
    "file": "/notes/c-m14-l02.md",
    "title": "Size and alignment describe storage requirements",
    "blurb": "sizeof reports the number of C bytes occupied by an object type.",
    "standfirst": "sizeof reports the number of C bytes occupied by an object type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 14 · Memory before pointers",
    "phaseOrder": 15,
    "order": 72,
    "minutes": 25,
    "curriculumId": "C-14.2",
    "contentType": "lesson",
    "requires": [
      "c-m14-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 14,
    "cLab": "memory",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m14-l03",
    "file": "/notes/c-m14-l03.md",
    "title": "Identity versus contents",
    "blurb": "An object can retain its identity while its value changes.",
    "standfirst": "An object can retain its identity while its value changes.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 14 · Memory before pointers",
    "phaseOrder": 15,
    "order": 73,
    "minutes": 25,
    "curriculumId": "C-14.3",
    "contentType": "lesson",
    "requires": [
      "c-m14-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 14,
    "cLab": "memory",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m14-l04",
    "file": "/notes/c-m14-l04.md",
    "title": "Lifetime controls valid access",
    "blurb": "An object can be accessed only while its lifetime permits that access.",
    "standfirst": "An object can be accessed only while its lifetime permits that access.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 14 · Memory before pointers",
    "phaseOrder": 15,
    "order": 74,
    "minutes": 25,
    "curriculumId": "C-14.4",
    "contentType": "lesson",
    "requires": [
      "c-m14-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 14,
    "cLab": "memory",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m14-l05",
    "file": "/notes/c-m14-l05.md",
    "title": "Automatic, static, and allocated storage",
    "blurb": "Automatic objects are typically tied to block execution.",
    "standfirst": "Automatic objects are typically tied to block execution.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 14 · Memory before pointers",
    "phaseOrder": 15,
    "order": 75,
    "minutes": 25,
    "curriculumId": "C-14.5",
    "contentType": "lesson",
    "requires": [
      "c-m14-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 14,
    "cLab": "memory",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m15-l01",
    "file": "/notes/c-m15-l01.md",
    "title": "Declare a pointer variable",
    "blurb": "int *p declares p as a pointer to int.",
    "standfirst": "int *p declares p as a pointer to int.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 15 · Pointers, slowly and explicitly",
    "phaseOrder": 16,
    "order": 76,
    "minutes": 25,
    "curriculumId": "C-15.1",
    "contentType": "lesson",
    "requires": [
      "c-m14-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 15,
    "cLab": "pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m15-l02",
    "file": "/notes/c-m15-l02.md",
    "title": "Address-of and dereference",
    "blurb": "The unary & operator obtains an address for an appropriate object expression.",
    "standfirst": "The unary & operator obtains an address for an appropriate object expression.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 15 · Pointers, slowly and explicitly",
    "phaseOrder": 16,
    "order": 77,
    "minutes": 25,
    "curriculumId": "C-15.2",
    "contentType": "lesson",
    "requires": [
      "c-m15-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 15,
    "cLab": "pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m15-l03",
    "file": "/notes/c-m15-l03.md",
    "title": "Read and update through an alias",
    "blurb": "An alias is another way to designate the same object.",
    "standfirst": "An alias is another way to designate the same object.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 15 · Pointers, slowly and explicitly",
    "phaseOrder": 16,
    "order": 78,
    "minutes": 25,
    "curriculumId": "C-15.3",
    "contentType": "lesson",
    "requires": [
      "c-m15-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 15,
    "cLab": "pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m15-l04",
    "file": "/notes/c-m15-l04.md",
    "title": "Null, uninitialized, and dangling pointers",
    "blurb": "A null pointer is a deliberately non-designating pointer value.",
    "standfirst": "A null pointer is a deliberately non-designating pointer value.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 15 · Pointers, slowly and explicitly",
    "phaseOrder": 16,
    "order": 79,
    "minutes": 25,
    "curriculumId": "C-15.4",
    "contentType": "lesson",
    "requires": [
      "c-m15-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 15,
    "cLab": "pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m15-l05",
    "file": "/notes/c-m15-l05.md",
    "title": "Pointer parameters and a real swap",
    "blurb": "A pointer parameter receives a copy of a pointer value.",
    "standfirst": "A pointer parameter receives a copy of a pointer value.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 15 · Pointers, slowly and explicitly",
    "phaseOrder": 16,
    "order": 80,
    "minutes": 25,
    "curriculumId": "C-15.5",
    "contentType": "lesson",
    "requires": [
      "c-m15-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 15,
    "cLab": "pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m16-l01",
    "file": "/notes/c-m16-l01.md",
    "title": "Array-to-pointer conversion has limits",
    "blurb": "In many expressions, an array converts to a pointer to its first element.",
    "standfirst": "In many expressions, an array converts to a pointer to its first element.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 16 · Arrays, pointers, and interfaces",
    "phaseOrder": 17,
    "order": 81,
    "minutes": 25,
    "curriculumId": "C-16.1",
    "contentType": "lesson",
    "requires": [
      "c-m15-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 16,
    "cLab": "array-pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m16-l02",
    "file": "/notes/c-m16-l02.md",
    "title": "Array parameters need explicit lengths",
    "blurb": "A function parameter written as int values[] is adjusted to pointer type.",
    "standfirst": "A function parameter written as int values[] is adjusted to pointer type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 16 · Arrays, pointers, and interfaces",
    "phaseOrder": 17,
    "order": 82,
    "minutes": 25,
    "curriculumId": "C-16.2",
    "contentType": "lesson",
    "requires": [
      "c-m16-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 16,
    "cLab": "array-pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m16-l03",
    "file": "/notes/c-m16-l03.md",
    "title": "Pointer arithmetic and the one-past position",
    "blurb": "Adding one to an int pointer advances by one int element, not by one byte.",
    "standfirst": "Adding one to an int pointer advances by one int element, not by one byte.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 16 · Arrays, pointers, and interfaces",
    "phaseOrder": 17,
    "order": 83,
    "minutes": 25,
    "curriculumId": "C-16.3",
    "contentType": "lesson",
    "requires": [
      "c-m16-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 16,
    "cLab": "array-pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m16-l04",
    "file": "/notes/c-m16-l04.md",
    "title": "Pointer-to-const and const pointer",
    "blurb": "const int *p means the pointed-to int cannot be modified through p.",
    "standfirst": "const int *p means the pointed-to int cannot be modified through p.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 16 · Arrays, pointers, and interfaces",
    "phaseOrder": 17,
    "order": 84,
    "minutes": 25,
    "curriculumId": "C-16.4",
    "contentType": "lesson",
    "requires": [
      "c-m16-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 16,
    "cLab": "array-pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m16-l05",
    "file": "/notes/c-m16-l05.md",
    "title": "Pointers to rows and arrays of pointers",
    "blurb": "A pointer to an array preserves a row’s type and width.",
    "standfirst": "A pointer to an array preserves a row’s type and width.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 16 · Arrays, pointers, and interfaces",
    "phaseOrder": 17,
    "order": 85,
    "minutes": 25,
    "curriculumId": "C-16.5",
    "contentType": "lesson",
    "requires": [
      "c-m16-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 16,
    "cLab": "array-pointer",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m17-l01",
    "file": "/notes/c-m17-l01.md",
    "title": "Read a bounded line with fgets",
    "blurb": "fgets reads at most capacity-1 characters and adds a null terminator when it succeeds.",
    "standfirst": "fgets reads at most capacity-1 characters and adds a null terminator when it succeeds.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 17 · Robust strings and user input",
    "phaseOrder": 18,
    "order": 86,
    "minutes": 25,
    "curriculumId": "C-17.1",
    "contentType": "lesson",
    "requires": [
      "c-m16-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 17,
    "cLab": "parse",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m17-l02",
    "file": "/notes/c-m17-l02.md",
    "title": "Parse integers with strtol",
    "blurb": "strtol converts a prefix of text to long.",
    "standfirst": "strtol converts a prefix of text to long.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 17 · Robust strings and user input",
    "phaseOrder": 18,
    "order": 87,
    "minutes": 25,
    "curriculumId": "C-17.2",
    "contentType": "lesson",
    "requires": [
      "c-m17-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 17,
    "cLab": "parse",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m17-l03",
    "file": "/notes/c-m17-l03.md",
    "title": "Understand scanf instead of copying it",
    "blurb": "scanf interprets formatted input and writes through pointer arguments.",
    "standfirst": "scanf interprets formatted input and writes through pointer arguments.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 17 · Robust strings and user input",
    "phaseOrder": 18,
    "order": 88,
    "minutes": 25,
    "curriculumId": "C-17.3",
    "contentType": "lesson",
    "requires": [
      "c-m17-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 17,
    "cLab": "parse",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m17-l04",
    "file": "/notes/c-m17-l04.md",
    "title": "String APIs and bounded formatting",
    "blurb": "strlen counts characters before the terminator and assumes a valid string.",
    "standfirst": "strlen counts characters before the terminator and assumes a valid string.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 17 · Robust strings and user input",
    "phaseOrder": 18,
    "order": 89,
    "minutes": 25,
    "curriculumId": "C-17.4",
    "contentType": "lesson",
    "requires": [
      "c-m17-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 17,
    "cLab": "parse",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m17-l05",
    "file": "/notes/c-m17-l05.md",
    "title": "Character classification and overlapping memory",
    "blurb": "ctype.h functions such as isdigit accept EOF or values representable as unsigned char.",
    "standfirst": "ctype.h functions such as isdigit accept EOF or values representable as unsigned char.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 17 · Robust strings and user input",
    "phaseOrder": 18,
    "order": 90,
    "minutes": 25,
    "curriculumId": "C-17.5",
    "contentType": "lesson",
    "requires": [
      "c-m17-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 17,
    "cLab": "parse",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m18-l01",
    "file": "/notes/c-m18-l01.md",
    "title": "Allocate only when the required lifetime needs it",
    "blurb": "malloc requests a number of bytes and returns a pointer to allocated storage or a null pointer on failure.",
    "standfirst": "malloc requests a number of bytes and returns a pointer to allocated storage or a null pointer on failure.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 18 · Dynamic memory and ownership",
    "phaseOrder": 19,
    "order": 91,
    "minutes": 25,
    "curriculumId": "C-18.1",
    "contentType": "lesson",
    "requires": [
      "c-m17-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 18,
    "cLab": "allocation",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m18-l02",
    "file": "/notes/c-m18-l02.md",
    "title": "Check allocation-size arithmetic",
    "blurb": "Before requesting count*sizeof *pointer bytes, ensure that the multiplication is representable in size_t.",
    "standfirst": "Before requesting count*sizeof *pointer bytes, ensure that the multiplication is representable in size_t.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 18 · Dynamic memory and ownership",
    "phaseOrder": 19,
    "order": 92,
    "minutes": 25,
    "curriculumId": "C-18.2",
    "contentType": "lesson",
    "requires": [
      "c-m18-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 18,
    "cLab": "allocation",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m18-l03",
    "file": "/notes/c-m18-l03.md",
    "title": "calloc, free, and ownership",
    "blurb": "calloc allocates storage for a count of elements and initializes its bytes to zero.",
    "standfirst": "calloc allocates storage for a count of elements and initializes its bytes to zero.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 18 · Dynamic memory and ownership",
    "phaseOrder": 19,
    "order": 93,
    "minutes": 25,
    "curriculumId": "C-18.3",
    "contentType": "lesson",
    "requires": [
      "c-m18-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 18,
    "cLab": "allocation",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m18-l04",
    "file": "/notes/c-m18-l04.md",
    "title": "Resize without losing the original allocation",
    "blurb": "realloc may resize an allocation in place or move its contents.",
    "standfirst": "realloc may resize an allocation in place or move its contents.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 18 · Dynamic memory and ownership",
    "phaseOrder": 19,
    "order": 94,
    "minutes": 25,
    "curriculumId": "C-18.4",
    "contentType": "lesson",
    "requires": [
      "c-m18-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 18,
    "cLab": "allocation",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m18-l05",
    "file": "/notes/c-m18-l05.md",
    "title": "Growing arrays and memory-error diagnosis",
    "blurb": "A dynamic array tracks data, length, and capacity.",
    "standfirst": "A dynamic array tracks data, length, and capacity.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 18 · Dynamic memory and ownership",
    "phaseOrder": 19,
    "order": 95,
    "minutes": 25,
    "curriculumId": "C-18.5",
    "contentType": "lesson",
    "requires": [
      "c-m18-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 18,
    "cLab": "allocation",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m19-l01",
    "file": "/notes/c-m19-l01.md",
    "title": "Structure definitions and member access",
    "blurb": "A struct describes a type with named members.",
    "standfirst": "A struct describes a type with named members.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 19 · Model real things with structures",
    "phaseOrder": 20,
    "order": 96,
    "minutes": 25,
    "curriculumId": "C-19.1",
    "contentType": "lesson",
    "requires": [
      "c-m18-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 19,
    "cLab": "struct",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m19-l02",
    "file": "/notes/c-m19-l02.md",
    "title": "Arrays of records and structure assignment",
    "blurb": "An array of structures holds multiple records of one structure type.",
    "standfirst": "An array of structures holds multiple records of one structure type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 19 · Model real things with structures",
    "phaseOrder": 20,
    "order": 97,
    "minutes": 25,
    "curriculumId": "C-19.2",
    "contentType": "lesson",
    "requires": [
      "c-m19-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 19,
    "cLab": "struct",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m19-l03",
    "file": "/notes/c-m19-l03.md",
    "title": "Structure pointers and the arrow operator",
    "blurb": "A pointer to a structure can designate a caller-owned record.",
    "standfirst": "A pointer to a structure can designate a caller-owned record.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 19 · Model real things with structures",
    "phaseOrder": 20,
    "order": 98,
    "minutes": 25,
    "curriculumId": "C-19.3",
    "contentType": "lesson",
    "requires": [
      "c-m19-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 19,
    "cLab": "struct",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m19-l04",
    "file": "/notes/c-m19-l04.md",
    "title": "enum, typedef, and expressive states",
    "blurb": "An enum names a set of integer constants, useful for states such as active, paused, and closed.",
    "standfirst": "An enum names a set of integer constants, useful for states such as active, paused, and closed.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 19 · Model real things with structures",
    "phaseOrder": 20,
    "order": 99,
    "minutes": 25,
    "curriculumId": "C-19.4",
    "contentType": "lesson",
    "requires": [
      "c-m19-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 19,
    "cLab": "struct",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m19-l05",
    "file": "/notes/c-m19-l05.md",
    "title": "Shallow copies, ownership, and invariants",
    "blurb": "A structure with a pointer member stores a reference value.",
    "standfirst": "A structure with a pointer member stores a reference value.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 19 · Model real things with structures",
    "phaseOrder": 20,
    "order": 100,
    "minutes": 25,
    "curriculumId": "C-19.5",
    "contentType": "lesson",
    "requires": [
      "c-m19-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 19,
    "cLab": "struct",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m20-l01",
    "file": "/notes/c-m20-l01.md",
    "title": "Open, check, and close a stream",
    "blurb": "fopen opens a file stream in a selected mode and returns FILE* or NULL on failure.",
    "standfirst": "fopen opens a file stream in a selected mode and returns FILE* or NULL on failure.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 20 · Files and persistence",
    "phaseOrder": 21,
    "order": 101,
    "minutes": 25,
    "curriculumId": "C-20.1",
    "contentType": "lesson",
    "requires": [
      "c-m19-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 20,
    "cLab": "file",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m20-l02",
    "file": "/notes/c-m20-l02.md",
    "title": "Text records and explicit validation",
    "blurb": "A text format makes stored information inspectable, but it still needs a grammar.",
    "standfirst": "A text format makes stored information inspectable, but it still needs a grammar.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 20 · Files and persistence",
    "phaseOrder": 21,
    "order": 102,
    "minutes": 25,
    "curriculumId": "C-20.2",
    "contentType": "lesson",
    "requires": [
      "c-m20-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 20,
    "cLab": "file",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m20-l03",
    "file": "/notes/c-m20-l03.md",
    "title": "EOF is observed after a read attempt",
    "blurb": "feof reports a stream’s end-of-file indicator; it does not predict whether the next read will succeed.",
    "standfirst": "feof reports a stream’s end-of-file indicator; it does not predict whether the next read will succeed.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 20 · Files and persistence",
    "phaseOrder": 21,
    "order": 103,
    "minutes": 25,
    "curriculumId": "C-20.3",
    "contentType": "lesson",
    "requires": [
      "c-m20-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 20,
    "cLab": "file",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m20-l04",
    "file": "/notes/c-m20-l04.md",
    "title": "Binary I/O and portable formats",
    "blurb": "fread and fwrite transfer elements of a specified size and count.",
    "standfirst": "fread and fwrite transfer elements of a specified size and count.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 20 · Files and persistence",
    "phaseOrder": 21,
    "order": 104,
    "minutes": 25,
    "curriculumId": "C-20.4",
    "contentType": "lesson",
    "requires": [
      "c-m20-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 20,
    "cLab": "file",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m20-l05",
    "file": "/notes/c-m20-l05.md",
    "title": "Load transactionally and report failures",
    "blurb": "A robust load operation builds a temporary collection.",
    "standfirst": "A robust load operation builds a temporary collection.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 20 · Files and persistence",
    "phaseOrder": 21,
    "order": 105,
    "minutes": 25,
    "curriculumId": "C-20.5",
    "contentType": "lesson",
    "requires": [
      "c-m20-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 20,
    "cLab": "file",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m21-l01",
    "file": "/notes/c-m21-l01.md",
    "title": "Headers, sources, and translation units",
    "blurb": "A translation unit is a source file after preprocessing, including included header content.",
    "standfirst": "A translation unit is a source file after preprocessing, including included header content.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 21 · Programs with multiple files",
    "phaseOrder": 22,
    "order": 106,
    "minutes": 25,
    "curriculumId": "C-21.1",
    "contentType": "lesson",
    "requires": [
      "c-m20-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 21,
    "cLab": "linker",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m21-l02",
    "file": "/notes/c-m21-l02.md",
    "title": "Declarations, definitions, and header guards",
    "blurb": "A declaration describes an entity’s type.",
    "standfirst": "A declaration describes an entity’s type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 21 · Programs with multiple files",
    "phaseOrder": 22,
    "order": 107,
    "minutes": 25,
    "curriculumId": "C-21.2",
    "contentType": "lesson",
    "requires": [
      "c-m21-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 21,
    "cLab": "linker",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m21-l03",
    "file": "/notes/c-m21-l03.md",
    "title": "Linkage and file-scope static",
    "blurb": "Linkage determines whether declarations in different places refer to the same entity.",
    "standfirst": "Linkage determines whether declarations in different places refer to the same entity.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 21 · Programs with multiple files",
    "phaseOrder": 22,
    "order": 108,
    "minutes": 25,
    "curriculumId": "C-21.3",
    "contentType": "lesson",
    "requires": [
      "c-m21-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 21,
    "cLab": "linker",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m21-l04",
    "file": "/notes/c-m21-l04.md",
    "title": "Compile and link separately",
    "blurb": "A compile-only command produces an object file, commonly using -c.",
    "standfirst": "A compile-only command produces an object file, commonly using -c.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 21 · Programs with multiple files",
    "phaseOrder": 22,
    "order": 109,
    "minutes": 25,
    "curriculumId": "C-21.4",
    "contentType": "lesson",
    "requires": [
      "c-m21-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 21,
    "cLab": "linker",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m21-l05",
    "file": "/notes/c-m21-l05.md",
    "title": "Command-line arguments and a small build recipe",
    "blurb": "The hosted main form int main(int argc,char *argv[]) receives an argument count and argument strings.",
    "standfirst": "The hosted main form int main(int argc,char *argv[]) receives an argument count and argument strings.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 21 · Programs with multiple files",
    "phaseOrder": 22,
    "order": 110,
    "minutes": 25,
    "curriculumId": "C-21.5",
    "contentType": "lesson",
    "requires": [
      "c-m21-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 21,
    "cLab": "linker",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m22-l01",
    "file": "/notes/c-m22-l01.md",
    "title": "Reproduce, minimize, inspect, repair",
    "blurb": "First state the expected behavior and the observed difference.",
    "standfirst": "First state the expected behavior and the observed difference.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 22 · Debugging, testing, and correctness",
    "phaseOrder": 23,
    "order": 111,
    "minutes": 25,
    "curriculumId": "C-22.1",
    "contentType": "lesson",
    "requires": [
      "c-m21-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 22,
    "cLab": "debug",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m22-l02",
    "file": "/notes/c-m22-l02.md",
    "title": "Warnings and debugger navigation",
    "blurb": "Build with diagnostics enabled and debug information when investigating.",
    "standfirst": "Build with diagnostics enabled and debug information when investigating.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 22 · Debugging, testing, and correctness",
    "phaseOrder": 23,
    "order": 112,
    "minutes": 25,
    "curriculumId": "C-22.2",
    "contentType": "lesson",
    "requires": [
      "c-m22-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 22,
    "cLab": "debug",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m22-l03",
    "file": "/notes/c-m22-l03.md",
    "title": "Assertions, unit tests, and regressions",
    "blurb": "An assertion expresses an internal condition that should hold if the program is correct.",
    "standfirst": "An assertion expresses an internal condition that should hold if the program is correct.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 22 · Debugging, testing, and correctness",
    "phaseOrder": 23,
    "order": 113,
    "minutes": 25,
    "curriculumId": "C-22.3",
    "contentType": "lesson",
    "requires": [
      "c-m22-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 22,
    "cLab": "debug",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m22-l04",
    "file": "/notes/c-m22-l04.md",
    "title": "Read sanitizer reports as evidence",
    "blurb": "AddressSanitizer instruments operations to detect classes of memory errors such as out-of-bounds access and use-after-free.",
    "standfirst": "AddressSanitizer instruments operations to detect classes of memory errors such as out-of-bounds access and use-after-free.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 22 · Debugging, testing, and correctness",
    "phaseOrder": 23,
    "order": 114,
    "minutes": 25,
    "curriculumId": "C-22.4",
    "contentType": "lesson",
    "requires": [
      "c-m22-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 22,
    "cLab": "debug",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m22-l05",
    "file": "/notes/c-m22-l05.md",
    "title": "Defined, unspecified, implementation-defined, undefined",
    "blurb": "Defined behavior follows the selected standard’s rules.",
    "standfirst": "Defined behavior follows the selected standard’s rules.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 22 · Debugging, testing, and correctness",
    "phaseOrder": 23,
    "order": 115,
    "minutes": 25,
    "curriculumId": "C-22.5",
    "contentType": "lesson",
    "requires": [
      "c-m22-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 22,
    "cLab": "debug",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m23-l01",
    "file": "/notes/c-m23-l01.md",
    "title": "Specify the contact-book behavior",
    "blurb": "Start with observable requirements.",
    "standfirst": "Start with observable requirements.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 23 · Core capstone: a dependable contact book",
    "phaseOrder": 24,
    "order": 116,
    "minutes": 25,
    "curriculumId": "C-23.1",
    "contentType": "lesson",
    "requires": [
      "c-m22-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 23,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m23-l02",
    "file": "/notes/c-m23-l02.md",
    "title": "Design records, ownership, and interfaces",
    "blurb": "Book owns one allocation containing Contact records.",
    "standfirst": "Book owns one allocation containing Contact records.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 23 · Core capstone: a dependable contact book",
    "phaseOrder": 24,
    "order": 117,
    "minutes": 25,
    "curriculumId": "C-23.2",
    "contentType": "lesson",
    "requires": [
      "c-m23-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 23,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m23-l03",
    "file": "/notes/c-m23-l03.md",
    "title": "Implement add, list, search, edit, delete",
    "blurb": "Add validates both fields before growing or changing the book.",
    "standfirst": "Add validates both fields before growing or changing the book.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 23 · Core capstone: a dependable contact book",
    "phaseOrder": 24,
    "order": 118,
    "minutes": 25,
    "curriculumId": "C-23.3",
    "contentType": "lesson",
    "requires": [
      "c-m23-l02"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 23,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m23-l04",
    "file": "/notes/c-m23-l04.md",
    "title": "Persistence, failures, and regression tests",
    "blurb": "The loader parses into a temporary Book.",
    "standfirst": "The loader parses into a temporary Book.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 23 · Core capstone: a dependable contact book",
    "phaseOrder": 24,
    "order": 119,
    "minutes": 25,
    "curriculumId": "C-23.4",
    "contentType": "lesson",
    "requires": [
      "c-m23-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 23,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m23-l05",
    "file": "/notes/c-m23-l05.md",
    "title": "Review, demonstrate, and extend independently",
    "blurb": "A completed project is reproducible.",
    "standfirst": "A completed project is reproducible.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Module 23 · Core capstone: a dependable contact book",
    "phaseOrder": 24,
    "order": 120,
    "minutes": 25,
    "curriculumId": "C-23.5",
    "contentType": "lesson",
    "requires": [
      "c-m23-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "core",
    "standard": "C17",
    "module": 23,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m24-l01",
    "file": "/notes/c-m24-l01.md",
    "title": "Count work before timing it",
    "blurb": "An algorithm is a finite procedure for solving a class of problems.",
    "standfirst": "An algorithm is a finite procedure for solving a class of problems.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 24 · Algorithms and growing workloads",
    "phaseOrder": 25,
    "order": 121,
    "minutes": 25,
    "curriculumId": "C-24.1",
    "contentType": "lesson",
    "requires": [
      "c-m23-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 24,
    "cLab": "sort",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m24-l02",
    "file": "/notes/c-m24-l02.md",
    "title": "Linear search with an explicit result",
    "blurb": "Linear search checks elements until a match appears or the range ends.",
    "standfirst": "Linear search checks elements until a match appears or the range ends.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 24 · Algorithms and growing workloads",
    "phaseOrder": 25,
    "order": 122,
    "minutes": 25,
    "curriculumId": "C-24.2",
    "contentType": "lesson",
    "requires": [
      "c-m24-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 24,
    "cLab": "sort",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m24-l03",
    "file": "/notes/c-m24-l03.md",
    "title": "Binary search maintains a shrinking range",
    "blurb": "Binary search requires sorted input under a consistent ordering.",
    "standfirst": "Binary search requires sorted input under a consistent ordering.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 24 · Algorithms and growing workloads",
    "phaseOrder": 25,
    "order": 123,
    "minutes": 25,
    "curriculumId": "C-24.3",
    "contentType": "lesson",
    "requires": [
      "c-m24-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 24,
    "cLab": "sort",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m24-l04",
    "file": "/notes/c-m24-l04.md",
    "title": "Insertion sort and a useful invariant",
    "blurb": "Insertion sort grows a sorted prefix.",
    "standfirst": "Insertion sort grows a sorted prefix.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 24 · Algorithms and growing workloads",
    "phaseOrder": 25,
    "order": 124,
    "minutes": 25,
    "curriculumId": "C-24.4",
    "contentType": "lesson",
    "requires": [
      "c-m24-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 24,
    "cLab": "sort",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m24-l05",
    "file": "/notes/c-m24-l05.md",
    "title": "Use qsort with a correct comparator",
    "blurb": "qsort accepts a base address, element count, element size, and comparison function.",
    "standfirst": "qsort accepts a base address, element count, element size, and comparison function.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 24 · Algorithms and growing workloads",
    "phaseOrder": 25,
    "order": 125,
    "minutes": 25,
    "curriculumId": "C-24.5",
    "contentType": "lesson",
    "requires": [
      "c-m24-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 24,
    "cLab": "sort",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m25-l01",
    "file": "/notes/c-m25-l01.md",
    "title": "A node connects data to another node",
    "blurb": "A linked-list node contains a value and a pointer to the next node.",
    "standfirst": "A linked-list node contains a value and a pointer to the next node.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 25 · Linked structures and ownership",
    "phaseOrder": 26,
    "order": 126,
    "minutes": 25,
    "curriculumId": "C-25.1",
    "contentType": "lesson",
    "requires": [
      "c-m24-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 25,
    "cLab": "nodes",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m25-l02",
    "file": "/notes/c-m25-l02.md",
    "title": "Insert at the head without losing the chain",
    "blurb": "For a dynamically owned list, allocate and initialize the new node before publishing it as the head.",
    "standfirst": "For a dynamically owned list, allocate and initialize the new node before publishing it as the head.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 25 · Linked structures and ownership",
    "phaseOrder": 26,
    "order": 127,
    "minutes": 25,
    "curriculumId": "C-25.2",
    "contentType": "lesson",
    "requires": [
      "c-m25-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 25,
    "cLab": "nodes",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m25-l03",
    "file": "/notes/c-m25-l03.md",
    "title": "Delete safely and preserve the next address",
    "blurb": "To remove a node, reconnect the surrounding links while the relevant nodes are still alive.",
    "standfirst": "To remove a node, reconnect the surrounding links while the relevant nodes are still alive.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 25 · Linked structures and ownership",
    "phaseOrder": 26,
    "order": 128,
    "minutes": 25,
    "curriculumId": "C-25.3",
    "contentType": "lesson",
    "requires": [
      "c-m25-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 25,
    "cLab": "nodes",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m25-l04",
    "file": "/notes/c-m25-l04.md",
    "title": "Stacks and queues describe behavior",
    "blurb": "A stack is last-in, first-out: push adds and pop removes the most recent item.",
    "standfirst": "A stack is last-in, first-out: push adds and pop removes the most recent item.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 25 · Linked structures and ownership",
    "phaseOrder": 26,
    "order": 129,
    "minutes": 25,
    "curriculumId": "C-25.4",
    "contentType": "lesson",
    "requires": [
      "c-m25-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 25,
    "cLab": "nodes",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m25-l05",
    "file": "/notes/c-m25-l05.md",
    "title": "Choose a representation from requirements",
    "blurb": "Arrays provide contiguous elements and direct indexing.",
    "standfirst": "Arrays provide contiguous elements and direct indexing.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 25 · Linked structures and ownership",
    "phaseOrder": 26,
    "order": 130,
    "minutes": 25,
    "curriculumId": "C-25.5",
    "contentType": "lesson",
    "requires": [
      "c-m25-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 25,
    "cLab": "nodes",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m26-l01",
    "file": "/notes/c-m26-l01.md",
    "title": "Binary and hexadecimal are ways to write values",
    "blurb": "A base describes the place values in a numeral.",
    "standfirst": "A base describes the place values in a numeral.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 26 · Bits, masks, and binary representation",
    "phaseOrder": 27,
    "order": 131,
    "minutes": 25,
    "curriculumId": "C-26.1",
    "contentType": "lesson",
    "requires": [
      "c-m25-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 26,
    "cLab": "bits",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m26-l02",
    "file": "/notes/c-m26-l02.md",
    "title": "Set, clear, toggle, and test a flag",
    "blurb": "A bit mask selects one or more bit positions.",
    "standfirst": "A bit mask selects one or more bit positions.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 26 · Bits, masks, and binary representation",
    "phaseOrder": 27,
    "order": 132,
    "minutes": 25,
    "curriculumId": "C-26.2",
    "contentType": "lesson",
    "requires": [
      "c-m26-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 26,
    "cLab": "bits",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m26-l03",
    "file": "/notes/c-m26-l03.md",
    "title": "Shift counts have a valid range",
    "blurb": "A left shift moves bits toward higher positions and a right shift toward lower positions.",
    "standfirst": "A left shift moves bits toward higher positions and a right shift toward lower positions.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 26 · Bits, masks, and binary representation",
    "phaseOrder": 27,
    "order": 133,
    "minutes": 25,
    "curriculumId": "C-26.3",
    "contentType": "lesson",
    "requires": [
      "c-m26-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 26,
    "cLab": "bits",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m26-l04",
    "file": "/notes/c-m26-l04.md",
    "title": "Assemble bytes without depending on machine order",
    "blurb": "Endianness describes byte ordering in a multi-byte representation.",
    "standfirst": "Endianness describes byte ordering in a multi-byte representation.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 26 · Bits, masks, and binary representation",
    "phaseOrder": 27,
    "order": 134,
    "minutes": 25,
    "curriculumId": "C-26.4",
    "contentType": "lesson",
    "requires": [
      "c-m26-l03"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 26,
    "cLab": "bits",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m26-l05",
    "file": "/notes/c-m26-l05.md",
    "title": "Bit fields, layouts, and portable boundaries",
    "blurb": "A C structure can contain bit-field members, but details such as allocation order are implementation-dependent.",
    "standfirst": "A C structure can contain bit-field members, but details such as allocation order are implementation-dependent.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 26 · Bits, masks, and binary representation",
    "phaseOrder": 27,
    "order": 135,
    "minutes": 25,
    "curriculumId": "C-26.5",
    "contentType": "lesson",
    "requires": [
      "c-m26-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 26,
    "cLab": "bits",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m27-l01",
    "file": "/notes/c-m27-l01.md",
    "title": "Read const through the pointer",
    "blurb": "const int *p allows p to be reassigned but prevents modifying the pointed-to int through that access path.",
    "standfirst": "const int *p allows p to be reassigned but prevents modifying the pointed-to int through that access path.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 27 · Deeper types and callbacks",
    "phaseOrder": 28,
    "order": 136,
    "minutes": 25,
    "curriculumId": "C-27.1",
    "contentType": "lesson",
    "requires": [
      "c-m26-l05"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 27,
    "cLab": "types",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m27-l02",
    "file": "/notes/c-m27-l02.md",
    "title": "A function pointer selects behavior",
    "blurb": "A function pointer identifies a function with a compatible signature.",
    "standfirst": "A function pointer identifies a function with a compatible signature.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 27 · Deeper types and callbacks",
    "phaseOrder": 28,
    "order": 137,
    "minutes": 25,
    "curriculumId": "C-27.2",
    "contentType": "lesson",
    "requires": [
      "c-m27-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 27,
    "cLab": "types",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m27-l03",
    "file": "/notes/c-m27-l03.md",
    "title": "Callbacks with caller-owned context",
    "blurb": "A context pointer lets a callback use caller-provided state without a global variable.",
    "standfirst": "A context pointer lets a callback use caller-provided state without a global variable.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 27 · Deeper types and callbacks",
    "phaseOrder": 28,
    "order": 138,
    "minutes": 25,
    "curriculumId": "C-27.3",
    "contentType": "lesson",
    "requires": [
      "c-m27-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 27,
    "cLab": "types",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m27-l04",
    "file": "/notes/c-m27-l04.md",
    "title": "Opaque types hide a representation",
    "blurb": "An opaque API exposes an incomplete structure declaration in its public header.",
    "standfirst": "An opaque API exposes an incomplete structure declaration in its public header.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 27 · Deeper types and callbacks",
    "phaseOrder": 28,
    "order": 139,
    "minutes": 25,
    "curriculumId": "C-27.4",
    "contentType": "lesson",
    "requires": [
      "c-m27-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 27,
    "cLab": "types",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m27-l05",
    "file": "/notes/c-m27-l05.md",
    "title": "Unions require a tag and a valid interpretation",
    "blurb": "A union shares storage among its members.",
    "standfirst": "A union shares storage among its members.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 27 · Deeper types and callbacks",
    "phaseOrder": 28,
    "order": 140,
    "minutes": 25,
    "curriculumId": "C-27.5",
    "contentType": "lesson",
    "requires": [
      "c-m27-l04"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 27,
    "cLab": "types",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m28-l01",
    "file": "/notes/c-m28-l01.md",
    "title": "Preprocessing transforms tokens before compilation",
    "blurb": "The preprocessor handles directives such as include and define before the compiler analyzes the resulting C translation unit.",
    "standfirst": "The preprocessor handles directives such as include and define before the compiler analyzes the resulting C translation unit.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 28 · The preprocessor and larger builds",
    "phaseOrder": 29,
    "order": 141,
    "minutes": 25,
    "curriculumId": "C-28.1",
    "contentType": "lesson",
    "requires": [
      "c-m27-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 28,
    "cLab": "macro",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m28-l02",
    "file": "/notes/c-m28-l02.md",
    "title": "Function-like macros can evaluate arguments twice",
    "blurb": "A function-like macro substitutes its arguments wherever they appear in its replacement.",
    "standfirst": "A function-like macro substitutes its arguments wherever they appear in its replacement.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 28 · The preprocessor and larger builds",
    "phaseOrder": 29,
    "order": 142,
    "minutes": 25,
    "curriculumId": "C-28.2",
    "contentType": "lesson",
    "requires": [
      "c-m28-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 28,
    "cLab": "macro",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m28-l03",
    "file": "/notes/c-m28-l03.md",
    "title": "Conditional compilation makes explicit variants",
    "blurb": "Conditional preprocessing includes or excludes source regions according to preprocessing expressions.",
    "standfirst": "Conditional preprocessing includes or excludes source regions according to preprocessing expressions.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 28 · The preprocessor and larger builds",
    "phaseOrder": 29,
    "order": 143,
    "minutes": 25,
    "curriculumId": "C-28.3",
    "contentType": "lesson",
    "requires": [
      "c-m28-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 28,
    "cLab": "macro",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m28-l04",
    "file": "/notes/c-m28-l04.md",
    "title": "Build dependencies determine what must be rebuilt",
    "blurb": "A build system describes outputs and the inputs that affect them.",
    "standfirst": "A build system describes outputs and the inputs that affect them.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 28 · The preprocessor and larger builds",
    "phaseOrder": 29,
    "order": 144,
    "minutes": 25,
    "curriculumId": "C-28.4",
    "contentType": "lesson",
    "requires": [
      "c-m28-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 28,
    "cLab": "macro",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m28-l05",
    "file": "/notes/c-m28-l05.md",
    "title": "Libraries, linkage, and documented interfaces",
    "blurb": "A library packages reusable implementation behind an interface.",
    "standfirst": "A library packages reusable implementation behind an interface.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 28 · The preprocessor and larger builds",
    "phaseOrder": 29,
    "order": 145,
    "minutes": 25,
    "curriculumId": "C-28.5",
    "contentType": "lesson",
    "requires": [
      "c-m28-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 28,
    "cLab": "macro",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m29-l01",
    "file": "/notes/c-m29-l01.md",
    "title": "A standard mode is part of the build contract",
    "blurb": "This course uses explicit C17 mode for its runnable portable examples.",
    "standfirst": "This course uses explicit C17 mode for its runnable portable examples.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 29 · Portability and modern C",
    "phaseOrder": 30,
    "order": 146,
    "minutes": 25,
    "curriculumId": "C-29.1",
    "contentType": "lesson",
    "requires": [
      "c-m28-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 29,
    "cLab": "portability",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m29-l02",
    "file": "/notes/c-m29-l02.md",
    "title": "Measure sizes and use the right limit headers",
    "blurb": "C guarantees minimum ranges and relationships, not one universal size for every type.",
    "standfirst": "C guarantees minimum ranges and relationships, not one universal size for every type.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 29 · Portability and modern C",
    "phaseOrder": 30,
    "order": 147,
    "minutes": 25,
    "curriculumId": "C-29.2",
    "contentType": "lesson",
    "requires": [
      "c-m29-l01"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 29,
    "cLab": "portability",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m29-l03",
    "file": "/notes/c-m29-l03.md",
    "title": "Locale, characters, and Unicode boundaries",
    "blurb": "A char stores a character-sized unit, not necessarily an entire human-visible character.",
    "standfirst": "A char stores a character-sized unit, not necessarily an entire human-visible character.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 29 · Portability and modern C",
    "phaseOrder": 30,
    "order": 148,
    "minutes": 25,
    "curriculumId": "C-29.3",
    "contentType": "lesson",
    "requires": [
      "c-m29-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 29,
    "cLab": "portability",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m29-l04",
    "file": "/notes/c-m29-l04.md",
    "title": "Explore C23 through feature checks",
    "blurb": "C23 adds and changes language facilities, including nullptr, typeof-related facilities, and checked integer arithmetic support through stdckdint.h.",
    "standfirst": "C23 adds and changes language facilities, including nullptr, typeof-related facilities, and checked integer arithmetic support through stdckdint.h.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 29 · Portability and modern C",
    "phaseOrder": 30,
    "order": 149,
    "minutes": 25,
    "curriculumId": "C-29.4",
    "contentType": "lesson",
    "requires": [
      "c-m29-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 29,
    "cLab": "portability",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m29-l05",
    "file": "/notes/c-m29-l05.md",
    "title": "Design a portability matrix",
    "blurb": "Portability is a tested claim over named environments.",
    "standfirst": "Portability is a tested claim over named environments.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 29 · Portability and modern C",
    "phaseOrder": 30,
    "order": 150,
    "minutes": 25,
    "curriculumId": "C-29.5",
    "contentType": "lesson",
    "requires": [
      "c-m29-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 29,
    "cLab": "portability",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m30-l01",
    "file": "/notes/c-m30-l01.md",
    "title": "Processes, threads, and shared state",
    "blurb": "A process is an operating-system execution environment.",
    "standfirst": "A process is an operating-system execution environment.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 30 · Systems boundaries and concurrency concepts",
    "phaseOrder": 31,
    "order": 151,
    "minutes": 25,
    "curriculumId": "C-30.1",
    "contentType": "lesson",
    "requires": [
      "c-m29-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 30,
    "cLab": "race",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m30-l02",
    "file": "/notes/c-m30-l02.md",
    "title": "A data race is more than a lost update",
    "blurb": "A lost-update diagram shows why a read-modify-write sequence is not automatically indivisible.",
    "standfirst": "A lost-update diagram shows why a read-modify-write sequence is not automatically indivisible.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 30 · Systems boundaries and concurrency concepts",
    "phaseOrder": 31,
    "order": 152,
    "minutes": 25,
    "curriculumId": "C-30.2",
    "contentType": "lesson",
    "requires": [
      "c-m30-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 30,
    "cLab": "race",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m30-l03",
    "file": "/notes/c-m30-l03.md",
    "title": "Atomics express specific synchronization operations",
    "blurb": "Atomic objects support operations that are indivisible with respect to the relevant atomic accesses.",
    "standfirst": "Atomic objects support operations that are indivisible with respect to the relevant atomic accesses.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 30 · Systems boundaries and concurrency concepts",
    "phaseOrder": 31,
    "order": 153,
    "minutes": 25,
    "curriculumId": "C-30.3",
    "contentType": "lesson",
    "requires": [
      "c-m30-l02"
    ],
    "runtimeTier": "native",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 30,
    "cLab": "race",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m30-l04",
    "file": "/notes/c-m30-l04.md",
    "title": "volatile, signals, and hardware need separate rules",
    "blurb": "volatile tells the implementation that accesses to an object have observable significance under its rules.",
    "standfirst": "volatile tells the implementation that accesses to an object have observable significance under its rules.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 30 · Systems boundaries and concurrency concepts",
    "phaseOrder": 31,
    "order": 154,
    "minutes": 25,
    "curriculumId": "C-30.4",
    "contentType": "lesson",
    "requires": [
      "c-m30-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 30,
    "cLab": "race",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m30-l05",
    "file": "/notes/c-m30-l05.md",
    "title": "Foreign interfaces and untrusted execution",
    "blurb": "C APIs often connect components written in different languages.",
    "standfirst": "C APIs often connect components written in different languages.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 30 · Systems boundaries and concurrency concepts",
    "phaseOrder": 31,
    "order": 155,
    "minutes": 25,
    "curriculumId": "C-30.5",
    "contentType": "lesson",
    "requires": [
      "c-m30-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 30,
    "cLab": "race",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m31-l01",
    "file": "/notes/c-m31-l01.md",
    "title": "Write the container contract first",
    "blurb": "A vector stores a variable number of int values contiguously.",
    "standfirst": "A vector stores a variable number of int values contiguously.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 31 · Advanced capstone: a reusable integer vector",
    "phaseOrder": 32,
    "order": 156,
    "minutes": 25,
    "curriculumId": "C-31.1",
    "contentType": "lesson",
    "requires": [
      "c-m30-l05"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 31,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m31-l02",
    "file": "/notes/c-m31-l02.md",
    "title": "Implement growth with checked arithmetic",
    "blurb": "Reserve capacity before publishing a new element.",
    "standfirst": "Reserve capacity before publishing a new element.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 31 · Advanced capstone: a reusable integer vector",
    "phaseOrder": 32,
    "order": 157,
    "minutes": 25,
    "curriculumId": "C-31.2",
    "contentType": "lesson",
    "requires": [
      "c-m31-l01"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 31,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m31-l03",
    "file": "/notes/c-m31-l03.md",
    "title": "Test boundaries and failure contracts",
    "blurb": "A container test suite should cover empty operations, first insertion, multiple growth transitions, valid and invalid indices, last-element removal, and reuse after destruction/reinitialization under the documented contract.",
    "standfirst": "A container test suite should cover empty operations, first insertion, multiple growth transitions, valid and invalid indices, last-element removal, and reuse after destruction/reinitialization under the documented contract.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 31 · Advanced capstone: a reusable integer vector",
    "phaseOrder": 32,
    "order": 158,
    "minutes": 25,
    "curriculumId": "C-31.3",
    "contentType": "lesson",
    "requires": [
      "c-m31-l02"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 31,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m31-l04",
    "file": "/notes/c-m31-l04.md",
    "title": "Package an interface another person can use",
    "blurb": "A reusable component needs more than source code.",
    "standfirst": "A reusable component needs more than source code.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 31 · Advanced capstone: a reusable integer vector",
    "phaseOrder": 32,
    "order": 159,
    "minutes": 25,
    "curriculumId": "C-31.4",
    "contentType": "lesson",
    "requires": [
      "c-m31-l03"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 31,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "c-m31-l05",
    "file": "/notes/c-m31-l05.md",
    "title": "Review your C reasoning and choose a next project",
    "blurb": "Review the complete chain: translate requirements into contracts, choose representations, maintain bounds and lifetimes, report failures, and verify observable behavior.",
    "standfirst": "Review the complete chain: translate requirements into contracts, choose representations, maintain bounds and lifetimes, report failures, and verify observable behavior.",
    "domain": "Programming",
    "section": "C · Your First Programming Language",
    "phase": "Optional · Module 31 · Advanced capstone: a reusable integer vector",
    "phaseOrder": 32,
    "order": 160,
    "minutes": 25,
    "curriculumId": "C-31.5",
    "contentType": "lesson",
    "requires": [
      "c-m31-l04"
    ],
    "runtimeTier": "concept",
    "status": "published",
    "courseId": "c",
    "coreOrOptional": "optional",
    "standard": "C17",
    "module": 31,
    "cLab": "project",
    "primarySubject": "subject-c",
    "catalogType": "lesson"
  },
  {
    "id": "fde-001",
    "file": "/notes/fde-001.md",
    "title": "FDE ownership and your diagnostic",
    "blurb": "Role boundaries: FDE, software engineer, data scientist and solutions roles.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 0 · Role, discovery and problem framing",
    "phaseOrder": 0,
    "order": 1,
    "minutes": 5,
    "standfirst": "Turn a vague customer request into a one-page brief with users, baseline, constraints, non-goals, risks and measurable acceptance criteria.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [],
    "roles": [
      "discovery",
      "architecture"
    ],
    "topics": [
      "Role boundaries: FDE, software engineer, data scientist and solutions roles",
      "Customer discovery → design → build → rollout → adoption",
      "Separate AI-assisted development from engineering AI-powered products"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-002",
    "file": "/notes/fde-002.md",
    "title": "Discover the real workflow",
    "blurb": "Interview users and map the current process.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 0 · Role, discovery and problem framing",
    "phaseOrder": 0,
    "order": 2,
    "minutes": 5,
    "standfirst": "Turn a vague customer request into a one-page brief with users, baseline, constraints, non-goals, risks and measurable acceptance criteria.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-001"
    ],
    "roles": [
      "discovery",
      "product"
    ],
    "topics": [
      "Interview users and map the current process",
      "Identify bottlenecks, exceptions, decision owners and existing workarounds",
      "Separate a requested feature from the underlying business problem"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-003",
    "file": "/notes/fde-003.md",
    "title": "Choose the simplest viable solution",
    "blurb": "Compare rules, SQL, classical ML, LLMs, retrieval and agents.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 0 · Role, discovery and problem framing",
    "phaseOrder": 0,
    "order": 3,
    "minutes": 6,
    "standfirst": "Turn a vague customer request into a one-page brief with users, baseline, constraints, non-goals, risks and measurable acceptance criteria.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-002"
    ],
    "roles": [
      "discovery",
      "architecture"
    ],
    "topics": [
      "Compare rules, SQL, classical ML, LLMs, retrieval and agents",
      "Check data availability, integration feasibility, budget and delivery constraints",
      "Define scope, non-goals, build-versus-buy and a thin end-to-end slice"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-004",
    "file": "/notes/fde-004.md",
    "title": "Define success before building",
    "blurb": "Set quality, latency, cost and safety acceptance criteria.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 0 · Role, discovery and problem framing",
    "phaseOrder": 0,
    "order": 4,
    "minutes": 5,
    "standfirst": "Turn a vague customer request into a one-page brief with users, baseline, constraints, non-goals, risks and measurable acceptance criteria.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-003"
    ],
    "roles": [
      "discovery",
      "ml",
      "product",
      "architecture"
    ],
    "topics": [
      "Set quality, latency, cost and safety acceptance criteria",
      "Measure a baseline and distinguish estimates from observations",
      "Specify prototype, pilot and production exit conditions"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-005",
    "file": "/notes/fde-005.md",
    "title": "Onboard into an unfamiliar domain",
    "blurb": "Build a domain glossary and data/system inventory.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 0 · Role, discovery and problem framing",
    "phaseOrder": 0,
    "order": 5,
    "minutes": 5,
    "standfirst": "Turn a vague customer request into a one-page brief with users, baseline, constraints, non-goals, risks and measurable acceptance criteria.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-004"
    ],
    "roles": [
      "discovery"
    ],
    "topics": [
      "Build a domain glossary and data/system inventory",
      "Map stakeholders, approvals and responsibility boundaries",
      "Identify policy questions for security, legal and domain specialists"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-006",
    "file": "/notes/fde-006.md",
    "title": "Python values, collections and functions",
    "blurb": "Mutability, aliasing, equality, scope and function arguments.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 6,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-001"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Mutability, aliasing, equality, scope and function arguments",
      "Lists, dictionaries, sets, tuples and comprehensions",
      "Iterators, generators and memory-conscious processing"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-007",
    "file": "/notes/fde-007.md",
    "title": "Errors and resource ownership",
    "blurb": "Exceptions, propagation, useful error messages and cleanup.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 7,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-006"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Exceptions, propagation, useful error messages and cleanup",
      "Context managers, files, sockets and connection lifetimes",
      "Handle partial failure without silently losing data"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-008",
    "file": "/notes/fde-008.md",
    "title": "Typed and maintainable Python",
    "blurb": "Type hints, dataclasses, schemas and boundary validation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 8,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-007"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Type hints, dataclasses, schemas and boundary validation",
      "Modules, packages, virtual environments and pinned dependencies",
      "Composition, interfaces, dependency injection and readable design"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-009",
    "file": "/notes/fde-009.md",
    "title": "Concurrency that you can explain",
    "blurb": "Async versus threads versus processes; I/O-bound versus CPU-bound work.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 9,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-008"
    ],
    "roles": [
      "software",
      "reliability"
    ],
    "topics": [
      "Async versus threads versus processes; I/O-bound versus CPU-bound work",
      "Timeouts, cancellation, bounded concurrency and backpressure",
      "Blocking event loops, race conditions and safe shared state"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-010",
    "file": "/notes/fde-010.md",
    "title": "Practical data structures and algorithms",
    "blurb": "Complexity, strings, hash maps, queues, stacks and heaps.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 10,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-009"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Complexity, strings, hash maps, queues, stacks and heaps",
      "Sorting, binary search, intervals and basic tree/graph traversal",
      "Deduplication, top-k, dependency ordering and edge-case tests"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-011",
    "file": "/notes/fde-011.md",
    "title": "Debug, profile and refactor",
    "blurb": "Read traces, reproduce failures and isolate root causes.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 1 · Production Python and practical coding",
    "phaseOrder": 1,
    "order": 11,
    "minutes": 5,
    "standfirst": "Without AI, write a tested data-processing function and debug an async service failure; then explain complexity, error handling and resource limits.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-010"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Read traces, reproduce failures and isolate root causes",
      "Unit tests, fixtures, mocks and property-oriented checks",
      "Profile time and memory before selecting an optimization"
    ],
    "primarySubject": "subject-python",
    "catalogType": "lesson"
  },
  {
    "id": "fde-012",
    "file": "/notes/fde-012.md",
    "title": "SQL querying with correct grain",
    "blurb": "Filtering, grouping, joins, subqueries and common table expressions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 2 · SQL, data models and persistence",
    "phaseOrder": 2,
    "order": 12,
    "minutes": 5,
    "standfirst": "Design an auditable job/tenant/document schema, write correct joins and window queries, and explain a transaction and a slow query plan.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-006"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Filtering, grouping, joins, subqueries and common table expressions",
      "Nulls, duplicate rows, date boundaries and join cardinality",
      "Parameterize values and validate identifiers instead of concatenating SQL"
    ],
    "primarySubject": "subject-sql",
    "catalogType": "lesson"
  },
  {
    "id": "fde-013",
    "file": "/notes/fde-013.md",
    "title": "Window functions and business questions",
    "blurb": "Ranking, running totals, lag/lead and time-window analysis.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 2 · SQL, data models and persistence",
    "phaseOrder": 2,
    "order": 13,
    "minutes": 5,
    "standfirst": "Design an auditable job/tenant/document schema, write correct joins and window queries, and explain a transaction and a slow query plan.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-012"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Ranking, running totals, lag/lead and time-window analysis",
      "Deduplicate records and select the latest valid state",
      "Check query results against business definitions and sample records"
    ],
    "primarySubject": "subject-sql",
    "catalogType": "lesson"
  },
  {
    "id": "fde-014",
    "file": "/notes/fde-014.md",
    "title": "Schemas, constraints and transactions",
    "blurb": "Entities, keys, normalization and deliberate denormalization.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 2 · SQL, data models and persistence",
    "phaseOrder": 2,
    "order": 14,
    "minutes": 5,
    "standfirst": "Design an auditable job/tenant/document schema, write correct joins and window queries, and explain a transaction and a slow query plan.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-013"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Entities, keys, normalization and deliberate denormalization",
      "Constraints, transactions, isolation and concurrency anomalies",
      "Tenant ownership, audit trails and idempotency records"
    ],
    "primarySubject": "subject-sql",
    "catalogType": "lesson"
  },
  {
    "id": "fde-015",
    "file": "/notes/fde-015.md",
    "title": "Indexes and query performance",
    "blurb": "Index selection, selectivity and execution plans.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 2 · SQL, data models and persistence",
    "phaseOrder": 2,
    "order": 15,
    "minutes": 5,
    "standfirst": "Design an auditable job/tenant/document schema, write correct joins and window queries, and explain a transaction and a slow query plan.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-014"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Index selection, selectivity and execution plans",
      "Connection pooling, batching and pagination",
      "Measure trade-offs between read speed, write cost and storage"
    ],
    "primarySubject": "subject-sql",
    "catalogType": "lesson"
  },
  {
    "id": "fde-016",
    "file": "/notes/fde-016.md",
    "title": "Choose an appropriate data store",
    "blurb": "Relational stores, object storage, caches and search indexes.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 2 · SQL, data models and persistence",
    "phaseOrder": 2,
    "order": 16,
    "minutes": 5,
    "standfirst": "Design an auditable job/tenant/document schema, write correct joins and window queries, and explain a transaction and a slow query plan.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-015"
    ],
    "roles": [
      "data",
      "architecture"
    ],
    "topics": [
      "Relational stores, object storage, caches and search indexes",
      "Operational versus analytical workloads and data freshness",
      "Data contracts, migrations and keeping a clear source of truth"
    ],
    "primarySubject": "subject-sql",
    "catalogType": "lesson"
  },
  {
    "id": "fde-017",
    "file": "/notes/fde-017.md",
    "title": "HTTP and API contracts",
    "blurb": "HTTP methods, status codes, headers and request lifecycles.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 17,
    "minutes": 5,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-008",
      "fde-014"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "HTTP methods, status codes, headers and request lifecycles",
      "REST resources, pagination, versioning and error contracts",
      "DNS/TLS basics, browser/server boundaries and CORS"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-018",
    "file": "/notes/fde-018.md",
    "title": "Build a structured FastAPI service",
    "blurb": "Request/response schemas, routes and dependency boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 18,
    "minutes": 5,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-017"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Request/response schemas, routes and dependency boundaries",
      "Configuration, environment separation and safe startup",
      "API documentation and testable service layers"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-019",
    "file": "/notes/fde-019.md",
    "title": "Authentication is not authorization",
    "blurb": "Sessions, tokens, OAuth/OIDC concepts and service identities.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 19,
    "minutes": 6,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-018"
    ],
    "roles": [
      "software",
      "security"
    ],
    "topics": [
      "Sessions, tokens, OAuth/OIDC concepts and service identities",
      "Roles, tenant boundaries and ownership checks",
      "Deny by default; validate authorization on the server"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-020",
    "file": "/notes/fde-020.md",
    "title": "Connect services to persistent data",
    "blurb": "Transactions, migrations, connection lifetimes and repository boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 20,
    "minutes": 5,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-019"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Transactions, migrations, connection lifetimes and repository boundaries",
      "Input validation and consistent domain errors",
      "Avoid duplicate writes and accidental cross-tenant access"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-021",
    "file": "/notes/fde-021.md",
    "title": "Integrate external APIs and webhooks",
    "blurb": "API pagination, rate limits, signatures and secret rotation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 21,
    "minutes": 5,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-020"
    ],
    "roles": [
      "software",
      "agents"
    ],
    "topics": [
      "API pagination, rate limits, signatures and secret rotation",
      "Retry transient failures with budgets and backoff",
      "Handle duplicate events, out-of-order events and provider changes"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-022",
    "file": "/notes/fde-022.md",
    "title": "Jobs, streaming and long-running work",
    "blurb": "Request-response versus queued jobs and polling.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 3 · APIs, backend services and integration",
    "phaseOrder": 3,
    "order": 22,
    "minutes": 5,
    "standfirst": "Ship a small authenticated API with validated inputs, persistent state, a background job and explicit failure states; prove one unauthorized request is rejected.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-021"
    ],
    "roles": [
      "software",
      "agents",
      "reliability"
    ],
    "topics": [
      "Request-response versus queued jobs and polling",
      "Streaming responses, disconnects, cancellation and timeouts",
      "Track pending, running, failed and completed states honestly"
    ],
    "primarySubject": "subject-backend",
    "catalogType": "lesson"
  },
  {
    "id": "fde-023",
    "file": "/notes/fde-023.md",
    "title": "Read structured and unstructured sources",
    "blurb": "Files, databases, APIs and connector permissions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 4 · Data ingestion and engineering",
    "phaseOrder": 4,
    "order": 23,
    "minutes": 5,
    "standfirst": "Build an ingestion pipeline that can be rerun safely, quarantines bad records, records lineage and propagates updates/deletions.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-012",
      "fde-017"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Files, databases, APIs and connector permissions",
      "CSV/JSON/Parquet plus document parsing and OCR fallback where needed",
      "Track source IDs, versions, timestamps and provenance"
    ],
    "primarySubject": "subject-data-engineering",
    "catalogType": "lesson"
  },
  {
    "id": "fde-024",
    "file": "/notes/fde-024.md",
    "title": "Reliable ETL and ELT pipelines",
    "blurb": "Batch versus streaming and extract/transform/load boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 4 · Data ingestion and engineering",
    "phaseOrder": 4,
    "order": 24,
    "minutes": 5,
    "standfirst": "Build an ingestion pipeline that can be rerun safely, quarantines bad records, records lineage and propagates updates/deletions.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-023"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Batch versus streaming and extract/transform/load boundaries",
      "Incremental updates, checkpoints, backfills and idempotent reruns",
      "Scheduling, orchestration and recoverable failure states"
    ],
    "primarySubject": "subject-data-engineering",
    "catalogType": "lesson"
  },
  {
    "id": "fde-025",
    "file": "/notes/fde-025.md",
    "title": "Data quality and contracts",
    "blurb": "Schema validation, nulls, duplicates and reference integrity.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 4 · Data ingestion and engineering",
    "phaseOrder": 4,
    "order": 25,
    "minutes": 6,
    "standfirst": "Build an ingestion pipeline that can be rerun safely, quarantines bad records, records lineage and propagates updates/deletions.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-024"
    ],
    "roles": [
      "data",
      "ml"
    ],
    "topics": [
      "Schema validation, nulls, duplicates and reference integrity",
      "Freshness, completeness, lineage and reconciliation",
      "Quarantine bad data; surface quality failures to operators"
    ],
    "primarySubject": "subject-data-engineering",
    "catalogType": "lesson"
  },
  {
    "id": "fde-026",
    "file": "/notes/fde-026.md",
    "title": "Choose pandas, SQL or distributed processing",
    "blurb": "Measure data size, memory, operation cost and available infrastructure.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 4 · Data ingestion and engineering",
    "phaseOrder": 4,
    "order": 26,
    "minutes": 6,
    "standfirst": "Build an ingestion pipeline that can be rerun safely, quarantines bad records, records lineage and propagates updates/deletions.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-025"
    ],
    "roles": [
      "data",
      "architecture"
    ],
    "topics": [
      "Measure data size, memory, operation cost and available infrastructure",
      "Compare SQL pushdown, columnar/local engines and distributed execution",
      "Use evidence to justify Spark rather than assuming bigger means Spark"
    ],
    "primarySubject": "subject-data-engineering",
    "catalogType": "lesson"
  },
  {
    "id": "fde-027",
    "file": "/notes/fde-027.md",
    "title": "PySpark production refresher",
    "blurb": "Lazy execution, partitions, transformations and actions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 4 · Data ingestion and engineering",
    "phaseOrder": 4,
    "order": 27,
    "minutes": 5,
    "standfirst": "Build an ingestion pipeline that can be rerun safely, quarantines bad records, records lineage and propagates updates/deletions.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-026"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Lazy execution, partitions, transformations and actions",
      "Joins, shuffles, skew, caching and avoiding accidental collection",
      "Inspect plans and tune a representative workload"
    ],
    "primarySubject": "subject-data-engineering",
    "catalogType": "lesson"
  },
  {
    "id": "fde-028",
    "file": "/notes/fde-028.md",
    "title": "Refresh applied ML judgement",
    "blurb": "Leakage-safe splits, imbalance, baselines and reproducibility.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 5 · ML and LLM foundations for delivery",
    "phaseOrder": 5,
    "order": 28,
    "minutes": 6,
    "standfirst": "Explain why a chosen model and baseline fit the task, how they are evaluated, and what failures cannot be solved by changing a prompt.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-004",
      "fde-006"
    ],
    "roles": [
      "ml"
    ],
    "topics": [
      "Leakage-safe splits, imbalance, baselines and reproducibility",
      "Precision/recall, threshold choice, calibration and business costs",
      "Batch/online inference, monitoring, drift and model versioning"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-029",
    "file": "/notes/fde-029.md",
    "title": "Statistics for product evidence",
    "blurb": "Sampling, uncertainty and representative evaluation data.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 5 · ML and LLM foundations for delivery",
    "phaseOrder": 5,
    "order": 29,
    "minutes": 6,
    "standfirst": "Explain why a chosen model and baseline fit the task, how they are evaluated, and what failures cannot be solved by changing a prompt.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-028"
    ],
    "roles": [
      "ml"
    ],
    "topics": [
      "Sampling, uncertainty and representative evaluation data",
      "A/B tests, confounding and before/after comparisons",
      "Avoid converting a model metric into an unsupported business claim"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-030",
    "file": "/notes/fde-030.md",
    "title": "Understand LLM inference",
    "blurb": "Tokens, embeddings, attention and transformer intuition.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 5 · ML and LLM foundations for delivery",
    "phaseOrder": 5,
    "order": 30,
    "minutes": 5,
    "standfirst": "Explain why a chosen model and baseline fit the task, how they are evaluated, and what failures cannot be solved by changing a prompt.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-029"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Tokens, embeddings, attention and transformer intuition",
      "Context limits, sampling, variability and hallucinations",
      "Pretraining versus adaptation versus inference; reasoning is not verified truth"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-031",
    "file": "/notes/fde-031.md",
    "title": "Select and compare models",
    "blurb": "Task-specific quality, latency, cost and context needs.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 5 · ML and LLM foundations for delivery",
    "phaseOrder": 5,
    "order": 31,
    "minutes": 5,
    "standfirst": "Explain why a chosen model and baseline fit the task, how they are evaluated, and what failures cannot be solved by changing a prompt.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-030"
    ],
    "roles": [
      "ml",
      "ai",
      "architecture"
    ],
    "topics": [
      "Task-specific quality, latency, cost and context needs",
      "Hosted versus open-weight models, licenses and deployment constraints",
      "Benchmark representative tasks and record model/configuration versions"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-032",
    "file": "/notes/fde-032.md",
    "title": "Inference performance fundamentals",
    "blurb": "Throughput, concurrency, batching and rate limits.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 5 · ML and LLM foundations for delivery",
    "phaseOrder": 5,
    "order": 32,
    "minutes": 5,
    "standfirst": "Explain why a chosen model and baseline fit the task, how they are evaluated, and what failures cannot be solved by changing a prompt.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-031"
    ],
    "roles": [
      "ai",
      "reliability"
    ],
    "topics": [
      "Throughput, concurrency, batching and rate limits",
      "Caching and bounded retries; distinguish latency from token throughput",
      "Estimate end-to-end cost including retrieval, tools and review"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-033",
    "file": "/notes/fde-033.md",
    "title": "Write clear task contracts",
    "blurb": "Instructions, examples, constraints and explicit output expectations.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 6 · Prompting, structured outputs and context",
    "phaseOrder": 6,
    "order": 33,
    "minutes": 5,
    "standfirst": "Build a schema-validated extraction flow, test difficult inputs, and demonstrate that well-formed output can still be factually wrong.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-030",
      "fde-018"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Instructions, examples, constraints and explicit output expectations",
      "Separate trusted instructions from untrusted task data",
      "Test ambiguity and missing information rather than adding prompt length"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-034",
    "file": "/notes/fde-034.md",
    "title": "Structured outputs and validation",
    "blurb": "Schemas, typed outputs and tool argument validation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 6 · Prompting, structured outputs and context",
    "phaseOrder": 6,
    "order": 34,
    "minutes": 5,
    "standfirst": "Build a schema-validated extraction flow, test difficult inputs, and demonstrate that well-formed output can still be factually wrong.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-033"
    ],
    "roles": [
      "ai",
      "agents",
      "product"
    ],
    "topics": [
      "Schemas, typed outputs and tool argument validation",
      "Separate format compliance from semantic correctness",
      "Handle refusal, missing fields, invalid data and recovery paths"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-035",
    "file": "/notes/fde-035.md",
    "title": "Engineer the full context",
    "blurb": "Select documents, tool results, history and state deliberately.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 6 · Prompting, structured outputs and context",
    "phaseOrder": 6,
    "order": 35,
    "minutes": 5,
    "standfirst": "Build a schema-validated extraction flow, test difficult inputs, and demonstrate that well-formed output can still be factually wrong.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-034"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Select documents, tool results, history and state deliberately",
      "Compaction, token budgeting and provenance",
      "Memory retention, user consent, stale context and deletion"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-036",
    "file": "/notes/fde-036.md",
    "title": "Version and test prompts",
    "blurb": "Version prompts with models, tools and data assumptions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 6 · Prompting, structured outputs and context",
    "phaseOrder": 6,
    "order": 36,
    "minutes": 5,
    "standfirst": "Build a schema-validated extraction flow, test difficult inputs, and demonstrate that well-formed output can still be factually wrong.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-035"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Version prompts with models, tools and data assumptions",
      "Use regression cases and compare configurations",
      "Document failure modes and define a safe rollback"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-037",
    "file": "/notes/fde-037.md",
    "title": "Decide when retrieval is needed",
    "blurb": "Compare keyword search, direct context, SQL and RAG baselines.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 37,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-023",
      "fde-025",
      "fde-033"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Compare keyword search, direct context, SQL and RAG baselines",
      "Define the corpus, query types, freshness and citation requirements",
      "Do not select vector search merely because the product is a chatbot"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-038",
    "file": "/notes/fde-038.md",
    "title": "Parse, chunk and preserve meaning",
    "blurb": "Document structure, headings, tables, source spans and metadata.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 38,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-037"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Document structure, headings, tables, source spans and metadata",
      "Chunk boundaries, overlap, parent-child relationships and deduplication",
      "Keep source pointers and handle malformed or low-quality documents"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-039",
    "file": "/notes/fde-039.md",
    "title": "Index and retrieve relevant evidence",
    "blurb": "Keyword/BM25, embeddings, vector similarity and hybrid retrieval.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 39,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-038"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Keyword/BM25, embeddings, vector similarity and hybrid retrieval",
      "Metadata filters, top-k, approximate search and index trade-offs",
      "Choose and benchmark an index using workload evidence"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-040",
    "file": "/notes/fde-040.md",
    "title": "Improve difficult retrieval",
    "blurb": "Query reformulation, decomposition and reranking.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 40,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-039"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Query reformulation, decomposition and reranking",
      "Multi-turn queries, ambiguous entities and structured filters",
      "Measure when added stages improve quality enough to justify cost"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-041",
    "file": "/notes/fde-041.md",
    "title": "Answer with evidence and boundaries",
    "blurb": "Context assembly, answer grounding and source citations.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 41,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-040"
    ],
    "roles": [
      "ai",
      "product"
    ],
    "topics": [
      "Context assembly, answer grounding and source citations",
      "Conflicting sources, stale evidence and unsupported questions",
      "Abstain or ask a clarifying question when evidence is inadequate"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-042",
    "file": "/notes/fde-042.md",
    "title": "Permissions and corpus lifecycle",
    "blurb": "Carry document access rules from ingestion through retrieval.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 42,
    "minutes": 5,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-041",
      "fde-061"
    ],
    "roles": [
      "data",
      "ai",
      "security"
    ],
    "topics": [
      "Carry document access rules from ingestion through retrieval",
      "Prevent leakage through snippets, citations, caches and logs",
      "Revoke access and propagate updates/deletions to derived artifacts"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-043",
    "file": "/notes/fde-043.md",
    "title": "Evaluate and debug RAG",
    "blurb": "Separate retrieval failures from answer-generation failures.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 7 · Retrieval and enterprise RAG",
    "phaseOrder": 7,
    "order": 43,
    "minutes": 6,
    "standfirst": "Build a cited document assistant with a stated test corpus, access controls, deletion propagation, an evaluation set and a diagnosable retrieval trace.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-042"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Separate retrieval failures from answer-generation failures",
      "Recall/precision at k, ranking, groundedness and citation checks",
      "Inspect representative, negative and adversarial cases"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-044",
    "file": "/notes/fde-044.md",
    "title": "Workflows versus autonomous agents",
    "blurb": "Deterministic orchestration, routing and constrained agent loops.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 44,
    "minutes": 5,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-034",
      "fde-019"
    ],
    "roles": [
      "agents",
      "architecture"
    ],
    "topics": [
      "Deterministic orchestration, routing and constrained agent loops",
      "Single-agent designs before multi-agent coordination",
      "Choose autonomy only where its quality/cost/risk trade-off is justified"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-045",
    "file": "/notes/fde-045.md",
    "title": "Design useful and safe tools",
    "blurb": "Narrow interfaces, argument schemas and explicit return types.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 45,
    "minutes": 5,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-044",
      "fde-060"
    ],
    "roles": [
      "agents",
      "security"
    ],
    "topics": [
      "Narrow interfaces, argument schemas and explicit return types",
      "Read-only versus write capabilities and authorization checks",
      "Tool errors, malicious results and meaningful success evidence"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-046",
    "file": "/notes/fde-046.md",
    "title": "Bound execution and control actions",
    "blurb": "Step, time and cost budgets; stop conditions and cancellation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 46,
    "minutes": 5,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-045",
      "fde-062"
    ],
    "roles": [
      "agents",
      "security"
    ],
    "topics": [
      "Step, time and cost budgets; stop conditions and cancellation",
      "Idempotency, retry safety and approval gates",
      "Escalate uncertain or irreversible actions instead of blindly retrying"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-047",
    "file": "/notes/fde-047.md",
    "title": "Durable state and recovery",
    "blurb": "State machines, checkpoints, resumability and event history.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 47,
    "minutes": 5,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-046"
    ],
    "roles": [
      "agents"
    ],
    "topics": [
      "State machines, checkpoints, resumability and event history",
      "Concurrency, partial completion and compensating actions",
      "Reconcile ambiguous external outcomes before re-executing a write"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-048",
    "file": "/notes/fde-048.md",
    "title": "MCP integration fundamentals",
    "blurb": "Hosts, clients, servers; tools, resources and prompts.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 48,
    "minutes": 5,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-047",
      "fde-019"
    ],
    "roles": [
      "agents",
      "security"
    ],
    "topics": [
      "Hosts, clients, servers; tools, resources and prompts",
      "Transport/version compatibility, discovery and capability boundaries",
      "Authentication, authorization, consent and server trust"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-049",
    "file": "/notes/fde-049.md",
    "title": "Evaluate orchestration choices",
    "blurb": "Understand what an SDK/framework adds and hides.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 8 · Agents, tools, state and MCP",
    "phaseOrder": 8,
    "order": 49,
    "minutes": 6,
    "standfirst": "Implement a bounded tool-using workflow that survives a timeout, rejects unauthorized actions and cannot execute a sensitive write without approval.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-048"
    ],
    "roles": [
      "agents",
      "architecture"
    ],
    "topics": [
      "Understand what an SDK/framework adds and hides",
      "Compare one framework with a direct implementation",
      "Know when handoffs or sub-agents are unnecessary"
    ],
    "primarySubject": "subject-ai-systems",
    "catalogType": "lesson"
  },
  {
    "id": "fde-050",
    "file": "/notes/fde-050.md",
    "title": "Specify work before prompting",
    "blurb": "Problem brief, acceptance tests and architecture constraints.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 9 · AI-assisted software engineering",
    "phaseOrder": 9,
    "order": 50,
    "minutes": 5,
    "standfirst": "Use AI to implement a scoped change, then explain the diff, run independent tests, repair a seeded bug without AI and produce a reviewable commit.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-008",
      "fde-018"
    ],
    "roles": [
      "software",
      "architecture"
    ],
    "topics": [
      "Problem brief, acceptance tests and architecture constraints",
      "Break work into reviewable tasks with explicit interfaces",
      "Record assumptions, decisions and dependency risks"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-051",
    "file": "/notes/fde-051.md",
    "title": "Work safely in a real repository",
    "blurb": "Git branches, commits, diffs, merges and code review.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 9 · AI-assisted software engineering",
    "phaseOrder": 9,
    "order": 51,
    "minutes": 5,
    "standfirst": "Use AI to implement a scoped change, then explain the diff, run independent tests, repair a seeded bug without AI and produce a reviewable commit.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-050"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Git branches, commits, diffs, merges and code review",
      "Understand an unfamiliar codebase before editing it",
      "Dependency locks, migrations and reproducible environments"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-052",
    "file": "/notes/fde-052.md",
    "title": "Verify generated implementations",
    "blurb": "Read and explain code; inspect data flows and boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 9 · AI-assisted software engineering",
    "phaseOrder": 9,
    "order": 52,
    "minutes": 5,
    "standfirst": "Use AI to implement a scoped change, then explain the diff, run independent tests, repair a seeded bug without AI and produce a reviewable commit.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-051"
    ],
    "roles": [
      "software"
    ],
    "topics": [
      "Read and explain code; inspect data flows and boundaries",
      "Write independent tests, lint/type checks and failure scenarios",
      "Find fabricated APIs, unsafe defaults and misleading success claims"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-053",
    "file": "/notes/fde-053.md",
    "title": "Coordinate coding agents responsibly",
    "blurb": "Bound context, file ownership and parallel task interfaces.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 9 · AI-assisted software engineering",
    "phaseOrder": 9,
    "order": 53,
    "minutes": 5,
    "standfirst": "Use AI to implement a scoped change, then explain the diff, run independent tests, repair a seeded bug without AI and produce a reviewable commit.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-052"
    ],
    "roles": [
      "software",
      "agents",
      "architecture"
    ],
    "topics": [
      "Bound context, file ownership and parallel task interfaces",
      "Checkpoints, review gates and conflict resolution",
      "Measure useful throughput rather than prompt count or code volume"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-054",
    "file": "/notes/fde-054.md",
    "title": "Test the software around the model",
    "blurb": "Unit, integration, contract and end-to-end tests.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 54,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-004",
      "fde-018",
      "fde-030"
    ],
    "roles": [
      "software",
      "security"
    ],
    "topics": [
      "Unit, integration, contract and end-to-end tests",
      "Fixtures, mocks, deterministic boundaries and failure injection",
      "Test retries, authorization, concurrency and migrations"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-055",
    "file": "/notes/fde-055.md",
    "title": "Build a representative evaluation set",
    "blurb": "Define task objectives and label criteria with domain input.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 55,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-054"
    ],
    "roles": [
      "ml",
      "ai"
    ],
    "topics": [
      "Define task objectives and label criteria with domain input",
      "Include normal, edge, unsupported and adversarial examples",
      "Use held-out cases; document sample size and coverage gaps"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-056",
    "file": "/notes/fde-056.md",
    "title": "Measure AI output quality",
    "blurb": "Task accuracy, grounding, citation correctness and abstention.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 56,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-055"
    ],
    "roles": [
      "ml",
      "ai"
    ],
    "topics": [
      "Task accuracy, grounding, citation correctness and abstention",
      "Retrieval metrics versus answer metrics",
      "Human review and calibrated model judges; avoid one magic score"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-057",
    "file": "/notes/fde-057.md",
    "title": "Evaluate tool-using systems",
    "blurb": "Tool selection, argument validity and task completion.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 57,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-056",
      "fde-046"
    ],
    "roles": [
      "agents"
    ],
    "topics": [
      "Tool selection, argument validity and task completion",
      "Unauthorized attempts, duplicate actions and recovery outcomes",
      "Regression tests across model, prompt, tool and data changes"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-058",
    "file": "/notes/fde-058.md",
    "title": "Trace and monitor production behaviour",
    "blurb": "Correlate requests, retrieval, model calls and tool executions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 58,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-057",
      "fde-022"
    ],
    "roles": [
      "reliability"
    ],
    "topics": [
      "Correlate requests, retrieval, model calls and tool executions",
      "Logs, metrics, traces; p50/p95 latency and failure rates",
      "Redact sensitive data; set alert ownership and investigation steps"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-059",
    "file": "/notes/fde-059.md",
    "title": "Measure adoption, cost and improvement",
    "blurb": "Track completion, user correction, adoption and workflow outcomes.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 10 · Testing, AI evaluation and observability",
    "phaseOrder": 10,
    "order": 59,
    "minutes": 5,
    "standfirst": "Create a reproducible evaluation report and trace a failing request end to end, with quality, safety, latency and cost reported separately.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-058"
    ],
    "roles": [
      "discovery",
      "ml",
      "product"
    ],
    "topics": [
      "Track completion, user correction, adoption and workflow outcomes",
      "Cost per successful task, tool usage and human review effort",
      "Compare variants with baselines and uncertainty, not anecdotes"
    ],
    "primarySubject": "subject-testing",
    "catalogType": "lesson"
  },
  {
    "id": "fde-060",
    "file": "/notes/fde-060.md",
    "title": "Threat-model the full application",
    "blurb": "Assets, attackers, entry points and trust boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 11 · Security and enterprise boundaries",
    "phaseOrder": 11,
    "order": 60,
    "minutes": 5,
    "standfirst": "Demonstrate denied cross-tenant access, a blocked unauthorized tool action, a safe prompt-injection failure and traceable access revocation.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-019"
    ],
    "roles": [
      "security"
    ],
    "topics": [
      "Assets, attackers, entry points and trust boundaries",
      "Prompt injection through retrieved content and tool results",
      "Secrets exposure, dependency risks and data exfiltration"
    ],
    "primarySubject": "subject-security",
    "catalogType": "lesson"
  },
  {
    "id": "fde-061",
    "file": "/notes/fde-061.md",
    "title": "Enforce identity and data isolation",
    "blurb": "Tenant, user, group and document authorization.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 11 · Security and enterprise boundaries",
    "phaseOrder": 11,
    "order": 61,
    "minutes": 5,
    "standfirst": "Demonstrate denied cross-tenant access, a blocked unauthorized tool action, a safe prompt-injection failure and traceable access revocation.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-060"
    ],
    "roles": [
      "security"
    ],
    "topics": [
      "Tenant, user, group and document authorization",
      "Apply checks before sensitive data reaches a model or user",
      "Permissions-aware caching and revocation propagation"
    ],
    "primarySubject": "subject-security",
    "catalogType": "lesson"
  },
  {
    "id": "fde-062",
    "file": "/notes/fde-062.md",
    "title": "Constrain agents and execution environments",
    "blurb": "Least privilege, sandboxing and controlled network/file access.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 11 · Security and enterprise boundaries",
    "phaseOrder": 11,
    "order": 62,
    "minutes": 5,
    "standfirst": "Demonstrate denied cross-tenant access, a blocked unauthorized tool action, a safe prompt-injection failure and traceable access revocation.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-061"
    ],
    "roles": [
      "agents",
      "security"
    ],
    "topics": [
      "Least privilege, sandboxing and controlled network/file access",
      "Allowlisted capabilities, approval policies and output validation",
      "Read-only defaults and security-focused failure tests"
    ],
    "primarySubject": "subject-security",
    "catalogType": "lesson"
  },
  {
    "id": "fde-063",
    "file": "/notes/fde-063.md",
    "title": "Integrate enterprise identity and infrastructure",
    "blurb": "SSO/OIDC, service accounts and secret management.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 11 · Security and enterprise boundaries",
    "phaseOrder": 11,
    "order": 63,
    "minutes": 5,
    "standfirst": "Demonstrate denied cross-tenant access, a blocked unauthorized tool action, a safe prompt-injection failure and traceable access revocation.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-062"
    ],
    "roles": [
      "security"
    ],
    "topics": [
      "SSO/OIDC, service accounts and secret management",
      "Network boundaries, private connectivity and connector ownership",
      "Work with customer identity/security teams and existing systems"
    ],
    "primarySubject": "subject-security",
    "catalogType": "lesson"
  },
  {
    "id": "fde-064",
    "file": "/notes/fde-064.md",
    "title": "Govern data and operational risk",
    "blurb": "Classification, retention, deletion, residency and audit evidence.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 11 · Security and enterprise boundaries",
    "phaseOrder": 11,
    "order": 64,
    "minutes": 5,
    "standfirst": "Demonstrate denied cross-tenant access, a blocked unauthorized tool action, a safe prompt-injection failure and traceable access revocation.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-063"
    ],
    "roles": [
      "security"
    ],
    "topics": [
      "Classification, retention, deletion, residency and audit evidence",
      "Data handling reviews and provider/service configuration checks",
      "Incident response and specialist sign-off; do not claim compliance from a checklist"
    ],
    "primarySubject": "subject-security",
    "catalogType": "lesson"
  },
  {
    "id": "fde-065",
    "file": "/notes/fde-065.md",
    "title": "Translate requirements into capacity",
    "blurb": "Functional/nonfunctional requirements and explicit assumptions.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 65,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-022",
      "fde-014"
    ],
    "roles": [
      "ml",
      "reliability",
      "architecture"
    ],
    "topics": [
      "Functional/nonfunctional requirements and explicit assumptions",
      "Users, concurrency, document sizes, growth and access patterns",
      "Estimate storage, throughput, latency and operating cost"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-066",
    "file": "/notes/fde-066.md",
    "title": "Design reliable service boundaries",
    "blurb": "Modular monolith versus services; synchronous versus asynchronous work.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 66,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-065"
    ],
    "roles": [
      "reliability",
      "architecture"
    ],
    "topics": [
      "Modular monolith versus services; synchronous versus asynchronous work",
      "Queues, caches, load balancing, retries and backpressure",
      "Delivery semantics, consistency and duplicate-safe processing"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-067",
    "file": "/notes/fde-067.md",
    "title": "Linux, networking and containers",
    "blurb": "Processes, files, permissions, shell diagnostics and environment configuration.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 67,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-066"
    ],
    "roles": [
      "reliability"
    ],
    "topics": [
      "Processes, files, permissions, shell diagnostics and environment configuration",
      "DNS, TLS, ports, proxies and connectivity debugging",
      "Docker images, volumes, networks and health checks"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-068",
    "file": "/notes/fde-068.md",
    "title": "Learn one cloud deployment path deeply",
    "blurb": "Compute/container hosting, managed database and object storage.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 68,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-067"
    ],
    "roles": [
      "reliability",
      "architecture"
    ],
    "topics": [
      "Compute/container hosting, managed database and object storage",
      "Identity, network boundaries, queueing and observability",
      "Compare customer constraints; map concepts to a second cloud later"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-069",
    "file": "/notes/fde-069.md",
    "title": "Build a release pipeline",
    "blurb": "CI checks, artifacts, staging and configuration separation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 69,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-068",
      "fde-051",
      "fde-054"
    ],
    "roles": [
      "reliability",
      "architecture"
    ],
    "topics": [
      "CI checks, artifacts, staging and configuration separation",
      "Migrations, secrets, feature flags and rollout strategy",
      "Rollback plans and explicit release evidence"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-070",
    "file": "/notes/fde-070.md",
    "title": "Operate and recover the system",
    "blurb": "Service objectives, dashboards, runbooks and incident triage.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 70,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-069"
    ],
    "roles": [
      "reliability",
      "architecture"
    ],
    "topics": [
      "Service objectives, dashboards, runbooks and incident triage",
      "Backup/restore, dependency outages and disaster-recovery basics",
      "Load tests, capacity limits and graceful degradation"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-071",
    "file": "/notes/fde-071.md",
    "title": "Defend scaling and cost trade-offs",
    "blurb": "Isolation, throttling, caching and model/provider fallback.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 12 · System design, cloud and production operations",
    "phaseOrder": 12,
    "order": 71,
    "minutes": 5,
    "standfirst": "Deploy a service plus worker and datastore on one cloud, size a stated workload, recover a failed job, and demonstrate rollback or restore.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [
      "ml",
      "reliability",
      "architecture"
    ],
    "topics": [
      "Isolation, throttling, caching and model/provider fallback",
      "Cost attribution and resource budgets by workload or tenant",
      "Explain why added infrastructure is or is not worth its complexity"
    ],
    "primarySubject": "subject-infrastructure",
    "catalogType": "lesson"
  },
  {
    "id": "fde-072",
    "file": "/notes/fde-072.md",
    "title": "TypeScript and frontend fundamentals",
    "blurb": "Types, async requests and browser/server responsibilities.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 13 · Full-stack delivery and AI user experience",
    "phaseOrder": 13,
    "order": 72,
    "minutes": 5,
    "standfirst": "Ship a usable interface that handles streaming, failure, approval and citations, then test a real task on mobile and desktop.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-017"
    ],
    "roles": [
      "product"
    ],
    "topics": [
      "Types, async requests and browser/server responsibilities",
      "Components, state and forms in one stack such as React",
      "Reuse an established design system instead of rebuilding every layer"
    ],
    "primarySubject": "subject-web-development",
    "catalogType": "lesson"
  },
  {
    "id": "fde-073",
    "file": "/notes/fde-073.md",
    "title": "Handle the complete interface lifecycle",
    "blurb": "Loading, empty, partial, error, retry and success states.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 13 · Full-stack delivery and AI user experience",
    "phaseOrder": 13,
    "order": 73,
    "minutes": 5,
    "standfirst": "Ship a usable interface that handles streaming, failure, approval and citations, then test a real task on mobile and desktop.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-072",
      "fde-022"
    ],
    "roles": [
      "product"
    ],
    "topics": [
      "Loading, empty, partial, error, retry and success states",
      "Streaming, disconnects, cancellation and saved progress",
      "Responsive layouts, keyboard use and accessible controls"
    ],
    "primarySubject": "subject-web-development",
    "catalogType": "lesson"
  },
  {
    "id": "fde-074",
    "file": "/notes/fde-074.md",
    "title": "Design interfaces for AI uncertainty",
    "blurb": "Citations, evidence inspection and user corrections.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 13 · Full-stack delivery and AI user experience",
    "phaseOrder": 13,
    "order": 74,
    "minutes": 5,
    "standfirst": "Ship a usable interface that handles streaming, failure, approval and citations, then test a real task on mobile and desktop.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-073"
    ],
    "roles": [
      "product"
    ],
    "topics": [
      "Citations, evidence inspection and user corrections",
      "Preview/approve actions and show actual execution status",
      "Explain limitations and provide recovery or human escalation"
    ],
    "primarySubject": "subject-web-development",
    "catalogType": "lesson"
  },
  {
    "id": "fde-075",
    "file": "/notes/fde-075.md",
    "title": "Measure usability rather than visual novelty",
    "blurb": "User journeys, hierarchy, typography, spacing and clear calls to action.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 13 · Full-stack delivery and AI user experience",
    "phaseOrder": 13,
    "order": 75,
    "minutes": 5,
    "standfirst": "Ship a usable interface that handles streaming, failure, approval and citations, then test a real task on mobile and desktop.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-074"
    ],
    "roles": [
      "product"
    ],
    "topics": [
      "User journeys, hierarchy, typography, spacing and clear calls to action",
      "Task completion, time-to-value, feedback and friction",
      "Consent-aware instrumentation and reusable interface patterns"
    ],
    "primarySubject": "subject-web-development",
    "catalogType": "lesson"
  },
  {
    "id": "fde-076",
    "file": "/notes/fde-076.md",
    "title": "Explain technical work clearly",
    "blurb": "A 90-second background, a two-minute case and a deeper design walkthrough.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 14 · Customer delivery and interview communication",
    "phaseOrder": 14,
    "order": 76,
    "minutes": 6,
    "standfirst": "Present a professional project and a personal project with clear ownership, verified outcomes, trade-offs and one failure or limitation each.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-001",
      "fde-004"
    ],
    "roles": [
      "discovery",
      "product",
      "architecture"
    ],
    "topics": [
      "A 90-second background, a two-minute case and a deeper design walkthrough",
      "Use diagrams and decision summaries for different audiences",
      "Lead with the problem and outcome, not a list of languages"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-077",
    "file": "/notes/fde-077.md",
    "title": "Run a pilot and hand over ownership",
    "blurb": "Co-build with customer engineers and collect user feedback.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 14 · Customer delivery and interview communication",
    "phaseOrder": 14,
    "order": 77,
    "minutes": 6,
    "standfirst": "Present a professional project and a personal project with clear ownership, verified outcomes, trade-offs and one failure or limitation each.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P1",
    "prerequisites": [
      "fde-076"
    ],
    "roles": [
      "discovery",
      "product"
    ],
    "topics": [
      "Co-build with customer engineers and collect user feedback",
      "Training, operating guides, escalation paths and maintenance ownership",
      "Turn repeated work into reusable components and product feedback"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-078",
    "file": "/notes/fde-078.md",
    "title": "Scope under pressure and disagreement",
    "blurb": "Negotiate success criteria, dependencies and trade-offs.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 14 · Customer delivery and interview communication",
    "phaseOrder": 14,
    "order": 78,
    "minutes": 5,
    "standfirst": "Present a professional project and a personal project with clear ownership, verified outcomes, trade-offs and one failure or limitation each.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-077"
    ],
    "roles": [
      "discovery",
      "architecture"
    ],
    "topics": [
      "Negotiate success criteria, dependencies and trade-offs",
      "Surface uncertainty, blockers and security constraints early",
      "Offer a scoped demonstration, not an unbounded overnight promise"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-079",
    "file": "/notes/fde-079.md",
    "title": "Build an honest evidence portfolio",
    "blurb": "Distinguish employer work, paid client work, personal products and prototypes.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 14 · Customer delivery and interview communication",
    "phaseOrder": 14,
    "order": 79,
    "minutes": 5,
    "standfirst": "Present a professional project and a personal project with clear ownership, verified outcomes, trade-offs and one failure or limitation each.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-078"
    ],
    "roles": [
      "discovery",
      "architecture"
    ],
    "topics": [
      "Distinguish employer work, paid client work, personal products and prototypes",
      "Identify your contribution, collaborators, AI assistance and evidence",
      "Use measured results or clearly labelled estimates; protect client confidentiality"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-080",
    "file": "/notes/fde-080.md",
    "title": "Prepare for varied interview formats",
    "blurb": "Customer discovery, coding, system design, debugging and project deep dives.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 14 · Customer delivery and interview communication",
    "phaseOrder": 14,
    "order": 80,
    "minutes": 6,
    "standfirst": "Present a professional project and a personal project with clear ownership, verified outcomes, trade-offs and one failure or limitation each.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "lesson",
    "priority": "P0",
    "prerequisites": [
      "fde-079"
    ],
    "roles": [
      "discovery",
      "architecture"
    ],
    "topics": [
      "Customer discovery, coding, system design, debugging and project deep dives",
      "Practise both no-AI fundamentals and allowed AI-assisted delivery",
      "Follow the employer’s assessment rules and tailor preparation to the actual posting"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "lesson"
  },
  {
    "id": "fde-081",
    "file": "/notes/fde-081.md",
    "title": "Studio: enterprise knowledge assistant",
    "blurb": "Design for a 500,000-document scenario; measure only the corpus you actually use.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 15 · Applied studios and readiness assessment",
    "phaseOrder": 15,
    "order": 81,
    "minutes": 9,
    "standfirst": "Pass the practical gates with inspectable code, tests, evaluation data, deployment evidence and an honest account of what was actually tested.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "studio",
    "priority": "P0",
    "prerequisites": [
      "fde-043",
      "fde-046",
      "fde-054",
      "fde-055",
      "fde-058",
      "fde-061",
      "fde-069",
      "fde-073",
      "fde-079"
    ],
    "roles": [
      "ai",
      "security",
      "product"
    ],
    "topics": [
      "Design for a 500,000-document scenario; measure only the corpus you actually use",
      "Implement ingestion, cited retrieval, permissions and lifecycle updates",
      "Ship a UI, evaluation report, trace, deployment/runbook and known-limitations record"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "studio"
  },
  {
    "id": "fde-082",
    "file": "/notes/fde-082.md",
    "title": "Studio: approval-based operations agent",
    "blurb": "Read a support case, gather evidence and draft a proposed action.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 15 · Applied studios and readiness assessment",
    "phaseOrder": 15,
    "order": 82,
    "minutes": 9,
    "standfirst": "Pass the practical gates with inspectable code, tests, evaluation data, deployment evidence and an honest account of what was actually tested.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "studio",
    "priority": "P0",
    "prerequisites": [
      "fde-043",
      "fde-046",
      "fde-054",
      "fde-055",
      "fde-058",
      "fde-061",
      "fde-069",
      "fde-073",
      "fde-079",
      "fde-047",
      "fde-063"
    ],
    "roles": [
      "software",
      "agents",
      "security"
    ],
    "topics": [
      "Read a support case, gather evidence and draft a proposed action",
      "Require approval for writes; use a sandbox or mock business system",
      "Test duplicate events, timeout ambiguity, denied access and audit history"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "studio"
  },
  {
    "id": "fde-083",
    "file": "/notes/fde-083.md",
    "title": "Studio: data-to-decision ML service",
    "blurb": "Build a validated data pipeline, baseline/model and repeatable scoring job.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 15 · Applied studios and readiness assessment",
    "phaseOrder": 15,
    "order": 83,
    "minutes": 9,
    "standfirst": "Pass the practical gates with inspectable code, tests, evaluation data, deployment evidence and an honest account of what was actually tested.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "studio",
    "priority": "P1",
    "prerequisites": [
      "fde-043",
      "fde-046",
      "fde-054",
      "fde-055",
      "fde-058",
      "fde-061",
      "fde-069",
      "fde-073",
      "fde-079",
      "fde-024",
      "fde-028"
    ],
    "roles": [
      "data",
      "ml"
    ],
    "topics": [
      "Build a validated data pipeline, baseline/model and repeatable scoring job",
      "Expose outcomes through an API and a report only where users need one",
      "Document leakage controls, monitoring, business decisions and failure recovery"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "studio"
  },
  {
    "id": "fde-084",
    "file": "/notes/fde-084.md",
    "title": "Studio: brownfield repair and customer handoff",
    "blurb": "Understand an unfamiliar repository and clarify an incomplete customer request.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 15 · Applied studios and readiness assessment",
    "phaseOrder": 15,
    "order": 84,
    "minutes": 9,
    "standfirst": "Pass the practical gates with inspectable code, tests, evaluation data, deployment evidence and an honest account of what was actually tested.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "studio",
    "priority": "P0",
    "prerequisites": [
      "fde-043",
      "fde-046",
      "fde-054",
      "fde-055",
      "fde-058",
      "fde-061",
      "fde-069",
      "fde-073",
      "fde-079"
    ],
    "roles": [
      "discovery",
      "reliability",
      "architecture"
    ],
    "topics": [
      "Understand an unfamiliar repository and clarify an incomplete customer request",
      "Fix a bug or add one integration with tests and deployment evidence",
      "Complete a no-AI explanation/debugging round and an AI-assisted scoped change"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "studio"
  },
  {
    "id": "fde-085",
    "file": "/notes/fde-085.md",
    "title": "Go bridge: language to service",
    "blurb": "Packages, types, slices, maps, interfaces and explicit errors.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 85,
    "minutes": 5,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [],
    "topics": [
      "Packages, types, slices, maps, interfaces and explicit errors",
      "HTTP handlers/clients, JSON, testing and context propagation",
      "Port a small Python service and explain what changes and what does not"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-086",
    "file": "/notes/fde-086.md",
    "title": "Go bridge: concurrency and performance",
    "blurb": "Goroutines, channels, select and synchronization.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 86,
    "minutes": 5,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-085"
    ],
    "roles": [],
    "topics": [
      "Goroutines, channels, select and synchronization",
      "Cancellation, bounded worker pools and race detection",
      "Benchmarking, profiling and graceful shutdown"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-087",
    "file": "/notes/fde-087.md",
    "title": "Advanced data engineering",
    "blurb": "Distributed joins, partition strategies and streaming state.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 87,
    "minutes": 5,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [
      "data"
    ],
    "topics": [
      "Distributed joins, partition strategies and streaming state",
      "Incremental lakehouse patterns, orchestration and data contracts",
      "Scale only from a measured workload and explicit reliability needs"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-088",
    "file": "/notes/fde-088.md",
    "title": "Model adaptation and self-hosted inference",
    "blurb": "Fine-tuning decisions, training data quality and evaluation isolation.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 88,
    "minutes": 6,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [],
    "topics": [
      "Fine-tuning decisions, training data quality and evaluation isolation",
      "Quantization, batching, GPU memory and serving architectures",
      "Model licenses, operational cost and rollback"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-089",
    "file": "/notes/fde-089.md",
    "title": "Advanced retrieval and document intelligence",
    "blurb": "Multimodal documents, tables, graph-assisted retrieval and complex queries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 89,
    "minutes": 6,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [
      "ai"
    ],
    "topics": [
      "Multimodal documents, tables, graph-assisted retrieval and complex queries",
      "Domain-specific parsing and relevance evaluation",
      "Benchmark against the simpler retrieval baseline"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-090",
    "file": "/notes/fde-090.md",
    "title": "Advanced agent orchestration",
    "blurb": "Multi-agent delegation, parallel work and shared-state boundaries.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 90,
    "minutes": 6,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [
      "agents"
    ],
    "topics": [
      "Multi-agent delegation, parallel work and shared-state boundaries",
      "Distributed execution, durable recovery and inter-agent protocols",
      "Evaluate coordination cost, propagation of errors and security"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-091",
    "file": "/notes/fde-091.md",
    "title": "Infrastructure specialization",
    "blurb": "Kubernetes workload concepts and infrastructure as code.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 91,
    "minutes": 5,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [
      "reliability"
    ],
    "topics": [
      "Kubernetes workload concepts and infrastructure as code",
      "Networking, autoscaling, deployment policies and cluster operations",
      "Promote this bridge only when the target role requires hands-on depth"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  },
  {
    "id": "fde-092",
    "file": "/notes/fde-092.md",
    "title": "Voice, multimodal and edge experiences",
    "blurb": "Speech pipelines, interruptions, latency and consent.",
    "domain": "Programming",
    "section": "Forward Deployed Engineering",
    "phase": "Phase 16 · Optional specialist bridges",
    "phaseOrder": 16,
    "order": 92,
    "minutes": 6,
    "standfirst": "Choose one bridge only when you can explain the problem it solves, its additional operational burden and the evidence that justifies it.",
    "notion": "",
    "source": "repository",
    "contentSource": "github",
    "lessonType": "optional_bridge",
    "priority": "P2",
    "prerequisites": [
      "fde-070"
    ],
    "roles": [],
    "topics": [
      "Speech pipelines, interruptions, latency and consent",
      "Device capabilities, offline boundaries and cloud handoff",
      "Use as a later extension for a voice-assistant product, not a first FDE gate"
    ],
    "primarySubject": "subject-software-delivery",
    "catalogType": "bridge"
  }
]
