docs / 3 / knowing-when-it-doesnt-know

Knowing when it doesn't know

Three words say how careful to be. They are not thresholds — you never name a number.

pythonlisting 3.1
from gut import likely
 
likely(email, "the customer threatens to cancel", lean="yes")
likely(email, "the customer threatens to cancel", ask_human=True)
likely(email, "the customer threatens to cancel", stakes="high", lean="yes", ask_human=True)
  • lean — "yes", "no", or nothing. Which mistake is worse, so which way to err.
  • ask_human — whether UNSURE is possible at all. Off by default; nothing becomes three-valued behind your back.
  • stakes — "low", "medium", "high". How bad an automatic mistake is next to a person looking instead. Only widens the range where a person is asked, so it needs ask_human=True; gut warns if you pass it without.

The two knobs are independent on purpose: being more careful must never quietly change which way you err. lean fixes where yes overtakes no; stakes only widens the band around it.

stakeslean=Nonelean="yes"lean="no"
lowask 0.40 – 0.60ask 0.20 – 0.40ask 0.60 – 0.80
mediumask 0.25 – 0.75ask 0.125 – 0.625ask 0.375 – 0.875
highask 0.10 – 0.90ask 0.05 – 0.85ask 0.15 – 0.95

Below the range it answers no, above it answers yes, inside it asks. Print the table yourself, so you never have to take this page's word for it:

pythonlisting 3.2
import gut
 
for preset in gut.presets():
    print(preset)

classify and rate take stakes and ask_human too, where they become a confidence floor. They do not take lean: there is no safer side of a four-way choice.

if and match

if decision: works. YES is truthy, NO is falsy, and UNSURE is an explicit choice rather than a silent one:

pythonlisting 3.3
import gut
 
gut.configure(on_unsure="raise")   # default: raises UnsureDecision
gut.configure(on_unsure="false")   # or "true", or a callback
 
with gut.on_unsure("false"):       # scoped; follows async tasks, never leaks across threads
    ...

The default raises because coercing UNSURE to False is the most dangerous option available: it is what your existing if already does, so the third branch would vanish down the "no" path — the exact bug this library exists to prevent, reintroduced as a default.

In a match, the outcomes must be dotted. A bare case YES: is not a value pattern in Python, it is a capture pattern that matches anything, and the compiler rejects it:

textlisting 3.4
SyntaxError: name capture 'YES' makes remaining patterns unreachable
pythonlisting 3.5
import gut
from gut import Outcome
 
match decision:
    case gut.YES: ...
    case gut.NO: ...
 
match decision:
    case Outcome.YES: ...