979b7f72d5
Slukker spilleren en produktion, hvis vare et tændt flow kører (manuel vare eller målet tager den i auto), en stald æder eller en aktiv kæde bruger, kommer én besked pr. slukning med brugere og lagerdage, og en rød post 'Slukket men nødvendig' i Kræver handling og statusvinduet så længe den er slukket og nødvendig. Ingen advarsel når en anden tændt produktion dækker, lageret rækker over 60 dage, kæden selv slukkede den eller den var slukket ved indlæsning. Tjekket kører i kædens tick også uden kæder; ren logik i adDependency.lua.
441 lines
20 KiB
Lua
441 lines
20 KiB
Lua
-- AD Storage Stats
|
|
-- Rene beregninger (ingen spil-API) til fanen Lagre: pr. vare i en bygning fyldning, værdi nu og
|
|
-- ved årets toppris, gårdens samlede beholdning og hvor mange døgn den rækker ved gårdens nettoforbrug.
|
|
|
|
ADStorageStats = {}
|
|
|
|
-- item = {fillType, level, capacity}; farmTotal = liter af varen på hele gården;
|
|
-- rate = {consumption, production} liter pr. døgn (eller nil); quote = {price, top} pr. liter (eller nil).
|
|
function ADStorageStats.itemStats(item, farmTotal, rate, quote)
|
|
local level = item.level or 0
|
|
local capacity = item.capacity or 0
|
|
local consumption = rate ~= nil and rate.consumption or 0
|
|
local production = rate ~= nil and rate.production or 0
|
|
-- palleskure: fyldning i paller af pladser (kapaciteten er et antal pladser, ikke liter)
|
|
local percent = capacity > 0 and math.floor(100 * level / capacity + 0.5) or nil
|
|
if item.count ~= nil and (item.slots or 0) > 0 then
|
|
percent = math.floor(100 * item.count / item.slots + 0.5)
|
|
end
|
|
return {
|
|
fillType = item.fillType,
|
|
level = level,
|
|
capacity = capacity,
|
|
count = item.count,
|
|
slots = item.slots,
|
|
percent = percent,
|
|
valueNow = quote ~= nil and quote.price ~= nil and math.floor(level * quote.price + 0.5) or nil,
|
|
valueTop = quote ~= nil and quote.top ~= nil and math.floor(level * quote.top + 0.5) or nil,
|
|
farmTotal = farmTotal or level,
|
|
consumption = consumption,
|
|
production = production,
|
|
net = production - consumption,
|
|
-- døgn til tom ved gårdens NETTO-forbrug; produktion ≥ forbrug = beholdningen stiger
|
|
days = consumption > production and (farmTotal or level) / (consumption - production) or nil,
|
|
rising = consumption > 0 and production >= consumption,
|
|
}
|
|
end
|
|
|
|
function ADStorageStats.buildingTotals(stats)
|
|
local now, top = 0, 0
|
|
for _, stat in ipairs(stats) do
|
|
now = now + (stat.valueNow or 0)
|
|
top = top + (stat.valueTop or 0)
|
|
end
|
|
return {valueNow = now, valueTop = top}
|
|
end
|
|
|
|
-- inventory = { {entries = {{fillType, level}}} } -> {[fillType] = liter}
|
|
function ADStorageStats.farmTotals(inventory)
|
|
local totals = {}
|
|
for _, building in ipairs(inventory or {}) do
|
|
for _, entry in ipairs(building.entries or {}) do
|
|
totals[entry.fillType] = (totals[entry.fillType] or 0) + (entry.level or 0)
|
|
end
|
|
end
|
|
return totals
|
|
end
|
|
|
|
-- Et stald-trug æder `perDay` liter i alt; fordel på fodertyperne efter hvad truget indeholder.
|
|
-- Tomt trug: alt på foderet med højest vægt (det der hentes først).
|
|
function ADStorageStats.splitTroughConsumption(perDay, levels, weights)
|
|
local total = 0
|
|
for _, level in pairs(levels or {}) do total = total + (level or 0) end
|
|
local result = {}
|
|
if total > 0 then
|
|
for fillType, level in pairs(levels) do
|
|
if (level or 0) > 0 then result[fillType] = perDay * level / total end
|
|
end
|
|
return result
|
|
end
|
|
local best, bestWeight = nil, -1
|
|
for fillType, weight in pairs(weights or {}) do
|
|
if weight > bestWeight or (weight == bestWeight and fillType < best) then best, bestWeight = fillType, weight end
|
|
end
|
|
if best ~= nil then result[best] = perDay end
|
|
return result
|
|
end
|
|
|
|
-- Deler en liste i rækker á `size` celler (gitter i fanen Lagre). Tom liste giver én tom række,
|
|
-- så bygningen stadig får sin navnerække.
|
|
function ADStorageStats.chunk(list, size)
|
|
local rows, current = {}, {}
|
|
for _, value in ipairs(list or {}) do
|
|
table.insert(current, value)
|
|
if #current == size then
|
|
table.insert(rows, current)
|
|
current = {}
|
|
end
|
|
end
|
|
if #current > 0 or #rows == 0 then
|
|
table.insert(rows, current)
|
|
end
|
|
return rows
|
|
end
|
|
|
|
-- "-14.4k +3.6k": forbrug med minus, produktion med plus pr. døgn; intet = "-".
|
|
function ADStorageStats.rateText(consumption, production, format)
|
|
local parts = {}
|
|
if (consumption or 0) > 0 then table.insert(parts, "-" .. format(consumption)) end
|
|
if (production or 0) > 0 then table.insert(parts, "+" .. format(production)) end
|
|
return #parts > 0 and table.concat(parts, " ") or "-"
|
|
end
|
|
|
|
-- Bygninger af én type (fanerne Fabrikker/Siloer/Stalde/Palleskure); rækkefølgen bevares.
|
|
function ADStorageStats.filterKind(inventory, kind)
|
|
local result = {}
|
|
for _, entry in ipairs(inventory or {}) do
|
|
if entry.kind == kind then table.insert(result, entry) end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- ---- vareoversigt (v1.52): én række pr. vare, der folder sig ud til bygningerne ----
|
|
|
|
-- Tærskler for farverne: døgn til tom (gårdens netto), fyldning i % (produceret vare / stald-trug).
|
|
ADStorageStats.DAYS_RED, ADStorageStats.DAYS_YELLOW = 1, 3
|
|
ADStorageStats.FULL_RED, ADStorageStats.FULL_YELLOW = 95, 85
|
|
ADStorageStats.BARN_LOW_RED, ADStorageStats.BARN_LOW_YELLOW = 10, 25
|
|
-- "Sælg nu": prisen er mindst 90 % af årets top, og partiet er værd at køre med
|
|
ADStorageStats.SELL_RATIO, ADStorageStats.SELL_MIN_VALUE = 0.9, 10000
|
|
|
|
local function percentOf(level, capacity)
|
|
return (capacity or 0) > 0 and math.floor(100 * (level or 0) / capacity + 0.5) or nil
|
|
end
|
|
|
|
-- inventory (fanens bygninger) -> { {fillType, title, level, capacity, percent, count, isOutput,
|
|
-- buildings = {{name, placeable, level, capacity, percent, count, slots, isOutput}}} } — varer efter titel,
|
|
-- bygninger med mest først. count (paller) summeres kun når alle bygninger melder paller.
|
|
function ADStorageStats.groupByFillType(inventory, titleOf)
|
|
local byType, groups = {}, {}
|
|
for _, building in ipairs(inventory or {}) do
|
|
for _, item in ipairs(building.entries or {}) do
|
|
local group = byType[item.fillType]
|
|
if group == nil then
|
|
group = {fillType = item.fillType, title = tostring(titleOf(item.fillType) or item.fillType),
|
|
level = 0, capacity = 0, count = 0, isOutput = false, buildings = {}}
|
|
byType[item.fillType] = group
|
|
table.insert(groups, group)
|
|
end
|
|
group.level = group.level + (item.level or 0)
|
|
group.capacity = group.capacity + (item.capacity or 0)
|
|
group.count = (group.count ~= nil and item.count ~= nil) and group.count + item.count or nil
|
|
group.isOutput = group.isOutput or item.isOutput == true
|
|
-- skur uden pladsgrænse har kapacitet 0: så er varens samlede procent ukendt
|
|
if (item.capacity or 0) <= 0 and (item.level or 0) > 0 then group.unbounded = true end
|
|
local percent = percentOf(item.level, item.capacity)
|
|
if item.count ~= nil and (item.slots or 0) > 0 then percent = percentOf(item.count, item.slots) end
|
|
table.insert(group.buildings, {name = building.name, placeable = building.placeable, level = item.level or 0,
|
|
capacity = item.capacity or 0, percent = percent, count = item.count, slots = item.slots, isOutput = item.isOutput == true})
|
|
end
|
|
end
|
|
for _, group in ipairs(groups) do
|
|
group.percent = not group.unbounded and percentOf(group.level, group.capacity) or nil
|
|
table.sort(group.buildings, function(a, b)
|
|
if a.level ~= b.level then return a.level > b.level end
|
|
return tostring(a.name) < tostring(b.name)
|
|
end)
|
|
end
|
|
table.sort(groups, function(a, b)
|
|
if a.title ~= b.title then return a.title < b.title end
|
|
return a.fillType < b.fillType
|
|
end)
|
|
return groups
|
|
end
|
|
|
|
-- inventory -> {[fillType] = kapacitet i liter} for hele gården
|
|
function ADStorageStats.farmCapacities(inventory)
|
|
local totals = {}
|
|
for _, building in ipairs(inventory or {}) do
|
|
for _, entry in ipairs(building.entries or {}) do
|
|
totals[entry.fillType] = (totals[entry.fillType] or 0) + (entry.capacity or 0)
|
|
end
|
|
end
|
|
return totals
|
|
end
|
|
|
|
function ADStorageStats.emptyLevel(days)
|
|
if days == nil then return nil end
|
|
if days < ADStorageStats.DAYS_RED then return "red" end
|
|
if days < ADStorageStats.DAYS_YELLOW then return "yellow" end
|
|
return nil
|
|
end
|
|
|
|
-- Fuldt er kun et problem for noget der bliver produceret (fyldes løbende); en fuld lagersilo er bare lager.
|
|
function ADStorageStats.fullLevel(percent, producing)
|
|
if not producing or percent == nil then return nil end
|
|
if percent >= ADStorageStats.FULL_RED then return "red" end
|
|
if percent >= ADStorageStats.FULL_YELLOW then return "yellow" end
|
|
return nil
|
|
end
|
|
|
|
local SEVERITY_RANK = {red = 2, yellow = 1}
|
|
|
|
function ADStorageStats.worst(a, b)
|
|
return (SEVERITY_RANK[a] or 0) >= (SEVERITY_RANK[b] or 0) and a or b
|
|
end
|
|
|
|
-- Én bygnings bjælke: stald-trug der tømmes (ikke mælk/gylle) er rødt når næsten tomt; produceret
|
|
-- vare/fabriksoutput er rødt når næsten fuldt.
|
|
function ADStorageStats.buildingLevel(building, kind, isStableOutput)
|
|
local percent = building.percent
|
|
if percent == nil then return nil end
|
|
if kind == "husbandry" and not isStableOutput then
|
|
if percent <= ADStorageStats.BARN_LOW_RED then return "red" end
|
|
if percent <= ADStorageStats.BARN_LOW_YELLOW then return "yellow" end
|
|
return nil
|
|
end
|
|
return ADStorageStats.fullLevel(percent, building.isOutput == true or isStableOutput)
|
|
end
|
|
|
|
-- group fra groupByFillType; farm = {level, capacity} for varen på hele gården (nil = kun fanens tal);
|
|
-- rate = {consumption, production} pr. døgn; quote = {price, top} pr. liter.
|
|
function ADStorageStats.groupStats(group, farm, rate, quote)
|
|
local farmLevel = farm ~= nil and farm.level or group.level
|
|
local farmCapacity = farm ~= nil and farm.capacity or group.capacity
|
|
local consumption = rate ~= nil and rate.consumption or 0
|
|
local production = rate ~= nil and rate.production or 0
|
|
local net = production - consumption
|
|
local stat = {}
|
|
for key, value in pairs(group) do stat[key] = value end
|
|
stat.farmLevel, stat.farmCapacity = farmLevel, farmCapacity
|
|
stat.consumption, stat.production, stat.net = consumption, production, net
|
|
stat.days = consumption > production and farmLevel / (consumption - production) or nil
|
|
stat.daysToFull = net > 0 and (farmCapacity or 0) > 0 and math.max(0, farmCapacity - farmLevel) / net or nil
|
|
stat.producing = group.isOutput == true or net > 0
|
|
if quote ~= nil and quote.price ~= nil then
|
|
stat.valueNow = math.floor(group.level * quote.price + 0.5)
|
|
stat.valueTop = math.floor(group.level * (quote.top or quote.price) + 0.5)
|
|
stat.priceRatio = (quote.top or 0) > 0 and quote.price / quote.top or nil
|
|
end
|
|
stat.emptyLevel = ADStorageStats.emptyLevel(stat.days)
|
|
stat.fullLevel = ADStorageStats.fullLevel(group.percent, stat.producing)
|
|
stat.severity = ADStorageStats.worst(stat.emptyLevel, stat.fullLevel)
|
|
return stat
|
|
end
|
|
|
|
-- Rangering: rød tom > rød fuld > gul tom > gul fuld > sælg nu (største beløb først).
|
|
-- Kortest tid til tom / mest fyldt vinder inden for samme trin.
|
|
local function attentionEntries(stat)
|
|
local entries = {}
|
|
if stat.emptyLevel ~= nil then
|
|
local base = stat.emptyLevel == "red" and 4000 or 2000
|
|
table.insert(entries, {kind = "empty", level = stat.emptyLevel, stat = stat, score = base - math.min(999, (stat.days or 0) * 100)})
|
|
end
|
|
if stat.fullLevel ~= nil then
|
|
local base = stat.fullLevel == "red" and 3000 or 1000
|
|
table.insert(entries, {kind = "full", level = stat.fullLevel, stat = stat, score = base + (stat.percent or 0)})
|
|
end
|
|
-- sælg kun det gården ikke selv æder af
|
|
if stat.priceRatio ~= nil and stat.priceRatio >= ADStorageStats.SELL_RATIO and (stat.valueNow or 0) >= ADStorageStats.SELL_MIN_VALUE
|
|
and (stat.net or 0) >= 0 then
|
|
table.insert(entries, {kind = "sell", level = "green", stat = stat, score = math.min(999, stat.valueNow / 1e6)})
|
|
end
|
|
return entries
|
|
end
|
|
|
|
function ADStorageStats.attentionList(stats, max)
|
|
local list = {}
|
|
for _, stat in ipairs(stats or {}) do
|
|
for _, entry in ipairs(attentionEntries(stat)) do table.insert(list, entry) end
|
|
end
|
|
return ADStorageStats.rankAttention(list, max)
|
|
end
|
|
|
|
-- Kæde-forslag ligger ikke på en vare (ingen stat): navnet til den stabile rækkefølge tages fra teksten.
|
|
local function entryName(entry)
|
|
return tostring(entry.stat ~= nil and entry.stat.title or entry.text)
|
|
end
|
|
|
|
function ADStorageStats.rankAttention(list, max)
|
|
table.sort(list, function(a, b)
|
|
if a.score ~= b.score then return a.score > b.score end
|
|
return entryName(a) < entryName(b)
|
|
end)
|
|
local result = {}
|
|
for index = 1, math.min(#list, max or #list) do result[index] = list[index] end
|
|
return result
|
|
end
|
|
|
|
-- "Kræver handling" fra kæden (ADChain.last): ÉT gult punkt "Kæde: N forslag" (klik -> fanen Kæder), så
|
|
-- forslagene ikke fylder listen. Rangeres under alle røde punkter (>= 3000), øverst blandt de gule, over "sælg nu".
|
|
ADStorageStats.CHAIN_SCORE = 2000
|
|
-- Manglende led: én rød post pr. kæde ("Brød mangler et led: Mel"), højst MAX_CHAIN_PROBLEMS (resten står i
|
|
-- fanen Kæder; den sidste viste post får more = antal skjulte). Score CHAIN_PROBLEM_SCORE + alvor (0-99): under
|
|
-- "rød tom" (3900-4000: løber tør inden et døgn), over "rød fuld" (3000-3100). Alvor = manglende andel af behovet;
|
|
-- kredsløb (kan ikke beregnes) = 99.
|
|
ADStorageStats.CHAIN_PROBLEM_SCORE = 3500
|
|
ADStorageStats.MAX_CHAIN_PROBLEMS = 2
|
|
ADStorageStats.MAX_CHAIN_PROBLEM_ITEMS = 2
|
|
|
|
local function severity(problems)
|
|
local missing, need = 0, 0
|
|
for _, problem in ipairs(problems) do
|
|
if problem.kind == "cycle" then return 99 end
|
|
missing, need = missing + (problem.detail or 0), need + (problem.need or 0)
|
|
end
|
|
if need <= 0 then return 99 end
|
|
return math.floor(99 * math.min(1, missing / need))
|
|
end
|
|
|
|
local function problemEntry(root, problems, titleOf)
|
|
local items, seen = {}, {}
|
|
for _, problem in ipairs(problems) do
|
|
local title = titleOf(problem.fillType)
|
|
if not seen[title] then
|
|
seen[title] = true
|
|
table.insert(items, title)
|
|
end
|
|
end
|
|
local shown = {}
|
|
for index = 1, math.min(#items, ADStorageStats.MAX_CHAIN_PROBLEM_ITEMS) do shown[index] = items[index] end
|
|
local itemsText = table.concat(shown, ", ")
|
|
if #items > #shown then itemsText = itemsText .. " +" .. tostring(#items - #shown) end
|
|
local rootTitle = titleOf(root.name)
|
|
return {kind = "chainProblem", level = "red", score = ADStorageStats.CHAIN_PROBLEM_SCORE + severity(problems), rootKey = root.key,
|
|
rootTitle = rootTitle, itemsText = itemsText, text = rootTitle .. ": " .. itemsText}
|
|
end
|
|
|
|
local function chainProblemEntries(last)
|
|
local titles = last.titles or {}
|
|
local titleOf = function(name) return tostring(titles[name] or name) end
|
|
local byRoot = {}
|
|
for _, problem in ipairs(last.chain ~= nil and last.chain.problems or {}) do
|
|
if problem.rootKey ~= nil then
|
|
byRoot[problem.rootKey] = byRoot[problem.rootKey] or {}
|
|
table.insert(byRoot[problem.rootKey], problem)
|
|
end
|
|
end
|
|
local entries = {}
|
|
for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do
|
|
if byRoot[root.key] ~= nil then table.insert(entries, problemEntry(root, byRoot[root.key], titleOf)) end
|
|
end
|
|
entries = ADStorageStats.rankAttention(entries)
|
|
local result = {}
|
|
for index = 1, math.min(#entries, ADStorageStats.MAX_CHAIN_PROBLEMS) do result[index] = entries[index] end
|
|
if #entries > #result then result[#result].more = #entries - #result end
|
|
return result
|
|
end
|
|
|
|
-- "Slukket men nødvendig" (ADChain.last.dependency.offNeeded): én rød post pr. fabrik, højst MAX_DEPENDENCY_ENTRIES
|
|
-- (den sidste får more = antal skjulte). Score DEPENDENCY_SCORE + hast (0-99, færre lagerdage = højere; ukendt = 50):
|
|
-- over manglende led (3500-3599), under "rød tom" (3900-4000).
|
|
ADStorageStats.DEPENDENCY_SCORE = 3700
|
|
ADStorageStats.MAX_DEPENDENCY_ENTRIES = 2
|
|
|
|
local function urgency(days)
|
|
if days == nil then return 50 end
|
|
if days == math.huge then return 0 end
|
|
return math.max(0, 99 - math.min(99, math.floor(days)))
|
|
end
|
|
|
|
function ADStorageStats.dependencyEntries(last)
|
|
local entries = {}
|
|
for _, item in ipairs(last ~= nil and last.dependency ~= nil and last.dependency.offNeeded or {}) do
|
|
local name = tostring(item.buildingName or item.building or "?")
|
|
table.insert(entries, {kind = "dependencyOff", level = "red", score = ADStorageStats.DEPENDENCY_SCORE + urgency(item.days),
|
|
building = item.building, buildingName = name, fillType = item.fillType, onlyChain = item.onlyChain == true, text = name})
|
|
end
|
|
entries = ADStorageStats.rankAttention(entries)
|
|
local result = {}
|
|
for index = 1, math.min(#entries, ADStorageStats.MAX_DEPENDENCY_ENTRIES) do result[index] = entries[index] end
|
|
if #entries > #result then result[#result].more = #entries - #result end
|
|
return result
|
|
end
|
|
|
|
function ADStorageStats.chainEntries(last)
|
|
if last == nil then return {} end
|
|
local result = chainProblemEntries(last)
|
|
for _, entry in ipairs(ADStorageStats.dependencyEntries(last)) do table.insert(result, entry) end
|
|
local count = 0
|
|
for _, action in ipairs(last.actions or {}) do
|
|
if action.mode == "suggest" then count = count + 1 end
|
|
end
|
|
if count > 0 then
|
|
table.insert(result, {kind = "chainSuggest", level = "yellow", score = ADStorageStats.CHAIN_SCORE, count = count,
|
|
text = string.format("Kæde: %d", count)})
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Varepunkterne (allerede rangeret) + kædens punkter, samme loft.
|
|
function ADStorageStats.mergeAttention(attention, chainEntries, max)
|
|
local list = {}
|
|
for _, entry in ipairs(attention or {}) do table.insert(list, entry) end
|
|
for _, entry in ipairs(chainEntries or {}) do table.insert(list, entry) end
|
|
return ADStorageStats.rankAttention(list, max)
|
|
end
|
|
|
|
function ADStorageStats.kpis(stats)
|
|
local kpi = {valueNow = 0, gain = 0, emptyCount = 0, fullCount = 0}
|
|
for _, stat in ipairs(stats or {}) do
|
|
kpi.valueNow = kpi.valueNow + (stat.valueNow or 0)
|
|
kpi.gain = kpi.gain + math.max(0, (stat.valueTop or 0) - (stat.valueNow or 0))
|
|
if stat.emptyLevel ~= nil then kpi.emptyCount = kpi.emptyCount + 1 end
|
|
if stat.fullLevel ~= nil then kpi.fullCount = kpi.fullCount + 1 end
|
|
end
|
|
return kpi
|
|
end
|
|
|
|
-- procent -> bjælkens brøk i [0,1]
|
|
function ADStorageStats.fraction(percent)
|
|
if percent == nil then return 0 end
|
|
return math.max(0, math.min(1, percent / 100))
|
|
end
|
|
|
|
-- ---- "kræver handling" -> flow ----
|
|
|
|
-- Bygninger der BRUGER varen (fabrik-input, stald-foder) — silo/objektlager lagrer kun. -> {{name, placeable, level}}
|
|
function ADStorageStats.consumersOf(inventory, fillType)
|
|
local result = {}
|
|
for _, building in ipairs(inventory or {}) do
|
|
if building.kind == "factory" or building.kind == "husbandry" then
|
|
for _, entry in ipairs(building.entries or {}) do
|
|
if entry.fillType == fillType and not entry.isOutput then
|
|
table.insert(result, {name = building.name, placeable = building.placeable, level = entry.level or 0})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Hjælper et flow? Et flow flytter kun varer: det nytter når varen ligger andre steder end hos forbrugerne.
|
|
-- s = {farmLevel, consumerLevel, perDay, consumerCount, existingRunId} -> "open" | "flow" | "produce" | "none"
|
|
ADStorageStats.MIN_FLOW_LITERS = 5000
|
|
ADStorageStats.MIN_FLOW_DAY_SHARE = 0.25 -- mindst en kvart dags forbrug skal kunne flyttes
|
|
|
|
function ADStorageStats.flowAdvice(s)
|
|
if (s.consumerCount or 0) == 0 then return "none" end
|
|
if s.existingRunId ~= nil then return "open" end
|
|
local elsewhere = (s.farmLevel or 0) - (s.consumerLevel or 0)
|
|
local needed = math.max(ADStorageStats.MIN_FLOW_LITERS, (s.perDay or 0) * ADStorageStats.MIN_FLOW_DAY_SHARE)
|
|
return elsewhere >= needed and "flow" or "produce"
|
|
end
|
|
|
|
-- "Sælg nu" -> udkørsels-flow: s = {sourceCount, existingRunId} -> "open" | "flow" | "none"
|
|
function ADStorageStats.sellAdvice(s)
|
|
if (s.sourceCount or 0) == 0 then return "none" end
|
|
if s.existingRunId ~= nil then return "open" end
|
|
return "flow"
|
|
end
|