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 (null = system template) GranteeID string `db:"grantee_id"` // who gets access (null = role template) EntryID string `db:"entry_id"` // specific entry (null = root level) Role string `db:"role"` // "Trainer", "Family", custom Ops string `db:"ops"` // "r", "rw", "rwd", "rwdm" CreatedAt int64 `db:"created_at"` } // HasOp checks if the access grant includes a specific operation func (a *Access) HasOp(op rune) bool { for _, c := range a.Ops { if c == op { return true } } return false } // CanRead returns true if ops includes 'r' func (a *Access) CanRead() bool { return a.HasOp('r') } // CanWrite returns true if ops includes 'w' func (a *Access) CanWrite() bool { return a.HasOp('w') } // CanDelete returns true if ops includes 'd' func (a *Access) CanDelete() bool { return a.HasOp('d') } // CanManage returns true if ops includes 'm' func (a *Access) CanManage() bool { return a.HasOp('m') } // 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 "" } } // DossierAccess represents sharing permissions (legacy - use RBAC access table instead) type DossierAccess struct { AccessID string `db:"access_id,pk"` AccessorDossierID string `db:"accessor_dossier_id"` TargetDossierID string `db:"target_dossier_id"` Relation int `db:"relation"` IsCareReceiver bool `db:"is_care_receiver"` CanEdit bool `db:"can_edit"` Status int `db:"status"` CreatedAt int64 `db:"created_at"` AccessedAt int64 `db:"accessed_at"` } // 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"` }