lotto lab 통계 수치 정상화
This commit is contained in:
@@ -34,8 +34,8 @@ export function recommend(params) {
|
|||||||
return apiGet(`/api/lotto/recommend?${qs.toString()}`);
|
return apiGet(`/api/lotto/recommend?${qs.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHistory(limit = 30) {
|
export function getHistory(limit = 30, offset = 0) {
|
||||||
return apiGet(`/api/history?limit=${limit}`);
|
return apiGet(`/api/history?limit=${limit}&offset=${offset}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteHistory(id) {
|
export function deleteHistory(id) {
|
||||||
|
|||||||
@@ -63,6 +63,55 @@ const buildFrequencySeries = (frequency) => {
|
|||||||
return { series, max };
|
return { series, max };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildMetricsFromCounts = (counts) => {
|
||||||
|
if (!counts?.length) return null;
|
||||||
|
const total = counts.reduce((acc, value) => acc + value, 0);
|
||||||
|
if (!total) return null;
|
||||||
|
const min = Math.min(...counts);
|
||||||
|
const max = Math.max(...counts);
|
||||||
|
const range = max - min;
|
||||||
|
const odd = counts.reduce(
|
||||||
|
(acc, value, idx) => (idx % 2 === 0 ? acc + value : acc),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const even = total - odd;
|
||||||
|
const buckets = {
|
||||||
|
'1-10': counts.slice(0, 10).reduce((a, b) => a + b, 0),
|
||||||
|
'11-20': counts.slice(10, 20).reduce((a, b) => a + b, 0),
|
||||||
|
'21-30': counts.slice(20, 30).reduce((a, b) => a + b, 0),
|
||||||
|
'31-40': counts.slice(30, 40).reduce((a, b) => a + b, 0),
|
||||||
|
'41-45': counts.slice(40, 45).reduce((a, b) => a + b, 0),
|
||||||
|
};
|
||||||
|
return { sum: total, min, max, range, odd, even, buckets };
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetricsFromFrequency = (frequency) => {
|
||||||
|
if (!frequency?.length) return null;
|
||||||
|
const counts = Array.from({ length: 45 }, () => 0);
|
||||||
|
frequency.forEach((item) => {
|
||||||
|
const number = Number(item?.number);
|
||||||
|
const count = Number(item?.count) || 0;
|
||||||
|
if (number >= 1 && number <= 45) {
|
||||||
|
counts[number - 1] = count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return buildMetricsFromCounts(counts);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMetricsFromHistory = (items) => {
|
||||||
|
if (!items?.length) return null;
|
||||||
|
const counts = Array.from({ length: 45 }, () => 0);
|
||||||
|
items.forEach((item) => {
|
||||||
|
(item?.numbers ?? []).forEach((value) => {
|
||||||
|
const number = Number(value);
|
||||||
|
if (number >= 1 && number <= 45) {
|
||||||
|
counts[number - 1] += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return buildMetricsFromCounts(counts);
|
||||||
|
};
|
||||||
|
|
||||||
const toBucketEntries = (metrics) => {
|
const toBucketEntries = (metrics) => {
|
||||||
if (!metrics?.buckets) return [];
|
if (!metrics?.buckets) return [];
|
||||||
const entries = Object.entries(metrics.buckets);
|
const entries = Object.entries(metrics.buckets);
|
||||||
@@ -95,19 +144,21 @@ const MetricBlock = ({ title, metrics }) => {
|
|||||||
<div className="lotto-metrics">
|
<div className="lotto-metrics">
|
||||||
<div className="lotto-metrics__head">
|
<div className="lotto-metrics__head">
|
||||||
<p className="lotto-metrics__title">{title}</p>
|
<p className="lotto-metrics__title">{title}</p>
|
||||||
<span className="lotto-metrics__sum">합계 {metrics.sum ?? '-'}</span>
|
<span className="lotto-metrics__sum">
|
||||||
|
총 출현 횟수 {metrics.sum ?? '-'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="lotto-metric-cards">
|
<div className="lotto-metric-cards">
|
||||||
<div className="lotto-metric-card">
|
<div className="lotto-metric-card">
|
||||||
<p className="lotto-metric-card__label">최솟값</p>
|
<p className="lotto-metric-card__label">최소 출현</p>
|
||||||
<p className="lotto-metric-card__value">{metrics.min ?? '-'}</p>
|
<p className="lotto-metric-card__value">{metrics.min ?? '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="lotto-metric-card">
|
<div className="lotto-metric-card">
|
||||||
<p className="lotto-metric-card__label">최댓값</p>
|
<p className="lotto-metric-card__label">최대 출현</p>
|
||||||
<p className="lotto-metric-card__value">{metrics.max ?? '-'}</p>
|
<p className="lotto-metric-card__value">{metrics.max ?? '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="lotto-metric-card">
|
<div className="lotto-metric-card">
|
||||||
<p className="lotto-metric-card__label">범위</p>
|
<p className="lotto-metric-card__label">출현 편차</p>
|
||||||
<p className="lotto-metric-card__value">{metrics.range ?? '-'}</p>
|
<p className="lotto-metric-card__value">{metrics.range ?? '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,6 +273,14 @@ export default function Functions() {
|
|||||||
history: false,
|
history: false,
|
||||||
});
|
});
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const overallMetrics = useMemo(
|
||||||
|
() => buildMetricsFromFrequency(stats?.frequency),
|
||||||
|
[stats]
|
||||||
|
);
|
||||||
|
const historyMetrics = useMemo(
|
||||||
|
() => buildMetricsFromHistory(history),
|
||||||
|
[history]
|
||||||
|
);
|
||||||
|
|
||||||
const refreshLatest = async () => {
|
const refreshLatest = async () => {
|
||||||
setLoading((s) => ({ ...s, latest: true }));
|
setLoading((s) => ({ ...s, latest: true }));
|
||||||
@@ -240,8 +299,17 @@ export default function Functions() {
|
|||||||
setLoading((s) => ({ ...s, history: true }));
|
setLoading((s) => ({ ...s, history: true }));
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const data = await getHistory(30);
|
const limit = 100;
|
||||||
setHistory(data.items ?? []);
|
let offset = 0;
|
||||||
|
const allItems = [];
|
||||||
|
while (true) {
|
||||||
|
const data = await getHistory(limit, offset);
|
||||||
|
const items = data.items ?? [];
|
||||||
|
allItems.push(...items);
|
||||||
|
if (items.length < limit) break;
|
||||||
|
offset += limit;
|
||||||
|
}
|
||||||
|
setHistory(allItems);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e?.message ?? String(e));
|
setError(e?.message ?? String(e));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -382,8 +450,11 @@ export default function Functions() {
|
|||||||
<p className="lotto-bonus">
|
<p className="lotto-bonus">
|
||||||
보너스 <strong>{latest.bonus}</strong>
|
보너스 <strong>{latest.bonus}</strong>
|
||||||
</p>
|
</p>
|
||||||
{latest.metrics ? (
|
{overallMetrics ? (
|
||||||
<MetricBlock title="당첨 통계" metrics={latest.metrics} />
|
<MetricBlock
|
||||||
|
title="당첨 통계 (전체 회차)"
|
||||||
|
metrics={overallMetrics}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -502,17 +573,12 @@ export default function Functions() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{result.numbers ? <NumberRow nums={result.numbers} /> : null}
|
{result.numbers ? <NumberRow nums={result.numbers} /> : null}
|
||||||
{result.metrics || latest?.metrics ? (
|
{historyMetrics ? (
|
||||||
<div className="lotto-compare">
|
<div className="lotto-compare">
|
||||||
{result.metrics ? (
|
|
||||||
<MetricBlock title="추천 통계" metrics={result.metrics} />
|
|
||||||
) : null}
|
|
||||||
{latest?.metrics ? (
|
|
||||||
<MetricBlock
|
<MetricBlock
|
||||||
title="당첨 통계 (최신 회차)"
|
title="추천 통계 (히스토리)"
|
||||||
metrics={latest.metrics}
|
metrics={historyMetrics}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{Array.isArray(result.items) && result.items.length ? (
|
{Array.isArray(result.items) && result.items.length ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user