Files
fs25-adsmartpickup/tests/test_adOutboundPlanner.lua
T

161 lines
10 KiB
Lua

-- Kør: luajit tests/test_adOutboundPlanner.lua (fra repo-roden)
-- Ren turvurdering for udkørsel: prisregel, tærskler, politikker, rangering, opsamlingsrunde.
dofile("FS25_ADSmartPickup/adOutboundPlanner.lua")
local P = ADOutboundPlanner
local failures = 0
local function check(name, actual, expected)
if actual == expected then
print("OK " .. name)
else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local function join(list) return table.concat(list or {}, ",") end
local SUGAR, FORAGE, BASKET, CRATE, BROOM = 5, 6, 7, 8, 9
local function input(products, overrides)
local result = {capacity = 20000, hasAutoload = false, sellAtPercent = 90, storeAbovePercent = 80, collectRound = false, products = products}
for key, value in pairs(overrides or {}) do result[key] = value end
return result
end
local function product(name, fillType, policy, sources, quote, store, isPallet)
return {fillType = fillType, name = name, policy = policy, isPallet = isPallet == true, sources = sources, quote = quote, store = store}
end
local quoteS = {percent = 92, price = 480, wayPointId = 900, stationName = "Grain Elevator"}
local storeSilo = {wayPointId = 400, name = "silo 1", free = 500000}
-- P1: salg ved pris over tærskel, uanset mængde
local trip = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 5000, level = 5000, capacity = 180000}}, quoteS, nil)}))
check("P1 salg", trip.kind, "sell")
check("P1 mål", trip.targetWayPointId, 900)
check("P1 målnavn", trip.targetName, "Grain Elevator")
check("P1 kilde", join(trip.sourceIds), "1")
check("P1 vare", join(trip.fillTypes), "5")
check("P1 procent", trip.percent, 92)
-- P2: pris under tærskel, lager lavt -> ingen tur med begrundelse
local low = {percent = 78, price = 400, wayPointId = 900, stationName = "Grain Elevator"}
local trip2, reasons2 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 74000, level = 74000, capacity = 180000}}, low, nil)}))
check("P2 ingen tur", trip2, nil)
check("P2 begrundelse", reasons2[1], "SUGAR 78 % af top (kræver 90), lager 41 %")
-- P3: sellElseStore, pris lav, lager 85 % -> lager
local trip3 = P.plan(input({product("SUGAR", SUGAR, "sellElseStore", {{wayPointId = 1, available = 153000, level = 153000, capacity = 180000}}, low, storeSilo)}))
check("P3 lager", trip3.kind, "store")
check("P3 silo", trip3.targetWayPointId, 400)
-- P4: store med fuldt læs, lager 20 %
local trip4 = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 700000, level = 700000, capacity = 3500000}}, nil, storeSilo)}))
check("P4 fuldt læs -> lager", trip4.kind, "store")
local trip4b = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 15000, level = 15000, capacity = 3500000}}, nil, storeSilo)}))
check("P4 for lidt og lager lavt -> nil", trip4b, nil)
-- P5: rangering: højeste pris-% først
local trip5 = P.plan(input({
product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 1000, level = 1000, capacity = 180000}}, {percent = 91, price = 1, wayPointId = 900, stationName = "A"}, nil),
product("FORAGE", FORAGE, "sell", {{wayPointId = 2, available = 1000, level = 1000, capacity = 3500000}}, {percent = 95, price = 1, wayPointId = 901, stationName = "B"}, nil),
}))
check("P5 højeste procent vinder", join(trip5.fillTypes), "6")
check("P5 uden runde kun én vare", #trip5.fillTypes, 1)
-- P6: intet nåeligt salgssted
local trip6, reasons6 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 170000, level = 170000, capacity = 180000}}, nil, nil)}))
check("P6 ingen tur", trip6, nil)
check("P6 begrundelse", reasons6[1], "SUGAR: intet nåeligt salgssted")
-- P7: ingen silo med plads ved lagerbehov
local trip7, reasons7 = P.plan(input({product("FORAGE", FORAGE, "store", {{wayPointId = 2, available = 3000000, level = 3000000, capacity = 3500000}}, nil, nil)}))
check("P7 ingen tur", trip7, nil)
check("P7 begrundelse", reasons7[1], "FORAGE: intet lager med plads (silo eller egen fabrik der bruger varen)")
-- P8: runde, bulk: samme vare fra flere kilder, mest først
local trip8 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 3000, level = 3000, capacity = 180000}, {wayPointId = 3, available = 9000, level = 9000, capacity = 180000}}, quoteS, nil)}, {collectRound = true, capacity = 10000}))
check("P8 kilder mest først", join(trip8.sourceIds), "3,1")
check("P8 én vare", #trip8.fillTypes, 1)
-- P9: runde, UAL: alle pallevarer med samme mål
local sellS = function(pct) return {percent = pct, price = 1, wayPointId = 950, stationName = "Halm-salg"} end
local sellT = {percent = 96, price = 1, wayPointId = 951, stationName = "Andet"}
local pallets = {
product("STRAW_BASKET", BASKET, "sell", {{wayPointId = 5, available = 8000, level = 8, capacity = 4000}}, sellS(95), nil, true),
product("STRAW_CRATE", CRATE, "sell", {{wayPointId = 5, available = 2000, level = 2, capacity = 4000}}, sellS(93), nil, true),
product("STRAW_BROOM", BROOM, "sell", {{wayPointId = 6, available = 1000, level = 1, capacity = 4000}}, sellT, nil, true),
}
local trip9 = P.plan(input(pallets, {collectRound = true, hasAutoload = true}))
check("P9 højeste procent = kost alene (andet mål)", join(trip9.fillTypes), "9")
local trip9b = P.plan(input({pallets[1], pallets[2]}, {collectRound = true, hasAutoload = true}))
check("P9 kurve+kasser samme mål", join(trip9b.fillTypes), "7,8")
check("P9 navne", join(trip9b.names), "STRAW_BASKET,STRAW_CRATE")
check("P9 kilder unikke", join(trip9b.sourceIds), "5")
local trip9c = P.plan(input({pallets[1], pallets[2]}, {collectRound = true, hasAutoload = false}))
check("P9 uden UAL kun én pallevare", #trip9c.fillTypes, 1)
-- P10: uden runde kun én kilde (den med mest)
local trip10 = P.plan(input({product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 3000, level = 3000, capacity = 180000}, {wayPointId = 3, available = 9000, level = 9000, capacity = 180000}}, quoteS, nil)}))
check("P10 én kilde, den fyldeste", join(trip10.sourceIds), "3")
-- P11: intet på lager
local trip11, reasons11 = P.plan(input({product("SUGAR", SUGAR, "sell", {}, quoteS, nil)}))
check("P11 ingen tur", trip11, nil)
check("P11 begrundelse", reasons11[1], "SUGAR: intet på lager")
-- P12: evaluate
local e = P.evaluate(product("SUGAR", SUGAR, "sell", {{wayPointId = 1, available = 150000, level = 150000, capacity = 180000}}, quoteS, nil), input({}))
check("P12 storagePercent", math.floor(e.storagePercent + 0.5), 83)
check("P12 storageHigh", e.storageHigh, true)
check("P12 fullLoad", e.fullLoad, true)
check("P12 priceOk", e.priceOk, true)
-- P13 (reserve): det gårdens fabrikker/stalde skal bruge, sælges ikke
local BEET = 10
local withReserve = product("SUGARBEET", BEET, "sell", {{wayPointId = 3, available = 268000, level = 268000, capacity = 12000000}}, quoteS, nil)
withReserve.reserve, withReserve.reservedFor = 196000, {"TMR-fabrik"}
local trip13 = P.plan(input({withReserve}))
check("P13 fuldt læs uden om reserven -> salg", trip13 ~= nil and trip13.kind, "sell")
withReserve.reserve = 260000 -- kun 8.000 til overs: mindre end et læs
local trip13b, reasons13b = P.plan(input({withReserve}))
check("P13 reserven ville blive rørt -> ingen tur", trip13b, nil)
check("P13 begrundelse", reasons13b[1], "SUGARBEET: 260.0k reserveret til TMR-fabrik")
withReserve.reserve = 300000 -- alt reserveret
check("P13 alt reserveret -> ingen tur", P.plan(input({withReserve})), nil)
withReserve.reserve = 0
check("P13 reserve 0 -> salg uanset mængde", P.plan(input({withReserve})).kind, "sell")
-- lager-politik ignorerer reserven (varen bliver på gården)
local storeReserve = product("SUGARBEET", BEET, "store", {{wayPointId = 3, available = 268000, level = 11000000, capacity = 12000000}}, nil, storeSilo)
storeReserve.reserve = 300000
check("P13 lager rører ikke reserven", P.plan(input({storeReserve})).kind, "store")
local e13 = P.evaluate(withReserve, input({}, {capacity = 20000}))
check("P13 evaluate sellable", e13.sellable, 268000)
-- P14 (paller): kapacitet ukendt for en autoload-bil -> "fuldt læs" = mindst PALLET_FULL_LOAD_COUNT paller klar
local function palletInput(products)
local inp = input(products, {hasAutoload = true})
inp.capacity = nil
return inp
end
local palletProduct = product("STRAW_BASKET", BASKET, "store", {{wayPointId = 5, available = 3000, level = 1600, capacity = 8000, palletCount = 3}}, nil, storeSilo, true)
check("P14 3 paller: ikke fuldt læs", P.plan(palletInput({palletProduct})), nil)
palletProduct.sources[1].palletCount = 4
check("P14 4 paller: lager-tur", P.plan(palletInput({palletProduct})).kind, "store")
local twoSources = product("STRAW_BASKET", BASKET, "store", {{wayPointId = 5, available = 2000, level = 1600, capacity = 8000, palletCount = 2}, {wayPointId = 3, available = 2000, level = 2, capacity = 4000, palletCount = 2}}, nil, storeSilo, true)
check("P14 paller summeres over kilder", P.plan(palletInput({twoSources})).kind, "store")
check("P14 evaluate palletCount", P.evaluate(twoSources, palletInput({})).palletCount, 4)
-- P15: kilder der deler lager (mælketanke = udvidelser inden for storageRadius) regnes som ét stop;
-- den første i forløbets rækkefølge beholdes, rækkefølgen bevares
local storA, storB, storC = {}, {}, {}
local pools = {[1] = {storA, storB}, [2] = {storB}, [3] = {storC}, [4] = {storA}, [5] = nil}
local srcs = {{wayPointId = 1}, {wayPointId = 2}, {wayPointId = 3}, {wayPointId = 4}, {wayPointId = 5}}
local kept, merged = P.dedupeSharedSources(srcs, function(source) return pools[source.wayPointId] end)
check("P15 behold 1, 3, 5", join((function() local ids = {} for _, s in ipairs(kept) do table.insert(ids, s.wayPointId) end return ids end)()), "1,3,5")
check("P15 slået sammen: 2 og 4 ind i 1", join(merged[1]), "2,4")
check("P15 kilde uden lagre beholdes", kept[3].wayPointId, 5)
local none = P.dedupeSharedSources({}, function() return nil end)
check("P15 tom liste", #none, 0)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)