persona-community-1/pkg/persona/consistency.go
jordan 4004f88f4a
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/manual/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-23 10:20:59 +00:00

178 lines
6.1 KiB
Go

package persona
import "fmt"
// ConsistencyIssue represents an internally inconsistent attribute combination.
// Unlike plausibility violations (which compare against ethnicity expectations),
// consistency issues catch impossible or contradictory combinations regardless
// of ethnicity.
type ConsistencyIssue struct {
Field1 string // First field involved (e.g., "identity.age")
Value1 interface{} // Value of first field
Field2 string // Second field involved (e.g., "face.hair_color")
Value2 interface{} // Value of second field
Severity string // "warning" or "error"
Description string // Human-readable explanation
}
// ConsistencyResult contains cross-attribute validation results.
type ConsistencyResult struct {
Valid bool // True if no errors (warnings are OK)
Issues []ConsistencyIssue // All issues found
}
// ValidateConsistency checks for internally inconsistent attribute combinations
// that are implausible regardless of ethnicity.
//
// Examples of inconsistencies:
// - Young age (< 25) with gray hair (without dye indication)
// - Mature skin texture with young age
// - Height category mismatched with height CM
// - Muscle definition that contradicts build type
func ValidateConsistency(dna *DNA) *ConsistencyResult {
if dna == nil {
return &ConsistencyResult{Valid: true}
}
result := &ConsistencyResult{
Valid: true,
Issues: []ConsistencyIssue{},
}
// Age vs Hair Color consistency
if dna.Identity.Age > 0 && dna.Face.HairColor != "" {
if dna.Identity.Age < 30 && dna.Face.HairColor == HairColorGray {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "identity.age",
Value1: dna.Identity.Age,
Field2: "face.hair_color",
Value2: dna.Face.HairColor,
Severity: "warning",
Description: fmt.Sprintf("Gray hair at age %d is unusual without premature graying or dye", dna.Identity.Age),
})
}
}
// Age vs Skin Texture consistency
if dna.Identity.Age > 0 && dna.Face.SkinTexture != "" {
if dna.Identity.Age < 35 && dna.Face.SkinTexture == SkinTextureMature {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "identity.age",
Value1: dna.Identity.Age,
Field2: "face.skin_texture",
Value2: dna.Face.SkinTexture,
Severity: "warning",
Description: fmt.Sprintf("Mature skin texture at age %d is inconsistent", dna.Identity.Age),
})
}
if dna.Identity.Age > 55 && dna.Face.SkinTexture == SkinTextureSmooth {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "identity.age",
Value1: dna.Identity.Age,
Field2: "face.skin_texture",
Value2: dna.Face.SkinTexture,
Severity: "warning",
Description: fmt.Sprintf("Smooth skin texture at age %d is unusual", dna.Identity.Age),
})
}
}
// Height Category vs Height CM consistency
if dna.Body.HeightCM > 0 && dna.Body.Height != "" {
if issue := validateHeightCMConsistency(dna.Body.HeightCM, dna.Body.Height); issue != nil {
result.Issues = append(result.Issues, *issue)
if issue.Severity == "error" {
result.Valid = false
}
}
}
// Muscle Definition vs Build consistency
if dna.Body.MuscleDefinition != "" && dna.Body.Build != "" {
// High muscle definition with slender build is contradictory
if dna.Body.MuscleDefinition == MuscleDefinitionDefined || dna.Body.MuscleDefinition == MuscleDefinitionRipped {
if dna.Body.Build == BodyBuildSlender || dna.Body.Build == BodyBuildPetite {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "body.muscle_definition",
Value1: dna.Body.MuscleDefinition,
Field2: "body.build",
Value2: dna.Body.Build,
Severity: "warning",
Description: fmt.Sprintf("High muscle definition (%s) is unusual with %s build", dna.Body.MuscleDefinition, dna.Body.Build),
})
}
}
}
// Body Fat Percent vs Build consistency
if dna.Body.BodyFatPercent > 0 && dna.Body.Build != "" {
// Very low body fat (< 15%) with curvy/plus_curvy build is contradictory
if dna.Body.BodyFatPercent < 15 {
if dna.Body.Build == BodyBuildCurvy || dna.Body.Build == BodyBuildPlusCurvy {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "body.body_fat_percent",
Value1: dna.Body.BodyFatPercent,
Field2: "body.build",
Value2: dna.Body.Build,
Severity: "error",
Description: fmt.Sprintf("Very low body fat (%d%%) is incompatible with %s build", dna.Body.BodyFatPercent, dna.Body.Build),
})
result.Valid = false
}
}
// High body fat (> 30%) with athletic/muscular build is contradictory
if dna.Body.BodyFatPercent > 30 {
if dna.Body.Build == BodyBuildAthletic || dna.Body.Build == BodyBuildMuscular {
result.Issues = append(result.Issues, ConsistencyIssue{
Field1: "body.body_fat_percent",
Value1: dna.Body.BodyFatPercent,
Field2: "body.build",
Value2: dna.Body.Build,
Severity: "error",
Description: fmt.Sprintf("High body fat (%d%%) is incompatible with %s build", dna.Body.BodyFatPercent, dna.Body.Build),
})
result.Valid = false
}
}
}
return result
}
// validateHeightCMConsistency checks if height CM matches height category.
func validateHeightCMConsistency(heightCM int, heightCat HeightCategory) *ConsistencyIssue {
expectedCat := HeightCategoryFromCM(heightCM)
if heightCat != expectedCat {
return &ConsistencyIssue{
Field1: "body.height_cm",
Value1: heightCM,
Field2: "body.height",
Value2: heightCat,
Severity: "error",
Description: fmt.Sprintf("Height %dcm should be category %s, not %s", heightCM, expectedCat, heightCat),
}
}
return nil
}
// HasErrors returns true if there are any error-level issues.
func (r *ConsistencyResult) HasErrors() bool {
for _, issue := range r.Issues {
if issue.Severity == "error" {
return true
}
}
return false
}
// HasWarnings returns true if there are any warning-level issues.
func (r *ConsistencyResult) HasWarnings() bool {
for _, issue := range r.Issues {
if issue.Severity == "warning" {
return true
}
}
return false
}