Skip to content

Commit 6c3485e

Browse files
jcasimirclaude
andcommitted
Add supplemental resources from EMBARC, Khan Academy, and CCSS standards
- Add khan_academy source and khan_practice resource type to SupplementalResource - Add SupplementalResourcesHelper with display filtering, labels, and YouTube embed support - Update lesson views to show embedded YouTube videos and supplemental resource links - Update topic views to show topic-level resources (quizzes, newsletters, Khan Academy) - Eager-load supplemental_resources in lessons and topics controllers - Add CCSS Math standards data for Grades 4-6 (110 standards from SirFizX/standards-data) - Add Khan Academy EngageNY Grade 5 alignment data (6 modules, 36 topics) - Add YouTube video metadata for 135 EMBARC lesson videos (titles, thumbnails via oEmbed) - Add Ruby scripts: fetch_ccss_standards.rb, fetch_khan_alignment.rb, fetch_video_metadata.rb - Add rake tasks: ccss:import, khan:import, khan:clear Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 319ac22 commit 6c3485e

14 files changed

Lines changed: 3153 additions & 3 deletions

app/controllers/lessons_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ def show
33
@grade = Grade.find(params[:grade_id])
44
@content_module = @grade.content_modules.find(params[:content_module_id])
55
@topic = @content_module.topics.find(params[:topic_id])
6-
@lesson = @topic.lessons.includes(:lesson_plan, :problem_set, :exit_ticket, :homework).find(params[:id])
6+
@lesson = @topic.lessons.includes(:lesson_plan, :problem_set, :exit_ticket, :homework, :supplemental_resources).find(params[:id])
77
end
88

99
# GET /lessons/:id/reconstructed

app/controllers/topics_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class TopicsController < ApplicationController
22
def show
33
@grade = Grade.find(params[:grade_id])
44
@content_module = @grade.content_modules.find(params[:content_module_id])
5-
@topic = @content_module.topics.find(params[:id])
5+
@topic = @content_module.topics.includes(:supplemental_resources).find(params[:id])
66
@lessons = @topic.lessons.includes(:lesson_plan, :problem_set, :exit_ticket, :homework).order(:position)
77
end
88
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module SupplementalResourcesHelper
2+
RESOURCE_LABELS = {
3+
"video" => "Video",
4+
"homework_solutions" => "HW Solutions",
5+
"exit_ticket_solutions" => "ET Solutions",
6+
"topic_quiz" => "Topic Quiz",
7+
"parent_newsletter" => "Parent Newsletter",
8+
"google_slides" => "Slides",
9+
"geogebra" => "GeoGebra",
10+
"lesson_pdf" => "Lesson PDF",
11+
"application_problems" => "Application Problems",
12+
"mid_module_review" => "Mid-Module Review",
13+
"end_module_review" => "End-of-Module Review",
14+
"pacing_guide" => "Pacing Guide",
15+
"fluency_games" => "Fluency Games",
16+
"vocabulary" => "Vocabulary",
17+
"number_talks" => "Number Talks",
18+
"downloadable_resources" => "Downloads",
19+
"eureka_essentials" => "Eureka Essentials",
20+
"khan_practice" => "Khan Academy"
21+
}.freeze
22+
23+
# Resource types worth showing as external links.
24+
# Excludes proprietary formats (flipcharts, SMARTboard) and platform-dependent (GoFormative).
25+
DISPLAYABLE_TYPES = %w[
26+
video
27+
homework_solutions
28+
exit_ticket_solutions
29+
google_slides
30+
geogebra
31+
topic_quiz
32+
parent_newsletter
33+
application_problems
34+
mid_module_review
35+
end_module_review
36+
pacing_guide
37+
fluency_games
38+
vocabulary
39+
number_talks
40+
eureka_essentials
41+
downloadable_resources
42+
khan_practice
43+
].freeze
44+
45+
def supplemental_label(resource)
46+
RESOURCE_LABELS[resource.resource_type] || resource.title
47+
end
48+
49+
def supplemental_url(resource)
50+
resource.url || resource.source_page_url
51+
end
52+
53+
def displayable_supplemental_resources(resources)
54+
resources
55+
.select { |r| r.resource_type.in?(DISPLAYABLE_TYPES) && supplemental_url(r).present? }
56+
.sort_by { |r| DISPLAYABLE_TYPES.index(r.resource_type) || 999 }
57+
end
58+
59+
def youtube_video_id(url)
60+
return nil unless url
61+
match = url.match(%r{(?:embed/|v=|youtu\.be/)([^&?/]+)})
62+
match[1] if match
63+
end
64+
end

app/models/supplemental_resource.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ class SupplementalResource < ApplicationRecord
2727
smartboard
2828
promethean_flipchart
2929
go_formative
30+
khan_practice
3031
other
3132
].freeze
3233

33-
SOURCES = %w[embarc].freeze
34+
SOURCES = %w[embarc khan_academy].freeze
3435

3536
validates :resource_type, inclusion: { in: RESOURCE_TYPES }
3637
validates :source, inclusion: { in: SOURCES }

app/views/lessons/show.html.erb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,47 @@
5757
</div>
5858
</div>
5959

60+
<% video = @lesson.supplemental_resources.find { |r| r.resource_type == "video" && r.url.present? } %>
61+
<% if video %>
62+
<% video_id = youtube_video_id(video.url) %>
63+
<% if video_id %>
64+
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
65+
<div class="px-5 py-3 bg-gray-50 border-b border-gray-200">
66+
<h2 class="font-semibold">Lesson Video</h2>
67+
</div>
68+
<div class="aspect-video">
69+
<iframe
70+
src="https://www.youtube.com/embed/<%= video_id %>"
71+
class="w-full h-full border-0"
72+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
73+
allowfullscreen
74+
></iframe>
75+
</div>
76+
</div>
77+
<% end %>
78+
<% end %>
79+
80+
<% supplementals = displayable_supplemental_resources(@lesson.supplemental_resources.reject { |r| r.resource_type == "video" }) %>
81+
<% if supplementals.any? %>
82+
<div class="bg-white rounded-lg border border-gray-200 p-5">
83+
<h2 class="font-semibold mb-3">Supplemental Resources</h2>
84+
<div class="flex flex-wrap gap-3">
85+
<% supplementals.each do |resource| %>
86+
<%= link_to supplemental_label(resource),
87+
supplemental_url(resource),
88+
target: "_blank",
89+
rel: "noopener",
90+
class: "px-4 py-2 bg-white border border-gray-300 rounded hover:bg-gray-50 text-sm" %>
91+
<% end %>
92+
</div>
93+
<p class="text-xs text-gray-400 mt-3">
94+
Supplemental materials from
95+
<%= link_to "EMBARC.Online", "https://embarc.online", target: "_blank", rel: "noopener", class: "underline" %>,
96+
licensed under CC BY-NC-SA 4.0.
97+
</p>
98+
</div>
99+
<% end %>
100+
60101
<% if @lesson.lesson_plan %>
61102
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
62103
<div class="px-5 py-3 bg-gray-50 border-b border-gray-200">

app/views/topics/show.html.erb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@
99
<h1 class="text-2xl font-bold mb-1">Topic <%= @topic.letter %></h1>
1010
<p class="text-lg text-gray-600 mb-6"><%= @topic.title %></p>
1111

12+
<% supplementals = displayable_supplemental_resources(@topic.supplemental_resources) %>
13+
<% if supplementals.any? %>
14+
<div class="bg-white rounded-lg border border-gray-200 p-5 mb-6">
15+
<h2 class="font-semibold mb-3">Topic Resources</h2>
16+
<div class="flex flex-wrap gap-3">
17+
<% supplementals.each do |resource| %>
18+
<%= link_to supplemental_label(resource),
19+
supplemental_url(resource),
20+
target: "_blank",
21+
rel: "noopener",
22+
class: "px-4 py-2 bg-white border border-gray-300 rounded hover:bg-gray-50 text-sm" %>
23+
<% end %>
24+
</div>
25+
<p class="text-xs text-gray-400 mt-3">
26+
From <%= link_to "EMBARC.Online", "https://embarc.online", target: "_blank", rel: "noopener", class: "underline" %>,
27+
CC BY-NC-SA 4.0.
28+
</p>
29+
</div>
30+
<% end %>
31+
1232
<div class="space-y-3">
1333
<% @lessons.each do |lesson| %>
1434
<div class="bg-white rounded-lg border border-gray-200 p-4 flex justify-between items-start">

0 commit comments

Comments
 (0)