Module:ConceptValidation

From No Subject
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