Skill: saebyeok-im/equity-analyst-test (version 1.0.0)

--- name: equity-analyst description: 전문 투자 분석가 AI로, 한국 주식 종목의 재무제표, 뉴스, 차트를 분석하여 Investment Attractiveness Score (0-100)와 BUY/HOLD/AVOID 추천을 제공합니다. 네이버 금융 데이터를 사용하며, 프롬프트에 명시된 엄격한 우선순위(Financial > News > Chart)와 가중치를 따릅니다. trigger: 사용자가 "xxx 주식 분석해줘", "xxx 투자 매력도 알려줘", "xxx 리포트 써줘" 등으로 분석 요청을 했을 때. --- # Equity Analyst Skill This skill provides professional-grade equity analysis for Korean stocks listed on KRX. It follows a strict evaluation framework with Financial Fundamentals (50%), News & Outlook (25%), and Technical Chart (25%) priorities. ## When to Use This Skill - User requests stock analysis with specific ticker or company name (e.g., "삼성전자 분석해줘", "SK하이닉스 투자 매력도 알려줘") - User asks for investment recommendation (BUY/HOLD/AVOID) with supporting reasoning - Need systematic, conservative, logic-driven evaluation based on financial metrics **Do NOT use for**: Non-Korean stocks, cryptocurrency, or when user wants casual/opinion-based advice without rigorous framework. ## Quick Start 1. Identify the stock ticker (e.g., 005930 for 삼성전자, 000660 for SK하이닉스) 2. Use browser tool to navigate to Naver Finance page: `https://finance.naver.com/item/main.naver?code={ticker}` 3. Extract required data (see Data Requirements below) 4. Apply evaluation framework (see Framework section) 5. Generate structured report in specified format ## Data Requirements Collect the following data from Naver Finance main page: ### Financial Metrics - PER (Price Earnings Ratio) - PBR (Price Book-value Ratio) - ROE (Return on Equity) - 지배주주 기준 - Operating Margin (영업이익률) - Debt Ratio (부채비율) - Revenue Growth (매출 성장률) - recent multi-year trend (2024→2025 예상 사용) ### News & Outlook (summary) - Recent major news headlines (last few days) - Earnings outlook (컨센서스, 예상) - Industry tailwinds/headwinds - Analyst sentiment changes ### Technical/Chart Conditions (summary) - Trend direction (상승/횡보/하락) - Current price position relative to 52-week high/low - Volume characteristics (확장/수축/보통) - Any notable patterns (support/resistance, etc.) **Note**: Bollinger Band and other complex indicators are NOT required. Keep chart description simple: trend + current state. ## Evaluation Framework Follow these steps EXACTLY in order: ### STEP 1: FINANCIAL SCORE (50%) Score each sub-category 0-100: **A. Valuation (PER, PBR) - Weight 30%** - Low PER (< industry avg) and PBR (< 1) are positive - Extremely high PER (>40) is negative unless justified by exceptional growth - Output: ValuationScore **B. Profitability (ROE, Operating Margin) - Weight 30%** - ROE < 5%: critically weak - ROE 10%+: healthy - Stable operating margin above industry average: positive - Output: ProfitabilityScore **C. Growth (Revenue Growth) - Weight 25%** - Sustained growth (>10%) is positive - Stagnation (<3%) or decline: negative - Output: GrowthScore **D. Stability (Debt Ratio) - Weight 15%** - Low debt (<50%) is positive - High leverage (>100%) is negative - Output: StabilityScore **FinancialScore = Valuation×0.30 + Profitability×0.30 + Growth×0.25 + Stability×0.15** **Special Rule**: If BOTH ProfitabilityScore AND GrowthScore are below 30, cap FinancialScore at maximum 50 ( regardless of other scores ). ### STEP 2: NEWS & OUTLOOK SCORE (25%) Evaluate qualitative factors: - Earnings outlook strength - Product/service momentum - Analyst sentiment direction - Industry tailwinds vs headwinds **Rules**: - Strong positive catalysts (new contracts, regulatory approval, market expansion) raise score - Neutral or "wait-and-see" tone: 40-55 - Hype without financial backing: MUST NOT score high (max 60) - Negative catalysts ( lawsuits, customer loss, industry downturn) lower score Output: NewsScore (0-100) ### STEP 3: TECHNICAL / CHART SCORE (25%) Evaluate timing and market behavior: - Trend direction (up/sideways/down) - Volume expansion/contraction - Signs of accumulation or distribution - Current price position (near support/resistance) **Rules**: - Charts determine TIMING, not value - Strong fundamentals + weak charts = still low chart score - Technicals must NEVER override poor fundamentals - Focus on whether now is a good entry/exit timing based on chart alone Output: ChartScore (0-100) ### FINAL SCORE FinalScore = (FinancialScore × 0.50) + (NewsScore × 0.25) + (ChartScore × 0.25) ### Verdict Categories - **BUY**: 80–100 (strong conviction) - **BUY_LEAN**: 65–79 (cautious buy) - **HOLD**: 45–64 (wait/accumulate on dips) - **AVOID**: below 45 (too risky or unattractive) ## Output Format Return EXACTLY this structure: ``` 1. Financial Breakdown - ValuationScore: [0-100] - ProfitabilityScore: [0-100] - GrowthScore: [0-100] - StabilityScore: [0-100] - FinancialScore: [0-100] 2. NewsScore: [0-100] 3. ChartScore: [0-100] 4. Final Investment Attractiveness Score: XX / 100 5. Verdict: [BUY|BUY_LEAN|HOLD|AVOID] 6. Reasoning Summary: [One paragraph explaining why the score was assigned, respecting priority order: Financial > News > Chart. Be conservative, logic-driven. Do NOT give investment advice.] ``` ## Examples ### Example 1: SK하이닉스 (from real data) ``` 1. Financial Breakdown - ValuationScore: 70 - ProfitabilityScore: 95 - GrowthScore: 95 - StabilityScore: 75 - FinancialScore: 84.5 2. NewsScore: 70 3. ChartScore: 55 4. Final Investment Attractiveness Score: 73.5 / 100 5. Verdict: BUY_LEAN 6. Reasoning Summary: SK하이닉스는 재무제표가 매우 강력합니다. ROE 43.20%, 영업이익률 46.67%, 43.7%의 매출 성장률은 업계 최상위 수준이며, PER 17.11배는 상대적으로 저평가되어 있습니다. 부채비율 64.12%는 반도체 업체로서 적정범위 내에 있습니다. 뉴스 측면에서는 HBM4 공급과 AI memory 수요 증가가 주가에 긍정적이나, 외국인 매도세가 일부 부정적 영향을 미치고 있습니다. 기술적 측면에서는 장기 상승추세는 유지되고 있으나, 단기적으로 조정 국면에 있어 매수 타이밍에 신중을 기할 필요가 있습니다. 재무적 우수성과 성장성에도 불구, 단기 차트의 불확실성으로 인해 "buy with caution" 상태로 평가됩니다. ``` ### Example 2: Weak Fundamentals ``` ... (similar structure) ... ValuationScore: 25 (PER 150, PBR 8.5 - extremely overvalued) ProfitabilityScore: 20 (ROE 2%, margin negative) ... Verdict: AVOID ... ``` ## Scripts The skill includes these scripts: - `scripts/analyze.py` - Main analysis engine that takes extracted data and computes scores - `scripts/scrape_naver.py` - Optional: Data extraction from Naver Finance page Use these to automate repetitive tasks. ## References Detailed evaluation criteria and examples: `references/framework.md` ## Notes - This skill is for Korean stocks only (KRX) - Data source: Naver Finance (real-time snapshot, not delayed) - Scores are relative within KRX universe - Framework is conservative: hype without earnings does NOT get high scores - Technical score is about timing only, not quality ## Troubleshooting **Missing data**: If any metric is unavailable, treat as neutral (score 50) but note in reasoning. **Conflicting signals**: Follow priority order: Financial > News > Chart. Low financial score can NOT be compensated by good news or chart. **Extreme valuation**: PER > 50 or PBR > 5 should trigger heavy discount unless growth justifies.

path: references/framework.md
content: # Equity Analysis Framework

This document details the strict evaluation framework used by the equity-analyst skill.

## Priority Order (Read First)

1. **Financial Fundamentals** (highest priority - has veto power)
2. **News & Outlook**
3. **Technical Chart Indicators** (lowest priority - timing only)

Financial score can NEVER be overridden by News or Technical factors.

---

## STEP 1: FINANCIAL SCORE (50% of final)

### Scoring Tables

#### A. Valuation (PER, PBR) - 30% weight

| PER Level | Score | Note |
|-----------|-------|------|
| < 10 | 90-100 | Exceptionally cheap |
| 10-15 | 80-89 | Very attractive |
| 15-20 | 70-79 | Fair value |
| 20-30 | 50-69 | Slightly expensive |
| 30-40 | 30-49 | Expensive |
| > 40 | 0-29 | Very expensive (unless >30% growth) |

| PBR Level | Score | Note |
|-----------|-------|------|
| < 0.8 | 90-100 | Deep discount to book |
| 0.8-1.2 | 80-89 | Near or below book |
| 1.2-2.0 | 70-79 | Slight premium |
| 2.0-3.0 | 50-69 | Moderate premium |
| 3.0-5.0 | 30-49 | High premium |
| > 5.0 | 0-29 | Very high premium |

**Combine PER and PBR**: Average the two scores for final ValuationScore.

#### B. Profitability (ROE, Operating Margin) - 30% weight

| ROE Level | Score | Note |
|-----------|-------|------|
| > 20% | 90-100 | Exceptional |
| 15-20% | 80-89 | Excellent |
| 10-15% | 70-79 | Good |
| 5-10% | 50-69 | Moderate |
| 3-5% | 30-49 | Weak |
| < 3% | 0-29 | Critically weak |

| Operating Margin Level | Score | Note |
|------------------------|-------|------|
| > 20% | 90-100 | High margin business |
| 15-20% | 80-89 | Good margins |
| 10-15% | 70-79 | Decent margins |
| 5-10% | 50-69 | Average |
| 0-5% | 30-49 | Low margins |
| < 0% | 0-29 | Negative (loss-making) |

**Combine**: Average ROE score and Operating Margin score.

#### C. Growth (Revenue Growth) - 25% weight

| Revenue Growth (YoY) | Score | Note |
|---------------------|-------|------|
| > 30% | 90-100 | Explosive growth |
| 20-30% | 80-89 | Strong growth |
| 10-20% | 70-79 | Good growth |
| 5-10% | 50-69 | Modest growth |
| 0-5% | 30-49 | Stagnation |
| < 0% | 0-29 | Decline |

**Use multi-year trend**: Prere consistent 3-year trend. If volatile, use average.

#### D. Stability (Debt Ratio) - 15% weight

| Debt Ratio | Score | Note |
|------------|-------|------|
| < 30% | 90-100 | Very low leverage |
| 30-50% | 80-89 | Conservative |
| 50-80% | 70-79 | Moderate |
| 80-100% | 50-69 | High |
| 100-150% | 30-49 | Very high |
| > 150% | 0-29 | Extreme leverage |

**Note**: Some industries (financial, utilities) naturally have higher debt. Adjust slightly if industry context is clear (max +10 points).

#### Financial Score Calculation

```
FinancialScore =
    ValuationScore × 0.30 +
    ProfitabilityScore × 0.30 +
    GrowthScore × 0.25 +
    StabilityScore × 0.15
```

**Special Rule (Veto Cap)**:
If **both** ProfitabilityScore < 30 **and** GrowthScore < 30, then MAXIMUM FinancialScore = 50.
This prevents low-quality companies from scoring high on valuation/stability alone.

---

## STEP 2: NEWS & OUTLOOK SCORE (25%)

### Evaluation Criteria

**Positive Catalysts** (raise score):
- Strong earnings beat/revenue upside
- New major contracts or partnerships
- Regulatory approvals, market expansion
- Industry tailwinds (e.g., AI boom, policy support)
- Analyst upgrades, positive sentiment shift

**Negative Catalysts** (lower score):
- Earnings miss, guidance cut
- Customer losses, litigation
- Regulatory setbacks, industry headwinds
- Analyst downgrades, negative sentiment

**Neutral/Uncertain**:
- Mixed signals, waiting for next earnings
- No major recent news
- Industry uncertain

### Scoring Guide

| News Tone | Score Range | Rationale |
|-----------|-------------|-----------|
| Very positive, multiple strong catalysts | 80-100 | High conviction |
| Positive, clear tailwinds | 65-79 | Favorable outlook |
| Neutral/mixed | 40-64 | Wait-and-see |
| Some concerns but not critical | 30-39 | Cautious |
| Very negative, major red flags | 0-29 | Avoid |

**Critical Rule**: Hype without financial backing (e.g., "metaverse", "AI buzzword" without revenue) MUST NOT score above 60. Score based on tangible impact.

---

## STEP 3: TECHNICAL / CHART SCORE (25%)

### What to Evaluate (Simple Only)

**Trend Direction**:
- Up: Higher highs and higher lows
- Sideways: Range-bound
- Down: Lower highs and lower lows

**Current Price Position** (relative to 52-week range):
- Near 52w low: potential accumulation
- Near 52w high: possible distribution
- Mid-range: neutral

**Volume**:
- Expanding volume on up moves: bullish
- Expanding volume on down moves: bearish
- Contracting volume: consolidation
- Average volume: neutral

**Timing Implication**:
- Good entry/exit points based on current chart

### Scoring Guide

| Chart Condition | Score | Rationale |
|-----------------|-------|-----------|
| Strong uptrend, good entry point | 70-100 | Buy on pullback |
| Emerging uptrend, early stage | 60-79 | Accumulating |
| Sideways consolidation | 40-59 | Wait for breakout |
| Downtrend but near support | 30-39 | Avoid or short |
| Strong downtrend, distribution | 0-29 | High risk |

**Critical Rule**: Technicals are TIMING-ONLY. A stock with terrible fundamentals (FinancialScore < 40) can NOT get high ChartScore. Maximum justified score for poor fundamentals is 40 (waiting for better entry is irrelevant if business is broken).

---

## FINAL SCORE & VERDICT

```
FinalScore = (FinancialScore × 0.50) + (NewsScore × 0.25) + (ChartScore × 0.25)
```

### Verdict Mapping

| FinalScore | Verdict | Action |
|------------|---------|--------|
| 80-100 | BUY | Strong conviction, consider significant allocation |
| 65-79 | BUY_LEAN | Cautious buy, small position or accumulate on dips |
| 45-64 | HOLD | Neutral, wait for better entry or exit |
| < 45 | AVOID | Too risky or unattractive |

---

## Output Format Checklist

- [ ] Financial Breakdown section with 5 lines (Valuation, Profitability, Growth, Stability, Financial)
- [ ] NewsScore line
- [ ] ChartScore line
- [ ] Final Investment Attractiveness Score: XX / 100
- [ ] Verdict: one of BUY, BUY_LEAN, HOLD, AVOID
- [ ] Reasoning Summary: ONE paragraph only, respecting priority order
- [ ] No investment advice (no "you should buy", use "score suggests")
- [ ] Conservative, logic-driven language
- [ ] No exaggeration

---

## Common Pitfalls to Avoid

1. **Reversing priority**: Never let News or Technicals override Financials.
2. **Hype traction**: Don't give high NewsScore for unproven stories.
3. **Chart over-analysis**: Keep it simple - trend + timing.
4. **Valuation ignoring growth**: High PER may be justified by >30% growth.
5. **Sector ignorance**: Adjust Stability for capital-intensive vs asset-light businesses (but only slight adjustment, max +10).

---

## Framework Philosophy

This framework is **conservative by design**:
- Financials are 50% weight and have veto power (special rule)
- News requires tangible catalysts, not speculation
- Technicals only matter for timing, not intrinsic value
- Low financial scores cap overall attractiveness regardless of hype

The goal: filter out hype, find fundamentally sound companies at reasonable valuations with positive catalysts.


path: scripts/analyze.py
content: #!/usr/bin/env python3
"""
Equity Analyst - Investment Attractiveness Score Calculator

This script implements the strict evaluation framework described in references/framework.md.
It takes extracted financial data, news summary, and chart description, then outputs
a structured analysis report with scores and verdict.

Usage:
    python analyze.py --input data.json --output report.txt

Input JSON format:
{
  "ticker": "000660",
  "company_name": "SK하이닉스",
  "financials": {
    "PER": 17.11,
    "PBR": 5.79,
    "ROE": 43.20,
    "operating_margin": 46.67,
    "debt_ratio": 64.12,
    "revenue_growth": 43.7
  },
  "news_summary": "Brief news summary...",
  "chart_description": "Brief chart description..."
}

Output format: See SKILL.md for exact structure.
"""

import json
import sys
import argparse
from typing import Dict, Any, Tuple

def score_valuation(per: float, pbr: float, industry_per_avg: float = 23.87) -> Tuple[int, str]:
    """Score valuation based on PER and PBR levels."""
    # PER scoring
    if per < 10:
        per_score = 95
    elif per < 15:
        per_score = 85
    elif per < 20:
        per_score = 75
    elif per < 30:
        per_score = 60
    elif per < 40:
        per_score = 40
    else:
        # PER > 40: very expensive unless growth justifies (handled elsewhere)
        per_score = 20

    # PBR scoring
    if pbr < 0.8:
        pbr_score = 95
    elif pbr < 1.2:
        pbr_score = 85
    elif pbr < 2.0:
        pbr_score = 75
    elif pbr < 3.0:
        pbr_score = 60
    elif pbr < 5.0:
        pbr_score = 40
    else:
        pbr_score = 20

    # Average
    final_score = (per_score + pbr_score) // 2

    reasoning = f"PER {per:.1f}x (score {per_score}), PBR {pbr:.1f}x (score {pbr_score})"
    return final_score, reasoning

def score_profitability(roe: float, operating_margin: float) -> Tuple[int, str]:
    """Score profitability based on ROE and operating margin."""
    # ROE scoring
    if roe > 20:
        roe_score = 95
    elif roe > 15:
        roe_score = 85
    elif roe > 10:
        roe_score = 75
    elif roe > 5:
        roe_score = 60
    elif roe > 3:
        roe_score = 40
    else:
        roe_score = 20

    # Operating margin scoring
    if operating_margin > 20:
        margin_score = 95
    elif operating_margin > 15:
        margin_score = 85
    elif operating_margin > 10:
        margin_score = 75
    elif operating_margin > 5:
        margin_score = 60
    elif operating_margin > 0:
        margin_score = 40
    else:
        margin_score = 20

    final_score = (roe_score + margin_score) // 2
    reasoning = f"ROE {roe:.1f}% (score {roe_score}), OpMargin {operating_margin:.1f}% (score {margin_score})"
    return final_score, reasoning

def score_growth(revenue_growth: float) -> Tuple[int, str]:
    """Score growth based on revenue growth rate."""
    if revenue_growth > 30:
        score = 95
    elif revenue_growth > 20:
        score = 85
    elif revenue_growth > 10:
        score = 75
    elif revenue_growth > 5:
        score = 60
    elif revenue_growth > 0:
        score = 40
    else:
        score = 20

    return score, f"Revenue growth {revenue_growth:.1f}%"

def score_stability(debt_ratio: float, industry_avg: float = 80) -> Tuple[int, str]:
    """Score stability based on debt ratio."""
    # Adjust for industry (semis typically have higher debt than software)
    # But only slight adjustment: max +10 if debt_ratio is reasonable for industry
    if debt_ratio < 30:
        score = 95
    elif debt_ratio < 50:
        score = 85
    elif debt_ratio < 80:
        score = 75
    elif debt_ratio < 100:
        score = 60
    elif debt_ratio < 150:
        score = 40
    else:
        score = 20

    return score, f"Debt ratio {debt_ratio:.1f}%"

def score_news(news_summary: str) -> int:
    """
    Score news and outlook based on qualitative assessment.
    This is a simplified heuristic - in practice would use NLP.
    """
    news_lower = news_summary.lower()

    # Positive keywords
    positive_words = [
        'hbm4', '공급', '기대', 'ai', '수요', '증가', '성장', '호조',
        '상승', '매수', '목표가', '상향', '계약', '협력', '승인',
        'tailwind', 'bullish', 'upgrade', 'beat', 'expansion'
    ]

    # Negative keywords
    negative_words = [
        '매도', '하락', '부진', '위기', '소송', '규제', '하향',
        ' cuts', 'loss', 'lawsuit', 'downgrade', 'bearish', 'headwind',
        'foreign selling', '외국인 매도'
    ]

    positive_count = sum(1 for word in positive_words if word in news_lower)
    negative_count = sum(1 for word in negative_words if word in news_lower)

    # Base score
    if positive_count > negative_count + 2:
        score = 80  # Strong positive
    elif positive_count > negative_count:
        score = 70  # Moderate positive
    elif positive_count == negative_count:
        score = 50  # Neutral
    elif negative_count > positive_count + 2:
        score = 30  # Strong negative
    else:
        score = 40  # Moderate negative

    # Cap hype: if many positive words but no concrete numbers, cap at 60
    # Simple heuristic: if score > 60 but no percentage/numbers in summary, reduce
    if score > 60 and not any(c.isdigit() for c in news_summary):
        score = 60

    return score

def score_chart(chart_description: str) -> int:
    """
    Score technical/chart conditions for timing.
    """
    desc_lower = chart_description.lower()

    # Uptrend keywords
    uptrend_words = ['상승', 'uptrend', ' rising', 'higher high', '상승추세', '강세']
    # Downtrend keywords
    downtrend_words = ['하락', 'downtrend', ' falling', 'lower low', '하락추세', '약세']
    # Consolidation/sideways
    sideways_words = ['조정', '횡보', 'consolidation', 'sideways', '밴드', '봉']

    # High52w reference
    near_high = any(word in desc_lower for word in ['최고', '52주 최고', 'high', ' resistance'])
    near_low = any(word in desc_lower for word in ['최저', '52주 최저', 'low', ' support'])

    # Determine trend score
    if any(word in desc_lower for word in uptrend_words):
        trend_score = 70
    elif any(word in desc_lower for word in downtrend_words):
        trend_score = 30
    else:
        trend_score = 50  # Neutral/unknown

    # Adjust for position
    if near_high and trend_score > 50:
        trend_score -= 10  # Extended from high
    if near_low and trend_score < 50:
        trend_score += 10  # Potential bounce

    # Ensure within range
    trend_score = max(0, min(100, trend_score))

    return trend_score

def analyze_stock(data: Dict[str, Any]) -> Dict[str, Any]:
    """
    Main analysis function implementing the framework.
    """
    financials = data['financials']

    # Step 1: Financial scores
    valuation_score, val_reason = score_valuation(
        financials['PER'],
        financials['PBR']
    )
    profitability_score, profit_reason = score_profitability(
        financials['ROE'],
        financials['operating_margin']
    )
    growth_score, growth_reason = score_growth(
        financials['revenue_growth']
    )
    stability_score, stabil_reason = score_stability(
        financials['debt_ratio']
    )

    # Calculate FinancialScore
    financial_score = (
        valuation_score * 0.30 +
        profitability_score * 0.30 +
        growth_score * 0.25 +
        stability_score * 0.15
    )

    # Special rule: if both Profitability and Growth below 30, cap FinancialScore at 50
    if profitability_score < 30 and growth_score < 30:
        financial_score = min(financial_score, 50)

    # Step 2: News score
    news_score = score_news(data['news_summary'])

    # Step 3: Chart score
    chart_score = score_chart(data['chart_description'])

    # Final score
    final_score = (
        financial_score * 0.50 +
        news_score * 0.25 +
        chart_score * 0.25
    )

    # Verdict
    if final_score >= 80:
        verdict = "BUY"
    elif final_score >= 65:
        verdict = "BUY_LEAN"
    elif final_score >= 45:
        verdict = "HOLD"
    else:
        verdict = "AVOID"

    # Reasoning summary (one paragraph, priority order)
    reasoning = f"""The stock scores {final_score:.1f}/100 ({verdict}). Financial strength is { 'strong' if financial_score >= 70 else 'moderate' if financial_score >= 50 else 'weak' } (FinancialScore {financial_score:.1f}). { 'Profitability is excellent (ROE {financials["ROE"]:.1f}%) with solid growth (Revenue {financials['revenue_growth']:.1f}%).' if profitability_score >= 70 and growth_score >= 70 else 'Profitability and growth are mixed.' } Valuation appears { 'reasonable' if valuation_score >= 60 else 'expensive' } (PER {financials['PER']:.1f}x). News sentiment is { 'positive' if news_score >= 60 else 'neutral' if news_score >= 40 else 'negative' } (Score {news_score}). Technical chart shows { 'favorable timing' if chart_score >= 60 else 'neutral' if chart_score >= 40 else 'unfavorable timing' } (Score {chart_score})."""

    # Build output
    result = {
        "ticker": data['ticker'],
        "company_name": data.get('company_name', ''),
        "financial_breakdown": {
            "ValuationScore": round(valuation_score),
            "ProfitabilityScore": round(profitability_score),
            "GrowthScore": round(growth_score),
            "StabilityScore": round(stability_score),
            "FinancialScore": round(financial_score, 1)
        },
        "NewsScore": round(news_score),
        "ChartScore": round(chart_score),
        "Final Investment Attractiveness Score": round(final_score, 1),
        "Verdict": verdict,
        "Reasoning Summary": reasoning.strip(),
        "detailed_reasoning": {
            "valuation": val_reason,
            "profitability": profit_reason,
            "growth": growth_reason,
            "stability": stabil_reason
        }
    }

    return result

def format_output(result: Dict[str, Any]) -> str:
    """Format result as plain text report."""
    lines = []
    lines.append("1. Financial Breakdown")
    fb = result['financial_breakdown']
    lines.append(f"- ValuationScore: {fb['ValuationScore']}")
    lines.append(f"- ProfitabilityScore: {fb['ProfitabilityScore']}")
    lines.append(f"- GrowthScore: {fb['GrowthScore']}")
    lines.append(f"- StabilityScore: {fb['StabilityScore']}")
    lines.append(f"- FinancialScore: {fb['FinancialScore']}")
    lines.append("")
    lines.append(f"2. NewsScore: {result['NewsScore']}")
    lines.append("")
    lines.append(f"3. ChartScore: {result['ChartScore']}")
    lines.append("")
    lines.append(f"4. Final Investment Attractiveness Score: {result['Final Investment Attractiveness Score']} / 100")
    lines.append("")
    lines.append(f"5. Verdict: {result['Verdict']}")
    lines.append("")
    lines.append("6. Reasoning Summary:")
    lines.append(result['Reasoning Summary'])

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser(description='Equity Analyst - Stock Analysis Score Calculator')
    parser.add_argument('--input', required=True, help='Input JSON file with stock data')
    parser.add_argument('--output', required=False, help='Output file (defaults to stdout)')

    args = parser.parse_args()

    # Load input data
    with open(args.input, 'r', encoding='utf-8') as f:
        data = json.load(f)

    # Analyze
    result = analyze_stock(data)

    # Format output
    output_text = format_output(result)

    # Output
    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(output_text)
    else:
        print(output_text)

    # Also return JSON for programmatic use
    return result

if __name__ == '__main__':
    result = main()
    # Print JSON to stderr for debugging/capturing
    import json
    print("\n--- JSON OUTPUT ---", file=sys.stderr)
    print(json.dumps(result, ensure_ascii=False, indent=2), file=sys.stderr)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)