[add] 최종분석

This commit is contained in:
kimheesu 2025-06-13 17:28:37 +09:00
parent 15f1ea64fd
commit 6f1856ada5
1 changed files with 19 additions and 134 deletions

View File

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import { Card, Tabs, Typography, Table, Tag, Row, Col, Spin, Alert } from 'antd';
import React from 'react';
import { Card, Typography } from 'antd';
import styled from 'styled-components';
const { Title, Paragraph } = Typography;
const { Title } = Typography;
const ReportWrapper = styled.div`
padding: ${props => props.theme.spacing.md};
@ -16,144 +16,29 @@ const SectionCard = styled(Card)`
& .ant-card-head {
background-color: ${props => props.theme.colors.backgroundSecondary};
}
& .ant-card-body {
padding-top: 16px;
padding-bottom: 16px;
}
`;
const ReportDisplay = ({ reportData }) => {
const [parsedData, setParsedData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (reportData) {
setLoading(true);
setError(null);
try {
const data = JSON.parse(reportData);
if (data && data.reports) {
setParsedData(data);
} else {
throw new Error("Invalid report structure received.");
}
} catch (e) {
console.error("Failed to parse report JSON:", e);
setError({
message: "보고서 파싱에 실패했습니다.",
originalData: reportData
});
} finally {
setLoading(false);
}
}
}, [reportData]);
if (loading) {
return <Spin tip="보고서를 로딩 중입니다..." size="large" style={{ display: 'block', marginTop: '50px' }} />;
}
if (error) {
return (
<SectionCard>
<Alert
message="보고서 파싱 오류"
description={error.message}
type="error"
showIcon
/>
<pre style={{ whiteSpace: 'pre-wrap', backgroundColor: '#f0f2f5', padding: '10px', borderRadius: '4px', marginTop: '16px' }}>
{error.originalData}
</pre>
</SectionCard>
);
}
if (!parsedData) {
return <Spin tip="보고서 데이터를 기다리는 중..." />;
}
const { company_info, reports, final_decision } = parsedData;
const renderMarketReport = (data) => {
const { price_summary, indicator_analysis, overall_conclusion } = data;
const indicatorColumns = [
{ title: '지표', dataIndex: 'indicator', key: 'indicator', width: '20%' },
{ title: '값', dataIndex: 'value', key: 'value', width: '15%' },
{ title: '해석', dataIndex: 'interpretation', key: 'interpretation' },
];
return (
<>
<SectionCard title="가격 동향">{price_summary}</SectionCard>
<SectionCard title="기술적 지표 분석">
<Table columns={indicatorColumns} dataSource={indicator_analysis} pagination={false} size="small" rowKey="indicator" />
</SectionCard>
<SectionCard title="결론">{overall_conclusion}</SectionCard>
</>
);
};
const renderFundamentalsReport = (data) => {
const { company_overview, financial_performance, stock_market_info, analyst_forecasts, insider_sentiment, summary } = data;
const fundamentalsColumns = [
{ title: '메트릭', dataIndex: 'metric', key: 'metric', width: '40%' },
{ title: '값', dataIndex: 'value', key: 'value', width: '60%' },
];
return (
<>
<SectionCard title="회사 개요">{company_overview}</SectionCard>
<SectionCard title="재무 성과"><Table columns={fundamentalsColumns} dataSource={financial_performance} pagination={false} size="small" rowKey="metric" /></SectionCard>
<SectionCard title="주식 시장 정보"><Table columns={fundamentalsColumns} dataSource={stock_market_info} pagination={false} size="small" rowKey="metric" /></SectionCard>
<SectionCard title="애널리스트 전망"><Table columns={fundamentalsColumns} dataSource={analyst_forecasts} pagination={false} size="small" rowKey="metric" /></SectionCard>
<SectionCard title="내부자 정서 및 거래">{insider_sentiment}</SectionCard>
<SectionCard title="요약">{summary}</SectionCard>
</>
);
};
const renderGenericReport = (data) => (
<SectionCard>
<pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>
{JSON.stringify(data, null, 2)}
</pre>
</SectionCard>
);
const reportRenderers = {
'market': renderMarketReport,
'fundamentals': renderFundamentalsReport,
// Add other specific renderers here
};
const tabItems = Object.entries(reports)
.filter(([key, data]) => data && !data.error)
.map(([key, data]) => {
const renderer = reportRenderers[key] || renderGenericReport;
// Capitalize first letter for label
const label = key.charAt(0).toUpperCase() + key.slice(1).replace('_', ' ');
return {
key: key,
label: `${label} 리포트`,
children: renderer(data),
};
});
const getTagColor = (proposal) => {
switch (proposal?.toUpperCase()) {
case 'BUY': return 'green';
case 'SELL': return 'red';
case 'HOLD': return 'blue';
default: return 'default';
}
if (!reportData) {
return null;
}
return (
<ReportWrapper>
<Title level={2}>최종 분석 보고서: {company_info.ticker}</Title>
<Paragraph type="secondary">분석 기준일: {company_info.analysis_date}</Paragraph>
<Row align="middle" gutter={16} style={{ marginBottom: 24 }}>
<Col><Title level={4} style={{ margin: 0 }}>최종 거래 제안:</Title></Col>
<Col><Tag color={getTagColor(final_decision.final_proposal)} style={{ fontSize: '18px', padding: '6px 12px' }}>{final_decision.final_proposal}</Tag></Col>
</Row>
<Tabs defaultActiveKey={tabItems[0]?.key} items={tabItems} />
<SectionCard title="최종 분석 보고서 (원본)">
<pre style={{
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px',
lineHeight: '1.6'
}}>
{reportData}
</pre>
</SectionCard>
</ReportWrapper>
);
};