Module:SBIR

From Defense Knowledge

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

local p = {}

-- Helper function to build an address string
local function getFullAddress(item)
    local fields = {"address1", "address2", "city", "state", "zip"}
    local addressParts = {}

    for _, key in ipairs(fields) do
        local value = item[key]
        if value and value ~= "" then
            table.insert(addressParts, value)
        end
    end

    return next(addressParts) and table.concat(addressParts, ", ") or "N/A"
end

-- Function to format large numbers into shortened notation
function formatLargeNumber(num)
    -- Check if the input is a valid number
    if not tonumber(num) then
        return tostring(num) -- Return as is if not a number
    end

    local absNumber = math.abs(num)
    local suffix = ""
    local formattedNumber = num

    if absNumber >= 1e9 then
        formattedNumber = num / 1e9
        suffix = " Billion"
    elseif absNumber >= 1e6 then
        formattedNumber = num / 1e6
        suffix = " Million"
    elseif absNumber >= 1e3 then
        formattedNumber = num / 1e3
        suffix = " Thousand"
    end

    -- Round to one decimal place
    formattedNumber = string.format("%.1f", formattedNumber)

    -- Handle edge cases for negative numbers
    return formattedNumber .. suffix
end

-- Helper function to get company details
local function getCompanyDetails(item)
    local fields = {
        {"uei", "UEI"},
        {"hubzone_owned", "HUBzone Owned"},
        {"socially_economically_disadvantaged", "SED Owned"},
        {"woman_owned", "Woman Owned"}
    }
    
    local companyDetails = {}
    
    for _, field in ipairs(fields) do
        local key, label = field[1], field[2]
        local value = item[key]
        
        if value and value ~= "" and value ~= "No" and value ~= "Unavailable" then
            table.insert(companyDetails, label .. ": " .. value)
        end
    end

    return next(companyDetails) and table.concat(companyDetails, "\n") or "N/A"
end

-- Helper function to get company details
local function getAwardDetails(item)

    local html = mw.html.create()
    local card = html:tag('div'):addClass('card rounded-0')

    local badges = {
        {"agency", "Agency"},
        {"branch", "Branch"},
        {"program", "Program"},
        {"phase", "Phase"},
        {"topic_code", "Topic"}
    }

    local details = {
        {"number_employees", "Employees"},
        {"proposal_award_date", "Award Date"},
        {"contract", "Contract"},
        {"contract_end_date", "Contract Ends"},
    }

    local solicitation = {
        {"solicitation_number", "Solicitation"},
        {"solicitation_year", "Solicitation Year"},
    }

    local keywords = {
        {"research_area_keywords", "Keywords"}
    }

    card:tag('h4')
        :addClass('card-header')
        :wikitext(item['award_title'] .. ' - ' .. item['award_year'])
    
    local tagline = card:tag('div')
        :addClass('card-tagline p-3')

    for _, field in ipairs(badges) do
        local key, label = field[1], field[2]
        local value = item[key]

        if value and value ~= "" and value ~= "No" and value ~= "Unavailable" then
             tagline:tag('span')
                 :addClass('badge badge-secondary')
                 :attr('title', label)
                 :wikitext(value)
        end
    end
    
    card:tag('div')
        :addClass('h1 px-3 py-2')
        :wikitext(formatLargeNumber(item['award_amount']))

    for _, field in ipairs(keywords) do
        local key, label = field[1], field[2]
        local value = item[key]

        if value and value ~= "" and value ~= "No" and value ~= "Unavailable" then
             tagline:tag('span')
                 :addClass('badge badge-secondary')
                 :attr('title', label)
                 :wikitext(value)
        end
    end

    for _, field in ipairs(details) do
        local key, label = field[1], field[2]
        local value = item[key]

        if value and value ~= "" and value ~= "No" and value ~= "Unavailable" then
            card:tag('div')
                :addClass('card-body py-1')
                :wikitext(label .. ": " .. value)
        end
    end

    card:tag('div')
        :addClass('card-body')
        :wikitext(item['abstract'])

    card:tag('div')
        :addClass('card-body')
        :wikitext('Solicitation: ' .. item['solicitation_number'] .. '(' .. item['solicitation_year'] .. ')' )

    local out = tostring(html)

    return out

end

function p.check(frame)

    local page = mw.title.getCurrentTitle().text

    local results = mw.ext.externalData.getWebData {
        url = 'https://api.www.sbir.gov/public/api/firm?name=' .. page,
        format = 'JSON',
        ['cache seconds'] = 86400
    }

    local html = mw.html.create()
    local wrapper = html:tag('div')
        :addClass('d-flex flex-wrap gap-3 mb-3')
    local card = ''

    if type(results) == "table" or #results > 0 then

    -- Add data rows
    for _, item in ipairs(results) do
       local awards = item.number_awards or 0
       out = string.format(
            '%d %s',
            awards, frame:preprocess('{{plural: ' .. awards .. '|award|awards}}')
       )
    end

    card = wrapper:tag('div')
        :addClass('card rounded-0')
    card:tag('h4')
        :addClass('card-header')
        :wikitext('SBIR')
    card:tag('div')
        :addClass('card-body')
        :wikitext(out)

    card = wrapper:tag('div')
        :addClass('card rounded-0')
    card:tag('h4')
        :addClass('card-header')
        :wikitext('Address')
    card:tag('div')
        :addClass('card-body')
        :wikitext(getFullAddress(results))

    card = wrapper:tag('div')
        :addClass('card rounded-0')
    card:tag('h4')
        :addClass('card-header')
        :wikitext('Details')
    card:tag('div')
        :addClass('card-body')
        :wikitext(getCompanyDetails(results))

    local awards = mw.ext.externalData.getWebData {
        url = 'https://api.www.sbir.gov/public/api/awards?firm=' .. page,
        format = 'JSON',
        ['cache seconds'] = 86400
    }

    if type(awards) == "table" or #awards > 0 then

    for _, award in ipairs(awards) do
        html:tag('div')
            :addClass('mb-3')
            :wikitext(getAwardDetails(award))
    end

    end

    end

    return tostring(html) .. '__NOTOC__'

end

function p.byKeyword(frame)

    local agency = frame.args[1]
    local apiUrl = 'https://api.www.sbir.gov/public/api/awards?agency=' .. agency

    local awards = mw.ext.externalData.getWebData {
        url = apiUrl,
        format = 'JSON',
        data = {
             json = '__json'
        },
        ['cache seconds'] = 86400
    }

    local html = mw.html.create()
    local awardsTable = html:tag('table')
        :attr('id', 'sbirtable')

    local row = ''

    for _, award in ipairs(awards.json) do

        local link = 'https://www.sbir.gov/awards/' .. award['award_link']
        local title = string.format('[' .. link .. ' %s]'
             , award['award_title'] or ''
        )
        local firm  = string.format('[%s %s] (%s)'
             , award['company_url'] or 'https://www.sbir.gov/portfolio'
             , award['firm'] or ''
             , award['number_employees'] or ''
        )
        local amount = '$' .. formatLargeNumber(award['award_amount'])

        row = awardsTable:tag('tr')

        row:tag('td')
            :wikitext(award['contract'])
        row:tag('td')
            :wikitext(title)
        row:tag('td')
            :wikitext(amount)
        row:tag('td')
            :wikitext(firm)
        row:tag('td')
            :wikitext(award['program'])
        row:tag('td')
            :wikitext(award['phase'])
        row:tag('td')
            :wikitext(award['topic_code'])
        row:tag('td')
            :wikitext(award['proposal_award_date'])
        row:tag('td')  
            :wikitext(award['contract_end_date'])
        row:tag('td')
            :wikitext(award['agency'])
        row:tag('td')
            :wikitext(award['branch'])
        row:tag('td')
            :wikitext(award['research_area_keywords'])

    end

    return html
end

-- Helper
local function extractUniqueValues(data)
    local uniqueItems = {}  -- Table to store unique items
    local seenItems = {}    -- Table to track already seen items

    -- Iterate through each table in the input data
    for _, tableEntry in ipairs(data) do
        local listString = tableEntry.list
        if listString then
            -- Split the comma-separated string into individual items
            for item in mw.text.gsplit(listString, ",%s*") do
                -- Trim whitespace and check if the item is already seen
                local trimmedItem = mw.text.trim(item)
                if trimmedItem ~= "" and not seenItems[trimmedItem] then
                    table.insert(uniqueItems, '<div>' .. trimmedItem .. '</div>')
                    seenItems[trimmedItem] = true
                end
            end
        end
    end
    table.sort(uniqueItems)
    return uniqueItems
end

function p.getKeywords(frame)

    local agency = frame.args[1]
    local apiUrl = 'https://api.www.sbir.gov/public/api/awards?agency=' .. agency

    local keywords = mw.ext.externalData.getWebData {
        url = apiUrl,
        format = 'JSON',
        data = {
            list = 'research_area_keywords'
        },
        ['cache seconds'] = 86400
    }

    local list = extractUniqueValues(keywords) 
    
    return table.concat(list, "\n")
end

function p.getFromMongo(frame)

    local keyword = frame.args[1]
    -- local query = '{ "Abstract": { "$regex": "' .. keyword .. '" } }'
    local query = '{ "Agency": "' .. keyword .. '" }'
    local keywords = mw.ext.externalData.getExternalData {
        source = 'samdb',
        from = 'sbir',
        limit = 1000,
        ['find query'] = query,
        format = 'JSON',
        data = {
             title = 'Award Title',
             amount = 'Award Amount',
             agency = 'Agency',
             branch = 'Branch',
             topic = 'Topic Code',
             phase = 'Phase',
             program = 'Program',
             company = 'Company',
             website = 'Company Website',
             award_date = 'Proposal Award Date',
             contract = 'Contract',
             contract_end = 'Contract End Date',
             abstract = 'Abstract'
        },
        ['cache seconds'] = 86400
    }

    local html = mw.html.create()
    local awardsTable = html:tag('table')
        :attr('id', 'sbirtable')

    local row = ''

    for _, award in ipairs(keywords) do

        local amount = '$' .. formatLargeNumber(award['amount'])
        local firm = string.format('[%s %s]'
            , award['website']
            , award['company']
        )
 
        row = awardsTable:tag('tr')

        row:tag('td')
            :wikitext(award['contract'])
        row:tag('td')
            :wikitext(award['title'])
        row:tag('td')
            :wikitext(amount)
        row:tag('td')
            :wikitext(firm)
        row:tag('td')
            :wikitext(award['program'])
        row:tag('td')
            :wikitext(award['phase'])
        row:tag('td')
            :wikitext(award['topic'])
        row:tag('td')
            :wikitext(award['award_date'])
        row:tag('td')
            :wikitext(award['contract_end'])
        row:tag('td')
            :wikitext(award['agency'])
        row:tag('td')
            :wikitext(award['branch'])
        row:tag('td')
            :wikitext(award['abstract'])

    end

    return html
end

return p