package lib import ( "fmt" "path/filepath" "time" ) // Category enum for entries and prompts const ( CategoryAll = iota CategoryImaging CategoryDocument CategoryLab CategoryGenome CategoryUpload CategoryConsultation CategoryDiagnosis CategoryVital CategoryExercise CategoryMedication CategorySupplement CategoryNutrition CategoryFertility CategorySymptom CategoryNote CategoryHistory CategoryFamilyHistory CategorySurgery CategoryHospital CategoryBirth CategoryDevice CategoryTherapy CategoryAssessment CategoryProvider CategoryQuestion CategoryTracker ) // GenomeTier enum - ordered by "fun" (scary last, other always last) const ( GenomeTierTraits = iota + 1 GenomeTierAncestry GenomeTierLongevity GenomeTierMetabolism GenomeTierMedication GenomeTierMentalHealth GenomeTierNeurological GenomeTierFertility GenomeTierBlood GenomeTierCardiovascular GenomeTierAutoimmune GenomeTierDisease GenomeTierCancer GenomeTierOther = 99 ) // GenomeTierFromString maps tier category names to ints var GenomeTierFromString = map[string]int{ "traits": GenomeTierTraits, "ancestry": GenomeTierAncestry, "longevity": GenomeTierLongevity, "metabolism": GenomeTierMetabolism, "medication": GenomeTierMedication, "mental_health": GenomeTierMentalHealth, "neurological": GenomeTierNeurological, "fertility": GenomeTierFertility, "blood": GenomeTierBlood, "cardiovascular": GenomeTierCardiovascular, "autoimmune": GenomeTierAutoimmune, "disease": GenomeTierDisease, "cancer": GenomeTierCancer, "other": GenomeTierOther, } // categoryNames maps category ints to their English names (source of truth) var categoryNames = map[int]string{ CategoryImaging: "imaging", CategoryDocument: "document", CategoryLab: "lab", CategoryGenome: "genome", CategoryUpload: "upload", CategoryConsultation: "consultation", CategoryDiagnosis: "diagnosis", CategoryVital: "vital", CategoryExercise: "exercise", CategoryMedication: "medication", CategorySupplement: "supplement", CategoryNutrition: "nutrition", CategoryFertility: "fertility", CategorySymptom: "symptom", CategoryNote: "note", CategoryHistory: "history", CategoryFamilyHistory: "family_history", CategorySurgery: "surgery", CategoryHospital: "hospitalization", CategoryBirth: "birth", CategoryDevice: "device", CategoryTherapy: "therapy", CategoryAssessment: "assessment", CategoryProvider: "provider", CategoryQuestion: "question", CategoryTracker: "tracker", } // CategoryFromString converts category names to ints (generated from categoryNames) var CategoryFromString map[string]int func init() { // Generate reverse map from categoryNames (single source of truth) CategoryFromString = make(map[string]int, len(categoryNames)) for catInt, name := range categoryNames { CategoryFromString[name] = catInt } } // CategoryKey returns the translation key for a category (e.g. "category003") func CategoryKey(cat int) string { return fmt.Sprintf("category%03d", cat) } // CategoryTypes maps category names to their valid type values var CategoryTypes = map[string][]string{ "imaging": {"study", "series", "slice"}, "document": {"radiology_report", "ultrasound", "other"}, "lab": {"lab_report", "result"}, "genome": {"extraction", "tier", "variant"}, "upload": {"pending", "processed"}, "consultation": {"visit", "telehealth"}, "diagnosis": {"active", "resolved"}, "vital": {"weight", "height", "blood_pressure", "heart_rate", "temperature", "oxygen", "glucose"}, "exercise": {"activity", "workout"}, "medication": {"prescription", "otc"}, "supplement": {"vitamin", "mineral", "herbal"}, "nutrition": {"meal", "snack"}, "fertility": {"cycle", "ovulation", "pregnancy"}, "symptom": {"acute", "chronic"}, "note": {"general", "clinical"}, "history": {"medical", "surgical"}, "family_history": {"parent", "sibling", "grandparent"}, "surgery": {"inpatient", "outpatient"}, "hospitalization": {"admission", "discharge"}, "birth": {"delivery", "newborn"}, "device": {"implant", "wearable"}, "therapy": {"physical", "occupational", "speech"}, "assessment": {"screening", "evaluation"}, "provider": {"physician", "specialist", "nurse"}, "question": {"inquiry", "followup"}, } // Categories returns all category definitions func Categories() []struct { ID int Name string Types []string } { var result []struct { ID int Name string Types []string } for i := 1; i <= CategoryQuestion; i++ { name := categoryNames[i] result = append(result, struct { ID int Name string Types []string }{i, name, CategoryTypes[name]}) } return result } // CategoryName returns the string name for a category int func CategoryName(cat int) string { if name, ok := categoryNames[cat]; ok { return name } return "unknown" } // ObjectDir is the base path for encrypted object storage const ObjectDir = "/tank/inou/objects" // ObjectPath returns the storage path for an entry: {base}/{dossier_hex}/{first_byte}/{entry_hex} func ObjectPath(dossierID, entryID string) string { return filepath.Join(ObjectDir, dossierID, entryID[0:2], entryID) } // Access represents a permission grant type Access struct { AccessID string `db:"AccessID,pk"` DossierID string `db:"DossierID"` // whose data GranteeID string `db:"GranteeID"` // who gets access EntryID string `db:"EntryID"` // specific entry (empty = root level) Relation int `db:"Relation"` // relationship type Ops int `db:"Ops"` // bitmask: 1=read, 2=write, 4=delete, 8=manage CreatedAt int64 `db:"CreatedAt"` } // CanRead returns true if ops includes read bit (1) func CanRead(ops int) bool { return ops&1 != 0 } // CanWrite returns true if ops includes write bit (2) func CanWrite(ops int) bool { return ops&2 != 0 } // CanDelete returns true if ops includes delete bit (4) func CanDelete(ops int) bool { return ops&4 != 0 } // CanManage returns true if ops includes manage bit (8) func CanManage(ops int) bool { return ops&8 != 0 } // MakeOps creates an ops bitmask from individual permissions func MakeOps(read, write, delete, manage bool) int { ops := 0 if read { ops |= 1 } if write { ops |= 2 } if delete { ops |= 4 } if manage { ops |= 8 } return ops } // DossierPreferences holds user preferences stored as JSON type DossierPreferences struct { Language string `json:"language,omitempty"` Timezone string `json:"timezone,omitempty"` WeightUnit string `json:"weight_unit,omitempty"` HeightUnit string `json:"height_unit,omitempty"` IsProvider bool `json:"is_provider,omitempty"` ProviderName string `json:"provider_name,omitempty"` AwayEnabled bool `json:"away_enabled,omitempty"` AwayMessage string `json:"away_message,omitempty"` } // Dossier represents a user profile (decrypted) type Dossier struct { DossierID string `db:"DossierID,pk"` Email string `db:"Email"` Name string `db:"Name"` DateOfBirth string `db:"DateOfBirth"` DOB time.Time `db:"-"` Sex int `db:"Sex"` Phone string `db:"Phone"` Preferences DossierPreferences `db:"Preferences"` AuthCode int `db:"AuthCode"` AuthCodeExpiresAt int64 `db:"AuthCodeExpiresAt"` LastLogin int64 `db:"LastLogin"` InvitedByDossierID string `db:"InvitedByDossierID"` CreatedAt int64 `db:"CreatedAt"` LastPullAt int64 `db:"LastPullAt"` SessionToken string `db:"SessionToken"` } // SexKey returns the translation key for the sex enum (ISO/IEC 5218) // 0=not known, 1=male, 2=female, 9=not applicable func (d *Dossier) SexKey() string { switch d.Sex { case 1: return "sex_male" case 2: return "sex_female" case 9: return "sex_na" default: return "" } } // Entry represents any data item (decrypted). // Dossier profiles are entries with Category=0. // IDs are string now, will be int64 when migration is complete. type Entry struct { EntryID string `db:"EntryID,pk"` DossierID string `db:"DossierID"` ParentID string `db:"ParentID"` Category int `db:"Category"` Type string `db:"Type"` Value string `db:"Value"` Summary string `db:"Summary"` Ordinal int `db:"Ordinal"` Timestamp int64 `db:"Timestamp"` TimestampEnd int64 `db:"TimestampEnd"` Status int `db:"Status"` Tags string `db:"Tags"` Data string `db:"Data"` SearchKey string `db:"SearchKey"` SearchKey2 string `db:"SearchKey2"` Import int64 `db:"Import"` } // AuditEntry represents an audit log entry type AuditEntry struct { AuditID string `db:"AuditID,pk"` Actor1ID string `db:"Actor1ID"` Actor2ID string `db:"Actor2ID"` TargetID string `db:"TargetID"` Action string `db:"Action"` Details string `db:"Details"` RelationID int `db:"RelationID"` Timestamp int64 `db:"Timestamp"` } // Tracker represents a scheduled question or tracker (decrypted) type Tracker struct { TrackerID string `db:"TrackerID,pk"` DossierID string `db:"DossierID"` Category string `db:"Category"` Type string `db:"Type"` Question string `db:"Question"` Frequency string `db:"Frequency"` TimeOfDay string `db:"TimeOfDay"` Schedule string `db:"Schedule"` NextAsk int64 `db:"NextAsk"` ExpiresAt int64 `db:"ExpiresAt"` InputType string `db:"InputType"` InputConfig string `db:"InputConfig"` GroupName string `db:"GroupName"` TriggerEntry int64 `db:"TriggerEntry"` CreatedBy int64 `db:"CreatedBy"` SourceInput string `db:"SourceInput"` LastResponse string `db:"LastResponse"` LastResponseRaw string `db:"LastResponseRaw"` LastResponseAt int64 `db:"LastResponseAt"` Dismissed bool `db:"Dismissed"` Open bool `db:"Open"` Active bool `db:"Active"` CreatedAt int64 `db:"CreatedAt"` UpdatedAt int64 `db:"UpdatedAt"` }