inou/lib/types.go

345 lines
10 KiB
Go

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"`
}