尝试用Notion进行项目管理及算法分析

博主最近的工作需要对接许多客户,每一个客户的业务进展都不一样,有的业务会突然紧迫,有的又会可能会暂缓,根据上级的要求随时会做出调整,而每一天不同的业务进展都会有新变化。对于一个新手来说,难免会感到手足无措,可能会混乱不同业务之间的优先级,导致错过了合理的时间分配,变得一会儿无事可做,一会儿又疯狂加班。我是一个非常讨厌靠大脑来记录进度的人,一是会增加精神负担,二是容易出错。我需要一个方法,能够记录我的所有工作进展,让我在休息的时候可以完全忘记工作的内容,在开始工作后又能迅速进入状态,马上就能安排出工作任务来。工作越来越多的时候,这种需求将会更加紧迫。

好在我已经有了一点Notion和编程的基础,在工作的时候也已经尝试用Notion进行内容管理,但安排还比较零散、混乱,没能形成一个简洁有效的体系。周末的时间决定梳理一下工作内容,尝试用Notion进行项目管理和进度跟踪。

我的需求如下:

  1. 能够跟踪不同进度的状态,能够随时更新和调整。
  2. 实现清单功能,对材料进行统计、梳理和记录。
  3. 记录时间表。
  4. 根据现有条件和业务进展情况自动计算现在能够进行的工作内容,并予以展示。

第二、三两个需求可以较容易地实现,可以参考之前的文章:用Notion进行读书管理。

第一和第四个需求有关联性,实现起来则需要多思考一些。

噢,差点忘了给大家看看实现的效果,工作内容计算在图中的workflow部分。

image-20200412180456434
image-20200412180456434

image-20200412180555791
image-20200412180555791

image-20200412180801735
image-20200412180801735

清单梳理:

image-20200412181341402
image-20200412181341402

本文重点讲述需求四的实现,即:根据现有条件和业务进展情况自动计算出现在能够进行的工作内容,内容涉及算法。

现有条件和业务进展采用Notion中的CheckBox和Select属性进行记录,自动计算就用Notion的Formula属性。

那么如何设置属性和编辑公式呢?显然需要严格按照项目的业务流程来,为了对业务流程有一个更清晰的了解,我将现在的工作业务做了一个流程图,当然这只是目前的版本,会根据工作情况进行调整。

项目开始
项目开始

可以看到有许多节点之间是呈上下文或平行关系,有的需要在上一个或多个节点的条件满足时才可以进行下一节点的工作,而有的工作在条件满足时可以并行开展,这和计算机基础中的“有向图”的数据结构非常相似。每一个节点代表一个任务,每一个任务的完成是下一个节点执行的条件。我们要实现的是根据已知条件输出必要而不重复执行的节点。

接下来是我思考的过程:

思考 1

是的,有多个思考方式。

将每一个节点作为条件,结合Notion编程中的判断语句就能逐步运算,得出想要的结果。

为了简化思考,先假设有如下一个图:

image-20200412191600930
image-20200412191600930

常规思维模式是:

1.如果 A条件满足 和 B条件满足,则C可执行,输出“C可执行”。

如果 C可执行, 但 C已执行,则不输出“C可执行”。

2.如果 A条件满足 和 B条件满足,且 C条件也满足(即 C已执行后),则D可执行,输出“D可执行”。(不过实际上只要C确实满足,A、B就一定满足)

如果 D可执行, 但 D已执行,则不输出“D可执行”。

依此类推……

上面的思维模式没有问题,但是实现起来是非常繁琐的,并且会有非常多的重复判断(比较1和2,A和B条件重复判断)。假如流程的节点非常多,越到后面,需要判断的条件就会越多,出现需要重复判断a、b、c、d、e、f、g、h、i、j条件的情况。不过这种算法的好处是不会出现嵌套的情况。

假如流程图出现支线流程,意味着我们需要自行选择判断条件:

image-20200412194728342
image-20200412194728342

在上图中,G的执行,与D’和C有关,但与D、E无关,E和G的执行都与C有关。

思考 2

继承了思考1的思维模式,但这次我们换一个方向,即从最后一个节点往回判断。

image-20200412191600930
image-20200412191600930

假如 E条件不满足,则F节点不可执行,不输出“F可执行”。

假如 E条件满足, 则理论上F节点可执行,但需要判断D条件是否满足,因为D条件满足是E条件满足的条件。若D条件不满足,则F可执行不成立,不输出“F可执行”。

同理,回溯判断 D条件是否满足,需要判断A、B、C条件是否满足,C条件满足需要判断A、B条件是否满足。

博主一开始思考的就是思考2的方式。变态的是,Notion的判断语句只有二元判断功能,没有Else If也没有Switch,于是就用IF语句循环嵌套,然而Notion的编程语言本来就是残次品,IF循环嵌套实在是太复杂,一晚上不知道死了多少脑细胞。好不容易写出来了,结果仍然存在致命的逻辑错误,你们发现了吗?

if(prop("申请书")==true,
    "| 验收报告 | 立项报告 ",
    if(prop("项目汇总")==true,
        if(prop("知产说明书")==true,
            "| 申请书",
            "| 知产说明书"
        ),
        if(prop("知产说明书")==true,
            "| 项目汇总",
            if(prop("知产清单")==true,
                "| 知产说明书 | 项目汇总",
                if(prop("确定高品")==true,
                    "| 知产清单",
                    if(prop("上年票细")==true,
                        "| 确定高品",
                        "| 上年票细"
                    )
                )
            )
        )
    )
)
+
if(prop("知产说明书")==true,
    "| 成果说明 | 高品说明",""
)

想要解决这个逻辑错误,就仍然需要用到思考1中的重复判断。

难道就没有更优化的方法了吗?

为了解决重复判断的问题,我想,能不能只靠一个计算值就能判断节点执行的有效性?

神奇的是,还真让我搞出来了。实现的原理灵感来自于斐波那契数列,本来这种想法旨在解决思考2的逻辑错误,但后来想到可以形成一个新的思考。

思考 3 ——基于唯一数值的节点有效性计算

这次我们不用简化问题了,直接上手真实的流程图吧。

example
example

想要让数值计算成为可能,就需要对每一个节点进行编号,如何编号是这个算法的关键,它需要能够保证针对节点有唯一的有效性验证,即一个节点编号能够计算出一个的数值,而这个数值能够反映出该节点的有效性(可执行或不可执行)。

判断流程路径

首先需要判断有向图中从起点到终点有多少条路径(流程路径,需要充分考虑上下文):

为了简化,从图中可以分出3条流程路径:

  1. A-B-C-D-E-G
  2. A-B-C-D-E’-G
  3. A-B-C-(D,D’)-E‘’-(F,F’)-G

节点编号

然后在每一天路径上进行编号,这个编号公式特别简单:

  1. 设起点数值为1。
  2. 之后的每一个节点的编号为该路径上为保证该节点有效的所有节点之和的两倍。写成数学公式就是:

$n_{该节点}=2\sum_{起点}^{该节点的上一个节点}n_{该路径有效节点}, n表示为编号$

举例来说,A的编号为1,B的编号为2(由(1+0)*2得),C的编号为6(由(1+2)*2)得,D和D’均为18,E和E’、E’‘在不同路径上,计算方式也有差异,E和E’编号为54,E’’编号为90(由(1+2+6+18+18)2*得)。

计算验证数值

image-20200412205703203
image-20200412205703203

获得编号后,就需要计算有效性验证数值,我们可以在Notion中添加一个Formula属性,名称为CheckV(Check Validity),计算公式也很简单,根据现有条件(CheckBox is true or not)和进展情况赋值,再求和即可。这里根据实际工作流程,代码如下:

if(prop("上年票细") == true or prop("确定高品") == true, 1, 0) + if(prop("确定高品") == true, 2, 0) + if(prop("知产清单") == true, 6, 0) + if(prop("知产说明书") == true, 18, 0) + if(prop("项目汇总") == true, 18, 0) + if(prop("申请书") == true, 90, 0) + if(prop("高品说明") == true, 54, 0) + if(prop("成果说明") == true, 54, 0) + if(prop("立项报告") == "已完成", 270, 0) + if(prop("验收报告") == "已完成", 270, 0)

格式化后更易理解:

if(prop("上年票细") == true or prop("确定高品") == true, 1, 0)
+ if(prop("确定高品") == true, 2, 0)
+ if(prop("知产清单") == true, 6, 0)
+ if(prop("知产说明书") == true, 18, 0)
+ if(prop("项目汇总") == true, 18, 0)
+ if(prop("申请书") == true, 90, 0)
+ if(prop("高品说明") == true, 54, 0)
+ if(prop("成果说明") == true, 54, 0)
+ if(prop("立项报告") == "已完成", 270, 0)
+ if(prop("验收报告") == "已完成", 270, 0)

如何用数值证明节点可执行性

这里我需要解释一下如何利用CheckV判断节点可执行性,以及为什么可以。

example
example

判断一个节点可以被执行需要满足两个条件:

1. 以该节点编号的1/2为除数,CheckV数值可以被该除数整除(即余数为0)。

这个条件可以初步判断该节点的先决条件均为有效的状态,编号的1/2即为路径上先决有效节点编号的和(设为T),按照编号公式,若先决条件均满足,则CheckV一定是T的整数倍(>=1)。

2.在满足1的条件下,若CheckV除以上述除数获得的商为奇数,则该节点可执行(不判断是否已执行),若获得的商为偶数,则不可执行。

为什么要判断CheckV除以上述除数获得的商是否为奇数?

由于该条件2是在条件1下判断,能整除只有两种情况:

a、先决条件均满足。

b、先决条件均不满足,但可能会有后文节点有效的情况(理论上不可能,但实际操作中可能误点后文属性导致这种情况,对于该节点仍然是不可执行的,也就是思考2中的逻辑错误),也可能会有CheckV为0的情况。

根据之前编号公式里的二倍求和条件,商为奇数当且仅当先决条件均有效时才满足,其余情况均为偶数。

因此,当条件1和条件2均满足时,即可认为该节点是可执行的,但未判断该节点是否已执行,完善这点,可以加上该节点是否已执行的判断语句。最终,在Notion中写出的代码如下:

if(prop("CheckV") % 2 == 0 or prop("上年票细") == false, "| 上年票细 ", if(prop("CheckV") % 2 == 1 and prop("确定高品") == false, "| 确定高品 ", "")) + if(prop("CheckV") % 3 == 0, if(prop("CheckV") / 3 % 2 == 1 and prop("知产清单") == false, "| 知产清单 ", ""), "") + if(prop("CheckV") % 9 == 0, if(prop("CheckV") / 9 % 2 == 1 and prop("知产说明书") == false, "| 知产说明书 ", ""), "") + if(prop("CheckV") % 9 == 0, if(prop("CheckV") / 9 % 2 == 1 and prop("项目汇总") == false, "| 项目汇总 ", ""), "") + if(prop("CheckV") % 45 == 0, if(prop("CheckV") / 45 % 2 == 1 and prop("申请书") == false, "|  申 请 书 ", ""), "") + if(prop("CheckV") % 135 == 0, if(prop("CheckV") / 135 % 2 == 1 and prop("立项报告") != "已完成", "| 立项报告 ", ""), "") + if(prop("CheckV") % 135 == 0, if(prop("CheckV") / 135 % 2 == 1 and prop("验收报告") != "已完成", "| 验收报告 ", ""), "") + if(prop("CheckV") % 27 == 0 or (prop("CheckV") - 18) % 27 == 0, if(prop("CheckV") / 27 % 2 == 1 or (prop("CheckV") - 18) % 27 == 0 and prop("成果说明") == false, "| 成果说明 ", ""), "") + if(prop("CheckV") % 27 == 0 or (prop("CheckV") - 18) % 27 == 0, if(prop("CheckV") / 27 % 2 == 1 or (prop("CheckV") - 18) % 27 == 0 and prop("高品说明") == false, "| 高品说明 ", ""), "")

格式化后更便于理解:

if(prop("CheckV") % 2 == 0 or prop("上年票细") == false, "| 上年票细 ", if(prop("CheckV") % 2 == 1 and prop("确定高品") == false, "| 确定高品 ", ""))
+ if(prop("CheckV") % 3 == 0, if(prop("CheckV") / 3 % 2 == 1 and prop("知产清单") == false, "| 知产清单 ", ""), "")
+ if(prop("CheckV") % 9 == 0, if(prop("CheckV") / 9 % 2 == 1 and prop("知产说明书") == false, "| 知产说明书 ", ""), "")
+ if(prop("CheckV") % 9 == 0, if(prop("CheckV") / 9 % 2 == 1 and prop("项目汇总") == false, "| 项目汇总 ", ""), "")
+ if(prop("CheckV") % 45 == 0, if(prop("CheckV") / 45 % 2 == 1 and prop("申请书") == false, "|  申 请 书 ", ""), "")
+ if(prop("CheckV") % 135 == 0, if(prop("CheckV") / 135 % 2 == 1 and prop("立项报告") != "已完成", "| 立项报告 ", ""), "")
+ if(prop("CheckV") % 135 == 0, if(prop("CheckV") / 135 % 2 == 1 and prop("验收报告") != "已完成", "| 验收报告 ", ""), "")
+ if(prop("CheckV") % 27 == 0 or (prop("CheckV") - 18) % 27 == 0, if(prop("CheckV") / 27 % 2 == 1 or (prop("CheckV") - 18) % 27 == 0 and prop("成果说明") == false, "| 成果说明 ", ""), "")
+ if(prop("CheckV") % 27 == 0 or (prop("CheckV") - 18) % 27 == 0, if(prop("CheckV") / 27 % 2 == 1 or (prop("CheckV") - 18) % 27 == 0 and prop("高品说明") == false, "| 高品说明 ", ""), "")

上面的代码相比前述思考的实现方式,可能会增加一些运算量,导致性能不如前述,实际使用根本差异。但该代码具有很高的复用性,也容易修改调整,最重要的是,方便理解。

具体实现效果参考文章开头截图的workflow部分。

好了,以上就是全部内容,感谢大家阅读。

See you soon。