Module 05 Exercises¶
These exercises are the practical half of the module. They use the cumulative FuncPipe reference state; they do not ask you to build another RAG application.
Work from programs/python-programming/python-functional-programming. Keep
this file and
capstone/module-reference-states/module-05/tests/learning/test_module_05_data_modelling.py
open together.
Use this focused command, replacing SELECTOR with the name shown in each
exercise:
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
PYTHONPATH=capstone/module-reference-states/module-05/src \
../../../artifacts/venv/python-programming/python-functional-programming/capstone/bin/pytest \
-q capstone/module-reference-states/module-05/tests/learning/test_module_05_data_modelling.py \
-k SELECTOR
For every exercise, first predict the result, then run the check, then explain any difference. A passing test without a written explanation is incomplete.
1. Trace the real stage types¶
Starting context: read core/rag_types.py and rag/stages.py. Run:
Objective: trace one document through:
Write down, for each arrow:
- the function responsible;
- the guarantee added;
- the fields that must remain equal;
- the constructor that can reject an invalid value.
Then implement a small describe function for Result[Chunk, ErrInfo]. It
must return the chunk span for Ok and the failure stage for Err.
Constraints:
- use the existing products and
Ok | Err; - do not add a status flag, nullable error field,
ChunkState, or job lifecycle; - match variants with keyword patterns;
- attempt one top-level metadata mutation and explain its failure.
Acceptance evidence:
- one successful span and one failure description;
- identity, text, offsets, and metadata preserved by embedding;
- a malformed embedding rejected;
- an explanation of why
Resultalready owns the success/failure distinction.
Earlier contract to preserve: Module 4's cleaning, chunk order, and structured failure values.
2. Transform and consume a result¶
Starting context: read fp/functor.py and your describe function. Run:
Objective: map two pure projections over Result[CleanDoc, ErrInfo]:
normalize the abstract for display, then count its words. Compare:
with one mapping of their explicit composition.
Use a calls list to observe which functions run for Ok and Err. Consume
the final result with match.
Constraints:
- do not inspect
.valuebefore mapping; - the original
Errobject must pass through; - do not use
result_try_map; neither projection is intended to raise; - spell composition as a function or lambda, not an invented
>>operator.
Acceptance evidence:
- sequential and composed mappings compare equal;
- both projections run for
Ok; - neither projection runs for
Err; - your match handles
OkandErr, then ends inassert_never.
Earlier contract to preserve: failure provenance must not be replaced or rewritten by a successful-value transformation.
3. Accumulate only independent errors¶
Starting context: read fp/validation.py, then inspect rag/domain/chunk.py.
Run:
Objective: add a third independent check to the title and abstract example:
the category string must be non-empty. Combine the three values with
v_liftA3 to construct RawDoc.
Next, draw the dependency graph for rag.domain.assemble: model agreement and
dimension agreement can both be reported, but construction requires their
successful evidence.
Constraints:
- preserve error order as title, abstract, category;
VFailuremust never be empty;- do not use
Resultmerely to make the checks fail fast; - do not claim that a range check can run before its input is parsed.
Acceptance evidence:
- one input producing all three messages in order;
- one
VSuccess(RawDoc(...)); - both embedding mismatch codes from
assemble; - one sentence identifying a genuinely dependent validation pair.
Earlier contract to preserve: Result remains the type for dependent
pipeline work; Validation does not replace it.
4. Audit a metric monoid¶
Starting context: read fp/monoid.py. Run:
Objective: extend a local copy of the Metrics idea with a rejected
count. Define its empty value and combine function, then check:
combine(empty, x) == x
combine(x, empty) == x
combine(combine(a, b), c) == combine(a, combine(b, c))
Use at least three chosen values. Keep summed latency and maximum latency as different fields.
Constraints:
rejectedcombines by addition;- maximum latency combines with
max; - inputs use non-negative finite latency;
- do not claim arbitrary floating-point regrouping is bit-identical;
- do not claim
tree_reducestarts parallel workers.
Acceptance evidence:
- an empty fold;
- left and right identity checks;
- both regroupings;
- a short explanation of associativity versus commutativity;
- the domain assumption that makes
0.0a maximum-latency identity.
Earlier contract to preserve: aggregation must not reorder chunks or reinterpret what an existing metric means.
5. Cross the validation and persistence boundaries¶
Starting context: read boundaries/adapters/pydantic_edges.py and
boundaries/adapters/serde.py. Run:
Objective: follow two separate boundary paths:
untrusted chunk mapping -> ChunkModel -> core Chunk
Err(ErrInfo) -> result envelope -> JSON -> result envelope -> Err(ErrInfo)
For the chunk, account for document ID, text, offsets, metadata, and all 16
embedding values. For the failure, use non-empty stage and path, inspect
the encoded payload, and compare the decoded value.
Then change only the envelope version and predict the decoder error before running it.
Constraints:
- Pydantic remains in the adapter;
- use the shipped encoder and decoder instead of
repror__dict__; - do not claim the default
ErrInfocodec preservescauseorctx; - reject wrong dimensions, non-finite values, and unknown versions.
Acceptance evidence:
- one complete accepted chunk conversion;
- one boundary rejection;
- the exact result envelope payload;
- successful provenance round trip;
- unknown-version rejection;
- an explanation of why validation and persistence are different boundaries.
Earlier contract to preserve: accepted core values do not depend on Pydantic and failures retain their operational code, stage, and path.
6. Review the focused domain lab before changing it¶
Starting context: compare core/rag_types.py with rag/domain/. Run:
Objective: write a review note answering:
- Which type family is the cumulative pipeline?
- Which type family is the focused cross-field assembly lab?
- What does
assembleteach that the stage products do not? - Which observables must the optimized batch representation preserve?
- Why does
AssembledChunknot replace the pipeline's indexedChunk?
Do not begin with a refactor. First identify ownership and evidence. If you propose a change, name the invalid state it removes and the exact tests that would protect accepted behavior.
Constraints:
- do not introduce a third chunk model;
- do not benchmark inside the equivalence test;
- compare identity, order, text, metadata, embedding model, and vector values;
- declare numeric tolerance;
- separate correctness evidence from performance evidence.
Acceptance evidence: a concise review note with a keep/change decision, the evidence supporting it, and one limit of that evidence.
Earlier contract to preserve: the main RAG pipeline remains understandable as an application; the focused lab supports a concept rather than replacing the application.
Before reading the answers¶
For each exercise, you should have:
- a prediction;
- executable evidence;
- a design explanation;
- one common wrong turn you avoided;
- one statement about what your evidence does not prove.
Use Exercise Answers as a review guide, not a source to copy before attempting the work.