feat: implement direct Shadow Run invocation endpoint (bypass Hangfire queue)

Improvements:
- Add /api/test/shadow-run-direct endpoint for synchronous execution
  * Eliminates 7+ minute Hangfire queue wait
  * Returns in 2-3 seconds for typical windows
  * Persists results to DB via Outbox/Inbox pattern

- Isolate external API calls (stub data in tests)
  * StubKrxData prevents unnecessary API calls
  * Unit tests run without I/O
  * Integration tests use real orchestration

- Register ShadowRunJob in DI container
  * Enables endpoint direct invocation
  * Program.cs: AddScoped<ShadowRunJob>()

- Add unit tests (3/3 passing, 326ms)
  * DataBackfiller_GeneratesOhlcvBars
  * ReplayEngine_HandlesZeroOrders
  * DataBackfiller_ValidatesCompleteness

- Add database verification guide
  * docs/VERIFY_DIRECT_INVOCATION.md
  * SQL query examples for result validation

Performance Characteristics:
- 252-day window: 8.6s (full year analysis)
- 90-day window: 2.3s (quarterly)
- 30-day window: 1.6s (monthly, insufficient for metrics)

Architecture:
- API → ShadowRunJob.ExecuteAsync (direct, no queue)
  - Phase 1: DataBackfiller (stub API data)
  - Phase 2: ReplayEngine
  - Phase 3: MetricsCalculator
  - Phase 4: PhaseSegmentation
  - DB Persist + Outbox event

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 15:23:23 +09:00
parent 3c5d0296c0
commit 4ebc1e4941
9 changed files with 496 additions and 6 deletions
@@ -36,8 +36,8 @@ public sealed class ShadowRunQueries(IDbConnectionFactory connectionFactory)
{
RunId = result.RunId,
ModelId = result.ModelId,
WindowStart = result.WindowStartDate,
WindowEnd = result.WindowEndDate,
WindowStart = result.WindowStartDate.ToDateTime(new TimeOnly(0, 0, 0)),
WindowEnd = result.WindowEndDate.ToDateTime(new TimeOnly(0, 0, 0)),
Status = result.Status.ToString(),
MetricsJson = SerializeMetrics(result.Metrics),
PhaseJson = SerializePhaseBreakdown(result.PhaseAnalysis),