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.