4ba9a6f422
Review internal/phraser/eval/checks.go -- it IS the measurement. Each check names in a comment which DESIGN.md line it defends: length, feminine self-reference (windowed around "я" so the operator's own masculine second-person forms are not flagged), the cringe list (pet names, emoji, "!!", fake concern, apology, emotional support, asking how he feels, praise), on-topic, mood enum. No send/veto signal anywhere, per DESIGN.md § "Rules decide, LLM phrases". Fixture (158 lines) and tests (252) do not count toward the diff ceiling; the scorer itself is still ~650. Splitting eval.go from checks.go would give two commits neither of which measures anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
159 lines
6.5 KiB
JSON
159 lines
6.5 KiB
JSON
{
|
|
"schema_version": 1,
|
|
"name": "nudge phrasing v1",
|
|
"reference_now": "2026-07-31T21:40:00+03:00",
|
|
"notes": [
|
|
"The five loop rules (water, meal, break, service_down, netdata_critical) plus one routine: and one morning: case, at the severities they actually ship with.",
|
|
"The bad-moment cases (quiet_hours, calendar_busy) are here because the gate already let them through — ops outranks quiet hours. The phrasing must stay short and plain, not apologise for the timing.",
|
|
"want_any lists stems, not whole words, so declension does not defeat the on-topic check. English stems are included because the deterministic Stub is still English.",
|
|
"There is no expected message. The checks measure properties, not similarity to a reference sentence — a fixed golden string would just freeze one arbitrary phrasing."
|
|
],
|
|
"cases": [
|
|
{
|
|
"id": "water-3h",
|
|
"rule": "water",
|
|
"severity": 1,
|
|
"since_minutes": 190,
|
|
"want_any": ["вод", "попей", "пить", "выпей", "напит", "water", "drink"],
|
|
"tags": ["care"],
|
|
"note": "The base case. Just over the 3h predicate."
|
|
},
|
|
{
|
|
"id": "water-7h",
|
|
"rule": "water",
|
|
"severity": 1,
|
|
"since_minutes": 430,
|
|
"want_any": ["вод", "попей", "пить", "выпей", "напит", "water", "drink"],
|
|
"tags": ["care"],
|
|
"note": "Long overdue. Severity is unchanged, so the phrasing must not escalate into alarm."
|
|
},
|
|
{
|
|
"id": "water-busy",
|
|
"rule": "water",
|
|
"severity": 1,
|
|
"since_minutes": 240,
|
|
"calendar_busy": true,
|
|
"want_any": ["вод", "попей", "пить", "выпей", "напит", "water", "drink"],
|
|
"tags": ["care", "bad-moment"],
|
|
"note": "Mid-meeting. A bad moment invites an apology, which is the check that should catch it."
|
|
},
|
|
{
|
|
"id": "meal-7h",
|
|
"rule": "meal",
|
|
"severity": 1,
|
|
"since_minutes": 420,
|
|
"want_any": ["ешь", "еда", "еды", "поешь", "перекус", "обед", "ужин", "покуш", "food", "eat"],
|
|
"tags": ["care"]
|
|
},
|
|
{
|
|
"id": "meal-11h-quiet",
|
|
"rule": "meal",
|
|
"severity": 1,
|
|
"since_minutes": 660,
|
|
"quiet_hours": true,
|
|
"want_any": ["ешь", "еда", "еды", "поешь", "перекус", "обед", "ужин", "покуш", "food", "eat"],
|
|
"tags": ["care", "bad-moment"],
|
|
"note": "Quiet hours suppresses care nudges in the gate, so this one only reaches the phraser via an explicit override. Included because it is the shape most likely to draw a hedge."
|
|
},
|
|
{
|
|
"id": "break-90m",
|
|
"rule": "break",
|
|
"severity": 2,
|
|
"since_minutes": 95,
|
|
"want_any": ["перерыв", "разомн", "встань", "отдохн", "пауз", "отойд", "размин", "break", "step away"],
|
|
"tags": ["care"]
|
|
},
|
|
{
|
|
"id": "break-4h",
|
|
"rule": "break",
|
|
"severity": 2,
|
|
"since_minutes": 240,
|
|
"want_any": ["перерыв", "разомн", "встань", "отдохн", "пауз", "отойд", "размин", "break", "step away"],
|
|
"tags": ["care"]
|
|
},
|
|
{
|
|
"id": "break-busy",
|
|
"rule": "break",
|
|
"severity": 2,
|
|
"since_minutes": 150,
|
|
"calendar_busy": true,
|
|
"want_any": ["перерыв", "разомн", "встань", "отдохн", "пауз", "отойд", "размин", "break", "step away"],
|
|
"tags": ["care", "bad-moment"]
|
|
},
|
|
{
|
|
"id": "service-down",
|
|
"rule": "service_down",
|
|
"severity": 4,
|
|
"since_minutes": 3,
|
|
"fact_key": "vaultwarden",
|
|
"fact_value": "\"down\"",
|
|
"fact_source": "poll:uptimekuma",
|
|
"want_any": ["vaultwarden", "сервис", "упал", "не отвеч", "лежит", "недоступ", "down", "service"],
|
|
"tags": ["ops"],
|
|
"note": "The service name is in the fact key, not the value. A nudge that says 'a service' without naming it is on-topic but useless — the on-topic check cannot catch that, a human reading Messages() can."
|
|
},
|
|
{
|
|
"id": "service-down-night",
|
|
"rule": "service_down",
|
|
"severity": 4,
|
|
"since_minutes": 2,
|
|
"quiet_hours": true,
|
|
"fact_key": "nextcloud",
|
|
"fact_value": "\"down\"",
|
|
"fact_source": "poll:uptimekuma",
|
|
"want_any": ["nextcloud", "сервис", "упал", "не отвеч", "лежит", "недоступ", "down", "service"],
|
|
"tags": ["ops", "bad-moment"],
|
|
"note": "03:00-shaped. Sev4 outranks quiet hours by design, so she speaks — plainly, without softening it into a maybe."
|
|
},
|
|
{
|
|
"id": "netdata-disk",
|
|
"rule": "netdata_critical",
|
|
"severity": 3,
|
|
"since_minutes": 5,
|
|
"fact_key": "netdata_alarm",
|
|
"fact_value": "\"critical\"",
|
|
"fact_source": "poll:netdata",
|
|
"want_any": ["netdata", "диск", "критич", "алярм", "аларм", "тревог", "место", "памят", "critical", "alarm", "disk"],
|
|
"tags": ["ops"]
|
|
},
|
|
{
|
|
"id": "netdata-busy",
|
|
"rule": "netdata_critical",
|
|
"severity": 3,
|
|
"since_minutes": 12,
|
|
"calendar_busy": true,
|
|
"fact_key": "netdata_alarm",
|
|
"fact_value": "\"critical\"",
|
|
"fact_source": "poll:netdata",
|
|
"want_any": ["netdata", "диск", "критич", "алярм", "аларм", "тревог", "место", "памят", "critical", "alarm", "disk"],
|
|
"tags": ["ops", "bad-moment"]
|
|
},
|
|
{
|
|
"id": "routine-pills",
|
|
"rule": "routine:таблетки",
|
|
"severity": 2,
|
|
"since_minutes": 0,
|
|
"want_any": ["таблетк", "приня", "лекарств", "pill"],
|
|
"tags": ["routine"],
|
|
"note": "A routine: rule has no fact of its own, so there is no duration to name. The rule name is the only context."
|
|
},
|
|
{
|
|
"id": "routine-stretch",
|
|
"rule": "routine:зарядка",
|
|
"severity": 1,
|
|
"since_minutes": 0,
|
|
"want_any": ["зарядк", "размин", "упражн", "потянис", "разомн", "stretch", "exercise"],
|
|
"tags": ["routine"]
|
|
},
|
|
{
|
|
"id": "morning-checklist",
|
|
"rule": "morning:утро",
|
|
"severity": 2,
|
|
"since_minutes": 0,
|
|
"want_any": ["утр", "чеклист", "список", "не сделан", "осталось", "morning"],
|
|
"tags": ["routine"],
|
|
"note": "In production cmd/mavend/tick.go phrases morning routines deterministically and never calls the LLM. Scored anyway: the phraser is reachable with this rule name, and a fallback that garbles it is still a bug."
|
|
}
|
|
]
|
|
}
|