package config import "time" // The two blocks that shape what reaches him unasked: how nudges are batched, // and whether a routine Maven inferred by herself may be announced at all. // DigestConfig — notification batching / digest mode. When enabled, eligible // nudges (severity ≤ SeverityCeiling) are queued in memory instead of sent // immediately. Every Window duration (or when MaxItems reached), the queue is // flushed as a single digest notification. nil ⇒ digest disabled (legacy // behaviour — every nudge is sent as it fires). type DigestConfig struct { Enabled bool `json:"enabled,omitempty"` Window Duration `json:"window,omitempty"` // e.g. "30m" MaxItems int `json:"max_items,omitempty"` // flush at this count SeverityCeiling int `json:"severity_ceiling,omitempty"` // max sev batched } // Digest defaults, applied in normaliseDigest. const ( DefaultDigestWindow = 30 * time.Minute DefaultDigestMaxItems = 5 DefaultDigestSeverityCeiling = 2 ) // normaliseDigest is the one block that does NOT fold an absent block to nil: // it materialises a disabled one instead, because the dispatcher reads // c.Digest.Enabled without a nil check. func (c *Config) normaliseDigest() { if c.Digest == nil { c.Digest = &DigestConfig{Enabled: false} } if c.Digest.Window == 0 { c.Digest.Window = Duration(DefaultDigestWindow) } if c.Digest.MaxItems == 0 { c.Digest.MaxItems = DefaultDigestMaxItems } if c.Digest.SeverityCeiling == 0 { c.Digest.SeverityCeiling = DefaultDigestSeverityCeiling } } // PatternProposalConfig — announcement policy for routines the digestion tick // inferred by itself (Vikunja #247, #43). // // Detection is always on and always silent by default: the tick writes a // proposed_routines row and the /routines page shows it. Notify is what turns // "she noticed" into "she said something", and it is OFF unless configured — // Maven is not a nag and not autonomous, so a behaviour that speaks without // being asked has to be switched on deliberately, like weather and telegram. // // When Notify is on, the announcement is still heavily restrained: // - at most one proposal per tick, however many were detected; // - at most one per Cooldown across all pairs (not per pair), so a batch of // freshly-detected patterns cannot turn into a queue of interruptions; // - through the ordinary care-class gate (quiet hours / away / snooze), at // sev1 — the lowest severity there is. A proposal is the least urgent // thing Maven can say. // // A pair is only ever announced once, because it is only ever proposed once: // proposed_routines is UNIQUE(action, object) and the row survives dismissal. type PatternProposalConfig struct { // Notify — announce newly inferred routines. Default false. Notify bool `json:"notify,omitempty"` // Cooldown — minimum spacing between two proposal announcements. 0 ⇒ // DefaultProposalCooldown (24h). Cooldown Duration `json:"cooldown,omitempty"` } // DefaultProposalCooldown — one inferred-routine announcement per day at // most. A proposal is never urgent; if two patterns surface in the same // hour, the second one waits, and the /routines page has it either way. const DefaultProposalCooldown = 24 * time.Hour // AnnounceProposals reports whether inferred routines may be announced. Safe // on a nil receiver — an absent config block means silent detection. func (p *PatternProposalConfig) AnnounceProposals() bool { return p != nil && p.Notify } // normalisePatternProposals leaves an absent block nil, which means silent // detection. A present-but-partial one gets the cooldown, so `{"notify": true}` // is enough to switch announcements on. func (c *Config) normalisePatternProposals() { if c.PatternProposals != nil && c.PatternProposals.Cooldown <= 0 { c.PatternProposals.Cooldown = Duration(DefaultProposalCooldown) } }