fix: map exact Brightwheel video_info, state (1=start/in, 0=end, 2=out), and actor metadata

This commit is contained in:
2026-08-23 13:38:00 -07:00
parent a3c7f6c8f9
commit cedaba4ca6
+46 -30
View File
@@ -642,6 +642,20 @@ function extractMediaUrls(act) {
let videoUrl = null; let videoUrl = null;
let thumbUrl = null; let thumbUrl = null;
// Direct check for Brightwheel video_info object
if (act.video_info && typeof act.video_info === 'object') {
if (act.video_info.downloadable_url) videoUrl = act.video_info.downloadable_url;
else if (act.video_info.streamable_url) videoUrl = act.video_info.streamable_url;
if (act.video_info.thumbnail_url) thumbUrl = act.video_info.thumbnail_url;
}
// Direct check for Brightwheel media object
if (act.media && typeof act.media === 'object') {
if (act.media.image_url) imageUrl = act.media.image_url;
if (act.media.video_url && !videoUrl) videoUrl = act.media.video_url;
if (act.media.thumbnail_url && !thumbUrl) thumbUrl = act.media.thumbnail_url;
}
const isVideoStr = (url) => { const isVideoStr = (url) => {
if (!url || typeof url !== 'string') return false; if (!url || typeof url !== 'string') return false;
const lower = url.toLowerCase(); const lower = url.toLowerCase();
@@ -662,7 +676,7 @@ function extractMediaUrls(act) {
if (isVideoStr(obj) && !videoUrl) videoUrl = obj; if (isVideoStr(obj) && !videoUrl) videoUrl = obj;
else if (isImageStr(obj) && !imageUrl) imageUrl = obj; else if (isImageStr(obj) && !imageUrl) imageUrl = obj;
else if (!imageUrl && !videoUrl) { else if (!imageUrl && !videoUrl) {
if (act.activity_type === 'ac_video' || act.type === 'video') videoUrl = obj; if (act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') videoUrl = obj;
else imageUrl = obj; else imageUrl = obj;
} }
} }
@@ -677,7 +691,7 @@ function extractMediaUrls(act) {
if (typeof obj === 'object') { if (typeof obj === 'object') {
// Check video keys // Check video keys
const possibleVideos = [ const possibleVideos = [
obj.video_url, obj.video_stream_url, obj.video_hd_url, obj.video_transcode_url, obj.downloadable_url, obj.streamable_url, obj.video_url, obj.video_stream_url, obj.video_hd_url, obj.video_transcode_url,
obj.transcoded_video_url, obj.original_video_url, obj.stream_url, obj.mp4_url, obj.transcoded_video_url, obj.original_video_url, obj.stream_url, obj.mp4_url,
obj.video, obj.source_url, obj.download_url obj.video, obj.source_url, obj.download_url
]; ];
@@ -702,7 +716,7 @@ function extractMediaUrls(act) {
// Check generic url // Check generic url
if (typeof obj.url === 'string' && obj.url.startsWith('http')) { if (typeof obj.url === 'string' && obj.url.startsWith('http')) {
if (isVideoStr(obj.url) || act.activity_type === 'ac_video' || act.type === 'video') { if (isVideoStr(obj.url) || act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') {
if (!videoUrl) videoUrl = obj.url; if (!videoUrl) videoUrl = obj.url;
} else if (!imageUrl) { } else if (!imageUrl) {
imageUrl = obj.url; imageUrl = obj.url;
@@ -719,6 +733,7 @@ function extractMediaUrls(act) {
} }
// Recurse into nested common containers // Recurse into nested common containers
if (obj.video_info) search(obj.video_info, depth + 1);
if (obj.media) search(obj.media, depth + 1); if (obj.media) search(obj.media, depth + 1);
if (obj.video_data) search(obj.video_data, depth + 1); if (obj.video_data) search(obj.video_data, depth + 1);
if (obj.attachments) search(obj.attachments, depth + 1); if (obj.attachments) search(obj.attachments, depth + 1);
@@ -733,7 +748,7 @@ function extractMediaUrls(act) {
search(act); search(act);
// If it's an ac_video activity and imageUrl was found but points to video, fix assignment // If it's an ac_video activity and imageUrl was found but points to video, fix assignment
if ((act.activity_type === 'ac_video' || act.type === 'video') && !videoUrl && imageUrl) { if ((act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') && !videoUrl && imageUrl) {
videoUrl = imageUrl; videoUrl = imageUrl;
imageUrl = null; imageUrl = null;
} }
@@ -743,11 +758,13 @@ function extractMediaUrls(act) {
function normalizeActivity(act) { function normalizeActivity(act) {
const { imageUrl, videoUrl, thumbUrl } = extractMediaUrls(act); const { imageUrl, videoUrl, thumbUrl } = extractMediaUrls(act);
const actType = (act.activity_type || act.action_type || act.type || 'activity').toLowerCase(); const actType = (act.action_type || act.activity_type || act.type || 'activity').toLowerCase();
const rawSubtype = (act.action_type || act.event_type || act.subtype || act.sub_type || act.action || act.state || act.status || '').toLowerCase(); const rawSubtype = (act.action_type || act.event_type || act.subtype || act.sub_type || act.action || '').toLowerCase();
const stateVal = String(act.state ?? '');
// Extract staff / actor name // Extract actor / staff name
const teacherName = act.actor_name || act.created_by?.name || act.teacher_name || act.staff_name || (act.actor ? `${act.actor.first_name || ''} ${act.actor.last_name || ''}`.trim() : '') || ''; const actor = act.actor ? `${act.actor.first_name || ''} ${act.actor.last_name || ''}`.trim() : '';
const teacherName = actor || act.actor_name || act.created_by?.name || act.teacher_name || act.staff_name || '';
// Parse Subtypes and Human-readable Summaries for each category // Parse Subtypes and Human-readable Summaries for each category
let actionSummary = ''; let actionSummary = '';
@@ -761,12 +778,12 @@ function normalizeActivity(act) {
let milestoneName = act.milestone_name || act.tag_name || act.skill_name || act.milestone || null; let milestoneName = act.milestone_name || act.tag_name || act.skill_name || act.milestone || null;
let attendanceInfo = ''; let attendanceInfo = '';
// 1. NAP // 1. NAP (state 1 = start, state 0 = end/wake)
if (actType.includes('nap')) { if (actType.includes('nap')) {
if (rawSubtype.includes('start') || (napStart && !napEnd && !durationMinutes)) { if (stateVal === '1' || rawSubtype.includes('start') || (napStart && !napEnd && !durationMinutes)) {
actionSubtype = 'start_nap'; actionSubtype = 'start_nap';
actionSummary = 'Nap Started'; actionSummary = 'Nap Started';
} else if (rawSubtype.includes('end') || rawSubtype.includes('wake') || napEnd || durationMinutes) { } else if (stateVal === '0' || rawSubtype.includes('end') || rawSubtype.includes('wake') || napEnd || durationMinutes) {
actionSubtype = 'end_nap'; actionSubtype = 'end_nap';
const durText = durationMinutes ? `${durationMinutes} mins` : ''; const durText = durationMinutes ? `${durationMinutes} mins` : '';
actionSummary = durText ? `Nap Ended (${durText})` : 'Nap Ended'; actionSummary = durText ? `Nap Ended (${durText})` : 'Nap Ended';
@@ -775,20 +792,27 @@ function normalizeActivity(act) {
actionSummary = durationMinutes ? `Nap (${durationMinutes} mins)` : 'Nap Log'; actionSummary = durationMinutes ? `Nap (${durationMinutes} mins)` : 'Nap Log';
} }
} }
// 2. CHECK-IN / ATTENDANCE // 2. CHECK-IN / ATTENDANCE (state 1 = signed in, state 2 = signed out)
else if (actType.includes('checkin') || actType.includes('checkout') || actType.includes('attendance')) { else if (actType.includes('checkin') || actType.includes('checkout') || actType.includes('attendance')) {
const isOut = rawSubtype.includes('out') || rawSubtype.includes('away') || rawSubtype.includes('pickup') || actType.includes('checkout'); const isOut = stateVal === '2' || rawSubtype.includes('out') || rawSubtype.includes('away') || rawSubtype.includes('pickup') || actType.includes('checkout');
const signedBy = act.signed_in_by || act.signed_out_by || act.guardian_name || act.actor_name || teacherName || '';
if (isOut) { if (isOut) {
actionSubtype = 'signed_out'; actionSubtype = 'signed_out';
actionSummary = signedBy ? `Checked Out / Signed Out (by ${signedBy})` : 'Checked Out / Signed Out'; actionSummary = actor ? `Checked Out / Signed Out (by ${actor})` : 'Checked Out / Signed Out';
} else { } else {
actionSubtype = 'signed_in'; actionSubtype = 'signed_in';
actionSummary = signedBy ? `Checked In / Signed In (by ${signedBy})` : 'Checked In / Signed In'; actionSummary = actor ? `Checked In / Signed In (by ${actor})` : 'Checked In / Signed In';
} }
attendanceInfo = signedBy ? `Signed by: ${signedBy}` : ''; attendanceInfo = actor ? `Signed by: ${actor}` : '';
} }
// 3. FOOD / MEALS // 3. PHOTOS & VIDEOS
else if (actType.includes('video') || videoUrl) {
actionSubtype = 'video';
actionSummary = act.note ? `Video: "${act.note}"` : 'Video Clip';
} else if (actType.includes('photo') || imageUrl) {
actionSubtype = 'photo';
actionSummary = act.note ? `Photo: "${act.note}"` : 'Photo';
}
// 4. FOOD / MEALS
else if (actType.includes('food') || actType.includes('meal')) { else if (actType.includes('food') || actType.includes('meal')) {
if (!foodType && rawSubtype) foodType = rawSubtype; if (!foodType && rawSubtype) foodType = rawSubtype;
const mealLabel = foodType ? capitalize(foodType) : 'Meal'; const mealLabel = foodType ? capitalize(foodType) : 'Meal';
@@ -796,21 +820,13 @@ function normalizeActivity(act) {
actionSubtype = foodType || 'meal'; actionSubtype = foodType || 'meal';
actionSummary = eatLabel ? `${mealLabel} (${eatLabel})` : mealLabel; actionSummary = eatLabel ? `${mealLabel} (${eatLabel})` : mealLabel;
} }
// 4. POTTY / DIAPER // 5. POTTY / DIAPER
else if (actType.includes('potty') || actType.includes('diaper')) { else if (actType.includes('potty') || actType.includes('diaper')) {
if (!pottyType && rawSubtype) pottyType = rawSubtype; if (!pottyType && rawSubtype) pottyType = rawSubtype;
const pLabel = pottyType ? capitalize(pottyType) : 'Check'; const pLabel = pottyType ? capitalize(pottyType) : 'Check';
actionSubtype = pottyType || 'potty'; actionSubtype = pottyType || 'potty';
actionSummary = `Potty / Diaper (${pLabel})`; actionSummary = `Potty / Diaper (${pLabel})`;
} }
// 5. PHOTOS & VIDEOS
else if (actType.includes('video') || videoUrl) {
actionSubtype = 'video';
actionSummary = act.note ? `Video: "${act.note}"` : 'Video Clip';
} else if (actType.includes('photo') || imageUrl) {
actionSubtype = 'photo';
actionSummary = act.note ? `Photo: "${act.note}"` : 'Photo';
}
// 6. MILESTONES & OBSERVATIONS // 6. MILESTONES & OBSERVATIONS
else if (actType.includes('milestone') || actType.includes('observation') || actType.includes('learn') || actType.includes('kudos')) { else if (actType.includes('milestone') || actType.includes('observation') || actType.includes('learn') || actType.includes('kudos')) {
actionSubtype = 'milestone'; actionSubtype = 'milestone';
@@ -839,9 +855,9 @@ function normalizeActivity(act) {
} }
return { return {
id: act.id || act.object_id || Math.random().toString(36).substring(2), id: act.object_id || act.id || Math.random().toString(36).substring(2),
created_at: act.created_at || act.event_date || new Date().toISOString(), created_at: act.event_date || act.created_at || new Date().toISOString(),
activity_type: act.activity_type || actType, activity_type: act.action_type || act.activity_type || actType,
action_subtype: actionSubtype, action_subtype: actionSubtype,
action_summary: actionSummary, action_summary: actionSummary,
title: act.title || '', title: act.title || '',