Module:StraitsDaily: Difference between revisions

From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs)
No edit summary
NewZimiaGov (talk | contribs)
No edit summary
Line 1: Line 1:
-- Module: StraitsDaily
-- Module: StraitsDaily
-- Purpose: Daily, in-world report of Strait of Haifa enforcement
-- Purpose: Daily, in-world report of Strait of Haifa enforcement  
 


local p = {}
local p = {}
Line 36: Line 35:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
local sectors = {
local sectors = {
     "Strait South Inbound", "Strait South Outbound", "Strait North Inbound", "Strait North Outbound",
     "Strait South Inbound","Strait South Outbound","Strait North Inbound","Strait North Outbound",
     "Archipelago Zone", "Sacred Buffer Approaches", "Environmental Buffer", "Northern Approach TSS", "Southern Approach TSS"
     "Archipelago Zone","Sacred Buffer Approaches","Environmental Buffer","Northern Approach TSS","Southern Approach TSS"
}
}


Line 54: Line 53:


local vessels = {
local vessels = {
     "Handymax bulker", "Product tanker", "General cargo ship", "Container feeder",
     "Handymax bulker","Product tanker","General cargo ship","Container feeder",
     "Ro-Ro ferry", "Research craft", "Declared submarine", "Undeclared submarine"
     "Ro-Ro ferry","Research craft","Declared submarine","Undeclared submarine"
}
}


Line 90: Line 89:
     {code="P-44", weight=4, risk=0.02},
     {code="P-44", weight=4, risk=0.02},
     {code="P-53", weight=3, risk=0.04}
     {code="P-53", weight=3, risk=0.04}
}
-- White-Lane corridors (codes + typical windows). Used for annotation only.
local wlCorridors = {
    {code="WL-NZ-1", name="Lucíusu–Corum Electric City", win="0700–1100"},
    {code="WL-GM-2", name="Ossyo–Lake Norton South Shore", win="0900–1300"},
    {code="WL-SEA-3", name="Corumia–Western Corum Coastal (short-sea)", win="0600–1800"}
}
-- Neutral observer pool (for WL attestation note)
local observers = {
    "Neutral temple auditor","Independent port compliance inspector",
    "Community elder (witness)","Straits Convention monitor (civilian)"
}
}


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- (C) MAIN GENERATION
-- (C) HELPERS
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function p.makeTable(frame)
local function pick(list) return list[math.random(#list)] end
    local dateInfo = getPSSCDate()
    math.randomseed(dateInfo.year*1000 + dateInfo.dayOfYear)
    local rows = tonumber(frame and frame.args and frame.args.rows) or 18


    local function pickWeighted(list)
local function pickWeighted(list)
        if type(list) ~= "table" then return nil end
    local total = 0
        local total = 0
    for _, item in ipairs(list) do total = total + (item.weight or 1) end
        for _, item in ipairs(list) do total = total + (item.weight or 1) end
    local roll, cumul = math.random(total), 0
        local roll, cumul = math.random(total), 0
    for _, item in ipairs(list) do
        for _, item in ipairs(list) do
        cumul = cumul + (item.weight or 1)
            cumul = cumul + (item.weight or 1)
        if roll <= cumul then return item end
            if roll <= cumul then return item end
        end
        return list[#list]
     end
     end
    return list[#list]
end


     local function pick(list) return list[math.random(#list)] end
-- Determine if this row qualifies for White-Lane annotation (Annex G eligibility, simplified)
local function isWhiteLane(cargo, tag)
    if not cargo or not tag then return false end
    -- Green/Amber cargoes likely eligible when not dual-use and tagged time-sensitive
     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")
    return (green and not cargo.dual) or (amber and not cargo.dual)
end


    local function baseProb(flag, sector, cargo, tag, partner)
-- Build legal basis string per Conventions
        local p = 0.28
local function legalBasis(sector, wl)
        if flag.risky then p = p + 0.06 end
    if wl then
         if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then p = p + 0.08 end
         return "Art III.5 (White-Lane); Annex G; Art X.3 (expedited remedies)"
        p = p + (cargo and cargo.risk or 0)
        if cargo and cargo.dual then p = p + 0.03 end
        p = p + (tag and tag.risk or 0)
        p = p + (partner and partner.risk or 0)
        return math.min(p, 0.72)
     end
     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


    -- NEW: simple appeals generator tied to Straits Conventions pathways
-- Appeals column text
    local function appealsStatus(sector, cargo, tag)
local function appealsStatus(sector, wl, tag, cargo)
         -- pick level
    if wl then
         local level
         -- Expedited track per Art X.3
         if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then
        local outcome
            level = "SAC Referral"           -- Sacred Arbitration Council
        local relief = 0.60
         else
        if tag and (tag.name=="emergency" or tag.name=="perishable") then relief = relief + 0.12 end
            level = (math.random() < 0.35) and "SCT Admin Review" or "HCE Review"
        if cargo and cargo.dual then relief = relief - 0.15 end
         end
         local r = math.random()
         if r <= relief * 0.6 then outcome = "clearance granted"
        elseif r <= relief then outcome = "expedited with conditions"
        else outcome = "conditions imposed" end
        return string.format("HCE Review → SCT (72h) → SAC (≤5d) — %s (Art X.3; Annex G)", 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


        -- outcome tendencies
-- Simple HCE reference (for WL notes)
        local relief = 0.50
local function hceRef(dateInfo, corridor)
        if level == "HCE Review" then
    return string.format("HCE-%03d.%03d-%s", dateInfo.year, dateInfo.dayOfYear, corridor.code)
            relief = relief + 0.10            -- admin fixes easier
end
        elseif level == "SCT Admin Review" then
            relief = relief - 0.05
        elseif level == "SAC Referral" then
            relief = relief - 0.10            -- conservative near sacred/ecology
        end
        if tag and (tag.name == "perishable" or tag.name == "emergency") then
            relief = relief + 0.08            -- time-sensitive goods get some relief
        end
        if cargo and cargo.dual then
            relief = relief - 0.08            -- dual-use disputes harder to win
        end
        if relief < 0.10 then relief = 0.10 end
        if relief > 0.85 then relief = 0.85 end


        local roll = math.random()
-------------------------------------------------------------------------------
        local result
-- (D) MAIN GENERATION
        if roll <= relief * 0.6 then
-------------------------------------------------------------------------------
            result = "clearance granted"
function p.makeTable(frame)
        elseif roll <= relief then
    local dateInfo = getPSSCDate()
            result = "expedited with conditions"
    math.randomseed(dateInfo.year*1000 + dateInfo.dayOfYear)
        else
    local rows = tonumber(frame and frame.args and frame.args.rows) or 18
            result = (level == "SAC Referral") and "hold sustained" or "conditions imposed"
        end
 
        -- cite clauses neutrally
        local cite = "SC §A2–A4, §R1–R3"
        if cargo and cargo.dual then
            cite = "SC §B1–B4, §R1–R3"
        end
        if sector == "Sacred Buffer Approaches" or sector == "Environmental Buffer" then
            cite = "SC §D1–D3, §E1–E2"
        end
        if tag and tag.name == "emergency" then
            cite = cite .. ", §L1–L2"
        end
        return string.format("%s — %s (%s)", level, result, cite)
    end


     local out = {}
     local out = {}
     table.insert(out, "{| class='wikitable sortable'")
     table.insert(out, "{| class='wikitable sortable'")
    -- NEW: add Appeals/Review column
     table.insert(out, "! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Appeals/Review (Straits Conventions) !! Notes")
     table.insert(out, "! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Appeals/Review (Straits Conventions) !! Notes")


     for _ = 1, rows do
     for _ = 1, rows do
         local sector = pick(sectors)
         local sector = pick(sectors)
         local flag = pickWeighted(flags)
         local flag   = pickWeighted(flags)
         local vessel = pick(vessels)
         local vessel = pick(vessels)
         local cargo = pickWeighted(cargoTypes)
         local cargo   = pickWeighted(cargoTypes)
         local tag = pickWeighted(cargoTags)
         local tag     = pickWeighted(cargoTags)
         local partner = pickWeighted(partnerProfiles)
         local partner = pickWeighted(partnerProfiles)


         local enforce = baseProb(flag, sector, cargo, tag, partner)
        local wlEligible = isWhiteLane(cargo, tag)
         local outcome, action, legal, notes, appealCol
        local corridor = wlEligible and pick(wlCorridors) or nil
        local obs = wlEligible and pick(observers) or nil
 
        -- Base enforcement probability
        local function baseProb()
            local q = 0.28
            if flag.risky then q = q + 0.06 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.03 end
            q = q + (tag and tag.risk or 0)
            q = q + (partner and partner.risk or 0)
            -- WL usually lowers friction (pre-cleared), but can still be inspected
            if wlEligible then q = math.max(q - 0.06, 0.10) end
            return math.min(q, 0.72)
        end
 
         local enforce = baseProb()
        local action, legal, outcome, notes, appealCol
 
        if wlEligible then
            action = "WL transit / pilotage verification"
            legal  = legalBasis(sector, true)
            notes  = string.format("WL %s (%s) | window %s | Observer: %s | %s | Cargo: %s [%s]; Partner: %s",
                                  corridor.code, corridor.name, corridor.win, (obs or "n/a"),
                                  hceRef(dateInfo, corridor), cargo.name, tag.name, partner.code)
         else
            action = "Cargo manifest reconciliation audit"
            legal = legalBasis(sector, false)
            notes = string.format("Cargo: %s [%s]; Partner: %s", cargo.name, tag.name, partner.code)
        end


         if math.random() > enforce then
         if math.random() > enforce then
             outcome = "No reports"
             outcome = wlEligible and "Cleared WL window" or "No reports"
             action = "Routine transit"
             appealCol = wlEligible and "N/A (WL cleared)" or "N/A"
            legal = "N/A"
            appealCol = "N/A"
            notes = string.format("Cargo: %s [%s]; Partner: %s — Compliant passage", cargo.name, tag.name, partner.code)
         else
         else
             action = "Cargo manifest reconciliation audit"
             outcome = wlEligible and "WL reconciliation check; monitoring" or "Administrative notice issued; monitoring"
            legal = "SC §R1–R3"
            outcome = "Administrative notice issued; monitoring"
            notes = string.format("Cargo: %s [%s]; Partner: %s", cargo.name, tag.name, partner.code)


            -- push to hold if multiple risk factors present
             local riskPush = 0
             local riskPush = 0
             if flag.name:match("Imperial Federation") or flag.name:match("Matamoros") then riskPush = riskPush + 1 end
             if flag.name:match("Imperial Federation") or flag.name:match("Matamoros") then riskPush = riskPush + 1 end
             if cargo.dual or cargo.risk >= 0.12 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 tag.name ~= "standard" then riskPush = riskPush + 1 end
             if partner.risk >= 0.09 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


             if riskPush >= 2 and math.random() < 0.65 then
             if riskPush >= 2 and math.random() < 0.65 then
                 outcome = "Random audit hold; clearance pending procedural update"
                 outcome = (wlEligible and "WL hold; clearance pending expedited review" or "Random audit hold; clearance pending")
                 if flag.name == "Imperial Federation" or flag.name == "Matamoros (IF)" then
                 if flag.name == "Imperial Federation" or flag.name == "Matamoros (IF)" then
                     outcome = outcome .. " [Extended regulatory review]"
                     outcome = outcome .. " [Extended regulatory review]"
Line 218: Line 242:
             end
             end


            -- NEW: populate appeals column when an action occurs
             appealCol = appealsStatus(sector, wlEligible, tag, cargo)
             appealCol = appealsStatus(sector, cargo, tag)
         end
         end



Revision as of 03:24, 10 September 2025

Documentation for this module may be created at Module:StraitsDaily/doc

-- Module: StraitsDaily
-- Purpose: Daily, in-world report of Strait of Haifa enforcement 

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 totalDaysElapsed = math.floor((now - 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

    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","Sacred Buffer Approaches","Environmental Buffer","Northern Approach TSS","Southern Approach TSS"
}

local flags = {
    {name="Bassaridian-flag", weight=6, risky=false},
    {name="Moorland", weight=2, risky=false},
    {name="Imperial Federation", weight=5, risky=true},
    {name="Matamoros (IF)", weight=2, risky=true},
    {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},
    {code="P-12", weight=3, risk=0.03},
    {code="P-17", weight=3, risk=0.12},
    {code="P-22", weight=3, risk=0.14},
    {code="P-31", weight=3, risk=0.05},
    {code="P-38", weight=2, risk=0.10},
    {code="P-44", weight=4, risk=0.02},
    {code="P-53", weight=3, risk=0.04}
}

-- White-Lane corridors (codes + typical windows). Used for annotation only.
local wlCorridors = {
    {code="WL-NZ-1", name="Lucíusu–Corum Electric City", win="0700–1100"},
    {code="WL-GM-2", name="Ossyo–Lake Norton South Shore", win="0900–1300"},
    {code="WL-SEA-3", name="Corumia–Western Corum Coastal (short-sea)", win="0600–1800"}
}

-- Neutral observer pool (for WL attestation note)
local observers = {
    "Neutral temple auditor","Independent port compliance inspector",
    "Community elder (witness)","Straits Convention monitor (civilian)"
}

-------------------------------------------------------------------------------
-- (C) 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

-- Determine if this row qualifies for White-Lane annotation (Annex G eligibility, simplified)
local function isWhiteLane(cargo, tag)
    if not cargo or not tag then return false end
    -- Green/Amber cargoes likely eligible when not dual-use and tagged time-sensitive
    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")
    return (green and not cargo.dual) or (amber and not cargo.dual)
end

-- Build legal basis string per Conventions
local function legalBasis(sector, wl)
    if wl then
        return "Art III.5 (White-Lane); Annex G; Art X.3 (expedited remedies)"
    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

-- Appeals column text
local function appealsStatus(sector, wl, tag, cargo)
    if wl then
        -- Expedited track per Art X.3
        local outcome
        local relief = 0.60
        if tag and (tag.name=="emergency" or tag.name=="perishable") then relief = relief + 0.12 end
        if cargo and cargo.dual then relief = relief - 0.15 end
        local r = math.random()
        if r <= relief * 0.6 then outcome = "clearance granted"
        elseif r <= relief then outcome = "expedited with conditions"
        else outcome = "conditions imposed" end
        return string.format("HCE Review → SCT (72h) → SAC (≤5d) — %s (Art X.3; Annex G)", 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

-- Simple HCE reference (for WL notes)
local function hceRef(dateInfo, corridor)
    return string.format("HCE-%03d.%03d-%s", dateInfo.year, dateInfo.dayOfYear, corridor.code)
end

-------------------------------------------------------------------------------
-- (D) MAIN GENERATION
-------------------------------------------------------------------------------
function p.makeTable(frame)
    local dateInfo = getPSSCDate()
    math.randomseed(dateInfo.year*1000 + dateInfo.dayOfYear)
    local rows = tonumber(frame and frame.args and frame.args.rows) or 18

    local out = {}
    table.insert(out, "{| class='wikitable sortable'")
    table.insert(out, "! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Appeals/Review (Straits Conventions) !! Notes")

    for _ = 1, rows do
        local sector  = pick(sectors)
        local flag    = pickWeighted(flags)
        local vessel  = pick(vessels)
        local cargo   = pickWeighted(cargoTypes)
        local tag     = pickWeighted(cargoTags)
        local partner = pickWeighted(partnerProfiles)

        local wlEligible = isWhiteLane(cargo, tag)
        local corridor = wlEligible and pick(wlCorridors) or nil
        local obs = wlEligible and pick(observers) or nil

        -- Base enforcement probability
        local function baseProb()
            local q = 0.28
            if flag.risky then q = q + 0.06 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.03 end
            q = q + (tag and tag.risk or 0)
            q = q + (partner and partner.risk or 0)
            -- WL usually lowers friction (pre-cleared), but can still be inspected
            if wlEligible then q = math.max(q - 0.06, 0.10) end
            return math.min(q, 0.72)
        end

        local enforce = baseProb()
        local action, legal, outcome, notes, appealCol

        if wlEligible then
            action = "WL transit / pilotage verification"
            legal  = legalBasis(sector, true)
            notes  = string.format("WL %s (%s) | window %s | Observer: %s | %s | Cargo: %s [%s]; Partner: %s",
                                   corridor.code, corridor.name, corridor.win, (obs or "n/a"),
                                   hceRef(dateInfo, corridor), cargo.name, tag.name, partner.code)
        else
            action = "Cargo manifest reconciliation audit"
            legal  = legalBasis(sector, false)
            notes  = string.format("Cargo: %s [%s]; Partner: %s", cargo.name, tag.name, partner.code)
        end

        if math.random() > enforce then
            outcome  = wlEligible and "Cleared WL window" or "No reports"
            appealCol = wlEligible and "N/A (WL cleared)" or "N/A"
        else
            outcome = wlEligible and "WL reconciliation check; monitoring" or "Administrative notice issued; monitoring"

            -- push to hold if multiple risk factors present
            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

            if riskPush >= 2 and math.random() < 0.65 then
                outcome = (wlEligible and "WL hold; clearance pending expedited review" or "Random audit hold; clearance pending")
                if flag.name == "Imperial Federation" or flag.name == "Matamoros (IF)" then
                    outcome = outcome .. " [Extended regulatory review]"
                end
            end

            appealCol = appealsStatus(sector, wlEligible, tag, cargo)
        end

        table.insert(out, "|-")
        table.insert(out, string.format("| %s || %s || %s (%s) || %s || %s || %s || %s || %s",
            dateInfo.text, sector, vessel, flag.name, action, legal, outcome, appealCol, notes))
    end

    table.insert(out, "|}")
    return table.concat(out, "\n")
end

return p