Updating a project plan with a single cypher query after any task duration change

I was suggested to post here in addition to stackoverflow, here is my issue :
I am refering to this graphgist : Project Management - graphgists I'm actually trying to update a project plan when a duration on one task changes.

In the GraphGist, the whole project is always calculated from the initial activity to the last activity. This doesn't work great for me in a multi-project environment where I don't really know what the starting point is, and I don't know either the end point. What I would like for now, is just to update the earliest start of any activity which depends on a task I just updated.

The latest I have is the following :

MATCH p1=(:Activity {description:'Perform needs analysis'})<-[:REQUIRES*]-(j:Activity)
UNWIND  nodes(p1) as task
MATCH (pre:Activity)<-[:REQUIRES]-(task:Activity)
WITH MAX(pre.duration+pre.earliest_start) as updateEF,task
SET task.earliest_start = updateEF

The intent is to get all the paths in the projects which depends on the task I just updated (in this case : "perform needs analysis"), also at every step of the path I'm checking if there aren't other dependencies which would override my duration update.

So, of course it only works on the direct connections.

if I have A<-[:requires]-B<-[:requires]-C if I increase duration A, I believe it updates B based on A, but then C is calculated with the duration of B before B duration was updated.

How can I make this recursive? maybe using REDUCE? or would APOC be mandatory for this?

(still searching...)

I am not sure if there is a fancy APOC way of doing this but should be possible with plain Cypher (though likely could get ugly). Just ran a little test locally and this propagated the changes. NOTE: If you have a rather long chain of requires this is NOT guaranteed to be performant :slight_smile:

UPDATE: original query was flawed, see follow up post below -- kept the graph setup info -- to add a divergent path I just ran same query except a MATCH on the the start node and changed the description of the other nodes, then created one last node that required each of the "fourth activity" nodes

Here is the data I used to set up my graph:

CREATE (a:Activity {description: 'Perform needs analysis', earliestStart: datetime(), duration: duration('P1Y1M5D')})<-[r:REQUIRES]-(a1:Activity {description: 'second activity', earliestStart: datetime(), duration: duration('P1Y1M1D')})<-[r2:REQUIRES]-(a2:Activity {description: 'third activity', earliestStart: datetime(), duration: duration('P1Y1M3D')})<-[r3:REQUIRES]-(a3:Activity {description: 'fourth activity', earliestStart: datetime(), duration: duration('P1Y4D')})


MATCH (a:Activity {description: 'fourth activity'})
CREATE (a)-[:REQUIRES]->(:Activity {description: 'Hidden dependency', earliestStart: datetime(), duration: duration("P30Y2M1D")})

Okay so assuming besides the effects of updating that one node, all earliest_starts (earliestStart in my queries) are currently valid (i.e. they account for :Activity nodes not in the path from your activity) this method should propagate all needed changes. It does this by adding up the durations of all previous nodes in the path the the earliest start of the first node.

MATCH path=(:Activity {description:'Perform needs analysis'})<-[:REQUIRES*0..]-(lastReq:Activity)
MATCH (lastReq)<-[:REQUIRES]-(j)
WITH j, nodes(path) AS reqs
WITH j, reduce(early = reqs[0].earliestStart, req in reqs | early + req.duration) AS earliestStart
WITH j, max(earliestStart) AS newStart
SET j.earliestStart = CASE WHEN j.earliestStart > newStart THEN j.earliestStart ELSE newStart END

Just tested with 2 divergent paths that meet back up and it works correctly! If the graph is not currently set up correctly before you update the first node, then that would be a horse of a different color. It would be potentially quite slow, but you could run this same query against every single "start" node (a node that does not require any other node) to get the initial state setup before changing any given node.