diff --git a/src/procedures/african-american-data/african-american-data.js b/src/procedures/african-american-data/african-american-data.js new file mode 100644 index 00000000..0bc8025b --- /dev/null +++ b/src/procedures/african-american-data/african-american-data.js @@ -0,0 +1,669 @@ +/** + * African American Data + * + * Explore African American people, history, media, culture, + * and community resources. + * + * @service + * @category History + */ + +const ApiConsumer = require("../utils/api-consumer"); + +const BASE_URL = + "http://flask-api-env.eba-er3e5y3h.us-east-2.elasticbeanstalk.com/api"; + +const AfricanAmericanData = new ApiConsumer( + "AfricanAmericanData", + BASE_URL, + { + cache: { ttl: 5 * 60 }, + }, +); + +function encode(value) { + return encodeURIComponent(String(value || "").trim()); +} +// PEOPLE +// Fields: +// name, achievement, occupation, category, image + +/** + * Get the names of all people. + * + * @category People + * @returns {Array} all person names + */ +AfricanAmericanData.getAllPeople = async function () { + const people = await this._requestData({ + path: "/people", + }); + return people.map((person) => person.name); +}; + +/** + * Search people by name, occupation, achievement, or category. + * + * @category People + * @param {String} nameOrFieldOrCategory search term + * @returns {Array} matching person names + */ +AfricanAmericanData.searchPeople = async function (nameOrFieldOrCategory) { + const people = await this._requestData({ + path: `/people/search?q=${encode(nameOrFieldOrCategory)}`, + }); + return people.map((person) => person.name); +}; + +/** + * Get the name of one random person. + * + * @category People + * @returns {String} the random person's name + */ +AfricanAmericanData.getRandomPerson = async function () { + const person = await this._requestData({ + path: "/people/random", + }); + return person.name || ""; +}; + +/** + * Get people from a category. + * + * @category People + * @param {String} category category name + * @returns {Array} matching person records + */ +AfricanAmericanData.getPeopleByCategory = async function (category) { + return this._requestData({ + path: `/people/category/${encode(category)}`, + }); +}; + +/** + * Get all available people categories. + * + * @category People + * @returns {Array} category names + */ +AfricanAmericanData.getPeopleCategories = async function () { + return this._requestData({ + path: "/people/categories", + }); +}; + +async function lookupPerson(self, name) { + return self._requestData({ + path: `/people/lookup/${encode(name)}`, + }); +} + +/** + * Get a person's achievement or trivia clue. + * + * @category People + * @param {String} name the person's name + * @returns {String} the person's achievement + */ +AfricanAmericanData.getPersonAchievement = async function (name) { + const person = await lookupPerson(this, name); + return person.achievement || ""; +}; + +/** + * Get a person's occupation. + * + * @category People + * @param {String} name the person's name + * @returns {String} the person's occupation + */ +AfricanAmericanData.getPersonOccupation = async function (name) { + const person = await lookupPerson(this, name); + return person.occupation || ""; +}; + +/** + * Get a person's category. + * + * @category People + * @param {String} name the person's name + * @returns {String} the person's category + */ +AfricanAmericanData.getPersonCategory = async function (name) { + const person = await lookupPerson(this, name); + return person.category || ""; +}; + +/** + * Get a person's image. + * + * @category People + * @param {String} name the person's name + * @returns {Image} the person's image + */ +AfricanAmericanData.getPersonImage = async function (name) { + const person = await lookupPerson(this, name); + return this._sendImage({ + baseUrl: person.image, + }); +}; + +// HISTORY +// title, summary, year, location, category, image + +/** + * Get the titles of all historical events. + * + * @category History + * @returns {Array} all event titles + */ +AfricanAmericanData.getAllHistoryEvents = async function () { + const events = await this._requestData({ + path: "/history", + }); + return events.map((event) => event.title); +}; + +/** + * Search historical events by title, summary, location, + * year, or category. + * + * @category History + * @param {String} titleOrCategory search term + * @returns {Array} matching event titles + */ +AfricanAmericanData.searchHistory = async function (titleOrCategory) { + const events = await this._requestData({ + path: `/history/search?q=${encode(titleOrCategory)}`, + }); + return events.map((event) => event.title); +}; + +/** + * Get the title of one random historical event. + * + * @category History + * @returns {String} the random event's title + */ +AfricanAmericanData.getRandomHistoryEvent = async function () { + const event = await this._requestData({ + path: "/history/random", + }); + return event.title || ""; +}; + +/** + * Get historical event titles from a category. + * + * @category History + * @param {String} category history category + * @returns {Array} matching event titles + */ +AfricanAmericanData.getHistoryByCategory = async function (category) { + const events = await this._requestData({ + path: `/history/category/${encode(category)}`, + }); + return events.map((event) => event.title); +}; + +/** + * Get all available history categories. + * + * @category History + * @returns {Array} category names + */ +AfricanAmericanData.getHistoryCategories = async function () { + return this._requestData({ + path: "/history/categories", + }); +}; + +async function lookupHistoryEvent(self, title) { + return self._requestData({ + path: `/history/lookup/${encode(title)}`, + }); +} + +/** + * Get the summary of a historical event. + * + * @category History + * @param {String} title the event's title + * @returns {String} the event summary + */ +AfricanAmericanData.getEventSummary = async function (title) { + const event = await lookupHistoryEvent(this, title); + return event.summary || ""; +}; + +/** + * Get the year of a historical event. + * + * @category History + * @param {String} title the event's title + * @returns {Number} the event year + */ +AfricanAmericanData.getEventYear = async function (title) { + const event = await lookupHistoryEvent(this, title); + return event.year; +}; + +/** + * Get the location of a historical event. + * + * @category History + * @param {String} title the event's title + * @returns {String} the event location + */ +AfricanAmericanData.getEventLocation = async function (title) { + const event = await lookupHistoryEvent(this, title); + return event.location || ""; +}; + +/** + * Get the category of a historical event. + * + * @category History + * @param {String} title the event's title + * @returns {String} the event category + */ +AfricanAmericanData.getEventCategory = async function (title) { + const event = await lookupHistoryEvent(this, title); + return event.category || ""; +}; + +/** + * Get an image for a historical event. + * + * @category History + * @param {String} title the event's title + * @returns {Image} the event image + */ +AfricanAmericanData.getHistoryImage = async function (title) { + const event = await lookupHistoryEvent(this, title); + return this._sendImage({ + baseUrl: event.image, + }); +}; + +// MEDIA +// name, summary, role, image + +/** + * Get the names of all media people. + * + * @category Media + * @returns {Array} all media person names + */ +AfricanAmericanData.getAllMediaPeople = async function () { + const people = await this._requestData({ + path: "/media", + }); + return people.map((person) => person.name); +}; + +/** + * Search media people by name. + * + * @category Media + * @param {String} personName name to search for + * @returns {Array} matching media person names + */ +AfricanAmericanData.searchMediaByName = async function (personName) { + const people = await this._requestData({ + path: `/media/search?q=${encode(personName)}`, + }); + return people.map((person) => person.name); +}; + +/** + * Get the name of one random media person. + * + * @category Media + * @returns {String} the random media person's name + */ +AfricanAmericanData.getRandomMediaPerson = async function () { + const person = await this._requestData({ + path: "/media/random", + }); + return person.name || ""; +}; + +async function lookupMediaPerson(self, name) { + return self._requestData({ + path: `/media/lookup/${encode(name)}`, + }); +} + +/** + * Get a media person's summary. + * + * @category Media + * @param {String} name the media person's name + * @returns {String} the person's summary + */ +AfricanAmericanData.getMediaSummary = async function (name) { + const media = await lookupMediaPerson(this, name); + return media.summary || ""; +}; + +/** + * Get a media person's role. + * + * @category Media + * @param {String} name the media person's name + * @returns {String} the person's role + */ +AfricanAmericanData.getMediaRole = async function (name) { + const media = await lookupMediaPerson(this, name); + return media.role || ""; +}; + +/** + * Get a media person's image. + * + * @category Media + * @param {String} name the media person's name + * @returns {Image} the person's image + */ +AfricanAmericanData.getMediaImage = async function (name) { + const media = await lookupMediaPerson(this, name); + return this._sendImage({ + baseUrl: media.image, + }); +}; + +// CULTURE +// title, category, description, image +/** + * Get the titles of all culture items. + * + * @category Culture + * @returns {Array} all culture item titles + */ +AfricanAmericanData.getAllCultureItems = async function () { + const items = await this._requestData({ + path: "/culture", + }); + return items.map((item) => item.title); +}; + +/** + * Search culture items by title, description, or category. + * + * @category Culture + * @param {String} titleOrDescription search term + * @returns {Array} matching culture item titles + */ +AfricanAmericanData.searchCulture = async function (titleOrDescription) { + const items = await this._requestData({ + path: `/culture/search?q=${encode(titleOrDescription)}`, + }); + return items.map((item) => item.title); +}; + +/** + * Get the title of one random culture item, optionally filtered + * by category. + * + * @category Culture + * @param {String=} category optional culture category to filter by + * @returns {String} the random item's title + */ +AfricanAmericanData.getRandomCultureItem = async function (category) { + const path = category + ? `/culture/random?category=${encode(category)}` + : "/culture/random"; + const item = await this._requestData({ path }); + return item.title || ""; +}; + +/** + * Get culture item titles from a category. + * + * @category Culture + * @param {String} category culture category + * @returns {Array} matching culture item titles + */ +AfricanAmericanData.getCultureByCategory = async function (category) { + const items = await this._requestData({ + path: `/culture/category/${encode(category)}`, + }); + return items.map((item) => item.title); +}; + +/** + * Get all available culture categories. + * + * @category Culture + * @returns {Array} category names + */ +AfricanAmericanData.getCultureCategories = async function () { + return this._requestData({ + path: "/culture/categories", + }); +}; + +async function lookupCultureItem(self, title) { + return self._requestData({ + path: `/culture/lookup/${encode(title)}`, + }); +} + +/** + * Get the category of a culture item. + * + * @category Culture + * @param {String} title the culture item's title + * @returns {String} the item category + */ +AfricanAmericanData.getCultureCategory = async function (title) { + const item = await lookupCultureItem(this, title); + return item.category || ""; +}; + +/** + * Get the description of a culture item. + * + * @category Culture + * @param {String} title the culture item's title + * @returns {String} the item description + */ +AfricanAmericanData.getCultureDescription = async function (title) { + const item = await lookupCultureItem(this, title); + return item.description || ""; +}; + +/** + * Get the image for a culture item. + * + * @category Culture + * @param {String} title the culture item's title + * @returns {Image} the item image + */ +AfricanAmericanData.getCultureImage = async function (title) { + const item = await lookupCultureItem(this, title); + return this._sendImage({ + baseUrl: item.image, + }); +}; + +// COMMUNITY +// Fields: +// name, description, type, city, state, website, image + +/** + * Get the names of all community resources. + * + * @category Community + * @returns {Array} all community resource names + */ +AfricanAmericanData.getAllCommunityResources = async function () { + const resources = await this._requestData({ + path: "/community", + }); + return resources.map((resource) => resource.name); +}; + +/** + * Search community resources by name, type, city, + * state, or description. + * + * @category Community + * @param {String} nameOrDescription search term + * @returns {Array} matching community resource names + */ +AfricanAmericanData.searchCommunity = async function (nameOrDescription) { + const resources = await this._requestData({ + path: `/community/search?q=${encode(nameOrDescription)}`, + }); + return resources.map((resource) => resource.name); +}; + +/** + * Get the name of one random community resource. + * + * @category Community + * @returns {String} the random resource's name + */ +AfricanAmericanData.getRandomCommunityResource = async function () { + const resource = await this._requestData({ + path: "/community/random", + }); + return resource.name || ""; +}; + +/** + * Get community resource names by type. + * + * @category Community + * @param {String} type community resource type + * @returns {Array} matching community resource names + */ +AfricanAmericanData.getCommunityByType = async function (type) { + const resources = await this._requestData({ + path: `/community/type/${encode(type)}`, + }); + return resources.map((resource) => resource.name); +}; + +/** + * Get community resource names by state. + * + * @category Community + * @param {String} state state name + * @returns {Array} matching community resource names + */ +AfricanAmericanData.getCommunityByState = async function (state) { + const resources = await this._requestData({ + path: `/community/state/${encode(state)}`, + }); + return resources.map((resource) => resource.name); +}; + +/** + * Get all available community resource types. + * + * @category Community + * @returns {Array} community resource types + */ +AfricanAmericanData.getCommunityTypes = async function () { + return this._requestData({ + path: "/community/types", + }); +}; + +/** + * Get all represented states. + * + * @category Community + * @returns {Array} state names + */ +AfricanAmericanData.getCommunityStates = async function () { + return this._requestData({ + path: "/community/states", + }); +}; + +async function lookupCommunityResource(self, name) { + return self._requestData({ + path: `/community/lookup/${encode(name)}`, + }); +} + +/** + * Get the description of a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {String} the resource description + */ +AfricanAmericanData.getCommunityDescription = async function (name) { + const resource = await lookupCommunityResource(this, name); + return resource.description || ""; +}; + +/** + * Get the type of a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {String} the resource type + */ +AfricanAmericanData.getCommunityType = async function (name) { + const resource = await lookupCommunityResource(this, name); + return resource.type || ""; +}; + +/** + * Get the city of a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {String} the resource city + */ +AfricanAmericanData.getCommunityCity = async function (name) { + const resource = await lookupCommunityResource(this, name); + return resource.city || ""; +}; + +/** + * Get the state of a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {String} the resource state + */ +AfricanAmericanData.getCommunityState = async function (name) { + const resource = await lookupCommunityResource(this, name); + return resource.state || ""; +}; + +/** + * Get the website of a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {String} the resource website + */ +AfricanAmericanData.getCommunityWebsite = async function (name) { + const resource = await lookupCommunityResource(this, name); + return resource.website || ""; +}; + +/** + * Get the image for a community resource. + * + * @category Community + * @param {String} name the resource's name + * @returns {Image} the resource image + */ +AfricanAmericanData.getCommunityImage = async function (name) { + const resource = await lookupCommunityResource(this, name); + return this._sendImage({ + baseUrl: resource.image, + }); +}; + +module.exports = AfricanAmericanData; diff --git a/src/procedures/african-american-data/data/black_women_stem.csv b/src/procedures/african-american-data/data/black_women_stem.csv new file mode 100644 index 00000000..8650430a --- /dev/null +++ b/src/procedures/african-american-data/data/black_women_stem.csv @@ -0,0 +1,25 @@ +name,field,dates,notes +Kandis Leslie Abdul-Aziz,chemical engineer and environmental engineer,,Developer of technologies that turn agricultural waste into a filtration system for water +Rediet Abebe,computer scientist,1991–,First female computer scientist to be appointed to the Harvard Society of Fellows +Lilia Ann Abron,chemical engineering and environmental engineering,1945–,First African-American woman to earn a PhD in chemical engineering +Stephanie G. Adams,engineer and academic administrator,,Dean of the Erik Jonsson School of Engineering and Computer Science at the University of Texas at Dallas since 2019 +Lucile Adams-Campbell,epidemiology,1953–,First African-American woman to receive a PhD in epidemiology in the United States. Serves as the Professor of Oncology at Lombardi Comprehensive Cancer Center and associate director for Minority Health at the Georgetown University Medical Center. +Javaune Adams-Gaston,psychologist and academic administrator,,President of Norfolk State University since 2019. +Paris Adkins-Jackson,epidemiology,,Assistant Professor of Epidemiology and Sociomedical Sciences in the Mailman School of Public Health at Columbia University in New York. +Modupe Akinola,organizational scholar and social psychologist,1974–,"Researches the science of stress, creativity, and how to maximize human potential in diverse organizations." +Jacqueline Akinpelu,"applied mathematician, operations researcher",1953–,"Research manager at the Applied Physics Laboratory of Johns Hopkins University, and developed a pipeline for students from Morgan State University to mentor them into careers in STEM fields." +Delores P. Aldridge,sociologist,1941–,First African-American woman faculty member of Emory University and founding director of the first African-American and African-Studies degree-granting program in the South. +Claudia Alexander,"geophysics, planetary science",1959–2015,Project manager for NASA's Galileo mission and Rosetta mission +Beverly Anderson,mathematician,1943–,"Emeritus professor at the University of the District of Columbia, and in the 1990s, worked at the National Academy of Sciences as Director of Minority Programs for the Mathematical Sciences Education Board" +Cheryl Anderson,epidemiologist,,Professor at and founding Dean of the University of California San Diego Herbert Wertheim School of Public Health and Human Longevity Science +Giovonnae Anderson,electrical engineering,,"First African-American women to earn a PhD in electrical engineering at the University of California, Davis (1979): Giovonnae Anderson" +Gloria Long Anderson,chemistry,1938–,"Pioneer of nuclear magnetic resonance spectroscopy, known for work with fluorine-19 and solid rocket propellants" +Ayana Holloway Arce,physicist and professor,,"Associate professor of Duke University who works on particle physics, using data from the Large Hadron Collider to understand phenomena beyond the Standard Model" +Treena Livingston Arinzeh,biomedical engineering,1970–,Researcher of adult stem-cell therapy +Ludmilla Aristilde,engineer,,Professor at Northwestern University whose research considers environmental biochemistry and bioengineering +Elayne Arrington,mathematician and engineer,1940–,First African-American woman to graduate with a bachelor's degree from the School of Engineering at the University of Pittsburgh +Valerie Ashby,chemist,,"Chemist and university professor currently serving as president of the University of Maryland, Baltimore County" +Estella Atekwana,Biogeophysics; tectonphysics,1961–,"In August 2021 began tenure as dean of the largest college of the University of California, Davis: UC Davis College of Letters and Science" +Balanda Atis,cosmetic science,,Cosmetic chemist at L'Oréal USA who expanded range of cosmetics available for people of color +Donna Auguste,"businesswoman, computer scientist",1958–,Senior engineering manager for the Newton personal digital assistant (PDA) +Wanda Austin,aerospace engineering,1954–,Former president and CEO of The Aerospace Corporation diff --git a/src/procedures/african-american-data/data/index.js b/src/procedures/african-american-data/data/index.js new file mode 100644 index 00000000..abf4e457 --- /dev/null +++ b/src/procedures/african-american-data/data/index.js @@ -0,0 +1,101 @@ +// const fs = require("fs").promises; +// const path = require("path"); +// const axios = require("axios"); + +// const logger = require("../../utils/logger")("mauna-loa-co2-data"); + +// const DATA_SOURCE = +// "https://gml.noaa.gov/webdata/ccgg/trends/co2/co2_mm_mlo.txt"; +// const DATA_SOURCE_LIFETIME = 1 * 24 * 60 * 60 * 1000; // 1 day + +// function restructure(content) { +// return content.split("\n") +// .map((s) => s.trim()) +// .filter((s) => s && !s.startsWith("#")) +// .map((line) => { +// const [, , date, interpolated, trend] = line.split(/\s+/).map(parseFloat); +// if ( +// date < 1800 || date > 2200 || interpolated < 250 || +// interpolated > 1500 | trend < 250 || trend > 1500 +// ) { +// throw Error( +// "CO2 data columns are not as expected - they might have changed the file organization", +// ); +// } +// return { date, interpolated, trend }; +// }); +// } +// async function loadDataFile() { +// const filename = path.join(__dirname, "co2_mm_mlo.txt"); +// return restructure(await fs.readFile(filename, "utf8")); +// } + +// let CACHED_DATA = undefined; +// let CACHE_TIME_STAMP = undefined; +// async function getData() { +// if ( +// CACHED_DATA !== undefined && +// Date.now() - CACHE_TIME_STAMP <= DATA_SOURCE_LIFETIME +// ) return CACHED_DATA; + +// let res = await loadDataFile(); // default to the data file we have, in case the up-to-date download/restructure fails + +// logger.info(`requesting data from ${DATA_SOURCE}`); +// const resp = await axios({ url: DATA_SOURCE, method: "GET" }); +// if (resp.status !== 200) { +// logger.error("download failed with status", resp.status); +// logger.error("falling back to saved file"); +// } else { +// logger.info("download complete - restructuring data"); +// try { +// res = restructure(resp.data); +// } catch (err) { +// logger.error("restructure failed:", err); +// logger.error("falling back to saved file"); +// } +// } + +// logger.info("caching result"); +// CACHED_DATA = res; +// CACHE_TIME_STAMP = Date.now(); +// return res; +// } + +// module.exports = { +// getData, +// }; + +const fs = require("fs").promises; +const path = require("path"); + +function restructure(content) { + return content.split("\n") + .map((s) => s.trim()) + .filter((s) => s) + .slice(1) // skip header row: name,field,dates,notes + .map((line) => { + // Need to handle commas INSIDE quoted fields (like "geophysics, planetary science") + const matches = line.match(/(".*?"|[^",]+)(?=\s*,|\s*$)/g); + const [name, field, dates, notes] = matches.map((s) => + s.replace(/^"|"$/g, "").trim() + ); + return { name, field, dates, notes }; + }); +} + +async function loadDataFile() { + const filename = path.join(__dirname, "black_women_stem.csv"); + return restructure(await fs.readFile(filename, "utf8")); +} + +let CACHED_DATA = undefined; + +async function getData() { + if (CACHED_DATA !== undefined) return CACHED_DATA; + CACHED_DATA = await loadDataFile(); + return CACHED_DATA; +} + +module.exports = { + getData, +};