UNPKG

@speakr/speakr-module-services

Version:
762 lines (703 loc) • 24 kB
"use strict"; const _ = require("underscore"); const moment = require("moment"); module.exports = { publicData, sortByCreated, typeaheadPublicData, reportData }; function publicData(options) { // 1. Get the campaign base object let campaign = options.campaigns; let platforms = options.platforms; let networks = options.networks; let agencies = options.agencies; let contacts = options.contacts; let brands = options.brands; let initiatives = options.initiatives; // 2. Delete the options campaign delete options.campaign; // 3. Reset the options campaign options.campaign = { id: campaign.id, id_hash: campaign.id_hash, title: campaign.title, created_at: campaign.created_at, start_date: campaign.start_date, end_date: campaign.end_date, description: campaign.description, budget: campaign.budget, impressions: campaign.impressions || 0, cpm: campaign.cpm, added_value: campaign.added_value, product_price: campaign.product_price, total_cost: campaign.total_cost, program_value: campaign.program_value, estimated_engagements: campaign.estimated_engagements || 0, inventory_types: campaign.inventory_types, other_interests: campaign.other_interests, rating: campaign.rating, categories: campaign.categories, tones: campaign.tones, ethnicities: campaign.ethnicities, age: campaign.age, gender: campaign.gender, countries: campaign.countries, states: campaign.states, cities: campaign.cities, audience_ethnicities: campaign.audience_ethnicities, audience_age: campaign.audience_age, audience_gender: campaign.audience_gender, audience_countries: campaign.audience_countries, audience_states: campaign.audience_states, audience_cities: campaign.audience_cities, audience_interests: campaign.audience_interests, account_categories: campaign.account_categories, approval_requirements: campaign.approval_requirements, hashtags: campaign.hashtags, keywords_to_avoid: campaign.keywords_to_avoid, platforms: platforms, networks: networks, agencies: agencies, contacts: contacts, brands: brands, initiatives: initiatives }; } function sortByCreated(options) { options.campaigns = _.sortBy(options.campaigns, "created_at"); return Promise.resolve(options); } function typeaheadPublicData(options) { return Promise.resolve( options.campaigns.map(campaign => { let brand_name; let agency_name; options.brands.forEach(brand => { if (brand.id === campaign.brandname_id) { brand_name = brand.name; } }); options.agencies.forEach(agency => { if (agency.id === campaign.agency_id) { agency_name = agency.name; } }); return { id_hash: campaign.id_hash, name: campaign.title, brand: brand_name, agency: agency_name, network_id_hash: campaign.network.id_hash }; }) ); } function reportData(options) { // array of columns that need null values replaced with zeros const engagementsColumns = [ "facebook_shares", "facebook_comments", "facebook_likes", "facebook_video_views", "instagram_comments", "instagram_likes", "instagram_video_views", "twitter_comment_count", "twitter_likes_count", "twitter_retweet_count", "twitter_video_views", "youtube_comments", "youtube_likes", "youtube_video_views", "youtube_shares" ]; try { const campaign = options.campaigns; const platforms = options.platforms; const networks = options.networks; const originalStats = options.tracking_stats ? options.tracking_stats .filter(stat => stat) .map(stat => (stat.dataValues ? stat.dataValues : stat)) : []; const creatives = options.creatives ? options.creatives : []; const initiatives = campaign.initiatives ? campaign.initiatives : []; const customStats = { engagements: campaign.campaign_reports_customs ? campaign.campaign_reports_customs.filter( customStat => customStat.category === "engagements" ) : [], impressions: campaign.campaign_reports_customs ? campaign.campaign_reports_customs.filter( customStat => customStat.category === "impressions" ) : [], totalEngagements: campaign.campaign_reports_customs ? campaign.campaign_reports_customs .filter(customStat => customStat.category === "engagements") .reduce((sum, next) => { return Number(sum) + Number(next.value); }, 0) : [], totalImpressions: campaign.campaign_reports_customs ? campaign.campaign_reports_customs .filter(customStat => customStat.category === "impressions") .reduce((sum, next) => { return Number(sum) + Number(next.value); }, 0) : [] }; // create arrays to push customStats into customStats.engagements.platform = []; customStats.engagements.product = []; customStats.engagements.media_type = []; customStats.engagements.initiative = []; customStats.engagements.engagement_type = []; customStats.impressions.platform = []; customStats.impressions.product = []; customStats.impressions.media_type = []; customStats.impressions.initiative = []; customStats.engagements.forEach(customStat => { switch (customStat.type) { case "platform": return customStats.engagements.platform.push(customStat); case "product": return customStats.engagements.product.push(customStat); case "media_type": return customStats.engagements.media_type.push(customStat); case "initiative": return customStats.engagements.initiative.push(customStat); case "engagement_type": return customStats.engagements.engagement_type.push(customStat); } }); customStats.impressions.forEach(customStat => { switch (customStat.type) { case "platform": return customStats.impressions.platform.push(customStat); case "product": return customStats.impressions.product.push(customStat); case "media_type": return customStats.impressions.media_type.push(customStat); case "initiative": return customStats.impressions.initiative.push(customStat); } }); const stats = originalStats.map(stat => { // convert null data to zeros Object.keys(stat).forEach(key => { if (engagementsColumns.includes(key)) { stat[key] = stat[key] !== null ? stat[key] : 0; } }); // add content types to stats creatives.forEach(creative => { let init = creative.initiative ? creative.initiative : {}; if (creative.id_hash === stat.creative_id_hash) { stat.content_type = creative.content_type ? creative.content_type : null; stat.product_type = init.product_type ? init.product_type : null; if (!stat.content_type) { if (creative.content_type === "image" || creative.image_link) stat.content_type = "image"; if (creative.content_type === "video" || creative.video_link) stat.content_type = "video"; if ( creative.content_type === "text only" || (!creative.image_link && !creative.video_link) ) stat.content_type = "text only"; } return stat; } }); return stat; }); // end stat .map() const initsByProduct = { content_dist: initiatives.filter( init => init.product.toLowerCase() === "content distribution" ), content_creation: initiatives.filter( init => init.product.toLowerCase() === "content creation" ), experiential: initiatives.filter( init => init.product.toLowerCase() === "experiential" ), brand_in_hand: initiatives.filter( init => init.product.toLowerCase() === "brand_in_hand" ) }; const initIdsByProduct = { content_dist: initsByProduct.content_dist.map(init => init.id), content_creation: initsByProduct.content_creation.map(init => init.id), experiential: initsByProduct.experiential.map(init => init.id), brand_in_hand: initsByProduct.brand_in_hand.map(init => init.id) }; const creativeIdHashesByInitProductType = { content_dist: creatives .filter(creative => initIdsByProduct.content_dist.includes(creative.initiative_id) ) .map(c => c.id_hash), content_creation: creatives .filter(creative => initIdsByProduct.content_creation.includes(creative.initiative_id) ) .map(c => c.id_hash), experiential: creatives .filter(creative => initIdsByProduct.experiential.includes(creative.initiative_id) ) .map(c => c.id_hash), brand_in_hand: creatives .filter(creative => initIdsByProduct.brand_in_hand.includes(creative.initiative_id) ) .map(c => c.id_hash) }; const statsByInitProductType = { content_dist: stats.filter(stat => creativeIdHashesByInitProductType.content_dist.includes( stat.creative_id_hash ) ), content_creation: stats.filter(stat => creativeIdHashesByInitProductType.content_creation.includes( stat.creative_id_hash ) ), experiential: stats.filter(stat => creativeIdHashesByInitProductType.experiential.includes( stat.creative_id_hash ) ), brand_in_hand: stats.filter(stat => creativeIdHashesByInitProductType.brand_in_hand.includes( stat.creative_id_hash ) ) }; const impressionsByProductType = { "Content Distribution": sumFollowers( statsByInitProductType.content_dist, "sum" ), "Content Creation": sumFollowers( statsByInitProductType.content_creation, "sum" ), Experiential: sumFollowers(statsByInitProductType.experiential, "sum") }; const engagementsByProductType = { "Content Distribution": sumEngagements( statsByInitProductType.content_dist ), "Content Creation": sumEngagements( statsByInitProductType.content_creation ), Experiential: sumEngagements(statsByInitProductType.experiential) }; let impressionsByInitiative = {}; let engagementsByInitiative = {}; initiatives.forEach(init => { impressionsByInitiative[init.name] = {}; engagementsByInitiative[init.name] = {}; }); const statsGroupedByInitId = _.groupBy(stats, stat => stat.initiative_id); let initIdToNameDictionary = {}; let totalImpressionsByInitId = {}; let totalEngagementssByInitId = {}; Object.keys(statsGroupedByInitId).forEach(key => { // create dictionary to match init_id to init_nam initIdToNameDictionary[key] = initiatives .filter(init => init.id === Number(key)) .map(init => init.name); // sum the impressions for this init (followers) totalImpressionsByInitId[key] = statsGroupedByInitId[key].length > 0 ? statsGroupedByInitId[key].reduce((sum, next) => { return Number(sum) + Number(next.followers); }, 0) : []; // sum engagements for this init totalEngagementssByInitId[key] = sumEngagementsColumns( statsGroupedByInitId[key] ); }); Object.keys(totalImpressionsByInitId).forEach(key => { const initName = initIdToNameDictionary[key]; impressionsByInitiative[initName] = totalImpressionsByInitId[key]; engagementsByInitiative[initName] = totalEngagementssByInitId[key]; }); Object.keys(impressionsByInitiative).forEach(key => { // replace empty objects with value of zero impressionsByInitiative[key] = typeof impressionsByInitiative[key] === "object" ? 0 : Number(impressionsByInitiative[key]); }); Object.keys(engagementsByInitiative).forEach(key => { // replace empty objects with value of zero engagementsByInitiative[key] = typeof engagementsByInitiative[key] === "object" ? 0 : Number(engagementsByInitiative[key]); }); const cpm = campaign.budget && campaign.impressions ? (campaign.budget / campaign.impressions * 1000).toFixed(2) : 0; const estimated_cpe = campaign.budget && campaign.estimated_engagements ? (campaign.budget / campaign.estimated_engagements).toFixed(2) : 0; const estimated_cpm = campaign.budget / campaign.impressions * 1000; const added_value = campaign.program_value && campaign.budget ? campaign.program_value - campaign.budget : 0; const impressions_delivered = sumFollowers(stats, "sum"); const engagements_delivered = sumEngagements(stats); const effective_cpm = campaign.budget / (impressions_delivered + customStats.totalImpressions) * 1000; // campaign_cost / impressions_delivered * 100 const effective_cpe = campaign.budget / (engagements_delivered + customStats.totalEngagements); // campaign_cost / engagements_delivered const engagement_rate = (engagements_delivered + customStats.totalEngagements) / (impressions_delivered + customStats.totalImpressions) * 100; const impressions_overage = (impressions_delivered + customStats.totalImpressions - campaign.impressions) / campaign.impressions * 100; const engagements_overage = (engagements_delivered + customStats.totalEngagements - campaign.estimated_engagements) / campaign.estimated_engagements * 100; const impressions_savings = (estimated_cpm - effective_cpm) / estimated_cpm * 100; const engagements_savings = (estimated_cpe - effective_cpe) / estimated_cpe * 100; const initiative_first_start = moment.min( initiatives.map(init => moment(init.start_date, "MM/DD/YYYY")) ); const initiative_last_end = moment.max( initiatives.map(init => moment(init.end_date, "MM/DD/YYYY")) ); options.campaigns = { id: campaign.id, id_hash: campaign.id_hash, title: campaign.title, created_at: campaign.created_at, status: campaign.status, start_date: campaign.start_date ? campaign.start_date : new Date(), end_date: campaign.end_date ? campaign.end_date : new Date(), description: campaign.description, budget: campaign.budget, impressions: campaign.impressions, product_price: campaign.product_price, total_cost: campaign.total_cost, program_value: campaign.program_value, estimated_engagements: campaign.estimated_engagements, report_url: campaign.report_url, platforms: platforms, networks: networks, initiatives: initiatives, cpm: cpm || 0, estimated_cpe: estimated_cpe || 0, estimated_cpm: estimated_cpm || 0, added_value: added_value || 0, stats: stats, custom_stats: customStats, impressions_delivered: impressions_delivered ? impressions_delivered + customStats.totalImpressions : customStats.totalImpressions, engagements_delivered: engagements_delivered ? engagements_delivered + customStats.totalEngagements : customStats.totalEngagements, effective_cpm: effective_cpm || 0, effective_cpe: effective_cpe || 0, engagement_rate: engagement_rate || 0, creatives: creatives, impressions_platform_data: Object.assign({}, sumFollowers(stats, "platform"), { customStats: customStats.impressions.platform } ), impressions_product_data: Object.assign({}, impressionsByProductType, { customStats: customStats.impressions.product }), impressions_media_type_data: Object.assign({}, sumFollowers(stats, "media"), { customStats: customStats.impressions.media_type } ), impressions_initiative_data: Object.assign({}, impressionsByInitiative, { customStats: customStats.impressions.initiative }), engagements_platform_data: Object.assign({}, sumEngagements(stats, "platform"), { customStats: customStats.engagements.platform } ), engagements_product_data: Object.assign({}, engagementsByProductType, { customStats: customStats.engagements.product }), engagements_media_type_data: Object.assign({}, sumEngagements(stats, "media"), { customStats: customStats.engagements.media_type } ), engagements_initiative_data: Object.assign({}, engagementsByInitiative, { customStats: customStats.engagements.initiative }), engagements_engagement_type_data: Object.assign({}, sumEngagementsByType(stats), { customStats: customStats.engagements.engagement_type } ), impressions_overage: impressions_overage || 0, engagements_overage: engagements_overage || 0, impressions_savings: impressions_savings || 0, engagements_savings: engagements_savings || 0, initiative_first_start: initiative_first_start, initiative_last_end: initiative_last_end }; return Promise.resolve(options); } catch (error) { console.log("\n\n\nError formating report data.", error); return Promise.reject(error); } } function sumFollowers(originalArray = [], type) { const arr = originalArray.map(item => { return item.dataValues ? item.dataValues : item; }); if (type === "sum") { return arr.reduce((sum, item) => { return sum + Number(item.followers); }, 0); } else if (type === "platform") { const facebook = arr.filter(item => { return item.platform.toLowerCase() === "facebook"; }); const instagram = arr.filter( item => item.platform.toLowerCase() === "instagram" ); const twitter = arr.filter( item => item.platform.toLowerCase() === "twitter" ); const youtube = arr.filter( item => item.platform.toLowerCase() === "youtube" ); const facebookTotal = facebook.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const instagramTotal = instagram.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const twitterTotal = twitter.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const youtubeTotal = youtube.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const platformTotals = { facebook: facebookTotal, instagram: instagramTotal, twitter: twitterTotal, youtube: youtubeTotal }; return platformTotals; } else if (type === "media") { let text = []; let image = []; let video = []; arr.forEach(item => { if ( item.content_type === "image" || (!item.video_link && item.image_link) ) { image.push(item); } else if ( item.content_type === "video" || (item.video_link && !item.image_link) ) { video.push(item); } else { text.push(item); } }); const textTotal = text.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const imageTotal = image.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const videoTotal = video.reduce((sum, item) => { return sum + Number(item.followers); }, 0); const mediaTypeTotals = { text: textTotal, image: imageTotal, video: videoTotal }; return mediaTypeTotals; } } function sumEngagementsByType(arr = []) { let byType = { "RTs/Shares": 0, Likes: 0, Comments: 0, Views: 0, Clicks: 0 }; if (arr.length > 0) { arr.forEach(item => { byType["RTs/Shares"] += item.facebook_shares + item.twitter_retweet_count + Number(item.youtube_shares); byType.Likes += item.facebook_likes + item.instagram_likes + item.twitter_likes_count + Number(item.youtube_likes); byType.Comments += item.facebook_comments + item.instagram_comments + item.twitter_comment_count + Number(item.youtube_comments); (byType.Views += item.facebook_video_views + item.instagram_video_views + Number(item.twitter_video_views)), Number(item.youtube_video_views); byType.Clicks += item.bitlyclicks; }); return byType; } else { return byType; } } function sumEngagementsColumns(arr = []) { if (arr.length > 0) { return arr.reduce((sum, item) => { return ( sum + item.facebook_shares + item.facebook_comments + item.facebook_likes + item.facebook_video_views + item.instagram_comments + item.instagram_likes + item.instagram_video_views + item.twitter_comment_count + item.twitter_likes_count + item.twitter_retweet_count + Number(item.twitter_video_views) + Number(item.youtube_comments) + Number(item.youtube_likes) + Number(item.youtube_video_views) + Number(item.youtube_shares) ); }, 0); } else { return 0; } } function sumEngagements(arr = [], type = "sum") { const facebook = arr.filter( item => item.platform.toLowerCase() === "facebook" ); const instagram = arr.filter( item => item.platform.toLowerCase() === "instagram" ); const twitter = arr.filter(item => item.platform.toLowerCase() === "twitter"); const youtube = arr.filter(item => item.platform.toLowerCase() === "youtube"); const facebookTotal = facebook.reduce((sum, item) => { return ( sum + item.facebook_shares + item.facebook_comments + item.facebook_likes + item.facebook_video_views ); }, 0); const instagramTotal = instagram.reduce((sum, item) => { return ( sum + item.instagram_comments + item.instagram_likes + item.instagram_video_views ); }, 0); const twitterTotal = twitter.reduce((sum, item) => { return ( sum + item.twitter_comment_count + item.twitter_likes_count + item.twitter_retweet_count + Number(item.twitter_video_views) ); }, 0); const youtubeTotal = youtube.reduce((sum, yt) => { return ( sum + Number(yt.youtube_comments) + Number(yt.youtube_likes) + Number(yt.youtube_video_views) + Number(yt.youtube_shares) ); }, 0); if (type === "sum") { return facebookTotal + instagramTotal + twitterTotal + youtubeTotal; } else if (type === "platform") { const platformTotals = { facebook: facebookTotal, instagram: instagramTotal, twitter: twitterTotal, youtube: youtubeTotal }; return platformTotals; } else if (type === "media") { let text = []; let image = []; let video = []; arr.forEach(item => { if ( item.content_type === "image" || (!item.video_link && item.image_link) ) { image.push(item); } else if ( item.content_type === "video" || (item.video_link && !item.image_link) ) { video.push(item); } else { text.push(item); } }); const textTotal = sumEngagementsColumns(text); const imageTotal = sumEngagementsColumns(image); const videoTotal = sumEngagementsColumns(video); const mediaTypeTotals = { text: textTotal, image: imageTotal, video: videoTotal }; return mediaTypeTotals; } }