Dates are the textbook case of Jev 1.13 jaggedness:
the model reads "August 14" as text, not as an ordered quantity, so asking which date comes
first or how far apart two dates are goes wrong – worse with mixed formats, "next Thursday",
and domain boundaries like quarters. TypeSafe's
date-extraction cookbook
shows the fix that keeps working: seven Choice questions read the date's
shape and parts off the text in one call; plain code turns the answers into a real
date, counting from today for relative dates, with a confidence gate
routing the weak reads to a human. This guide summarises the recipe with the key code shape.
You end up with one function, extract_date(document, role): hand it a document
and a phrase like "the deadline to return the form", get back a date plus a confidence –
or a flag saying no such date is stated, or a person should look at it.
The pattern: Choices for parts, code for calendars
Every part of a date is a small closed set – twelve months, thirty-one days, a bounded range
of years – which makes extraction a Choice over enumerated options rather than
free-form parsing, with an explicit "not stated" escape so a missing part is reported instead
of guessed. The seven questions:
mode– how the date is written:absolute(names a month),relative(today, tomorrow, day after, named weekday), ornone(the document never states this date).month,day,year– the absolute parts. The year list runs 1900–2050 with two escapes:none(code infers the year) andout_of_range(flagged, never guessed).day_anchor– today / tomorrow / day_after / weekday.weekday+week_offset– which weekday, and which week:current("this Thursday"),next("next Thursday"), or bare ("Thursday" = next occurrence on or after today).
Code reads only the pieces mode calls for, assembles the date, and
reports the lowest confidence among the parts it used – so one weak part
sends the whole date to review. Anything under 0.60, or anything code cannot assemble at
all, goes to a person; the rest go straight through.
The questions in code
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; "
"'relative' = relative to today; 'none' = the document does not state it."
),
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 (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? 'none' if unstated "
"(code infers it), 'out_of_range' if stated but off the list."
),
criteria={str(y): None for y in YEAR_WINDOW}
| {"out_of_range": "Stated but outside the listed range.",
"none": "No year is stated."},
),
"day_anchor": Choice(
instructions=f"If {role} is relative to today, which day is it?",
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?",
criteria={"current": None, "next": None, "none": absent},
),
}
Resolve it in code
The assemble step is deliberately boring calendar math: fill in a missing year (current
year, bumped to next only if the date is already more than a month past), map a named
weekday to a date by a stated convention, reject impossible dates like February 30 as
inconsistent reads. Both year inference and weekday resolution count from a pinned
TODAY so relative dates reproduce on every run:
def extract_date(document: str, role: str) -> dict:
return assemble(read_parts(document, role)) # one TypeSafe call, then pure code
Results on the demo set
Six questions over four short documents (contract, form, survey, review notice), resolved against a fixed Thursday. All six come out right:
| Question | Expected | Got | Conf | Flags |
|---|---|---|---|---|
| Date the agreement takes effect | 2025-01-01 | 2025-01-01 | 0.97 | – |
| Date the agreement expires | 2027-12-31 | 2027-12-31 | 0.91 | – |
| Deadline to return the form | 2026-08-14 | 2026-08-14 | 0.95 | – |
| Date of the kickoff call | none | none | 0.46 | review |
| Date the survey closes ("today") | 2026-07-30 | 2026-07-30 | 0.94 | – |
| Date of the design review ("next Thursday") | 2026-08-06 | 2026-08-06 | 0.92 | – |
The interesting row is the kickoff call – a date the form never mentions. The model still
returns absolute shape with no month to go with it, so the date comes back
empty at 0.46 confidence, flagged for a person instead of hallucinated. Five auto-accept,
one human review: that is the whole operating point.
Bottom line
Never ask Jev to do calendar math – ask it which boxes the text ticks, then do the math yourself. Choices over closed sets plus explicit "not stated" options plus weakest-link confidence is a reusable shape well beyond dates. Full source: TypeSafe's date-extraction cookbook; background on why dates are jagged in the Jev 1.13 jaggedness guide.