Module:MedicoInfobox

From determinar.ia.br - Determine suas informações
Revision as of 16:10, 3 August 2026 by Determinaradmin (talk | contribs)

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

-- Module:MedicoInfobox — lê a entidade Wikibase (por ID ou sitelink da página)
-- e renderiza o infobox do médico (web + celular).
-- P-IDs conforme o esquema real de determinar.ia.br

local p = {}

-- ============ CONFIGURAÇÃO (ajuste se criar novas propriedades) ============
local CFG = {
    foto          = 'P1',   -- localMedia -> nome do arquivo
    nome          = 'P2',   -- monolingualtext
    registro      = 'P4',   -- monolingualtext (CRM / RQE)
    especialidade = 'P10',  -- wikibase-item
    subesp        = 'P11',  -- wikibase-item
    servicos      = { 'P13', 'P14', 'P18', 'P19', 'P23' }, -- wikibase-item
    logradouro    = 'P28',  -- monolingualtext
    bairro        = 'P29',  -- monolingualtext
    email         = 'P30',  -- string
    website       = 'P31',  -- url
    agendamento   = 'P32',  -- url (wa.link / agendamento)
    lat           = 'P33',  -- string
    lon           = 'P34',  -- string
    convenio      = 'P35',  -- monolingualtext
    estado        = 'P36',  -- monolingualtext
    operadora     = 'P37',  -- string
    opinioes      = 'P38',  -- quantity
    estrelas      = 'P39',  -- quantity (0–5)
    duvidas       = 'P40',  -- quantity
    doctoralia    = 'P57',  -- url
    municipio     = 'P71',  -- wikibase-item (Q102 = Campinas)
    cep           = 'P72',  -- string
    bio           = 'P73',  -- string (dentro do escopo)
    escopo_neg    = 'P74',  -- string (fora do escopo / desambiguação)
    maps_cid      = 'P75',  -- external-id (Google Maps CID)
    horario       = 'P76',  -- string
    publicacoes   = 'P77',  -- url (múltiplos)
    scholar_user  = 'P62',  -- external-id (Google Scholar)
    fonte         = 'P79',  -- qualificador: wikibase-item
    coleta        = 'P80',  -- qualificador: time
}
-- =====================================================================

local function esc( s )
    if not s then return '' end
    return mw.text.encode( tostring( s ), '<>&"' )
end

local function rotulo( ent, lang )
    if not ent then return nil end
    local seq, seen = {}, {}
    for _, l in ipairs( { 'pt-br', 'pt', 'mul', lang, 'en' } ) do
        if not seen[l] then seen[l] = true; seq[#seq + 1] = l end
    end
    for _, l in ipairs( seq ) do
        local lb = ent:getLabel( l )
        if lb then return lb end
    end
    return nil
end

local function descricao( ent, lang )
    if not ent then return '' end
    local seq, seen = {}, {}
    for _, l in ipairs( { 'pt-br', 'pt', 'mul', lang, 'en' } ) do
        if not seen[l] then seen[l] = true; seq[#seq + 1] = l end
    end
    for _, l in ipairs( seq ) do
        local d = ent:getDescription( l )
        if d then return d end
    end
    return ''
end

local function snakParaTexto( snak, lang )
    if not snak or snak.snaktype ~= 'value' then return nil end
    local dv = snak.dataValue
    if not dv then return nil end
    if dv.type == 'string' or dv.type == 'url' or dv.type == 'external-id' then
        return dv.value
    elseif dv.type == 'monolingualtext' then
        return dv.value.text
    elseif dv.type == 'quantity' then
        return tostring( dv.value.amount ):gsub( '^%+', '' )
    elseif dv.type == 'time' then
        return ( dv.value.time or '' ):match( '(%d+%-%d+%-%d+)' )
    elseif dv.type == 'wikibase-entityid' then
        local alvo = mw.wikibase.getEntity( dv.value.id )
        return rotulo( alvo, lang ) or dv.value.id
    end
    return nil
end

local function valor( ent, pid, lang )
    if not ent then return nil end
    local sts = ent:getBestStatements( pid )
    if not sts then return nil end
    for _, st in ipairs( sts ) do
        local v = snakParaTexto( st:getMainSnak(), lang )
        if v and v ~= '' then return v end
    end
    return nil
end

local function valores( ent, pid, lang )
    local out = {}
    if not ent then return out end
    local sts = ent:getBestStatements( pid )
    if not sts then return out end
    for _, st in ipairs( sts ) do
        local v = snakParaTexto( st:getMainSnak(), lang )
        if v and v ~= '' then out[#out + 1] = v end
    end
    return out
end

local function qualificador( ent, pid, qpid, lang )
    if not ent then return nil end
    local sts = ent:getBestStatements( pid )
    if not sts or not sts[1] then return nil end
    local qs = sts[1]:getQualifiers()
    if not qs then return nil end
    for _, q in ipairs( qs ) do
        if q:getPropertyId() == qpid then
            return snakParaTexto( q, lang )
        end
    end
    return nil
end

local function chips( lista )
    local s = ''
    for _, v in ipairs( lista ) do
        s = s .. '<span class="chip-especialidade">' .. esc( v ) .. '</span> '
    end
    return s
end

local function estrelas( n )
    n = math.floor( tonumber( n ) or 0 )
    n = math.max( 0, math.min( 5, n ) )
    return string.rep( '★', n ) .. string.rep( '☆', 5 - n )
end

function p.main( frame )
    local args = frame.args
    local id = ( args.ID or args[1] or '' ):match( '^%s*(.-)%s*$' )
    local lang = mw.getContentLanguage():getCode()

    local ent = mw.wikibase.getEntity( id ~= '' and id or nil )
    if not ent then
        return '<div class="infobox-medico"><div class="infobox-medico-titulo">Entidade não encontrada — informe ID=Q… ou conecte a página à entidade no Wikibase.</div></div>'
    end

    local nome       = valor( ent, CFG.nome, lang ) or rotulo( ent, lang ) or '—'
    local desc       = descricao( ent, lang )
    local foto       = valor( ent, CFG.foto, lang )
    local regs       = valores( ent, CFG.registro, lang )
    local esp        = valor( ent, CFG.especialidade, lang )
    local sub        = valor( ent, CFG.subesp, lang )
    local logradouro = valor( ent, CFG.logradouro, lang )
    local bairro     = valor( ent, CFG.bairro, lang )
    local municipio  = valor( ent, CFG.municipio, lang )
    local estado     = valor( ent, CFG.estado, lang )
    local cep        = valor( ent, CFG.cep, lang )
    local horario    = valor( ent, CFG.horario, lang )
    local convenio   = valor( ent, CFG.convenio, lang )
    local operadora  = valor( ent, CFG.operadora, lang )
    local email      = valor( ent, CFG.email, lang )
    local website    = valor( ent, CFG.website, lang )
    local agenda     = valor( ent, CFG.agendamento, lang )
    local doc        = valor( ent, CFG.doctoralia, lang )
    local estrelasN  = valor( ent, CFG.estrelas, lang )
    local opinioes   = valor( ent, CFG.opinioes, lang )
    local bio        = valor( ent, CFG.bio, lang )
    local cid        = valor( ent, CFG.maps_cid, lang )
    local pubs       = valores( ent, CFG.publicacoes, lang )
    local scholar    = valor( ent, CFG.scholar_user, lang )
    local escopoNeg  = valores( ent, CFG.escopo_neg, lang )
    local fonteTxt   = qualificador( ent, CFG.registro, CFG.fonte, lang ) or qualificador( ent, CFG.bio, CFG.fonte, lang )
    local coletaTxt  = qualificador( ent, CFG.registro, CFG.coleta, lang ) or qualificador( ent, CFG.bio, CFG.coleta, lang )

    local servicos = {}
    for _, spid in ipairs( CFG.servicos ) do
        for _, v in ipairs( valores( ent, spid, lang ) ) do
            servicos[#servicos + 1] = v
        end
    end

    local out = {}
    out[#out + 1] = '<div class="infobox-medico">'

    -- Foto
    if foto then
        out[#out + 1] = '<div class="infobox-medico-imagem">' .. frame:preprocess( '[[File:' .. foto .. '|300px|center]]' ) .. '</div>'
    end

    -- Título + descrição
    out[#out + 1] = '<div class="infobox-medico-titulo">' .. esc( nome ) .. '</div>'
    if desc and desc ~= '' then
        out[#out + 1] = '<div class="infobox-medico-descricao">' .. esc( desc ) .. '</div>'
    end

    -- Chips de especialidade
    local espChips = {}
    if esp then espChips[#espChips + 1] = esp end
    if sub then espChips[#espChips + 1] = sub end
    if #espChips > 0 then
        out[#out + 1] = '<div class="infobox-medico-chips">' .. chips( espChips ) .. '</div>'
    end

    -- Avaliação Doctoralia
    if estrelasN then
        local nota = '<div class="medico-avaliacao"><span class="medico-estrelas">' .. estrelas( estrelasN ) .. '</span>'
            .. ' <span class="medico-nota">' .. esc( estrelasN ) .. '/5</span>'
        if opinioes then
            nota = nota .. ' · <a href="' .. esc( doc or '#' ) .. '" target="_blank" rel="noopener nofollow">' .. esc( opinioes ) .. ' opiniões</a>'
        end
        out[#out + 1] = nota .. '</div>'
    end

    -- Tabela de dados
    out[#out + 1] = '<table class="infobox-medico-tabela">'
    local function linha( r, v )
        if v and v ~= '' then
            return '<tr><th>' .. esc( r ) .. '</th><td>' .. v .. '</td></tr>'
        end
        return ''
    end

    if #regs > 0 then
        local regHtml = {}
        for _, r in ipairs( regs ) do regHtml[#regHtml + 1] = esc( r ) end
        out[#out + 1] = linha( 'Registro', table.concat( regHtml, '<br>' ) )
    end

    local partesEnd = {}
    for _, v in ipairs( { logradouro, bairro, municipio, estado, cep } ) do
        if v and v ~= '' then partesEnd[#partesEnd + 1] = v end
    end
    if #partesEnd > 0 then
        out[#out + 1] = linha( 'Endereço', esc( table.concat( partesEnd, ', ' ) ) )
    end

    if horario   then out[#out + 1] = linha( 'Horário', esc( horario ) ) end
    if convenio  then out[#out + 1] = linha( 'Convênio', esc( convenio ) ) end
    if operadora then out[#out + 1] = linha( 'Operadora', esc( operadora ) ) end
    if email     then out[#out + 1] = linha( 'E-mail', '<a href="mailto:' .. esc( email ) .. '">' .. esc( email ) .. '</a>' ) end
    if website then
        local href = website:match( '^https?://' ) and website or ( 'https://' .. website )
        out[#out + 1] = linha( 'Site', '<a href="' .. esc( href ) .. '" target="_blank" rel="noopener nofollow">' .. esc( website ) .. '</a>' )
    end
    if doc then
        out[#out + 1] = linha( 'Doctoralia', '<a href="' .. esc( doc ) .. '" target="_blank" rel="noopener nofollow">Ver perfil</a>' )
    end
    out[#out + 1] = '</table>'

    -- Bio (dentro do escopo)
    if bio and bio ~= '' then
        out[#out + 1] = '<div class="infobox-medico-bio">' .. esc( bio ) .. '</div>'
    end

    -- Serviços / procedimentos
    if #servicos > 0 then
        out[#out + 1] = '<div class="infobox-medico-secao">Procedimentos e serviços</div>'
        out[#out + 1] = '<div class="infobox-medico-chips">' .. chips( servicos ) .. '</div>'
    end

    -- CTA agendamento (WhatsApp)
    if agenda then
        out[#out + 1] = '<div class="cta-agendar"><a href="' .. esc( agenda ) .. '" target="_blank" rel="noopener nofollow">Agendar consulta</a></div>'
    end

    -- Mapa (Google Maps via CID, usando sua tag <perfilexterno>)
    if cid and cid ~= '' then
        local mapUrl = 'https://maps.google.com/maps?q=&cid=' .. cid .. '&output=embed&hl=pt-BR'
        out[#out + 1] = frame:extensionTag( 'perfilexterno', '', { url = mapUrl, largura = '100%', altura = '300' } )
    end

    -- Publicações científicas
    if #pubs > 0 then
        out[#out + 1] = '<div class="infobox-medico-secao">Publicações científicas</div>'
        out[#out + 1] = '<ul class="infobox-medico-pubs">'
        local n = math.min( 5, #pubs )
        for i = 1, n do
            local u = pubs[i]
            out[#out + 1] = '<li><a href="' .. esc( u ) .. '" target="_blank" rel="noopener nofollow">' .. esc( u:gsub( '^https?://', '' ) ) .. '</a></li>'
        end
        out[#out + 1] = '</ul>'
        if #pubs > n then
            local alvo = scholar and ( 'https://scholar.google.com/citations?user=' .. scholar .. '&hl=pt-BR' ) or '#'
            out[#out + 1] = '<div class="infobox-medico-mais"><a href="' .. esc( alvo ) .. '" target="_blank" rel="noopener nofollow">+ ' .. ( #pubs - n ) .. ' publicações</a></div>'
        end
    end

    -- Desambiguação (fora do escopo)
    if #escopoNeg > 0 then
        out[#out + 1] = '<details class="medico-detalhes"><summary>Desambiguação — o que este perfil <b>não</b> é</summary><ul>'
        for _, v in ipairs( escopoNeg ) do
            out[#out + 1] = '<li>' .. esc( v ) .. '</li>'
        end
        out[#out + 1] = '</ul></details>'
    end

    -- Proveniência (diferencial anti-alucinação)
    if fonteTxt or coletaTxt then
        out[#out + 1] = '<div class="infobox-medico-rodape">Dados curados · fonte: ' .. esc( fonteTxt or '—' )
        if coletaTxt then out[#out + 1] = ' · verificado em ' .. esc( coletaTxt ) end
        out[#out + 1] = '</div>'
    end

    out[#out + 1] = '</div>'
    return frame:preprocess( table.concat( out ) )
end

return p