public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

            //Configurable variables for Cosmos Query
            var minPartitionId  = DateTime.Now.ToString("yyyy-MM-dd HH");
            var whereVariable   = ".Type";
            var orderByVariable = "._ts";

            var queryBuilder = new StringBuilder();

            queryBuilder.Append("SELECT Top 1* FROM ");
            queryBuilder.Append(cosmosDoc).Append(" WHERE ").Append(cosmosDoc).Append(whereVariable).Append(" = \'balloon\'");
            queryBuilder.Append(" ORDER BY ").Append(cosmosDoc).Append(orderByVariable).Append(" DESC");

            log.LogInformation($"Query text: {queryBuilder.ToString()}");

            var client = new DocumentClient(new Uri(cosmosUrl), cosmosKey);

            FeedOptions feedOptions = new FeedOptions()
            {
                EnableCrossPartitionQuery = true
            };
            //Get latest balloon message from Cosmos
            var balloonDocument =
                client.CreateDocumentQuery <BalloonDocument>(UriFactory.CreateDocumentCollectionUri(cosmosDB, cosmosDoc),
                                                             queryBuilder.ToString(), feedOptions).AsEnumerable().FirstOrDefault();

            log.LogInformation($"Balloon state: {balloonDocument.State}");

            if (balloonDocument.TimestampUTC.AddMinutes(5) < DateTime.UtcNow)
            {
                log.LogInformation("No new information. Recent balloon time: " + balloonDocument.TimestampUTC.AddHours(-7).ToShortTimeString());
                return;
            }

            if (balloonDocument.State == BalloonState.Landed)
            {
                log.LogInformation($"Balloon not in the air, no prediction calculated");

                // TEST CODE

                var fakePrediction = new PredictionDocument()
                {
                    FlightId        = balloonDocument.FlightId,
                    TrackerSource   = balloonDocument.TrackerSource,
                    Geopoint        = new Point(balloonDocument.Longitude, balloonDocument.Latitude),
                    Latitude        = balloonDocument.Latitude,
                    Longitude       = balloonDocument.Longitude,
                    partitionid     = balloonDocument.partitionid,
                    LandingDateTime = DateTime.UtcNow
                };

                var predictionNotification = new PredictionNotification(fakePrediction, balloonDocument.Latitude, balloonDocument.Longitude);

                SendPredictionNotification(predictionNotification);
                return;
            }

            if (balloonDocument.AveAscent <= 5)
            {
                // climb rate of less than 1 is a invalid in habhub
                log.LogInformation($"Balloon Ave Ascent rate too low ({balloonDocument.AveAscent}), adjusted to 5 m/s");
                balloonDocument.AveAscent = 5;
            }

            if (balloonDocument.BurstAltitude > 30000)
            {
                log.LogInformation("Balloon burst set too high, forcing to 30,000 m.");
                balloonDocument.BurstAltitude = 30000;
            }

            if ((balloonDocument.State == BalloonState.Rising) || (balloonDocument.State == BalloonState.PreLaunch))
            {
                try
                {
                    PredictionEngine predictionEngine = new PredictionEngine();
                    var predictionDocument            = predictionEngine.Generate(balloonDocument, log);

                    // enrich prediction document
                    if (predictionDocument != null)
                    {
                        predictionDocument.FlightId      = balloonDocument.FlightId;
                        predictionDocument.TrackerSource = balloonDocument.TrackerSource;
                        predictionDocument.Geopoint      = new Point(predictionDocument.Longitude, predictionDocument.Latitude);
                        predictionDocument.partitionid   = balloonDocument.partitionid;

                        WriteDocument(predictionDocument).Wait();

                        try
                        {
                            var predictionNotification = new PredictionNotification(predictionDocument, balloonDocument.Latitude, balloonDocument.Longitude);

                            SendPredictionNotification(predictionNotification);
                        }
                        catch (Exception ex)
                        {
                            log.LogError(ex, "Failed to send prediction to notification function.");
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.LogError(ex, $"Failed to generate prediction: {ex.Message}");
                }
            }
            else if (balloonDocument.State == BalloonState.Falling)
            {
                // can't create a new prediction will falling
                var lastPredictionDocument = GetLastPrediction(balloonDocument.FlightId);
                if (lastPredictionDocument != null)
                {
                    lastPredictionDocument.DistanceToLanding = 10; // todo

                    var notification = new PredictionNotification(lastPredictionDocument, balloonDocument.Latitude, balloonDocument.Longitude);

                    // notify of balloon location
                    SendPredictionNotification(notification);
                }
            }
        }