inou/api/api_categories.go

258 lines
6.3 KiB
Go

package main
import (
"encoding/json"
"net/http"
"strings"
"inou/lib"
)
type CategoryCount struct {
Shown int `json:"shown"`
Hidden int `json:"hidden"`
}
func handleCategories(w http.ResponseWriter, r *http.Request) {
// Get accessor (who is asking)
ctx := getAccessContextOrFail(w, r)
if ctx == nil {
return
}
dossierHex := r.URL.Query().Get("dossier")
if dossierHex == "" {
http.Error(w, "missing dossier", http.StatusBadRequest)
return
}
dossierID := dossierHex
obsType := r.URL.Query().Get("type")
category := r.URL.Query().Get("category")
// Pass accessor + dossier to lib - RBAC handled there
var counts map[string]CategoryCount
if obsType == "" {
counts = getTopLevelCounts(ctx.AccessorID, dossierID)
} else if obsType == "genome" {
if category != "" {
counts = getGenomeSubcategoryCounts(ctx.AccessorID, dossierID, category)
} else {
counts = getGenomeCounts(ctx.AccessorID, dossierID)
}
} else {
counts = make(map[string]CategoryCount)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(counts)
}
func getTopLevelCounts(accessorID, dossierID string) map[string]CategoryCount {
counts := make(map[string]CategoryCount)
categoryNames := map[int]string{
1: "imaging", 2: "documents", 3: "labs", 4: "genome",
}
// Check each category - only include if accessor has access
for catInt, catName := range categoryNames {
// Try to get a count by querying entries with RBAC
entries, err := lib.EntryList(accessorID, "", catInt, &lib.EntryFilter{
DossierID: dossierID,
Limit: 1, // Just check if we can see any
})
if err != nil || len(entries) == 0 {
continue // No access or no entries
}
// Get actual count (using system context for counting only)
var catCounts []struct {
Count int `db:"cnt"`
}
lib.Query("SELECT COUNT(*) as cnt FROM entries WHERE dossier_id = ? AND category = ?",
[]any{dossierID, catInt}, &catCounts)
if len(catCounts) > 0 && catCounts[0].Count > 0 {
counts[catName] = CategoryCount{Shown: catCounts[0].Count, Hidden: 0}
}
}
// For genome, replace with detailed subcategory counts
genomeCats := getGenomeCounts(accessorID, dossierID)
if len(genomeCats) > 0 {
totalShown, totalHidden := 0, 0
for _, c := range genomeCats {
totalShown += c.Shown
totalHidden += c.Hidden
}
counts["genome"] = CategoryCount{Shown: len(genomeCats), Hidden: totalHidden}
}
return counts
}
// variantData for parsing the data JSON
type variantData struct {
Mag float64 `json:"mag"`
Rep string `json:"rep"`
Sub string `json:"sub"`
}
// shouldIncludeVariant returns true if the variant should be counted/shown
func shouldIncludeVariant(data variantData, includeHidden bool) bool {
if includeHidden {
return true
}
// Hide high magnitude variants
if data.Mag > 4.0 {
return false
}
// Hide "Bad" repute variants
if data.Rep == "Bad" || data.Rep == "bad" {
return false
}
return true
}
// getGenomeCounts reads cached counts from the extraction entry (fast path)
func getGenomeCounts(accessorID, dossierID string) map[string]CategoryCount {
counts := make(map[string]CategoryCount)
// Create access context for RBAC
ctx := &lib.AccessContext{AccessorID: accessorID}
// Find extraction entry and read its data
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
if err != nil {
return counts
}
if extraction.Data == "" {
return counts
}
// Parse extraction data which contains pre-computed counts
var extractionData struct {
Counts map[string]CategoryCount `json:"counts"`
}
if err := json.Unmarshal([]byte(extraction.Data), &extractionData); err != nil {
return counts
}
// Return cached counts if available
if extractionData.Counts != nil {
return extractionData.Counts
}
// Fallback: compute counts (for old data without cached counts)
return getGenomeCountsSlow(accessorID, dossierID)
}
// getGenomeCountsSlow computes counts by scanning all variants (fallback for old data)
func getGenomeCountsSlow(accessorID, dossierID string) map[string]CategoryCount {
counts := make(map[string]CategoryCount)
// Create access context for RBAC
ctx := &lib.AccessContext{AccessorID: accessorID}
// Find extraction entry
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
if err != nil {
return counts
}
// Get all tiers
tiers, err := lib.GenomeGetTiers(ctx, dossierID, extraction.EntryID)
if err != nil {
return counts
}
// For each tier, count shown and hidden variants
for _, tier := range tiers {
variants, err := lib.GenomeGetVariantsByTier(ctx, dossierID, tier.TierID)
if err != nil {
continue
}
shown, hidden := 0, 0
for _, v := range variants {
data := variantData{
Mag: v.Magnitude,
Rep: v.Repute,
Sub: v.Subcategory,
}
if shouldIncludeVariant(data, false) {
shown++
} else {
hidden++
}
}
if shown > 0 || hidden > 0 {
counts[tier.Category] = CategoryCount{Shown: shown, Hidden: hidden}
}
}
return counts
}
func getGenomeSubcategoryCounts(accessorID, dossierID string, category string) map[string]CategoryCount {
counts := make(map[string]CategoryCount)
shownCounts := make(map[string]int)
hiddenCounts := make(map[string]int)
// Create access context for RBAC
ctx := &lib.AccessContext{AccessorID: accessorID}
// Find extraction entry
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
if err != nil {
return counts
}
// Find tier for this category
tier, err := lib.GenomeGetTierByCategory(ctx, dossierID, extraction.EntryID, category)
if err != nil {
return counts
}
// Get variants and count by subcategory
variants, err := lib.GenomeGetVariantsByTier(ctx, dossierID, tier.TierID)
if err != nil {
return counts
}
for _, v := range variants {
if v.Subcategory == "" {
continue
}
data := variantData{
Mag: v.Magnitude,
Rep: v.Repute,
Sub: v.Subcategory,
}
if shouldIncludeVariant(data, false) {
shownCounts[v.Subcategory]++
} else {
hiddenCounts[v.Subcategory]++
}
}
// Combine into CategoryCount
allSubs := make(map[string]bool)
for k := range shownCounts {
allSubs[k] = true
}
for k := range hiddenCounts {
allSubs[k] = true
}
for sub := range allSubs {
counts[sub] = CategoryCount{Shown: shownCounts[sub], Hidden: hiddenCounts[sub]}
}
return counts
}
// Unused but keeping for compatibility - remove if not needed
var _ = strings.Contains