Skip to content

feat: #81 복구안 비교 View 연결 (recovery_compare) - #88

Merged
wngjs8114 merged 6 commits into
devfrom
feature/#81-recovery-compare-view
Aug 9, 2026
Merged

feat: #81 복구안 비교 View 연결 (recovery_compare)#88
wngjs8114 merged 6 commits into
devfrom
feature/#81-recovery-compare-view

Conversation

@wngjs8114

@wngjs8114 wngjs8114 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈

Refs #81

작업 내용

finalize_daily_plan()이 생성한 두 복구안(분량 유지형/핵심 집중형)을 사용자가 비교할 수 있는 recovery_compare View를 연결한다.

  • planner:recovery_compare (GET /planner/recovery/<uuid:group_id>/)
  • 같은 recovery_group_idRecoveryPlanPENDING 상태만 조회
  • maintain_volume/core_focus 둘 다 존재해야 정상 렌더링 (한쪽만 있거나 없으면 404)

Fit Bar 설계

기존 코드베이스에 Fit Bar(min_pct/band_pct/mark_pct/axis_max) 계산 로직이 없어서 이번에 새로 작성했다. static/css/planner.css.fit-min(0A)/.fit-band(AB) 정의를 기준으로:

min_pct = min_minutes / axis_max * 100
max_pct = max_minutes / axis_max * 100
band_pct = max_pct - min_pct

두 복구안 카드가 "같은 눈금 위에 그렸습니다"라는 화면 문구를 전제로 하므로, axis_max는 두 plan의 max_minutesavailable_minutes를 모두 고려해 View에서 한 번만 계산하고 두 카드에 공통으로 넘긴다 (카드별로 따로 계산하지 않음).

확정 안 된 부분 (TODO)

  • reason.*(오늘 남은 분량/학습 속도 보정/시험까지 남은 가능시간)는 아직 None으로 비워뒀습니다. #82 리뷰에서 reason.speed_factor/speed_subject가 과목이 여러 개일 때 단일 값으로 표현이 안 된다는 문제를 지적했고, FE2가 필드 구조를 바꾸기로 한 상태라 그 반영 후 별도로 채울 예정입니다.
  • _item_min_max_minutes()의 최소 필요시간(A)은 근사값입니다. RecoveryPlanItem엔 최대 필요시간(B, remaining_minutes)만 저장되고 A는 저장되지 않아서, estimate_task_minutes()의 min/max 비율을 B에 적용해 화면 표시용으로만 역산했습니다. 실제 배치 가능 여부 판정(generate_recovery_options())에는 안 쓰이고 Fit Bar 표시 전용입니다 — 이 근사 방식이 맞는 정책인지는 팀 확인 필요합니다.
  • Fit Bar에서 A~B 사이일 때 문구가 "추가 시간 필요"인데, 최소 기준으로는 가능한 상태라 이 표현이 맞는지 FE 확인 필요합니다.

이번 PR 범위 밖

  • recovery_preview, recovery_apply View — 다음 이슈로 분리
  • recovery_compare.html/recovery_card.html 템플릿 — #82(FE2) 담당, 이번 PR엔 포함 안 함

테스트

  • python manage.py test planner.tests.RecoveryCompareViewTests → 9개 통과 (정상 조회, 한쪽만 존재/그룹 없음/타 사용자 소유/이미 처리됨 각각 404, 공통 axis_max, 제외 작업 반영, context 확인, 로그인 필요)
  • python manage.py test planner → 160개 전체 통과

@wngjs8114
wngjs8114 requested a review from 6ye0m August 8, 2026 14:10
@wngjs8114 wngjs8114 self-assigned this Aug 8, 2026
@6ye0m

6ye0m commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

코드 직접 받아서 검증했습니다.

python manage.py test planner.tests.RecoveryCompareViewTests → Ran 10 tests, OK
python manage.py test planner → Ran 161 tests, OK
python manage.py test → Ran 234 tests, OK
manage.py makemigrations --check → 이상 없음

확인된 것

exam_period__user=request.user 필터로 소유권 체크되고, status=RecoveryPlanStatus.PENDING 필터로 이미 처리된 복구안은 자동 404 처리됩니다. 테스트로 재확인했습니다.
한쪽 타입만 있어도 200으로 렌더되도록 리뷰 반영하신 부분도 정상 동작 확인했습니다.
axis_max를 View에서 한 번만 계산해서 두 카드에 공통으로 넘기는 설계, PR 설명대로 정확히 구현되어 있습니다. max(available_minutes, *max_totals, 1) * 1.15로 0 나눗셈 방지까지 되어 있어서 좋았습니다.
mark_pct만 100으로 clamp하고 min_pct/max_pct는 안 하는데, axis_max 설계상(1.15배 여유) 어차피 100을 못 넘어서 문제는 없어 보입니다.

짚을 만한 것 하나 (블로킹은 아닙니다)

python
from planner.services.recovery import _future_available_capacity

_future_available_capacity가 recovery.py(BE1 파일)에서 언더스코어로 시작하는 private 헬퍼인데, views.py에서 그대로 import해서 쓰고 있습니다. Python 컨벤션상 private으로 표시된 함수라, recovery.py 담당자가 나중에 이 함수를 자유롭게 리팩터링(이름 변경, 시그니처 조정)하다가 recovery_compare()가 예고 없이 깨질 수 있습니다.

recovery.py에 이 함수를 public으로 노출하는 얇은 wrapper(예: get_future_available_minutes(exam_period, from_date))를 하나 추가해서 그걸 가져다 쓰는 방향을 검토해주시면 좋겠습니다. 급한 건 아니고, BE1과 상의해서 정리하면 될 것 같습니다.

PR 설명에 스스로 밝히신 TODO들(reason.* 보류, _item_min_max_minutes()의 근사치가 표시 전용이라는 점)은 이미 범위와 이유가 명확해서 별도로 지적할 내용은 없습니다.

private 함수 재사용 부분만 후속으로 정리해주시면, 나머지는 그대로 승인 가능할 것 같습니다.

@wngjs8114

Copy link
Copy Markdown
Collaborator Author

확인 감사합니다! _future_available_capacity()는 말씀해주신 대로 View에서 private helper를 직접 의존하지 않도록 recovery.py에 미래 잔여 가용시간 총합을 반환하는 public wrapper(get_future_available_minutes)를 추가해서 정리했습니다.

마침 #82가 dev에 병합되어 FE context 계약도 확정됐기 때문에, 최신 dev를 반영한 뒤 summary / feasibility_status / feasibility_status_label 및 reason context까지 함께 맞추고 전체 테스트(161개) 재확인했습니다.

@6ye0m

6ye0m commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

public wrapper 추가 및 dev 반영, FE context 계약 확인했습니다.

python manage.py test planner.tests.RecoveryCompareViewTests → Ran 10 tests, OK
python manage.py test planner → Ran 161 tests, OK (말씀하신 숫자와 일치)
python manage.py test → Ran 234 tests, OK
manage.py makemigrations --check → 이상 없음

summary/feasibility_status/feasibility_status_label, reason 연결도 코드 확인했고 필드명 정리가 일관되게 잘 되어 있습니다.

다만 private 함수 의존 문제가 완전히는 해결되지 않았습니다.

python
from planner.services.recovery import _future_available_capacity, get_future_available_minutes

get_future_available_minutes(새 public wrapper)는 647번째 줄에서 잘 쓰이는데, 676번째 줄에서 _future_available_capacity(private)를 여전히 직접 가져다 씁니다.

python
"available_days": len({
at.date for at in _future_available_capacity(exam_period, source_daily_plan.date)
}),

이번에 새로 추가하신 reason context의 available_days 필드가 "날짜 개수"를 필요로 하는데, get_future_available_minutes는 "합계(분)"만 반환해서 여기엔 못 쓰시고 private 함수를 다시 가져오신 것 같습니다. 결과적으로 지적드렸던 문제가 한 곳은 해결되고 다른 한 곳에서 재발한 상태입니다.

제안: wrapper를 용도별로 여러 개 만들기보다, _future_available_capacity()가 반환하는 날짜별 리스트 자체를 공개하는 public 함수 하나로 통합하는 게 나을 것 같습니다.

python
def get_future_available_capacity(exam_period, from_date) -> list[AvailableTimeInput]:
"""복구에 사용할 수 있는 날짜별 순수 잔여 가용시간."""
return _future_available_capacity(exam_period, from_date)

get_future_available_minutes()도 내부적으로 이 함수를 쓰게 리팩터링하고, views.py의 두 지점(합계, 날짜 개수) 다 이 하나의 public 함수에서 파생시키면 될 것 같습니다. 그러면 wrapper를 여러 개 안 만들어도 되고, private 함수 재사용도 완전히 없어집니다.

이 부분만 마저 정리해주시면 완전히 깔끔해질 것 같습니다. 그 외에는 문제없어 보입니다.

@wngjs8114

Copy link
Copy Markdown
Collaborator Author

확인했습니다. 말씀해주신 대로 get_future_available_capacity() public wrapper를 추가하고, recovery_compare()에서는 해당 결과를 한 번만 조회한 뒤 available_minutes와 available_days를 모두 파생하도록 수정했습니다. views.py의 _future_available_capacity 직접 의존도 제거했습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants