Patterns
Ways to build with Datagoat. Datagoat answers narrow questions about outcomes. Your code keeps control: it owns the thresholds, the costs and the action. These are Jev's patterns, rebuilt for answers that come from outcomes, plus the ones only a learned answer allows.
| Pattern | Question type | The decision |
|---|---|---|
| Fan-out | any | Ask everything once, use what you need |
| Gate | yesno |
Act, review, or don't |
| Grade | score |
Which band is this case in? |
| Route | choice |
Which option works best here? |
| Rerank | rank |
Which cases first? |
| Check | yesno |
Did this run succeed, from its telemetry? |
| Prove | levers | Did acting on the answers work? |
| Watch | drift | Has the pattern moved since we fitted? |
| Read, then learn | with Jev | Turn text into columns, then learn from them |
Fan-out
Send every question about one record in one call, including ones your code may not use. Questions
on the same outcome, with the same outcome_is_desirable, share one fit. An extra question adds no fit, only its own answered cases.
out = dg.ask({
"churn": yesno("churned", outcome_is_desirable=False),
"risk": score("churned", outcome_is_desirable=False),
"contract": choice(option_column="contract", options=["month-to-month", "one_year", "two_year"],
outcome_column="churned", outcome_is_desirable=False),
}, dataset_id="sample:telco_churn", entity_column="account_id", subject_kind="org",
cases={"ids": ["acct_0001"]})
Gate
Decide whether to act on a case, using the answer's state first and the chance second. It works like Jev's confidence-gated routing, with a third outcome: the record refused.
a = out["answers"]["churn"]
if a["state"] != "answered":
fallback() # refused or not_yet: the record can't say
else:
p = a["cases"][0]["p"]
if p >= 0.8: offer_discount() # expensive action: high bar
elif p >= 0.5: queue_call() # cheap action: lower bar
Thresholds scale with what a mistake costs. For people, or for anything you can't undo, send
answers near the threshold to a person with the reasons attached. band: true adds the engine's
own act / escalate / refuse gate to each case.
Grade
Put cases into bands that people act on. Set the cuts where your actions change:
score("defaulted", outcome_is_desirable=False,
levels=["approve", "review", "decline"], cuts=[0.1, 0.4])
To grade on several things at once, ask a score or yesno about each outcome and combine them
in your code, with weights you choose. That is Jev's composite scoring, with each part learned.
Route
Send each case to the option that works best for cases like it: a team, a channel, a plan.
choice(option_column="team", options=["billing", "engineering", "support"],
outcome_column="resolved_first_touch", outcome_is_desirable=True)
Jev's intent routing reads what a request says. Datagoat's routing reads what happened when
requests like it went to each team. When the team makes no difference in the record, the answer
is refused (option_not_in_pattern) rather than a coin flip.
Rerank
Order a queue by the chance of the outcome: the deals you'll lose, the accounts about to churn, the machines about to fault.
out = dg.ask({"at_risk": rank("lost", top_k=50, outcome_is_desirable=False)},
dataset_id=deals, entity_column="deal_id", subject_kind="org")
Rank today's new cases against the history with cases={"rows": [...]}.
Check
Judge whether something worked from its telemetry, not its self-report. For example, ask whether this agent run succeeded, answered from runs whose outcome was later confirmed. A log of runs is a traces record:
dg.ask({"fail": yesno("failed", outcome_is_desirable=False)},
dataset_id="sample:agent_traces", entity_column="run_id", subject_kind="event",
time_column="ts", shape=traces(agent_column="agent", task_column="task", tool_column="tool"),
cases={"ids": ["run_0001"]})
Prove
Find out whether acting on the answers works. When a case carries a lever with a lever_token,
act on it. Then record the action, report the outcome when you learn it, and read the evidence:
lever = case["levers"][0]
dg.attest(model_ref, case["entity_id"], lever["lever_token"], post_value="two_year", acted_at=now)
# … weeks later
dg.report_outcomes(model_ref, [{"entity_id": case["entity_id"], "outcome": 0, "observed_at": today}])
dg.evidence(model_ref) # acted vs not acted, once each has 30 outcomes
This is the question a zero-shot model can't answer for you. See Closing the loop.
Watch
Know when the pattern under your answers has moved. Refit on the new version of the record with
refit_of, then ask for drift:
new = dg.ask({"churn": yesno("churned", outcome_is_desirable=False, refit_of=old_model_ref)}, …)
dg.drift(new["answers"]["churn"]["model_ref"]) # {"pattern": "changed", "recommendation": "refit"}
Report real outcomes as they arrive (dg.report_outcomes). They build the model's track record,
and they never change an answer.
Read, then learn
Jev reads what a case says. Datagoat reads what happened to cases like it. Use both. Ask Jev questions about each ticket's text ("is the customer asking for a refund?", "how angry is this?"). Store the answers as columns beside the ticket's numbers and its eventual outcome. Datagoat then learns which of those judgements, with the numbers, predicts what happened, and it tells you when none do. See Datagoat and Jev.