ZhuLinsen/daily_stock_analysis · error · Error

大盘复盘正在执行中,请稍后再试

Error message

大盘复盘正在执行中,请稍后再试

What it means

Raised during quantity replay (portfolio_service.py:763) when a trade row's side is neither 'buy' nor 'sell' after strip+lowercase. The replay cannot apply the event to the running position, so it fails with validation_error instead of guessing.

Source

Thrown at apps/dsa-web/src/api/analysis.ts:121

  triggerMarketReview: async (data: MarketReviewRequest = {}): Promise<MarketReviewAccepted> => {
    const response = await apiClient.post<Record<string, unknown>>(
      '/api/v1/analysis/market-review',
      {
        send_notification: data.sendNotification ?? true,
        report_language: data.reportLanguage,
        ...(data.regions !== undefined && { region: serializeMarketReviewRegions(data.regions) }),
      },
      {
        validateStatus: (status) => status === 202 || status === 409,
      }
    );

    if (response.status === 409) {
      const detail = response.data?.detail;
      const message = detail && typeof detail === 'object' && 'message' in detail
        ? String((detail as { message?: unknown }).message || '')
        : String(response.data?.message || '');
      throw new Error(message || '大盘复盘正在执行中,请稍后再试');
    }

    return toCamelCase<MarketReviewAccepted>(response.data);
  },

  /**
   * Get async task status.
   * @param taskId Task ID
   */
  getStatus: async (taskId: string): Promise<TaskStatus> => {
    const response = await apiClient.get<Record<string, unknown>>(
      `/api/v1/analysis/status/${taskId}`
    );

    const data = toCamelCase<TaskStatus>(response.data);

    // Ensure nested result payloads are converted recursively.
    if (data.result) {

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Query trades for the symbol/account where side NOT IN ('buy','sell') or side IS NULL and repair the rows
  2. Map the source vocabulary (B->buy, S->sell) in the importer before writing
  3. Re-write rows through add_trade which enforces VALID_SIDES at write time
  4. Add a NOT NULL + CHECK constraint or audit query to catch drift early

Example fix

# before
repo_inserted_row.side = "B"
# after
svc.add_trade(account_id=1, symbol="AAPL", side="buy", quantity=10, price=100.0, ...)
Defensive patterns

Strategy: validation

Validate before calling

VALID_SIDES = {"buy", "sell"}

def stored_side_ok(side):
    return bool(side) and side.strip().lower() in VALID_SIDES

Type guard

from typing import Literal, TypeGuard

StoredSide = Literal["buy", "sell"]

def is_stored_side(value: str | None) -> TypeGuard[StoredSide]:
    return bool(value) and value.strip().lower() in {"buy", "sell"}

Try / catch

try:
    svc.get_snapshot(account_id=a)
except ValueError as exc:
    if "Unsupported trade side" in str(exc):
        normalize_sides_in_db(a); svc.get_snapshot(account_id=a)
    else:
        raise

Prevention

When it happens

Trigger: A trades row with side='B', side='Bought', side=NULL/empty (None or '' fails both the buy and sell branches), or any non-enum value reaching _calculate_available_quantity; typically rows written outside add_trade's validated path.

Common situations: Broker exports using B/S codes loaded by a script that writes straight to the repository; NULL side from a partially failed import; a schema change where side became optional and older rows have empty strings.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/c5df192ecc9ab89b. Report an issue: GitHub.