에러코드 누적 순위 차트 축 수정 — 에러코드 Y축, 충전기별 스택

- 기존: 충전기 Y축 + 에러코드별 스택 (충전기 중심)
- 변경: 에러코드 Y축 + 충전기별 스택 (에러코드 순위 중심)
- 어떤 에러코드가 가장 많이 발생했는지 + 어떤 충전기에서 발생했는지 한눈에 확인

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
byun
2026-06-01 08:28:18 +09:00
parent 585cacfa13
commit b59569ca11
2 changed files with 63 additions and 55 deletions

View File

@@ -543,63 +543,77 @@ def stats_top_chargers(limit: int = 10):
@app.get("/api/stats/charger-error-codes")
def stats_charger_error_codes(charger_limit: int = 10, code_limit: int = 6):
"""충전기별 에러코드 누적 건수 Top N (에러코드 입력된 신고 기준)."""
def stats_charger_error_codes(code_limit: int = 10, charger_limit: int = 5):
"""에러코드 누적 건수 Top N (어느 충전기에서 발생했는지 함께 반환)."""
from database import SessionLocal
from sqlalchemy import text
from collections import defaultdict
db = SessionLocal()
try:
rows = db.execute(text("""
SELECT rep.charger_id,
COALESCE(c.station_name, rep.charger_id) AS station_name,
COALESCE(c.name, '') AS charger_name,
TRIM(rep.error_code) AS error_code,
COUNT(*) AS cnt
SELECT TRIM(rep.error_code) AS error_code,
rep.charger_id,
COALESCE(c.station_name, rep.charger_id) AS station_name,
COALESCE(c.name, '') AS charger_name,
COUNT(*) AS cnt
FROM reports rep
LEFT JOIN chargers c ON c.id = rep.charger_id
WHERE rep.error_code IS NOT NULL
AND TRIM(rep.error_code) != ''
GROUP BY rep.charger_id, c.station_name, c.name, TRIM(rep.error_code)
GROUP BY TRIM(rep.error_code), rep.charger_id, c.station_name, c.name
""")).fetchall()
if not rows:
return {"chargers": [], "error_codes": []}
return {"error_codes": [], "charger_labels": {}}
# error_code → {total, chargers: {cid: cnt}}
code_info = {}
# charger_id → label, total across all codes
charger_info = {}
code_totals = defaultdict(int)
for row in rows:
cid, sname, cname, ecode, cnt = row
for ecode, cid, sname, cname, cnt in rows:
cnt = int(cnt)
if cid not in charger_info:
charger_info[cid] = {"station_name": sname, "charger_name": cname,
"total": 0, "errors": {}}
charger_info[cid]["total"] += cnt
charger_info[cid]["errors"][ecode] = cnt
code_totals[ecode] += cnt
if ecode not in code_info:
code_info[ecode] = {"total": 0, "chargers": {}}
code_info[ecode]["total"] += cnt
code_info[ecode]["chargers"][cid] = cnt
top_chargers = sorted(charger_info.items(), key=lambda x: -x[1]["total"])[:charger_limit]
top_codes = [c for c, _ in sorted(code_totals.items(), key=lambda x: -x[1])[:code_limit]]
if cid not in charger_info:
label = sname + (f" ({cname})" if cname else "")
if len(label) > 20: label = label[:18] + ""
charger_info[cid] = {"label": label, "total": 0}
charger_info[cid]["total"] += cnt
# Top N error codes by total
top_codes = sorted(code_info.items(), key=lambda x: -x[1]["total"])[:code_limit]
# Top M chargers across all top codes
top_cids = [cid for cid, _ in
sorted(charger_info.items(), key=lambda x: -x[1]["total"])[:charger_limit]]
result = []
for cid, info in reversed(top_chargers): # 역순: 차트에서 1위가 위에
label = info["station_name"]
if info["charger_name"]:
label += f" ({info['charger_name']})"
if len(label) > 22:
label = label[:20] + ""
errors = info["errors"]
other = sum(cnt for code, cnt in errors.items() if code not in top_codes)
entry = {"charger_id": cid, "label": label, "total": info["total"]}
for code in top_codes:
entry[code] = errors.get(code, 0)
for ecode, info in reversed(top_codes): # 역순: 차트에서 1위가 위에
entry = {"error_code": ecode, "total": info["total"]}
other = 0
for cid, cnt in info["chargers"].items():
if cid in top_cids:
entry[cid] = cnt
else:
other += cnt
if other:
entry["기타"] = other
entry["__other__"] = other
result.append(entry)
has_other = any(r.get("기타", 0) > 0 for r in result)
all_codes = top_codes + (["기타"] if has_other else [])
return {"chargers": result, "error_codes": all_codes}
has_other = any(r.get("__other__", 0) > 0 for r in result)
charger_labels = {cid: charger_info[cid]["label"] for cid in top_cids}
if has_other:
charger_labels["__other__"] = "기타"
dataset_keys = top_cids + (["__other__"] if has_other else [])
return {
"error_codes": result, # 에러코드별 집계 (역순)
"charger_labels": charger_labels, # charger_id → 표시명
"dataset_keys": dataset_keys, # 차트 dataset 순서
}
finally:
db.close()