# /// script
# requires-python = ">=3.12,<3.15"
# dependencies = [
#     "marimo",
# ]
# ///
"""Lists and records.
"""

import marimo

__generated_with = "0.24.2"
app = marimo.App(width="medium", sql_output="polars")


@app.cell
def _():
    import marimo as mo

    return (mo,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # Lists and Records

    Sections 1 and 2 ran in your notebook last week, and here they get their names.
    Section 3 practises them. **Sections 4 and 5 are the new material.**

    | | |
    |---|---|
    | ✏️ | Your turn. Add cells with the **+** button |
    | 🚀 | This week's work |

    For a written answer, add a cell under the question, open the cell's **⋯** menu and choose **Convert to Markdown**.

    Every ✏️ is part of this week's work. Those marked **Advanced** are optional; try them once the rest is done. So is the **Going further** at the end of an exercise.

    **How to work with your agent, in every ✏️ section.** The order matters, and it is the
    same order all term.

    1. **Try it yourself first.** If you cannot write the code, write the steps in words.
    2. **Then ask your agent**, and read what it gives you before you keep it.
    3. **If any line of its answer is unclear, ask it to explain that line.** Keep asking
       until you could write the same line tomorrow. An answer you cannot read is not an
       answer you can check.
    4. **Then ask which concepts its answer used**, and find those rows in the table
       below.

    **The course rules for your agent** are a block of text you paste once into marimo's
    **Settings > AI Features > Custom Rules**, and they make it do steps 3 and 4 without
    being asked. Download them from the course site, under Downloads. Nothing here is
    required.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 1. What You Already Have

    Every row ran in your notebook last week.

    | | The name for it | What it is for |
    |---|---|---|
    | `16.75` | a **value** | a single number, or a single piece of text |
    | `total = sum(charges)` | a **name**, and `=` is **assignment** | storing a value you will use again |
    | `16.75` against `"16.75"` | a **type**: number against text | deciding what can be done with a value. Arithmetic and sorting behave differently on each |
    | `f"${total:.2f}"` | an **f-string** | putting a value inside a sentence, to two decimal places |
    | `[16.75, 22.25, 25.00]` | a **list** | holding many values of one kind, in order |
    | `charges[0]` | an **index**, counting from zero | reading one item by its position |
    | `for charge in charges:` | a **loop**, once for each item | running the same lines once per item |
    | `if charge < 25:` | a **condition** | running lines only when a test is true |
    | `sum(charges)` | **many values into one number** | combining every item into a single result |
    | `sorted(charges, reverse=True)` | a **function**, and `reverse=True` is an **argument** | calling an operation somebody already wrote. The argument is what you hand it |
    | `NameError`, `IndexError`, `SyntaxError` | an **error** | Python stopping and reporting why. Read the last line first |
    """)
    return


@app.cell
def _():
    # Your own example of each name.

    # 1. value:
    # 2. name and assignment:
    # 3. type:
    # 4. list:
    # 5. index:
    # 6. loop:
    # 7. condition:
    # 8. f-string:
    # 9. many into one number:
    # 10. function and argument:
    # 11. error:
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 2. What You Do to a List

    Four things, and you have done all four already.

    | | on last week's five charges |
    |---|---|
    | **Take one out** | `charges[0]` |
    | **Keep some** | `for` with an `if` |
    | **Do the same to each** | `for charge in charges:` |
    | **Turn many into one number** | `sum(charges)`, or a running `total` |

    Today you will do them to records. In October you will do them to a table in
    polars, and in November to a database table.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## In class

    Last week's five freight charges are below. Add cells under it and type these with me.

    ```python
    charges[0]
    charges[-1]
    charges[5]
    ```

    The third one fails on purpose. Then the loop, one line at a time:

    ```python
    total = 0
    for charge in charges:
        if charge < 25:
            total = total + charge
    total
    ```

    **Check yourself: 59.25.**
    """)
    return


@app.cell
def _():
    charges = [16.75, 22.25, 25.00, 20.25, 36.25]
    charges
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 3. ✏️ Your Turn

    Five short ones, each a small piece of code with something surprising in it. Every
    one already has its code in a cell, so there is nothing to copy.

    **How to work them**

    - **Run a cell:** click into it and press `Ctrl+Enter`, or `Cmd+Enter` on macOS.
    - **Add a cell:** hover between two cells and click the **+** button, or use the **+**
      at the bottom of the notebook.
    - **Write words instead of code:** add a cell, open its menu (the **⋮** at its right
      edge) and choose **Convert to Markdown cell**. Prose typed into a code cell turns
      the cell red and it stays broken until you convert it or delete it.
    - **`This variable is already defined in another cell`:** you are trying to give a name
      that already exists somewhere else in this notebook. Edit the cell that already has
      it, or pick a different name. marimo allows one definition per name in the whole
      file, which is what stops two cells from quietly disagreeing.
    - **If your number is not the one written under a question**, check your data before
      you check your code. Last week's Experiment 1 put 999.99 into the freight list on
      purpose and asked you to put it back.
    - **Stuck:** try it yourself first, then ask your agent and paste the error if there
      is one. Read its answer, ask it to explain any line you could not have written, and
      finish by asking which concepts it used. The four steps are at the top of this
      notebook.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## A · The training scores

    A training program marks anyone at 90 or above as `A`, and anyone at 60 or above as
    `Pass`. The cell below is supposed to do that. It does not.

    1. **Run the cell below** and read what it printed for a score of 95.
    2. **Edit that same cell** so that 95 prints `A`, changing as little as you can. Then
       set `score` to 75, run it again, and confirm it still prints `Pass`.
    3. **Add a markdown cell** and answer in one sentence: when a score satisfies two of
       these tests at once, which one decides what gets printed?

    **Going further.** Extend the same cell so that anything below 60 prints `Fail`, then
    run 95, 75, 60 and 55 through it one at a time.

    *Expected: `Pass` before your change, `A` after it, and `Pass` again for 75.*
    """)
    return


@app.cell
def _():
    score = 95
    if score >= 60:
        print("Pass")
    elif score >= 90:
        print("A")
    return (score,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## B · A day of orders

    Five orders, each with the status it ended the day on. Counting how many are in one
    state is the most common thing anybody does to a column of a table, and here it is on
    a plain list first.

    1. **Add a cell** under the one below that counts how many of these orders shipped. A
       `for` loop with an `if` inside it and a counter that starts at zero will do it.
    2. **Add another cell** that counts how many did not ship.
    3. **Add a third cell** that prints the percentage that shipped. It is the count
       divided by `len(statuses)`, times 100.

    *Expected: 3, then 2, then 60.0.*
    """)
    return


@app.cell
def _():
    statuses = ["shipped", "pending", "shipped", "cancelled", "shipped"]
    statuses
    return (statuses,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## C · Adding items to an order

    An order has two lines on it. Somebody adds a stapler and some tape, so it should
    finish with four. Run the cell below and it has three.

    1. **Add a cell** that prints `order_lines[2]`, and read what came back. That one item
       is the whole problem.
    2. **Edit the cell below** so the order ends up with four separate lines. The method
       you need is not `append`; ask your agent for the one that adds several items at
       once, or search the handbook for it.
    3. **Add a markdown cell** and answer in one sentence: how many items does `append`
       add, whatever you hand it?

    *Expected: `['stapler', 'tape']` printed as one item, then a list of length 4.*
    """)
    return


@app.cell
def _():
    order_lines = ["notebook", "pen"]
    order_lines.append(["stapler", "tape"])
    len(order_lines)
    return (order_lines,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## D · Sorting the tickers

    Two ways to put a list in order. They look alike and they do different things, and
    the difference costs people real time later.

    1. **Run the cell below.** It prints three things: `sorted(tickers)`, then
       `tickers.sort()`, then `tickers` itself at the end.
    2. **Add a markdown cell** and answer in one sentence: why did `tickers.sort()` print
       `None`, when `sorted(tickers)` printed a list?
    3. **Add a cell** that prints the tickers largest first, without changing `tickers`
       again. One of the two ways above takes an extra argument that does this.

    *Expected: `['AAPL', 'MSFT', 'NVDA']`, then `None`, then largest first is
    `['NVDA', 'MSFT', 'AAPL']`.*
    """)
    return


@app.cell
def _():
    tickers = ["NVDA", "AAPL", "MSFT"]
    print(sorted(tickers))
    print(tickers.sort())
    tickers
    return (tickers,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## E · The price list that changed by itself

    A shop builds its sale prices from its regular prices, adds one more sale price, and
    the regular price list comes out wrong. Nobody touched it.

    1. **Run the cell below** and read what `prices` holds at the end.
    2. **Add a cell** that prints `prices is sale_prices`. That is Python's way of asking
       whether two names refer to one single list, and it answers `True` or `False`.
    3. **Edit the cell below**, changing the second line to `sale_prices = prices[:]`, and
       run both again. `[:]` makes a copy of the list, where the first version made a
       second name for one list.
    4. **Add a markdown cell** and answer in one sentence: when would you want two names to
       refer to the same list on purpose?

    **Going further.** With `sale_prices = prices[:]` in place, take 10% off every sale
    price and leave `prices` untouched. A `for` loop over positions, or a new list built
    from the old one, will both do it.

    *Expected: `[12.5, 8.0, 19.99, 4.99]` and `True`, then `[12.5, 8.0, 19.99]` and
    `False`. Discounted: `[11.25, 7.2, 17.991, 4.4910000000000005]`, with `prices`
    unchanged; `round(price * 0.9, 2)` gives cents, `[11.25, 7.2, 17.99, 4.49]`.*
    """)
    return


@app.cell
def _():
    prices = [12.50, 8.00, 19.99]
    sale_prices = prices
    sale_prices.append(4.99)
    prices
    return (prices, sale_prices,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## If You Finish

    Required of nobody. Take them in any order.

    > **Advanced · F · A quantity that arrived as text.** A web form sent its quantities
    > with quotes around them, so Python received text where you wanted numbers. Run the
    > cell below to see both behaviours side by side.
    >
    > 1. **Add a markdown cell** and answer in one sentence: what did `"100" + "50"` do,
    >    and why is that reasonable for text?
    > 2. **Add a cell** that adds the two form values as numbers and prints `150`. The
    >    function you need is named after the type you want.
    > 3. **Add another cell** and try that same conversion on `"100.5"`. Read the error,
    >    then get `100.5` a different way.
    """)
    return


@app.cell
def _():
    print("100" + "50")
    print(100 + 50)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    > **Advanced · G · Off the end.** `charges` in section 2 holds last week's five freight
    > charges. This one has no cell of its own, because every line in it fails on
    > purpose and a notebook that raises on load is a nuisance.
    >
    > 1. **Add a cell**, type `charges[5]` in it and run it. **Add a markdown cell** saying
    >    why there is no item 5 when the list holds five charges.
    > 2. **Add a cell** that gets the last charge, in two different ways, without counting
    >    the items by hand.
    > 3. **Add a cell** and run `charges[-6]`. Explain in markdown what happened and why.

    > **Advanced · H · A line for the operations team.** Using the counts you worked out in
    > B, **add a cell** that prints exactly `3 of 5 orders shipped`. Your code has to
    > compute both numbers. Then extend it so that it reads
    > `3 of 5 orders shipped (60%)`. An f-string is the short way to build a sentence out of
    > values, and it was section 5 of last week's notebook.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 4. A Record Is a Value With Fields

    A freight charge on its own does not say whose order it was, where it went, or
    whether it ever shipped. An order carries all of that. **This one is new.**
    """)
    return


@app.cell
def _():
    first_order = {
        "OrderID": 10248,
        "CustomerID": "VINET",
        "ShipCountry": "France",
        "ShipCity": "Reims",
        "OrderDate": "2016-07-04",
        "ShippedDate": "2016-07-16",
        "Freight": 16.75,
    }
    first_order
    return (first_order,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## Taking one field out

    A list is read **by position**, `charges[0]`. A record is read **by name**.
    """)
    return


@app.cell
def _(first_order):
    first_order["ShipCountry"]
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## ✏️ One of these fails

    ```python
    first_order["Freight"]
    first_order["freight"]
    first_order[0]
    ```

    Two of them fail, and both give the same kind of error. Add a cell and find out
    which, and what the message says. A `KeyError` names the key it could not find.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 5. A Table Is a List of Records

    One order is a record. A list of records is a table. **This one is new too.**

    Real rows from the warehouse this course uses all term: orders 10248 to 10274, plus
    three that were never shipped. The full table holds 16,282 of them.
    """)
    return


@app.cell
def _():
    orders = [
    {"OrderID": 10248, "CustomerID": "VINET", "ShipCountry": "France", "ShipCity": "Reims", "OrderDate": "2016-07-04", "ShippedDate": "2016-07-16", "Freight": 16.75},
    {"OrderID": 10249, "CustomerID": "TOMSP", "ShipCountry": "Germany", "ShipCity": "Münster", "OrderDate": "2016-07-05", "ShippedDate": "2016-07-10", "Freight": 22.25},
    {"OrderID": 10250, "CustomerID": "HANAR", "ShipCountry": "Brazil", "ShipCity": "Rio de Janeiro", "OrderDate": "2016-07-08", "ShippedDate": "2016-07-12", "Freight": 25.00},
    {"OrderID": 10251, "CustomerID": "VICTE", "ShipCountry": "France", "ShipCity": "Lyon", "OrderDate": "2016-07-08", "ShippedDate": "2016-07-15", "Freight": 20.25},
    {"OrderID": 10252, "CustomerID": "SUPRD", "ShipCountry": "Belgium", "ShipCity": "Charleroi", "OrderDate": "2016-07-09", "ShippedDate": "2016-07-11", "Freight": 36.25},
    {"OrderID": 10253, "CustomerID": "HANAR", "ShipCountry": "Brazil", "ShipCity": "Rio de Janeiro", "OrderDate": "2016-07-10", "ShippedDate": "2016-07-16", "Freight": 35.50},
    {"OrderID": 10254, "CustomerID": "CHOPS", "ShipCountry": "Switzerland", "ShipCity": "Bern", "OrderDate": "2016-07-11", "ShippedDate": "2016-07-23", "Freight": 24.25},
    {"OrderID": 10255, "CustomerID": "RICSU", "ShipCountry": "Switzerland", "ShipCity": "Genève", "OrderDate": "2016-07-12", "ShippedDate": "2016-07-15", "Freight": 37.50},
    {"OrderID": 10256, "CustomerID": "WELLI", "ShipCountry": "Brazil", "ShipCity": "Resende", "OrderDate": "2016-07-15", "ShippedDate": "2016-07-17", "Freight": 16.75},
    {"OrderID": 10257, "CustomerID": "HILAA", "ShipCountry": "Venezuela", "ShipCity": "San Cristóbal", "OrderDate": "2016-07-16", "ShippedDate": "2016-07-22", "Freight": 21.50},
    {"OrderID": 10258, "CustomerID": "ERNSH", "ShipCountry": "Austria", "ShipCity": "Graz", "OrderDate": "2016-07-17", "ShippedDate": "2016-07-23", "Freight": 40.25},
    {"OrderID": 10259, "CustomerID": "CENTC", "ShipCountry": "Mexico", "ShipCity": "México D.F.", "OrderDate": "2016-07-18", "ShippedDate": "2016-07-25", "Freight": 12.75},
    {"OrderID": 10260, "CustomerID": "OTTIK", "ShipCountry": "Germany", "ShipCity": "Köln", "OrderDate": "2016-07-19", "ShippedDate": "2016-07-29", "Freight": 35.50},
    {"OrderID": 10261, "CustomerID": "QUEDE", "ShipCountry": "Brazil", "ShipCity": "Rio de Janeiro", "OrderDate": "2016-07-19", "ShippedDate": "2016-07-30", "Freight": 20.00},
    {"OrderID": 10262, "CustomerID": "RATTC", "ShipCountry": "USA", "ShipCity": "Albuquerque", "OrderDate": "2016-07-22", "ShippedDate": "2016-07-25", "Freight": 17.25},
    {"OrderID": 10263, "CustomerID": "ERNSH", "ShipCountry": "Austria", "ShipCity": "Graz", "OrderDate": "2016-07-23", "ShippedDate": "2016-07-31", "Freight": 56.00},
    {"OrderID": 10264, "CustomerID": "FOLKO", "ShipCountry": "Sweden", "ShipCity": "Bräcke", "OrderDate": "2016-07-24", "ShippedDate": "2016-08-23", "Freight": 25.00},
    {"OrderID": 10265, "CustomerID": "BLONP", "ShipCountry": "France", "ShipCity": "Strasbourg", "OrderDate": "2016-07-25", "ShippedDate": "2016-08-12", "Freight": 22.50},
    {"OrderID": 10266, "CustomerID": "WARTH", "ShipCountry": "Finland", "ShipCity": "Oulu", "OrderDate": "2016-07-26", "ShippedDate": "2016-07-31", "Freight": 13.00},
    {"OrderID": 10267, "CustomerID": "FRANK", "ShipCountry": "Germany", "ShipCity": "München", "OrderDate": "2016-07-29", "ShippedDate": "2016-08-06", "Freight": 43.75},
    {"OrderID": 10268, "CustomerID": "GROSR", "ShipCountry": "Venezuela", "ShipCity": "Caracas", "OrderDate": "2016-07-30", "ShippedDate": "2016-08-02", "Freight": 13.50},
    {"OrderID": 10269, "CustomerID": "WHITC", "ShipCountry": "USA", "ShipCity": "Seattle", "OrderDate": "2016-07-31", "ShippedDate": "2016-08-09", "Freight": 30.00},
    {"OrderID": 10270, "CustomerID": "WARTH", "ShipCountry": "Finland", "ShipCity": "Oulu", "OrderDate": "2016-08-01", "ShippedDate": "2016-08-02", "Freight": 23.75},
    {"OrderID": 10271, "CustomerID": "SPLIR", "ShipCountry": "USA", "ShipCity": "Lander", "OrderDate": "2016-08-01", "ShippedDate": "2016-08-30", "Freight": 16.00},
    {"OrderID": 10272, "CustomerID": "RATTC", "ShipCountry": "USA", "ShipCity": "Albuquerque", "OrderDate": "2016-08-02", "ShippedDate": "2016-08-06", "Freight": 27.50},
    {"OrderID": 10273, "CustomerID": "QUICK", "ShipCountry": "Germany", "ShipCity": "Cunewalde", "OrderDate": "2016-08-05", "ShippedDate": "2016-08-12", "Freight": 48.00},
    {"OrderID": 10274, "CustomerID": "VINET", "ShipCountry": "France", "ShipCity": "Reims", "OrderDate": "2016-08-06", "ShippedDate": "2016-08-16", "Freight": 16.75},
    {"OrderID": 11008, "CustomerID": "ERNSH", "ShipCountry": "Austria", "ShipCity": "Graz", "OrderDate": "2018-04-08", "ShippedDate": None, "Freight": 55.25},
    {"OrderID": 11019, "CustomerID": "RANCH", "ShipCountry": "Argentina", "ShipCity": "Buenos Aires", "OrderDate": "2018-04-13", "ShippedDate": None, "Freight": 11.25},
    {"OrderID": 11039, "CustomerID": "LINOD", "ShipCountry": "Venezuela", "ShipCity": "I. de Margarita", "OrderDate": "2018-04-21", "ShippedDate": None, "Freight": 43.00},
    ]
    len(orders)
    return (orders,)


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## Which record and which field

    `orders[0]` asks **which record**. `["ShipCountry"]` asks **which field**.
    """)
    return


@app.cell
def _(orders):
    orders[0]["ShipCountry"]
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## ✏️ The Orders Table

    Each question is one of the moves from section 2, done to records.

    1. What is the **total freight** across all 30 orders?
    2. How many orders have **no `ShippedDate`**? That field holds `None` for them, and
       `if order["ShippedDate"] is None:` is how you ask.
    3. Which order has the **largest** freight, and what is it?

    **Check yourself:** 827.00 · 3 · order 10263 at 56.00.

    Then ask your agent, and ask which concepts its answer used.

    **Going further.** Look at the three orders with no `ShippedDate`. What do they have
    in common that the other 27 do not? The answer is not about shipping.
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## ✏️ What One Row Means

    **In the markdown cell below**, replace the placeholder line with your own sentence,
    in words somebody outside this course would understand. Name what a row *is*. Listing
    the columns is not an answer.

    Start it with *One row is...*

    Then check it: if a row were what you just wrote, **how many rows would this table
    have?** Does that match 30?
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    *One row is ...*

    *(Replace this line with your own sentence. If this cell shows you code instead of
    text, use the cell menu to turn it into a markdown cell.)*
    """)
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 6. The Portfolio

    Six holdings. **What does it cost to buy the whole portfolio?**

    Work the steps below in order. They are what to do with any problem you cannot yet
    write yourself.

    1. **By hand, no agent. Markdown cell.** Write how you would do it in plain words,
       three or four lines. *"For each holding, multiply... then..."*
    2. **Ask your agent to write it.** Read what comes back before you keep it.
    3. **Ask it to explain every detail.** Pick the line you would not have written and
       ask what it does and why it is there.
    4. **Ask it to set you a similar problem**, then solve that one yourself.

    **Check yourself: $116,302.70.**
    """)
    return


@app.cell
def _():
    portfolio = [
        {"Symbol": "AAPL", "Shares": 100, "Price": 173.93},
        {"Symbol": "MSFT", "Shares": 50, "Price": 319.53},
        {"Symbol": "GOOG", "Shares": 80, "Price": 131.36},
        {"Symbol": "AMZN", "Shares": 200, "Price": 129.33},
        {"Symbol": "NVDA", "Shares": 20, "Price": 410.17},
        {"Symbol": "TSLA", "Shares": 150, "Price": 255.70},
    ]
    portfolio
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    ## Going Further · The Same Question From a File

    The version above hands you the data already typed into Python. Real data arrives in
    a file. Run the cell below: it writes `portfolio.csv` into your project's `data`
    folder, which is where [Your Python Project](https://oim6301.github.io/guides/python-projects/) says a data
    file goes, and makes that folder if you do not have one yet.

    Then work the same steps on this question: **open that file, read every line, and
    print the table and the total.** We have not reached files yet, so let the agent write
    that part and spend your time on steps 3 and 4.

    ```text
    name     shares     price
    AAPL        100    173.93
    ...
    Total cost: $116302.70
    ```
    """)
    return


@app.cell
def _(mo):
    _lines = ["name,shares,price"]
    for _holding in [
        ("AAPL", 100, 173.93), ("MSFT", 50, 319.53), ("GOOG", 80, 131.36),
        ("AMZN", 200, 129.33), ("NVDA", 20, 410.17), ("TSLA", 150, 255.70),
    ]:
        _lines.append(f"{_holding[0]},{_holding[1]},{_holding[2]}")

    # A data file goes in data/, one folder up from the notebook, as the Python Projects guide describes.
    _data_dir = mo.notebook_dir().parent / "data"
    try:
        _data_dir.mkdir(parents=True, exist_ok=True)
        portfolio_csv = _data_dir / "portfolio.csv"
        portfolio_csv.write_text("\n".join(_lines) + "\n")
        _where = f"wrote {portfolio_csv.parent.name}/{portfolio_csv.name}"
    except OSError as _error:
        _where = f"could not write into {_data_dir.name}/: {_error}"

    _where
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(r"""
    # 🚀 This Week's Work

    1. **What is due is listed under *Before next session* in the post-class notes.** I post them on the session page after each class, with the sections we reached, and the checkpoint on Canvas lists the same items.
    2. Commit as you go, with messages that say what changed, and push before you stop

    **Self-check:**

    - [ ] I can take one field out of one record, by name
    - [ ] I can state what one row of a table means, in a sentence
    - [ ] I can walk a collection and total the part of it that meets a condition
    - [ ] I can tell a missing value from a zero
    - [ ] I can hand a problem to an agent and check what comes back

    You will do the same things to a polars table in October and to a database table in
    November.
    """)
    return


if __name__ == "__main__":
    app.run()
