Module:BassaridEmployment: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary Tag: Manual revert |
||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local market = require("Module:BassaridMarketActivity") | |||
local CITY_POP = { | |||
["Vaeringheim"] = 20887650.86, | |||
["Lunalis Sancta"] = 7832869.07, | |||
["Luminaria"] = 6382335.99, | |||
["Serena"] = 6672442.61, | |||
["Symphonara"] = 4351592.87, | |||
["Aurelia"] = 3771379.63, | |||
["Sylvapolis"] = 3481276.21, | |||
["Delphica"] = 2030743.12, | |||
["Catonis Atrium"] = 1682617.10, | |||
["Ferrum Citadel"] = 928339.26, | |||
["Pyralis"] = 12965917.88, | |||
["Nexa"] = 7979025.10, | |||
["Saluria"] = 7729680.74, | |||
["Acheron"] = 4238856.91, | |||
["Koinonía"] = 5485581.50, | |||
["Aureum"] = 4488201.27, | |||
["Aetherium"] = 3740168.19, | |||
["Erythros"] = 3241479.47, | |||
["Somniumpolis"] = 16035032.55, | |||
["Hafaan"] = 3568579.65, | |||
["Jeseri"] = 2430778.52, | |||
["Skýrophos"] = 5132630.96, | |||
["Bjornopolis"] = 4016840.25, | |||
["Aegerheim"] = 3347368.98, | |||
["Norsolyra"] = 2901052.70, | |||
["Thorsalon"] = 2454736.41, | |||
["Myrene"] = 1338945.70, | |||
["Ephyra"] = 1115790.71, | |||
["Thyrea"] = 892632.57, | |||
["Pelagia"] = 669474.43, | |||
["Halicarn"] = 446316.28, | |||
["Keybir-Aviv"] = 4592562.31, | |||
["Tel-Amin"] = 4155174.90, | |||
["Jogi"] = 3936483.03, | |||
["Lewisburg"] = 3280401.91, | |||
["Diamandis"] = 2843014.50, | |||
["Thermosalem"] = 1312160.40, | |||
["Akróstadium"] = 1093466.69, | |||
["Lykopolis"] = 437387.41, | |||
["Sufriya"] = 218693.71, | |||
["Riddersborg"] = 11757190.77, | |||
["Ardclach"] = 5426395.09, | |||
["Ourid"] = 31826487.08, | |||
["Bashkim"] = 23046766.50 | |||
} | } | ||
local MOD = 2147483647 | |||
local MOD = 2147483647 | |||
local A = 1103515245 | local A = 1103515245 | ||
local C = 12345 | local C = 12345 | ||
| Line 117: | Line 71: | ||
end | end | ||
local function | local function randUnit(nextInt) | ||
return (nextInt() % 100000) / 100000 | |||
end | end | ||
local function clamp(x, lo, hi) | |||
if x < lo then return lo end | |||
if x > hi then return hi end | |||
local function | return x | ||
end | |||
local function round(num) | |||
return math.floor(num + 0.5) | |||
end | |||
local | local function getCurrentDate() | ||
if | local startDate = os.time({ year = 1999, month = 8, day = 6 }) | ||
local secondsInDay = 86400 | |||
local daysPerYear = 183 | |||
local currentDate = os.time() | |||
local totalDaysElapsed = math.floor((currentDate - startDate) / secondsInDay) | |||
local yearFraction = totalDaysElapsed / daysPerYear | |||
local psscYear = math.floor(yearFraction) | |||
local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1 | |||
local month, dayOfMonth | |||
if dayOfYear <= 61 then | |||
month = 1; dayOfMonth = dayOfYear | |||
elseif dayOfYear <= 122 then | |||
month = 2; dayOfMonth = dayOfYear - 61 | |||
else | |||
month = 3; dayOfMonth = dayOfYear - 122 | |||
end | |||
return string.format("%d/%d/%d [[PSSC]]", dayOfMonth, month, psscYear) | |||
end | |||
local function getSectorModule(sector) | |||
sector = tostring(sector or "agriculture"):lower() | |||
if sector == "agriculture" or sector == "ag" then | |||
return require("Module:AutoUpdateAgPrices"), "Agriculture" | |||
elseif sector == "industry" or sector == "manufacturing" or sector == "manf" then | |||
return require("Module:AutoUpdateManfPrices"), "Mining / Manufacturing / Energy" | |||
elseif sector == "services" or sector == "serv" then | |||
return require("Module:AutoUpdateServPrices"), "Services" | |||
end | end | ||
return nil, nil | |||
end | |||
local function getEmploymentSignal(city, cache) | |||
city = city or "Vaeringheim" | |||
if cache[city] then | |||
return cache[city] | |||
end | |||
local sig | |||
if market.getEmploymentSignals then | |||
sig = market.getEmploymentSignals(city) | |||
else | |||
sig = { marketScore = 0, volatility = 0 } | |||
end | |||
cache[city] = sig | |||
return sig | |||
end | |||
local | local function computeWeights(companies) | ||
local | local total = 0 | ||
for _, co in ipairs(companies) do | |||
local cityPop = CITY_POP[co.city] or 1000000 | |||
local cityFactor = math.max(0.60, math.min(1.50, cityPop / 5000000)) | |||
local weight = (co.count or 1) * math.max(0.01, co.avgBase or 0.01) * cityFactor | |||
co.weight = weight | |||
total = total + weight | |||
end | |||
for _, co in ipairs(companies) do | |||
co.weight = co.weight / total | |||
end | |||
end | end | ||
function p.generateTable(frame) | function p.generateTable(frame) | ||
frame = frame or {} | frame = frame or {} | ||
local args = (frame.getParent and frame:getParent() and frame:getParent().args) or {} | local args = (frame.getParent and frame:getParent() and frame:getParent().args) or {} | ||
local fargs = frame.args or {} | local fargs = frame.args or {} | ||
local population = tonumber(fargs.population or args.population) or | |||
local sectorArg = fargs.sector or args.sector or "agriculture" | |||
local population = tonumber(fargs.population or args.population) or 293833483.50 | |||
local unemploymentRate = tonumber(fargs.unemployment or args.unemployment) or 0.04 | local unemploymentRate = tonumber(fargs.unemployment or args.unemployment) or 0.04 | ||
local sectorModule, sectorLabel = getSectorModule(sectorArg) | |||
if | if not sectorModule then | ||
if | return "Invalid sector. Use agriculture, industry, or services." | ||
end | |||
if not sectorModule.getCompanyEmploymentInputs then | |||
return "This sector module does not expose getCompanyEmploymentInputs() yet." | |||
end | |||
local employedPopulation = math.floor(population * (1 - unemploymentRate)) | local employedPopulation = math.floor(population * (1 - unemploymentRate)) | ||
local dateString = getCurrentDate() | |||
local localTime = os.date("*t") | local localTime = os.date("*t") | ||
local dailySeed = localTime.year * 1000 + localTime.yday | local dailySeed = localTime.year * 1000 + localTime.yday | ||
local | local companies = sectorModule.getCompanyEmploymentInputs() | ||
computeWeights(companies) | |||
table.sort(companies, function(a, b) return a.name < b.name end) | |||
local signalCache = {} | |||
local out = {} | local out = {} | ||
out[#out + 1] = | out[#out + 1] = '{| class="wikitable sortable"\n' | ||
out[#out + 1] = | out[#out + 1] = '! Date !! Sector !! Company !! City !! Price Change !! Previous Employees !! Current Employees !! Change\n' | ||
local totalEmployed = 0 | local totalEmployed = 0 | ||
local | for _, co in ipairs(companies) do | ||
if | local baseEmployees = round(employedPopulation * co.weight) | ||
elseif | local sig = getEmploymentSignal(co.city, signalCache) | ||
local nextInt = lcg(dailySeed + hashString((co.name or "") .. "|" .. (co.city or ""))) | |||
local jitter = (randUnit(nextInt) - 0.5) * 0.04 | |||
local priceEffect = (tonumber(co.pctChange) or 0) * 0.30 / 100 | |||
local marketEffect = sig.marketScore or 0 | |||
local volDrag = (sig.volatility or 0) * 0.03 | |||
local netEffect = clamp(priceEffect + marketEffect + jitter - volDrag, -0.18, 0.18) | |||
local currEmployees = round(baseEmployees * (1 + netEffect)) | |||
if currEmployees < 1 then currEmployees = 1 end | |||
local changeText = "Same" | |||
local rowStyle = ' style="background-color:#f3f3f3;"' | |||
if currEmployees > baseEmployees then | |||
changeText = "Increased" | |||
rowStyle = ' style="background-color:#d8f0d2;"' | |||
elseif currEmployees < baseEmployees then | |||
changeText = "Decreased" | |||
rowStyle = ' style="background-color:#f8d7d7;"' | |||
end | end | ||
totalEmployed = totalEmployed + currEmployees | |||
out[#out + 1] = string.format( | out[#out + 1] = string.format( | ||
'|-%s\n| %s || %s || %s || %s || %+0.2f%% || %d || %d || %s\n', | |||
rowStyle, | rowStyle, | ||
dateString, | dateString, | ||
sectorLabel, | |||
co.name, | |||
co.city or "", | |||
co.pctChange or 0, | |||
baseEmployees, | |||
currEmployees, | |||
changeText | |||
) | ) | ||
end | end | ||
local totalUnemployed = population - totalEmployed | local totalUnemployed = math.max(0, math.floor(population - totalEmployed)) | ||
out[#out + 1] = string.format('|-\n! colspan="6" | Total Employed || %d || \n', totalEmployed) | |||
out[#out + 1] = string.format('|-\n! colspan="6" | Total Unemployed || %d || \n', totalUnemployed) | |||
out[#out + 1] = string.format( | out[#out + 1] = '|}' | ||
out[#out + 1] = string.format( | |||
out[#out + 1] = | |||
return table.concat(out) | return table.concat(out) | ||
Revision as of 18:29, 20 March 2026
Documentation for this module may be created at Module:BassaridEmployment/doc
local p = {}
local market = require("Module:BassaridMarketActivity")
local CITY_POP = {
["Vaeringheim"] = 20887650.86,
["Lunalis Sancta"] = 7832869.07,
["Luminaria"] = 6382335.99,
["Serena"] = 6672442.61,
["Symphonara"] = 4351592.87,
["Aurelia"] = 3771379.63,
["Sylvapolis"] = 3481276.21,
["Delphica"] = 2030743.12,
["Catonis Atrium"] = 1682617.10,
["Ferrum Citadel"] = 928339.26,
["Pyralis"] = 12965917.88,
["Nexa"] = 7979025.10,
["Saluria"] = 7729680.74,
["Acheron"] = 4238856.91,
["Koinonía"] = 5485581.50,
["Aureum"] = 4488201.27,
["Aetherium"] = 3740168.19,
["Erythros"] = 3241479.47,
["Somniumpolis"] = 16035032.55,
["Hafaan"] = 3568579.65,
["Jeseri"] = 2430778.52,
["Skýrophos"] = 5132630.96,
["Bjornopolis"] = 4016840.25,
["Aegerheim"] = 3347368.98,
["Norsolyra"] = 2901052.70,
["Thorsalon"] = 2454736.41,
["Myrene"] = 1338945.70,
["Ephyra"] = 1115790.71,
["Thyrea"] = 892632.57,
["Pelagia"] = 669474.43,
["Halicarn"] = 446316.28,
["Keybir-Aviv"] = 4592562.31,
["Tel-Amin"] = 4155174.90,
["Jogi"] = 3936483.03,
["Lewisburg"] = 3280401.91,
["Diamandis"] = 2843014.50,
["Thermosalem"] = 1312160.40,
["Akróstadium"] = 1093466.69,
["Lykopolis"] = 437387.41,
["Sufriya"] = 218693.71,
["Riddersborg"] = 11757190.77,
["Ardclach"] = 5426395.09,
["Ourid"] = 31826487.08,
["Bashkim"] = 23046766.50
}
local MOD = 2147483647
local A = 1103515245
local C = 12345
local function hashString(s)
s = tostring(s or "")
local h = 0
for i = 1, #s do
h = (h * 31 + s:byte(i)) % MOD
end
return h
end
local function lcg(seed)
seed = seed % MOD
return function()
seed = (A * seed + C) % MOD
return seed
end
end
local function randUnit(nextInt)
return (nextInt() % 100000) / 100000
end
local function clamp(x, lo, hi)
if x < lo then return lo end
if x > hi then return hi end
return x
end
local function round(num)
return math.floor(num + 0.5)
end
local function getCurrentDate()
local startDate = os.time({ year = 1999, month = 8, day = 6 })
local secondsInDay = 86400
local daysPerYear = 183
local currentDate = os.time()
local totalDaysElapsed = math.floor((currentDate - startDate) / secondsInDay)
local yearFraction = totalDaysElapsed / daysPerYear
local psscYear = math.floor(yearFraction)
local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1
local month, dayOfMonth
if dayOfYear <= 61 then
month = 1; dayOfMonth = dayOfYear
elseif dayOfYear <= 122 then
month = 2; dayOfMonth = dayOfYear - 61
else
month = 3; dayOfMonth = dayOfYear - 122
end
return string.format("%d/%d/%d [[PSSC]]", dayOfMonth, month, psscYear)
end
local function getSectorModule(sector)
sector = tostring(sector or "agriculture"):lower()
if sector == "agriculture" or sector == "ag" then
return require("Module:AutoUpdateAgPrices"), "Agriculture"
elseif sector == "industry" or sector == "manufacturing" or sector == "manf" then
return require("Module:AutoUpdateManfPrices"), "Mining / Manufacturing / Energy"
elseif sector == "services" or sector == "serv" then
return require("Module:AutoUpdateServPrices"), "Services"
end
return nil, nil
end
local function getEmploymentSignal(city, cache)
city = city or "Vaeringheim"
if cache[city] then
return cache[city]
end
local sig
if market.getEmploymentSignals then
sig = market.getEmploymentSignals(city)
else
sig = { marketScore = 0, volatility = 0 }
end
cache[city] = sig
return sig
end
local function computeWeights(companies)
local total = 0
for _, co in ipairs(companies) do
local cityPop = CITY_POP[co.city] or 1000000
local cityFactor = math.max(0.60, math.min(1.50, cityPop / 5000000))
local weight = (co.count or 1) * math.max(0.01, co.avgBase or 0.01) * cityFactor
co.weight = weight
total = total + weight
end
for _, co in ipairs(companies) do
co.weight = co.weight / total
end
end
function p.generateTable(frame)
frame = frame or {}
local args = (frame.getParent and frame:getParent() and frame:getParent().args) or {}
local fargs = frame.args or {}
local sectorArg = fargs.sector or args.sector or "agriculture"
local population = tonumber(fargs.population or args.population) or 293833483.50
local unemploymentRate = tonumber(fargs.unemployment or args.unemployment) or 0.04
local sectorModule, sectorLabel = getSectorModule(sectorArg)
if not sectorModule then
return "Invalid sector. Use agriculture, industry, or services."
end
if not sectorModule.getCompanyEmploymentInputs then
return "This sector module does not expose getCompanyEmploymentInputs() yet."
end
local employedPopulation = math.floor(population * (1 - unemploymentRate))
local dateString = getCurrentDate()
local localTime = os.date("*t")
local dailySeed = localTime.year * 1000 + localTime.yday
local companies = sectorModule.getCompanyEmploymentInputs()
computeWeights(companies)
table.sort(companies, function(a, b) return a.name < b.name end)
local signalCache = {}
local out = {}
out[#out + 1] = '{| class="wikitable sortable"\n'
out[#out + 1] = '! Date !! Sector !! Company !! City !! Price Change !! Previous Employees !! Current Employees !! Change\n'
local totalEmployed = 0
for _, co in ipairs(companies) do
local baseEmployees = round(employedPopulation * co.weight)
local sig = getEmploymentSignal(co.city, signalCache)
local nextInt = lcg(dailySeed + hashString((co.name or "") .. "|" .. (co.city or "")))
local jitter = (randUnit(nextInt) - 0.5) * 0.04
local priceEffect = (tonumber(co.pctChange) or 0) * 0.30 / 100
local marketEffect = sig.marketScore or 0
local volDrag = (sig.volatility or 0) * 0.03
local netEffect = clamp(priceEffect + marketEffect + jitter - volDrag, -0.18, 0.18)
local currEmployees = round(baseEmployees * (1 + netEffect))
if currEmployees < 1 then currEmployees = 1 end
local changeText = "Same"
local rowStyle = ' style="background-color:#f3f3f3;"'
if currEmployees > baseEmployees then
changeText = "Increased"
rowStyle = ' style="background-color:#d8f0d2;"'
elseif currEmployees < baseEmployees then
changeText = "Decreased"
rowStyle = ' style="background-color:#f8d7d7;"'
end
totalEmployed = totalEmployed + currEmployees
out[#out + 1] = string.format(
'|-%s\n| %s || %s || %s || %s || %+0.2f%% || %d || %d || %s\n',
rowStyle,
dateString,
sectorLabel,
co.name,
co.city or "",
co.pctChange or 0,
baseEmployees,
currEmployees,
changeText
)
end
local totalUnemployed = math.max(0, math.floor(population - totalEmployed))
out[#out + 1] = string.format('|-\n! colspan="6" | Total Employed || %d || \n', totalEmployed)
out[#out + 1] = string.format('|-\n! colspan="6" | Total Unemployed || %d || \n', totalUnemployed)
out[#out + 1] = '|}'
return table.concat(out)
end
return p