CoolFace
Apppublic

marimo-team/marimo-learn

sourceHugging Faceupdated 5mo agoView on Hugging Face
3likes
02_formative.py452 linesDownload Raw Back to tools
1# /// script2# requires-python = "==3.12"3# dependencies = [4#     "anywidget",5#     "marimo",6#     "marimo-learn==0.13.0",7# ]8# ///9 10"""11Example Marimo Notebook: Education Widgets Demo12 13This notebook demonstrates all formative assessment widgets.14Run with: marimo edit demo.py15"""16 17import marimo18 19__generated_with = "0.23.1"20app = marimo.App()21 22 23@app.cell24def _():25    import marimo as mo26    from marimo_learn import (27        ConceptMapWidget,28        FlashcardWidget,29        LabelingWidget,30        MatchingWidget,31        MultipleChoiceWidget,32        NumericEntryWidget,33        OrderingWidget,34        PredictThenCheckWidget,35    )36 37    return (38        ConceptMapWidget,39        FlashcardWidget,40        LabelingWidget,41        MatchingWidget,42        MultipleChoiceWidget,43        NumericEntryWidget,44        OrderingWidget,45        PredictThenCheckWidget,46        mo,47    )48 49 50@app.cell51def _(mo):52    mo.md("""53    # Formative Assessment Widgets54 55    This notebook demonstrates several widgets from the [marimo-learn](https://pypi.org/project/marimo-learn/) package that you can use to embed formative assessments in your lessons. They also show how easy it is to build custom plugins for the marimo notebook to meet your teaching needs.56    """)57    return58 59 60@app.cell61def _(mo):62    mo.md("""63    ## Concept Map64    """)65    return66 67 68@app.cell69def _(ConceptMapWidget, mo):70    concept_map = mo.ui.anywidget(71        ConceptMapWidget(72            question="Map the relationships between these Python concepts:",73            concepts=["function", "parameter", "argument", "return value", "call site"],74            terms=["defines", "accepts", "supplies", "produces", "invokes"],75            correct_edges=[76                {"from": "function", "to": "parameter", "label": "accepts"},77                {"from": "function", "to": "return value", "label": "produces"},78                {"from": "call site", "to": "argument", "label": "supplies"},79                {"from": "argument", "to": "parameter", "label": "defines"},80                {"from": "call site", "to": "function", "label": "invokes"},81            ],82        )83    )84    concept_map85    return (concept_map,)86 87 88@app.cell89def _(concept_map, mo):90    def concept_map_msg(widget):91        val = widget.value.get("value") or {}92        score = val.get("score")93        total = val.get("total", 5)94        if score is None:95            return "Draw connections between concepts to see your score."96        msg = f"**{score}/{total}** correct connection{'s' if total != 1 else ''}"97        if val.get("correct"):98            msg += " — complete!"99        return msg100 101    mo.md(concept_map_msg(concept_map))102    return103 104 105@app.cell106def _(mo):107    mo.md("""108    ## Flashcard Deck109    """)110    return111 112 113@app.cell114def _(FlashcardWidget, mo):115    flashcard_deck = mo.ui.anywidget(116        FlashcardWidget(117            question="Python Concepts — rate yourself on each card:",118            cards=[119                {120                    "front": "What does a list comprehension look like?",121                    "back": "[expr for item in iterable if condition] — e.g., [x**2 for x in range(10) if x % 2 == 0]",122                },123                {124                    "front": "What is the difference between a list and a tuple?",125                    "back": "Lists are mutable (can be changed after creation); tuples are immutable (cannot be changed).",126                },127                {128                    "front": "What does the `*args` parameter do in a function definition?",129                    "back": "It collects any number of positional arguments into a tuple named `args`.",130                },131                {132                    "front": "What is a Python generator?",133                    "back": "A function that uses `yield` to produce values one at a time, pausing between each, without building the full sequence in memory.",134                },135                {136                    "front": "What does `if __name__ == '__main__':` do?",137                    "back": "It runs the indented code only when the file is executed directly, not when it is imported as a module.",138                },139                {140                    "front": "What is the difference between `is` and `==` in Python?",141                    "back": "`==` tests value equality; `is` tests identity (whether two names refer to the exact same object in memory).",142                },143            ],144            shuffle=True,145        )146    )147    flashcard_deck148    return (flashcard_deck,)149 150 151@app.cell152def _(flashcard_deck, mo):153    def flashcard_progress(widget):154        val = widget.value.get("value") or {}155        results = val.get("results", {})156        counts = {"got_it": 0, "almost": 0, "no": 0}157        for r in results.values():158            counts[r["rating"]] = counts.get(r["rating"], 0) + 1159        return len(results), counts, val.get("complete", False)160 161    _rated, _counts, _complete = flashcard_progress(flashcard_deck)162    mo.md(f"""163    **Progress:** {_rated} card(s) rated —164    ✓ Got it: {_counts["got_it"]}  165    ~ Almost: {_counts["almost"]}  166    ✗ No: {_counts["no"]}167    {"  🎉 Deck complete!" if _complete else ""}168    """)169    return170 171 172@app.cell173def _(mo):174    mo.md("""175    ## Labeling Question176    """)177    return178 179 180@app.cell181def _(LabelingWidget, mo):182    labeling_question = mo.ui.anywidget(183        LabelingWidget(184            question="Label the parts of this Python code:",185            labels=[186                "Variable declaration",187                "Function call",188                "String literal",189                "Arithmetic operation",190            ],191            text_lines=[192                "name = 'Alice'",193                "age = 25",194                "result = age + 5",195                "print(name)",196            ],197            correct_labels={198                0: [0, 2],  # Line 0: labels 0 and 2199                1: [0],  # Line 1: label 0200                2: [0, 3],  # Line 2: labels 0 and 3201                3: [1],  # Line 3: label 1202            },203        )204    )205    labeling_question206    return (labeling_question,)207 208 209@app.cell210def _(labeling_question, mo):211    _val = labeling_question.value.get("value") or {}212    _total = _val.get("total", 0)213    _msg = f"Score: {_val['score']}/{_total}" if _total > 0 else "Not submitted yet"214    mo.md(f"**{_msg}**")215    return216 217 218@app.cell219def _(mo):220    mo.md("""221    ## Matching Question222    """)223    return224 225 226@app.cell227def _(MatchingWidget, mo):228    matching_question = mo.ui.anywidget(229        MatchingWidget(230            question="Match the programming languages to their primary paradigms:",231            left=["Python", "Haskell", "C", "SQL"],232            right=["Functional", "Procedural", "Multi-paradigm", "Declarative"],233            correct_matches={0: 2, 1: 0, 2: 1, 3: 3},234        )235    )236    matching_question237    return (matching_question,)238 239 240@app.cell241def _(matching_question, mo):242    _val = matching_question.value.get("value") or {}243    _msg = f"Score: {_val['score']}/{_val['total']}" if _val else "Not answered yet"244    mo.md(f"**{_msg}**")245    return246 247 248@app.cell249def _(mo):250    mo.md("""251    ## Multiple Choice Question252    """)253    return254 255 256@app.cell257def _(MultipleChoiceWidget, mo):258    multiple_choice_question = mo.ui.anywidget(259        MultipleChoiceWidget(260            question="What is the capital of France?",261            options=["London", "Berlin", "Paris", "Madrid"],262            correct_answer=2,263            explanation="Paris has been the capital of France since the 12th century.",264        )265    )266    multiple_choice_question267    return (multiple_choice_question,)268 269 270@app.cell271def _(mo, multiple_choice_question):272    _val = multiple_choice_question.value.get("value") or {}273    if _val.get("answered"):274        _msg = "Score: 1/1" if _val.get("correct") else "Score: 0/1"275    else:276        _msg = "Not answered yet"277    mo.md(f"**{_msg}**")278    return279 280 281@app.cell282def _(mo):283    mo.md("""284    ## Ordering Question285    """)286    return287 288 289@app.cell290def _(OrderingWidget, mo):291    ordering_question = mo.ui.anywidget(292        OrderingWidget(293            question="Arrange these steps of the scientific method in the correct order:",294            items=[295                "Ask a question",296                "Do background research",297                "Construct a hypothesis",298                "Test with an experiment",299                "Analyze data",300                "Draw conclusions",301            ],302            shuffle=True,303        )304    )305    ordering_question306    return (ordering_question,)307 308 309@app.cell310def _(mo, ordering_question):311    _val = ordering_question.value.get("value") or {}312    if _val.get("correct") is not None and _val.get("order"):313        _msg = "Score: 1/1" if _val.get("correct") else "Score: 0/1"314    else:315        _msg = "Not answered yet"316    mo.md(f"**{_msg}**")317    return318 319 320@app.cell321def _(mo):322    mo.md("""323    ## Numeric Entry Question324    """)325    return326 327 328@app.cell329def _(NumericEntryWidget, mo):330    numeric_entry_question = mo.ui.anywidget(331        NumericEntryWidget(332            question="How many bits are in one byte?",333            correct_answer=8,334            tolerance=0.5,335            explanation="A byte consists of exactly 8 bits.",336        )337    )338    numeric_entry_question339    return (numeric_entry_question,)340 341 342@app.cell343def _(mo, numeric_entry_question):344    _val = numeric_entry_question.value.get("value") or {}345    if _val.get("answered"):346        _msg = "Score: 1/1" if _val.get("ok") else "Score: 0/1"347    else:348        _msg = "Not answered yet"349    mo.md(f"**{_msg}**")350    return351 352 353@app.cell354def _(mo):355    mo.md("""356    ## Predict-Then-Check Question357    """)358    return359 360 361@app.cell362def _(PredictThenCheckWidget, mo):363    predict_then_check_question = mo.ui.anywidget(364        PredictThenCheckWidget(365            question="What does this Python code print?",366            code='words = ["one", "two", "three"]\nprint(len(words))',367            output="3",368            options=["2", "3", "['one', 'two', 'three']", "None"],369            correct_answer=1,370            explanations=[371                "Wrong: len() counts all items; the list has three elements.",372                "Correct: len() returns the number of items, which is 3.",373                "Wrong: len() returns an integer count, not the list itself.",374                "Wrong: len() always returns an integer, never None.",375            ],376        )377    )378    predict_then_check_question379    return (predict_then_check_question,)380 381 382@app.cell383def _(mo, predict_then_check_question):384    _val = predict_then_check_question.value.get("value") or {}385    if _val.get("answered"):386        _msg = "Score: 1/1" if _val.get("correct") else "Score: 0/1"387    else:388        _msg = "Not answered yet"389    mo.md(f"**{_msg}**")390    return391 392 393@app.cell394def _(mo):395    mo.md("""396    ---397 398    ## Quiz Results Summary399 400    You can access the values from all widgets to create a summary or scoring system.401    """)402    return403 404 405@app.cell406def _(407    concept_map,408    flashcard_deck,409    matching_question,410    mo,411    multiple_choice_question,412    numeric_entry_question,413    ordering_question,414    predict_then_check_question,415):416    def widget_val(widget):417        return widget.value.get("value") or {}418 419    def calculate_score():420        score = 0421        total = 0422        for val, answered_key, correct_key in [423            (widget_val(concept_map), "score", "correct"),424            (widget_val(matching_question), "score", "correct"),425            (widget_val(multiple_choice_question), "answered", "correct"),426            (widget_val(ordering_question), "order", "correct"),427            (widget_val(numeric_entry_question), "answered", "ok"),428            (widget_val(predict_then_check_question), "answered", "correct"),429        ]:430            if val.get(answered_key) is not None and val.get(answered_key) is not False:431                total += 1432                if val.get(correct_key):433                    score += 1434        fc = widget_val(flashcard_deck)435        if fc.get("results"):436            total += 1437            if fc.get("complete"):438                score += 1439        return score, total440 441    score, total = calculate_score()442    mo.md(f"""443    ### Current Score: {score}/{total}444 445    {"🎉 Perfect score!" if score == total and total > 0 else "Keep going!" if total > 0 else "Answer the questions above to see your score."}446    """)447    return448 449 450if __name__ == "__main__":451    app.run()452