Module:ConceptValidation
Jump to navigation
Jump to search
Documentation for this module may be created at Module:ConceptValidation/doc
--------------------------------------------------------------------------------
-- Module:ConceptValidation
-- Validates semantic consistency, checks completeness, and auto-categorizes
-- concept pages based on Infobox parameters
--------------------------------------------------------------------------------
local p = {}
local cargo = mw.ext.cargo
--------------------------------------------------------------------------------
-- CATEGORIZE: Main entry point for validation and categorization
--------------------------------------------------------------------------------
function p.categorize(frame)
local args = frame:getParent().args
local categories = {}
local validationMessages = {}
-- Get basic info
local conceptName = args.concept_name or mw.title.getCurrentTitle().text
local originatingThinker = args.originating_thinker
local firstAppearance = args.first_appearance
local historicalPeriod = args.historical_period
-- === COMPLETENESS CHECKS ===
-- Check etymology completeness
local hasEtymology = (args.etymology and args.etymology ~= '') or
(args.original_term and args.original_term ~= '')
if not hasEtymology then
table.insert(categories, '[[Category:Concepts - Etymology needed]]')
end
-- Check school views completeness
local hasSchoolViews = false
local schoolCount = 0
local schools = {'freudian', 'lacanian', 'kleinian', 'ego_psychology',
'object_relations', 'relational'}
for _, school in ipairs(schools) do
if args[school .. '_view'] and args[school .. '_view'] ~= '' then
hasSchoolViews = true
schoolCount = schoolCount + 1
end
end
if not hasSchoolViews then
table.insert(categories, '[[Category:Concepts - Missing school views]]')
elseif schoolCount == 1 then
table.insert(categories, '[[Category:Concepts - Needs additional school views]]')
end
-- Check relationship mapping
local hasRelationships = false
for i = 1, 50 do
if args['relation_' .. i .. '_target'] and args['relation_' .. i .. '_target'] ~= '' then
hasRelationships = true
break
end
end
if not hasRelationships then
table.insert(categories, '[[Category:Concepts - Relationship mapping incomplete]]')
end
-- Check clinical information
local hasClinical = (args.clinical_relevance and args.clinical_relevance ~= '') or
(args.clinical_description and args.clinical_description ~= '')
if not hasClinical then
table.insert(categories, '[[Category:Concepts - Clinical information needed]]')
end
-- Check if concept is complete
if hasEtymology and hasSchoolViews and hasRelationships and hasClinical then
table.insert(categories, '[[Category:Concepts - Complete]]')
end
-- === CONSISTENCY CHECKS ===
-- Check Freudian view vs. originating thinker
if args.freudian_view and args.freudian_view ~= '' then
if originatingThinker and not originatingThinker:match('[Ff]reud') then
table.insert(validationMessages,
'<div class="validation-warning">⚠ Has Freudian view but originator is not Freud</div>')
end
end
-- Check historical period consistency
if firstAppearance and historicalPeriod then
local year = tonumber(firstAppearance:match('%d%d%d%d'))
if year then
local periodMismatch = false
if historicalPeriod:match('[Ee]arly [Ff]reud') and (year > 1905) then
periodMismatch = true
elseif historicalPeriod:match('[Pp]ost%-[Ww]ar') and (year < 1945 or year > 1980) then
periodMismatch = true
elseif historicalPeriod:match('[Cc]ontemporary') and (year < 1980) then
periodMismatch = true
end
if periodMismatch then
table.insert(validationMessages,
'<div class="validation-warning">⚠ First appearance year may not match historical period</div>')
table.insert(categories, '[[Category:Concepts - Needs period review]]')
end
end
end
-- Check translation debate consistency
if args.translation_debate and args.translation_debate ~= '' then
if not args.first_translation or args.first_translation == '' then
table.insert(validationMessages,
'<div class="validation-warning">⚠ Translation debate present but first translation missing</div>')
end
if not args.original_term or args.original_term == '' then
table.insert(validationMessages,
'<div class="validation-warning">⚠ Translation debate present but original term missing</div>')
end
end
-- === SPECIAL CATEGORIZATION ===
-- Homonym flagging
if args.homonym_flag == 'true' or args.homonym_flag == 'yes' then
table.insert(categories, '[[Category:Concepts - Homonyms]]')
end
-- Contested concepts
if args.scholarly_consensus == 'contested' or args.scholarly_consensus == 'divided' then
table.insert(categories, '[[Category:Concepts - Contested]]')
end
-- Evolving concepts
if args.conceptual_precision == 'evolving' then
table.insert(categories, '[[Category:Concepts - Evolving]]')
end
-- Abandoned concepts
if args.conceptual_precision == 'abandoned' then
table.insert(categories, '[[Category:Concepts - Historical (abandoned)]]')
end
-- Clinical frequency categorization
if args.clinical_frequency then
if args.clinical_frequency:match('[Cc]entral') then
table.insert(categories, '[[Category:Concepts - Central to practice]]')
elseif args.clinical_frequency:match('[Tt]heoretical') then
table.insert(categories, '[[Category:Concepts - Primarily theoretical]]')
end
end
-- === OUTPUT ===
local output = {}
-- Add validation messages if any
if #validationMessages > 0 then
table.insert(output, '<div class="concept-validation-section">')
for _, msg in ipairs(validationMessages) do
table.insert(output, msg)
end
table.insert(output, '</div>')
end
-- Add maintenance categories
for _, cat in ipairs(categories) do
table.insert(output, cat)
end
return table.concat(output, '\n')
end
--------------------------------------------------------------------------------
-- CHECK FOR ORPHANED CONCEPTS (concepts with no relationships)
--------------------------------------------------------------------------------
function p.findOrphans()
-- This would be called from a maintenance page
local tables = 'Concepts'
local fields = '_pageName, concept_name'
local allConcepts = cargo.query(tables, fields, {})
local orphans = {}
for _, concept in ipairs(allConcepts) do
-- Check if concept has any relationships (as source or target)
local relationTables = 'Concept_Relations'
local relationFields = '_rowID'
local where = 'source_concept="' .. concept.concept_name .. '" OR target_concept="' ..
concept.concept_name .. '"'
local rels = cargo.query(relationTables, relationFields, {where=where, limit=1})
if #rels == 0 then
table.insert(orphans, concept.concept_name)
end
end
return orphans
end
--------------------------------------------------------------------------------
-- CHECK FOR MISSING RECIPROCAL RELATIONSHIPS
--------------------------------------------------------------------------------
function p.findMissingReciprocals()
-- This would be called from a maintenance page
local tables = 'Concept_Relations'
local fields = 'source_concept, target_concept, relation_type'
local where = 'bidirectional=true'
local bidirectional = cargo.query(tables, fields, {where=where})
local missing = {}
for _, rel in ipairs(bidirectional) do
-- Check if reverse exists
local reverseTables = 'Concept_Relations'
local reverseFields = '_rowID'
local reverseWhere = 'source_concept="' .. rel.target_concept .. '" AND target_concept="' ..
rel.source_concept .. '"'
local reverse = cargo.query(reverseTables, reverseFields,
{where=reverseWhere, limit=1})
if #reverse == 0 then
table.insert(missing, {
from = rel.source_concept,
to = rel.target_concept,
type = rel.relation_type
})
end
end
return missing
end
--------------------------------------------------------------------------------
-- GENERATE COMPLETENESS REPORT
--------------------------------------------------------------------------------
function p.completenessReport(frame)
local output = {}
-- Query all concepts
local tables = 'Concepts'
local fields = '_pageName, concept_name'
local concepts = cargo.query(tables, fields, {})
local stats = {
total = #concepts,
complete = 0,
needsEtymology = 0,
needsSchools = 0,
needsRelationships = 0,
needsClinical = 0
}
-- Count concepts in each category
for _, concept in ipairs(concepts) do
local pageName = concept._pageName
-- Check categories (this is a simplified version)
-- In practice, you'd query the categorylinks table
-- For now, just provide structure
end
-- Build report
table.insert(output, '{| class="wikitable sortable"')
table.insert(output, '! Metric !! Count !! Percentage')
table.insert(output, '|-')
table.insert(output, '| Total Concepts || ' .. stats.total .. ' || 100%')
table.insert(output, '|-')
table.insert(output, '| Complete Concepts || ' .. stats.complete .. ' || ')
table.insert(output, string.format('%.1f%%', (stats.complete/stats.total)*100))
table.insert(output, '|-')
table.insert(output, '| Need Etymology || ' .. stats.needsEtymology .. ' || ')
table.insert(output, string.format('%.1f%%', (stats.needsEtymology/stats.total)*100))
table.insert(output, '|}')
return table.concat(output, '\n')
end
--------------------------------------------------------------------------------
-- UTILITY: Verify concept exists in database
--------------------------------------------------------------------------------
function p.conceptExists(conceptName)
local tables = 'Concepts'
local fields = '_pageName'
local where = 'concept_name="' .. conceptName .. '"'
local results = cargo.query(tables, fields, {where=where, limit=1})
return #results > 0
end
--------------------------------------------------------------------------------
-- UTILITY: Get concept metadata
--------------------------------------------------------------------------------
function p.getConceptMetadata(conceptName)
local tables = 'Concepts'
local fields = '*'
local where = 'concept_name="' .. conceptName .. '"'
local results = cargo.query(tables, fields, {where=where, limit=1})
if #results > 0 then
return results[1]
else
return nil
end
end
return p