using QuestionsApi.Models; using QuestionsApi.Services.Flashcards; namespace QuestionsApi.Services; // Idempotent, single-target seed for testing "system" (shared, admin-curated) flashcards. // Unlike ContentPromotionCommand (cross-environment copy, source+target connections), this only // ever writes to the one database the app is already configured against, using the exact same // store methods a real user's create/edit path uses - just under the reserved FlashcardOwners.System // identity - so seeded content is created/updated through the same validation and revision // handling as any other deck/note. public static class SeedFlashcardsCommand { public static async Task RunAsync(IConfiguration configuration, TextWriter output) { var mongo = new MongoDatabaseProvider(configuration); var scheduleStore = new FlashcardScheduleStore(mongo, new FsrsFlashcardScheduler()); var deckStore = new FlashcardDeckStore(mongo); var noteStore = new FlashcardNoteStore(mongo, scheduleStore); var deck = await UpsertSystemDeckAsync(deckStore, "Step 3 Essentials", "Official curated review deck"); await UpsertSystemNoteAsync(noteStore, deck.Id!, FlashcardTypes.Basic, "A 62-year-old woman presents with sudden, painless, monocular vision loss. What is the diagnosis?", "Central retinal artery occlusion", "\"Cherry-red spot\" on fundoscopy is the classic finding."); await UpsertSystemNoteAsync(noteStore, deck.Id!, FlashcardTypes.Basic, "Thyroid storm: correct order of pharmacologic treatment?", "Beta-blocker (propranolol) first -> thionamide (PTU/methimazole) -> iodine solution (started >=1hr after thionamide) -> glucocorticoids", "Giving iodine before the thionamide can fuel new hormone synthesis (Wolff-Chaikoff escape risk)."); await UpsertSystemNoteAsync(noteStore, deck.Id!, FlashcardTypes.Cloze, "First-line treatment for uncomplicated hypertension in a Black patient is {{c1::a thiazide diuretic or calcium channel blocker}}.", "", ""); await output.WriteLineAsync($"Seeded system flashcards into deck '{deck.Name}' ({deck.Id})."); return 0; } private static async Task UpsertSystemDeckAsync(FlashcardDeckStore deckStore, string name, string description) { var existing = await deckStore.GetByNameAsync(FlashcardOwners.System, FlashcardDeckStore.Normalize(name)); var request = new UpsertFlashcardDeckRequest { Name = name, Description = description }; var result = await deckStore.UpsertAsync(FlashcardOwners.System, request, existing?.Id); return result.Deck ?? existing ?? throw new InvalidOperationException($"failed to seed deck '{name}'"); } private static async Task UpsertSystemNoteAsync( FlashcardNoteStore noteStore, string deckId, string type, string prompt, string answer, string extra) { var fingerprint = FlashcardNoteStore.Fingerprint(type, prompt, answer); var existingNotes = await noteStore.ListByDeckAsync(FlashcardOwners.System, deckId); var existing = existingNotes.FirstOrDefault(v => v.ContentFingerprint == fingerprint); var request = new UpsertFlashcardNoteRequest { DeckId = deckId, Type = type, Prompt = prompt, Answer = answer, Extra = extra, ExpectedRevision = existing?.Revision }; await noteStore.UpsertAsync(FlashcardOwners.System, request, existing?.Id); } }