iachat/db/migrate/20260422105901_seed_jasmine_and_daniela_prompts.rb
Rodribm10 c512e3e5f6
Some checks failed
Build and Push to GHCR (multi-arch) / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push to GHCR (multi-arch) / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Build and Push to GHCR (multi-arch) / merge (push) Has been cancelled
chore(prompts): split prod snapshot from staging from target
Reorganized db/seed_prompts/ into three clear bins:

  _prod_snapshot/   — 16 prompts pulled from iachat_production
                      (4 Jasmines + 12 scenarios). Read-only baseline.

  _staging_current/ — 6 prompts active in iachat-v2 right now
                      (Jasmine + 5 scenarios, including
                      outras_unidades and Reclamacoes_Ouvidoria
                      which were created on this branch).

  target/           — empty for now. Source of truth: the seed
                      migration only writes from here. Files we
                      review and approve land here, then deploy
                      pushes them to prod.

Updated the seed migration to walk target/ and to support both
generic scenarios (apply to every unit) and unit-scoped scenarios
(file prefixed with assistant slug, only that unit). Empty files
are skipped — useful for staged rollouts.

This guarantees no prompt ships to prod by accident: only what
ends up in target/ is applied.
2026-04-22 11:31:42 -03:00

108 lines
3.9 KiB
Ruby

# Sincroniza os prompts da Jasmine (orchestrator) e dos cenários
# (Daniela, Maria, Disponibilidade, etc) com os arquivos versionados em
# db/seed_prompts/target/. Esses são a fonte de verdade — esta migration
# apenas espelha o conteúdo nos registros do DB.
#
# Convenções:
# - target/assistants/<slug>.md
# → Captain::Assistant#orchestrator_prompt onde name == ASSISTANT_MAP[slug]
# - target/scenarios/<slug>.md
# → Captain::Scenario#instruction onde title == SCENARIO_TITLE_MAP[slug]
# (aplica em TODAS as unidades)
# - target/scenarios/<assistant_slug>__<scenario_slug>.md
# → mesmo que o anterior, mas restrito ao assistant_slug — sobrescreve
# o arquivo genérico só pra aquela unidade
#
# Idempotente: pula se conteúdo já bate. Arquivos vazios são ignorados.
class SeedJasmineAndDanielaPrompts < ActiveRecord::Migration[7.1]
ASSISTANT_MAP = {
'jasmine_qnn01' => 'Jasmine( Qnn01)',
'jasmine_primeal' => 'Jasmine(PrimeAL)',
'jasmine_primevl' => 'Jasmine(PrimeVL)',
'jasmine_express' => 'Jasmine (Express)'
}.freeze
SCENARIO_TITLE_MAP = {
'daniela_reservas' => 'Daniela_Reservas',
'disponibilidade_suites' => 'Disponibilidade de suites',
'maria_fotos' => 'maria_fotos',
'outras_unidades' => 'outras_unidades',
'reclamacoes_ouvidoria' => 'Reclamacoes_Ouvidoria'
}.freeze
def up
return unless defined?(Captain::Assistant) && defined?(Captain::Scenario)
sync_assistants
sync_scenarios
end
def down
# No-op. Rollback manual se necessário.
end
private
def sync_assistants
Dir.glob(Rails.root.join('db/seed_prompts/target/assistants/*.md')).each do |path|
slug = File.basename(path, '.md')
assistant_name = ASSISTANT_MAP[slug]
next if assistant_name.blank?
content = File.read(path)
next if content.strip.empty?
Captain::Assistant.where(name: assistant_name).find_each do |assistant|
next if assistant.orchestrator_prompt == content
assistant.update_columns(orchestrator_prompt: content, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
say "Synced orchestrator prompt → #{assistant_name} (id=#{assistant.id}, #{content.size} chars)"
end
end
end
def sync_scenarios
Dir.glob(Rails.root.join('db/seed_prompts/target/scenarios/*.md')).each do |path|
filename = File.basename(path, '.md')
content = File.read(path)
next if content.strip.empty?
if filename.include?('__')
apply_unit_scoped_scenario(filename, content)
else
apply_generic_scenario(filename, content)
end
end
end
def apply_generic_scenario(slug, content)
scenario_title = SCENARIO_TITLE_MAP[slug]
return if scenario_title.blank?
Captain::Scenario.where(title: scenario_title).find_each do |scenario|
next if scenario.instruction == content
scenario.update_columns(instruction: content, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
assistant_name = scenario.assistant&.name
say "Synced scenario (all units) → #{assistant_name} / #{scenario_title} (id=#{scenario.id}, #{content.size} chars)"
end
end
def apply_unit_scoped_scenario(filename, content)
assistant_slug, scenario_slug = filename.split('__', 2)
assistant_name = ASSISTANT_MAP[assistant_slug]
scenario_title = SCENARIO_TITLE_MAP[scenario_slug]
return if assistant_name.blank? || scenario_title.blank?
assistant_ids = Captain::Assistant.where(name: assistant_name).pluck(:id)
return if assistant_ids.empty?
Captain::Scenario.where(assistant_id: assistant_ids, title: scenario_title).find_each do |scenario|
next if scenario.instruction == content
scenario.update_columns(instruction: content, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
say "Synced scenario (unit-scoped) → #{assistant_name} / #{scenario_title} (id=#{scenario.id}, #{content.size} chars)"
end
end
end