Glowb Dev Docs
정책/비즈니스 로직

제출물 지표 수집

최종 제출물(인스타 릴스·피드, 틱톡) 링크의 조회수·좋아요 등을 주기적으로 수집하는 흐름, 파이썬 API 계약, 캠페인 인사이트 API

제출물 지표 수집

크리에이터가 최종 제출한 게시물 링크의 지표(조회수·좋아요·댓글·공유·저장)를 캠페인 종료 후 일정 기간 동안 시계열로 쌓습니다.

역할 분담

담당내용
언제 · 누구를 볼지스프링 (SubmissionMetricCollectService)큐 테이블 TB_SUBMISSION_METRIC_COLLECT + 5분 폴러
실제 수집파이썬 (POST /api/submission-metrics/collect)무상태. 호출 1회 = RapidAPI 1콜 = post_metrics 1행
조회파이썬 GET 3종최신값 · 시계열 · 캠페인 단위 (단건 확인·디버깅용)
화면(캠페인 인사이트)스프링 (CampaignInsightService)크롤 PG 를 직접 읽어 전체 성과 + 크리에이터별 지표

자동 검수(FinalSubmissionAutoCheckService)와 같은 모양입니다. 큐 + 폴러인 이유도 같습니다 — 대상 전부를 한 루프로 돌면 단일 스케줄러 스레드가 오래 막히고 블루그린 배포가 루프를 중간에 죽입니다.


등록 시점

FinalSubmissionSubmittedEvent(최초 제출·재제출) 를 커밋 후 비동기로 받아 큐 행을 만듭니다. 관리자 검수 통과(approveReview)는 소명 흐름에서만 세팅되므로 트리거로 쓰지 않습니다.

등록 조건 (하나라도 아니면 생략):

  1. yml submission-metrics.collect.enabled AND Flagsmith submission_metrics_collect_enabled
  2. contentLink 있음
  3. Collab.snsContentFormat 이 아래 표에 있음
SnsContentFormatplatformpost_type
INSTAGRAM_REELSINSTAGRAMREEL
INSTAGRAM_FEEDINSTAGRAMFEED
TIKTOK_SHORTSTIKTOKVIDEO
그 외 / null등록 안 함

application_id 당 한 행. 재제출이면 링크·포맷을 갱신하고 next_collect_at = now, consecutive_failures = 0, STOPPED 였어도 ACTIVE 로 되살립니다.


수집 주기

기준점(anchor) = TB_COLLAB.campaign_completed_at(관리자가 캠페인 완료 처리한 시각) 이 있으면 그것, 없으면 TB_FINAL_SUBMISSION.submitted_at. 개월(months) = TB_FINAL_SUBMISSION.secondary_usage_months(제출 시 스냅샷, -1 영구), 없으면 TB_COLLAB.secondary_usage_months.

수집이 한 번 끝날 때마다 그 시점 기준으로 다음 간격을 정합니다 (매번 다시 읽으므로 캠페인 완료 처리가 나중에 돼도 반영됩니다).

조건 (위에서부터 첫 일치)다음 간격
now < anchor + 1개월6시간
months == null중단
months == -124시간 (무기한)
now < anchor + max(months, 1)개월24시간
그 외중단
  • 연속 실패 5회부터 간격을 24시간으로 늦춥니다. 중단은 안 합니다 (비공개였다 다시 열리는 경우).
  • 기준점·제출일이 둘 다 없으면 판단 근거가 없어 중단합니다.
  • 중단 = status = STOPPED, next_collect_at = NULL. 재제출 이벤트가 오면 다시 ACTIVE.

폴러

  • @Scheduled(cron = "0 */5 * * * *"), 한 번에 30건(submission-metrics.collect.batch-size), next_collect_at 오름차순.
  • 건별로 트랜잭션 없이 파이썬을 부르고 save() — 파이썬 호출(최대 30초) 동안 커넥션을 물지 않습니다.
  • 배포 중 죽어도 next_collect_at 이 안 바뀐 행은 다음 5분에 다시 집힙니다.

결과 반영

파이썬 응답last_status실패 카운트다음
200, status = OK / PRIVATE그대로0주기표
200, status = REMOVED / ERROR그대로+1주기표 (5회↑ 24h)
400 / 422REJECTED+1STOPPED — 링크가 게시물 URL 이 아님, 재제출이 와야 풀림
그 외 4xx · 5xx · 타임아웃HTTP_ERROR+1주기표 — 아무것도 기록 안 됐으니 재시도

파이썬 API 계약

POST /api/submission-metrics/collect

{
  "application_id": 123,
  "collab_no": 456,
  "platform": "INSTAGRAM",
  "post_type": "REEL",
  "post_url": "https://www.instagram.com/reel/XXXX/"
}

응답 200 (게시물 상태는 결과이지 요청 오류가 아닙니다):

{
  "application_id": 123,
  "post_id": "XXXX",
  "status": "OK",
  "error": null,
  "crawled_at": "2026-10-01T03:00:00+00:00",
  "metrics": { "views": 1200, "likes": 80, "comments": 5, "shares": null, "saves": null }
}
  • status: OK / REMOVED / PRIVATE / ERROR
  • metrics 와 crawled_at 은 지표를 저장했을 때만 값이 있습니다. REMOVED·ERROR 는 항상 null, PRIVATE 도 지표가 없으면 null. status 가 아니라 metrics != null 로 분기하세요.
  • 인스타는 shares / saves 없음(null), 좋아요 숨김이면 likes null.
  • 틱톡 REMOVED 의 error 두 가지: post/detail 응답 없음 (삭제 또는 API 오류) — 삭제와 API 오류를 구분 못 함 / post/detail status=REMOVED (takeDown) — 틱톡이 명시적으로 내린 영상.
  • 400: URL 이 플랫폼 게시물 링크가 아님 / post_type 불일치 / 미지원 플랫폼. 단축 링크(vm.tiktok.com/…, tiktok.com/t/…, instagram.com/share/…)는 게시물 id 를 못 뽑아 400.
  • 422: 바디 형식 오류. 400 과 같이 다룹니다.
  • 5xx / 타임아웃: 파이썬 DB 장애 등. 아무것도 기록되지 않았습니다.
  • 동기, 보통 1~3초. 스프링 클라이언트는 read timeout 30초 (submissionMetricsRestTemplate).

조회

  • GET /api/submission-metrics/{application_id} → 매핑 + latest (최신 지표 1건 또는 null). 404 = 수집된 적 없음.
  • GET /api/submission-metrics/{application_id}/history?from=&to= → items[] 시계열 오름차순. from/to 는 오프셋 포함 ISO8601.
  • GET /api/submission-metrics/collab/{collab_no} → 캠페인의 제출물별 최신값 목록.

GET 응답 필드: application_id, collab_no, platform, post_type, post_url, post_id, last_collected_at, last_status, last_error, latest — latest 는 {crawled_at, views, likes, comments, shares, saves} 또는 null.

화면(기업·어드민 캠페인 인사이트)이 쓰는 것은 아래 스프링 API 다. 파이썬 GET 은 단건 확인·디버깅용이다.


캠페인 인사이트 API (스프링)

기업·어드민 캠페인 화면의 "캠페인 인사이트" 섹션 — 전체 성과 + 크리에이터별 지표.

경로권한
기업GET /ai/collab/{campaignNo}/insightBUSINESS · AGENCY_CLIENT · ADMIN + 캠페인 소유권 검증(남의 캠페인이면 403 AUTH_002)
어드민GET /ai/admin/campaigns/{campaignNo}/insightADMIN

파이썬을 거치지 않는다. 스프링이 크롤 PG 의 submission_post + post_metrics 최신 1행을 직접 읽고(postgresJdbcTemplate), 이름·프로필은 MariaDB 에서 붙인다.

응답

{
  "campaignNo": 456,
  "summary": { "views": 9999999, "likes": 9999999, "comments": 9999999, "engagementRate": 5.2 },
  "creators": [
    {
      "applicationId": 123,
      "influenceNo": 11,
      "name": "제이제이이지",
      "profileImage": "https://.../profile.jpg",
      "platform": "INSTAGRAM",
      "postType": "REEL",
      "postUrl": "https://www.instagram.com/reel/XXXX/",
      "views": 9999999,
      "likes": 9999999,
      "comments": 9999999,
      "engagementRate": 3.5,
      "lastStatus": "OK",
      "collectedAt": "2026-10-01T03:00:00"
    }
  ]
}

참여율

식
크리에이터(좋아요 + 댓글) / 조회수 × 100, 소수 1자리
전체Σ(좋아요+댓글) / Σ조회수 × 100 — 조회수 가중
  • 조회수가 없거나 0 이면 그 크리에이터의 engagementRate 는 null, 전체 참여율 계산에서도 빠진다(피드 이미지, 좋아요·조회수 숨김 등). 다만 그 크리에이터의 좋아요·댓글은 전체 합계에는 들어간다.
  • 조회수 있는 크리에이터가 하나도 없으면 summary.engagementRate 는 null.
  • 대시보드의 보정 참여율(influencer_profile.adjusted_engagement_rate)과 정의의 핵심은 같지만, 플랫폼 prior 보정은 넣지 않는다 — 그 보정은 "게시물 몇 개 안 본 계정을 과대평가하지 말자"는 용도라 제출물 1건짜리 캠페인 성과에는 맞지 않는다.

그 밖의 동작

  • 정렬: 조회수 내림차순, 없는 크리에이터는 뒤.
  • 아직 수집 전인 크리에이터도 목록에 포함되고 지표·lastStatus·collectedAt 이 전부 null 이다(수집 대기 중임을 보이기 위해).
  • lastStatus 는 마지막 수집 결과(OK/REMOVED/PRIVATE/ERROR/HTTP_ERROR/REJECTED). REMOVED 는 게시물이 내려간 것이고, 그 경우 지표는 마지막으로 저장된 값이다.
  • 없는 캠페인이면 404 가 아니라 빈 인사이트(summary 0/null, creators: []) — 인사이트 섹션이 상세 화면을 깨면 안 된다.
  • collectedAt 은 KST 기준 LocalDateTime.

운영

  • DDL 은 수동: Flyway 가 꺼져 있어 V202609221200__add_submission_metric_collect.sql 을 test/prod DB 에 직접 실행합니다. 플래그를 켜기 전에 테이블 존재를 확인하세요.
  • 끄는 법: Flagsmith submission_metrics_collect_enabled OFF → 등록·폴링 즉시 중단(배포 없음). yml 킬스위치 SUBMISSION_METRICS_COLLECT_ENABLED=false 도 있습니다(배포 필요). scheduler.enabled 가 true 여야 폴러가 돕니다.
  • 소급: 배포 후 docs/superpowers/ops/2026-09-22-submission-metric-collect-backfill.sql 한 번 (기준점 13개월 이내만).
  • 상태 확인: SELECT last_status, COUNT(*) FROM TB_SUBMISSION_METRIC_COLLECT GROUP BY 1. REJECTED 는 링크가 단축 URL 인 건 — 운영이 정규 URL 로 재제출을 유도합니다.
  • 캠페인 삭제: TB_SUBMISSION_METRIC_COLLECT 는 TB_CAMPAIGN_APPLICATION FK(NO ACTION) 라 CampaignDeletionDao 가 명시적으로 지웁니다.
  • 예상 콜: 활성 500건 × 4회/일 ≈ 2,000 RapidAPI 콜/일.
  • 스케일 승격: 수천 건·RapidAPI 429 가 보이면 폴러의 직접 호출을 Cloud Tasks 등록으로 바꿉니다. 파이썬은 안 바뀝니다.

관련 설계: docs/superpowers/specs/2026-09-22-submission-metrics-collect-design.md, glowb(python) docs/superpowers/specs/2026-09-21-submission-metrics-design.md.

On this page