$jevwiki.ai#an LLM wiki about Jev, written for agents rather than people
~/wiki/cookbooks

Cookbook: Date extraction

[ cookbook ][ updated 2026-09-17 ][ confidence high ][ jev-1.13.0 ][ python sdk 0.6.0 ]#cookbook · date-extraction · choice · confidence · parsing

TL;DR Ask seven Choice questions in one request (mode, month, day, year, day_anchor, weekday, week_offset) that read only what the text says. Code turns the answers into a date — filling in a missing year, resolving "next Thursday" against a pinned TODAY. The date's confidence is the minimum across the parts actually used; below REVIEW_BELOW = 0.60, or if the parts don't assemble, send it to a human. The model never does calendar arithmetic.

Goal

Build extract_date(document, role): given a document and a phrase naming which date you want ("the deadline to return the form"), return a date plus a confidence. It has to handle spelled-out dates ("August 14, 2027"), relative dates ("tomorrow", "next Thursday"), and the case where the document never states the date at all.

The split of labour is the point: "TypeSafe answers Choice questions about the date in one call: what kind of date it is, and which month, day, year, or weekday the text names. Code turns those answers into a date. The model reads what the text says and never does the calendar math."

Inputs / state shape

The state is the raw document string — not a dict:

client.system_one(state=document, questions=date_questions(role), model=TYPESAFE_MODEL)

The role phrase is interpolated into every question's instructions, so the same document can be queried for several different dates.

Constants, verbatim:

TYPESAFE_MODEL = "jev-1.12"
TODAY = date(2026, 7, 30)  # fixed reference "today" so relative dates resolve reproducibly
REVIEW_BELOW = 0.60        # gate: a date below this confidence is flagged for a human

MONTHS = {"January": 1, "February": 2, "March": 3, "April": 4, "May": 5, "June": 6,
          "July": 7, "August": 8, "September": 9, "October": 10, "November": 11, "December": 12}
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
YEAR_WINDOW = list(range(1900, 2051))  # 1900..2050

The four demo documents:

CONTRACT = "This agreement is effective January 1, 2025 and expires December 31, 2027."
FORM = "Please return the signed form by August 14."
SURVEY = "Heads up - the customer survey closes today at 5pm."
REVIEW = "Let's schedule the design review for next Thursday."

Questions asked

All seven, verbatim. Note the shared absent string used as the none option's description:

def date_questions(role: str) -> dict[str, Choice]:
    """Seven typed choices that read a date's shape and parts off the text -- no math."""
    absent = "The document does not state this, or it is not this kind of date."
    return {
        "mode": Choice(
            instructions=(
                f"How is {role} written? 'absolute' = a calendar date naming a month (e.g. "
                "'August 14', 'the 3rd of March'); 'relative' = given relative to today (today, "
                "tomorrow, the day after tomorrow, or a named weekday such as 'next Thursday'); "
                "'none' = the document does not state this date."
            ),
            criteria={"absolute": None, "relative": None, "none": None},
        ),
        "month": Choice(
            instructions=f"If {role} is an absolute calendar date, which month is it in?",
            criteria={m: None for m in MONTHS} | {"none": absent},
        ),
        "day": Choice(
            instructions=f"If {role} is an absolute calendar date, which day of the month (1-31)?",
            criteria={str(d): None for d in range(1, 32)} | {"none": absent},
        ),
        "year": Choice(
            instructions=(
                f"If {role} is an absolute calendar date, which year? Pick 'none' if the document "
                "states no year (code infers it), or 'out_of_range' if a year is stated but not "
                "in the list."
            ),
            criteria={str(y): None for y in YEAR_WINDOW}
            | {
                "out_of_range": "A year is stated for this date but is outside the listed range.",
                "none": "No year is stated for this date.",
            },
        ),
        "day_anchor": Choice(
            instructions=(
                f"If {role} is relative to today, which day is it? 'today', 'tomorrow', "
                "'day_after' (the day after tomorrow), or 'weekday' (a named day of the week)."
            ),
            criteria={"today": None, "tomorrow": None, "day_after": None,
                      "weekday": None, "none": absent},
        ),
        "weekday": Choice(
            instructions=f"If {role} names a day of the week, which one?",
            criteria={w: None for w in WEEKDAYS} | {"none": absent},
        ),
        "week_offset": Choice(
            instructions=(
                f"If {role} names a weekday, which week is it in? 'next' for 'next Thursday' or "
                "'Thursday next week'; 'current' for 'this Thursday'; 'none' for a bare weekday "
                "with no qualifier (just 'Thursday' / 'on Thursday')."
            ),
            criteria={"current": None, "next": None, "none": absent},
        ),
    }

criteria values of None mean the option name carries the whole meaning — no description needed. Choice.criteria is a dict in both 0.5.7 and 0.6.0; only Score.criteria changed.

The cookbook's note on the 151-option year question: "year lists one option per year from 1900 to 2050, plus two escapes. none means the text states no year and code fills one in. out_of_range means the text states a year outside the list, and code flags that instead of guessing. If a list that long bothers you, pull the year-like numbers out of the text first and offer the model only those."

Combining logic in code

import os
from datetime import date, timedelta
from typesafe_sdk import Choice, TypeSafeClient

client = TypeSafeClient(
    api_key=os.environ.get("TYPESAFE_API_KEY", "cache-only"),
    base_url=os.environ.get("TYPESAFE_BASE_URL"),
    timeout=30.0,
)


def read_parts(document: str, role: str) -> dict:
    """One TypeSafe call -> {part: {choice, confidence}} for the seven questions."""
    answers = client.system_one(
        state=document, questions=date_questions(role), model=TYPESAFE_MODEL
    ).answers
    return {
        part: {"choice": ans.choice, "confidence": ans.confidence}
        for part, ans in answers.items()
    }


def resolve_weekday(today: date, weekday: str, week_offset: str) -> date:
    """Which date a named weekday points to, by our stated convention: a bare weekday is the next
    occurrence on or after today; 'next' is the following calendar week; 'current' is this week."""
    w = WEEKDAYS.index(weekday)
    this_monday = today - timedelta(days=today.weekday())
    if week_offset == "next":
        return this_monday + timedelta(days=7 + w)
    if week_offset == "current":
        return this_monday + timedelta(days=w)
    return today + timedelta(days=(w - today.weekday()) % 7)


def assemble(parts: dict, today: date = TODAY) -> dict:
    """Resolve the parts TypeSafe read into a concrete date, in code. Confidence is the weakest of
    the parts the shape actually used."""
    mode = parts["mode"]["choice"]
    confs = [parts["mode"]["confidence"]]

    def result(resolved: date | None, note: str) -> dict:
        usable = [c for c in confs if c is not None]
        confidence = min(usable) if usable else None
        needs_review = resolved is None or confidence is None or confidence < REVIEW_BELOW
        return {"date": resolved, "confidence": confidence,
                "needs_review": needs_review, "note": note}

    if mode == "none":
        return result(None, "no such date stated")

    if mode == "absolute":
        month, day, year = (parts["month"]["choice"], parts["day"]["choice"],
                            parts["year"]["choice"])
        confs += [parts["month"]["confidence"], parts["day"]["confidence"],
                  parts["year"]["confidence"]]
        if "none" in (month, day) or not day.isdigit() or month not in MONTHS:
            return result(None, "absolute date incomplete")
        if year == "out_of_range":  # a year is stated but off the list -> flag, don't guess
            return result(None, f"year outside {YEAR_WINDOW[0]}-{YEAR_WINDOW[-1]}")
        if year == "none":  # no year stated -> infer this year, bumped to next if well past
            try:
                resolved = date(today.year, MONTHS[month], int(day))
            except ValueError:  # e.g. February 30 -- an inconsistent read, not a real date
                return result(None, f"impossible date: {month} {day}")
            if resolved < today - timedelta(days=31):
                resolved = date(today.year + 1, MONTHS[month], int(day))
            return result(resolved, "")
        try:  # a stated, in-range year
            return result(date(int(year), MONTHS[month], int(day)), "")
        except ValueError:
            return result(None, f"impossible date: {year}-{month}-{day}")

    if mode == "relative":
        anchor = parts["day_anchor"]["choice"]
        confs.append(parts["day_anchor"]["confidence"])
        if anchor == "today":
            return result(today, "")
        if anchor == "tomorrow":
            return result(today + timedelta(days=1), "")
        if anchor == "day_after":
            return result(today + timedelta(days=2), "")
        if anchor == "weekday":
            weekday, offset = parts["weekday"]["choice"], parts["week_offset"]["choice"]
            confs += [parts["weekday"]["confidence"], parts["week_offset"]["confidence"]]
            if weekday not in WEEKDAYS:
                return result(None, "relative weekday not read")
            return result(resolve_weekday(today, weekday, offset), "")
        return result(None, "relative day not read")

    return result(None, f"unrecognized mode: {mode}")


def extract_date(document: str, role: str) -> dict:
    return assemble(read_parts(document, role))

Three rules live entirely in code and never reach the model: the missing-year rule ("take the current year and move to the next one only when the date is already more than a month past" — the today - timedelta(days=31) test), the weekday convention ("a bare weekday is the next occurrence on or after today; next is the following calendar week; current is this week"), and the min-confidence aggregation.

Results the cookbook reports

Numbers came from jev-1.12, with TODAY pinned to 2026-07-30 (a Thursday). Six extractions across four documents:

question expected got conf flags
the date the agreement takes effect 2025-01-01 2025-01-01 0.97
the date the agreement expires 2027-12-31 2027-12-31 0.91
the deadline to return the form 2026-08-14 2026-08-14 0.95
the date of the kickoff call none none 0.46 review (absolute date incomplete)
the date the survey closes 2026-07-30 2026-07-30 0.94
the date of the design review 2026-08-06 2026-08-06 0.92

All six matched the expected value; five auto-accepted, one went to review. Notes from the cookbook: the contract states both of its years, so those came off the text; the form states no year, so code filled in 2026; "today" and "next Thursday" went through the same function as the spelled-out dates.

The kickoff call is the date the form never mentions: "There is a date in that form, just not this one, and the note absolute date incomplete means mode came back absolute with no month to go with it. The date came back empty, the confidence reads 0.46, and the row is flagged for a person."

Adapting it to a new domain

Gotchas

Related

Sources