Module:StraitsDaily
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:StraitsDaily/doc
-- Module: StraitsDaily
-- Purpose: Daily, in-world report of Strait of Haifa enforcement [[Straits Conventions of 52.06 PSSC]]
local p = {}
-------------------------------------------------------------------------------
-- (A) PSSC DATE
-------------------------------------------------------------------------------
local function getPSSCDate()
local startDate = os.time({year = 1999, month = 8, day = 6})
local secondsInDay = 86400
local daysPerYear = 183
local now = os.time()
local totalDays = math.floor((now - startDate) / secondsInDay)
local yearFraction = totalDays / 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
local dateString = string.format("%d/%d/%d PSSC", dayOfMonth, month, psscYear)
return { text = dateString, year = psscYear, dayOfYear = dayOfYear }
end
-------------------------------------------------------------------------------
-- (B) STATIC DATA
-------------------------------------------------------------------------------
local sectors = {
"Strait South Inbound","Strait South Outbound","Strait North Inbound","Strait North Outbound",
"Archipelago Zone Inbound", "Archipelago Zone Outbound", "Sacred Buffer Approaches",
"Environmental Buffer","Northern Approach TSS","Southern Approach TSS",
"Northern Sea of Storms","Western Sea of Storms","Southern Sea of Storms",
}
local flags = {
{name="Bassaridian-flag", weight=6, risky=false},
{name="Moorland", weight=2, risky=false},
{name="Imperial Federation", weight=5, risky=false},
{name="Matamoros (IF)", weight=2, risky=false},
{name="Neutral Merchant", weight=4, risky=false},
{name="Diaspora Stakeholders- Euranidom", weight=3, risky=true},
{name="Diaspora Stakeholders- Corumia", weight=3, risky=true},
{name="Diaspora Stakeholders- South Keltiania", weight=3, risky=true},
{name="Consortium Registry", weight=2, risky=true},
{name="Independent Coastal State", weight=7, risky=false}
}
local vessels = {
"Handymax bulker","Product tanker","General cargo ship","Container feeder",
"Ro-Ro ferry","Research craft","Declared submarine","Undeclared submarine"
}
local cargoTypes = {
{name="Medical reagents (dual-use precursors)", weight=3, risk=0.14, dual=true},
{name="Industrial oxidizers / nitrates (dual-use)", weight=2, risk=0.16, dual=true},
{name="Precision machine tools & CNC spares (dual-use)", weight=3, risk=0.15, dual=true},
{name="Encrypted electronics & comms modules (dual-use)", weight=2, risk=0.17, dual=true},
{name="Satellite components / sensors (dual-use)", weight=1, risk=0.18, dual=true},
{name="Industrial fasteners (coded)", weight=2, risk=0.12, dual=true},
{name="Hydraulic lubricants (dual-use)", weight=2, risk=0.13, dual=true},
{name="High-frequency alloys (coded)", weight=1, risk=0.14, dual=true},
{name="Aviation fuels & additives", weight=3, risk=0.12, dual=false},
{name="Refined petroleum products", weight=4, risk=0.08, dual=false},
{name="Relief supplies (mixed pallets)", weight=5, risk=0.06, dual=false},
{name="Agricultural commodities", weight=5, risk=0.04, dual=false},
{name="Finished consumer goods", weight=6, risk=0.03, dual=false}
}
local cargoTags = {
{name="standard", weight=6, risk=0.00},
{name="expedited", weight=3, risk=0.05},
{name="emergency", weight=2, risk=0.07},
{name="perishable",weight=2, risk=0.06}
}
local partnerProfiles = {
{code="P-07", weight=4, risk=0.00, recentLift=false},
{code="P-12", weight=3, risk=0.03, recentLift=false},
{code="P-17", weight=3, risk=0.12, recentLift=true},
{code="P-22", weight=3, risk=0.14, recentLift=true},
{code="P-31", weight=3, risk=0.05, recentLift=false},
{code="P-38", weight=2, risk=0.10, recentLift=false},
{code="P-44", weight=4, risk=0.02, recentLift=false},
{code="P-53", weight=3, risk=0.04, recentLift=false}
}
-- Corum districts (for WL district annotations)
local corumDistricts = {
{name="Lyvian Zones", kind="NZ"},
{name="Lucinarian Zones", kind="NZ"},
{name="Brentonian Zones", kind="NZ"},
{name="Ossyani Zones", kind="NZ"},
{name="Sinclarian Zones", kind="GM"},
{name="Nortonian Zones", kind="GM"},
{name="Wallisian Zones", kind="GM"},
{name="Alindan Zones", kind="GM"},
{name="Aijiac Zones", kind="GM"},
}
local districtKindByName = {}
for _,d in ipairs(corumDistricts) do districtKindByName[d.name] = d.kind end
-- WL corridors (labels/windows)
local wlCorridors = {
{ code="WL-NZ-1", name="New Zimian Humanitarian Belt", win="0700–1800 (expanded)", kind="NZ",
districts={"Lyvian Zones","Lucinarian Zones","Brentonian Zones","Ossyani Zones"} },
{ code="WL-GM-2", name="Bayen Relief Spur", win="0800–1900 (expanded)", kind="GM",
districts={"Sinclarian Zones","Nortonian Zones","Wallisian Zones","Alindan Zones","Aijiac Zones"} },
{ code="WL-SEA-3", name="Corum Coastal Shuttle", win="0600–2000 (expanded)", kind="Mixed",
districts={"Wallisian Zones","Nortonian Zones","Lyvian Zones"} }
}
local observers = {
"Neutral temple auditor","Independent port compliance inspector",
"Community elder (witness)","Straits Convention monitor (civilian)"
}
local wl03Signers = {
["Matamoros (IF)"] = true,
["Imperial Federation"] = true,
["Bassaridian-flag"] = true,
["Neutral Merchant"] = true,
["Moorland"] = true,
["Independent Coastal State"] = true
}
-------------------------------------------------------------------------------
-- (B1.5) SECTOR RULES (allow/block flags and/or vessels per sector)
-------------------------------------------------------------------------------
local sectorRules = {
["Northern Sea of Storms"] = {
flags = { block = { ["Imperial Federation"]=true, ["Matamoros (IF)"]=true, ["Moorland"]=true } },
-- vessels = { allow = { ["Research craft"]=true } }, -- optional example
},
["Western Sea of Storms"] = {
flags = { block = { ["Imperial Federation"]=true, ["Matamoros (IF)"]=true, ["Moorland"]=true } },
},
["Southern Sea of Storms"] = {
flags = { block = { ["Imperial Federation"]=true, ["Matamoros (IF)"]=true, ["Moorland"]=true } },
},
}
-------------------------------------------------------------------------------
-- (B2) PUBLIC HEALTH (<=10% rows)
-------------------------------------------------------------------------------
local phDiseases = {
{name="Thalassan Rot", code="TR", controls="canal flush; rat-proofing; food-handler checks"},
{name="Festival Blush", code="FB", controls="ring vaccination; crowd spacing; dorm rotation"},
{name="Pilgrim’s Flux", code="PF", controls="boil notices; cistern audit; ORS distribution"},
{name="Stygian Dust", code="SD", controls="respirators; mist carts; guide clinic checks"},
{name="Haifan Salt-Sickness", code="HS", controls="indoor rotations; antifungal clinics; dehumidify"},
{name="Norsolyrian Drain", code="ND", controls="lined ditches; praziquantel; snail abatement"},
{name="Somniumpolis Lung", code="SL", controls="dust suppression; respirator fit-checks"},
{name="Haifan Crimson Fever", code="HCF",controls="larval control; dusk patrols; vector abatement"},
{name="Strip Yellowing", code="SY", controls="single-use ladles; vendor relicensing; recall"},
{name="Eye of Styx", code="ES", controls="eye-wash stations; visor compliance"},
{name="Cato’s Curse", code="CC", controls="PPE audits; solvent/oxidizer exposure controls"},
{name="White Pestilence", code="WP", controls="tiered quarantine; convoy assays; cordons"},
{name="Breath of Nephele", code="BN", controls="ventilation drills; mask catechism; spacing"},
{name="Agnian Lockjaw", code="AL", controls="tetanus boosters; injury clinics"},
}
local phIndex = {}
for _,d in ipairs(phDiseases) do phIndex[d.code] = d end
local function phScore(sector, flag, vessel, cargo, tag, partner)
local score = 0
local cname = cargo and cargo.name or ""
local tname = tag and tag.name or "standard"
if sector == "Environmental Buffer" or sector == "Sacred Buffer Approaches"
or sector:find("Strait") or sector:find("Approach") then score = score + 1 end
if cname == "Agricultural commodities" or tname == "perishable" then score = score + 2 end
if cname == "Relief supplies (mixed pallets)" then score = score + 1 end
if cname:find("oxidizers") or cname:find("lubricants") or cname:find("petroleum") or cname:find("fuels") then score = score + 2 end
if vessel == "Ro-Ro ferry" and (tname == "expedited" or tname == "emergency") then score = score + 2 end
if (partner and partner.risk or 0) >= 0.09 then score = score + 1 end
if flag and flag.risky then score = score + 0.5 end
local function pick(code)
local d = phIndex[code]; if not d then return 0,nil,nil end
return score, d.code, (d.code .. " ("..d.name.."): " .. d.controls)
end
if score >= 5 then return pick("WP") end
if (cname == "Agricultural commodities" or tname == "perishable") then return pick("SY") end
if cname == "Relief supplies (mixed pallets)" then return pick("PF") end
if sector == "Environmental Buffer" or sector:find("Strait") then return pick("TR") end
if sector:find("Archipelago Zone") then return pick("HS") end -- matches Inbound/Outbound
if (cname:find("oxidizers") or cname:find("lubricants") or cname:find("petroleum") or cname:find("fuels")) then return pick("CC") end
if vessel == "Ro-Ro ferry" and (tname == "expedited" or tname == "emergency") then return pick("FB") end
if (partner and partner.risk or 0) >= 0.09 then return pick("BN") end
return 0,nil,nil
end
-------------------------------------------------------------------------------
-- (C) POLICY STAGES (per flag)
-------------------------------------------------------------------------------
local stageMap = {
["Imperial Federation"] = 1,
["Matamoros (IF)"] = 1,
}
local function getFlagStage(flagName, globalStage)
local s = stageMap[flagName]
if s ~= nil then return s end
return globalStage or 0
end
-------------------------------------------------------------------------------
-- (D) HELPERS
-------------------------------------------------------------------------------
local function pick(list) return list[math.random(#list)] end
local function pickWeighted(list)
local total = 0
for _, item in ipairs(list) do total = total + (item.weight or 1) end
local roll, cumul = math.random(total), 0
for _, item in ipairs(list) do
cumul = cumul + (item.weight or 1)
if roll <= cumul then return item end
end
return list[#list]
end
-- Filter helpers (respect sectorRules; safe fallback if empty)
local function isAllowedByRule(ruleTbl, key)
if not ruleTbl then return true end
if ruleTbl.allow then return ruleTbl.allow[key] == true end
if ruleTbl.block then return ruleTbl.block[key] ~= true end
return true
end
local function flagAllowedInSector(sector, flagName)
local rules = sectorRules[sector]
return isAllowedByRule(rules and rules.flags, flagName)
end
local function vesselAllowedInSector(sector, vesselName)
local rules = sectorRules[sector]
return isAllowedByRule(rules and rules.vessels, vesselName)
end
local function pickWeightedFiltered(list, predicate)
local filtered = {}
for _, item in ipairs(list) do
if predicate(item) then table.insert(filtered, item) end
end
if #filtered == 0 then return pickWeighted(list) end
return pickWeighted(filtered)
end
local function pickFiltered(list, predicate)
local filtered = {}
for _, item in ipairs(list) do
if predicate(item) then table.insert(filtered, item) end
end
if #filtered == 0 then return pick(list) end
return pick(filtered)
end
-- FULL WL expansion switch
local fullWL = true
-- WL eligibility (expanded)
local function wlEligibility(cargo, tag, flag, partner)
if not cargo or not tag then return false, "n/a" end
local disqualify = false
if cargo.dual then disqualify = true end
if flag and flag.name == "Matamoros (IF)" and not wl03Signers["Matamoros (IF)"] then disqualify = true end
if disqualify then
local signed = wl03Signers[flag.name]
if signed == nil then signed = true end
local cooling = (partner and partner.recentLift) or false
if not signed then return false, "WL-03 not signed (M-AMBER)" end
if cooling then return false, "WL-03 cooldown (90 days)" end
return false, "Not eligible cargo/tag"
end
if fullWL then
return true, "WL-03 compliant"
else
local green = (cargo.name == "Relief supplies (mixed pallets)" or cargo.name == "Agricultural commodities")
local amber = (tag.name == "expedited" or tag.name == "emergency" or tag.name == "perishable")
local baseEligible = ((green or amber) and not cargo.dual)
local signed = wl03Signers[flag.name]; if signed == nil then signed = true end
local cooling = (partner and partner.recentLift) or false
if not baseEligible then return false, "Not eligible cargo/tag" end
if not signed then return false, "WL-03 not signed (M-AMBER)" end
if cooling then return false, "WL-03 cooldown (90 days)" end
return true, "WL-03 compliant"
end
end
local function legalBasis(sector, wl, wl03note, phApplied)
if phApplied then
return "Art VII (Public Health Safeguards); Annex D; HCE PH-Bulletin"
end
if wl then
return "Art III.5 (White-Lane); Annex G; Art X.3 (expedited remedies); HCE Circular WL-03 (" .. wl03note .. ")"
end
if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then
return "Art III.2.3 (Sacred quiet zones); Annex A (delims); Art VII; Annex D"
end
return "Art IV.1–1.2 (PSC); Annex F (SCT/SAC procedures)"
end
local function appealsStatus(sector, wl, tag, cargo)
if wl then
local relief = 0.70
if tag and (tag.name=="emergency" or tag.name=="perishable") then relief = relief + 0.10 end
if cargo and cargo.dual then relief = relief - 0.20 end
local r = math.random()
local outcome = (r <= relief * 0.6) and "clearance granted"
or (r <= relief) and "expedited with conditions"
or "conditions imposed"
return string.format("HCE Review → SCT (72h) → SAC (≤5d) — %s (Art X.3; Annex G; WL-03 liaison)", outcome)
else
local level = (sector=="Sacred Buffer Approaches" or sector=="Environmental Buffer") and "SAC Referral"
or ((math.random()<0.5) and "SCT Admin Review" or "PSC reconciliation")
local outcome = (level=="PSC reconciliation") and "notice acknowledged"
or ((math.random()<0.5) and "clearance granted" or "conditions imposed")
local cite = (level=="SAC Referral") and "Annex F (SAC)" or "Annex F (SCT)"
return string.format("%s — %s (%s)", level, outcome, cite)
end
end
local function hceRef(dateInfo, corridor)
return string.format("HCE-%03d.%03d-%s", dateInfo.year, dateInfo.dayOfYear, corridor.code)
end
local function pickServedDistrict(corr)
local dname = corr.districts[math.random(#corr.districts)]
local kind = districtKindByName[dname] or "NZ"
return dname, kind
end
local function buildTriggers(flag, sector, cargo, tag, partner, wlEligible, wl03note, riskPush, flagStage)
local t = {}
if flag.risky then table.insert(t, "risky flag") end
if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then
table.insert(t, "sacred/ecology buffer")
end
if cargo and cargo.dual then table.insert(t, "dual-use cargo") end
if cargo and (cargo.risk or 0) >= 0.12 then table.insert(t, "cargo risk ≥0.12") end
if tag and tag.name ~= "standard" then table.insert(t, "non-standard tag: "..tag.name) end
if partner and (partner.risk or 0) >= 0.09 then table.insert(t, "partner risk ≥0.09") end
if wlEligible then table.insert(t, "WL pre-cleared") end
if wl03note == "WL-03 not signed (M-AMBER)" then table.insert(t, "WL-03 unsigned (M-AMBER)") end
if wl03note == "WL-03 cooldown (90 days)" then table.insert(t, "WL-03 cooldown") end
if flagStage and flagStage > 0 then table.insert(t, "policy stage "..flagStage) end
if riskPush and riskPush >= 2 then table.insert(t, "multi-trigger escalation") end
if #t == 0 then return "none" end
return table.concat(t, "; ")
end
-------------------------------------------------------------------------------
-- (E) MAIN GENERATION (PH <=10%; no Notes column)
-------------------------------------------------------------------------------
function p.makeTable(frame)
local dateInfo = getPSSCDate()
local dailySeed = dateInfo.year*1000 + dateInfo.dayOfYear
math.randomseed(dailySeed)
local rows = tonumber(frame and frame.args and frame.args.rows) or 36
local globalStage = tonumber(frame and frame.args and frame.args.policy_stage) or 0
local showLegend = (frame and frame.args and tostring(frame.args.showlegend) or "yes"):lower() ~= "no"
-- PH toggles (cap fixed at 10%)
local phOn = (frame and frame.args and tostring(frame.args.ph) or "on"):lower() ~= "off"
local phRateArg = tonumber(frame and frame.args and frame.args.phrate) or 0.10
if phRateArg < 0 then phRateArg = 0 end
if phRateArg > 0.10 then phRateArg = 0.10 end
local showPHLeg = (frame and frame.args and tostring(frame.args.showphlegend) or "yes"):lower() ~= "no"
local phCap = math.floor(rows * 0.10)
-- Pass 1: compose rows and score PH
local rowObjs = {}
for i = 1, rows do
math.randomseed(dailySeed + i*97)
local sector = pick(sectors)
-- NEW: sector-aware picks
local flag = pickWeightedFiltered(flags, function(f)
return flagAllowedInSector(sector, f.name)
end)
local vessel = pickFiltered(vessels, function(v)
return vesselAllowedInSector(sector, v)
end)
local cargo = pickWeighted(cargoTypes)
local tag = pickWeighted(cargoTags)
local partner = pickWeighted(partnerProfiles)
local wlOK, wl03note = wlEligibility(cargo, tag, flag, partner)
local wlEligible = wlOK
local corridor = wlEligible and wlCorridors[math.random(#wlCorridors)] or nil
local obs = wlEligible and observers[math.random(#observers)] or nil
local flagStage = getFlagStage(flag.name, globalStage)
local function baseProb()
local q = 0.20
if flag.risky then q = q + 0.05 end
if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then q = q + 0.08 end
q = q + (cargo and cargo.risk or 0)
if cargo and cargo.dual then q = q + 0.12 end
q = q + (tag and tag.risk or 0)
q = q + (partner and partner.risk or 0)
if wlEligible then q = math.max(q - 0.08, 0.08) end
local isIF = flag.name == "Imperial Federation"
local isMT = flag.name:match("^Matamoros")
if (isIF or isMT) and not wlEligible then
if flagStage >= 1 then q = q + 0.04 end
if flagStage >= 2 then q = q + 0.04 end
end
if flag.name:match("Matamoros") and wl03note == "WL-03 not signed (M-AMBER)" then
q = q + 0.08
end
if flag.name == "Bassaridian-flag" then
q = q - 0.18
if wlEligible then q = q - 0.04 end
if q < 0.02 then q = 0.02 end
if q > 0.08 then q = 0.08 end
end
return math.min(q, 0.72)
end
local riskPush = 0
if flag.name:match("Imperial Federation") or flag.name:match("^Matamoros") then riskPush = riskPush + 1 end
if cargo.dual or (cargo.risk or 0) >= 0.12 then riskPush = riskPush + 1 end
if tag.name ~= "standard" then riskPush = riskPush + 1 end
if (partner.risk or 0) >= 0.09 then riskPush = riskPush + 1 end
if wlEligible then riskPush = math.max(riskPush - 1, 0) end
local score, phCode, phText = phScore(sector, flag, vessel, cargo, tag, partner)
score = score + phRateArg * 0.5
rowObjs[i] = {
i=i, sector=sector, flag=flag, vessel=vessel, cargo=cargo, tag=tag, partner=partner,
wlEligible = wlEligible, wl03note = wl03note, corridor = corridor, obs = obs,
flagStage = flagStage, enforceProb = baseProb(), riskPush = riskPush,
phScore = score, phCode = phCode, phText = phText
}
end
-- Select top K PH rows
if phOn and phCap > 0 then
table.sort(rowObjs, function(a,b) return a.phScore > b.phScore end)
local applied = 0
for _,r in ipairs(rowObjs) do
if r.phCode and applied < phCap and r.phScore > 0 then
r.phApplied = true
applied = applied + 1
else
r.phApplied = false
end
end
table.sort(rowObjs, function(a,b) return a.i < b.i end)
else
for _,r in ipairs(rowObjs) do r.phApplied = false end
end
-- Pass 2: render (no Notes column)
local out = {}
table.insert(out, "{| class='wikitable sortable'")
if phOn then
table.insert(out, "! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Appeals/Review (Straits Conventions) !! Case ID !! PH Case !! Public Health Advisory !! Triggers")
else
table.insert(out, "! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Appeals/Review (Straits Conventions) !! Case ID !! Triggers")
end
for _,r in ipairs(rowObjs) do
math.randomseed(dailySeed + r.i*131)
local action, legal, outcome, appealCol
local dname, dkind
if r.phApplied then
action = "Public health interdiction ("..r.phCode..")"
legal = legalBasis(r.sector, false, r.wl03note, true)
outcome = "Public health hold; extended review"
appealCol = "SAC Referral — health safeguards (Annex D)"
else
if r.wlEligible then
dname, dkind = pickServedDistrict(r.corridor)
action = "WL transit / pilotage verification"
legal = legalBasis(r.sector, true, r.wl03note, false)
else
action = (r.wl03note == "WL-03 not signed (M-AMBER)" or r.wl03note == "WL-03 cooldown (90 days)")
and "WL-ineligible: standard PSC applied"
or "Cargo manifest reconciliation audit"
legal = legalBasis(r.sector, false, r.wl03note, false)
end
local enforce = r.enforceProb
if math.random() > enforce then
outcome = r.wlEligible and "Cleared WL window" or "No reports"
appealCol = r.wlEligible and "N/A (WL cleared)" or "N/A"
else
outcome = r.wlEligible and "WL reconciliation check; monitoring" or "Administrative notice issued; monitoring"
if r.riskPush >= 2 and math.random() < 0.65 then
outcome = (r.wlEligible and "WL hold; clearance pending expedited review" or "Random audit hold; clearance pending")
if r.flag.name == "Imperial Federation" or r.flag.name:match("^Matamoros") then
outcome = outcome .. " [Extended regulatory review]"
end
end
local isIF = r.flag.name == "Imperial Federation"
local isMT = r.flag.name:match("^Matamoros")
if r.flagStage >= 3 and (isIF or isMT) and not r.wlEligible and r.riskPush >= 2 then
outcome = "Denial of passage (policy Stage III)"
appealCol = "SCT injunction eligible | Appeals Case pending"
end
if r.flag.name == "Bassaridian-flag" and outcome:match("hold") then
outcome = r.wlEligible and "WL reconciliation complete; cleared same window" or "Administrative reconciliation complete; cleared"
appealCol = r.wlEligible and "N/A (WL cleared)" or "N/A"
end
appealCol = appealCol or appealsStatus(r.sector, r.wlEligible, r.tag, r.cargo)
end
end
local caseId = string.format("CASE-%03d.%03d-%02d", dateInfo.year, dateInfo.dayOfYear, r.i)
local phCaseId = "—"
local phText = "No concern"
if phOn and r.phApplied then
phCaseId = string.format("PH-CASE-%03d.%03d-%02d-%s", dateInfo.year, dateInfo.dayOfYear, r.i, r.phCode or "PH")
phText = r.phText or "Public health concern"
end
local triggers = buildTriggers(r.flag, r.sector, r.cargo, r.tag, r.partner, r.wlEligible, r.wl03note, r.riskPush, r.flagStage)
if outcome:lower():match("hold") or outcome:lower():match("conditions") or outcome:lower():match("monitoring") or outcome:lower():match("denial") then
appealCol = (appealCol ~= "N/A" and (appealCol .. " | ")) or ""
appealCol = appealCol .. ("Appeals Case: APP-" .. caseId)
end
local style
local o = outcome:lower()
if r.phApplied or o:match("hold") or o:match("denial") or o:match("detention") or o:match("impound") or o:match("extended regulatory review") then
style = ' style="background-color:#f8d7da;"'
elseif o:match("administrative notice") or o:match("monitoring") or o:match("conditions") or o:match("reconciliation") then
style = ' style="background-color:#fff3cd;"'
else
style = ' style="background-color:#d4edda;"'
end
table.insert(out, "|-" .. style)
if phOn then
table.insert(out, string.format("| %s || %s || %s (%s) || %s || %s || %s || %s || %s || %s || %s || %s",
dateInfo.text, r.sector, r.vessel, r.flag.name, action, legal, outcome, appealCol, caseId, phCaseId, phText, triggers))
else
table.insert(out, string.format("| %s || %s || %s (%s) || %s || %s || %s || %s || %s || %s",
dateInfo.text, r.sector, r.vessel, r.flag.name, action, legal, outcome, appealCol, caseId, triggers))
end
end
table.insert(out, "|}")
if showLegend then
table.insert(out, "\n<div style='margin-top:0.5em; font-size:90%;'>")
table.insert(out, "<b>Legend:</b> ")
table.insert(out, "<span style='background:#d4edda;padding:2px 6px;border:1px solid #c3e6cb;'>Green</span> = Cleared/Compliant ")
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Administrative/Conditional/Monitoring ")
table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = Hold/Denial/Extended Review")
table.insert(out, "<br/><i>Policy Stage (per flag):</i> I = Audit emphasis; II = +Market surcharge advisory; III = limited denial on high-risk, non-WL rows.")
table.insert(out, "</div>")
end
if phOn then
table.insert(out, "\n<div style='margin-top:0.3em; font-size:90%;'><b>Public Health Advisory Codes:</b> ")
local parts = {}
for _,d in ipairs(phDiseases) do
table.insert(parts, string.format("<span style='white-space:nowrap;'><b>%s</b> – %s</span>", d.code, d.name))
end
table.insert(out, table.concat(parts, " "))
table.insert(out, "</div>")
end
return table.concat(out, "\n")
end
return p