Advanced

Documenting scripts with AI

Documenting scripts with AI
June 19, 2025 07:25AM
I just want to make people aware of how useful AI can be.

A question was raised on Facebook regarding a challenge checker that didn't give the expected result. During research I found at least two issues.
1) A bug in the checker giving confusing output.
2) The cloned tag not matching the challenge requirements.

To understand what was happening (not my script) I wanted to document the LUA code. I gave it to an AI (Anthropic) and asked it to add comments. It was almost perfect on first attempt, only minor semantics.

I then explained the issue itself, gave it output, and what was missing. From there we concluded about the issues, and how to best solve it. AI ain't always superb, but in this case it for sure wanted to solve things the same way I wanted.

In the end there was a more or less complete rewrite of the script. Most of the code itself was kept, but changing the order of it quite a bit. Primarily making it from a single pass process into dual pass process. First figure out what data to use, then render output.

In the end I ended with a more well written script, which became well documented. I will post before and after here.

My main message here is, don't be afraid to use AI to produce comments. It makes the scripts much easier to read. Let the AI produce them. Read through it yourself and correct the minor issues that might show up. It's a fast way of producing well documented code.

PS! One bug was that it could write that one had 23/25 geocaches, but yet it would list 24. The issue was unnecessary complex code to render output at the same time as it was figuring out which geocaches was valid and not.
Re: Documenting scripts with AI
June 19, 2025 07:25AM
Original:
local args={...}

conf = args[1].config

local filter = {}
filter.country = conf.country
filter.region = conf.region
filter.county = conf.county
filter.minVisitDate = conf.minVisitDate
filter.maxVisitDate = conf.maxVisitDate
filter.minHiddenDate = conf.minHiddenDate
filter.maxHiddenDate = conf.maxHiddenDate
filter.types = conf.types
filter.sizes = conf.sizes
filter.difficulties = conf.difficulties
filter.terrains = conf.terrains

local mincaches = 1
if (not (conf.mincaches == nil)) and tonumber(conf.mincaches)>0 then
	mincaches = tonumber(conf.mincaches)
end

local minage = 1
if (not (conf.minage == nil)) and tonumber(conf.minage)>=0 then
	minage = tonumber(conf.minage)
end

local maxage = 999
if (not (conf.maxage == nil)) and tonumber(conf.maxage)>=0 then
	maxage = tonumber(conf.maxage)
end

local mintotal = 1
if (not (conf.mintotal == nil)) and tonumber(conf.mintotal)>0 then
	mintotal = tonumber(conf.mintotal)
end

local mindifferentages = 1
if (conf.mindifferentages ~= nil) and tonumber(conf.mindifferentages)>0 then
	mindifferentages = tonumber(conf.mindifferentages)
end

uniquedates = false
if conf.uniquedates ~= nil and conf.uniquedates=="YES" then
	uniquedates = true
end


local showall = false
if (not (conf.showall == nil)) and conf.showall=="YES" then
	showall = true
end

profileName = args[1]['profileName']
profileId = PGC.ProfileName2Id(profileName)

local cachesfound = 0
local totalage = 0
local differentages = 0
local datelog = {}
local resultText = ""
local htmlBlob = ""
local meets
local countsperage = {}
local logperdate = {}

local caches
myFinds = PGC.GetFinds( profileId, { fields = { 'gccode', 'cache_name', 'hidden', 'visitdate' }, filter=filter, order = 'OLDESTFIRST' } )

if #myFinds>0 then
	local lastyear = tonumber(string.sub(myFinds[#myFinds].visitdate,1,4));
	for age=1,lastyear-2000 do
		countsperage[age] = 0
    end
end

local qualified = {}
for n,v in ipairs(myFinds) do
	local birthdate = string.sub(v.hidden,6)
	if birthdate == string.sub(v.visitdate,6) then
		local age = tonumber(string.sub(v.visitdate,1,4))-tonumber(string.sub(v.hidden,1,4))
		if age >= minage and age <= maxage then
			if uniquedates then
				if datelog[birthdate] ~= nil then
					-- Project-GC has changed this from %d to %d+ to include multidigit ages
                                        local prevage = tonumber(string.match(datelog[birthdate]," %(age (%d+)%)$"))
					if prevage < age then
						totalage = totalage - prevage
						cachesfound = cachesfound - 1
						countsperage[prevage] = countsperage[prevage] - 1
						if countsperage[prevage] <= 0 then differentages = differentages - 1 end
					else
						age = 0
					end
				end
				if age > 0 then
					datelog[birthdate] = "[" .. v.gccode .. "](http://coord.info/" .. v.gccode .. ") " .. v.cache_name .. ", hidden " .. string.sub(v.hidden,1,4) .. " found " .. string.sub(v.visitdate,1,4) .. " (age " .. age .. ")"
				end
			end
		end
		if age >= minage and age <= maxage then
			table.insert (qualified, v)
			cachesfound = cachesfound + 1
			totalage = totalage + age
			if countsperage[age] == 0 then
				differentages = differentages + 1
			end
			countsperage[age] = countsperage[age] + 1
			if showall or (mincaches>mindifferentages) or (countsperage[age] == 1) or (mintotal>mindifferentages) then
				resultText = resultText .. birthdate .. " [" .. v.gccode .. "](http://coord.info/" .. v.gccode .. ") " .. v.cache_name .. ", hidden " .. string.sub(v.hidden,1,4) .. " found " .. string.sub(v.visitdate,1,4) .. " (age " .. age .. ")\n"
			end
			meets = (totalage >= mintotal) and (cachesfound >= mincaches) and (differentages >= mindifferentages)
			if meets and not showall then break end
		end
	end		
end

if meets and uniquedates then
	resultText = ""
	local a = {}
    for n in pairs(datelog) do table.insert(a, n) end
    table.sort(a)
    for i,n in ipairs(a) do
    	resultText = resultText .. n .. " " .. datelog[n] .. "\n"
    end
end

if not meets then
	if cachesfound == 0 then
		htmlBlob = "You have not found any qualifying cache."
	elseif cachesfound < mincaches then
		htmlBlob = "You have only found " .. cachesfound .. " of the " .. mincaches .. " caches required."
	elseif differentages < mindifferentages then
		htmlBlob = "You have only found caches with " .. differentages .. " different ages ("
		local printcomma = false
		for age,count in ipairs(countsperage) do
			if count > 0 then
				if printcomma then htmlBlob = htmlBlob .. "," end
				htmlBlob = htmlBlob .. age
				printcomma = true
			end
		end
		htmlBlob = htmlBlob .. "), but you need " .. mindifferentages .. "."
	elseif totalage < mintotal then
		htmlBlob = "The total age of the birthday caches you found is only " .. totalage .. ", but you need at least " .. mintotal .. "."
	else
		htmlBlob = ""
	end
	if #qualified > 0 then
		htmlBlob = htmlBlob .. "\n<TABLE><TR><TH>GCcode</TH><TH>Hidden</TH><TH>Found</TH></TR>\n"
		local loga = {}
		for _,v in ipairs(qualified) do
			table.insert (loga, "<TR><TD><A href=http://coord.info/"..v.gccode..">"..v.gccode.."</A></TD><TD>"..v.hidden.."</TD><TD>"..v.visitdate.."</TD><TR>")
		end
		htmlBlob = htmlBlob .. table.concat (loga, "\n") .. "\n</TABLE>"
	end
end

return { ok = meets, log = resultText, html = htmlBlob }
Re: Documenting scripts with AI
June 19, 2025 07:25AM
New version:
--[[
Geocaching Birthday Geocache Challenge Checker

This script validates whether a geocacher has completed a "birthday geocache" challenge.
A birthday geocache is one where the geocache was found on the same month/day it was hidden,
but in a different year (hence the "age" calculation).

The checker supports:
- Finding a minimum number of birthday geocaches
- Achieving a minimum total "age" (sum of all geocache ages)
- Finding geocaches with a minimum number of different ages
- Optionally requiring unique dates (only one geocache per birth date)

Example: A geocache hidden on 2010-05-15 and found on 2015-05-15 has an "age" of 5 years.
]]

--[[
Changelog:
2025-06-19: Documented scripts with several comments.
2025-06-19: Rewrote script into a two pass procedure. This was done to make sure that the
            "You have only found X of the Y..." actually matched the number of items in the list.
2025-06-19: Return `html = false` when not used, not an empty string.
]]

-- Get input arguments and configuration
local args = {...}
conf = args[1].config

-- Initialize filter object for geocache search
local filter = {}
filter.country = conf.country
filter.region = conf.region
filter.county = conf.county
filter.minVisitDate = conf.minVisitDate
filter.maxVisitDate = conf.maxVisitDate
filter.minHiddenDate = conf.minHiddenDate
filter.maxHiddenDate = conf.maxHiddenDate
filter.types = conf.types
filter.sizes = conf.sizes
filter.difficulties = conf.difficulties
filter.terrains = conf.terrains

-- Parse configuration parameters with defaults

-- Minimum number of birthday geocaches required (default: 1)
local mincaches = 1
if (not (conf.mincaches == nil)) and tonumber(conf.mincaches) > 0 then
    mincaches = tonumber(conf.mincaches)
end
PGC.print("mincaches: ", mincaches, "\n")

-- Minimum age of geocaches (years between hidden and found dates, default: 1)
local minage = 1
if (not (conf.minage == nil)) and tonumber(conf.minage) >= 0 then
    minage = tonumber(conf.minage)
end
PGC.print("minage: ", minage, "\n")

-- Maximum age of geocaches (default: 999 years)
local maxage = 999
if (not (conf.maxage == nil)) and tonumber(conf.maxage) >= 0 then
    maxage = tonumber(conf.maxage)
end
PGC.print("maxage: ", maxage, "\n")

-- Minimum total age (sum of all geocache ages, default: 1)
local mintotal = 1
if (not (conf.mintotal == nil)) and tonumber(conf.mintotal) > 0 then
    mintotal = tonumber(conf.mintotal)
end
PGC.print("mintotal: ", mintotal, "\n")

-- Minimum number of different ages required (default: 1)
local mindifferentages = 1
if (conf.mindifferentages ~= nil) and tonumber(conf.mindifferentages) > 0 then
    mindifferentages = tonumber(conf.mindifferentages)
end
PGC.print("mindifferentages: ", mindifferentages, "\n")

-- Whether to enforce unique dates (only one geocache per birth date)
uniquedates = false
if conf.uniquedates ~= nil and conf.uniquedates == "YES" then
    uniquedates = true
end
PGC.print("uniquedates: ", uniquedates, "\n")

-- Whether to show all qualifying geocaches or stop when requirements are met
local showall = false
if (not (conf.showall == nil)) and conf.showall == "YES" then
    showall = true
end
PGC.print("showall: ", showall, "\n")

-- Get profile information
profileName = args[1]['profileName']
profileId = PGC.ProfileName2Id(profileName)

-- Retrieve user's geocache finds from backend
myFinds = PGC.GetFinds(profileId, {
    fields = { 'gccode', 'cache_name', 'hidden', 'visitdate' },
    filter = filter,
    order = 'OLDESTFIRST'
})

-- PASS 1: Determine best geocache for each birth date
local bestPerBirthdate = {}

for n, v in ipairs(myFinds) do
    -- Extract birth date (month-day) from hidden date
    local birthdate = string.sub(v.hidden, 6)

    -- Check if geocache was found on its "birthday" (same month-day)
    if birthdate == string.sub(v.visitdate, 6) then
        -- Calculate geocache age (years between hidden and found)
        local age = tonumber(string.sub(v.visitdate, 1, 4)) - tonumber(string.sub(v.hidden, 1, 4))

        -- Check if age is within acceptable range
        if age >= minage and age <= maxage then
            -- If uniquedates is enabled, only keep the oldest geocache for each birth date
            if uniquedates then
                if bestPerBirthdate[birthdate] == nil or age > bestPerBirthdate[birthdate].age then
                    PGC.print("Adding " .. v.gccode .. " for birthdate " .. birthdate .. ", replacing potential duplicates", "\n")
                    bestPerBirthdate[birthdate] = {
                        geocache = v,
                        age = age
                    }
                end
            else
                -- If uniquedates is disabled, keep all qualifying geocaches
                if bestPerBirthdate[birthdate] == nil then
                    bestPerBirthdate[birthdate] = {}
                end
                PGC.print("Adding " .. v.gccode .. " for birthdate " .. birthdate, "\n")
                table.insert(bestPerBirthdate[birthdate], {
                    geocache = v,
                    age = age
                })
            end
        end
    end
end

--PGC.print("Pass 1 complete - bestPerBirthdate:", bestPerBirthdate)

-- PASS 2: Build results from the clean data
local qualified = {}
local cachesfound = 0
local totalage = 0
local differentages = 0
local countsperage = {}
local resultText = ""

-- Initialize age counters
if #myFinds > 0 then
    local lastyear = tonumber(string.sub(myFinds[#myFinds].visitdate, 1, 4));
    PGC.print("Initializing countsperage from age 1 to " .. (lastyear - 2000), "\n")
    for age = 1, lastyear - 2000 do
        countsperage[age] = 0
    end
end

-- Sort birth dates for consistent output (oldest hidden dates first)
local sortedEntries = {}
for birthdate, entry in pairs(bestPerBirthdate) do
    if uniquedates then
        table.insert(sortedEntries, {birthdate = birthdate, entry = entry})
    else
        for _, subentry in ipairs(entry) do
            table.insert(sortedEntries, {birthdate = birthdate, entry = subentry})
        end
    end
end

-- Sort by hidden date (oldest first)
table.sort(sortedEntries, function(a, b)
    return a.entry.geocache.hidden < b.entry.geocache.hidden
end)

-- Process each entry in sorted order
for _, sortedEntry in ipairs(sortedEntries) do
    local birthdate = sortedEntry.birthdate
    local entry = sortedEntry.entry
    local v = entry.geocache
    local age = entry.age

    table.insert(qualified, v)
    cachesfound = cachesfound + 1
    totalage = totalage + age

    if countsperage[age] == 0 then
        differentages = differentages + 1
    end
    countsperage[age] = countsperage[age] + 1

    -- Add to result text
    if showall or (mincaches > mindifferentages) or (countsperage[age] == 1) or (mintotal > mindifferentages) then
        resultText = resultText .. birthdate .. " [" .. v.gccode .. "](http://coord.info/" .. v.gccode .. ") " ..
                   v.cache_name .. ", hidden " .. string.sub(v.hidden, 1, 4) ..
                   " found " .. string.sub(v.visitdate, 1, 4) .. " (age " .. age .. ")\n"
    end

    -- Check if all requirements are now met
    local meets = (totalage >= mintotal) and (cachesfound >= mincaches) and (differentages >= mindifferentages)

    -- Stop processing if requirements are met and not showing all
    if meets and not showall then
        break
    end
end

-- Check final requirements
local meets = (totalage >= mintotal) and (cachesfound >= mincaches) and (differentages >= mindifferentages)

-- Generate failure message if requirements not met
local htmlBlob = false
if not meets then
    htmlBlob = ""
    if cachesfound == 0 then
        htmlBlob = "You have not found any qualifying geocache."
    elseif cachesfound < mincaches then
        htmlBlob = "You have only found " .. cachesfound .. " of the " .. mincaches .. " geocaches required."
    elseif differentages < mindifferentages then
        htmlBlob = "You have only found geocaches with " .. differentages .. " different ages ("
        local printcomma = false
        for age, count in ipairs(countsperage) do
            if count > 0 then
                if printcomma then
                    htmlBlob = htmlBlob .. ","
                end
                htmlBlob = htmlBlob .. age
                printcomma = true
            end
        end
        htmlBlob = htmlBlob .. "), but you need " .. mindifferentages .. "."
    elseif totalage < mintotal then
        htmlBlob = "The total age of the birthday geocaches you found is only " .. totalage ..
                  ", but you need at least " .. mintotal .. "."
    else
        htmlBlob = ""
    end

    -- Add table of qualifying geocaches even if requirements not fully met
    if #qualified > 0 then
        htmlBlob = htmlBlob .. "\n<TABLE><TR><TH>GCcode</TH><TH>Hidden</TH><TH>Found</TH></TR>\n"
        local loga = {}
        for _, v in ipairs(qualified) do
            table.insert(loga, "<TR><TD><A href=http://coord.info/" .. v.gccode .. ">" ..
                              v.gccode .. "</A></TD><TD>" .. v.hidden .. "</TD><TD>" ..
                              v.visitdate .. "</TD><TR>")
        end
        htmlBlob = htmlBlob .. table.concat(loga, "\n") .. "\n</TABLE>"
    end
end

-- Return results
return {
    ok = meets,           -- Boolean: whether challenge is completed
    log = resultText,     -- Formatted log text
    html = htmlBlob       -- HTML formatted results
}
Sorry, you do not have permission to post/reply in this forum.