absurdor

Files related to my duties are Absurdor of Agora Nomic
Log | Files | Refs

absurdor (8113B)


      1 #!/usr/bin/env lua5.4
      2 
      3 local argparse = require "argparse"
      4 local json = require "json"
      5 local path = require "path"
      6 local fs = require "path.fs"
      7 local record = require "lib.record"
      8 local pprint = require("pprint").pprint
      9 local date = require "date"
     10 
     11 require "lib.util"
     12 require "lib.log"
     13 
     14 local parser = argparse("absurdor", "Manage Absurdor duties")
     15 	:command_target("command")
     16 local commands = {}
     17 
     18 commands.report = parser:command("report", "Generate and send absurdor report")
     19 commands.report:flag("-p", "Print report, but do not send")
     20 commands.report:flag("-d", "Plot the difference, not the height")
     21 commands.report:option("-t", "Which template to use")
     22 	:default("templates/banner.m4")
     23 	:argname("template")
     24 	:target("template")
     25 commands.record = parser:command("record", "Record events")
     26 	:command_target("what")
     27 commands.push = commands.record:command("push", "A push of The Boulder")
     28 commands.push:argument("who", "Name of the player")
     29 commands.push:argument("when", "Timestamp of boulder push")
     30 	:convert(maildate)
     31 commands.push:option("-m", "Message ID where push happened"):target("where")
     32 commands.log = parser:command("log", "Display log")
     33 
     34 commands.transfer = commands.record:command("transfer", "Transfer the Veblen")
     35 commands.transfer:argument("who", "Name of the player")
     36 commands.transfer:argument("when", "When the transfer took place")
     37 	:convert(maildate)
     38 commands.transfer:argument("payed", "Amount spent on the transfer")
     39 	:convert(tonumber)
     40 commands.transfer:option("-m", "Message ID where transfer happened"):target("where")
     41 
     42 commands.devalue = commands.record:command("devalue", "Devalue the Veblen")
     43 commands.devalue:argument("who", "Name of the player")
     44 commands.devalue:argument("when", "When the devalueing took place")
     45 	:convert(maildate)
     46 commands.devalue:option("-m", "Message ID where devalue happened"):target("where")
     47 
     48 local args = parser:parse()
     49 
     50 fn = "log.json"
     51 log = decodewith(json.decode, fn)
     52 
     53 if args.command == "report" then
     54 
     55 	height = 0
     56 	local plot = {}
     57 	local pushed = {}
     58 	local max = 1
     59 	local failed = false
     60 	local who = {}
     61 	local players = {}
     62 	local veblen = {
     63 		cost = 1,
     64 		history = {},
     65 		namewidth = 0
     66 	}
     67 	local last = 0
     68 	local slope
     69 	local maxslope = 1
     70 	local maxheight = 0
     71 
     72 	if #log > 0 then
     73 		start = unix2week(log[1].when)
     74 		max = start
     75 	end
     76 
     77 	for i,e in ipairs(log) do
     78 		if e.what == "push" then
     79 			local w = unix2week(e.when)
     80 			if (unix2week(1739754105) <= w and plot[w] == 0 and plot[unix2week(e.when - 7*24*60*60)] < slope) or
     81 			   (unix2week(1693159683) <= w and w < unix2week(1739754105) and not (pushed[unix2week(e.when - 7*24*60*60)] or pushed[w])) or
     82 			   (w < unix2week(1693159683) and height == 100)
     83                 -- At 1739754105 seconds from Unix epoch, the governing rule was changed
     84                 -- so that the Boulder falls to zero if it was not pushed as much as its
     85                 -- slope the previous week. However, the change is not retroactive; hence
     86                 -- the magic number.
     87 
     88 				-- At 1693159683 seconds from Unix epoch, the governing
     89 				-- rule was changed so that the Boulder falls to zero
     90 				-- if it was not pushed the previous week. However, the
     91 				-- change is not retroactive; hence the magic number.
     92 			then
     93 				height = 1
     94 				slope = 1
     95 			else
     96 				height = (height + 1)
     97 				maxheight = math.max(height, maxheight)
     98 			end
     99 			plot[w] = (plot[w] or 0) + 1
    100 			if slope and plot[w] > slope then
    101 				slope = plot[w]
    102 				if e.when > 1739754105 then
    103 					maxslope = math.max(slope, maxslope)
    104 				end
    105 			end
    106 			who[w] = who[w] or {}
    107 			table.insert(who[w], e.who)
    108 			pushed[w] = true
    109 			if height >= plot[max] then
    110 				max = w
    111 			end
    112 			if players[e.who] then
    113 				players[e.who] = players[e.who] + 1
    114 			else
    115 				players[e.who] = 1
    116 			end
    117 		elseif e.what == "transfer" then
    118 			die(e.payed < veblen.cost, string.format("Recorded transfer by %s used less spendies (%d) than the current Veblen cost (%s)", e.who, e.payed, veblen.cost))
    119 			if veblen.current then table.insert(veblen.history, veblen.current) end
    120 			veblen.current = {who = e.who, payed = e.payed, cost = veblen.cost, when = e.when, what = "transfer"}
    121 			veblen.cost = e.payed + 1
    122 			veblen.namewidth = math.max(veblen.namewidth, string.len(e.who))
    123 		elseif e.what == "devalue" then
    124 			local val = math.ceil(veblen.cost/2)
    125 			table.insert(veblen.history, {when = e.when, what = "devalue", value = e.value})
    126 			veblen.cost = e.value
    127 		elseif e.what == "report" then
    128 			veblen.cost = e.cost or veblen.cost
    129 			height = e.height or e.height
    130 			last = e.when
    131 			slope = e.slope or 1
    132 		end
    133 	end
    134 
    135 	local news = {}
    136 	for i,e in ipairs(log) do
    137 		if e.when > last then
    138 			table.insert(news, e)
    139 		end
    140 	end
    141 
    142 	table.insert(veblen.history, veblen.current)
    143 
    144 	local t = os.time()
    145 	local w = unix2week(t)
    146 	if (not (w == 0)) and (not pushed[unix2week(t - 7*24*60*60)]) then
    147 		failed = true
    148 		if not pushed[w] then
    149 			height = 0
    150 			plot[w] = 0
    151 		end
    152 	end
    153 
    154 	vars = {
    155 		YYYY = os.date("!%Y"),
    156 		MM = os.date("!%m"),
    157 		DD = os.date("!%d"),
    158 		N = height,
    159 		K = slope,
    160 		MN = maxheight,
    161 		MK = maxslope
    162 	}
    163 
    164 	defs = ""
    165 
    166 	for k, v in pairs(vars) do
    167 		defs = defs .. string.format(" --define=%s=%s", k, v)
    168 	end
    169 
    170 	tmpname = ".tmp"
    171 	-- Height banner
    172 	os.execute(string.format("m4 %s %s >> %s", defs, args.template, tmpname))
    173 
    174 	f = io.open(tmpname, "a")
    175 
    176 	f:write("EVENTS SINCE LAST REPORT\n")
    177 	table.sort(news, function(e0, e1) return e0.when > e1.when end)
    178 	for i,e in ipairs(news) do
    179 		f:write(fmt_event(e) .. "\n")
    180 	end
    181 	f:write("\n")
    182 
    183 	-- Top pushers
    184 	f:write("TOP PUSHERS\n")
    185 	local scores = {}
    186 	for p,v in pairs(players) do
    187 		table.insert(scores, {player = p, pushes = v})
    188 	end
    189 	table.sort(scores, function (s0, s1) return s0.pushes > s1.pushes end)
    190 	for i,s in ipairs(scores) do
    191 		f:write(string.format("#%02d %2d %s %s\n", i, s.pushes, string.rep("=", s.pushes), s.player))
    192 	end
    193 	f:write("\n")
    194 
    195 	f:write("\n----------------------------------------------------------------------\nTHE VEBLEN\n\n")
    196 	f:write("          The Veblen\n")
    197 	f:write(string.format("          is owned by %s\n", veblen.current.who))
    198 	f:write(string.format("          and costs %d spendies\n\n", veblen.cost))
    199 
    200 	f:write("          It is shiny.\n")
    201 	f:write("          It is round (and thus pointless).\n")
    202 	f:write("          It is admired.\n")
    203 	f:write("          It has infinite points and is thus no longer pointless.\n")
    204 	f:write("            (N.B. do note that that's a circle)\n")
    205 	f:write("\n")
    206 
    207 	f:write("HISTORY\n")
    208 	table.sort(veblen.history, function(x,y) return x.when > y.when end)
    209 	for _,e in ipairs(veblen.history) do
    210 		if e.what == "transfer" then
    211 			f:write(string.format("[%s] %s %s%s\n", os.date("!%Y-%m-%d %H:%M %z", e.when), string.rep(" ", veblen.namewidth - string.len(e.who)) .. e.who, string.rep("$", e.cost), string.rep("+", e.payed - e.cost)))
    212 		elseif e.what == "devalue" then
    213 			f:write(string.format("[%s] %s %s\n", os.date("!%Y-%m-%d %H:%M %z", e.when), string.rep(" ", veblen.namewidth), string.rep("$", e.value)))
    214 		end
    215 	end
    216 	f:write('[2024-07-18 02:59 +0000] The Veblen is created')
    217 	f:write("\n\n")
    218 
    219 	f:write("----------------------------------------------------------------------\n")
    220 	f:write("Do you have any suggestions on what I should put on the report?\n")
    221 	f:write("Send them to me!\n")
    222 	f:write("======================================================================\n")
    223 	f:close()
    224 
    225 	if args.p then
    226 		os.execute(string.format("cat %s", tmpname))
    227 		os.remove(tmpname)
    228 	else
    229 		os.execute(string.format("neomutt -H %s -E", tmpname))
    230 
    231 		if yn("Archive report? [Yn] ") then
    232 			table.insert(log, {
    233 							 when = os.time(),
    234 							 what = "report",
    235 							 height = height,
    236 							 cost = veblen.cost,
    237 							 owner = veblen.current.who
    238 			})
    239 			os.rename(tmpname, string.format("archive/%s", os.date("!%F")))
    240 		else
    241 			os.remove(tmpname)
    242 		end
    243 	end
    244 elseif args.command == "record" then
    245 	record[args.what](io.stdout, args, log)
    246 elseif args.command == "log" then
    247 	for _,e in ipairs(log) do
    248 		io.write(fmt_event(e).."\n")
    249 	end
    250 end
    251 
    252 encodewith(json.encode, fn, log)
    253 os.execute(string.format('jq --sort-keys . %s > .tmp', fn))
    254 os.execute(string.format('mv .tmp %s', fn))