Module:ConceptRelations

From No Subject
Jump to navigation Jump to search

Documentation for this module may be created at Module:ConceptRelations/doc

--------------------------------------------------------------------------------
-- Module:ConceptRelations (Enhanced Version 4.0)
-- Handles rendering of schools, contributors, and relationships for Infobox_concept
-- Includes Cargo storage integration and semantic validation
--------------------------------------------------------------------------------

local p = {}
local cargo = mw.ext.cargo

-- Mapping of contribution types to CSS classes
local contributionTypes = {
    ["originator"] = "originator",
    ["reinterpretation"] = "reinterpretation",
    ["elaboration"] = "elaboration",
    ["systematic elaboration"] = "systematic-elaboration",
    ["systematic-elaboration"] = "systematic-elaboration",
    ["clinical extension"] = "clinical-extension",
    ["clinical-extension"] = "clinical-extension",
    ["clinical"] = "clinical",
    ["interdisciplinary application"] = "interdisciplinary-application",
    ["interdisciplinary-application"] = "interdisciplinary-application",
    ["interdisciplinary"] = "interdisciplinary",
    ["critique"] = "critique",
    ["influence"] = "elaboration"
}

-- Mapping of relationship types to CSS classes
local relationTypes = {
    ["foundational"] = "foundational",
    ["foundational dependency"] = "foundational-dependency",
    ["foundational-dependency"] = "foundational-dependency",
    ["prerequisite"] = "prerequisite",
    ["elaborates"] = "elaborates",
    ["extension"] = "extension",
    ["elaborates-refines"] = "elaborates-refines",
    ["elaborates/refines"] = "elaborates-refines",
    ["clinical manifestation"] = "clinical-manifestation",
    ["clinical-manifestation"] = "clinical-manifestation",
    ["clinical application"] = "clinical-application",
    ["clinical-application"] = "clinical-application",
    ["clinical"] = "clinical",
    ["opposition"] = "opposition",
    ["theoretical opposition"] = "theoretical-opposition",
    ["theoretical-opposition"] = "theoretical-opposition",
    ["contradicts"] = "contradicts",
    ["supersedes"] = "supersedes",
    ["temporal-sequence"] = "temporal-sequence",
    ["mutual-constitution"] = "mutual-constitution",
    ["clinical-alternative"] = "clinical-alternative",
    ["translational-equivalent"] = "translational-equivalent",
    ["interdisciplinary"] = "interdisciplinary",
    ["interdisciplinary parallel"] = "interdisciplinary-parallel",
    ["interdisciplinary-parallel"] = "interdisciplinary-parallel",
    ["interdisciplinary influence"] = "interdisciplinary-influence",
    ["interdisciplinary-influence"] = "interdisciplinary-influence",
    ["translation"] = "translation",
    ["school translation"] = "school-translation",
    ["school-translation"] = "school-translation",
    ["lacanian translation"] = "lacanian-translation",
    ["lacanian-translation"] = "lacanian-translation",
    ["theoretical evolution"] = "theoretical-evolution",
    ["theoretical-evolution"] = "theoretical-evolution"
}

-- School names and their CSS classes
local schoolClasses = {
    ["freudian"] = "freudian",
    ["lacanian"] = "lacanian",
    ["kleinian"] = "kleinian",
    ["laplanchian"] = "laplanchian",
    ["ego psychology"] = "ego-psychology",
    ["ego-psychology"] = "ego-psychology",
    ["object relations"] = "object-relations",
    ["object-relations"] = "object-relations",
    ["relational"] = "relational",
    ["contemporary"] = "contemporary",
    ["trauma theory"] = "contemporary",
    ["trauma-theory"] = "contemporary",
    ["self psychology"] = "contemporary",
    ["self-psychology"] = "contemporary",
    ["intersubjective"] = "contemporary",
    ["neuropsychoanalytic"] = "contemporary"
}

--------------------------------------------------------------------------------
-- RENDER SCHOOL INTERPRETATIONS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.renderSchools(frame)
    local args = frame:getParent().args
    local output = {}
    local schoolData = {}
    
    -- Check if any school views exist
    local hasSchools = false
    for k, v in pairs(args) do
        if k:match("^%a+_view$") and v ~= '' then
            hasSchools = true
            break
        end
    end
    
    if not hasSchools then
        return ''
    end
    
    table.insert(output, '<div class="concept-section-header mw-collapsible' .. 
        (args.schools_collapsed == 'yes' and ' mw-collapsed' or '') .. 
        '">School Interpretations</div>')
    table.insert(output, '<div class="mw-collapsible-content">')
    
    -- Define school order
    local schools = {
        'freudian', 'lacanian', 'kleinian', 'laplanchian', 
        'ego_psychology', 'object_relations', 'relational', 
        'self_psychology', 'intersubjective', 'trauma_theory',
        'neuropsychoanalytic', 'feminist'
    }
    
    local conceptName = args.concept_name or mw.title.getCurrentTitle().text
    local order = 1
    
    for _, school in ipairs(schools) do
        local view = args[school .. '_view']
        local keyTexts = args[school .. '_key_texts']
        local keyTerms = args[school .. '_key_terms']
        
        if view and view ~= '' then
            local schoolName = school:gsub('_', ' '):gsub("(%a)([%w_']*)", 
                function(first, rest) return first:upper() .. rest end)
            local cssClass = schoolClasses[school:lower()] or 'contemporary'
            
            -- Store to Cargo
            table.insert(schoolData, {
                concept_name = conceptName,
                school_name = schoolName,
                school_view = view,
                key_texts = keyTexts or '',
                key_terms = keyTerms or '',
                view_order = order
            })
            
            -- Render school box
            table.insert(output, '<div class="school-box ' .. cssClass .. '">')
            table.insert(output, '<div class="school-header">' .. schoolName .. '</div>')
            table.insert(output, '<div class="school-content">' .. view .. '</div>')
            
            if keyTexts and keyTexts ~= '' then
                table.insert(output, '<div class="school-texts"><strong>Key texts:</strong> ' .. 
                    keyTexts .. '</div>')
            end
            
            if keyTerms and keyTerms ~= '' then
                table.insert(output, '<div class="school-terms"><strong>Related:</strong> ' .. 
                    keyTerms .. '</div>')
            end
            
            table.insert(output, '</div>')
            order = order + 1
        end
    end
    
    table.insert(output, '</div>')
    
    -- Store all school data to Cargo
    if #schoolData > 0 then
        for _, data in ipairs(schoolData) do
            cargo.store('Concept_Schools', data)
        end
    end
    
    return table.concat(output, '\n')
end

--------------------------------------------------------------------------------
-- RENDER CONTRIBUTOR BLOCKS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.contributors(frame)
    local args = frame:getParent().args
    local output = {}
    local contributorData = {}
    local conceptName = args.concept_name or mw.title.getCurrentTitle().text
    
    for i = 1, 30 do
        local name = args['contributor_' .. i .. '_name']
        local year = args['contributor_' .. i .. '_year']
        local ctype = args['contributor_' .. i .. '_type']
        local content = args['contributor_' .. i .. '_content']
        
        if name and name ~= '' then
            local typeLower = (ctype or ''):lower()
            local cssClass = contributionTypes[typeLower] or 'elaboration'
            
            -- Store to Cargo
            table.insert(contributorData, {
                concept_name = conceptName,
                contributor_name = name,
                contribution_year = year or '',
                contribution_type = ctype or '',
                contribution_content = content or '',
                contribution_order = i
            })
            
            -- Render contributor block
            table.insert(output, '<div class="contributor-block ' .. cssClass .. '">')
            
            -- Name with auto-linking
            local linkedName = name
            if not name:match('%[%[') then
                linkedName = '[[' .. name .. ']]'
            end
            table.insert(output, '<div class="contributor-name">' .. linkedName)
            
            -- Type tag
            if ctype and ctype ~= '' then
                table.insert(output, '<span class="contributor-type-tag">' .. ctype .. '</span>')
            end
            
            table.insert(output, '</div>')
            
            -- Year
            if year and year ~= '' then
                table.insert(output, '<div class="contributor-year">' .. year .. '</div>')
            end
            
            -- Content
            if content and content ~= '' then
                table.insert(output, '<div class="contributor-content">' .. content .. '</div>')
            end
            
            table.insert(output, '</div>')
        end
    end
    
    -- Store all contributor data to Cargo
    if #contributorData > 0 then
        for _, data in ipairs(contributorData) do
            cargo.store('Concept_Contributors', data)
        end
    end
    
    return table.concat(output, '\n')
end

--------------------------------------------------------------------------------
-- RENDER RELATIONSHIP BLOCKS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.relations(frame)
    local args = frame:getParent().args
    local output = {}
    local relationData = {}
    local conceptName = args.concept_name or mw.title.getCurrentTitle().text
    
    for i = 1, 50 do
        local target = args['relation_' .. i .. '_target']
        local rtype = args['relation_' .. i .. '_type']
        local content = args['relation_' .. i .. '_content']
        
        if target and target ~= '' then
            local typeLower = (rtype or ''):lower()
            local cssClass = relationTypes[typeLower] or 'foundational'
            
            -- Determine if bidirectional
            local bidirectional = false
            if typeLower:match('mutual') or typeLower:match('parallel') then
                bidirectional = true
            end
            
            -- Store to Cargo
            table.insert(relationData, {
                source_concept = conceptName,
                target_concept = target,
                relation_type = rtype or '',
                relation_content = content or '',
                relation_order = i,
                bidirectional = bidirectional
            })
            
            -- Render relation block
            table.insert(output, '<div class="relation-block ' .. cssClass .. '">')
            
            -- Target with auto-linking
            local linkedTarget = target
            if not target:match('%[%[') then
                linkedTarget = '[[' .. target .. ']]'
            end
            table.insert(output, '<div class="relation-target">' .. linkedTarget)
            
            -- Type tag
            if rtype and rtype ~= '' then
                table.insert(output, '<span class="relation-type-tag">' .. rtype .. '</span>')
            end
            
            table.insert(output, '</div>')
            
            -- Content
            if content and content ~= '' then
                table.insert(output, '<div class="relation-content">' .. content .. '</div>')
            end
            
            table.insert(output, '</div>')
        end
    end
    
    -- Store all relation data to Cargo
    if #relationData > 0 then
        for _, data in ipairs(relationData) do
            cargo.store('Concept_Relations', data)
        end
    end
    
    return table.concat(output, '\n')
end

--------------------------------------------------------------------------------
-- VALIDATION: Check for reciprocal relationships
--------------------------------------------------------------------------------
function p.checkReciprocal(sourceConcept, targetConcept, relationType)
    -- Query Cargo to see if reverse relationship exists
    local tables = 'Concept_Relations'
    local fields = 'source_concept, relation_type'
    local where = 'source_concept="' .. targetConcept .. '" AND target_concept="' .. sourceConcept .. '"'
    
    local results = cargo.query(tables, fields, {where=where})
    
    if #results > 0 then
        return true, results[1].relation_type
    else
        return false, nil
    end
end

--------------------------------------------------------------------------------
-- UTILITY: Get all concepts related to current concept
--------------------------------------------------------------------------------
function p.getRelatedConcepts(conceptName)
    local tables = 'Concept_Relations'
    local fields = 'target_concept, relation_type'
    local where = 'source_concept="' .. conceptName .. '"'
    local orderBy = 'relation_order'
    
    local results = cargo.query(tables, fields, {where=where, orderBy=orderBy})
    
    return results
end

--------------------------------------------------------------------------------
-- UTILITY: Get all contributors to a concept
--------------------------------------------------------------------------------
function p.getContributors(conceptName, contributionType)
    local tables = 'Concept_Contributors'
    local fields = 'contributor_name, contribution_year, contribution_type'
    local where = 'concept_name="' .. conceptName .. '"'
    
    if contributionType then
        where = where .. ' AND contribution_type="' .. contributionType .. '"'
    end
    
    local orderBy = 'contribution_order'
    
    local results = cargo.query(tables, fields, {where=where, orderBy=orderBy})
    
    return results
end

return p