def maybeTweetMaxSince(city, valByYear, field, monthDate, monthStr, maxSince,
                       cumulative):
    if maxSince is None:
        return
    if monthDate.year - maxSince < 8:
        return
    if len(
            tuple(
                filter(lambda t: t in range(maxSince, monthDate.year),
                       valByYear.keys()))) < 8:
        return
    thisYearVal = valByYear[monthDate.year]
    cityName = stations.city[city].name

    monthAdjective = fieldAdjective(field)[0]
    amount = formatWithUnits(thisYearVal, field)
    aggregate = "Total" if cumulative else "Average"
    #import pudb; pu.db
    tweet = ('#{cityName} just had its {monthAdjective} {monthStr}'
             ' since {maxSince}. {aggregate} {field.englishName} was {amount}.'
             .format(**locals()))
    if field.units == 'days':
        tweet = ('#{cityName} had 7 {monthStr} {field.englishName} days;'
                 ' more than any year since {maxSince}.'.format(**locals()))

    #pprint.PrettyPrinter().pprint(valByYear)
    #print(tweet)
    #input()
    alertTweets.maybeTweetWithSuffix(city, tweet)
def tweetTopN(city, db, field, thisYear):
    top10 = {
        1: '❶',
        2: '❷',
        3: '❸',
        4: '❹',
        5: '❺',
        6: '❻',
        7: '❼',
        8: '❽',
        9: '❾',
        10: '❿',
    }
    top10Double = {
        1: '⓵',
        2: '⓶',
        3: '⓷',
        4: '⓸',
        5: '⓹',
        6: '⓺',
        7: '⓻',
        8: '⓼',
        9: '⓽',
        10: '⓾'
    }
    place = 1
    totalCount = sum(len(a) for a in db.values())
    adjective = fieldAdjective(field)[0]
    description = fieldDescription(field)
    cityName = stations.city[city].name
    units = field.units
    tweetText = (
        '#{cityName}\'s 5 {adjective} years (by {description}):'.format(
            **locals()))
    for key in reversed(sorted(db.keys())):
        count = place
        for year in reversed(sorted(db[key])):
            nth = top10[place]
            if year == thisYear:
                nth = top10Double.get(place, '*')
            if count > 5:
                break
            val = key
            tweetText += "\n{nth} {year}: {val} {units}".format(**locals())
            count += 1
        place += len(db[key])
    alertTweets.maybeTweetWithSuffix(city, tweetText)
def maybeTweetMinSince(city, valByYear, field, monthDate, monthStr, minSince,
                       cumulative):
    if minSince is None:
        return
    if monthDate.year - minSince < 8:
        return
    thisYearVal = valByYear[monthDate.year]
    cityName = stations.city[city].name

    monthAdjective = fieldAdjective(field)[1]
    amount = formatWithUnits(thisYearVal, field)
    aggregate = "Total" if cumulative else "Average"
    #import pudb; pu.db
    tweet = (
        '#{cityName} just had its {monthAdjective} {monthStr}'
        ' since {minSince}. {aggregate} {field.englishName} was just {amount}.'
        .format(**locals()))
    alertTweets.maybeTweetWithSuffix(city, tweet)
def main(city, year):
    data = daily.load(city)
    cold = ValWithDate(+999, None)
    heat = ValWithDate(-999, None)
    snow = ValWithDate(0, None)
    rain = ValWithDate(0, None)
    wind = ValWithDate(0, None)

    for day, weather in data.items():
        if day.year != year:
            continue
        #import pudb; pu.db
        if (weather.MAX_TEMP is not None and weather.MAX_TEMP > heat.val):
            heat = ValWithDate(weather.MAX_TEMP, day)
        if (weather.MIN_TEMP is not None and weather.MIN_TEMP < cold.val):
            cold = ValWithDate(weather.MIN_TEMP, day)
        if (weather.TOTAL_SNOW_CM is not None
                and weather.TOTAL_SNOW_CM > snow.val):
            snow = ValWithDate(weather.TOTAL_SNOW_CM, day)
        if (weather.TOTAL_RAIN_MM is not None
                and weather.TOTAL_RAIN_MM > rain.val):
            rain = ValWithDate(weather.TOTAL_RAIN_MM, day)
        if (weather.SPD_OF_MAX_GUST_KPH is not None
                and weather.SPD_OF_MAX_GUST_KPH > wind.val):
            wind = ValWithDate(weather.SPD_OF_MAX_GUST_KPH, day)

    cityName = stations.city[city].name
    tweet = '#{cityName} 2016 extremes:'.format(**locals())
    for name, u in (('heat', '℃'), ('cold', '℃'), ('snow', 'cm'),
                    ('rain', 'mm'), ('wind', 'km/h')):
        if locals()[name].date is None:
            continue
        tweet += ('\n{}: {}{} ({})'.format(
            name,
            locals()[name].val, u,
            locals()[name].date.strftime('%b %d')))
    alertTweets.maybeTweetWithSuffix(city, tweet)
def tweetTopN(city, db, monthStr, field, thisYear, daysLeftThisYear):
    #import pudb; pu.db
    top10 = {
        1: '❶',
        2: '❷',
        3: '❸',
        4: '❹',
        5: '❺',
        6: '❻',
        7: '❼',
        8: '❽',
        9: '❾',
        10: '❿',
    }
    top10Double = {
        1: '⓵',
        2: '⓶',
        3: '⓷',
        4: '⓸',
        5: '⓹',
        6: '⓺',
        7: '⓻',
        8: '⓼',
        9: '⓽',
        10: '⓾'
    }
    place = 1
    totalCount = sum(len(a) for a in db.values())
    adjective = fieldAdjective(field)[0]
    description = fieldDescription(field)
    cityName = stations.city[city].name
    units = field.units
    tweetText = '#{cityName}\'s {totalCount} {adjective} {monthStr}s'.format(
        **locals())
    if description is not None:
        tweetText += '(by {description})'.format(**locals())
    for key in reversed(sorted(db.keys())):
        for year in reversed(sorted(db[key])):
            #val = '{:.{}f}'.format(key, field.precision+1)
            #print(field.precision, D('.'+'0'*(field.precision)+'1'))
            #val = key.quantize(D('.'+'0'*(field.precision)+'1'))
            val = key
            nth = top10.get(place, '*')
            if year == thisYear:
                nth = top10Double.get(place, '*')
                if place == 1 and daysLeftThisYear <= 0:
                    tweetText = f'#{cityName} just had its {adjective} {monthStr} ever'
                    if description is not None:
                        tweetText += f' (by {description})'
            nextLine = "\n{nth} {year}: {val}{units}".format(**locals())
            if year == thisYear and daysLeftThisYear > 0:
                if daysLeftThisYear > 1:
                    nextLine += ' ({daysLeftThisYear} days left)'.format(
                        **locals())
                else:
                    nextLine += ' (1 day left)'.format(**locals())
            if len(tweetText) + len(nextLine) > 140:
                tweetText += '...'
                break
            tweetText += nextLine
        place += len(db[key])
    #print(tweetText); input()
    alertTweets.maybeTweetWithSuffix(city, tweetText)
def main(city, args, lastCheckedValue=None, today=None):
    #import pdb; pdb.set_trace()
    monthlyAverages.cityData = daily.load(city)
    if today is None:
        today = daily.timeByCity[city].date()
    monthDate = today - datetime.timedelta(7)
    nextMonthStart = datetime.date(
        monthDate.year if monthDate.month < 12 else monthDate.year + 1,
        monthDate.month + 1 if monthDate.month < 12 else 1, 1)
    daysLeft = (nextMonthStart - today).days
    monthlyAverages.now = monthDate
    tomorrow = today + datetime.timedelta(days=1)

    fieldList = []
    if args.daysAbove:
        fieldList += [(ExprVal('max>=' + str(t),
                               title=str(t) + "℃",
                               name="max>=" + str(t),
                               units="days",
                               unit="day",
                               precision=0,
                               field=daily.MAX_TEMP,
                               description=str(t) + "℃ days"), True)
                      for t in range(20, 29 + 1)]
    if args.daysBelow:
        fieldList += [(ExprVal('min<=' + str(t),
                               title=str(t) + "℃",
                               name="min<=" + str(t),
                               units="days",
                               unit="day",
                               precision=0,
                               field=daily.MIN_TEMP,
                               description=str(t) + "℃ nights"), True)
                      for t in range(-10, 1)]
        #[ ( ExprVal('maxHumidex>max and maxHumidex>=' + str(t),
        #              title=str(t) + " humidex",
        #              name="humidex>=" + str(t),
        #              units="days",
        #              unit="day",
        #              precision=0),
        #      True ) for t in range(30, 51) ]
    if args.rain:
        if 'TOTAL_RAIN_MM' not in stations.city[args.city].skipDailyFields:
            fieldList += [(FractionVal(daily.TOTAL_RAIN_MM, "Rain"), True)]
    if args.snow:
        fieldList += [(FractionVal(daily.TOTAL_SNOW_CM, "snow"), True)]
    if args.snowDays:
        fieldList += ([(ExprVal('snow>0',
                                title="snow",
                                name="snow",
                                units="days",
                                unit="day",
                                precision=0,
                                field=daily.TOTAL_SNOW_CM,
                                description="snow days"), True)] +
                      [(ExprVal('snow>=' + str(t),
                                title=str(t) + "cm snow",
                                name="snow>=" + str(t),
                                units="days",
                                unit="day",
                                precision=0,
                                field=daily.TOTAL_SNOW_CM,
                                description=str(t) + "cm snow days"), True)
                       for t in range(5, 41, 5)])
    if args.wind:
        fieldList += [(FractionVal(daily.AVG_WIND, "wind"), False)]
    if args.meanTemp:
        fieldList += [(ExprVal(
            'meanTemp'
            ' if ("M" not in meanTempFlag and "I" not in meanTempFlag)'
            ' else ((max+min)/2'
            '  if ("M" not in minFlag and "I" not in minFlag'
            '      and "M" not in maxFlag and "I" not in maxFlag)'
            '  else None)',
            name="Mean temperature",
            title="Mean temperature",
            units="℃",
            unit="℃",
            precision=2,
            field=daily.MEAN_TEMP,
            description='avg. hourly temp'), False)]
    if args.avgTemp:
        fieldList += [(Avg(daily.MIN_TEMP, daily.MAX_TEMP,
                           "Temperature"), False)]
    if args.humidity:
        fieldList += [(FractionVal(daily.MEAN_HUMIDITY, "Humidity"), False)]

    monthStr = monthName(monthDate.month, long=True)
    monFilter = monthlyAverages.MonthFilter(month=monthDate.month)
    nextMonthYear = (monthDate.year * 12 + monthDate.month) // 12
    nextMonthMonth = monthDate.month % 12 + 1

    #import pudb; pu.db
    for field, cumulative in fieldList:
        valByYear = monthlyAverages.yearDatas(monFilter,
                                              field=field,
                                              lastYear=monthDate.year,
                                              cumulative=cumulative)
        normalVal = monthlyAverages.normalMonthlyDatas(monthDate.year,
                                                       monFilter,
                                                       field,
                                                       cumulative=cumulative)
        if monthDate.year not in valByYear:
            continue
        ci80 = confidenceInterval80(valByYear, monthDate)
        ci50 = confidenceInterval50(valByYear, monthDate)

        thisYearVal = valByYear[monthDate.year]
        maxSince = None
        minSince = None
        for year in reversed(sorted(valByYear.keys())):
            if year != monthDate.year:
                val = valByYear[year]
                if maxSince is None and val >= thisYearVal:
                    maxSince = year
                if minSince is None and val <= thisYearVal:
                    minSince = year
        del val

        maybeTweetTop5(city, valByYear, nextMonthYear, nextMonthMonth,
                       monthStr, field, monthDate)
        maybeTweetMaxSince(city, valByYear, field, monthDate, monthStr,
                           maxSince, cumulative)
        maybeTweetMinSince(city, valByYear, field, monthDate, monthStr,
                           minSince, cumulative)

        insideCi80 = (thisYearVal >= ci80[0] and thisYearVal <= ci80[1])
        insideCi50 = (thisYearVal >= ci50[0] and thisYearVal <= ci50[1])

        amountDescription = ''
        if thisYearVal < ci50[0] and (field.units != 'days'
                                      or thisYearVal != 0):
            amountDescription = 'just '
        if daysLeft > 5 and thisYearVal > ci50[0]:
            amountDescription = 'already '
        amount = formatWithUnits(thisYearVal, field)
        if field.units == 'days':
            amount = formatWithUnits(thisYearVal, field, 0, skipUnits=True)
            if thisYearVal == 0:
                amount = "no"

        aboveBelow = 'above'
        if thisYearVal < normalVal.average:
            aboveBelow = 'below'

        if cumulative:
            deviation = deviationDescription(thisYearVal, normalVal.average)
        else:
            diff = abs(float(thisYearVal - normalVal.average))
            deviation = formatWithUnits(diff, field) + ' ' + aboveBelow
        start = 'average'
        if cumulative:
            start = 'total'
        title = field.title.lower()
        ci80Low = formatWithUnits(ci80[0], field, skipUnits=True)
        ci80High = formatWithUnits(ci80[1], field)
        insideOutside = 'outside'
        if insideCi80:
            insideOutside = 'inside'
        cityName = stations.city[city].name
        average = formatWithUnits(normalVal.average,
                                  field,
                                  precision=max(field.precision, 1))
        tweetText = ('#{cityName}\'s {start} {title} this {monthStr} was'
                     ' {amountDescription}{amount},'
                     ' {deviation} the average of {average}.'.format(
                         **locals()))
        if daysLeft > 0:
            tweetText = (
                f'With {daysLeft} days left, #{cityName}\'s {start} {title} this {monthStr} is'
                f' {amountDescription}{amount},'
                f' {deviation} the average of {average}.')
        if field.units == 'days':
            units = field.unit
            if thisYearVal != 1:
                units = field.units
            tweetText = (
                '#{cityName} had'
                ' {amountDescription}{amount} {title} {units} this {monthStr},'
                ' {deviation} the average of {average}.'.format(**locals()))
        print(tweetText)
        #alertTweets.maybeTweetWithSuffix(city, tweetText)
        recordMin = normalVal.minimum
        recordMax = normalVal.maximum
        print(
            'Record minimum was {recordMin}{field.units} in {normalVal.minYear}'
            .format(**locals()))
        print(
            'Record maximum was {recordMax}{field.units} in {normalVal.maxYear}'
            .format(**locals()))
        plotIt = not insideCi80 or args.force
        if maxSince is None:
            print('***Max since records began in {}.'.format(
                min(valByYear.keys())))
            plotIt = True
        elif monthDate.year - maxSince > 10:
            maxSinceVal = float(valByYear[maxSince])
            print('***Max since {maxSince}:{maxSinceVal}{field.units}'.format(
                **locals()))
            plotIt = True
        if minSince is None:
            print('***Min since records began in {}.'.format(
                min(valByYear.keys())))
            plotIt = True
        elif monthDate.year - minSince > 10:
            minSinceVal = float(valByYear[minSince])
            print('***Min since {minSince}:{minSinceVal}{field.units}'.format(
                **locals()))
            plotIt = True
        if plotIt:
            monthlyAverages.annualOrderedByMonth(city,
                                                 monFilter,
                                                 field,
                                                 cumulative=cumulative)
            plotFname = snow.createPlot(
                city,
                running=cumulative,
                field=field,
                otheryears=(),
                name=monthName(monthDate.month) +
                title.replace(' ', '_').replace('℃', 'C'),
                dataStartDay=datetime.date(monthDate.year, monthDate.month, 1),
                plotStartDay=datetime.date(monthDate.year, monthDate.month, 1),
                plotEndDay=datetime.date(nextMonthYear, nextMonthMonth, 1),
                plotYMin=None)
            pngname = plotFname.replace('/svg/', '/') + '.png'
            command = 'rsvg-convert -o {pngname} --background-color=white {plotFname}'.format(
                **locals())
            print(command)
            assert os.system(command) == 0
            alertTweets.maybeTweetWithSuffix(city, tweetText, fname=pngname)
def main(
    city,
    force=False,
    lastCheckedValue=None,
    today=None,
    maxValueToCheck=None,
    allYear=False,
    allWinter=False,
    justTop5=False,
    dataSinceDay=None,
):
    #import pdb; pdb.set_trace()
    data = daily.load(city)
    if dataSinceDay is not None:
        for day in tuple(data.keys()):
            if day < dataSinceDay:
                del data[day]
    monthlyAverages.cityData = data
    if today is None:
        today = daily.timeByCity[city].date()
        #if allYear:
        #    today = dt.date(daily.timeByCity[city].date().year, 12, 31)
    yearToCheck = (today - dt.timedelta(7)).year
    if yearToCheck != today.year:
        today = dt.date(yearToCheck, 12, 31)
    monthlyAverages.now = today
    tomorrow = today + dt.timedelta(days=1)

    todayMaxInfo = dailyRecords.getInfo(city, today, daily.MAX_TEMP)

    todayAverageMax = roundAwayFromZero(todayMaxInfo.normal)
    todayMax = None
    if todayMaxInfo.recent is not None:
        todayMax = int(todayMaxInfo.recent)

    if lastCheckedValue is None:
        minValueToCheck = int(todayAverageMax) + 2
    else:
        minValueToCheck = int(lastCheckedValue) + 1

    if maxValueToCheck is None:
        maxValueToCheck = todayMax
        if todayMax is None:
            maxValueToCheck = 35

    maxValuesToCheck = filter(lambda t: t % 10 == 0,
                              range(minValueToCheck, maxValueToCheck + 1))

    fieldList = [
        #*[ ( ExprVal('max>={} if max is not None else None'.format(t),
        #             title=str(t) + "℃",
        #             name="max>=" + str(t),
        #             units="days",
        #             unit="day",
        #             precision=0,
        #             field=daily.MAX_TEMP,
        #             description=str(t)+"℃ days"),
        #     True ) for t in maxValuesToCheck ],
        #[ ( ExprVal('maxHumidex>max and maxHumidex>=' + str(t),
        #              title=str(t) + " humidex",
        #              name="humidex>=" + str(t),
        #              units="days",
        #              unit="day",
        #              precision=0),
        #      True ) for t in range(30, 51) ]
        #( FractionVal(daily.TOTAL_RAIN_MM, "Rain"), True ),
        (FractionVal(daily.TOTAL_SNOW_CM, "snow"), True),
        #( FractionVal(daily.TOTAL_PRECIP_MM, "precipitation"), True ),
        #( FractionVal(daily.AVG_WIND, "Wind"), False ),
        #( Avg(daily.MIN_TEMP, daily.MAX_TEMP, "temperature"), False ),
        #( FractionVal(daily.MEAN_HUMIDITY, "Humidity"), False ),
    ]

    monFilter = monthlyAverages.BeforeDateFilter(month=tomorrow.month,
                                                 day=tomorrow.day)
    if allYear:
        monFilter = monthlyAverages.BeforeDateFilter(month=1, day=1)
    if allWinter:
        monFilter = WinterFilter()
    todayFilter = monthlyAverages.OneDayFilter(month=today.month,
                                               day=today.day)

    #import pudb; pu.db
    for field, cumulative in fieldList:
        thisyear = today.year
        if allWinter:
            thisyear = today.year if today.month >= 7 else today.year - 1
        valByYear = monthlyAverages.yearDatas(monFilter,
                                              field=field,
                                              lastYear=thisyear,
                                              cumulative=cumulative)
        normalVal = monthlyAverages.normalMonthlyDatas(today.year,
                                                       monFilter,
                                                       field,
                                                       cumulative=cumulative)
        if thisyear not in valByYear:
            continue
        ci80 = confidenceInterval80(valByYear, thisyear)
        ci50 = confidenceInterval50(valByYear, thisyear)

        thisYearVal = valByYear[thisyear]
        maxSince = None
        minSince = None
        for year in reversed(sorted(valByYear.keys())):
            if year != thisyear:
                val = valByYear[year]
                if maxSince is None and val >= thisYearVal:
                    maxSince = year
                if minSince is None and val <= thisYearVal:
                    minSince = year
        del val
        insideCi80 = (thisYearVal >= ci80.low and thisYearVal <= ci80.high)
        insideCi50 = (thisYearVal >= ci50.low and thisYearVal <= ci50.high)

        if field.units == 'days':  # and False:
            todayValByYear = monthlyAverages.yearDatas(todayFilter,
                                                       field=field,
                                                       lastYear=today.year,
                                                       cumulative=cumulative)
            if todayValByYear[today.year] == 0:
                #The countable event did not occur today, so skip.
                continue
            #import pdb; pdb.set_trace()

        yearByVal = reverseDict(valByYear)
        top5 = topNValuesLists(yearByVal, 5)
        if ((allYear or allWinter)
                and (inListOfLists(top5.values(), thisyear) or args.force)):
            tweetTopN(city, top5, field, thisyear)
        if justTop5:
            continue

        amountDescription = ''
        if thisYearVal < ci50[0] and (field.units != 'days'
                                      or thisYearVal != 0):
            amountDescription = 'just '
        amount = formatWithUnits(thisYearVal, field, field.precision)
        if field.units == 'days':
            amount = formatWithUnits(thisYearVal, field, 0, skipUnits=True)
            if thisYearVal == 0:
                amount = "no"

        aboveBelow = 'above'
        if thisYearVal < normalVal.average:
            aboveBelow = 'below'

        if cumulative:
            deviation = deviationDescription(thisYearVal, normalVal.average,
                                             field)
        else:
            diff = abs(thisYearVal - normalVal.average)
            deviation = formatWithUnits(diff, field,
                                        field.precision) + ' ' + aboveBelow
        start = 'average'
        if cumulative:
            start = 'total'
        title = field.title.lower()
        ci80Low = formatWithUnits(ci80[0],
                                  field,
                                  field.precision,
                                  skipUnits=True)
        ci80High = formatWithUnits(ci80[1], field, field.precision)
        insideOutside = 'outside'
        if insideCi80:
            insideOutside = 'inside'
        cityName = stations.city[city].name
        avgWithUnits = formatWithUnits(normalVal.average, field,
                                       field.precision)
        #tweetText = (
        #    '#{cityName}\'s {start} {title} so far this year was'
        #    ' {amountDescription}{amount},'
        #    ' {deviation} {aboveBelow} average; {insideOutside} the normal range of'
        #    ' {ci80Low} to {ci80High}'.format(**locals()))
        tweetText = ('#{cityName}\'s {start} {title} so far this year was'
                     ' {amountDescription}{amount},'
                     ' {deviation} the average of'
                     ' {avgWithUnits}'.format(**locals()))
        if allYear:
            tweetText = (
                '#{cityName}\'s {start} {title} during {today.year} was'
                ' {amountDescription}{amount},'
                ' {deviation} the average of'
                ' {avgWithUnits}'.format(**locals()))
        if field.units == 'days':
            units = field.unit
            if thisYearVal != 1:
                units = field.units
            nth = alert.nth(thisYearVal)
            #import pdb; pdb.set_trace()
            average = formatWithUnits(normalVal.average, field, precision=1)
            todayString = 'Today is'
            if today != daily.timeByCity[city].date():
                todayString = 'Yesterday was'
            tweetText = (
                '{todayString} #{cityName}\'s {nth} {title} day so far this year,'
                ' {deviation} the average of {average}.'.format(**locals()))
        #alertTweets.maybeTweetWithSuffix(city, tweetText)
        recordMin = normalVal.minimum
        recordMax = normalVal.maximum
        print(
            'Record minimum was {recordMin}{field.units} in {normalVal.minYear}'
            .format(**locals()))
        print(
            'Record maximum was {recordMax}{field.units} in {normalVal.maxYear}'
            .format(**locals()))
        plotIt = not insideCi80 or args.force
        if maxSince is None:
            print('***Max since records began in {}.'.format(
                min(valByYear.keys())))
            plotIt = True
        elif today.year - maxSince > 10:
            maxSinceVal = valByYear[maxSince]
            print('***Max since {maxSince}:{maxSinceVal}{field.units}'.format(
                **locals()))
            plotIt = True
        if minSince is None:
            print('***Min since records began in {}.'.format(
                min(valByYear.keys())))
            plotIt = True
        elif today.year - minSince > 10:
            minSinceVal = valByYear[minSince]
            print('***Min since {minSince}:{minSinceVal}{field.units}'.format(
                **locals()))
            plotIt = True
        if plotIt:
            fname = field.name
            pngname = "%s/Annual_%s.png" % (city, fname)
            alertTweets.maybeTweetWithSuffix(city, tweetText, fname=pngname)
    if todayMax is None:
        return todayAverageMax + 2
    return max(todayMax, todayAverageMax + 2)