impress/lib/tasks/items.rake
Emi Matchu 04ed182cef First draft of auto-modeling
Run `rails items:auto_model`! We'll set this up on a cron if we continue to be satisfied with the results.
2026-01-20 18:37:17 -08:00

98 lines
3.2 KiB
Ruby

namespace :items do
desc "Update cached fields for all items (useful if logic changes)"
task :update_cached_fields => :environment do
puts "Updating cached item fields for all items…"
Item.includes(:swf_assets).find_in_batches.with_index do |items, batch|
puts "Updating item batch ##{batch+1}"
Item.transaction do
items.each(&:update_cached_fields)
end
end
end
desc "Auto-model items on missing body types using NC Mall preview API"
task :auto_model, [:limit] => :environment do |task, args|
limit = (args[:limit] || 100).to_i
dry_run = ENV["DRY_RUN"] == "1"
auto_hint = ENV["AUTO_HINT"] != "0"
puts "Auto-modeling up to #{limit} items#{dry_run ? ' (DRY RUN)' : ''}..."
puts "Auto-hint: #{auto_hint ? 'enabled' : 'disabled'}"
puts
# Find items that need modeling, newest first
items = Item.is_not_modeled.order(created_at: :desc).limit(limit)
puts "Found #{items.count} items to process"
puts
items.each_with_index do |item, index|
puts "[#{index + 1}/#{items.count}] Item ##{item.id}: #{item.name}"
missing_body_ids = item.predicted_missing_body_ids
if missing_body_ids.empty?
puts " ⚠️ No missing body IDs (item may already be fully modeled)"
puts
next
end
puts " Missing #{missing_body_ids.size} body IDs: #{missing_body_ids.join(', ')}"
# Track results for this item
results = {modeled: 0, not_compatible: 0, not_found: 0}
had_transient_error = false
missing_body_ids.each do |body_id|
if dry_run
puts " Body #{body_id}: [DRY RUN] would attempt modeling"
next
end
begin
result = Pet::AutoModeling.model_item_on_body(item, body_id)
results[result] += 1
case result
when :modeled
puts " Body #{body_id}: ✅ Modeled successfully"
when :not_compatible
puts " Body #{body_id}: ❌ Not compatible (heuristic over-predicted)"
end
rescue Pet::AutoModeling::NoPetTypeForBody => e
puts " Body #{body_id}: ⚠️ #{e.message}"
rescue Neopets::NCMall::ResponseNotOK => e
if e.status >= 500
puts " Body #{body_id}: ⚠️ Server error (#{e.status}), will retry later"
had_transient_error = true
else
puts " Body #{body_id}: ❌ HTTP error (#{e.status})"
Sentry.capture_exception(e)
end
rescue Neopets::NCMall::UnexpectedResponseFormat => e
puts " Body #{body_id}: ❌ Unexpected response format: #{e.message}"
Sentry.capture_exception(e)
rescue Neopets::CustomPets::DownloadError => e
puts " Body #{body_id}: ⚠️ AMF error: #{e.message}"
had_transient_error = true
end
end
unless dry_run
# Set hint if we've addressed all bodies without transient errors.
# That way, if the item is not compatible with some bodies, we'll stop
# trying to auto-model it.
if auto_hint && !had_transient_error
item.update!(modeling_status_hint: "done")
puts " 📋 Set modeling_status_hint = 'done'"
end
end
puts " Summary: #{results[:modeled]} modeled, #{results[:not_compatible]} not compatible, #{results[:not_found]} not found"
puts
# Be nice to Neopets API
sleep 0.5 unless dry_run || index == items.count - 1
end
puts "Done!"
end
end