fix(kæde): højst 3 beskeder og samlede poster i Kræver handling

notifyProblems sender højst 3 beskeder pr. tick og samler resten i én
("+N manglende led — se fanen Kæder"). Alle kædeproblemer står i én rød post
("Kæder: N manglende led", scoret under rød fuld; klik folder kæderne ud) og
slukkede nødvendige fabrikker i højst én post ("Slukket men nødvendig: X +k").
This commit is contained in:
2026-09-30 15:27:08 +02:00
parent d2a4cf0d23
commit b33f2a3b01
6 changed files with 48 additions and 57 deletions
+9 -2
View File
@@ -391,6 +391,8 @@ end
-- Engangsbesked pr. nyt manglende led (kun fra tick). Ikke i opstartsfristen og ikke mens gården er læst delvist -- Engangsbesked pr. nyt manglende led (kun fra tick). Ikke i opstartsfristen og ikke mens gården er læst delvist
-- eller markscanningen kører (ellers falske alarmer lige efter indlæsning) — så huskes intet, og det meldes senere. -- eller markscanningen kører (ellers falske alarmer lige efter indlæsning) — så huskes intet, og det meldes senere.
-- Højst MAX_PROBLEM_NOTES beskeder pr. tick; resten samles i én besked (alle logges stadig).
ADChain.MAX_PROBLEM_NOTES = 3
function ADChain.notifyProblems(last) function ADChain.notifyProblems(last)
if last == nil or ADChain.now < ADChain.STARTUP_GRACE_MS then return 0 end if last == nil or ADChain.now < ADChain.STARTUP_GRACE_MS then return 0 end
if last.fieldsIncomplete or (last.data ~= nil and last.data.partial) then return 0 end if last.fieldsIncomplete or (last.data ~= nil and last.data.partial) then return 0 end
@@ -403,11 +405,16 @@ function ADChain.notifyProblems(last)
for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do
rootNames[root.key] = tostring((last.titles or {})[root.name] or root.name) rootNames[root.key] = tostring((last.titles or {})[root.name] or root.name)
end end
for _, problem in ipairs(fresh) do for index, problem in ipairs(fresh) do
-- kædens navn foran, så to kæder der mangler samme vare kan skelnes -- kædens navn foran, så to kæder der mangler samme vare kan skelnes
local text = tostring(rootNames[problem.rootKey] or problem.rootKey) .. ": " .. ADChainView.problemText(problem, last, textOf, nil, fmt) local text = tostring(rootNames[problem.rootKey] or problem.rootKey) .. ": " .. ADChainView.problemText(problem, last, textOf, nil, fmt)
Logging.info(ADChain.LOG_PREFIX .. "manglende led: %s", text) Logging.info(ADChain.LOG_PREFIX .. "manglende led: %s", text)
ADFieldJobs.notify(text, true) if index <= ADChain.MAX_PROBLEM_NOTES then ADFieldJobs.notify(text, true) end
end
-- resten samles i én besked (fx mange fermenteringssiloer efter indlæsning); alle står i fanen Kæder
if #fresh > ADChain.MAX_PROBLEM_NOTES then
local ok, text = pcall(string.format, textOf("spu_chain_notifyMore"), #fresh - ADChain.MAX_PROBLEM_NOTES)
ADFieldJobs.notify(ok and text or ("+" .. tostring(#fresh - ADChain.MAX_PROBLEM_NOTES)), true)
end end
return #fresh return #fresh
end end
+25 -44
View File
@@ -279,13 +279,11 @@ end
-- "Kræver handling" fra kæden (ADChain.last): ÉT gult punkt "Kæde: N forslag" (klik -> fanen Kæder), så -- "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". -- forslagene ikke fylder listen. Rangeres under alle røde punkter (>= 3000), øverst blandt de gule, over "sælg nu".
ADStorageStats.CHAIN_SCORE = 2000 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 -- Manglende led: ALLE kædeproblemer i ÉN rød post ("Kæder: N manglende led", klik -> fanen Kæder med kæderne
-- fanen Kæder; den sidste viste post får more = antal skjulte). Score CHAIN_PROBLEM_SCORE + alvor (0-99): under -- foldet ud), så en gård med mange kæder (fx fermenteringssiloer) ikke fylder "Kræver handling". N = unikke
-- "rød tom" (3900-4000: løber tør inden et døgn), over "rød fuld" (3000-3100). Alvor = manglende andel af behovet; -- (kæde, vare). Score CHAIN_PROBLEM_SCORE + alvor (0-99): under "rød fuld" (3000-3100), så lagerets røde poster
-- kredsløb (kan ikke beregnes) = 99. -- altid vises først, og over de gule (<= 2000). Alvor = manglende andel af behovet; kredsløb = 99.
ADStorageStats.CHAIN_PROBLEM_SCORE = 3500 ADStorageStats.CHAIN_PROBLEM_SCORE = 2900
ADStorageStats.MAX_CHAIN_PROBLEMS = 2
ADStorageStats.MAX_CHAIN_PROBLEM_ITEMS = 2
local function severity(problems) local function severity(problems)
local missing, need = 0, 0 local missing, need = 0, 0
@@ -297,50 +295,33 @@ local function severity(problems)
return math.floor(99 * math.min(1, missing / need)) return math.floor(99 * math.min(1, missing / need))
end 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 function chainProblemEntries(last)
local titles = last.titles or {} local known = {}
local titleOf = function(name) return tostring(titles[name] or name) end for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do known[root.key] = true end
local byRoot = {} local problems, seen, rootKeys, rootSeen = {}, {}, {}, {}
for _, problem in ipairs(last.chain ~= nil and last.chain.problems or {}) do for _, problem in ipairs(last.chain ~= nil and last.chain.problems or {}) do
if problem.rootKey ~= nil then if problem.rootKey ~= nil and known[problem.rootKey] then
byRoot[problem.rootKey] = byRoot[problem.rootKey] or {} table.insert(problems, problem)
table.insert(byRoot[problem.rootKey], problem) seen[tostring(problem.rootKey) .. "|" .. tostring(problem.fillType)] = true
if not rootSeen[problem.rootKey] then
rootSeen[problem.rootKey] = true
table.insert(rootKeys, problem.rootKey)
end
end end
end end
local entries = {} if #problems == 0 then return {} end
for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do local count = 0
if byRoot[root.key] ~= nil then table.insert(entries, problemEntry(root, byRoot[root.key], titleOf)) end for _ in pairs(seen) do count = count + 1 end
end table.sort(rootKeys, function(a, b) return tostring(a) < tostring(b) end)
entries = ADStorageStats.rankAttention(entries) return {{kind = "chainProblem", level = "red", score = ADStorageStats.CHAIN_PROBLEM_SCORE + severity(problems), count = count,
local result = {} rootKeys = rootKeys, text = string.format("Kæder: %d", count)}}
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 end
-- "Slukket men nødvendig" (ADChain.last.dependency.offNeeded): én rød post pr. fabrik, højst MAX_DEPENDENCY_ENTRIES -- "Slukket men nødvendig" (ADChain.last.dependency.offNeeded): højst MAX_DEPENDENCY_ENTRIES post (den mest hastende
-- (den sidste får more = antal skjulte). Score DEPENDENCY_SCORE + hast (0-99, færre lagerdage = højere; ukendt = 50): -- fabrik; more = antal skjulte, vist som "+k"). Score DEPENDENCY_SCORE + hast (0-99, færre lagerdage = højere;
-- over manglende led (3500-3599), under "rød tom" (3900-4000). -- ukendt = 50): over "rød fuld" (3000-3100), under "rød tom" (3900-4000).
ADStorageStats.DEPENDENCY_SCORE = 3700 ADStorageStats.DEPENDENCY_SCORE = 3700
ADStorageStats.MAX_DEPENDENCY_ENTRIES = 2 ADStorageStats.MAX_DEPENDENCY_ENTRIES = 1
local function urgency(days) local function urgency(days)
if days == nil then return 50 end if days == nil then return 50 end
@@ -106,10 +106,10 @@ end
function SmartPickupFrame:onAttentionClick(entry) function SmartPickupFrame:onAttentionClick(entry)
self:safe(function(frame) self:safe(function(frame)
if entry ~= nil and (entry.kind == "chainSuggest" or entry.kind == "chainProblem") then if entry ~= nil and (entry.kind == "chainSuggest" or entry.kind == "chainProblem") then
-- manglende led: kæden foldes ud, så problemteksten står under den røde linje -- manglende led: kæderne med problemer foldes ud, så problemteksten står under den røde linje
if entry.kind == "chainProblem" and entry.rootKey ~= nil then if entry.kind == "chainProblem" then
frame.chainExpanded = frame.chainExpanded or {} frame.chainExpanded = frame.chainExpanded or {}
frame.chainExpanded[entry.rootKey] = true for _, rootKey in ipairs(entry.rootKeys or {}) do frame.chainExpanded[rootKey] = true end
end end
frame:switchTab(SmartPickupFrame.TAB_CHAINS) frame:switchTab(SmartPickupFrame.TAB_CHAINS)
return return
@@ -202,14 +202,13 @@ function SmartPickupFrame:alertText(entry)
return string.format(i18n:getText("spu_attention_chainSuggests"), entry.count or 0) return string.format(i18n:getText("spu_attention_chainSuggests"), entry.count or 0)
end end
if entry.kind == "chainProblem" then if entry.kind == "chainProblem" then
local text = string.format(i18n:getText("spu_attention_chainProblem"), tostring(entry.rootTitle), tostring(entry.itemsText)) return string.format(i18n:getText("spu_attention_chainProblems"), entry.count or 0)
if (entry.more or 0) > 0 then text = text .. string.format(" (+%d)", entry.more) end
return text
end end
if entry.kind == "dependencyOff" then if entry.kind == "dependencyOff" then
local text = string.format(i18n:getText("spu_attention_depOff"), tostring(entry.buildingName)) if (entry.more or 0) > 0 then
if (entry.more or 0) > 0 then text = text .. string.format(" (+%d)", entry.more) end return string.format(i18n:getText("spu_attention_depOffMore"), tostring(entry.buildingName), entry.more)
return text end
return string.format(i18n:getText("spu_attention_depOff"), tostring(entry.buildingName))
end end
if entry.kind == "empty" then if entry.kind == "empty" then
return string.format(i18n:getText("spu_alert_empty"), stat.title, self:formatDays(stat.days), fc(stat.farmLevel or 0), fc(math.abs(stat.net or 0))) return string.format(i18n:getText("spu_alert_empty"), stat.title, self:formatDays(stat.days), fc(stat.farmLevel or 0), fc(math.abs(stat.net or 0)))
+3 -1
View File
@@ -599,6 +599,7 @@
<text name="spu_chain_noField" text="ingen mark"/> <text name="spu_chain_noField" text="ingen mark"/>
<text name="spu_chain_buySource" text="køb"/> <text name="spu_chain_buySource" text="køb"/>
<text name="spu_chain_fromFields" text="fra marker"/> <text name="spu_chain_fromFields" text="fra marker"/>
<text name="spu_chain_notifyMore" text="+%d manglende led — se fanen Kæder"/>
<text name="spu_chain_pause" text="[Pause]"/> <text name="spu_chain_pause" text="[Pause]"/>
<text name="spu_chain_resume" text="[Start]"/> <text name="spu_chain_resume" text="[Start]"/>
<text name="spu_chain_rootOk" text="dækket"/> <text name="spu_chain_rootOk" text="dækket"/>
@@ -616,7 +617,7 @@
<text name="spu_chain_problem_bottleneck" text="%s kan kun lave %s af %s/md"/> <text name="spu_chain_problem_bottleneck" text="%s kan kun lave %s af %s/md"/>
<text name="spu_chain_problem_off" text="%s (%s) er slukket af dig — tænd den eller sæt kæden på pause"/> <text name="spu_chain_problem_off" text="%s (%s) er slukket af dig — tænd den eller sæt kæden på pause"/>
<text name="spu_chain_problem_cycle" text="Kredsløb omkring %s — kæden kan ikke beregnes"/> <text name="spu_chain_problem_cycle" text="Kredsløb omkring %s — kæden kan ikke beregnes"/>
<text name="spu_attention_chainProblem" text="%s mangler et led: %s"/> <text name="spu_attention_chainProblems" text="Kæder: %d manglende led"/>
<text name="input_SPU_TOGGLE_STATUS" text="AD Smart Pickup: statusvindue"/> <text name="input_SPU_TOGGLE_STATUS" text="AD Smart Pickup: statusvindue"/>
<text name="spu_status_flows" text="Flows"/> <text name="spu_status_flows" text="Flows"/>
<text name="spu_status_fields" text="Marker"/> <text name="spu_status_fields" text="Marker"/>
@@ -636,6 +637,7 @@
<text name="spu_dep_warn" text="Advarsel: du slukkede %s (%s) — %s bruger det. Lageret rækker ca. %d dage."/> <text name="spu_dep_warn" text="Advarsel: du slukkede %s (%s) — %s bruger det. Lageret rækker ca. %d dage."/>
<text name="spu_dep_warnNoDays" text="Advarsel: du slukkede %s (%s) — %s bruger det."/> <text name="spu_dep_warnNoDays" text="Advarsel: du slukkede %s (%s) — %s bruger det."/>
<text name="spu_attention_depOff" text="Slukket men nødvendig: %s"/> <text name="spu_attention_depOff" text="Slukket men nødvendig: %s"/>
<text name="spu_attention_depOffMore" text="Slukket men nødvendig: %s +%d"/>
<text name="spu_attention_hintFactories" text=" → klik: se Fabrikker"/> <text name="spu_attention_hintFactories" text=" → klik: se Fabrikker"/>
</texts> </texts>
</l10n> </l10n>
+3 -1
View File
@@ -599,6 +599,7 @@
<text name="spu_chain_noField" text="no field"/> <text name="spu_chain_noField" text="no field"/>
<text name="spu_chain_buySource" text="buy"/> <text name="spu_chain_buySource" text="buy"/>
<text name="spu_chain_fromFields" text="from fields"/> <text name="spu_chain_fromFields" text="from fields"/>
<text name="spu_chain_notifyMore" text="+%d more missing links — see the Chains tab"/>
<text name="spu_chain_pause" text="[Pause]"/> <text name="spu_chain_pause" text="[Pause]"/>
<text name="spu_chain_resume" text="[Resume]"/> <text name="spu_chain_resume" text="[Resume]"/>
<text name="spu_chain_rootOk" text="covered"/> <text name="spu_chain_rootOk" text="covered"/>
@@ -616,7 +617,7 @@
<text name="spu_chain_problem_bottleneck" text="%s can only make %s of %s/mo"/> <text name="spu_chain_problem_bottleneck" text="%s can only make %s of %s/mo"/>
<text name="spu_chain_problem_off" text="%s (%s) was turned off by you — turn it on or pause the chain"/> <text name="spu_chain_problem_off" text="%s (%s) was turned off by you — turn it on or pause the chain"/>
<text name="spu_chain_problem_cycle" text="Loop around %s — the chain cannot be calculated"/> <text name="spu_chain_problem_cycle" text="Loop around %s — the chain cannot be calculated"/>
<text name="spu_attention_chainProblem" text="%s is missing a link: %s"/> <text name="spu_attention_chainProblems" text="Chains: %d missing links"/>
<text name="input_SPU_TOGGLE_STATUS" text="AD Smart Pickup: status window"/> <text name="input_SPU_TOGGLE_STATUS" text="AD Smart Pickup: status window"/>
<text name="spu_status_flows" text="Flows"/> <text name="spu_status_flows" text="Flows"/>
<text name="spu_status_fields" text="Fields"/> <text name="spu_status_fields" text="Fields"/>
@@ -636,6 +637,7 @@
<text name="spu_dep_warn" text="Warning: you switched off %s (%s) — %s uses it. Stock lasts about %d days."/> <text name="spu_dep_warn" text="Warning: you switched off %s (%s) — %s uses it. Stock lasts about %d days."/>
<text name="spu_dep_warnNoDays" text="Warning: you switched off %s (%s) — %s uses it."/> <text name="spu_dep_warnNoDays" text="Warning: you switched off %s (%s) — %s uses it."/>
<text name="spu_attention_depOff" text="Switched off but needed: %s"/> <text name="spu_attention_depOff" text="Switched off but needed: %s"/>
<text name="spu_attention_depOffMore" text="Switched off but needed: %s +%d"/>
<text name="spu_attention_hintFactories" text=" → click: see Factories"/> <text name="spu_attention_hintFactories" text=" → click: see Factories"/>
</texts> </texts>
</l10n> </l10n>