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 ) // 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", } // 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) } // ParseID converts 16-char hex string to int64 (for legacy/token use) func ParseID(s string) int64 { var id int64 fmt.Sscanf(s, "%x", &id) return id } // FormatID converts int64 to 16-char hex string (for legacy/token use) func FormatID(id int64) string { return fmt.Sprintf("%016x", id) } // Access represents a permission grant or role template type Access struct { AccessID string `db:"access_id,pk"` DossierID string `db:"dossier_id"` // whose data GranteeID string `db:"grantee_id"` // who gets access EntryID string `db:"entry_id"` // specific entry (empty = root level) Relation int `db:"relation"` // relationship type (0=self, 1=child, 2=parent, etc.) Ops int `db:"ops"` // bitmask: 1=read, 2=write, 4=delete, 8=manage CreatedAt int64 `db:"created_at"` } // 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 } // Dossier represents a user profile (decrypted) type Dossier struct { DossierID string `db:"dossier_id,pk"` EmailHash string `db:"email_hash"` Email string `db:"email"` Name string `db:"name"` DateOfBirth string `db:"date_of_birth"` // encrypted YYYY-MM-DD DOB time.Time `db:"-"` // parsed date, not stored Sex int `db:"sex"` Phone string `db:"phone"` Language string `db:"language"` Timezone string `db:"timezone"` AuthCode int `db:"auth_code"` AuthCodeExpiresAt int64 `db:"auth_code_expires_at"` LastLogin int64 `db:"last_login"` InvitedByDossierID string `db:"invited_by_dossier_id"` CreatedAt int64 `db:"created_at"` WeightUnit string `db:"weight_unit"` HeightUnit string `db:"height_unit"` LastPullAt int64 `db:"last_pull_at"` IsProvider bool `db:"is_provider"` ProviderName string `db:"provider_name"` AwayMessage string `db:"away_message"` AwayEnabled bool `db:"away_enabled"` SessionToken string `db:"session_token"` } // 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) type Entry struct { EntryID string `db:"entry_id,pk"` DossierID string `db:"dossier_id"` ParentID string `db:"parent_id"` ProductID string `db:"product_id"` 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:"timestamp_end"` Status int `db:"status"` Tags string `db:"tags"` Data string `db:"data"` SearchKey string `db:"search_key"` // LOINC (labs), gene (genome), modality (imaging) - encrypted } // Audit represents an audit log entry type AuditEntry struct { AuditID string `db:"audit_id,pk"` Actor1ID string `db:"actor1_id"` Actor2ID string `db:"actor2_id"` TargetID string `db:"target_id"` Action string `db:"action,encrypt"` Details string `db:"details,encrypt"` RelationID int `db:"relation_id"` Timestamp int64 `db:"timestamp"` } // Prompt represents a scheduled question or tracker (decrypted) type Tracker struct { TrackerID string `db:"tracker_id,pk"` DossierID string `db:"dossier_id"` Category string `db:"category"` Type string `db:"type"` Question string `db:"question"` Frequency string `db:"frequency"` TimeOfDay string `db:"time_of_day"` Schedule string `db:"schedule"` NextAsk int64 `db:"next_ask"` ExpiresAt int64 `db:"expires_at"` InputType string `db:"input_type"` InputConfig string `db:"input_config"` GroupName string `db:"group_name"` TriggerEntry int64 `db:"trigger_entry"` CreatedBy int64 `db:"created_by"` SourceInput string `db:"source_input"` // Last response (for pre-filling) LastResponse string `db:"last_response"` LastResponseRaw string `db:"last_response_raw"` LastResponseAt int64 `db:"last_response_at"` // State Dismissed bool `db:"dismissed"` Open bool `db:"open"` Active bool `db:"active"` CreatedAt int64 `db:"created_at"` UpdatedAt int64 `db:"updated_at"` }