plural service_down nudges agree with the count (V-534)
Two services down read "Мониторинг сообщает: nginx, paperless лежит." — a list
dropped into the singular sentence. Russian agrees the verb with the subject,
so the noun, the verb and the adjective all have to move.
A family may now carry a second set named <rule>_many, used when {service}
holds more than one name. pluralFamily picks it; a family with no _many set is
returned unchanged, so adding one elsewhere is a data change. Only service_down
has one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011x5DgnExQ5XZy8TZPs5bot
This commit is contained in:
@@ -94,7 +94,7 @@ func (t *NudgeTemplates) PhraseNudge(_ context.Context, c loop.Candidate) (deliv
|
||||
// template fits it uses the plain per-rule fallback.
|
||||
func (t *NudgeTemplates) Nudge(c loop.Candidate) (body, mood string) {
|
||||
rule := c.Rule.Name
|
||||
family := t.family(rule)
|
||||
family := t.pluralFamily(t.family(rule), c)
|
||||
set, ok := t.file.Rules[family]
|
||||
if !ok {
|
||||
return fallbackNudge(c), "neutral"
|
||||
@@ -155,6 +155,25 @@ func (t *NudgeTemplates) family(rule string) string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
// pluralFamily swaps in the plural wording when {service} will hold a list.
|
||||
// Russian agrees the verb with the subject, so one set of templates cannot
|
||||
// serve both: "Сервис paperless не отвечает" and "Сервисы nginx, paperless не
|
||||
// отвечают" differ in the noun, the verb and the adjective. Filling a list into
|
||||
// the singular text is the kind of near-miss that reads as machine-written.
|
||||
//
|
||||
// Only service_down has a plural form today. A family with no "_many" set in
|
||||
// the file is returned unchanged, so adding one is a data change.
|
||||
func (t *NudgeTemplates) pluralFamily(family string, c loop.Candidate) string {
|
||||
if len(loop.DownServices(c.State)) < 2 {
|
||||
return family
|
||||
}
|
||||
many := family + "_many"
|
||||
if _, ok := t.file.Rules[many]; ok {
|
||||
return many
|
||||
}
|
||||
return family
|
||||
}
|
||||
|
||||
// placeholderRE — the {name} slots a template may use.
|
||||
var placeholderRE = regexp.MustCompile(`\{([a-z]+)\}`)
|
||||
|
||||
|
||||
@@ -55,6 +55,45 @@ func TestNudgeNamesTheDownService(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Russian agrees the verb with the subject, so a list of services cannot go
|
||||
// into the singular sentence. One down takes the singular set, two or more
|
||||
// take service_down_many.
|
||||
func TestNudgeAgreesWithTheServiceCount(t *testing.T) {
|
||||
nt := newTestTemplates(t, 9)
|
||||
// "упал " keeps its trailing space: "упали" starts with "упал", and the
|
||||
// plural must not read as the singular by prefix.
|
||||
singular := []string{"не отвечает", "недоступен", "лежит", "упал "}
|
||||
plural := []string{"не отвечают", "недоступны", "лежат", "упали"}
|
||||
|
||||
for i := 0; i < 60; i++ {
|
||||
body, _ := nt.Nudge(downCand("paperless"))
|
||||
if !containsAny(body, singular) {
|
||||
t.Fatalf("one down, no singular verb: %q", body)
|
||||
}
|
||||
if containsAny(body, plural) {
|
||||
t.Fatalf("one down, plural wording: %q", body)
|
||||
}
|
||||
}
|
||||
for i := 0; i < 60; i++ {
|
||||
body, _ := nt.Nudge(downCand("nginx", "paperless"))
|
||||
if !containsAny(body, plural) {
|
||||
t.Fatalf("two down, no plural verb: %q", body)
|
||||
}
|
||||
if containsAny(body, singular) {
|
||||
t.Fatalf("two down, singular wording: %q", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsAny(s string, subs []string) bool {
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Nothing down means no template fits, and the fallback answers rather than
|
||||
// the picker inventing a name.
|
||||
func TestNudgeServiceDownWithoutFacts(t *testing.T) {
|
||||
@@ -93,7 +132,7 @@ func newTestTemplates(t *testing.T, seed int64) *NudgeTemplates {
|
||||
|
||||
func TestNudgeTemplatesLoad(t *testing.T) {
|
||||
nt := newTestTemplates(t, 1)
|
||||
for _, rule := range []string{"water", "meal", "break", "service_down", "netdata_critical", "routine", "morning", "default"} {
|
||||
for _, rule := range []string{"water", "meal", "break", "service_down", "service_down_many", "netdata_critical", "routine", "morning", "default"} {
|
||||
set, ok := nt.file.Rules[rule]
|
||||
if !ok {
|
||||
t.Errorf("no templates for %q", rule)
|
||||
@@ -120,7 +159,7 @@ func TestNudgeTemplatesLoad(t *testing.T) {
|
||||
}
|
||||
seen[v] = true
|
||||
}
|
||||
if plain == 0 && rule != "routine" && rule != "morning" && rule != "service_down" {
|
||||
if plain == 0 && rule != "routine" && rule != "morning" && !strings.HasPrefix(rule, "service_down") {
|
||||
t.Errorf("%s: every variant needs a placeholder value", rule)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"Hand-written Russian nudges. Edit the wording here, no Go changes needed.",
|
||||
"Rules: she is feminine about herself, he is a man addressed as ты. Never вы/вас/ваш, never plural imperatives (выпейте), never он/его about him.",
|
||||
"One short sentence. No questions, no emoji, no pet names, no emotional support.",
|
||||
"Placeholders: {since} how long it has been (only used when it is at least an hour), {service} the service name, {what} the routine name. A variant whose placeholder has no value is skipped, so every rule needs at least one variant with no placeholder. The exception is routine and morning: those only exist for rules like routine:таблетки that always carry a name, and a routine nudge that drops the name is useless.",
|
||||
"Placeholders: {since} how long it has been (only used when it is at least an hour), {service} the service name, {what} the routine name. A variant whose placeholder has no value is skipped, so every rule needs at least one variant with no placeholder. The exception is routine, morning and service_down: those only exist for rules that always carry a name, and one that drops the name is useless.",
|
||||
"A rule may carry a second set named <rule>_many, used when {service} holds more than one name. Russian agrees the verb with the subject, so the plural needs its own wording rather than a list dropped into the singular sentence. Only service_down has one.",
|
||||
"mood must be one of: neutral, happy, thinking, tired, confused."
|
||||
],
|
||||
"rules": {
|
||||
@@ -67,6 +68,19 @@
|
||||
"Сервис {service} не отвечает, посмотри логи."
|
||||
]
|
||||
},
|
||||
"service_down_many": {
|
||||
"mood": "neutral",
|
||||
"variants": [
|
||||
"Сервисы {service} не отвечают.",
|
||||
"{service} упали — сервисы не отвечают.",
|
||||
"{service} не отвечают, сервисы нужно поднимать.",
|
||||
"Сервисы {service} недоступны.",
|
||||
"Проверь {service}: сервисы не отвечают.",
|
||||
"Сервисы {service} лежат, нужно смотреть.",
|
||||
"Мониторинг сообщает: {service} лежат.",
|
||||
"Сервисы {service} не отвечают, посмотри логи."
|
||||
]
|
||||
},
|
||||
"netdata_critical": {
|
||||
"mood": "neutral",
|
||||
"variants": [
|
||||
|
||||
Reference in New Issue
Block a user