package main import ( "fmt" "strings" ) // AnalyticsFilters represents the filter state for analytics queries type AnalyticsFilters struct { KillHour []uint8 KillDayOfWeek []uint8 KillDate []string Month []string SolarSystemID []int32 RegionName []string ConstellationName []string SecurityStatus []string VictimShipTypeID []int32 VictimShipGroupName []string VictimShipCategory []string VictimCharacterName []string VictimCorporation []string VictimAlliance []string AttackerShipType []string AttackerShipGroup []string AttackerCharacter []string AttackerCorporation []string AttackerAlliance []string SlotType []string HasModule *ModuleFilter } type ModuleFilter struct { ModuleID int32 } type FlatKillmailComplete struct { FlatKillmail Items []FlatKillmailItem `json:"items"` } // Time aggregation results type TimeAggregationByHour struct { KillHour uint8 `json:"kill_hour"` KillCount uint64 `json:"kill_count"` UniqueVictims uint64 `json:"unique_victims"` SystemsHit uint64 `json:"systems_hit"` } type TimeAggregationByDay struct { KillDayOfWeek uint8 `json:"kill_day_of_week"` DayName string `json:"day_name"` KillCount uint64 `json:"kill_count"` UniqueVictims uint64 `json:"unique_victims"` } type TimeAggregationByDate struct { KillDate string `json:"kill_date"` KillCount uint64 `json:"kill_count"` UniqueVictims uint64 `json:"unique_victims"` SystemsHit uint64 `json:"systems_hit"` } type TimeAggregationByMonth struct { Month string `json:"month"` KillCount uint64 `json:"kill_count"` UniqueVictims uint64 `json:"unique_victims"` } // Location aggregation results type LocationAggregationBySystem struct { SolarSystemID int32 `json:"solar_system_id"` SolarSystemName string `json:"solar_system_name"` RegionName string `json:"region_name"` Security float32 `json:"security"` KillCount uint64 `json:"kill_count"` UniqueVictims uint64 `json:"unique_victims"` ShipVariety uint64 `json:"ship_variety"` } type LocationAggregationByRegion struct { RegionName string `json:"region_name"` KillCount uint64 `json:"kill_count"` SystemsWithKills uint64 `json:"systems_with_kills"` UniqueVictims uint64 `json:"unique_victims"` } type LocationAggregationByConstellation struct { ConstellationName string `json:"constellation_name"` RegionName string `json:"region_name"` KillCount uint64 `json:"kill_count"` SystemsInConstellation uint64 `json:"systems_in_constellation"` } type LocationAggregationBySecurity struct { SecStatus string `json:"sec_status"` KillCount uint64 `json:"kill_count"` Systems uint64 `json:"systems"` } // Ship aggregation results type ShipAggregationByVictimShip struct { ShipTypeID int32 `json:"victim_ship_type_id"` ShipTypeName string `json:"victim_ship_type_name"` ShipGroupName string `json:"victim_ship_group_name"` ShipCategoryName string `json:"victim_ship_category_name"` KillCount uint64 `json:"kill_count"` UniquePilots uint64 `json:"unique_pilots_killed"` } type ShipAggregationByAttackerShip struct { ShipTypeName string `json:"ship_type_name"` ShipGroupName string `json:"ship_group_name"` TimesUsed uint64 `json:"times_used"` KillsParticipated uint64 `json:"kills_participated"` TotalDamage int64 `json:"total_damage"` FinalBlows uint64 `json:"final_blows"` } // Player/Entity aggregation results type PlayerAggregationByVictimCharacter struct { CharacterName string `json:"victim_character_name"` Corporation string `json:"victim_corporation_name"` Alliance string `json:"victim_alliance_name"` Deaths uint64 `json:"deaths"` ShipsLost uint64 `json:"ships_lost"` } type PlayerAggregationByVictimCorporation struct { Corporation string `json:"victim_corporation_name"` Alliance string `json:"victim_alliance_name"` Losses uint64 `json:"losses"` MembersKilled uint64 `json:"members_killed"` } type PlayerAggregationByVictimAlliance struct { Alliance string `json:"victim_alliance_name"` Losses uint64 `json:"losses"` CorpsHit uint64 `json:"corps_hit"` MembersKilled uint64 `json:"members_killed"` } type PlayerAggregationByAttackerCharacter struct { CharacterName string `json:"character_name"` Corporation string `json:"corporation_name"` Alliance string `json:"alliance_name"` KillsParticipated uint64 `json:"kills_participated"` FinalBlows uint64 `json:"final_blows"` TotalDamage int64 `json:"total_damage"` } type PlayerAggregationByAttackerCorporation struct { Corporation string `json:"corporation_name"` Alliance string `json:"alliance_name"` KillsParticipated uint64 `json:"kills_participated"` MembersInvolved uint64 `json:"members_involved"` } type PlayerAggregationByAttackerAlliance struct { Alliance string `json:"alliance_name"` KillsParticipated uint64 `json:"kills_participated"` CorpsInvolved uint64 `json:"corps_involved"` MembersInvolved uint64 `json:"members_involved"` } // Module/Item aggregation results type ModuleAggregationBySlotType struct { SlotType string `json:"slot_type"` ItemsFitted uint64 `json:"items_fitted"` ShipsWithSlot uint64 `json:"ships_with_slot"` ModuleVariety uint64 `json:"module_variety"` } type ModuleAggregationByModule struct { ItemTypeID int32 `json:"item_type_id"` ItemTypeName string `json:"item_type_name"` ItemGroupName string `json:"item_group_name"` ItemCategoryName string `json:"item_category_name"` TimesFitted uint64 `json:"times_fitted"` ShipsWithModule uint64 `json:"ships_with_module"` } type ModuleCoOccurrence struct { ItemTypeName string `json:"item_type_name"` SlotType string `json:"slot_type"` TimesTogether uint64 `json:"times_fitted_together"` Percentage float64 `json:"percentage"` } // buildWhereClause builds a WHERE clause from filters func buildWhereClause(filters AnalyticsFilters) (string, []interface{}) { var conditions []string var args []interface{} if len(filters.KillHour) > 0 { placeholders := make([]string, len(filters.KillHour)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "kill_hour IN ("+strings.Join(placeholders, ",")+")") for _, h := range filters.KillHour { args = append(args, h) } } if len(filters.KillDayOfWeek) > 0 { placeholders := make([]string, len(filters.KillDayOfWeek)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "kill_day_of_week IN ("+strings.Join(placeholders, ",")+")") for _, d := range filters.KillDayOfWeek { args = append(args, d) } } if len(filters.KillDate) > 0 { placeholders := make([]string, len(filters.KillDate)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "kill_date IN ("+strings.Join(placeholders, ",")+")") for _, d := range filters.KillDate { args = append(args, d) } } if len(filters.Month) > 0 { placeholders := make([]string, len(filters.Month)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "toYYYYMM(killmail_time) IN ("+strings.Join(placeholders, ",")+")") for _, m := range filters.Month { args = append(args, m) } } if len(filters.SolarSystemID) > 0 { placeholders := make([]string, len(filters.SolarSystemID)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "solar_system_id IN ("+strings.Join(placeholders, ",")+")") for _, id := range filters.SolarSystemID { args = append(args, id) } } if len(filters.RegionName) > 0 { placeholders := make([]string, len(filters.RegionName)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "region_name IN ("+strings.Join(placeholders, ",")+")") for _, r := range filters.RegionName { args = append(args, r) } } if len(filters.ConstellationName) > 0 { placeholders := make([]string, len(filters.ConstellationName)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "constellation_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.ConstellationName { args = append(args, c) } } if len(filters.SecurityStatus) > 0 { var secConditions []string for _, s := range filters.SecurityStatus { switch s { case "High Sec": secConditions = append(secConditions, "security >= 0.5") case "Low Sec": secConditions = append(secConditions, "security > 0.0 AND security < 0.5") case "Null Sec": secConditions = append(secConditions, "security <= 0.0") } } if len(secConditions) > 0 { conditions = append(conditions, "("+strings.Join(secConditions, " OR ")+")") } } if len(filters.VictimShipTypeID) > 0 { placeholders := make([]string, len(filters.VictimShipTypeID)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_ship_type_id IN ("+strings.Join(placeholders, ",")+")") for _, id := range filters.VictimShipTypeID { args = append(args, id) } } if len(filters.VictimShipGroupName) > 0 { placeholders := make([]string, len(filters.VictimShipGroupName)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_ship_group_name IN ("+strings.Join(placeholders, ",")+")") for _, g := range filters.VictimShipGroupName { args = append(args, g) } } if len(filters.VictimShipCategory) > 0 { placeholders := make([]string, len(filters.VictimShipCategory)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_ship_category_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.VictimShipCategory { args = append(args, c) } } if len(filters.VictimCharacterName) > 0 { placeholders := make([]string, len(filters.VictimCharacterName)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_character_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.VictimCharacterName { args = append(args, c) } } if len(filters.VictimCorporation) > 0 { placeholders := make([]string, len(filters.VictimCorporation)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_corporation_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.VictimCorporation { args = append(args, c) } } if len(filters.VictimAlliance) > 0 { placeholders := make([]string, len(filters.VictimAlliance)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "victim_alliance_name IN ("+strings.Join(placeholders, ",")+")") for _, a := range filters.VictimAlliance { args = append(args, a) } } if filters.HasModule != nil { subquery := `SELECT DISTINCT killmail_id FROM zkill.killmail_items WHERE item_type_id = ? AND slot_type != 'cargo'` conditions = append(conditions, fmt.Sprintf("killmail_id IN (%s)", subquery)) args = append(args, filters.HasModule.ModuleID) } whereClause := "" if len(conditions) > 0 { whereClause = "WHERE " + strings.Join(conditions, " AND ") } return whereClause, args } // buildAttackerWhereClause builds WHERE clause for attacker queries func buildAttackerWhereClause(filters AnalyticsFilters) (string, []interface{}) { var conditions []string var args []interface{} if len(filters.AttackerShipType) > 0 { placeholders := make([]string, len(filters.AttackerShipType)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "ship_type_name IN ("+strings.Join(placeholders, ",")+")") for _, s := range filters.AttackerShipType { args = append(args, s) } } if len(filters.AttackerShipGroup) > 0 { placeholders := make([]string, len(filters.AttackerShipGroup)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "ship_group_name IN ("+strings.Join(placeholders, ",")+")") for _, g := range filters.AttackerShipGroup { args = append(args, g) } } if len(filters.AttackerCharacter) > 0 { placeholders := make([]string, len(filters.AttackerCharacter)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "character_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.AttackerCharacter { args = append(args, c) } } if len(filters.AttackerCorporation) > 0 { placeholders := make([]string, len(filters.AttackerCorporation)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "corporation_name IN ("+strings.Join(placeholders, ",")+")") for _, c := range filters.AttackerCorporation { args = append(args, c) } } if len(filters.AttackerAlliance) > 0 { placeholders := make([]string, len(filters.AttackerAlliance)) for i := range placeholders { placeholders[i] = "?" } conditions = append(conditions, "alliance_name IN ("+strings.Join(placeholders, ",")+")") for _, a := range filters.AttackerAlliance { args = append(args, a) } } whereClause := "" if len(conditions) > 0 { whereClause = "AND " + strings.Join(conditions, " AND ") } return whereClause, args } // getKillmailIDSubquery returns a subquery to filter by killmail_id from killmails table func getKillmailIDSubquery(filters AnalyticsFilters) (string, []interface{}) { whereClause, args := buildWhereClause(filters) if whereClause == "" { return "(SELECT killmail_id FROM zkill.killmails)", args } return fmt.Sprintf("(SELECT killmail_id FROM zkill.killmails %s)", whereClause), args }