Skip to main content
valen

Pick the right date from an email

Supplier emails mention many dates: when the order was placed, the date that slipped, an invoice number that looks like a date, and somewhere the new delivery date. Select, don't generate: code finds every date with a regular expression and normalizes it, Valen picks which one is the new delivery date, and code copies the normalized value into the order. The model never writes a date, so it can never write a malformed one.

How it works​

QuestionTypePurpose
has_new_date: the supplier gives a new delivery datepredicatethe null case: no date at all, or the old date confirmed
delivery_date: which date is the new delivery date?choice over the dates code foundpicks the span

Each candidate's description quotes the text around it, so the model judges the date in context. The truth table:

has_new_datedelivery_dateAction
below 0.3, or no dates found—no_new_date
0.7 or abovetop ≥ 0.8 and gap ≥ 0.2update_order with the normalized date
otherwiseotherwisereview

Run it​

Download the bundle once. The recorded answers in caches/value_extraction.json let the script run without an API key; --replay off calls the free hosted API instead.

Bash
curl -fLO https://valen-team.github.io/cookbook/valen-cookbook.zip
python -m zipfile -e valen-cookbook.zip .
cd valen-cookbook && python -m pip install -r requirements.txt
python value_extraction.py
python value_extraction.py --replay off --base-url https://api.valen-team.workers.dev/v1

Code​

value_extraction.py
"""Select instead of generate: code finds the dates, Valen picks the one the business needs. Apache-2.0."""
from datetime import date
import json
import re

from common import HERE, choice_status, client, emit, parser, request

MONTHS = {m: i + 1 for i, m in enumerate(["january", "february", "march", "april", "may", "june", "july",
"august", "september", "october", "november", "december"])}
PATTERN = re.compile(r"\b(\d{4})-(\d{2})-(\d{2})\b|\b(\d{1,2}) (" + "|".join(MONTHS) + r")\b|\b("
+ "|".join(MONTHS) + r") (\d{1,2})\b", re.IGNORECASE)


def candidates(text, year):
"""Every date written in the message, normalized by code; the model never writes a date."""
found = []
for m in PATTERN.finditer(text):
if m.group(1):
value = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
else:
day, month = (m.group(4), m.group(5)) if m.group(4) else (m.group(7), m.group(6))
value = date(year, MONTHS[month.lower()], int(day))
found.append({"span": m.group(0), "iso": value.isoformat(),
"context": text[max(0, m.start() - 60):m.end() + 40].replace("\n", " ")})
return found


def run(predict, email):
found = candidates(email["text"], email["year"])
asked = [{"name": "has_new_date", "type": "predicate",
"instructions": "The supplier gives a new delivery date in this email."}]
if found:
asked.append({"name": "delivery_date", "type": "choice",
"instructions": "Which date is the new date the order will be delivered?",
"choices": [{"value": c["span"], "description": f"The date in: “{c['context']}”"}
for c in found]})
answers = request(predict, email["text"], asked)
has_new = answers["has_new_date"]["probability"]
result = {"id": email["id"], "candidates": [c["span"] for c in found], "has_new_date": has_new}
if has_new < 0.3 or not found:
return {**result, "status": "no_new_date"}
picked = answers["delivery_date"]
confidence, certain = choice_status(picked)
status = "update_order" if certain and has_new >= 0.7 else "review"
iso = next(c["iso"] for c in found if c["span"] == picked["choice"])
return {**result, "status": status, "delivery_date": iso, "span": picked["choice"], "confidence": confidence}


def main():
args = parser(__doc__).parse_args()
emails = json.loads((HERE / "value_extraction_data.json").read_text())
with client(args) as predict:
emit([run(predict, email) for email in emails], args)


if __name__ == "__main__":
main()
value_extraction_data.json
[
{"id": "po-1182", "year": 2026, "text": "Hi Mei,\n\nThanks for your order placed on 2 March. Unfortunately the glaze kiln is down, so we can't ship on March 9 as promised. The new delivery date is 16 March. Invoice 2026-03-02 stays the same.\n\nBest,\nTomas"},
{"id": "po-1190", "year": 2026, "text": "Hello,\n\nQuick update on PO 1190: everything is on track and will arrive on 2026-03-20 as originally agreed. Our office is closed on 18 March for a holiday.\n\nRegards,\nPriya"},
{"id": "po-1204", "year": 2026, "text": "Hi team,\n\nThe courier lost the first pallet. We are re-shipping today; you should have it by April 7 instead of 31 March. Sorry about this. We'll credit the freight on the 15 April statement.\n\nAnna"},
{"id": "po-1211", "year": 2026, "text": "Dear Mei,\n\nWe received your payment, thank you. Delivery is being planned and we'll confirm a date next week.\n\nKind regards,\nTomas"}
]

Output​

PO 1182 has four candidates, including the invoice number 2026-03-02; Valen picks "16 March". PO 1190 confirms the original date, so has_new_date is 0.004 and the order stays as it is.

Recorded output
[
{
"id": "po-1182",
"candidates": [
"2 March",
"March 9",
"16 March",
"2026-03-02"
],
"has_new_date": 0.9980370964696507,
"status": "update_order",
"delivery_date": "2026-03-16",
"span": "16 March",
"confidence": 0.970832077353691
},
{
"id": "po-1190",
"candidates": [
"2026-03-20",
"18 March"
],
"has_new_date": 0.00443016482795233,
"status": "no_new_date"
},
{
"id": "po-1204",
"candidates": [
"April 7",
"31 March",
"15 April"
],
"has_new_date": 0.9780033681094145,
"status": "update_order",
"delivery_date": "2026-04-07",
"span": "April 7",
"confidence": 0.9725751976466991
},
{
"id": "po-1211",
"candidates": [],
"has_new_date": 0.19682010421693014,
"status": "no_new_date"
}
]

Make it yours​

  • The same shape works for amounts, order numbers, names and addresses: find candidates with code, ask Valen which one the business means, normalize in code.
  • Add the year, the time zone or the sender's locale in code; the model only chooses.