ZhuLinsen/daily_stock_analysis · error · SystemConfigConflictError
配置版本冲突
Error message
配置版本冲突
What it means
Raised during full account replay (portfolio_service.py:883) when a trade event's side is neither 'buy' nor 'sell' (compared after lower().strip()). The account-level replay (cash balances, FIFO/avg cost) cannot classify the event, so the snapshot fails with validation_error.
Source
Thrown at apps/dsa-web/src/api/systemConfig.ts:329
return toCamelCase<UpdateSystemConfigResponse>(response.data);
} catch (error: unknown) {
const parsed = getParsedApiError(error);
if (error && typeof error === 'object' && 'response' in error) {
const status = (error as { response?: { status?: number } }).response?.status;
const payloadData = (error as { response?: { data?: unknown } }).response?.data;
if (status === 400) {
const validationError = toCamelCase<SystemConfigValidationErrorResponse>(payloadData ?? {});
throw new SystemConfigValidationError(
parsed.message || validationError.message || '配置校验失败',
validationError.issues || [],
parsed,
);
}
if (status === 409) {
const conflict = toCamelCase<SystemConfigConflictResponse>(payloadData ?? {});
throw new SystemConfigConflictError(
parsed.message || conflict.message || '配置版本冲突',
conflict.currentConfigVersion,
parsed,
);
}
}
throw error;
}
},
/**
* 获取自选队列股票代码列表
*/
getWatchlist: async (): Promise<string[]> => {
const response = await apiClient.get<Record<string, unknown>>('/api/v1/stocks/watchlist');
const data = toCamelCase<{ stockCodes: string[] }>(response.data);
return data.stockCodes || [];View on GitHub (pinned to 5159bd72e8)
Solutions
- Audit trades for the account: SELECT ... WHERE side IS NULL OR LOWER(TRIM(side)) NOT IN ('buy','sell') and repair
- Map legacy values (B/S, long/short) to buy/sell in a migration script
- Insert future trades only via add_trade
- Add a DB CHECK constraint on side to stop drift at the storage layer
Example fix
# before raw_row.side = "long" # after svc.add_trade(account_id=1, symbol="AAPL", side="buy", quantity=10, price=185.50, ...)
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
- Route all writes through add_trade's enum validation
- Map legacy side vocabularies during migration
- Add NOT NULL/CHECK constraints on the trades table
When it happens
Trigger: A trades row with side='LONG', side='B', side=NULL, or an unexpected enum reaching _replay_account. Same class of corruption as the quantity-replay variant at portfolio_service.py:763, but hit while building the full account snapshot including cash and cost basis.
Common situations: Rows written by external ETL or manual SQL that bypass add_trade's VALID_SIDES check; enum drift after migrating data from another portfolio tool; partially failed imports leaving rows with NULL side.
Related errors
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/4466059e94ba8486.
Report an issue: GitHub.