package location import ( "context" "fmt" "log/slog" "sync" "time" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/config" "github.com/AFASystems/presence/internal/pkg/kafkaclient" "github.com/AFASystems/presence/internal/pkg/logger" pkglocation "github.com/AFASystems/presence/internal/pkg/location" "github.com/AFASystems/presence/internal/pkg/model" ) // LocationApp holds dependencies for the location service. type LocationApp struct { Cfg *config.Config KafkaManager *kafkaclient.KafkaManager AppState *appcontext.AppState Inferencer pkglocation.Inferencer ChRaw chan model.BeaconAdvertisement ChSettings chan map[string]any Cleanup func() wg sync.WaitGroup } // New creates a LocationApp with Kafka readers (rawbeacons, settings) and writer (locevents). func New(cfg *config.Config) (*LocationApp, error) { appState := appcontext.NewAppState() kafkaManager := kafkaclient.InitKafkaManager() srvLogger, cleanup := logger.CreateLogger("location.log") slog.SetDefault(srvLogger) readerTopics := []string{"rawbeacons", "settings"} writerTopics := []string{"locevents"} kafkaManager.PopulateKafkaManager(cfg.KafkaURL, "location", readerTopics) kafkaManager.PopulateKafkaManager(cfg.KafkaURL, "", writerTopics) slog.Info("location service initialized", "readers", readerTopics, "writers", writerTopics) return &LocationApp{ Cfg: cfg, KafkaManager: kafkaManager, AppState: appState, Inferencer: pkglocation.NewDefaultInferencer(cfg.TLSInsecureSkipVerify), ChRaw: make(chan model.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE), ChSettings: make(chan map[string]any, config.SMALL_CHANNEL_SIZE), Cleanup: cleanup, }, nil } // Run starts consumers and the event loop until ctx is cancelled. func (a *LocationApp) Run(ctx context.Context) { a.wg.Add(2) go kafkaclient.Consume(a.KafkaManager.GetReader("rawbeacons"), a.ChRaw, ctx, &a.wg) go kafkaclient.Consume(a.KafkaManager.GetReader("settings"), a.ChSettings, ctx, &a.wg) locTicker := time.NewTicker(config.SMALL_TICKER_INTERVAL) defer locTicker.Stop() for { select { case <-ctx.Done(): return case <-locTicker.C: settings := a.AppState.GetSettings() slog.Info("location tick", "settings", fmt.Sprintf("%+v", settings)) switch settings.CurrentAlgorithm { case "filter": pkglocation.GetLikelyLocations(a.AppState, a.KafkaManager.GetWriter("locevents")) case "ai": inferred, err := a.Inferencer.Infer(ctx, a.Cfg) if err != nil { slog.Error("AI inference", "err", err) continue } slog.Info("AI algorithm", "count", inferred.Count, "items", len(inferred.Items)) } case msg := <-a.ChRaw: pkglocation.AssignBeaconToList(msg, a.AppState) case msg := <-a.ChSettings: slog.Info("settings update", "msg", msg) a.AppState.UpdateSettings(msg) } } } // Shutdown waits for consumers and cleans up Kafka and logger. func (a *LocationApp) Shutdown() { a.wg.Wait() a.KafkaManager.CleanKafkaReaders() a.KafkaManager.CleanKafkaWriters() if a.Cleanup != nil { a.Cleanup() } slog.Info("location service shutdown complete") }