init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
// Package metricparse extracts a numeric value from the free-text engine/metric strings
// used across FM reports (e.g. "GPC/N1 63.2"). The same parser MUST be used both when
// aggregating totals and when deriving a single report's "today" contribution, otherwise
// prev = total - today would not reconcile.
package metricparse
import (
"regexp"
"strconv"
"strings"
"unicode"
)
// numberToken matches a signed decimal number (integer or fractional).
var numberToken = regexp.MustCompile(`[-+]?\d*\.?\d+`)
// Parse returns ParseString(*v), or 0 when v is nil.
func Parse(v *string) float64 {
if v == nil {
return 0
}
return ParseString(*v)
}
// ParseString pulls the numeric VALUE out of a metric label string:
//
// "GPC/N1 63.2" -> 63.2 "PTC/N2 61.8" -> 61.8 "63.2" -> 63.2
// "63,2" -> 63.2 " -5 " -> -5 "N1" -> 0 (label only)
//
// It ignores digits fused to a label letter (the "1" in "N1", the "2" in "N2") by skipping
// any number immediately preceded by a letter, and prefers a token that carries a decimal
// point when several stand-alone numbers are present.
func ParseString(s string) float64 {
s = strings.TrimSpace(s)
if s == "" {
return 0
}
// Normalize a decimal comma ("63,2") to a dot when it is the sole separator.
if !strings.Contains(s, ".") {
if i := strings.IndexByte(s, ','); i > 0 && i+1 < len(s) &&
isASCIIDigit(s[i-1]) && isASCIIDigit(s[i+1]) {
s = s[:i] + "." + s[i+1:]
}
}
locs := numberToken.FindAllStringIndex(s, -1)
candidates := make([]string, 0, len(locs))
for _, loc := range locs {
// A value is never glued to a preceding letter; that only happens for labels
// like N1/N2. Real values start the string or follow a space/slash/colon/etc.
if loc[0] > 0 && unicode.IsLetter(rune(s[loc[0]-1])) {
continue
}
candidates = append(candidates, s[loc[0]:loc[1]])
}
if len(candidates) == 0 {
return 0
}
chosen := candidates[0]
for _, c := range candidates {
if strings.Contains(c, ".") {
chosen = c
break
}
}
n, err := strconv.ParseFloat(chosen, 64)
if err != nil {
return 0
}
return n
}
func isASCIIDigit(b byte) bool { return b >= '0' && b <= '9' }

View File

@@ -0,0 +1,43 @@
package metricparse
import "testing"
func TestParseString(t *testing.T) {
cases := []struct {
in string
want float64
}{
{"GPC/N1 63.2", 63.2}, // the "1" in N1 must NOT win
{"PTC/N2 61.8", 61.8}, // the "2" in N2 must NOT win
{"GPC/NG/N1 105.3", 105.3},
{"63.2", 63.2},
{" 63.2 ", 63.2},
{"63", 63},
{"63,2", 63.2}, // decimal comma
{"GPC/N1: 63.2 %", 63.2}, // colon + trailing unit
{"GPC/N1 63.2 / N2 61.5", 63.2}, // first standalone decimal
{"-5", -5},
{"+7.5", 7.5},
{".5", 0.5},
{"N1", 0}, // label only, no value
{"N2", 0}, // label only
{"", 0},
{" ", 0},
{"no numbers here", 0},
}
for _, c := range cases {
if got := ParseString(c.in); got != c.want {
t.Errorf("ParseString(%q) = %v, want %v", c.in, got, c.want)
}
}
}
func TestParseNilAndPtr(t *testing.T) {
if got := Parse(nil); got != 0 {
t.Fatalf("Parse(nil) = %v, want 0", got)
}
s := "GPC/N1 63.2"
if got := Parse(&s); got != 63.2 {
t.Fatalf("Parse(&%q) = %v, want 63.2", s, got)
}
}