Module:BassaridEmployment

From MicrasWiki
Revision as of 18:27, 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 = {}
---------------------------------------------------------
-- Module:BassaridEmployment (Optimized)
--
-- What this module does
--  • Computes a daily "previous vs current" employment estimate per company
--  • Uses deterministic per-company variation so:
--      - results are stable for a given day
--      - results do NOT depend on loop order / table order
--  • Builds wiki output using buffers (table.concat) for speed
--
-- Usage:
--   {{#invoke:BassaridEmployment|generateTable}}
--   Optional args:
--     |population=55499301
--     |unemployment=0.04
--     |maxChange=0.04
---------------------------------------------------------

---------------------------------------------------------
-- 1) getCurrentDate
--   Returns a date string in PSSC format, plus day/month/year/dayOfYear.
---------------------------------------------------------
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), dayOfMonth, month, psscYear, dayOfYear
end

---------------------------------------------------------
-- 2) Company table (same data as current module)
---------------------------------------------------------
local companies = {
	{name = "Lake Morovia Blockade Fund", employmentPercentage = 0.0300},
	{name = "Gladeseed Farmers' Union", employmentPercentage = 0.1205},
	{name = "Atterian Cattle Drivers", employmentPercentage = 0.0502},
	{name = "Roving Wind Farm Corporation", employmentPercentage = 0.0210},
	{name = "Baratar Corporation", employmentPercentage = 0.0401},
	{name = "Noctic Fleet", employmentPercentage = 0.0603},
	{name = "Keepers of the Bloodbaths of Laprind", employmentPercentage = 0.0105},
	{name = "Jogiani Merchants of Manwine", employmentPercentage = 0.0208},
	{name = "Keepers of the Grove of Zoe Elm", employmentPercentage = 0.0112},
	{name = "Plains of Jogi", employmentPercentage = 0.0807},
	{name = "Ale of the Night Central Brewery", employmentPercentage = 0.0201},
	{name = "Camel Herders of the Jangsong Province", employmentPercentage = 0.0306},
	{name = "Thalassian Temple Granite Export Cooperative", employmentPercentage = 0.0310},
	{name = "East Keltian Iron Company", employmentPercentage = 0.0415},
	{name = "Mylecian Coal Ports", employmentPercentage = 0.0317},
	{name = "Merchants of the Valley of Diamonds", employmentPercentage = 0.0102},
	{name = "Maritime Guild of the Cult of Maskmakers", employmentPercentage = 0.0109},
	{name = "Hatch Ministry", employmentPercentage = 0.0203},
	{name = "Anterran Imports and Services", employmentPercentage = 0.0113},
	{name = "East Keltian Timber Company", employmentPercentage = 0.0312},
	{name = "Grand Cave Bee Meadery", employmentPercentage = 0.0215},
	{name = "Merchant Guild of the Poppy Goddess", employmentPercentage = 0.0111},
	{name = "Cinnamon Plains of Rouge", employmentPercentage = 0.0218},
	{name = "Seamstresses of Rouge", employmentPercentage = 0.0114},
	{name = "Salurian Temple of Sacred Horehound", employmentPercentage = 0.0212},
	{name = "Suncliff Fisheries", employmentPercentage = 0.0309},
	{name = "Bird Keepers of Saluria", employmentPercentage = 0.0108},
	{name = "Velvet Homestead", employmentPercentage = 0.0110},
	{name = "Hunters of the Giant Fanged Penguin", employmentPercentage = 0.0116},
	{name = "Professional Pillarion League", employmentPercentage = 0.0052},
	{name = "La Sái Ebile", employmentPercentage = 0.0104},
	{name = "Temple Bank of the Reformed Stripping Path", employmentPercentage = 0.0118},
	{name = "Market Hanavusu", employmentPercentage = 0.0157},
	{name = "Couriers of the Lizard Queen", employmentPercentage = 0.0117},
	{name = "Temple University of Delphica", employmentPercentage = 0.0209},
	{name = "Trans-Morovian Railway Company", employmentPercentage = 0.0155},
}

---------------------------------------------------------
-- 2.5) Normalize employment percentages into weights
---------------------------------------------------------
local totalEmploymentWeight = 0
for _, company in ipairs(companies) do
	totalEmploymentWeight = totalEmploymentWeight + company.employmentPercentage
end

---------------------------------------------------------
-- 3) Deterministic RNG helpers (fast; no global math.random)
---------------------------------------------------------
local MOD = 2147483647 -- 2^31 - 1
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 randIntInclusive(nextInt, lo, hi)
	if hi < lo then lo, hi = hi, lo end
	local span = (hi - lo) + 1
	local r = (nextInt() + nextInt()) % MOD
	return lo + (r % span)
end

---------------------------------------------------------
-- 4) Per-company calculation
---------------------------------------------------------
local function calculateEmployees(company, employedPopulation, maxChangeFrac, dailySeed)
	local normalizedShare = company.employmentPercentage / totalEmploymentWeight
	local base = math.floor(employedPopulation * normalizedShare)

	local h = hashString(company.name)
	local nextInt = lcg(dailySeed + h)
	local maxChange = math.floor(base * maxChangeFrac)
	if maxChange < 0 then maxChange = 0 end

	local changeAmount = 0
	if maxChange > 0 then
		changeAmount = randIntInclusive(nextInt, -maxChange, maxChange)
	end

	local prevEmployees = base
	local currEmployees = base + changeAmount
	if currEmployees < 1 then currEmployees = 1 end

	return prevEmployees, currEmployees, normalizedShare
end

---------------------------------------------------------
-- 5) generateTable
---------------------------------------------------------
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 population = tonumber(fargs.population or args.population) or 55499301
	local unemploymentRate = tonumber(fargs.unemployment or args.unemployment) or 0.04
	local maxChangeFrac = tonumber(fargs.maxChange or args.maxChange) or 0.04

	if unemploymentRate < 0 then unemploymentRate = 0 end
	if unemploymentRate > 1 then unemploymentRate = 1 end
	if maxChangeFrac < 0 then maxChangeFrac = 0 end
	if maxChangeFrac > 1 then maxChangeFrac = 1 end
	local employedPopulation = math.floor(population * (1 - unemploymentRate))

	local localTime = os.date("*t")
	local dailySeed = localTime.year * 1000 + localTime.yday

	local dateString = getCurrentDate()

	local out = {}
	out[#out + 1] = "{| class=\"wikitable\"\n"
	out[#out + 1] = "! Date !! Company !! Employment % !! Previous Employees !! Current Employees !! Change\n"

	local totalEmployed = 0
	for _, company in ipairs(companies) do
		local prevE, currE, normalizedShare = calculateEmployees(company, employedPopulation, maxChangeFrac, dailySeed)

		local changeType
		if currE > prevE then
			changeType = "Increased"
		elseif currE < prevE then
			changeType = "Decreased"
		else
			changeType = "Same"
		end

		local rowStyle
		if changeType == "Increased" then
			rowStyle = ' style="background-color:lightblue;"'
		elseif changeType == "Same" then
			rowStyle = ' style="background-color:lightgreen;"'
		else
			rowStyle = ' style="background-color:#ffcccb;"'
		end

		totalEmployed = totalEmployed + currE
		out[#out + 1] = string.format(
			"|-%s\n| %s || %s || %.2f%% || %d || %d || %s\n",
			rowStyle,
			dateString,
			company.name,
			normalizedShare * 100,
			prevE,
			currE,
			changeType
		)
	end

	local totalUnemployed = population - totalEmployed
	if totalUnemployed < 0 then totalUnemployed = 0 end

	out[#out + 1] = string.format("|-\n! colspan=\"4\" | Total Employed || %d || \n", totalEmployed)
	out[#out + 1] = string.format("|-\n! colspan=\"4\" | Total Unemployed || %d || \n", totalUnemployed)
	out[#out + 1] = "|}"

	return table.concat(out)
end

return p