174 lines
5.7 KiB
Go
174 lines
5.7 KiB
Go
package brain
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"neuroforge/internal/core"
|
|
"neuroforge/internal/store"
|
|
"neuroforge/internal/vector"
|
|
)
|
|
|
|
var targetNumberRE = regexp.MustCompile(`(?i)(\d{1,9})`)
|
|
|
|
func (e *Engine) refreshGoalResearchProgress(goal *core.Goal, evaluation float64) {
|
|
if goal == nil {
|
|
return
|
|
}
|
|
// Recompute from relevant evidence instead of keeping monotonic counters from
|
|
// old research runs. This intentionally lets upgrades remove previously
|
|
// counted off-topic evidence (for example an NVIDIA goal polluted by WebRTC).
|
|
sourceSet := map[string]struct{}{}
|
|
memorySet := map[string]struct{}{}
|
|
corroborationSet := map[string]struct{}{}
|
|
runs := e.store.ResearchRunsSnapshot(goal.ID, 200)
|
|
for _, run := range runs {
|
|
for _, ev := range run.Events {
|
|
if ev.Type != "evidence.learned" && ev.Type != "evidence.corroborated" {
|
|
continue
|
|
}
|
|
m, ok := e.store.GetMemory(ev.MemoryID)
|
|
if !ok || m == nil {
|
|
continue
|
|
}
|
|
var src *core.KnowledgeSource
|
|
if m.Provenance.SourceID != "" {
|
|
if x, ok := e.store.GetSource(m.Provenance.SourceID); ok {
|
|
src = x
|
|
}
|
|
}
|
|
if !goalEvidenceRelevant(goal, *m, src) {
|
|
continue
|
|
}
|
|
memorySet[m.ID] = struct{}{}
|
|
if ev.SourceID != "" {
|
|
sourceSet[ev.SourceID] = struct{}{}
|
|
} else if m.Provenance.SourceID != "" {
|
|
sourceSet[m.Provenance.SourceID] = struct{}{}
|
|
}
|
|
if ev.Type == "evidence.corroborated" && ev.SourceID != "" {
|
|
corroborating, ok := e.store.GetSource(ev.SourceID)
|
|
if ok && corroborating != nil {
|
|
primaryOrigin := ""
|
|
if src != nil {
|
|
primaryOrigin = sourceOriginKey(src.URI)
|
|
}
|
|
origin := sourceOriginKey(corroborating.URI)
|
|
if origin != "" && origin != primaryOrigin {
|
|
corroborationSet[m.ID+"\x00"+origin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Durable provenance/legacy goal tags cover evidence older than the bounded
|
|
// research-run history and make the relevance repair effective after restart.
|
|
for _, m := range e.store.MemoriesSnapshot() {
|
|
if !memoryBelongsToGoal(m, goal.ID) || m.Provenance.SourceID == "" {
|
|
continue
|
|
}
|
|
var src *core.KnowledgeSource
|
|
if x, ok := e.store.GetSource(m.Provenance.SourceID); ok {
|
|
src = x
|
|
}
|
|
if !goalEvidenceRelevant(goal, m, src) {
|
|
continue
|
|
}
|
|
memorySet[m.ID] = struct{}{}
|
|
sourceSet[m.Provenance.SourceID] = struct{}{}
|
|
primaryOrigin := ""
|
|
if src != nil {
|
|
primaryOrigin = sourceOriginKey(src.URI)
|
|
}
|
|
for _, sid := range m.EvidenceSourceIDs {
|
|
if sid == "" || sid == m.Provenance.SourceID {
|
|
continue
|
|
}
|
|
corroborating, ok := e.store.GetSource(sid)
|
|
if !ok || corroborating == nil {
|
|
continue
|
|
}
|
|
origin := sourceOriginKey(corroborating.URI)
|
|
if origin != "" && origin != primaryOrigin {
|
|
corroborationSet[m.ID+"\x00"+origin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
goal.ResearchEvidence = len(memorySet)
|
|
goal.ResearchSources = len(sourceSet)
|
|
goal.ResearchCorroborations = len(corroborationSet)
|
|
goal.ResearchSourceIDs = goal.ResearchSourceIDs[:0]
|
|
for id := range sourceSet {
|
|
goal.ResearchSourceIDs = append(goal.ResearchSourceIDs, id)
|
|
if len(goal.ResearchSourceIDs) >= 512 {
|
|
break
|
|
}
|
|
}
|
|
|
|
target := strings.ToLower(strings.TrimSpace(goal.Target))
|
|
if m := targetNumberRE.FindStringSubmatch(target); len(m) == 2 {
|
|
if n, err := strconv.Atoi(m[1]); err == nil && n > 0 {
|
|
current, label := goal.ResearchEvidence, "quellengebundene Evidenzen"
|
|
if strings.Contains(target, "artikel") || strings.Contains(target, "article") || strings.Contains(target, "draft") || strings.Contains(target, "entwurf") {
|
|
current, label = 0, "validierte Staging-Artikel"
|
|
if goal.LastStagingDraftID != "" && goal.StagingDraftValidated && goal.StagingQualityGateVersion == stagingQualityGateVersion {
|
|
current = 1
|
|
}
|
|
} else {
|
|
evidenceTarget := strings.Contains(target, "wissensein") || strings.Contains(target, "evidenz") || strings.Contains(target, "claim") || strings.Contains(target, "eintr")
|
|
if !evidenceTarget && (strings.Contains(target, "quelle") || strings.Contains(target, "source")) {
|
|
current, label = goal.ResearchSources, "unabhängige Quellen"
|
|
}
|
|
if strings.Contains(target, "bestät") || strings.Contains(target, "corrobor") {
|
|
current, label = goal.ResearchCorroborations, "Bestätigungen"
|
|
}
|
|
}
|
|
goal.Progress = vector.Clamp(float64(current)/float64(n), 0, 1)
|
|
goal.ProgressReason = fmt.Sprintf("%d/%d %s", current, n, label)
|
|
return
|
|
}
|
|
}
|
|
|
|
sat := func(v, target int) float64 {
|
|
if target <= 0 {
|
|
return 0
|
|
}
|
|
return vector.Clamp(float64(v)/float64(target), 0, 1)
|
|
}
|
|
quality := vector.Clamp((evaluation+1)/2, 0, 1)
|
|
goal.Progress = vector.Clamp(.55*sat(goal.ResearchEvidence, 20)+.25*sat(goal.ResearchSources, 8)+.15*sat(goal.ResearchCorroborations, 5)+.05*quality, 0, 1)
|
|
goal.ProgressReason = fmt.Sprintf("%d Evidenzen · %d Quellen · %d Bestätigungen", goal.ResearchEvidence, goal.ResearchSources, goal.ResearchCorroborations)
|
|
}
|
|
|
|
func filterGoalEvidenceHits(hits []store.SearchHit) []store.SearchHit {
|
|
out := make([]store.SearchHit, 0, len(hits))
|
|
for _, h := range hits {
|
|
if h.Memory.Kind == "goal-learning" || h.Memory.Provenance.Source == "goal-cycle" {
|
|
continue
|
|
}
|
|
out = append(out, h)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ReconcileGoalProgress backfills the measurable progress fields from persisted
|
|
// research-run telemetry. It makes upgrades immediately reflect historical work
|
|
// without requiring a fresh web-research cycle first.
|
|
func (e *Engine) ReconcileGoalProgress() error {
|
|
for _, g := range e.store.GoalsSnapshot() {
|
|
if !g.ResearchEnabled {
|
|
continue
|
|
}
|
|
before, beforeReason := g.Progress, g.ProgressReason
|
|
e.refreshGoalResearchProgress(&g, g.LastEvaluation)
|
|
if g.Progress != before || g.ProgressReason != beforeReason {
|
|
if err := e.store.UpsertGoal(&g); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|