docs: add stock name display implementation plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ayuriel 2026-02-16 12:47:13 +09:00
parent c836c133dd
commit b92f8f298b

View File

@ -0,0 +1,722 @@
# Stock Name Display Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Replace stock code displays with stock name as primary text across all portfolio-related views, with stock codes available via tooltip.
**Architecture:** Backend schemas and API endpoints that currently lack stock names (`SnapshotHoldingResponse`, `TransactionResponse`, backtest `TransactionItem`) get `name` fields added, with name resolution via the existing `RebalanceService.get_stock_names()`. Frontend swaps display pattern from `ticker` (primary) + `name` (subtitle) to `name` (primary) with `title={ticker}` tooltip.
**Tech Stack:** FastAPI + SQLAlchemy (backend), Next.js 15 + React 19 + TypeScript (frontend)
---
### Task 1: Add `name` field to `SnapshotHoldingResponse` schema
**Files:**
- Modify: `backend/app/schemas/portfolio.py:114-122`
**Step 1: Add name field to schema**
In `backend/app/schemas/portfolio.py`, add `name` field to `SnapshotHoldingResponse`:
```python
class SnapshotHoldingResponse(BaseModel):
ticker: str
name: str | None = None
quantity: int
price: FloatDecimal
value: FloatDecimal
current_ratio: FloatDecimal
class Config:
from_attributes = True
```
**Step 2: Commit**
```bash
git add backend/app/schemas/portfolio.py
git commit -m "feat: add name field to SnapshotHoldingResponse schema"
```
---
### Task 2: Add `name` field to `TransactionResponse` schema
**Files:**
- Modify: `backend/app/schemas/portfolio.py:72-76`
**Step 1: Add name field to schema**
In `backend/app/schemas/portfolio.py`, add `name` field to `TransactionResponse`:
```python
class TransactionResponse(TransactionBase):
id: int
name: str | None = None
class Config:
from_attributes = True
```
**Step 2: Commit**
```bash
git add backend/app/schemas/portfolio.py
git commit -m "feat: add name field to TransactionResponse schema"
```
---
### Task 3: Add `name` field to backtest `TransactionItem` schema
**Files:**
- Modify: `backend/app/schemas/backtest.py:119-131`
**Step 1: Add name field to schema**
In `backend/app/schemas/backtest.py`, add `name` field to `TransactionItem`:
```python
class TransactionItem(BaseModel):
"""Single transaction."""
id: int
date: date
ticker: str
name: str | None = None
action: str
shares: int
price: FloatDecimal
commission: FloatDecimal
class Config:
from_attributes = True
```
**Step 2: Commit**
```bash
git add backend/app/schemas/backtest.py
git commit -m "feat: add name field to backtest TransactionItem schema"
```
---
### Task 4: Populate snapshot holdings with stock names in snapshot API
**Files:**
- Modify: `backend/app/api/snapshot.py:53-128` (create_snapshot endpoint)
- Modify: `backend/app/api/snapshot.py:131-168` (get_snapshot endpoint)
**Step 1: Update imports**
Add `RebalanceService` import at top of `backend/app/api/snapshot.py`:
```python
from app.services.rebalance import RebalanceService
```
**Step 2: Update `create_snapshot` endpoint to include names**
In the `create_snapshot` function, after getting prices and before creating snapshot, resolve names and include them in the response. Replace the return statement (lines 113-128):
```python
# Get stock names
name_service = RebalanceService(db)
names = name_service.get_stock_names(tickers)
return SnapshotResponse(
id=snapshot.id,
portfolio_id=snapshot.portfolio_id,
total_value=snapshot.total_value,
snapshot_date=snapshot.snapshot_date,
holdings=[
SnapshotHoldingResponse(
ticker=h.ticker,
name=names.get(h.ticker),
quantity=h.quantity,
price=h.price,
value=h.value,
current_ratio=h.current_ratio,
)
for h in snapshot.holdings
],
)
```
**Step 3: Update `get_snapshot` endpoint to include names**
In the `get_snapshot` function, resolve names before returning. Replace lines 153-168:
```python
# Get stock names
tickers = [h.ticker for h in snapshot.holdings]
name_service = RebalanceService(db)
names = name_service.get_stock_names(tickers)
return SnapshotResponse(
id=snapshot.id,
portfolio_id=snapshot.portfolio_id,
total_value=snapshot.total_value,
snapshot_date=snapshot.snapshot_date,
holdings=[
SnapshotHoldingResponse(
ticker=h.ticker,
name=names.get(h.ticker),
quantity=h.quantity,
price=h.price,
value=h.value,
current_ratio=h.current_ratio,
)
for h in snapshot.holdings
],
)
```
**Step 4: Commit**
```bash
git add backend/app/api/snapshot.py
git commit -m "feat: include stock names in snapshot API responses"
```
---
### Task 5: Populate transactions with stock names in portfolio API
**Files:**
- Modify: `backend/app/api/portfolio.py:218-234`
**Step 1: Update imports**
At top of `backend/app/api/portfolio.py`, `RebalanceService` is already imported (line 21). No change needed.
**Step 2: Update `get_transactions` endpoint**
Replace the `get_transactions` function body to resolve names:
```python
@router.get("/{portfolio_id}/transactions", response_model=List[TransactionResponse])
async def get_transactions(
portfolio_id: int,
current_user: CurrentUser,
db: Session = Depends(get_db),
limit: int = 50,
):
"""Get transaction history for a portfolio."""
_get_portfolio(db, portfolio_id, current_user.id)
transactions = (
db.query(Transaction)
.filter(Transaction.portfolio_id == portfolio_id)
.order_by(Transaction.executed_at.desc())
.limit(limit)
.all()
)
# Resolve stock names
tickers = list({tx.ticker for tx in transactions})
service = RebalanceService(db)
names = service.get_stock_names(tickers)
return [
TransactionResponse(
id=tx.id,
ticker=tx.ticker,
name=names.get(tx.ticker),
tx_type=tx.tx_type.value,
quantity=tx.quantity,
price=tx.price,
executed_at=tx.executed_at,
memo=tx.memo,
)
for tx in transactions
]
```
**Step 3: Commit**
```bash
git add backend/app/api/portfolio.py
git commit -m "feat: include stock names in transaction API responses"
```
---
### Task 6: Populate backtest transactions with stock names
**Files:**
- Modify: `backend/app/api/backtest.py:209-242`
**Step 1: Add import**
Add `RebalanceService` import at top of `backend/app/api/backtest.py`:
```python
from app.services.rebalance import RebalanceService
```
**Step 2: Update `get_transactions` endpoint**
Replace the return logic in the backtest `get_transactions` endpoint:
```python
transactions = (
db.query(BacktestTransaction)
.filter(BacktestTransaction.backtest_id == backtest_id)
.order_by(BacktestTransaction.date, BacktestTransaction.id)
.all()
)
# Resolve stock names
tickers = list({t.ticker for t in transactions})
name_service = RebalanceService(db)
names = name_service.get_stock_names(tickers)
return [
TransactionItem(
id=t.id,
date=t.date,
ticker=t.ticker,
name=names.get(t.ticker),
action=t.action,
shares=t.shares,
price=t.price,
commission=t.commission,
)
for t in transactions
]
```
**Step 3: Commit**
```bash
git add backend/app/api/backtest.py
git commit -m "feat: include stock names in backtest transaction responses"
```
---
### Task 7: Update portfolio-card.tsx - show name instead of ticker
**Files:**
- Modify: `frontend/src/components/portfolio/portfolio-card.tsx`
**Step 1: Add `name` to Holding interface**
```typescript
interface Holding {
ticker: string;
name: string | null;
current_ratio: number | null;
}
```
**Step 2: Update pieData mapping to use name**
Change line 64 from `name: h.ticker` to:
```typescript
name: h.name || h.ticker,
```
**Step 3: Add title attribute to holdings preview badges**
Change the badge span (line 144-149) to include a `title`:
```tsx
<span
key={index}
className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
title={pieData[index]?.name !== holdings[index]?.name ? holdings.find(h => (h.name || h.ticker) === item.name)?.ticker : undefined}
>
{item.name}
</span>
```
Actually, simpler approach - since `pieData` is derived from `holdings`, we can track the original ticker. Let's use a simpler approach: map pieData to include the original ticker, then use `title={item.ticker}`:
Change the pieData mapping (lines 60-67):
```typescript
const pieData = holdings
.filter((h) => h.current_ratio !== null && h.current_ratio > 0)
.slice(0, 6)
.map((h, index) => ({
name: h.name || h.ticker,
ticker: h.ticker,
value: h.current_ratio ?? 0,
color: CHART_COLORS[index % CHART_COLORS.length],
}));
```
Then update the badge (lines 143-149):
```tsx
{pieData.slice(0, 4).map((item, index) => (
<span
key={index}
className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
title={item.ticker}
>
{item.name}
</span>
))}
```
**Step 4: Also update HoldingWithValue in portfolio list page**
In `frontend/src/app/portfolio/page.tsx`, add `name` to the `HoldingWithValue` interface (line 12-15):
```typescript
interface HoldingWithValue {
ticker: string;
name: string | null;
current_ratio: number | null;
}
```
**Step 5: Commit**
```bash
git add frontend/src/components/portfolio/portfolio-card.tsx frontend/src/app/portfolio/page.tsx
git commit -m "feat: show stock names in portfolio cards"
```
---
### Task 8: Update portfolio detail page - fix holdings ticker subtitle removal + transactions name
**Files:**
- Modify: `frontend/src/app/portfolio/[id]/page.tsx`
**Step 1: Add `name` to Transaction interface**
```typescript
interface Transaction {
id: number;
ticker: string;
name: string | null;
tx_type: string;
quantity: number;
price: number;
executed_at: string;
}
```
**Step 2: Update holdings table - remove ticker subtitle, add title tooltip**
Change lines 343-348 from:
```tsx
<td className="px-4 py-3">
<div className="font-medium text-sm">{holding.name || holding.ticker}</div>
{holding.name && (
<div className="text-xs text-muted-foreground">{holding.ticker}</div>
)}
</td>
```
To:
```tsx
<td className="px-4 py-3">
<span className="font-medium text-sm" title={holding.ticker}>{holding.name || holding.ticker}</span>
</td>
```
**Step 3: Update transactions table - show name instead of ticker**
Change line 457 from:
```tsx
<td className="px-4 py-3 text-sm font-medium">{tx.ticker}</td>
```
To:
```tsx
<td className="px-4 py-3 text-sm font-medium" title={tx.ticker}>{tx.name || tx.ticker}</td>
```
**Step 4: Update Target vs Actual section - add title tooltip**
Change line 522 from:
```tsx
<span className="font-medium">{holding?.name || target.ticker}</span>
```
To:
```tsx
<span className="font-medium" title={target.ticker}>{holding?.name || target.ticker}</span>
```
**Step 5: Commit**
```bash
git add frontend/src/app/portfolio/[id]/page.tsx
git commit -m "feat: show stock names as primary display in portfolio detail"
```
---
### Task 9: Update rebalance page - name as primary display
**Files:**
- Modify: `frontend/src/app/portfolio/[id]/rebalance/page.tsx`
**Step 1: Fetch stock names for price input labels**
The rebalance page needs names for the price input section. The targets and holdings don't have names, but the rebalance result does. We need to store a name map. Add state and fetch names when targets/holdings load.
Add `nameMap` state after existing state declarations (after line 61):
```typescript
const [nameMap, setNameMap] = useState<Record<string, string>>({});
```
In the `init` function, after setting targets and holdings, fetch portfolio detail to get names:
```typescript
// Fetch stock names from portfolio detail
try {
const detail = await api.get<{ holdings: { ticker: string; name: string | null }[] }>(`/api/portfolios/${portfolioId}/detail`);
const names: Record<string, string> = {};
for (const h of detail.holdings) {
if (h.name) names[h.ticker] = h.name;
}
setNameMap(names);
} catch {
// Names are optional, continue without
}
```
**Step 2: Update price input labels to use name**
Change line 183 from:
```tsx
{ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}주
```
To:
```tsx
{nameMap[ticker] || ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}주
```
**Step 3: Update results table - name as primary**
Change lines 299-304 from:
```tsx
<td className="px-3 py-3">
<div className="font-medium">{item.ticker}</div>
{item.name && (
<div className="text-xs text-muted-foreground">{item.name}</div>
)}
</td>
```
To:
```tsx
<td className="px-3 py-3">
<span className="font-medium" title={item.ticker}>{item.name || item.ticker}</span>
</td>
```
**Step 4: Also update nameMap from rebalance results**
After the calculate function sets the result, update nameMap with any names from the result:
In the `calculate` function, after `setResult(data)` (line 117), add:
```typescript
// Update name map from results
const newNames = { ...nameMap };
for (const item of data.items) {
if (item.name) newNames[item.ticker] = item.name;
}
setNameMap(newNames);
```
**Step 5: Commit**
```bash
git add frontend/src/app/portfolio/[id]/rebalance/page.tsx
git commit -m "feat: show stock names as primary display in rebalance page"
```
---
### Task 10: Update portfolio history page - show names in snapshot
**Files:**
- Modify: `frontend/src/app/portfolio/[id]/history/page.tsx`
**Step 1: Add `name` to SnapshotDetail holdings interface**
Change the `holdings` type in `SnapshotDetail` interface (lines 23-29):
```typescript
holdings: {
ticker: string;
name: string | null;
quantity: number;
price: string;
value: string;
current_ratio: string;
}[];
```
**Step 2: Update snapshot detail modal table**
Change line 428 from:
```tsx
<td className="px-4 py-2 text-sm text-foreground">
{holding.ticker}
</td>
```
To:
```tsx
<td className="px-4 py-2 text-sm text-foreground" title={holding.ticker}>
{holding.name || holding.ticker}
</td>
```
**Step 3: Commit**
```bash
git add frontend/src/app/portfolio/[id]/history/page.tsx
git commit -m "feat: show stock names in portfolio history snapshots"
```
---
### Task 11: Update strategy pages - name as primary (3 files)
**Files:**
- Modify: `frontend/src/app/strategy/multi-factor/page.tsx:209-211`
- Modify: `frontend/src/app/strategy/quality/page.tsx:174-176`
- Modify: `frontend/src/app/strategy/value-momentum/page.tsx:190-192`
**Step 1: Update multi-factor page**
Change lines 209-211 from:
```tsx
<td className="px-4 py-3">
<div className="font-medium">{stock.ticker}</div>
<div className="text-xs text-muted-foreground">{stock.name}</div>
</td>
```
To:
```tsx
<td className="px-4 py-3">
<span className="font-medium" title={stock.ticker}>{stock.name || stock.ticker}</span>
</td>
```
**Step 2: Update quality page**
Same change at lines 174-176.
**Step 3: Update value-momentum page**
Same change at lines 190-192.
**Step 4: Commit**
```bash
git add frontend/src/app/strategy/multi-factor/page.tsx frontend/src/app/strategy/quality/page.tsx frontend/src/app/strategy/value-momentum/page.tsx
git commit -m "feat: show stock names as primary display in strategy pages"
```
---
### Task 12: Update backtest detail page - name as primary
**Files:**
- Modify: `frontend/src/app/backtest/[id]/page.tsx`
**Step 1: Add `name` to TransactionItem interface**
```typescript
interface TransactionItem {
id: number;
date: string;
ticker: string;
name: string | null;
action: string;
shares: number;
price: number;
commission: number;
}
```
**Step 2: Update holdings tab display**
Change lines 392-394 from:
```tsx
<td className="px-4 py-3">
<div className="font-medium">{h.ticker}</div>
<div className="text-xs text-muted-foreground">{h.name}</div>
</td>
```
To:
```tsx
<td className="px-4 py-3">
<span className="font-medium" title={h.ticker}>{h.name || h.ticker}</span>
</td>
```
**Step 3: Update transactions tab display**
Change line 425 from:
```tsx
<td className="px-4 py-3 text-sm font-medium">{t.ticker}</td>
```
To:
```tsx
<td className="px-4 py-3 text-sm font-medium" title={t.ticker}>{t.name || t.ticker}</td>
```
**Step 4: Commit**
```bash
git add frontend/src/app/backtest/[id]/page.tsx
git commit -m "feat: show stock names as primary display in backtest detail"
```
---
### Task 13: Verify frontend build
**Step 1: Run frontend build to check for TypeScript errors**
```bash
cd frontend && npm run build
```
Expected: Build succeeds with no type errors.
**Step 2: If build fails, fix any TypeScript errors and re-run**
**Step 3: Commit any fixes**
```bash
git add -A && git commit -m "fix: resolve build errors from stock name display changes"
```