.vbw-planning/milestones/ui-fixes-and-smart-scraping/phases/04-smart-scrape-recommendations/04-RESEARCH.md
File: app/models/source_monitor/source.rb
scraping_enabled (boolean), scraper_adapter (string, validates presence), scrape_settings (JSONB), min_scrape_interval (optional)active scope for filteringdue_for_fetch class method for schedulingavg_word_count method (lines 134-139) — currently computes average of scraped word counts onlyavg_feed_words and avg_scraped_words (lines 83-99) — query ItemContent for feed/scraped word counts by sourceitems_count auto-maintained via has_many associationFile: lib/source_monitor/configuration.rb
SourceMonitor.configure { |c| c.attr = value } pattern@scraping = ScrapingSettings.new already initializedmax_in_flight_per_source, max_bulk_batch_size, min_scrape_intervalattr_accessor + DEFAULT_* constant + reset! methodFiles: app/controllers/source_monitor/dashboard_controller.rb, lib/source_monitor/dashboard/queries.rb
queries.stats, queries.recent_activity, queries.quick_actions, queries.job_metrics, queries.upcoming_fetch_schedule{ total_sources, active_sources, failed_sources, total_items, fetches_today, health_distribution }Files: app/controllers/source_monitor/sources_controller.rb + view
searchable_with mixin)@q (Ransack query), computes @avg_feed_word_counts and @avg_scraped_word_counts as hashes (source_id -> avg)item_activity_rates, word count mapsFiles: app/jobs/source_monitor/scrape_item_job.rb, lib/source_monitor/scraping/bulk_source_scraper.rb, lib/source_monitor/scraping/item_scraper.rb
source.scraping_enabled?, respects min_scrape_interval, calls ItemScraper:current, :unscraped, :all. Returns Result struct with: status, selection, attempted_count, enqueued_count, already_enqueued_count, failure_count, failure_details, messages, rate_limitedAdapterResolver, calls adapter, persists resultFiles: app/models/source_monitor/item.rb, app/models/source_monitor/item_content.rb
has_one :item_content (autosave: true)feed_word_count and scraped_word_count (computed)feed_word_count from item.content, scraped_word_count from item_content.scraped_contentbefore_save hookFile: lib/source_monitor/analytics/sources_index_metrics.rb
base_scope, result_scope, search_paramsSourceFetchIntervalDistributionFile: app/controllers/source_monitor/source_bulk_scrapes_controller.rb
POST /sources/:source_id/bulk_scrape{ bulk_scrape: { selection: :current/:unscraped/:all } }SourceTurboResponses mixinFile: config/routes.rb
resource :bulk_scrape, only: :createget "/dashboard" -> dashboard#indexcreate_source! factory, mocks services, tests Turbo Stream responsesdom_id(source, :row)scrape_recommendation_threshold to ScrapingSettings with DEFAULT constant + resetavg_feed_words)SourcesIndexMetrics pattern for recommendation computationavg_feed_words Ransacker joins ItemContent — need all items to have content recordsSource.scrape_candidates scope using avg_feed_words thresholdPOST /sources/:id/test_scrape -> comparison pageSourceMonitor::Analytics::ScrapeRecommendations for candidate computation