Module:BassaridEmployment

From MicrasWiki
Revision as of 18:29, 20 March 2026 by NewZimiaGov (talk | contribs)
Jump to navigationJump to search

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