diff --git a/adapters/go/README.md b/adapters/go/README.md new file mode 100644 index 0000000..a26001f --- /dev/null +++ b/adapters/go/README.md @@ -0,0 +1,75 @@ +# lh_standard_adapter (Go) + +Go implementation of the LongHun Standard Adapter v1.0.0. + +## Install + +```bash +go get github.com/UID9622/lh-standard-adapter +``` + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/UID9622/lh-standard-adapter/lhstandardadapter" +) + +func main() { + adapter := lhstandardadapter.NewAdapter("9622", "HM-9622-001") + + data := map[string]interface{}{ + "code": "print('hello')", + "module": "demo", + } + + wrapped, err := adapter.Wrap(data, "code", "P04") + if err != nil { + panic(err) + } + + fmt.Println("DNA:", wrapped.DNA) + fmt.Println("Pattern:", wrapped.Audit.BehaviorPattern) + fmt.Println("Color:", wrapped.Audit.Color) + + result := adapter.Validate(wrapped) + fmt.Println("Valid:", result.Valid) + fmt.Println("Summary:", result.Summary) +} +``` + +## API + +### `NewAdapter(uid, device string) *Adapter` + +Create a new adapter instance. + +### `(*Adapter).Wrap(data interface{}, taskType, persona string) (*WrappedPayload, error)` + +Wrap a payload with DNA traceability and seven-factor audit metadata. + +### `(*Adapter).Validate(wrapped *WrappedPayload) ValidationResult` + +Validate a wrapped payload for standard compliance. + +### `(*Adapter).GetSchemas() (dnaSchema, auditSchema map[string]interface{})` + +Get JSON schemas for DNA and Audit formats. + +### `Wrap(data, taskType, persona, uid, device string) (*WrappedPayload, error)` + +Convenience one-shot wrapper function. + +## Features + +- Zero external dependencies (Go stdlib only) +- Full cross-validation with Python reference implementation +- 44 test cases covering all modules +- `go vet` clean + +## License + +CC BY-NC-SA 4.0 diff --git a/adapters/go/go.mod b/adapters/go/go.mod new file mode 100644 index 0000000..8cdd43e --- /dev/null +++ b/adapters/go/go.mod @@ -0,0 +1,3 @@ +module github.com/UID9622/lh-standard-adapter + +go 1.21 diff --git a/adapters/go/lhstandardadapter/adapter.go b/adapters/go/lhstandardadapter/adapter.go new file mode 100644 index 0000000..a8cd8d6 --- /dev/null +++ b/adapters/go/lhstandardadapter/adapter.go @@ -0,0 +1,123 @@ +package lhstandardadapter + +import ( + "encoding/json" + "time" +) + +// WrappedPayload is the top-level output of Adapter.Wrap. +type WrappedPayload struct { + DNA string `json:"dna"` + Audit AuditRecord `json:"audit"` + Payload interface{} `json:"payload"` + Meta Meta `json:"meta"` +} + +// Meta holds metadata about the wrapping operation. +type Meta struct { + AdapterVersion string `json:"adapter_version"` + UID string `json:"uid"` + Device string `json:"device"` + TaskType string `json:"task_type"` + Persona string `json:"persona"` + GeneratedAt string `json:"generated_at"` + Format string `json:"format"` +} + +// Adapter is the main LongHun Standard Adapter. +// It wraps JSON payloads with DNA traceability and seven-factor +// behavioral audit metadata. +type Adapter struct { + UID string + Device string + dnaGen *DNAGenerator + audit *AuditWrapper + valid *Validator +} + +// NewAdapter creates an Adapter with the given UID and device. +func NewAdapter(uid, device string) *Adapter { + return &Adapter{ + UID: uid, + Device: device, + dnaGen: NewDNAGenerator(uid, device), + audit: NewAuditWrapper(uid), + valid: NewValidator(), + } +} + +// Wrap wraps a payload with DNA traceability and audit metadata. +// +// Parameters: +// - data: Raw payload (any JSON-serializable value) +// - taskType: Task category ("code", "deploy", "audit", "default", etc.) +// - persona: Persona identifier ("P04-Luban", "P00-Wenxin", etc.) +// +// Returns a WrappedPayload and nil on success. +func (a *Adapter) Wrap(data interface{}, taskType, persona string) (*WrappedPayload, error) { + if taskType == "" { + taskType = "default" + } + if persona == "" { + persona = "P04" + } + + dna := a.dnaGen.Generate(taskType, "WRAP", "") + audit := a.audit.Wrap(data, taskType, persona) + + now := time.Now().UTC().Add(cstOffset * time.Hour) + + return &WrappedPayload{ + DNA: dna, + Audit: audit, + Payload: data, + Meta: Meta{ + AdapterVersion: Version, + UID: a.UID, + Device: a.Device, + TaskType: taskType, + Persona: persona, + GeneratedAt: now.Format(time.RFC3339Nano), + Format: "longhun-v∞", + }, + }, nil +} + +// Validate checks a wrapped payload for standard compliance. +func (a *Adapter) Validate(wrapped *WrappedPayload) ValidationResult { + // Convert to map for validation + b, err := json.Marshal(wrapped) + if err != nil { + return ValidationResult{ + Valid: false, + Errors: []string{"Failed to marshal wrapped payload: " + err.Error()}, + Summary: "❌ INVALID — marshal error", + } + } + var m map[string]interface{} + if err := json.Unmarshal(b, &m); err != nil { + return ValidationResult{ + Valid: false, + Errors: []string{"Failed to unmarshal: " + err.Error()}, + Summary: "❌ INVALID — unmarshal error", + } + } + return a.valid.Validate(m) +} + +// GetSchemas returns the JSON schemas for DNA and Audit formats. +func (a *Adapter) GetSchemas() (dnaSchema, auditSchema map[string]interface{}) { + if err := json.Unmarshal([]byte(dnaSchemaJSON), &dnaSchema); err != nil { + dnaSchema = map[string]interface{}{} + } + if err := json.Unmarshal([]byte(auditSchemaJSON), &auditSchema); err != nil { + auditSchema = map[string]interface{}{} + } + return +} + +// Wrap is a convenience one-shot wrapper function. +func Wrap(data interface{}, taskType, persona, uid, device string) (*WrappedPayload, error) { + adapter := NewAdapter(uid, device) + return adapter.Wrap(data, taskType, persona) +} diff --git a/adapters/go/lhstandardadapter/adapter_test.go b/adapters/go/lhstandardadapter/adapter_test.go new file mode 100644 index 0000000..9943960 --- /dev/null +++ b/adapters/go/lhstandardadapter/adapter_test.go @@ -0,0 +1,490 @@ +package lhstandardadapter + +import ( + "testing" +) + +func TestDNAGenerator_Default(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("default", "WRAP", "") + if !startsWith(dna, "#LongHun⚡️") { + t.Errorf("DNA should start with prefix, got: %s", dna[:30]) + } + if !contains(dna, "ADAPTER-DEFAULT-WRAP-V1.0") { + t.Errorf("DNA should contain module path, got: %s", dna) + } + if !DNARegex.MatchString(dna) { + t.Errorf("DNA should match regex: %s", dna[:60]) + } +} + +func TestDNAGenerator_CodeTask(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("code", "GENERATE", "v2.0") + if !startsWith(dna, "#LongHun⚡️") { + t.Error("should start with prefix") + } + if !contains(dna, "ADAPTER-CODE-GENERATE-v2.0") { + t.Errorf("should contain module path, got: %s", dna) + } +} + +func TestDNAGenerator_DeployTask(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("deploy", "DEPLOY", "") + if !contains(dna, "ADAPTER-DEPLOY-DEPLOY") { + t.Errorf("should contain deploy path, got: %s", dna) + } +} + +func TestDNAGenerator_AuditTask(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("audit", "AUDIT", "") + if len(dna) < 30 { + t.Errorf("DNA too short: %s", dna) + } +} + +func TestDNAGenerator_RegexMatch(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + tasks := []string{"default", "code", "deploy"} + for _, task := range tasks { + dna := gen.Generate(task, "WRAP", "") + if !DNARegex.MatchString(dna) { + t.Errorf("DNA[%s] should match regex: %s", task, dna[:60]) + } + } +} + +func TestDNAGenerator_Hash8Length(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("default", "WRAP", "v1.0") + if len(dna) < 8 { + t.Fatal("DNA too short") + } + hash8 := dna[len(dna)-8:] + if !isHexLower(hash8) { + t.Errorf("hash8 should be 8 hex chars: %s", hash8) + } +} + +func TestDNAGenerator_HexagramSelection(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + auditHex := gen.selectHexagram("audit") + if auditHex.EnName == "" { + t.Error("audit hexagram should not be empty") + } + // Audit should map to "Li" hexagram + validAuditHex := map[string]bool{"Li": true, "JiJi": true, "Kan": true, "Zhen": true} + if !validAuditHex[auditHex.EnName] { + t.Errorf("audit hexagram unexpected: %s", auditHex.EnName) + } + // Unknown task should get default (Qian) + defaultHex := gen.selectHexagram("unknown-task") + if defaultHex.EnName == "" { + t.Error("default hexagram should not be empty") + } +} + +func TestDNAGenerator_DifferentTasksDifferentHexagrams(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + auditHex := gen.selectHexagram("audit") + deployHex := gen.selectHexagram("deploy") + if auditHex.EnName == deployHex.EnName { + t.Error("audit and deploy should have different hexagrams") + } +} + +func TestDNAGenerator_ConvenienceFunction(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + dna := gen.Generate("test", "TEST", "") + if !startsWith(dna, "#LongHun⚡️") { + t.Error("convenience func should work") + } +} + +// --- AuditWrapper tests --- + +func TestAuditWrapper_Default(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap(map[string]string{"key": "value"}, "default", "P04") + if audit.AuditVersion != "v1.0" { + t.Errorf("audit version should be v1.0, got: %s", audit.AuditVersion) + } + if audit.UID != "UID9622" { + t.Errorf("UID should be UID9622, got: %s", audit.UID) + } +} + +func TestAuditWrapper_Signature(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap("test", "default", "P04") + sig := audit.BehaviorSignature + if sig.P != "HasPromise" { + t.Errorf("P should be HasPromise, got: %s", sig.P) + } + if sig.F != "Fulfilled" { + t.Errorf("F should be Fulfilled, got: %s", sig.F) + } +} + +func TestAuditWrapper_Pattern(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap("test", "default", "P04") + // Default signature should be StableDisciplined + if audit.BehaviorPattern != "MODE-StableDisciplined" { + t.Errorf("pattern should be StableDisciplined, got: %s", audit.BehaviorPattern) + } +} + +func TestAuditWrapper_Color(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap("test", "default", "P04") + if audit.Color != "🟢" { + t.Errorf("color should be 🟢 for StableDisciplined, got: %s", audit.Color) + } +} + +func TestAuditWrapper_PayloadHash(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap(map[string]string{"code": "print('hello')"}, "code", "P04") + if len(audit.PayloadHash) != 16 { + t.Errorf("payload_hash should be 16 chars, got: %s", audit.PayloadHash) + } + if !isHexLower(audit.PayloadHash) { + t.Errorf("payload_hash should be hex, got: %s", audit.PayloadHash) + } +} + +func TestAuditWrapper_Labels(t *testing.T) { + w := NewAuditWrapper("9622") + audit := w.Wrap("test", "default", "P04") + if len(audit.BehaviorLabels) == 0 { + t.Error("labels should not be empty") + } + // Last label should be the pattern + last := audit.BehaviorLabels[len(audit.BehaviorLabels)-1] + if last != audit.BehaviorPattern { + t.Errorf("last label should be pattern, got: %s", last) + } +} + +func TestClassify_DefensiveDefaulter(t *testing.T) { + sig := BehaviorSignature{F: "Unfulfilled", X: "OverExplain", Z: 1.0} + if classify(sig) != "MODE-DefensiveDefaulter" { + t.Error("should be DefensiveDefaulter") + } +} + +func TestClassify_ExternalTrustSpender(t *testing.T) { + sig := BehaviorSignature{F: "Fulfilled", A: "Outsider", Z: 1.0} + if classify(sig) != "MODE-ExternalTrustSpender" { + t.Error("should be ExternalTrustSpender") + } +} + +func TestClassify_InternalDestroyer(t *testing.T) { + sig := BehaviorSignature{F: "Unfulfilled", Y: "Indifferent", Z: 1.0} + if classify(sig) != "MODE-InternalDestroyer" { + t.Error("should be InternalDestroyer") + } +} + +func TestClassify_Fluctuating(t *testing.T) { + sig := BehaviorSignature{F: "Fulfilled", Z: 3.0} + if classify(sig) != "MODE-Fluctuating" { + t.Error("should be Fluctuating") + } +} + +func TestClassify_StableDisciplined(t *testing.T) { + sig := BehaviorSignature{F: "Fulfilled", X: "Genuine", A: "Self", Y: "NoResponse", Z: 1.0} + if classify(sig) != "MODE-StableDisciplined" { + t.Error("should be StableDisciplined") + } +} + +func TestDetermineColor_InternalDestroyer(t *testing.T) { + if determineColor("MODE-InternalDestroyer", 0) != "🔴" { + t.Error("InternalDestroyer should be 🔴") + } +} + +func TestDetermineColor_FluctuatingHighRepeat(t *testing.T) { + if determineColor("MODE-Fluctuating", 4) != "🟡" { + t.Error("Fluctuating with repeat>3 should be 🟡") + } +} + +func TestDetermineColor_StableDisciplined(t *testing.T) { + if determineColor("MODE-StableDisciplined", 0) != "🟢" { + t.Error("StableDisciplined should be 🟢") + } +} + +// --- Validator tests --- + +func TestValidator_ValidWrapped(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + wrapped, err := adapter.Wrap(map[string]string{"key": "value"}, "default", "P04") + if err != nil { + t.Fatal(err) + } + result := adapter.Validate(wrapped) + if !result.Valid { + t.Errorf("expected valid, got errors: %v", result.Errors) + } +} + +func TestValidator_MissingTopLevelKeys(t *testing.T) { + v := NewValidator() + result := v.Validate(map[string]interface{}{"dna": "something"}) + if result.Valid { + t.Error("should be invalid with missing keys") + } +} + +func TestValidator_EmptyDNA(t *testing.T) { + v := NewValidator() + result := v.Validate(map[string]interface{}{ + "dna": "", + "audit": map[string]interface{}{}, + "payload": "test", + "meta": map[string]interface{}{}, + }) + if result.Valid { + t.Error("should be invalid with empty DNA") + } +} + +func TestValidator_InvalidDNAFormat(t *testing.T) { + v := NewValidator() + result := v.Validate(map[string]interface{}{ + "dna": "invalid-dna-string", + "audit": map[string]interface{}{}, + "payload": "test", + "meta": map[string]interface{}{}, + }) + if result.Valid { + t.Error("should be invalid with bad DNA format") + } +} + +func TestValidator_UIDMismatch(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + wrapped, _ := adapter.Wrap("test", "default", "P04") + wrapped.Meta.UID = "9999" + result := adapter.Validate(wrapped) + if result.Valid { + t.Error("should be invalid with UID mismatch") + } +} + +func TestQuickValidate_Valid(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + wrapped, _ := adapter.Wrap("test", "default", "P04") + + // Convert to map + b, _ := jsonMarshal(wrapped) + var m map[string]interface{} + jsonUnmarshal(b, &m) + + if !QuickValidate(m) { + t.Error("QuickValidate should return true for valid wrapped") + } +} + +func TestQuickValidate_Invalid(t *testing.T) { + if QuickValidate(map[string]interface{}{"foo": "bar"}) { + t.Error("QuickValidate should return false for invalid") + } +} + +func TestQuickValidate_Empty(t *testing.T) { + if QuickValidate(map[string]interface{}{}) { + t.Error("QuickValidate should return false for empty") + } +} + +// --- Adapter integration tests --- + +func TestAdapter_WrapAndValidate(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + data := map[string]interface{}{ + "code": "print('hello')", + "module": "test", + } + wrapped, err := adapter.Wrap(data, "code", "P04") + if err != nil { + t.Fatal(err) + } + if wrapped.DNA == "" { + t.Error("DNA should not be empty") + } + if wrapped.Audit.UID == "" { + t.Error("audit UID should not be empty") + } + result := adapter.Validate(wrapped) + if !result.Valid { + t.Errorf("validation failed: %v", result.Errors) + } +} + +func TestAdapter_GetSchemas(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + dnaSchema, auditSchema := adapter.GetSchemas() + if dnaSchema == nil { + t.Error("DNA schema should not be nil") + } + if auditSchema == nil { + t.Error("Audit schema should not be nil") + } +} + +func TestAdapter_DefaultValues(t *testing.T) { + adapter := NewAdapter("9622", "HM-9622-001") + wrapped, err := adapter.Wrap("test", "", "") + if err != nil { + t.Fatal(err) + } + if wrapped.Meta.TaskType != "default" { + t.Errorf("expected default task type, got: %s", wrapped.Meta.TaskType) + } + if wrapped.Meta.Persona != "P04" { + t.Errorf("expected P04 persona, got: %s", wrapped.Meta.Persona) + } +} + +func TestWrap_ConvenienceFunction(t *testing.T) { + wrapped, err := Wrap("test data", "default", "P04", "9622", "HM-9622-001") + if err != nil { + t.Fatal(err) + } + if !startsWith(wrapped.DNA, "#LongHun⚡️") { + t.Error("convenience Wrap should produce valid DNA") + } +} + +func TestMod_NegativeInput(t *testing.T) { + if mod(-1, 10) != 9 { + t.Error("mod(-1, 10) should be 9") + } + if mod(-3, 12) != 9 { + t.Error("mod(-3, 12) should be 9") + } +} + +func TestToUpper(t *testing.T) { + if toUpper("hello") != "HELLO" { + t.Error("toUpper failed") + } + if toUpper("Wrap") != "WRAP" { + t.Error("toUpper failed for mixed case") + } +} + +// --- Stems/Branches tests --- + +func TestStemBranch_AllFieldsPresent(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + // Use a fixed time for testing + sb := gen.computeStemBranch(timeFromYMDH(2026, 7, 24, 13)) + if sb.Year == "" { + t.Error("year should not be empty") + } + if sb.Month == "" { + t.Error("month should not be empty") + } + if sb.Day == "" { + t.Error("day should not be empty") + } + if sb.ShiChen == "" { + t.Error("shichen should not be empty") + } +} + +func TestStemBranch_ShiChen_13Hour(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + sb := gen.computeStemBranch(timeFromYMDH(2026, 7, 24, 13)) + // hour // 2 = 6 → WuShi, matches Python reference implementation + if sb.ShiChen != "WuShi" { + t.Errorf("13:00 → WuShi (hour//2=6), got: %s", sb.ShiChen) + } +} + +func TestStemBranch_ShiChen_0Hour(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + sb := gen.computeStemBranch(timeFromYMDH(2026, 7, 24, 0)) + if sb.ShiChen != "ZiShi" { + t.Errorf("00:00 → ZiShi, got: %s", sb.ShiChen) + } +} + +func TestStemBranch_ShiChen_23Hour(t *testing.T) { + gen := NewDNAGenerator("9622", "HM-9622-001") + sb := gen.computeStemBranch(timeFromYMDH(2026, 7, 24, 23)) + // hour // 2 = 11 → HaiShi, matches Python reference implementation + if sb.ShiChen != "HaiShi" { + t.Errorf("23:00 → HaiShi (hour//2=11), got: %s", sb.ShiChen) + } +} + +func TestIsHexLower(t *testing.T) { + if !isHexLower("abc123") { + t.Error("abc123 should be hex lower") + } + if isHexLower("ABC123") { + t.Error("ABC123 should not be hex lower") + } + if isHexLower("xyz") { + t.Error("xyz should not be hex lower") + } +} + +func TestTruncate(t *testing.T) { + if truncate("hello world", 5) != "hello" { + t.Error("truncate failed") + } + if truncate("hi", 10) != "hi" { + t.Error("truncate of short string should return original") + } +} + +func TestItoa(t *testing.T) { + if itoa(0) != "0" { + t.Error("itoa(0) failed") + } + if itoa(42) != "42" { + t.Error("itoa(42) failed") + } + if itoa(-5) != "-5" { + t.Error("itoa(-5) failed") + } +} + +// --- Test helpers --- + +func startsWith(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func contains(s, sub string) bool { + return len(s) >= len(sub) && (indexOf(s, sub) >= 0) +} + +func indexOf(s, sub string) int { + for i := 0; i <= len(s)-len(sub); i++ { + if s[i:i+len(sub)] == sub { + return i + } + } + return -1 +} + +func jsonMarshal(v interface{}) ([]byte, error) { + return jsonMarshalImpl(v) +} + +func jsonUnmarshal(b []byte, v interface{}) error { + return jsonUnmarshalImpl(b, v) +} diff --git a/adapters/go/lhstandardadapter/audit_wrapper.go b/adapters/go/lhstandardadapter/audit_wrapper.go new file mode 100644 index 0000000..0fcee5a --- /dev/null +++ b/adapters/go/lhstandardadapter/audit_wrapper.go @@ -0,0 +1,188 @@ +package lhstandardadapter + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "time" +) + +// Seven-factor value sets (public standard) + +var PValues = []string{"HasPromise", "NoPromise"} +var FValues = []string{"Fulfilled", "Unfulfilled", "Partial"} +var EValues = []string{"Willing", "Perfunctory", "Resentful", "Numb"} +var AValues = []string{"Self", "Partner", "Family", "Outsider", "Public"} +var XValues = []string{"OverExplain", "Silent", "Genuine", "Indifferent"} +var YValues = []string{"Changed", "Resisted", "Indifferent", "NoResponse"} + +// BehaviorPattern classifications +var Patterns = map[string]string{ + "MODE-DefensiveDefaulter": "Promises fail + over-explains to deflect", + "MODE-ExternalTrustSpender": "Keeps promises to outsiders at inner-circle expense", + "MODE-InternalDestroyer": "Breaks promises with indifference, no correction", + "MODE-Fluctuating": "High volatility in commitment-to-fulfillment ratio", + "MODE-StableDisciplined": "Consistent, reliable execution", +} + +// factorLabelMap maps factor → value → bilingual label. +var factorLabelMap = map[string]map[string]string{ + "P": {"HasPromise": "7F-P-有承诺", "NoPromise": "7F-P-无承诺"}, + "F": {"Fulfilled": "7F-F-已兑现", "Unfulfilled": "7F-F-未兑现", "Partial": "7F-F-部分兑现"}, + "E": {"Willing": "7F-E-心甘情愿", "Perfunctory": "7F-E-敷衍", + "Resentful": "7F-E-怨恨", "Numb": "7F-E-麻木"}, + "A": {"Self": "7F-A-自己", "Partner": "7F-A-伴侣", + "Family": "7F-A-家庭", "Outsider": "7F-A-外人", "Public": "7F-A-公众"}, + "X": {"OverExplain": "7F-X-过度解释", "Silent": "7F-X-沉默", + "Genuine": "7F-X-真诚", "Indifferent": "7F-X-冷漠"}, + "Y": {"Changed": "7F-Y-改正", "Resisted": "7F-Y-抗拒", + "Indifferent": "7F-Y-无视", "NoResponse": "7F-Y-无响应"}, +} + +// BehaviorSignature holds the seven-factor audit values. +type BehaviorSignature struct { + P string `json:"P"` + F string `json:"F"` + T float64 `json:"T"` + E string `json:"E"` + C int `json:"C"` + R int `json:"R"` + A string `json:"A"` + X string `json:"X"` + Y string `json:"Y"` + Z float64 `json:"Z"` +} + +// AuditRecord holds the full audit wrapper output. +type AuditRecord struct { + AuditVersion string `json:"audit_version"` + UID string `json:"uid"` + Persona string `json:"persona"` + TaskType string `json:"task_type"` + BehaviorSignature BehaviorSignature `json:"behavior_signature"` + BehaviorPattern string `json:"behavior_pattern"` + BehaviorLabels []string `json:"behavior_labels"` + Color string `json:"color"` + Timestamp string `json:"timestamp"` + PayloadHash string `json:"payload_hash"` +} + +// AuditWrapper wraps payloads with seven-factor behavioral audit metadata. +type AuditWrapper struct { + UID string +} + +// NewAuditWrapper creates an AuditWrapper with the given UID. +func NewAuditWrapper(uid string) *AuditWrapper { + return &AuditWrapper{UID: uid} +} + +// Wrap generates an audit wrapper with seven-factor signature. +func (w *AuditWrapper) Wrap(payload interface{}, taskType, persona string) AuditRecord { + if taskType == "" { + taskType = "default" + } + if persona == "" { + persona = "P04" + } + + now := time.Now().UTC().Add(cstOffset * time.Hour) + + // Default signature (StableDisciplined baseline) + sig := BehaviorSignature{ + P: "HasPromise", + F: "Fulfilled", + T: 0.0, + E: "Willing", + C: 0, + R: 0, + A: "Self", + X: "Genuine", + Y: "NoResponse", + Z: 1.0, + } + + pattern := classify(sig) + labels := makeLabels(sig, pattern) + color := determineColor(pattern, sig.R) + + payloadHash := computePayloadHash(payload) + + return AuditRecord{ + AuditVersion: "v1.0", + UID: "UID" + w.UID, + Persona: persona, + TaskType: taskType, + BehaviorSignature: sig, + BehaviorPattern: pattern, + BehaviorLabels: labels, + Color: color, + Timestamp: now.Format(time.RFC3339Nano), + PayloadHash: payloadHash, + } +} + +// classify classifies seven-factor signature into behavior pattern. +func classify(sig BehaviorSignature) string { + switch { + case sig.F == "Unfulfilled" && sig.X == "OverExplain": + return "MODE-DefensiveDefaulter" + case sig.F == "Fulfilled" && sig.A == "Outsider": + return "MODE-ExternalTrustSpender" + case sig.F == "Unfulfilled" && sig.Y == "Indifferent": + return "MODE-InternalDestroyer" + case sig.Z > 2.0: + return "MODE-Fluctuating" + default: + return "MODE-StableDisciplined" + } +} + +// makeLabels generates bilingual behavior labels from signature. +func makeLabels(sig BehaviorSignature, pattern string) []string { + labels := []string{} + factors := []struct { + key string + val string + }{ + {"P", sig.P}, + {"F", sig.F}, + {"E", sig.E}, + {"A", sig.A}, + {"X", sig.X}, + {"Y", sig.Y}, + } + for _, f := range factors { + if m, ok := factorLabelMap[f.key]; ok { + if label, ok := m[f.val]; ok { + labels = append(labels, label) + } + } + } + labels = append(labels, pattern) + return labels +} + +// determineColor determines three-color audit tag. +func determineColor(pattern string, repeat int) string { + switch { + case pattern == "MODE-InternalDestroyer": + return "🔴" + case pattern == "MODE-Fluctuating" && repeat > 3: + return "🟡" + case pattern == "MODE-DefensiveDefaulter" && repeat > 2: + return "🟡" + default: + return "🟢" + } +} + +// computePayloadHash computes SHA-256 hash of JSON-serialized payload (first 16 hex chars). +func computePayloadHash(payload interface{}) string { + b, err := json.Marshal(payload) + if err != nil { + return "" + } + h := sha256.Sum256(b) + return hex.EncodeToString(h[:])[:16] +} diff --git a/adapters/go/lhstandardadapter/dna_generator.go b/adapters/go/lhstandardadapter/dna_generator.go new file mode 100644 index 0000000..b4c7774 --- /dev/null +++ b/adapters/go/lhstandardadapter/dna_generator.go @@ -0,0 +1,186 @@ +package lhstandardadapter + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "time" +) + +// Heavenly Stems (天干) +var tianGan = []string{"Jia", "Yi", "Bing", "Ding", "Wu", "Ji", "Geng", "Xin", "Ren", "Gui"} + +// Earthly Branches (地支) +var diZhi = []string{"Zi", "Chou", "Yin", "Mao", "Chen", "Si", + "Wu", "Wei", "Shen", "You", "Xu", "Hai"} + +// Shi Chen (时辰) — 12 two-hour periods +var shiChen = []string{"ZiShi", "ChouShi", "YinShi", "MaoShi", "ChenShi", "SiShi", + "WuShi", "WeiShi", "ShenShi", "YouShi", "XuShi", "HaiShi"} + +// Hexagram represents an I Ching hexagram with metadata. +type Hexagram struct { + Symbol string `json:"symbol"` + EnName string `json:"en_name"` + CnName string `json:"cn_name"` + Domain string `json:"domain"` +} + +var hexagrams = []Hexagram{ + {"䷀", "Qian", "乾", "governance"}, + {"䷁", "Kun", "坤", "archive"}, + {"䷂", "Zhun", "屯", "init"}, + {"䷃", "Meng", "蒙", "learn"}, + {"䷄", "Xu", "需", "async"}, + {"䷅", "Song", "讼", "legal"}, + {"䷜", "Kan", "坎", "engine"}, + {"䷝", "Li", "离", "audit"}, + {"䷲", "Zhen", "震", "security"}, + {"䷳", "Gen", "艮", "privacy"}, + {"䷸", "Xun", "巽", "deploy"}, + {"䷹", "Dui", "兑", "trust"}, + {"䷾", "JiJi", "既济", "complete"}, + {"䷿", "WeiJi", "未济", "progress"}, +} + +// Task-to-hexagram domain mapping +var taskHexagramMap = map[string]string{ + "default": "governance", + "code": "engine", + "deploy": "deploy", + "audit": "audit", + "security": "security", + "archive": "archive", + "init": "init", + "learn": "learn", + "legal": "legal", + "privacy": "privacy", + "trust": "trust", + "complete": "complete", + "progress": "progress", +} + +// StemBranch holds the four pillars computed from a timestamp. +type StemBranch struct { + Year string `json:"year"` + Month string `json:"month"` + Day string `json:"day"` + ShiChen string `json:"shichen"` +} + +const ( + cycleYear = 1984 // JiaZi year reference + cstOffset = 8 // UTC+8 for Asia/Shanghai +) + +// cycleMonth maps year-stem-index to month stem offsets. +var cycleMonth = []int{2, 4, 6, 8, 10, 0, 2, 4, 6, 8} + +// DNAGenerator generates v∞ format DNA traceability codes. +// +// Format: #LongHun⚡️{StemBranch}·{Hexagram}-{ModulePath}-{Hash8} +type DNAGenerator struct { + UID string + Device string + Locale string +} + +// NewDNAGenerator creates a DNAGenerator with defaults. +func NewDNAGenerator(uid, device string) *DNAGenerator { + return &DNAGenerator{UID: uid, Device: device, Locale: "Asia/Shanghai"} +} + +// Generate produces a full DNA traceability string. +func (g *DNAGenerator) Generate(taskType, action, version string) string { + if taskType == "" { + taskType = "default" + } + if action == "" { + action = "WRAP" + } + if version == "" { + version = "V1.0" + } + + now := time.Now().UTC().Add(cstOffset * time.Hour) + sb := g.computeStemBranch(now) + hg := g.selectHexagram(taskType) + + body := fmt.Sprintf("ADAPTER-%s-%s-%s", toUpper(taskType), toUpper(action), version) + + raw := fmt.Sprintf("%s%s%s%s%s%s%s%s%s", + sb.Year, sb.Month, sb.Day, sb.ShiChen, + hg.Symbol, hg.EnName, + body, + g.Device, + now.Format(time.RFC3339Nano), + ) + + h := sha256.Sum256([]byte(raw)) + hash8 := hex.EncodeToString(h[:])[:8] + + return fmt.Sprintf("#LongHun⚡️%s·%s·%s·%s·%s%s-%s-%s", + sb.Year, sb.Month, sb.Day, sb.ShiChen, + hg.Symbol, hg.EnName, + body, hash8) +} + +// computeStemBranch computes Heavenly Stem + Earthly Branch for a time. +func (g *DNAGenerator) computeStemBranch(t time.Time) StemBranch { + year := t.Year() + + yearStemIdx := mod(year-cycleYear, 10) + yearBranchIdx := mod(year-cycleYear, 12) + + yearOffset := mod(year-cycleYear, 10) + monthStemIdx := mod(cycleMonth[yearOffset]+int(t.Month())-1, 10) + monthBranchIdx := mod(int(t.Month())+1, 12) + + dayOfYear := t.YearDay() + dayBase := (year - 1900) + (year-1900)/4 + dayOfYear + dayStemIdx := mod(dayBase, 10) + dayBranchIdx := mod(dayBase, 12) + + shichenIdx := t.Hour() / 2 + + return StemBranch{ + Year: tianGan[yearStemIdx] + diZhi[yearBranchIdx], + Month: tianGan[monthStemIdx] + diZhi[monthBranchIdx], + Day: tianGan[dayStemIdx] + diZhi[dayBranchIdx], + ShiChen: shiChen[shichenIdx], + } +} + +// selectHexagram selects an I Ching hexagram based on task type. +func (g *DNAGenerator) selectHexagram(taskType string) Hexagram { + domain, ok := taskHexagramMap[taskType] + if !ok { + domain = "governance" + } + for _, h := range hexagrams { + if h.Domain == domain { + return h + } + } + return hexagrams[0] // Default: Qian +} + +// mod returns the non-negative remainder of a % b. +func mod(a, b int) int { + m := a % b + if m < 0 { + m += b + } + return m +} + +// toUpper returns the uppercase ASCII version of s. +func toUpper(s string) string { + b := []byte(s) + for i := range b { + if b[i] >= 'a' && b[i] <= 'z' { + b[i] -= 32 + } + } + return string(b) +} diff --git a/adapters/go/lhstandardadapter/doc.go b/adapters/go/lhstandardadapter/doc.go new file mode 100644 index 0000000..ed128a7 --- /dev/null +++ b/adapters/go/lhstandardadapter/doc.go @@ -0,0 +1,23 @@ +// Package lhstandardadapter implements the LongHun Standard Adapter v1.0.0 in Go. +// +// DNA: #LongHun⚡️BingWu·GuiWei·JiaZi·ZiShi·䷾JiJi-ADAPTER-v1.0.0 +// +// This adapter wraps JSON payloads with DNA traceability and seven-factor +// behavioral audit metadata. It is a pure Go port of the Python reference +// implementation, using only the Go standard library. +// +// Usage: +// +// adapter := lhstandardadapter.NewAdapter("9622", "HM-9622-001") +// wrapped, err := adapter.Wrap(data, "code", "P04") +// result := adapter.Validate(wrapped) +// +// Open the standard. Guard the engine. +package lhstandardadapter + +const ( + Version = "1.0.0" + Author = "LongHun Core · UID9622 · 龍芯北辰" + License = "CC BY-NC-SA 4.0" + DNA = "#LongHun⚡️BingWu·GuiWei·JiaZi·ZiShi·䷾JiJi-ADAPTER-v1.0.0-4f7a3b1c" +) diff --git a/adapters/go/lhstandardadapter/helpers_test.go b/adapters/go/lhstandardadapter/helpers_test.go new file mode 100644 index 0000000..c99dd36 --- /dev/null +++ b/adapters/go/lhstandardadapter/helpers_test.go @@ -0,0 +1,20 @@ +package lhstandardadapter + +import ( + "encoding/json" + "time" +) + +// timeFromYMDH creates a time.Time at UTC+8 for testing. +func timeFromYMDH(year, month, day, hour int) time.Time { + loc := time.FixedZone("CST", cstOffset*3600) + return time.Date(year, time.Month(month), day, hour, 0, 0, 0, loc) +} + +func jsonMarshalImpl(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +func jsonUnmarshalImpl(b []byte, v interface{}) error { + return json.Unmarshal(b, v) +} diff --git a/adapters/go/lhstandardadapter/schemas.go b/adapters/go/lhstandardadapter/schemas.go new file mode 100644 index 0000000..7cc9a1b --- /dev/null +++ b/adapters/go/lhstandardadapter/schemas.go @@ -0,0 +1,78 @@ +package lhstandardadapter + +const dnaSchemaJSON = `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://uid9622.cn/schemas/dna-v1.0.json", + "title": "LongHun DNA Traceability Code", + "type": "object", + "required": ["dna", "format", "uid", "timestamp"], + "properties": { + "dna": { + "type": "string", + "description": "Full v∞ DNA traceability code", + "pattern": "^#LongHun⚡️[A-Z][a-zA-Z]+·[A-Z][a-zA-Z]+·[A-Z][a-zA-Z]+·[A-Z][a-zA-Z]+·[䷀-䷿][A-Za-z]+-.+-[a-f0-9]{8}$" + }, + "format": { "type": "string", "enum": ["v1.0", "v2.0", "v∞", "compact"] }, + "uid": { "type": "string", "pattern": "^UID\\d+$" }, + "device": { "type": "string" }, + "timestamp": { "type": "string", "format": "date-time" } + } +}` + +const auditSchemaJSON = `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://uid9622.cn/schemas/audit-v1.0.json", + "title": "LongHun Audit Record", + "type": "object", + "required": ["dna", "audit", "payload", "meta"], + "properties": { + "dna": { "type": "string" }, + "audit": { + "type": "object", + "required": ["audit_version", "uid", "behavior_signature", "behavior_pattern", "behavior_labels", "color"], + "properties": { + "audit_version": { "type": "string" }, + "uid": { "type": "string" }, + "persona": { "type": "string" }, + "task_type": { "type": "string" }, + "behavior_signature": { + "type": "object", + "required": ["P", "F", "T", "E", "C", "R", "A", "X", "Y", "Z"], + "properties": { + "P": { "enum": ["HasPromise", "NoPromise"] }, + "F": { "enum": ["Fulfilled", "Unfulfilled", "Partial"] }, + "T": { "type": "number" }, + "E": { "enum": ["Willing", "Perfunctory", "Resentful", "Numb"] }, + "C": { "type": "number" }, + "R": { "type": "integer", "minimum": 0 }, + "A": { "enum": ["Self", "Partner", "Family", "Outsider", "Public"] }, + "X": { "enum": ["OverExplain", "Silent", "Genuine", "Indifferent"] }, + "Y": { "enum": ["Changed", "Resisted", "Indifferent", "NoResponse"] }, + "Z": { "type": "number" } + } + }, + "behavior_pattern": { + "enum": ["MODE-DefensiveDefaulter", "MODE-ExternalTrustSpender", "MODE-InternalDestroyer", "MODE-Fluctuating", "MODE-StableDisciplined"] + }, + "behavior_labels": { "type": "array", "items": { "type": "string" } }, + "color": { "enum": ["🟢", "🟡", "🔴"] }, + "timestamp": { "type": "string", "format": "date-time" }, + "payload_hash": { "type": "string", "pattern": "^[a-f0-9]{16}$" } + } + }, + "payload": {}, + "meta": { + "type": "object", + "required": ["adapter_version", "uid", "device", "task_type", "persona"], + "properties": { + "adapter_version": { "type": "string" }, + "uid": { "type": "string" }, + "device": { "type": "string" }, + "task_type": { "type": "string" }, + "persona": { "type": "string" }, + "generated_at": { "type": "string", "format": "date-time" }, + "format": { "const": "longhun-v∞" } + } + } + } +}` diff --git a/adapters/go/lhstandardadapter/validator.go b/adapters/go/lhstandardadapter/validator.go new file mode 100644 index 0000000..cf80501 --- /dev/null +++ b/adapters/go/lhstandardadapter/validator.go @@ -0,0 +1,303 @@ +package lhstandardadapter + +import ( + "encoding/json" + "regexp" + "strings" +) + +// DNARegex validates the v∞ DNA traceability code format. +var DNARegex = regexp.MustCompile( + `^#LongHun⚡️` + + `([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)` + // Four pillars + `·([䷀-䷿][A-Za-z]+)` + // Hexagram + `-(.+)` + // Body (module-action-version) + `-([a-f0-9]{8})$`, // Hash8 +) + +var ( + requiredTopKeys = []string{"dna", "audit", "payload", "meta"} + requiredAuditKeys = []string{"audit_version", "uid", "behavior_signature", + "behavior_pattern", "behavior_labels", "color"} + requiredSigKeys = []string{"P", "F", "T", "E", "C", "R", "A", "X", "Y", "Z"} + + validColors = map[string]bool{"🟢": true, "🟡": true, "🔴": true} + validPatterns = map[string]bool{ + "MODE-DefensiveDefaulter": true, + "MODE-ExternalTrustSpender": true, + "MODE-InternalDestroyer": true, + "MODE-Fluctuating": true, + "MODE-StableDisciplined": true, + } + validPValues = map[string]bool{"HasPromise": true, "NoPromise": true} + validFValues = map[string]bool{"Fulfilled": true, "Unfulfilled": true, "Partial": true} + validEValues = map[string]bool{"Willing": true, "Perfunctory": true, "Resentful": true, "Numb": true} + validAValues = map[string]bool{"Self": true, "Partner": true, "Family": true, "Outsider": true, "Public": true} + validXValues = map[string]bool{"OverExplain": true, "Silent": true, "Genuine": true, "Indifferent": true} + validYValues = map[string]bool{"Changed": true, "Resisted": true, "Indifferent": true, "NoResponse": true} +) + +// ValidationResult holds the outcome of validation. +type ValidationResult struct { + Valid bool `json:"valid"` + Errors []string `json:"errors"` + Warnings []string `json:"warnings"` + Summary string `json:"summary"` +} + +// Validator validates wrapped payloads for LongHun standard compliance. +type Validator struct { + errors []string + warnings []string +} + +// NewValidator creates a Validator. +func NewValidator() *Validator { + return &Validator{} +} + +// Validate checks a wrapped payload map for compliance. +func (v *Validator) Validate(wrapped map[string]interface{}) ValidationResult { + v.errors = []string{} + v.warnings = []string{} + + if len(wrapped) == 0 { + v.errors = append(v.errors, "Input is not a non-empty dict") + return v.result() + } + + // 1. Top-level keys + for _, k := range requiredTopKeys { + if _, ok := wrapped[k]; !ok { + v.errors = append(v.errors, "Missing top-level key: "+k) + } + } + + // 2. DNA validation + dna, _ := wrapped["dna"].(string) + if dna == "" { + v.errors = append(v.errors, "DNA field is empty") + } else { + match := DNARegex.FindStringSubmatch(dna) + if match == nil { + v.errors = append(v.errors, "DNA does not match regex: "+truncate(dna, 60)+"...") + } else { + hash8 := match[7] + if len(hash8) != 8 || !isHexLower(hash8) { + v.errors = append(v.errors, "Invalid hash8: "+hash8) + } + } + } + + // 3. Audit validation + audit, ok := wrapped["audit"].(map[string]interface{}) + if !ok { + v.errors = append(v.errors, "Audit is not a dict") + } else { + v.validateAudit(audit) + + // 4. UID consistency check + if meta, ok := wrapped["meta"].(map[string]interface{}); ok { + metaUID, _ := meta["uid"].(string) + auditUID, _ := audit["uid"].(string) + if metaUID != "" && auditUID != "" { + auditUIDClean := strings.TrimPrefix(auditUID, "UID") + if metaUID != auditUIDClean { + v.errors = append(v.errors, + "UID mismatch: meta.uid="+metaUID+", audit.uid="+auditUID) + } + } + } + } + + return v.result() +} + +func (v *Validator) validateAudit(audit map[string]interface{}) { + // Required keys + for _, k := range requiredAuditKeys { + if _, ok := audit[k]; !ok { + v.errors = append(v.errors, "Missing audit key: "+k) + } + } + + // behavior_signature + sig, ok := audit["behavior_signature"].(map[string]interface{}) + if !ok { + v.errors = append(v.errors, "behavior_signature is not a dict") + } else { + for _, k := range requiredSigKeys { + if _, ok := sig[k]; !ok { + v.errors = append(v.errors, "Missing signature key: "+k) + } + } + if len(sig) >= len(requiredSigKeys) { + v.validateSigValues(sig) + } + } + + // pattern + pattern, _ := audit["behavior_pattern"].(string) + if pattern != "" && !validPatterns[pattern] { + v.warnings = append(v.warnings, "Unknown behavior pattern: "+pattern) + } + + // color + color, _ := audit["color"].(string) + if color != "" && !validColors[color] { + v.warnings = append(v.warnings, "Unknown audit color: "+color) + } + + // payload_hash + ph, _ := audit["payload_hash"].(string) + if ph != "" && (len(ph) != 16 || !isHexLower(ph)) { + v.warnings = append(v.warnings, "Suspicious payload_hash: "+ph) + } +} + +func (v *Validator) validateSigValues(sig map[string]interface{}) { + // P + if val, ok := sig["P"].(string); ok && !validPValues[val] { + v.warnings = append(v.warnings, "Invalid P: '"+val+"'") + } + // F + if val, ok := sig["F"].(string); ok && !validFValues[val] { + v.warnings = append(v.warnings, "Invalid F: '"+val+"'") + } + // T (number) + if _, ok := toFloat(sig["T"]); !ok { + v.warnings = append(v.warnings, "Invalid T (number)") + } + // E + if val, ok := sig["E"].(string); ok && !validEValues[val] { + v.warnings = append(v.warnings, "Invalid E: '"+val+"'") + } + // C (number) + if _, ok := toFloat(sig["C"]); !ok { + v.warnings = append(v.warnings, "Invalid C (number)") + } + // R (int >= 0) + if r, ok := toInt(sig["R"]); !ok || r < 0 { + v.warnings = append(v.warnings, "Invalid R (int >= 0)") + } + // A + if val, ok := sig["A"].(string); ok && !validAValues[val] { + v.warnings = append(v.warnings, "Invalid A: '"+val+"'") + } + // X + if val, ok := sig["X"].(string); ok && !validXValues[val] { + v.warnings = append(v.warnings, "Invalid X: '"+val+"'") + } + // Y + if val, ok := sig["Y"].(string); ok && !validYValues[val] { + v.warnings = append(v.warnings, "Invalid Y: '"+val+"'") + } + // Z (number) + if _, ok := toFloat(sig["Z"]); !ok { + v.warnings = append(v.warnings, "Invalid Z (number)") + } +} + +func (v *Validator) result() ValidationResult { + valid := len(v.errors) == 0 + var summary string + if valid { + summary = "✅ VALID — " + itoa(len(v.warnings)) + " warning(s)" + } else { + summary = "❌ INVALID — " + itoa(len(v.errors)) + " error(s)" + } + return ValidationResult{ + Valid: valid, + Errors: v.errors, + Warnings: v.warnings, + Summary: summary, + } +} + +// QuickValidate checks if a wrapped payload has required keys and valid DNA. +func QuickValidate(wrapped map[string]interface{}) bool { + if len(wrapped) == 0 { + return false + } + for _, k := range []string{"dna", "audit"} { + if _, ok := wrapped[k]; !ok { + return false + } + } + dna, _ := wrapped["dna"].(string) + return DNARegex.MatchString(dna) +} + +// --- helpers --- + +func isHexLower(s string) bool { + for _, c := range s { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { + return false + } + } + return true +} + +func truncate(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] +} + +func toFloat(v interface{}) (float64, bool) { + switch n := v.(type) { + case float64: + return n, true + case float32: + return float64(n), true + case int: + return float64(n), true + case int64: + return float64(n), true + case json.Number: + f, err := n.Float64() + return f, err == nil + default: + return 0, false + } +} + +func toInt(v interface{}) (int, bool) { + switch n := v.(type) { + case float64: + return int(n), true + case int: + return n, true + case int64: + return int(n), true + case json.Number: + i, err := n.Int64() + return int(i), err == nil + default: + return 0, false + } +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var buf [20]byte + i := len(buf) + for n > 0 { + i-- + buf[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + buf[i] = '-' + } + return string(buf[i:]) +}