- 발행일
표준데이터 통계 대시보드를 "개수"에서 "유지관리 인사이트"로 재정의한 회고
표준데이터 통계 대시보드를 "개수"에서 "유지관리 인사이트"로 재정의한 회고
표준데이터 통계 대시보드가 있었지만, 보여주는 건 "총 몇 개, 상태별 몇 개" 정도였습니다. 숫자는 맞는데 "그래서 뭘 해야 하지?" 에 답을 못 주는 대시보드였어요. 이걸 유지관리 인사이트 중심으로 다시 정의한 작업을 정리합니다.
1. 왜 다시 짰나 — "개수"는 행동을 못 만든다
기존 카드 1은 이런 모양이었습니다.
# 변경 전: 총계 + 상태별 단순 분포
return JsonResponse({"data": {
"total": total,
"active": state_counts.get("ACT", 0),
"inactive": state_counts.get("DLT", 0),
"in_review": state_counts.get("SBM", 0) + ... ,
"breakdown": breakdown,
}})
active가 182개라는 숫자는, 관리자가 다음에 뭘 해야 하는지 알려주지 않았어요. 표준데이터 운영의 진짜 관심사는 "확정된 데이터 중 메타데이터까지 충실히 채워진 게 얼마나 되나", "최근에 손이 닿은 건 뭔가" 같은 것들이었습니다.
그래서 지표를 생애주기 + 완성도 로 재정의했어요.
2. 완성도를 어떻게 표현할 것인가
"메타데이터가 충실한가"를 화면에 올리는 방법이 여러 가지였습니다.
| 방법 | 장점 | 포기하는 것 | 판단 |
|---|---|---|---|
| ① 필드별 채움률을 각각 노출 | 정보량 최대. 무엇이 비었는지 정확 | 지표가 10개로 늘어남. "그래서 뭘 해야 하지?"라는 원래 문제로 회귀 | ✗ |
| ② 완성도를 0~100점으로 환산 | 한 숫자로 요약, 추이 비교 쉬움 | 가중치를 우리가 정해야 함. 제목이 30점이고 ASN.1이 40점인 근거를 댈 수 없고, 점수가 바뀌면 과거와 비교 불가 | ✗ |
| ③ 이진 판정 — 완성형이냐 아니냐 | 기준이 단순해 해석에 논쟁이 없음. 곧장 "덜 채워진 것 목록"으로 이어짐 | 얼마나 덜 찼는지는 안 보임 | ✓ 채택 |
②가 그럴듯해 보여서 오래 붙들었습니다. "완성도 85%" 같은 게 대시보드스럽기도 하고요. 접은 이유는 가중치의 근거가 없기 때문입니다. 제목이 없는 것과 ASN.1 노드가 없는 것 중 뭐가 더 나쁜지 우리가 정할 문제가 아니었어요. 근거 없는 숫자는 정확해 보이기까지 해서 더 위험합니다.
③은 정보를 잃지만 "이건 완성, 저건 미완성"이라는 판정이 곧바로 할 일 목록이 됩니다. 대시보드의 목적이 행동을 만드는 것이었으니 이 교환이 맞았어요.
3. 핵심 지표 — "완성형" 데이터 정의하기
가장 중요한 변화는 완성형(고품질) 이라는 개념을 도입한 거예요. 단순히 ACT(표준 확정) 상태인 것과, 메타데이터까지 충실한 것은 다릅니다.
# '완성형' 데이터: 등록 완료(ACT)이면서 필수 메타데이터가 모두 있는 경우
# (제목·설명·ASN.1 노드가 있는 경우로 정의)
completed_qs = (
Dataset.objects.filter(state="ACT")
.annotate(asn1_count=Count("asn1node"))
.filter(
Q(title__isnull=False) & ~Q(title="")
& Q(notes__isnull=False) & ~Q(notes="")
& Q(asn1_count__gt=0)
)
)
completed_count = completed_qs.count()
생애주기도 운영자 언어로 다시 묶었습니다.
| 지표 | 의미 | 산출 |
|---|---|---|
| 표준 확정 | 확정된 표준 | state='ACT' |
| 완성(고품질) | 확정 + 메타데이터 충실 | ACT & 제목·설명·ASN.1 노드 존재 |
| 진행 중 | 절차 진행 | SBM + ONG + HLD |
| 미사용/삭제 | 폐기 | DLT |
상태 코드(SBM/ONG/HLD/DLT)는 운영자에게 의미가 없어서, 표준 확정 / 절차 진행 중 / 미사용 같은 라벨로 묶어 보여줬어요.
4. 유지관리 카드 — 무엇을 손봐야 하는지
새로 만든 DatasetMaintenanceView는 "다음에 볼 것" 을 모았습니다. 최근 업데이트 항목, 속성이 풍부한(rich) 데이터셋, 항목이 많은 카탈로그 같은 거예요.
class DatasetMaintenanceView(TemplateView):
"""표준데이터 인사이트: 등록 현황, 최근 업데이트, 인기 항목."""
def get(self, request, *args, **kwargs):
limit = min(int(request.GET.get("limit", 20)), 50)
recent_before = timezone.now() - timedelta(days=30)
active_qs = Dataset.objects.filter(state="ACT")
summary = {
"active_total": active_qs.count(),
"total_tags": Tag.objects.count(),
...
}
요약 카드는 4종으로 개편했어요 — 공식 표준 / 상세 명세 / 준비 중 / 최근 30일. 그 아래에 카탈로그·풍부한 데이터셋·최근 업데이트 리스트를 붙여, 숫자에서 개별 항목으로 바로 들어갈 수 있게 했습니다.
5. 샘플 fallback 데이터 동기화
대시보드는 API가 비어도(또는 개발 환경) 화면이 그려지도록 샘플 fallback JSON을 둡니다. 지표를 바꿨으니 이 샘플도 같이 맞춰야 했어요. 안 맞추면 "실서버는 새 지표, 샘플은 옛 지표" 로 어긋납니다.
// sample-stats-maintenance.json — 새 지표에 맞춘 fallback
{ "summary": { "total_tags": 154, "total_definitions": 82,
"total_oids": 210, "avg_completeness": 85, "recently_updated": 12,
"active_total": 450 },
"top_catalogs": [ { "name": "itsk_common", "title": "ITS 공통 카탈로그", "count": 42 }, ... ],
"top_rich_datasets": [ { "name": "Signal_Status", "title": "교차로 신호 상태 정보", "count": 24, "unit": "속성" }, ... ] }
월별 추이 샘플도 상태별 분포 에서 단순 등록 추이(count) 로 형태를 바꿔, 프론트가 기대하는 새 계약과 일치시켰습니다. 샘플은 "계약의 예시"라서, 지표가 바뀌면 반드시 같이 움직여야 한다 는 걸 다시 확인했어요.
6. 한계
① "완성형" 정의가 자의적이고, 코드에 박혀 있습니다.
제목·설명·ASN.1 노드 세 가지로 정한 건 논의를 거친 결과지만 어디까지나 우리가 정한 기준이에요. 문제는 두 가지입니다.
- 운영자가 바꿀 수 없습니다. 기준을 조정하려면 배포가 필요해요. 앞서 정책 값을 constance로 빼서 화면에서 조정하게 만든 작업2026-06-13Open API 정책 토글의 캐시·만료 상태가 어긋나던 문제를 동기화한 회고관리자가 오퍼레이션을 토글했는데 TTL이 지나야 반영되던 캐시 갭, 그리고 만료된 토큰의 신청 상태가 "승인됨"으로 남던 stale 문제를 정리합니다. 캐시 즉시 무효화와 진입 시점 sync_expired로 "보이는 상태 = 실제 상태"를 맞춘 이야기예요.을 했으면서, 정작 이 기준은 상수로 두었습니다.
- 기준이 바뀌면 과거와 비교가 안 됩니다. "완성형이 지난달보다 늘었다"는 판단이 기준을 손댄 순간 무의미해져요. 정의를 바꿀 때 그 시점을 어딘가 남겨야 하는데 지금은 그 장치가 없습니다.
② fallback 샘플 자체가 어긋남을 감춥니다.
샘플 JSON은 API가 비어도 화면이 그려지게 해주는데, 바로 그 성질 때문에 실서버 응답과 샘플이 어긋나도 화면은 멀쩡해 보입니다. 지표를 바꾸면서 샘플을 같이 고친 건 사람이 기억해서 한 일이지, 구조가 강제한 게 아니에요.
샘플이 계약의 일부라면 계약이 지켜지는지 확인하는 장치도 있어야 합니다. 실제 응답 키와 샘플 키를 비교하는 테스트 한 줄이면 되는데, 지금은 없습니다. 앞서 provider 추상화 작업2026-06-10Open API를 mock으로 먼저 만들고 OrmDataProvider로 실데이터 전환을 준비한 회고Open API의 데이터 계층을 처음부터 provider 인터페이스로 추상화해 mock으로 화면·계약을 먼저 굳히고, 실데이터 전환은 OrmDataProvider 골격 + 한 줄 스위치로 분리한 과정을 정리합니다. 미완 활성화가 곧 비공개 데이터 유출이라 fail-closed로 설계한 이야기도 담았어요.에서도 같은 숙제를 남겼더라고요 — mock과 실구현의 형태를 눈으로만 대조하고 있다는 점에서 정확히 같은 빚입니다.
③ 완성도 계산이 매 요청마다 전체를 훑습니다.
annotate(Count("asn1node")) 후 필터·카운트라, 데이터가 늘면 대시보드 진입이 느려집니다. 지금 규모에서는 괜찮지만 캐시나 집계 테이블이 필요해질 지점이에요.
7. 교훈
- 대시보드는 "숫자"가 아니라 "행동"을 만들어야 합니다. 총 등록 수보다, 완성형이 얼마나 되고 무엇을 손봐야 하는지가 운영자에게 쓸모 있어요.
- 품질 지표는 명시적으로 정의하세요. "완성형 = ACT & 제목·설명·ASN.1 노드 존재"처럼 기준을 코드로 박아두면, 모두가 같은 정의로 숫자를 읽습니다.
- 상태 코드는 운영자 언어로 번역합니다. SBM/ONG/HLD 같은 내부 코드를 절차 진행 중 으로 묶어주면 화면이 이해됩니다.
- fallback 샘플은 계약의 일부입니다. 지표를 바꾸면 샘플 JSON도 같이 갱신해야 실서버와 어긋나지 않아요.
같은 데이터에서 어떤 질문을 던지느냐 로 대시보드의 쓸모가 갈렸습니다. "몇 개야?"에서 "뭘 손봐야 해?"로 질문을 바꾼 게, 이번 개편의 전부였어요.