-- 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) -- P16: fodring ("Lager til" valgt = feedMode): kør så snart kilden har FEED_MIN_LITERS — ikke fuldt læs / 80 % local feedStore = {wayPointId = 50, name = "Slurry Drop 1", free = 100000} local feedProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 20000, level = 20000, capacity = 500000}}, nil, feedStore) local tripNoFeed = P.plan(input({feedProduct}, {capacity = 100000})) check("P16 uden fodring: 4 % og intet fuldt læs -> ingen tur", tripNoFeed, nil) local tripFeed = P.plan(input({feedProduct}, {capacity = 100000, feedMode = true})) check("P16 fodring: 20k >= minimum -> lagertur", tripFeed and tripFeed.kind, "store") check("P16 fodring: mål = valgt drop", tripFeed and tripFeed.targetWayPointId, 50) local tinyProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 3000, level = 3000, capacity = 500000}}, nil, feedStore) check("P16 fodring: under minimum -> ingen tur", P.plan(input({tinyProduct}, {capacity = 100000, feedMode = true})), nil) -- P17: valgt lager ("Lager til") kan ikke bruges -> begrundelsen er den præcise grund, ikke "intet lager med plads" local blocked = product("EGG", FORAGE, "store", {{wayPointId = 2, available = 3000000, level = 3000000, capacity = 3500000}}, nil, nil) blocked.storeReason = "Bale & Pallet Storage: ingen vej til 'Chicken Storage Unload'" local _, reasons17 = P.plan(input({blocked})) check("P17 præcis grund i status", reasons17[1], "EGG: Bale & Pallet Storage: ingen vej til 'Chicken Storage Unload'") print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)