Files
knowledge-governance/internal/qaopsquality/projection.go
T
姚章浩 09b2c1dce5
build / build (push) Successful in 1m17s
fix(qa): 表头筛选并统一未覆盖正确性待确认
修正筛选入口位置:五项条件在表头就地选择,保持完整集筛选与无感续载。
统一固定规则:未覆盖一律待确认,原问答/人工模型证据不改写,已覆盖仍保留点踩优先。
验证:行为红绿、前端/Go全套及隔离PostgreSQL全mirror竞态通过;完整本地门禁与原生备份真实恢复通过。
2026-10-25 13:58:08 +08:00

129 lines
4.1 KiB
Go

package qaopsquality
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"knowledge-governance/internal/dobotaiops"
"regexp"
"strings"
"unicode/utf8"
)
var hashPattern = regexp.MustCompile(`^[a-f0-9]{64}$`)
var noncePattern = regexp.MustCompile(`^[A-Za-z0-9_-]{16,128}$`)
func validText(s string, max int) bool {
return utf8.ValidString(s) && utf8.RuneCountInString(s) <= max && !strings.ContainsRune(s, 0)
}
func validID(s string) bool { return strings.TrimSpace(s) != "" && validText(s, 256) }
func canonical(raw json.RawMessage) (json.RawMessage, error) {
if len(raw) == 0 {
return json.RawMessage("null"), nil
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
var v any
if decoder.Decode(&v) != nil {
return nil, ErrInvalid
}
var extra any
if decoder.Decode(&extra) != io.EOF {
return nil, ErrInvalid
}
out, err := json.Marshal(v)
return out, err
}
func EvidenceFor(record dobotaiops.Record) (Evidence, error) {
if !validID(record.ID) {
return Evidence{}, ErrInvalid
}
refs, err := canonical(record.References)
if err != nil {
return Evidence{}, ErrInvalid
}
media, err := canonical(record.Media)
if err != nil {
return Evidence{}, ErrInvalid
}
// Keep only assessment inputs, never personnel or mutable feedback/organisation.
payload, err := json.Marshal(struct {
Version string `json:"version"`
ID string `json:"id"`
Status string `json:"status"`
Question string `json:"question"`
Answer string `json:"answer"`
References json.RawMessage `json:"references"`
Media json.RawMessage `json:"media"`
}{"dobotai-2.0-dingtalk-quality-v1", record.ID, record.Status, record.Question, record.Answer, refs, media})
if err != nil || len(payload) > 8<<20 {
return Evidence{}, ErrInvalid
}
sum := sha256.Sum256(payload)
hash := hex.EncodeToString(sum[:])
if record.QualityInputHash != "" {
if !hashPattern.MatchString(record.QualityInputHash) {
return Evidence{}, ErrInvalid
}
hash = record.QualityInputHash
}
return Evidence{Hash: hash, Payload: payload, Record: record, Result: dobotaiops.CheckAnswerCoverage(record.Answer)}, nil
}
func Validate(r SaveRequest) error {
if !hashPattern.MatchString(r.InputHash) || r.InputEpoch < 1 || r.BaseRevision < 0 || !noncePattern.MatchString(r.Nonce) || strings.TrimSpace(r.Reason) == "" || !validText(r.Reason, 4000) {
return ErrInvalid
}
f := r.Fields
validCoverage := false
for _, s := range []string{"covered", "uncovered", "partial", "pending", "excluded"} {
validCoverage = validCoverage || f.Coverage == s
}
if !validCoverage || (f.Correctness != "correct" && f.Correctness != "incorrect" && f.Correctness != "pending") {
return ErrInvalid
}
if (f.Correctness == "pending") != (f.Coverage == "uncovered") {
return ErrInvalid
}
if !validText(f.ProductCategory, 128) || !validText(f.ProblemType, 128) || !validText(f.RootCause, 2000) || !validText(f.ExclusionReason, 2000) {
return ErrInvalid
}
if f.Correctness == "correct" && f.RootCause != "" || f.Coverage == "excluded" && strings.TrimSpace(f.ExclusionReason) == "" || f.Coverage != "excluded" && f.ExclusionReason != "" {
return ErrInvalid
}
return nil
}
func Project(hash string, epoch, revision int64, automatic Check, review *Review) View {
view := View{InputHash: hash, InputEpoch: epoch, Revision: revision, State: "unreviewed", Automatic: automatic, LatestReview: review, Effective: Fields{Coverage: automatic.Result.Verdict}}
if review == nil {
return view
}
view.State = "stale"
if review.InputEpoch == epoch && review.InputHash == hash {
view.State = "reviewed"
view.Effective = review.Fields
}
return view
}
func Summarize(views map[string]View) Summary {
result := Summary{Total: len(views), Rates: ComputeRates(views)}
for _, v := range views {
switch v.State {
case "reviewed":
result.Reviewed++
if v.Effective.Coverage == "pending" || v.Effective.Correctness == "pending" {
result.PendingConfirmation++
}
case "stale":
result.Stale++
default:
result.Unreviewed++
}
if v.Automatic.Result.Verdict == "uncovered" {
result.RuleUncovered++
}
}
return result
}