Skip to content

[Fix] TilSeeder charCount 실제 콘텐츠 길이 기반으로 계산#183

Merged
noeyoseel merged 2 commits into
mainfrom
fix/180-til-seeder-char-count
Jun 22, 2026
Merged

[Fix] TilSeeder charCount 실제 콘텐츠 길이 기반으로 계산#183
noeyoseel merged 2 commits into
mainfrom
fix/180-til-seeder-char-count

Conversation

@noeyoseel

@noeyoseel noeyoseel commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

User description

🔎 What

  • 하드코딩된 charCount 대신 TilContentLength.countVisibleCharacters()로
    실제 HTML 콘텐츠의 가시 텍스트 길이를 계산하여 XP 산정 및
    watering_log.content_length에 반영한다.

🔗 Issue

✅ 체크리스트

  • 브랜치 base가 적절한가요?
  • 제목이 이슈 제목과 동일한가요?
  • 최소 1명의 리뷰를 받았나요?

PR Type

Bug fix, Enhancement


Description

  • TilSeeder의 charCount 계산 로직 개선

  • HTML 콘텐츠의 실제 가시 텍스트 길이 반영

  • XP 및 watering_log.content_length 정확성 향상


Diagram Walkthrough

flowchart LR
  A["기존 TilSeeder (하드코딩된 charCount)"] --> B{charCount 계산 방식 변경};
  B -- "TilContentLength 유틸리티 사용" --> C["HTML 콘텐츠에서 가시 텍스트 길이 계산"];
  C --> D["saveTil 메서드에서 charCount 및 XP 동적 계산"];
  D --> E["정확한 watering_log.content_length 및 XP 반영"];
Loading

File Walkthrough

Relevant files
Enhancement
TilSeeder.java
TilSeeder의 콘텐츠 길이 및 경험치 계산 로직 개선                                                 

src/main/java/com/Rootin/global/config/seeder/TilSeeder.java

  • TilContentLength 유틸리티 클래스를 임포트하여 TIL 콘텐츠 길이 계산에 활용합니다.
  • MonthlyEntryDailyEntry 레코드에서 charCount 필드를 제거하여 하드코딩된 값 사용을 중단합니다.
  • saveTil 메서드의 시그니처를 변경하여 charCountexp 파라미터를 제거하고, 메서드 내부에서 실제 콘텐츠 길이를
    기반으로 charCountexp를 계산하도록 수정했습니다.
  • saveTil 호출 시 exp 값을 반환하도록 변경하여, 호출부에서 반환된 exp를 누적 경험치 계산에 사용하도록
    업데이트했습니다.
+90/-98 

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

180 - Partially compliant

Compliant requirements:

  • TilSeeder.saveTil() 메서드 내에서 TilContentLength.countVisibleCharacters()를 사용하여 HTML 태그가 제거된 가시 텍스트 길이를 계산합니다.
  • 계산된 실제 charCount를 XP 계산 및 watering_log.content_length에 사용하도록 수정되었습니다.

Non-compliant requirements:

Requires further human verification:

⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@github-actions

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
반복되는 일별 TIL 생성 로직을 추상화합니다

CODING_DAYS, ENGLISH_DAYSDailyEntry 목록을 처리하는 로직이 여러 곳에서 거의 동일하게 반복되고 있습니다. 이 중복된
로직을 별도의 private 헬퍼 메서드로 추출하여 코드의 재사용성을 높이고 가독성을 개선할 수 있습니다. 이를 통해 향후 유사한 로직 추가 시
유지보수 비용을 절감할 수 있습니다.

src/main/java/com/Rootin/global/config/seeder/TilSeeder.java [522-529]

-        int curCodingExp = 0;
-        for (DailyEntry d : CODING_DAYS) {
-            double mult = 1.0 + Math.min(d.streakDays() * 0.05, 0.5);
-            int before = potExp[0] + curCodingExp;
-            int exp = saveTil(user, codingPot, tags.get(d.tagKey()), d.streakDays(), mult, before,
-                    today.minusDays(d.daysAgo()).atTime(21, 0), questDates);
-            curCodingExp += exp;
+        // ── 이번 달 코딩 ──────────────────────────────────────────────────
+        processDailyEntries(user, codingPot, tags, CODING_DAYS, potExp, 0, questDates, 21, 0,
+                            d -> 1.0 + Math.min(d.streakDays() * 0.05, 0.5));
+
+        // ── 이번 달 영어 ──────────────────────────────────────────────────
+        processDailyEntries(user, englishPot, tags, ENGLISH_DAYS, potExp, 1, questDates, 20, 0,
+                            d -> 1.0); // 영어는 스트릭 보너스 없음
+
+        // ... 다른 DailyEntry 처리도 유사하게 변경
+
+    // 새로운 private 헬퍼 메서드 추가
+    private void processDailyEntries(User user, Pot pot, Map<String, Tag> tags,
+                                     List<DailyEntry> dailyEntries, int[] potExp, int potIdx,
+                                     Set<LocalDate> questDates, int hour, int minute,
+                                     java.util.function.Function<DailyEntry, Double> multiplierCalculator) {
+        int currentPotExp = 0;
+        for (DailyEntry d : dailyEntries) {
+            double multiplier = multiplierCalculator.apply(d);
+            int before = potExp[potIdx] + currentPotExp;
+            int exp = saveTil(user, pot, tags.get(d.tagKey()), d.streakDays(), multiplier, before,
+                    today.minusDays(d.daysAgo()).atTime(hour, minute), questDates);
+            currentPotExp += exp;
         }
+    }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies significant code duplication in the DailyEntry processing loops. Abstracting this into a helper method would greatly improve code readability, maintainability, and reduce redundancy, which is a high-impact improvement.

High
콘텐츠 길이 계산 메서드의 성능을 검토합니다

TilContentLength.countVisibleCharacters 메서드는 TIL 콘텐츠의 실제 길이를 계산하는 중요한 역할을 합니다. 만약 이
메서드가 복잡한 문자열 파싱이나 정규식 처리를 포함한다면, 대량의 시드 데이터를 생성할 때 성능 병목이 될 수 있습니다. 메서드 내부 로직을 검토하여
불필요한 연산을 줄이거나, 더 효율적인 문자열 처리 방식을 적용하는 것을 고려해 볼 수 있습니다.

src/main/java/com/Rootin/global/config/seeder/TilSeeder.java [623-625]

       String content  = nextContent(tag.getName());
-      int charCount   = TilContentLength.countVisibleCharacters(content);
+      int charCount   = TilContentLength.countVisibleCharacters(content); // TilContentLength 내부 로직 최적화 필요
       int exp         = (int) Math.floor(calcExp(charCount) * multiplier);
Suggestion importance[1-10]: 7

__

Why: The suggestion highlights a potential performance bottleneck in TilContentLength.countVisibleCharacters during bulk data seeding. While not a bug, optimizing this method could improve the efficiency of the seeder, making it a valuable performance consideration.

Medium

@Yunseok3541

Copy link
Copy Markdown
Collaborator

🔴 반드시 수정

  1. Q3 퀘스트 포인트가 실제 글자 수와 불일치할 수 있음
    TilSeeder.java:584-586, TilSeeder.java:624
    이번 변경으로 charCount가 실제 콘텐츠 길이에서 계산되는데, 퀘스트 포인트는 여전히 모든 questDates에 대해 Q1/Q2/Q3를 무조건 지급합니다.
    문제는 대시보드의 Q3 기준이 오늘 작성 글자 수 >= 200이라는 점입니다. 실제 콘텐츠가 200자 미만이면
    watering_log.content_length 기준으로는 Q3 미달인데, 시더는 QUEST_Q3 포인트 로그를 생성하게 됩니다.
saveQuestPointLog(user, QUEST_Q3, 20, questDate, questCreatedAt);

수정 방향:

  • 날짜별 실제 charCount 합계를 모은 뒤 >= 200인 날짜에만 Q3 지급
  • 또는 시드 데이터 의도가 “모든 날짜 Q3 달성”이라면 콘텐츠를 200자 이상으로 보장
  1. 경험치 계산식이 실제 서비스 로직과 다름
    TilSeeder.java:625, TilSeeder.java:695
    LevelCalculator.java:36-43
    시더는 현재 이렇게 계산합니다.
int exp = (int) Math.floor(calcExp(charCount) * multiplier);

그런데 실제 서비스는 LevelCalculator.calculateExperience()에서 contentLength * 0.2를 double 상태로 유지한 뒤 multiplier를 곱하고 마지막에 floor 처리합니다.
현재 시더는 calcExp()에서 base exp를 먼저 floor 처리하기 때문에 일부 글자 수에서 실제 서비스와 경험치가 달라질 수 있습니다.
수정 방향:
TilSeeder에서도 LevelCalculator.calculateExperience(charCount, streakDays)를 사용
multiplier도 LevelCalculator.calculateStreakMultiplier(streakDays)로 계산해 저장값과 실제 정책을 맞추기

🟠 가급적 수정
3. 하드코딩 charCount 제거로 시드 데이터 시나리오가 바뀔 수 있음
TilSeeder.java의 MONTHLY_PLAN, CODING_DAYS, ENGLISH_DAYS 등
기존에는 월별/일별 시드 데이터가 의도한 글자 수를 직접 갖고 있었는데, 이제 콘텐츠 템플릿 길이에 따라 그래프, 잔디 농도, 스트릭 막대, 경험치가 결정됩니다.
즉 “12개월 그래프”, “최근 30일 작성량”, “화분 레벨” 같은 데모 데이터 모양이 기존 의도와 달라질 수 있습니다.
수정 방향:

  • 시드 데이터가 특정 그래프 모양을 의도한다면 targetCharCount를 유지하고 콘텐츠를 padding
  • 단순히 실제 콘텐츠 기반으로 바꾸는 게 목적이라면 변경 후 대시보드/화분 상세 데모 화면을 재확인
  1. calcExp, calcLevel 중복 정책 제거 권장
    TilSeeder.java:695
    경험치/레벨 정책은 이미 LevelCalculator가 담당하고 있습니다. 시더가 별도 계산식을 유지하면 정책 변경 시 다시 어긋날 가능성이 큽니다.
    수정 방향:
  • TilSeeder에 LevelCalculator를 주입해서 사용
  • 최소한 calcExp는 제거하고 실제 계산기 기준으로 통일
    🟡 확인 필요
  1. 시더 주석과 실제 동작 불일치 가능성
    TilSeeder.java 퀘스트 생성부 주변
    기존 주석은 “모든 시드 TIL은 태그 포함 + 200자 이상” 전제를 갖고 있는데, 이제 실제 콘텐츠 길이를 쓰므로 항상 맞는 말이 아닐 수 있습니다. Q3 지급 로직 수정과 함께 주석도 실제 정책에 맞게 바꾸는 게 좋습니다.
  2. 테스트 보강 필요
    TilContentLength 자체 테스트는 있지만, 이번 PR의 핵심은 “시더가 실제 콘텐츠 길이를 기반으로 watering log / 경험치 / 포인트 로그를 일관되게 만든다”입니다.
    추가하면 좋은 검증:
  • WateringLog.contentLength == TilContentLength.countVisibleCharacters(content)
  • expGained == LevelCalculator.calculateExperience(contentLength, streakDays)
  • Q3 포인트 로그는 날짜별 글자 수 합계가 200 이상일 때만 생성
    🔵 Low
  1. calcExp 이름이 실제 의미와 조금 다름
    현재 calcExp는 최종 경험치가 아니라 multiplier 적용 전 base exp를 int로 반환합니다. 유지한다면 calcBaseExp처럼 이름을 바꾸는 편이 오해가 적습니다.
    결론: TilContentLength를 사용하는 방향은 좋지만, Q3 포인트 지급과 경험치 계산식 불일치는 실제 시드 데이터 정합성에 영향을 줄 수 있어서 머지 전 수정하는 쪽이 안전해 보입니다.

@noeyoseel

Copy link
Copy Markdown
Collaborator Author

🟠 지적 3 — 하드코딩 charCount 제거로 시드 데이터 시나리오가 바뀔 수 있음

이 변경은 이번 PR의 의도된 결과입니다.

이전 시더는 charCount를 하드코딩한 뒤 실제 콘텐츠와 무관하게 watering_log에 기록하고 있었습니다. 그 결과 watering_log.content_length와 실제 TIL 본문 길이가 처음부터 불일치하는 상태였고, 이번 PR은 바로 그 불일치를 제거하는 것이 목적입니다.

그래프·잔디·레벨 모양이 바뀔 수 있다는 점은 동의합니다. 다만 그 변화는 "콘텐츠 기반이라는 실제 정책과 일치하는 올바른 데이터"로의 변화입니다. 이전 모양을 유지하기 위해 charCount를 다시 하드코딩하거나 콘텐츠를 패딩하면, 시더가 다시 실제 정책과 어긋난 데이터를 생성하는 원래 문제로 되돌아갑니다.

데모 화면의 시각적 모양은 콘텐츠 템플릿의 길이를 조정하는 것으로 제어할 수 있으므로, 코드 구조 수정 없이 데이터 레벨에서 해결 가능합니다.


🟡 지적 6 — 테스트 보강 필요

이번 PR의 핵심 로직인 TilContentLength.countVisibleCharacters()는 이미 별도 단위 테스트로 검증되어 있습니다.

시더 자체는 DB·JPA·JdbcTemplate이 모두 필요한 통합 환경에서만 동작하므로, 요청하신 세 가지 검증(content_length 일치, expGained 일치, Q3 날짜 조건)을 단위 테스트로 커버하기 어렵습니다.

이번 수정에서 LevelCalculator를 직접 주입해 사용하도록 변경했기 때문에, 경험치 계산의 정확성은 LevelCalculator의 기존 단위 테스트가 이미 보장합니다. Q3 조건부 지급 로직은 단순한 >= 200 분기로, 별도 테스트를 추가하는 것보다 코드를 직접 읽는 것이 더 명확하다고 판단했습니다.

통합 테스트 추가가 필요하다면 별도 이슈로 트래킹하는 것을 제안합니다.


수정 내용 요약

  • Q3 조건부 지급saveTil()questCharCounts 맵에 날짜별 charCount를 누적하고, 퀘스트 루프에서 합계 ≥ 200인 날에만 Q3 지급
  • LevelCalculator 주입calcExp() · calcLevel() 제거, calculateExperience() · calculateStreakMultiplier() · calculateLevel() 직접 호출로 정책 단일화
  • 불필요해진 multiplier 호출부 파라미터 및 연산 제거

@Yunseok3541 Yunseok3541 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍
고생하셨습니다!

@noeyoseel noeyoseel merged commit f54f568 into main Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] TilSeeder charCount 실제 콘텐츠 길이 기반으로 계산

2 participants