/// <summary> /// Meets global criteria is a criteria that must be met in order to notify /// no matter delivery channel /// </summary> /// <param name="feed"></param> /// <returns></returns> public bool MeetsGlobalCriteria(CityFeed feed) { bool isValid = true; // default to "valid" as if no rules are applied _globalRules.ForEach(rule => isValid &= rule.Invoke(feed)); return(isValid); }
// TODO: needs strong/strict validation public static string GetTwitterMessageSpanish(CityFeed feed) { var charactersLeft = TwitterCharLimit; var attributions = from attr in feed.MaxAqiStation.Attributions let attributionMsg = attr.GetNameOrDefault() select attributionMsg; var encodedText = string.Concat("Fuente:\n", attributions.Aggregate((prev, current) => string.Concat(prev, "\n", current))); var attrText = HttpUtility.HtmlDecode(encodedText); // how many characters do we have left? charactersLeft -= attrText.Length; var pascalCity = new StringBuilder().Append(char.ToUpper(feed.CityName[0])) .Append(feed.CityName.Substring(1)) .ToString(); var followText = $"#aireEn{pascalCity}"; if (charactersLeft < 200) { var custom = getQuality(feed.MaxAQI) == Quality.Good ? "Es momento de respirar" : "Mantenga sus precauciones"; return($"Calidad del aire en {pascalCity} es {getScaleSpanish(feed.MaxAQI)}\nIndice AQI:{feed.MaxAQI} segun Estacion \"{feed.MaxAqiStation.Name}\")\n\n{attrText}\n{followText}"); } // attempt to send this message return($"La calidad del aire en {pascalCity} es {feed.MaxAQI} (estacion {feed.MaxAqiStation.Name})\n{attrText}\n{followText}"); }
public async Task<string> AsyncExecute(string input, ILambdaContext context) { var waqiProxy = Waqi.create("sometoken"); // add token var feeds = new List<string>(); var stationFeedsObtained = new List<WaqiCityFeed>(); feeds.AddRange(new string[]{@"mexico/guadalajara/tlaquepaque" , @"mexico/guadalajara/vallarta" , @"mexico/guadalajara/miravalle" }); foreach (var feed in feeds) { System.Console.WriteLine($"Requesting feed for:{feed}"); string mutableString = string.Empty; try { mutableString = await waqiProxy.getCityFeed(feed); if(mutableString != string.Empty) { System.Console.WriteLine("found this result:\n"); System.Console.WriteLine(mutableString); var wcf = JsonSerializer.Deserialize<WaqiCityFeed>(mutableString); stationFeedsObtained.Add(wcf); } } catch(Exception e) { System.Console.WriteLine(e);} } var cityFeedDto = CityFeed.From(stationFeedsObtained); return JsonSerializer.Serialize(cityFeedDto).ToString(); //System.Console.WriteLine(JsonSerializer.Serialize(cityFeedDto).ToString()); }
public void ShouldDetectMaxAqiFromMultipleStations() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); Assert.Equal(MaxAqi, cityFeed.MaxAQI); }
public void ShouldDetectAndFindBestCityName() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); Assert.Equal(cityFeed.CityName, GuadalajaraCity); }
public static string GetSimpleMessage(CityFeed feed) { var attributionText = (from attr in feed.MaxAqiStation.Attributions let attributionMsg = attr.ToString() select attributionMsg) .Aggregate((prev, current) => string.Concat(prev, "\n", current)); return($"En {feed.CityName} calidad del aire es {getScaleSpanish(feed.MaxAQI)}(Indice Calidad:{feed.MaxAQI}) reportado por la estacion {feed.MaxAqiStation.Name})\n{attributionText}"); }
public void ShouldGetMaxAqiStation() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); // max aqi station is set on constructor, currently is Tlaquepaque Assert.Equal(TlaquepaqueStationId, cityFeed.MaxAqiStation.Id); }
public static Predicate <CityFeed> AbsoluteAqiChangedBy(CityFeed prevFeed, int absoluteDelta) { return(feed => { if (prevFeed == null) { return true; } return Math.Abs(prevFeed.MaxAQI - feed.MaxAQI) > absoluteDelta; }); }
public void ShouldCreateCityFeedFromExternalJson() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); Assert.NotNull(cityFeed); Assert.NotNull(cityFeed.CityName); Assert.NotEmpty(cityFeed.Stations); }
public void ShouldContainAllStations() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); Assert.NotNull(cityFeed); Assert.NotNull(cityFeed.CityName); Assert.Equal(GuadalajaraCityStations.Count, cityFeed.Stations.Count); }
public static Predicate <CityFeed> MinutesApartFrom(CityFeed prevFeed, int minutes) { return(feed => { if (prevFeed == null) { return true; } return prevFeed.UpdatedAt.AddMinutes(minutes).CompareTo(feed) < 0; }); }
public void ShouldParseCityFeedWithInvalidAqiValue() { var waqiStations = new List <WaqiCityFeed>(); waqiStations.Add(Values.Waqi.CreateWaqiCityFeedResponseWithInvalidAqi(GuadalajaraCity, FailingStation, int.Parse(FailingStationId))); var cityFeed = CityFeed.From(waqiStations); Assert.NotNull(cityFeed); Assert.NotNull(cityFeed.CityName); Assert.NotEmpty(cityFeed.Stations); }
/// <summary> /// Creates Document from a cityFeed but also accepts a previous Max aqui value /// to create a Delta /// delta is negative if aqi is less than previous /// </summary> /// <param name="cityFeed"></param> /// <param name="previousMaxAqi"></param> /// <returns></returns> public static Document From(CityFeed cityFeed, int previousMaxAqi) { var doc = new Document(); // NOTE: default country is only MX as this is the only supported use case, however leaving room for scaling in format doc.Add(PartitionKeyName, $"{_defaultCountry}-{cityFeed.CityName}"); doc.Add(FieldMaxStationName, $"{cityFeed.MaxAqiStation.Id}-{cityFeed.MaxAqiStation.Name}"); doc.Add(FieldMaxAqi, cityFeed.MaxAQI); doc.Add(FieldDeltaAqi, cityFeed.MaxAQI - previousMaxAqi); // delta is null by default doc.Add(JsonValue, JsonSerializer.Serialize(cityFeed)); doc.Add(FieldUpdatedTime, cityFeed.UpdatedAtText); return(doc); }
public void ShouldHaveAtLeastOneAttribution() { var waqiStations = GuadalajaraCityStations; var cityFeed = CityFeed.From(waqiStations); // max aqi station is set on constructor, currently is Tlaquepaque Assert.NotEmpty(cityFeed.MaxAqiStation.Attributions); // attributions by default contain Waqi's URL, we should expect "n = waqiStations.Length" matches var attributions = from station in cityFeed.Stations from attribution in station.Attributions where attribution.Url.Contains(Waqi.WaqiAttributionUrl) select attribution; Assert.Equal(attributions.Count(), waqiStations.Count()); }
public async Task <string> AsyncExecute(string input, ILambdaContext context) { var waqiProxy = Waqi.create(_token); var cities = JsonSerializer.Deserialize <List <Latincoder.AirQuality.Model.Config.City> >(_citiesRaw); // default C# serialization // process multiple cities var stationFeedsByCity = from city in cities from station in city.Stations let feed = new { Uri = $"{city.Country}/{city.Name}/{station}", CityName = city.Name } group feed by $"{city.Country}-{city.Name}" into stationsByCity select stationsByCity; var cityFeedsDTO = new List <CityFeed>(); foreach (var cityFeeds in stationFeedsByCity) { var feeds = from value in cityFeeds select value.Uri; var stationFeedsObtained = new List <WaqiCityFeed>(); foreach (var feed in feeds) { System.Console.WriteLine($"Requesting feed for:{feed}"); string mutableString = string.Empty; try { mutableString = await waqiProxy.getCityFeed(feed); if (mutableString != string.Empty) { System.Console.WriteLine("found this result:\n"); System.Console.WriteLine(mutableString); var wcf = JsonSerializer.Deserialize <WaqiCityFeed>(mutableString); stationFeedsObtained.Add(wcf); } } catch (Exception e) { System.Console.WriteLine(e); } } try { cityFeedsDTO.Add(CityFeed.From(stationFeedsObtained)); } catch (Exception e) { System.Console.WriteLine(e); } } var response = await sendCityFeed(cityFeedsDTO); System.Console.WriteLine(response.ToString()); return("done"); }
/// <summary> /// A simple function that takes a string and does a ToUpper /// </summary> /// <param name="input"></param> /// <param name="context"></param> /// <returns></returns> public async Task <string> FunctionHandler(DynamoDBEvent dynamoDBEvent, ILambdaContext context) { snsClient = new AmazonSimpleNotificationServiceClient(); foreach (var record in dynamoDBEvent.Records) { var dynamoDocument = Document.FromAttributeMap(record.Dynamodb.NewImage); CityFeed feed = CityFeedDocument.ToDTO(dynamoDocument); if (feed == null) { System.Console.WriteLine("Streamed had no new data"); continue; } System.Console.WriteLine($"Feed Object:{feed}"); System.Console.WriteLine($"Accessed stream: {feed.CityName}"); var msg = NotificationFormatter.GetTwitterMessageSpanish(feed); await publishToGeneral(NotificationFormatter.GetSimpleMessage(feed)); await publishToTwitter(msg); } return(""); }
public static bool AqiAboveUnhealthyForSensitiveGroupsThreshold(CityFeed feed) { return(feed.MaxAQI >= 101); }
public static bool AqiAboveModerateThreshold(CityFeed feed) { return(feed.MaxAQI >= 51); }
public static bool FeedWithinLast10Minutes(CityFeed feed) { return(feed.UpdatedAt.AddMinutes(10).CompareTo(DateTime.Now) > 0); }
public async Task <string> AsyncExecute(System.IO.Stream input, ILambdaContext context) { _ = input; // discard var waqiProxy = Waqi.create(_token); var cities = JsonSerializer.Deserialize <List <Latincoder.AirQuality.Model.Config.City> >(_citiesRaw); // default C# serialization // process multiple cities var stationFeedsByCity = from city in cities from station in city.Stations let feed = new { Uri = $"{city.Country}/{city.Name}/{station}", CityName = city.Name } group feed by $"{city.Country}-{city.Name}" into stationsByCity select stationsByCity; var cityFeedsDTO = new List <CityFeed>(); foreach (var cityFeeds in stationFeedsByCity) { var feeds = from value in cityFeeds select value.Uri; var stationFeedsObtained = new List <WaqiCityFeed>(); foreach (var feed in feeds) { System.Console.WriteLine($"Requesting feed for:{feed}"); string mutableString = string.Empty; try { mutableString = await waqiProxy.getCityFeed(feed); if (mutableString != string.Empty) { System.Console.WriteLine("found this result:\n"); System.Console.WriteLine(mutableString); var wcf = JsonSerializer.Deserialize <WaqiCityFeed>(mutableString); stationFeedsObtained.Add(wcf); } } catch (Exception e) { System.Console.WriteLine(e); } } try { cityFeedsDTO.Add(CityFeed.From(stationFeedsObtained)); } catch (Exception e) { System.Console.WriteLine(e); } } var aqiTable = Table.LoadTable(_dynamoClient, _AqiTableName); var notificationIsUrgent = NotificationValidator.CreateyValidator(); notificationIsUrgent.AddRules(Rules.AqiAboveUnhealthyThreshold); // store in DynamoDB if meets notification criteria foreach (var feed in cityFeedsDTO) { // Gather previous and new feeds var prevFeedDoc = await aqiTable.GetItemAsync(CityFeedDocument.PartitionKeyName); var newFeed = prevFeedDoc != null? CityFeedDocument.From(feed, prevFeedDoc[CityFeedDocument.FieldMaxAqi].AsInt()) : CityFeedDocument.From(feed); // if no previous stuff if (prevFeedDoc == null) { Console.WriteLine($"FIRST TIME: Store in dynamo now for {feed.CityName}"); await aqiTable.PutItemAsync(newFeed); continue; } var prevFeed = CityFeedDocument.ToDTO(prevFeedDoc); // each feed will has a custom validator with it's own criteria since this is done per city var notificationIsNotSpam = NotificationValidator.CreateyValidator(); // spam means max aqi changed at least by 5 points, and is at least 20 minutes apart frm last notification notificationIsNotSpam.AddRules( Rules.MinutesApartFrom(prevFeed, 30), Rules.AbsoluteAqiChangedBy(prevFeed, 5)); // urgent or meets criteria if (notificationIsUrgent.MeetsGlobalCriteria(feed) && notificationIsNotSpam.MeetsGlobalCriteria(feed)) { Console.WriteLine($"Store in dynamo now for {feed.CityName}"); await aqiTable.PutItemAsync(newFeed); } else { Console.WriteLine("Does not meet rules..."); } } return("done"); }
public static Predicate <CityFeed> AqiBecameGoodFromModerateOrWorseWithDelta(CityFeed feed, int delta) { return(feed => feed.MaxAQI <= 50 && delta < 0); }
public static bool AqiAboveHazardousThreshold(CityFeed feed) { return(feed.MaxAQI >= 151); }
public static bool AqiAboveUnhealthyThreshold(CityFeed feed) { return(feed.MaxAQI >= 151); }