ecFlow笔记:处理空的容器节点

目录

NMC网站将于今年3月份启用新版,重新规划了数据预报的图片产品展示,调整浏览目录结构,大幅度缩减图片数量。

新版网站要求使用 GRAPES MESO 3km 的图片产品替换 GRAPES MESO 10km 的部分图片产品。

因为 MESO 3km 模式在00,06,12和18时次积分36小时,而 MESO 10km 在00和12时次积分84小时,03,06,09,15,18,21时次则积分30小时,所以需要对两套系统生成的图片进行合并。 如果 MESO 3km 有数据则使用3km的图片,没有数据则使用10km的图片。

对于 GRAPES MESO 3km 后处理系统来说,只需要增加新的图片绘制任务。 而现有的 GRAPES MESO 10KM 后处理系统则需要进行修改,关掉3km系统已绘制的那些图片,保留其他任务。

本文介绍如何修改现有系统,并说明如何处理删减任务后产生的空的容器节点(Family node)。

修改方式

在之前的文章《NWPC业务系统笔记:构建图片产品制作系统》里提到过, 图片制作系统会使用不同的时效列表。 未修改前的GRAPES MESO 10km后处理系统(简称MESO POST)使用如下的时效列表。

def _init_forecast_hour_list(self):
    self.config['forecast_hour_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(0, self.config["forecast_length"]+1, 1)
    ]

    self.config['forecast_hour_list_1'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(1, self.config["forecast_length"]+1, 1)
    ]

    self.config['forecast_hour_per_3_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(3, self.config["forecast_length"]+1, 1)
    ]

    self.config['forecast_hour_per_6_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(6, self.config["forecast_length"]+1, 1)
    ]

    self.config['forecast_hour_per_12_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(12, self.config["forecast_length"]+1, 1)
    ]

    self.config['forecast_hour_per_24_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(24, self.config["forecast_length"]+1, 1)
    ]

按照要求,MESO POST 系统需要构建新的时效列表,去掉与 MESO 3km 重叠的时效。 所以首先要判断 3km 是否预报当前时次,如果有预报,则start_forecast_hour37开始。 构建新时效列表的代码如下所示。

def _init_forecast_hour_list_exclude_3km(self):
    start_forecast_hour = 0
    if self.name in self.config["3km_circles"]:
        start_forecast_hour = 37

    self.config['3km_forecast_hour_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(start_forecast_hour, self.config["forecast_length"]+1, 1)
    ]

    self.config['3km_forecast_hour_list_1'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(max(1, start_forecast_hour), self.config["forecast_length"]+1, 1)
    ]

    self.config['3km_forecast_hour_per_3_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(max(3, start_forecast_hour), self.config["forecast_length"]+1, 1)
    ]

    self.config['3km_forecast_hour_per_6_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(max(6, start_forecast_hour), self.config["forecast_length"]+1, 1)
    ]

    self.config['3km_forecast_hour_per_12_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(max(12, start_forecast_hour), self.config["forecast_length"]+1, 1)
    ]

    self.config['3km_forecast_hour_per_24_list'] = [
        '{hour:03}'.format(hour=an_hour)
        for an_hour in range(max(24, start_forecast_hour), self.config["forecast_length"]+1, 1)
    ]

空容器节点

因为 MESO POST 部分时次只预报30小时,所以构建的部分新列表是空的。 在构建绘图任务的代码段中,我只修改了使用的时效列表,这就导致某些产品的容器节点可能为空。

对比下面00和06两个时次,可以看到06时次存在空的容器节点。

GRAPES MESO 10km后处理系统00时次

GRAPES MESO 10km后处理系统06时次,存在空的容器节点

从上图中可以看到,即便06时次所有其他任务都完成(complete),空的容器节点也因为没有任务节点而一直保持排队状态(queued)。 这样明显无法保证系统自动滚动循环运行。

查找空容器节点

最直接的方法就是找到这些节点并删除。

下面的代码中resolve_empty_family_nodes函数后续遍历所有节点,如果该节点是Family节点且没有子节点,则删除该节点。

class EmptyFamilyRemoveVistor(object):
    def __init__(self):
        pass

    def visit(self, node):
        if isinstance(node, Family) and len(list(node.nodes)) == 0:
            node_path=node.get_abs_node_path()
            node.remove()

def post_order_travel(root_node, visitor):
    for child_node in root_node.nodes:
        post_order_travel(child_node, visitor)
    visitor.visit(root_node)

def resolve_empty_family_nodes(self, root):
    visitor = EmptyFamilyRemoveVistor()
    post_order_travel(root, visitor)

但遗憾的是,使用CMA-PI上的ecFlow运行上述代码会在node.remove()出现段错误,可能我使用API的方式不对。 不过可以采用另一种方式实现。

自动设置完成状态

既然空的容器节点无法自动完成,那找到一种能让节点自动完成的方法就可以。

ecFlow提供defstatus属性,可以设置某个节点默认的状态。 系统每次滚动到下一天时,所有节点都会被设置为defstatus定义的状态。 默认情况下,所有节点的defstatus都是排队(queue)。

只需要修改上一节中的一行代码,就可以实现将空容器节点的defstatus设置为complete

class EmptyFamilyRemoveVistor(object):
    def __init__(self):
        pass

    def visit(self, node):
        if isinstance(node, Family) and len(list(node.nodes)) == 0:
            node_path=node.get_abs_node_path()
            node.add_defstatus(DState.complete)

修改后的系统就能自动将空容器节点设置为完成状态。

GRAPES MESO 10km后处理系统18时次,空的容器节点自动设为complete状态