EngagementSurveys 서비스 3개 TDD 구현

ID: 614a8fe8-e5a9-4c12-8c2a-ea42f25163a8

높음 할 일

## 작업 내용: 3개 서비스 객체 구현

### 참조 스킬
- `.claude/skills/service-objects/SKILL.md` — Result 패턴, 네이밍 컨벤션
- `.claude/skills/rails-testing/SKILL.md` — Minitest 테스트 패턴

### 기존 코드 참조 (반드시 읽기)
- `app/services/diagnosis/scoring/engagement_calculator.rb` — 기존 채점 로직 (FACTOR_DIMENSION_MAP 등)
- `app/services/diagnosis/engagement_result_formatter.rb` — 결과 포맷
- `app/services/diagnosis/score_calculator_factory.rb` — 팩토리
- `app/models/workspace_engagement_survey.rb` — PR1에서 생성한 모델
- `app/models/diagnosis_session.rb` — DiagnosisSession 구조
- `db/seeds/engagement/engagement_questions_b2b.csv` — B2B 10문항 구조

### 1. EngagementSurveys::CreateService
파일: `app/services/engagement_surveys/create_service.rb`

```ruby
# 호출: EngagementSurveys::CreateService.call(workspace:, user:, year:, quarter:)
# 반환: Result (success/failure)
```
- workspace + year + quarter로 WorkspaceEngagementSurvey 생성
- access_token은 모델에서 자동 생성
- 동일 workspace+year+quarter 중복 시 실패 반환
- title은 자동 생성: "#{year} Q#{quarter}"

### 2. EngagementSurveys::SubmitResponseService
파일: `app/services/engagement_surveys/submit_response_service.rb`

```ruby
# 호출: EngagementSurveys::SubmitResponseService.call(
# survey:, workspace_group:, responses:, user: nil
# )
# responses: [{ question_no: 1, dimension: "belonging", factor: "psychological_safety", value: 5 }, ...]
# 반환: Result (success/failure)
```
- DiagnosisSession을 생성하여 응답 저장
- context_type: "b2b"
- workspace_engagement_survey_id: survey.id
- workspace_group_id: workspace_group.id
- user_id: user&.id (로그인 시 참고용, nil 허용)
- scoring_framework: "engagement_3factor"
- 설문이 active 상태가 아니면 실패 반환
- responses를 DiagnosisSession의 response_data(또는 적절한 필드)에 저장
- 기존 EngagementCalculator로 채점하여 결과 저장
- 주의: 기존 DiagnosisSession이 어떤 필드에 응답/결과를 저장하는지 확인 후 동일 패턴 사용

### 3. EngagementSurveys::ResultAggregator
파일: `app/services/engagement_surveys/result_aggregator.rb`

```ruby
# 호출: EngagementSurveys::ResultAggregator.call(survey:, group: nil)
# group이 nil이면 전체 집계, 있으면 해당 그룹만
# 반환: Result with data:
# {
# total_responses: Integer,
# overall_score: Float, # 10번 문항(overall) 전체 평균
# engagement_types: { # 10번 문항 기준 비율
# active: Float, # 6,7점 비율 (%)
# passive: Float, # 4,5점 비율 (%)
# disengaged: Float # 1,2,3점 비율 (%)
# },
# factor_scores: { # 3요인 평균
# psychological_safety: Float,
# self_efficacy: Float,
# flow_experience: Float
# },
# dimension_scores: { # 9요인 평균
# belonging: Float, autonomy: Float, meaning: Float,
# recognition: Float, achievement: Float, growth: Float,
# vitality: Float, strength_use: Float, positive_affect: Float
# },
# group_results: [ # 그룹별 결과 (group이 nil일 때)
# { group: WorkspaceGroup, ...위와 동일 구조... } # 3명 이하는 nil
# ]
# }
```
- 해당 survey의 모든 DiagnosisSession을 조회
- 3명 이하 그룹은 결과를 nil로 반환 (insufficient_responses: true 표시)
- overall_score: 각 세션의 overall(10번 문항) 점수 평균
- engagement_types: overall 문항 기준 6,7점=active / 4,5점=passive / 1,2,3점=disengaged 비율
- factor_scores: 3요인별 평균 (EngagementCalculator의 FACTOR_DIMENSION_MAP 참조)
- dimension_scores: 9개 dimension별 평균
- 강약점: 가장 높은/낮은 factor, dimension 표시

### 테스트
- `test/services/engagement_surveys/create_service_test.rb`
- 정상 생성, 중복 방지, 필수 필드 누락
- `test/services/engagement_surveys/submit_response_service_test.rb`
- 정상 응답 저장, 비활성 설문 거부, user_id 기록, 채점 결과 확인
- `test/services/engagement_surveys/result_aggregator_test.rb`
- 전체 집계, 그룹별 집계, 3명 이하 필터링, 몰입 유형 비율, 3요인/9요인 분석

## 중요 사항
- 모든 rails 명령은 `docker compose exec web` 으로 실행
- TDD: 테스트 먼저 작성 → 구현
- 기존 EngagementCalculator 로직을 최대한 재사용
- Result 패턴 사용 (service-objects 스킬 참조)

첨부 이미지

이미지 추가 (Ctrl+V로 붙여넣기 또는 클릭)

JPEG, PNG, GIF, WebP / 최대 10MB

담당자: developer-1
생성일: 2026년 03월 17일 08:14

활동 로그

아직 활동 기록이 없습니다.