9a2d939bb6
**Problem:** Previous commit stubbed HasReportBeenSentAsync/MarkReportSentAsync due to Dapper AOT error, but didn't restore idempotency check/mark calls. This broke CLAUDE.md guarantee: "Each job must be replayable without side effects." **Solution:** Implement idempotency using proven ADO pattern from GetSellDecisionsAsync: - HasReportBeenSentAsync: SELECT COUNT from recommendation_sent_log - MarkReportSentAsync: CREATE TABLE IF NOT EXISTS + INSERT with ON CONFLICT **Changes:** - RecommendationReportGenerator: Restored real idempotency logic (ADO pattern, no Dapper) - GenerateDailyRecommendationJob: Restore idempotency check/mark calls - GenerateWeeklyRecommendationJob: Restore idempotency check/mark calls - GenerateMonthlyRecommendationJob: Restore idempotency check/mark calls **Guarantees Restored:** - Partial failure safe (Telegram succeeds, job throws → no duplicate on retry) - Manual trigger safe (dashboard re-run → skips if already sent) - [DisableConcurrentExecution] per CLAUDE.md blocking rule Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>