Functional Weave
Code in Python

todo.summary

Counts for a todo list's header or footer: total, active, done, overdue, due today, percent done and "3 items left".

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 13 tests, run in TypeScript, Python and Rust.

What it does

The numbers a todo list shows about itself, in one call: how many there are, how many are open and done, how many are overdue or due today, the percentage done and the "N items left" text.

- `overdue` and `dueToday` come from todo.due-status, so they count exactly the todos in the Overdue and Today sections of todo.group-by-due. Done todos are never counted as overdue. - **`percentDone` rounds down** (math.round-div, mode `down`): 199 of 200 done is 99, not 100, so the bar only reads 100% when everything is done, and only reads 0% when nothing is. An empty list is 0. - `itemsLeft` counts active todos, with English plurals: `"0 items left"`, `"1 item left"`, `"2 items left"`. A localised app builds its own string from `active`. - `today` is checked even for an empty list, and a malformed or impossible date anywhere is an error (dates.add-days's messages).

For example

  • summarise_todos(, 2026-09-28) → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left an empty list is all zeros
  • summarise_todos(todos ×3, 2026-09-28) → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left everything done is 100%, 0 items left, and a done late todo is not overdue
  • summarise_todos(todos ×1, 2026-09-28) → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left one open todo due today is 1 item left

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def summarise_todos(todos: Sequence[Todo], today: str) -> TodoSummary
todosTodo[]
todaydatethe user's local date, from the app; never the clock
returnsTodoSummary

The type it declares, generated into your project

@dataclass(frozen=True)
class TodoSummary:
    """The numbers a todo list shows about itself."""

    total: int
    #: not done
    active: int
    done: int
    #: open and past due
    overdue: int
    #: open and due today
    due_today: int
    #: done * 100 / total rounded down; 0 for an empty list
    percent_done: int
    #: "0 items left", "1 item left", "3 items left"
    items_left: str

Your code names it in one line, in the file that uses it

from fune.todo.summary import summarise_todos  # todo.summary@^1
impl/python.py · 34 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Sequence

from .dates_day_of_week import day_of_week  ← from dates.day-of-week ^1.0.0 · built alongside by fune
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .todo_due_status import due_status  ← from todo.due-status ^1.0.0 · built alongside by fune
from .todo_item import Todo  ← from todo.item ^1.0.0 · built alongside by fune
from .todo_summary_types import TodoSummary


def summarise_todos(todos: Sequence[Todo], today: str) -> TodoSummary:
    """Counts for a todo list; percent_done rounds down so 100 means all done."""
    day_of_week(today)  # a bad today is an error even for an empty list
    done = 0
    overdue = 0
    due_today = 0
    for todo in todos:
        status = due_status(todo, today)
        if todo.done:
            done += 1
        if status == "overdue":
            overdue += 1
        if status == "today":
            due_today += 1
    total = len(todos)
    active = total - done
    return TodoSummary(
        total=total,
        active=active,
        done=done,
        overdue=overdue,
        due_today=due_today,
        percent_done=0 if total == 0 else round_div(done * 100, total, "down"),
        items_left="1 item left" if active == 1 else "%d items left" % active,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add todo.summary
Download for Python todo.summary-1.0.0-python.fune · 62,916 bytes sha256 3c014b083183ebca368ab0bbb49727756b0108f72d231c6249da355271b28369

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./todo.summary-1.0.0-python.fune, or fetch it from a terminal with fune pull todo.summary@1.0.0:python.

The whole function, every language, is one file too: todo.summary-1.0.0.fune, 66,009 bytes, sha256 c18e7aa1361361be82208402f37176789c3697b153d357e69072e6d82a83db45. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before todo.summary

after — your function gets the result and the arguments, and returns the final result.

# fune: after todo.summary

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace dates.day-of-week in todo.summary
# fune: replace math.round-div in todo.summary
# fune: replace todo.due-status in todo.summary
# fune: replace todo.item in todo.summary

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show todo.summary --steps.

# fune: step todo.summary after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
an empty list is all zeros , 2026-09-28 → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left
everything done is 100%, 0 items left, and a done late todo is not overdue todos ×3, 2026-09-28 → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left
one open todo due today is 1 item left todos ×1, 2026-09-28 → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left
a mixed list todos ×4, 2026-09-28 → total 4, active 3, done 1, overdue 1, due today 1, percent done 25, items left 3 items left
two of three done rounds down to 66 todos ×3, 2026-09-28 → total 3, active 1, done 2, overdue 0, due today 0, percent done 66, items left 1 item left
one of three done rounds down to 33 todos ×3, 2026-09-28 → total 3, active 2, done 1, overdue 0, due today 0, percent done 33, items left 2 items left
199 of 200 done is 99, not 100 todos ×200, 2026-09-28 → total 200, active 1, done 199, overdue 0, due today 0, percent done 99, items left 1 item left
a done todo due today does not count as due today todos ×2, 2026-09-28 → total 2, active 1, done 1, overdue 0, due today 1, percent done 50, items left 1 item left
two overdue, from last year and yesterday todos ×3, 2026-09-28 → total 3, active 3, done 0, overdue 2, due today 0, percent done 0, items left 3 items left
on Sunday, yesterday's todo is overdue todos ×2, 2026-10-04 → total 2, active 2, done 0, overdue 1, due today 1, percent done 0, items left 2 items left
Show the other 3 tests
CaseArgumentsExpected
a malformed today is an error even for an empty list , 20260928 → error: is not an ISO date
a today that never existed todos ×1, 2026-02-29 → error: is not a real calendar date
a due date that never existed todos ×1, 2026-09-28 → error: is not a real calendar date

Files

PathBytes
README.md898
impl/python.py1,074
impl/rust.rs1,831
impl/typescript.ts1,100
vectors.json50,954