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
@@ -62,8 +62,9 @@ public sealed class InitiateShadowRunHandler(
createdAt,
CancellationToken.None);
// Enqueue Hangfire job (durable; survives app restart)
// Enqueue Hangfire job to q-evaluation queue (Host listens to this queue)
var jobId = backgroundJobClient.Enqueue<ShadowRunJob>(
"q-evaluation",
job => job.ExecuteAsync(command, CancellationToken.None));
LogJobEnqueued(logger, runId, null);