Module:Accession descriptors
From Landrace.wiki
More actions
Documentation for this module may be created at Module:Accession descriptors/doc
--[[
Map an accession's plant descriptors to the wiki's descriptor vocabulary: the character names and
state words of UPOV TG/276/2 (2025), the hemp and cannabis test guideline, with in-house states where
no standard defines one. The wiki uses the words only. A value here is an observation of a population,
never a UPOV note or a trial result.
Template:Accession passes each descriptor parameter through this module twice: once for the value
Semantic MediaWiki stores, once for the infobox. The page source is never changed. A page that says
"Christmas tree" stores and shows the plant shape "Conical"; "Intermediate" leaflets store "Medium".
Every value of a parameter falls in one of five kinds (the tables below):
* a state of the parameter's own character, stored and shown ("Intermediate" -> leaflet width Medium);
* a state of another character, stored and shown there ("Purples" in leaf_color -> leaf anthocyanin
colour Purple; "Foxtailed" in flower_structure -> flower cluster shape Foxtailed);
* not a state at all (a condition, a growth stage, a texture): shown in plain words under
"Also noted" and not stored ("Woody" stem colour -> "woody stem");
* recorded in the right character but not a standard state: shown as written, not stored;
* a placeholder ("Unknown"): nothing.
A value the tables do not list is passed through as written, stored and shown in its own character,
so SMW checks it against the property's allowed values exactly as before.
Functions (named arguments are the template's parameters, by name; pass every value by name):
store |<character>|<param>=… -> the stored states, separated by ";" (use with #set |+sep=;)
display |<character>|<param>=… -> the infobox text for one character
noted |<param>=…|<param>=… -> the infobox text for "Also noted"
formLabel|1=<value> -> the label Form:Accession shows for a stored state
]]
local p = {}
-- The characters: the property each one stores, and the parameters that feed it, in display order.
-- A parameter named here but not in its own FIELDS entry feeds the character through its table.
local CHARACTERS = {
sex_expression = { 'sexual_characteristics' },
photoperiod = { 'photoperiod_response' },
chemotype = { 'chemotype' },
plant_shape = { 'growth_pattern' },
branching = { 'branching' },
leaflet_width = { 'leaf_shape' },
leaf_green = { 'leaf_color' },
leaf_anthocyanin = { 'leaf_color', 'leaf_anthocyanin' },
stem_colour = { 'stem_color' },
stigma_colour = { 'stigma_color' },
leaflet_serration = { 'serration' },
flower_density = { 'flower_structure', 'flower_density' },
flower_cluster_size = { 'flower_structure', 'flower_cluster_size' },
flower_cluster_shape = { 'flower_structure', 'flower_cluster_shape' },
terpenes = { 'terpene_profile' },
aroma = { 'aroma', 'terpene_profile' },
seed_viability = { 'seed_viability' },
}
--[[
FIELDS[param]: own = the parameter's own character (nil: its unlisted values go to "Also noted");
multi = the parameter holds a comma-separated list (otherwise the whole value is one value);
map = lower-cased value -> one of:
'State' a state of the own character
{ 'character', 'State' } a state of another character
{ note = 'plain words' } not a state: "Also noted", not stored
{ asis = true } shown as written in the own character, not stored
{ 'character', 'State', show = 'text' } stored as State, shown as text
false a placeholder: nothing
The standard states map to themselves, so a value written in the vocabulary's own words keeps working.
]]
local FIELDS = {
sexual_characteristics = { own = 'sex_expression', map = {
dioecious = 'Dioecious', monoecious = 'Monoecious',
unknown = false,
} },
photoperiod_response = { own = 'photoperiod', map = {
['short-day'] = 'Short-day', ['day-neutral'] = 'Day-neutral', other = 'Other',
['semi-autoflowering'] = { 'photoperiod', 'Other', show = 'Other ("semi-autoflowering")' },
unknown = false,
} },
chemotype = { own = 'chemotype', map = {
['thc-predominant'] = 'THC-predominant', ['mixed thc and cbd'] = 'Mixed THC and CBD',
['cbd-predominant'] = 'CBD-predominant', ['cbg-predominant'] = 'CBG-predominant',
['low cannabinoid content'] = 'Low cannabinoid content', other = 'Other', segregating = 'Segregating',
['thc-dominant'] = 'THC-predominant', ['cbd-dominant'] = 'CBD-predominant',
['mixed (thc-dominant, cbd-dominant, thcv)'] = 'Segregating',
['high thc'] = { note = 'high THC' },
unknown = false,
} },
growth_pattern = { own = 'plant_shape', multi = true, map = {
conical = 'Conical', columnar = 'Columnar', bushy = 'Bushy', candelabra = 'Candelabra',
spreading = 'Spreading', sprawling = 'Sprawling',
['christmas tree'] = 'Conical', menorah = 'Candelabra', creeper = 'Sprawling', ['vine-like'] = 'Sprawling',
unknown = false,
} },
branching = { own = 'branching', map = {
weak = 'Weak', medium = 'Medium', strong = 'Strong',
minimal = 'Weak', moderate = 'Medium', extensive = 'Strong',
['apical dominant'] = { note = 'apical dominance' },
unknown = false,
} },
leaf_shape = { own = 'leaflet_width', map = {
narrow = 'Narrow', medium = 'Medium', broad = 'Broad',
intermediate = 'Medium', ['skinny leaf'] = 'Narrow',
unknown = false,
} },
leaf_color = { own = 'leaf_green', multi = true, map = {
light = 'Light', medium = 'Medium', dark = 'Dark',
['light greens'] = 'Light', ['dark greens'] = 'Dark',
purples = { 'leaf_anthocyanin', 'Purple' }, reds = { 'leaf_anthocyanin', 'Red' },
blacks = { 'leaf_anthocyanin', 'Very dark purple' },
purple = { 'leaf_anthocyanin', 'Purple' }, red = { 'leaf_anthocyanin', 'Red' },
['very dark purple'] = { 'leaf_anthocyanin', 'Very dark purple' },
yellowing = { note = 'yellowing leaves' }, browning = { note = 'browning leaves' },
bruised = { note = 'bruised leaves' }, variegated = { note = 'variegated leaves' },
unknown = false,
} },
leaf_anthocyanin = { own = 'leaf_anthocyanin', multi = true, map = {
purple = 'Purple', red = 'Red', ['very dark purple'] = 'Very dark purple',
unknown = false,
} },
stem_color = { own = 'stem_colour', multi = true, map = {
yellow = 'Yellow', ['medium green'] = 'Medium green', ['dark green'] = 'Dark green', purple = 'Purple',
['light greens'] = 'Medium green', ['dark greens'] = 'Dark green', reds = 'Purple', purples = 'Purple',
woody = { note = 'woody stem' }, striped = { note = 'striped stem' },
yellowing = { note = 'yellowing stem' },
unknown = false,
} },
stigma_color = { own = 'stigma_colour', multi = true, map = {
white = 'White', pink = 'Pink', red = 'Red', orange = 'Orange', brown = 'Brown',
['pink hairs'] = 'Pink', ['red hairs'] = 'Red', ['orange hairs'] = 'Orange', ['brown hairs'] = 'Brown',
immature = { note = 'immature stigmas' },
unknown = false,
} },
serration = { own = 'leaflet_serration', map = {
shallow = 'Shallow', medium = 'Medium', deep = 'Deep',
smooth = { asis = true },
sharp = { note = 'sharp leaflet teeth' },
other = false, unknown = false,
} },
flower_structure = { multi = true, map = {
loose = { 'flower_density', 'Loose' }, airy = { 'flower_density', 'Loose' },
fluffy = { 'flower_density', 'Loose' }, dense = { 'flower_density', 'Dense' },
small = { 'flower_cluster_size', 'Small' }, big = { 'flower_cluster_size', 'Large' },
large = { 'flower_cluster_size', 'Large' },
foxtailed = { 'flower_cluster_shape', 'Foxtailed' }, running = { 'flower_cluster_shape', 'Elongated' },
elongated = { 'flower_cluster_shape', 'Elongated' }, compact = { 'flower_cluster_shape', 'Compact' },
thick = { note = 'thick flower clusters' }, thin = { note = 'thin flower clusters' },
unknown = false,
} },
flower_density = { own = 'flower_density', multi = true, map = {
loose = 'Loose', medium = 'Medium', dense = 'Dense', unknown = false,
} },
flower_cluster_size = { own = 'flower_cluster_size', multi = true, map = {
small = 'Small', medium = 'Medium', large = 'Large', unknown = false,
} },
flower_cluster_shape = { own = 'flower_cluster_shape', multi = true, map = {
compact = 'Compact', elongated = 'Elongated', foxtailed = 'Foxtailed', unknown = false,
} },
-- Named terpenes, from a test. A compound named with no test behind it is shown as written and
-- not stored; aroma words written here are shown and stored as aroma.
terpene_profile = { own = 'terpenes', map = {
unknown = false,
['myrcene dominant'] = { asis = true },
['earthy spice, herbal musk, tropical fruit undertones'] = { 'aroma', 'Earthy spice, herbal musk, tropical fruit undertones' },
['earthy spice, herbal musk, woody undertones'] = { 'aroma', 'Earthy spice, herbal musk, woody undertones' },
['earthy spice, herbal musk, woody undertones, bright sweet fruit, mango'] = { 'aroma', 'Earthy spice, herbal musk, woody undertones, bright sweet fruit, mango' },
['earthy spice, herbal musk, woody undertones, sweet fruit, mango'] = { 'aroma', 'Earthy spice, herbal musk, woody undertones, sweet fruit, mango' },
['earthy spice, herbal musk, mango, tropical fruit undertones'] = { 'aroma', 'Earthy spice, herbal musk, mango, tropical fruit undertones' },
['mango, tropical fruit, floral, earthy spice, herbal musk'] = { 'aroma', 'Mango, tropical fruit, floral, earthy spice, herbal musk' },
['earthy spice, herbal musk, woody undertones, mango, subtle herbal notes'] = { 'aroma', 'Earthy spice, herbal musk, woody undertones, mango, subtle herbal notes' },
['earthy spice, herbal musk, woody undertones, sweet tropical fruit, mango'] = { 'aroma', 'Earthy spice, herbal musk, woody undertones, sweet tropical fruit, mango' },
['myrcene dominant, earthy spice, herbal musk, woody undertones, tropical fruit, mango'] = { 'aroma', 'Myrcene dominant, earthy spice, herbal musk, woody undertones, tropical fruit, mango' },
['incense, bright lemon citrus'] = { 'aroma', 'Incense, bright lemon citrus' },
} },
aroma = { own = 'aroma', map = {} },
seed_viability = { own = 'seed_viability', map = {
viable = 'Viable', marginal = 'Marginal', ['not viable'] = 'Not viable',
unknown = false, ['n/a'] = false,
} },
}
-- What Form:Accession shows for a stored state, where the vocabulary's word is not the one growers use.
local FORM_LABELS = {
Conical = 'Conical (Christmas tree)',
Candelabra = 'Candelabra (menorah)',
Sprawling = 'Sprawling (creeper, vine-like)',
Elongated = 'Elongated (running)',
}
local function trim(s)
return mw.text.trim(s or '')
end
local function split(value, multi)
value = trim(value)
if value == '' then
return {}
end
if not multi then
return { value }
end
local out = {}
for _, v in ipairs(mw.text.split(value, ',', true)) do
v = trim(v)
if v ~= '' then
table.insert(out, v)
end
end
return out
end
-- One value of one parameter -> { char =, state =, show =, store =, note = } or nil (nothing).
local function classify(param, value)
local f = FIELDS[param]
if not f then
return nil
end
local m = f.map[mw.ustring.lower(value)]
if m == false then
return nil
end
if m == nil then
if f.own then
return { char = f.own, state = value, show = value, store = true }
end
return { note = value }
end
if type(m) == 'string' then
return { char = f.own, state = m, show = m, store = true }
end
if m.note then
return { note = m.note }
end
if m.asis then
return { char = f.own, show = value, store = false }
end
return { char = m[1], state = m[2], show = m.show or m[2], store = true }
end
-- All classified values of the parameters in args, in parameter then value order.
local function collect(args, params)
local out = {}
for _, param in ipairs(params) do
local f = FIELDS[param]
if f then
for _, v in ipairs(split(args[param], f.multi)) do
local c = classify(param, v)
if c then
table.insert(out, c)
end
end
end
end
return out
end
local function addUnique(list, seen, s)
if s and s ~= '' and not seen[s] then
seen[s] = true
table.insert(list, s)
end
end
-- "Conical, bushy": a list in running text, capital only at the start. A word that starts with two
-- capitals (THC-predominant) keeps them.
local function sentence(list, sep)
local out = {}
for i, s in ipairs(list) do
if i > 1 and not mw.ustring.match(s, '^%u%u') then
s = mw.ustring.lower(mw.ustring.sub(s, 1, 1)) .. mw.ustring.sub(s, 2)
end
out[i] = s
end
local text = table.concat(out, sep or ', ')
return mw.ustring.upper(mw.ustring.sub(text, 1, 1)) .. mw.ustring.sub(text, 2)
end
function p._store(char, args)
local list, seen = {}, {}
for _, c in ipairs(collect(args, CHARACTERS[char] or {})) do
if c.char == char and c.store then
addUnique(list, seen, c.state)
end
end
return table.concat(list, ';')
end
function p._display(char, args)
local list, seen = {}, {}
for _, c in ipairs(collect(args, CHARACTERS[char] or {})) do
if c.char == char then
addUnique(list, seen, c.show)
end
end
if #list == 0 then
return ''
end
if char == 'aroma' or char == 'terpenes' then
-- free text: each value is a description of its own, shown as written
return table.concat(list, '; ')
end
return sentence(list)
end
local NOTED_ORDER = { 'growth_pattern', 'branching', 'leaf_shape', 'leaf_color', 'stem_color', 'stigma_color',
'serration', 'flower_structure', 'chemotype' }
function p._noted(args)
local list, seen = {}, {}
for _, c in ipairs(collect(args, NOTED_ORDER)) do
if c.note then
addUnique(list, seen, c.note)
end
end
if #list == 0 then
return ''
end
return sentence(list)
end
function p._formLabel(value)
value = trim(value)
return FORM_LABELS[value] or value
end
local function namedArgs(frame)
local args = {}
for k, v in pairs(frame.args) do
if type(k) == 'string' then
args[k] = v
end
end
return args
end
function p.store(frame)
return p._store(trim(frame.args[1]), namedArgs(frame))
end
function p.display(frame)
return p._display(trim(frame.args[1]), namedArgs(frame))
end
function p.noted(frame)
return p._noted(namedArgs(frame))
end
function p.formLabel(frame)
return p._formLabel(frame.args[1])
end
-- For checks: the tables, read-only.
p.CHARACTERS, p.FIELDS, p.FORM_LABELS = CHARACTERS, FIELDS, FORM_LABELS
return p