92 lines
4.4 KiB
Lua
92 lines
4.4 KiB
Lua
-- AD Crop Plan (ren logik)
|
|
-- Mangel pr. afgrøde over 12 måneder (lager + forventet høst mod kædens behov) og valg af marker der skal skifte
|
|
-- målafgrøde: "fri til kæden" -> auto, ellers forslag. Brugerens egen målafgrøde ændres aldrig automatisk.
|
|
|
|
ADCropPlan = {}
|
|
ADCropPlan.HORIZON_MONTHS = 12
|
|
ADCropPlan.MIN_SHARE = 0.10
|
|
ADCropPlan.MIN_HA = 0.5
|
|
-- højst så mange forslag (ikke auto-skift) pr. afgrøde, så listen ikke vokser til snesevis af rækker
|
|
ADCropPlan.MAX_SUGGEST_PER_FRUIT = 3
|
|
|
|
function ADCropPlan.plannedFruit(field)
|
|
if field.targetFruit ~= nil and field.targetFruit ~= "" then return field.targetFruit end
|
|
return field.fruit
|
|
end
|
|
|
|
function ADCropPlan.shortages(demand, stock, fields, yields)
|
|
local list = {}
|
|
for fruit, perMonth in pairs(demand or {}) do
|
|
local perHa = (yields or {})[fruit] or 0
|
|
if perHa > 0 and (perMonth or 0) > 0 then
|
|
local need = perMonth * ADCropPlan.HORIZON_MONTHS
|
|
local supply = (stock or {})[fruit] or 0
|
|
for _, field in ipairs(fields or {}) do
|
|
if ADCropPlan.plannedFruit(field) == fruit then supply = supply + (field.areaHa or 0) * perHa end
|
|
end
|
|
local missing = need - supply
|
|
local missingHa = missing / perHa
|
|
if missing > need * ADCropPlan.MIN_SHARE and missingHa > ADCropPlan.MIN_HA then
|
|
table.insert(list, {fruit = fruit, needLiters = need, supplyLiters = supply, missingLiters = missing, missingHa = missingHa})
|
|
end
|
|
end
|
|
end
|
|
table.sort(list, function(a, b)
|
|
if a.missingHa ~= b.missingHa then return a.missingHa > b.missingHa end
|
|
return a.fruit < b.fruit
|
|
end)
|
|
return list
|
|
end
|
|
|
|
local function isCandidate(field, used, dismissedFruit, fruit)
|
|
if field.permanent or field.excluded then return false end
|
|
local planned = ADCropPlan.plannedFruit(field)
|
|
if planned ~= nil and planned ~= "" and used[planned] then return false end
|
|
return dismissedFruit ~= fruit
|
|
end
|
|
|
|
-- prices: {[afgrøde] = pris} eller en funktion afgrøde -> pris (slås kun op for kandidaternes afgrøder).
|
|
-- harvesting = {[afgrøde] = true}: afgrøder der høstes lige nu — ingen forslag/skift for dem (læssene er på vej).
|
|
function ADCropPlan.choose(shortages, fields, usedFruits, yields, prices, dismissed, harvesting)
|
|
local actions, taken = {}, {}
|
|
local used = usedFruits or {}
|
|
local priceOf = type(prices) == "function" and prices or function(fruit) return (prices or {})[fruit] or 0 end
|
|
local priceCache = {}
|
|
local function valueOf(fruit)
|
|
if fruit == "" then return 0 end
|
|
if priceCache[fruit] == nil then priceCache[fruit] = ((yields or {})[fruit] or 0) * (priceOf(fruit) or 0) end
|
|
return priceCache[fruit]
|
|
end
|
|
for _, shortage in ipairs(shortages or {}) do
|
|
local remaining = (harvesting or {})[shortage.fruit] and 0 or shortage.missingHa
|
|
local candidates = {}
|
|
for _, field in ipairs(fields or {}) do
|
|
if not taken[field.id] and isCandidate(field, used, (dismissed or {})[field.id], shortage.fruit) then
|
|
local planned = ADCropPlan.plannedFruit(field) or ""
|
|
local value = valueOf(planned)
|
|
table.insert(candidates, {field = field, value = value})
|
|
end
|
|
end
|
|
table.sort(candidates, function(a, b)
|
|
if a.value ~= b.value then return a.value < b.value end
|
|
local da, db = math.abs((a.field.areaHa or 0) - remaining), math.abs((b.field.areaHa or 0) - remaining)
|
|
if da ~= db then return da < db end
|
|
return a.field.id < b.field.id
|
|
end)
|
|
local suggested = 0
|
|
for _, candidate in ipairs(candidates) do
|
|
if remaining <= 0 then break end
|
|
local field = candidate.field
|
|
local auto = field.chainFree == true and field.targetSource ~= "user"
|
|
if auto or suggested < ADCropPlan.MAX_SUGGEST_PER_FRUIT then
|
|
table.insert(actions, {fieldId = field.id, from = ADCropPlan.plannedFruit(field) or "", to = shortage.fruit,
|
|
hectares = field.areaHa or 0, reason = shortage.fruit, mode = auto and "auto" or "suggest"})
|
|
taken[field.id] = true
|
|
remaining = remaining - (field.areaHa or 0)
|
|
if not auto then suggested = suggested + 1 end
|
|
end
|
|
end
|
|
end
|
|
return actions
|
|
end
|