Next-gen rules: Reusable function for multiple rules simultaneously

In short
openHAB 3.1.0
Using javascript of the next-gen rules, what do I use for repeatable functions, which can get called from multiple rules simultaneously?
Looking on the forums and documentation, it’s not clear to me if what I’m after is a lambda, a module or a helper library?

In detail:
Recently upgraded to v3.1.0 of openHAB.
Whereas I used to use the DSL rules, I decided it was time to start using the javascript ‘next-gen’ rules.

Was very excited about the potential to finally use fully fledged functions to apply the DRY principle (don’t repeat yourself) and thought I was being clever by writing a rule (‘solar_monitor’) that doesn’t get triggered, but takes a map as an input and can be called from a separate rule.
The map being passed contains an item and some parameters, which then based on grid_watts, switches the passed item on or off.
This rule was then intended to be called multiple times by a separate rules, with the map depending on the items requirements.

This worked beautifully for my first item.
But as soon as I tried to call the rule for a second item, the log showed:
Failed to execute rule 'solar_monitor' with status 'RUNNING'

Now, I’m assuming this is because both top level rules call the ‘solar_monitor’ at the same time, i.e. when grid_watts changed. And which ever happens to get there first, gets executed and the second call fails due to rule already RUNNING.

I’ve had a look through the documentation and searched through the forums, but because there are so many different way that rules can be written, i.e. languages (javascript, pyton etc), old (DSL) rules and new ‘next-gen’ rules. I can’t figure out what is relevant to my problem.
As far as I can tell there are lambdas, modules, helper libraries, amongst others.

All I need is a point in the right direction (correct name), because I’m sure there is a nice guide or post about this somewhere already, as this feels like a very fundamental use of openHAB.

OpenHAB: 3.1.0
Running on openhabian
Raspberrypi 3
next-gen rules in the GUI via ‘execute a given script’
Javascript

The top level ‘trigger’ rule, (i.e. this one for pool):

var bundle_context = Java.type("org.osgi.framework.FrameworkUtil").getBundle(scriptExtension.class).getBundleContext()
var RuleManager = bundle_context.getService(bundle_context.getServiceReference("org.openhab.core.automation.RuleManager"));
var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Expamples");

var myItem = itemRegistry.getItem('pool_relay');

var map = new java.util.HashMap();
map.put("item_being_monitored", myItem)
map.put("on_delay", 6)           //in minutes
map.put("off_delay", 3)          //in minutes
map.put("fast_off_delay", 30)    //in seconds
map.put("on_limit", -2900)       //in Watts, needs to be lower (more negative) than off_limit
map.put("off_limit", -800)       //in Watts, needs to be lower (more negative) than fast_off_limit
map.put("fast_off_limit", 0)     //in Watts, 0 implies grid import
map.put("min_allowable_runtime", 1)

RuleManager.runNow("solar_monitor",false, map);

solar_monitor rule:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Expamples");
var ZonedDateTime   = Java.type("java.time.ZonedDateTime");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

if (typeof this.timers === 'undefined') {
	 this.timers =[]
}

var switch_item = context.getAttribute("item_being_monitored");
var on_delay = context.getAttribute("on_delay");
var off_delay = context.getAttribute("off_delay");
var fast_off_delay = context.getAttribute("fast_off_delay");
var on_limit = context.getAttribute("on_limit");
var off_limit = context.getAttribute("off_limit");
var fast_off_limit = context.getAttribute("fast_off_limit");
var min_allowable_runtime = context.getAttribute("min_allowable_runtime");

//timer names
var item_name = switch_item.getName();
var on_timer = item_name+"_on_timer";
var off_timer = item_name+"_off_timer";
var fast_off_timer = item_name+"_fast_off_timer";
var min_allowable_runtime_timer = item_name+"min_allowable_runtime_timer";

var timer_active = function(timer_name){
  if (this.timers[timer_name] === undefined){
      //logger.info("Timer '{}' did not exist", timer_name);
      return false;
  }
  if(this.timers[timer_name].isActive()){
      //logger.info("Timer '{}' did exist", timer_name);
      return true;
  } else {
      //logger.info("Timer '{}' was inactive", timer_name);
      return false;
  }
}

var timer_cancel = function(timer_name){
  if (this.timers[timer_name] === undefined){
    logger.info("Timer '{}' was undefined", timer_name);
    return;
  } else {
      try{
        this.timers[timer_name].cancel();
        logger.info("Timer '{}' was canceled", timer_name);
        return;
      }
      catch(err){
        logger.info("Cencelling timer '{}' failed", timer_name);
        return;
      }
  }
}

var solar_above_ON_threshold = function(){
  //logger.info("solar_above_ON_threshold");
  if(switch_item.state == 'ON'){
    //logger.info("{} already ON", item_name);
    if(timer_active(fast_off_timer)){
      timer_cancel(fast_off_timer);
    }
    if(timer_active(off_timer)){
      timer_cancel(off_timer);
    }
    return  // Do Nothing
  }
  if(timer_active(on_timer)){
    logger.info("{} already running", on_timer);
    return // Do nothing
  }
  logger.info("{} started", on_timer);
  this.timers[on_timer] = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(on_delay), function() {
    logger.info("Timer expired, {} started", item_name);
    events.sendCommand(switch_item, ON);
    this.timers[min_allowable_runtime_timer] = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(min_allowable_runtime), function() {
      logger.info("{} min allowable run time ended", item_name);
    });
  });
}

var solar_below_OFF_threshold = function(){
  //logger.info("solar_below_OFF_threshold");
  if(switch_item.state == 'OFF'){
    //logger.info("{} already OFF", item_name);
    if(timer_active(on_timer)){
      timer_cancel(on_timer);
    }
    return  // Do Nothing
  }
  if(timer_active(min_allowable_runtime_timer)){
    logger.info("Device {} only just switched ON, min runtime is running", item_name);
    return
  }
  if(items['Grid_watts'] > fast_off_limit){
    //IMPORTING, SWITCHING TO FAST OFF
    if(timer_active(fast_off_timer)){
      logger.info("{} already running", fast_off_timer);
      return // Do nothing
    }
    this.timers[fast_off_timer] = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(fast_off_delay), function() {
      logger.info("{} expired, {} turned off", fast_off_timer, item_name);
      events.sendCommand(switch_item, OFF);
    });
  } else {
      //NORMAL OFF TIMING
      if(timer_active(fast_off_timer)){
        timer_cancel(fast_off_timer);
      }
      if(timer_active(off_timer)){
        logger.info("{} already running", off_timer);
        return // Do nothing
      }
      this.timers[off_timer] = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(off_delay), function() {
        logger.info("{} expired, {} turned off", off_timer, item_name);
        events.sendCommand(switch_item, OFF);
    });
  }
}

//////SCRIPT EXECUTION STARTS HERE///////
if (items['Grid_watts'] < on_limit){
  solar_above_ON_threshold()
} else if(items['Grid_watts'] > off_limit){
    solar_below_OFF_threshold()
}