// Package wing decides which brain wing an incoming raw note belongs to // without the human typing it (hyperguild#205, #208). // // An earlier design classified each note with a local model and cross-checked // it with a text score. Measured against pages whose wing a human had chosen, // the model was right 60% of the time with a saturated self-reported // confidence, and the cross-check was right 11% of the time, so the gate it // formed placed 2 of 55 pages (hyperguild#208). The Router that replaced it // decides with rules only: a deliberate folder or frontmatter wing, else one // default wing; nothing it cannot decide safely is guessed. package wing import ( "context" "strings" ) // Source says how a Decision was reached. type Source string const ( Explicit Source = "explicit" // subfolder or frontmatter named the wing Default Source = "default" // no signal; the configured default wing Exception Source = "exception" // needs a human ) // Note is what the Placer sees of an incoming raw note. type Note struct { Dir string // first subfolder under raw/, "" when dropped at the top FrontmatterWing string // `wing:` from the note's frontmatter, "" if absent Text string } // Decision is the outcome. For an Exception, Wing holds the proposal when // there was one (so a reviewer can approve it) and is empty otherwise. type Decision struct { Wing string Source Source Confidence float64 Reason string } // Placer decides where a raw note goes. Router is the production // implementation; the interface lets the watcher stay ignorant of how. type Placer interface { Resolve(ctx context.Context, n Note) Decision } func exception(w string, conf float64, reason string) Decision { return Decision{Wing: w, Source: Exception, Confidence: conf, Reason: reason} } func norm(s string) string { return strings.ToLower(strings.TrimSpace(s)) }