Run_at_sunrise callback runs twice (it should not)

Hey, pulling my hair out trying to debug an app that controls the window blinds based on various parameters. It’s a complicated app that has numerous callbacks for temp, light and forecast and dynamically chooses which blinds to close based on the sun’s position in the sky. It all works suprisingly well, except for some reason a callback that runs at sunrise seems to run twice, consecutively… This completely confuses my z-wave controller and the blinds get stuck about halfway down and have to be reset manually before they will work again.

The whole routine is quite long, so I’ll paste what is relevant. I’ve added a bunch of debug log code and have I think traced to the self.run_at_sunrise callback:

import appdaemon.plugins.hass.hassapi as hass
import datetime
from datetime import timedelta
import time

class ShadeCloseRoutine(hass.Hass):
    close_triggered = False
    day_close_triggered = False
    
    def initialize(self):
        # initialize variables for later
        self.close_reset_timer = None
        self.old_light_level = 0
        ###SHADE GROUPS DEFINED VIA SELF.ARG VARIABLES###
        runtime = datetime.time(4,0,0)
        self.run_daily(self.VarReset, reset)
        self.run_daily(self.ForecastRoutine, runtime, home_state="occupied")
            
    def ForecastRoutine(self, kwargs):
        high_temp = float(self.get_state("sensor.daily_hightemp"))
        cloud_cover = float(self.get_state("sensor.weather_forecast_cloud_coverage_0d"))
        kwargs = { "shade_trigger" : "forecast" }
        if high_temp > 69 and cloud_cover < 35:
            ShadeCloseRoutine.close_triggered = True
            self.log("DEBUG FORECAST")
            self.run_at_sunrise(self.ShadeRoutine, offset=1200, **kwargs)
        else: 
            self.log("It's not forecast to be too hot today, so I'll leave the blinds open")
            
            
    def CloseReset(self, kwargs): # let the reset routine run 
        ShadeCloseRoutine.close_triggered = False
        
    def AOICalc(self):
        x = float(self.get_state("sun.sun", attribute="azimuth"))
        y = float(self.get_state("sun.sun", attribute="elevation"))
        y = int((1-((68.9 - y) / 68.9)) * 100)
        return x, y
            
    def ShadeRoutine(self, kwargs):
    # Run through each trigger possibility and act accordingly
        sun_azimuth, sun_elevation = self.AOICalc()
        shade_trigger = kwargs["shade_trigger"]
        blinds = None
		###CODE FOR OTHER CONTINGENCIES REDACTED FOR BREVITY###
        elif shade_trigger == "forecast":
            blinds = self.shadegroup_east
            self.log(blinds)
            self.ShadeClose(blinds, kwargs)
            self.run_in(self.CloseReset, 5400)
        if blinds is not None:
            try: kwargs["delta"]
            except KeyError: self.log("Closing blinds due to {}. The sun is currently at {}% elevation and {} degree azimuth".format(shade_trigger, sun_elevation, sun_azimuth))
            else: self.log("Closing blinds due to {}. Temp increased {}. The sun is currently at {}% elevation and {} degree azimuth".format(shade_trigger, kwargs["delta"], sun_elevation, sun_azimuth))
    
    def ShadeClose(self, blinds, kwargs):
        shade_trigger = kwargs["shade_trigger"]
        if blinds is not None: 
            if self.get_state("input_boolean.blind_override") == "off":
                for x in blinds:
                    if isinstance(x,dict):
                        c = next(iter(x))
                        level = x[c]["level"]
                    else: 
                        c = x
                        level = 0
                    position = self.get_state(entity_id=c, attribute="current_position")
                    friendly_name = self.get_state(entity_id=c, attribute="friendly_name")
                    if (position - 5) > level:
                        self.call_service("cover/set_cover_position", entity_id=c, position=level)
                        self.log("Closing {} to {}%".format(friendly_name, level))
                        time.sleep(1)
                ShadeCloseRoutine.day_close_triggered = True
            else: self.log("Blinds would close due to {} but the override is on".format(shade_trigger))
        else: self.log("No blinds appropriate to close right now")

The log:

2020-07-14 04:00:00.016246 INFO CloseShades: DEBUG FORECAST
2020-07-14 05:45:37.060490 INFO CloseShades: Closing Living Room Bay Window 1 to 0%
2020-07-14 05:45:38.085233 INFO CloseShades: Closing Living Room Bay Window 2 to 0%
2020-07-14 05:45:39.101696 INFO CloseShades: Closing Living Room Side Window 2 to 10%
2020-07-14 05:45:40.119742 INFO CloseShades: Closing Living Room Vented Window 4 to 10%
2020-07-14 05:45:41.138247 INFO CloseShades: Closing Dining Room East Window 1 to 0%
2020-07-14 05:45:42.155665 INFO CloseShades: Closing Dining Room East Window 2 to 0%
2020-07-14 05:45:43.158824 INFO CloseShades: Closing blinds due to forecast. The sun is currently at 2% elevation and 58.93 degree azimuth
2020-07-14 05:45:43.191229 INFO CloseShades: Closing Living Room Bay Window 1 to 0%
2020-07-14 05:45:44.211677 INFO CloseShades: Closing Living Room Bay Window 2 to 0%
2020-07-14 05:45:45.229390 INFO CloseShades: Closing Living Room Side Window 2 to 10%
2020-07-14 05:45:46.247117 INFO CloseShades: Closing Living Room Vented Window 4 to 10%
2020-07-14 05:45:47.272706 INFO CloseShades: Closing Dining Room East Window 1 to 0%
2020-07-14 05:45:48.296383 INFO CloseShades: Closing Dining Room East Window 2 to 0%
2020-07-14 05:45:49.298844 INFO CloseShades: Closing blinds due to forecast. The sun is currently at 3% elevation and 59.3 degree azimuth

As you can see the ForecastRoutine gets called daily at 4AM, and only runs once, but the ShadeRoutine function seems to run twice… What am I missing here?

Thanks in advance!