git fetch origin
만약 충돌 날 경우
git branch -r | grep "origin/feature/wears” (충돌 난 브랜치)
원격에 없는 브랜치 흔적 제거
git remote prune origin
그 후 다시 fetch
git fetch origin
그리고 알 수 없는 이유로 프로젝트를 저장하고 껐다가 나중에 다시 켰을 때 git status 시 파일이 deleted 와 동시에 untracked files 에 똑같은 파일이 재생성된 경우가 있었다. 프로젝트 파일을 바탕화면에 놓고 작업하고 있는데, 이게 아이클라우드에 백업되면서 중복된 파일을 생성하는건지… 명확한 이유는 찾을 수 없었다. 이때 정상화를 위해서.
git restore --staged .
git restore .
git clean -fd
그 후 원격 동기화를 강제 진행한다.
git pull --ff-only
그리고 다시 git status 하면 문제가 해결된다.
사실 AI를 쓴다면 쉽게 해결되겠지만, 아직 말하는 감자 단계인 이슈로.. 직접 조건을 설정해 하드코딩 하기로 했다.
의상 추천의 조건은 비 여부와 날씨 , 그리고 어제 입은 옷을 전제로 잡았다. 이때 날씨는 기온으로 판별했다. 예를 들어 기온이 28도라면 사용자가 SUMMER라고 체크한 옷을 우선 추천하는 방식으로.
또한 어제 입은 옷은 최대한 추천하지 않도록 설계하였다.
그리고 옷 프리셋 기능을 추가했다. 이 기능은 사용자가 즐겨 입는 옷 세트를 저장하는 기능인데, 날씨와 비 여부 조건을 모두 충족하는 옷 조합이 프리셋에 존재한다면 그 조합을 우선 추천하도록 했다.
또한 “우선 추천” 이므로 T/F 가 아닌 점수 차감제를 도입했다. 기본 베이스 점수가 100이라면, 비 룰 위반 시 -40, 계절 룰 위반 시 -80와 같다.
@Override
public WearRecommendResponse recommend(Long userId, LocalDate date) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));
// 1. 날씨 조회 - 구현 시 연결
WeatherTodaySummaryResponse weather = safeGetWeatherSummary(date);
boolean isRaining = weather.hasRainOrSnow();
double temperature = weather.avgTemp();
// 2. 어제 착용한 옷 조회
Set<Long> yesterdayClothIds = wearRepository.findByUser_IdAndDate(user.getId(), date.minusDays(1))
.map(this::extractClothIds)
.orElseGet(Collections::emptySet);
// 3. 내 옷 전체 로딩 후 카테고리 분류
...
우선 사용자를 조회한 뒤, 날씨 정보와 어제 착용한 옷을 조회한다. 그 후 내 옷을 로딩 후 카테고리를 분류한다. 이 때, 만약 사용자의 옷이 너무 많게 되면 그 조합의 경우의 수가 많아질 수 있다. 따라서 랜덤 10개만 우선 조합하기로 했다. 10개는 추후 변동될 수 있다..
// 프리셋 코디 1순위. 단, 하나라도 위반되면 고려하지 않는다.
List<PresetListResponse> presets = presetService.getPresetList(user.getId());
for (PresetListResponse preset : presets) {
PresetDetailResponse detail = presetService.getPresetDetail(user.getId(), preset.presetId());
List<Long> clothIds = detail.items().stream()
.map(PresetItemDetailResponse::clothId)
.toList();
// 1. 프리셋 옷들이 전부 내 옷인지 검증과 동시에 엔티티 확보
...
그 후 프리셋 코디를 불러온다. 프리셋 옷들이 조건을 위반하는지 체크한 뒤, 모두 통과하면 100점으로 반환한다.
// 1. DRESS 단일 코디 후보
for (Cloth d : dresses) {
WearRecommendResponse.Recommendation base =
scoreCandidate("DRESS", List.of(d), isRaining, temperature, yesterdayClothIds);
if (base != null) candidates.add(base);
}
// 2. TOP + BOTTOM 후보
List<Cloth> topPool = tops.stream().limit(10).toList(); // 10개만 받아옴
List<Cloth> bottomPool = bottoms.stream().limit(10).toList();
for (Cloth t : topPool) {
for (Cloth b : bottomPool) {
WearRecommendResponse.Recommendation base =
scoreCandidate("TOP_BOTTOM", List.of(t, b), isRaining, temperature, yesterdayClothIds);
if (base != null) candidates.add(base);
}
}
// 3. OUTER 추가 여부
...
그 후 각 조건에 대입하여 점수를 차감한다. 이후 상위 3개의 조합을 반환한다.
// 1. 비
if (isRaining && violatesRainRule(clothes, true)) {
score -= PENALTY_RAIN;
warnings.add("isRaining 위배");
}
// 2. 온도
if (violatesTemperatureRule(clothes, temperature)) {
score -= PENALTY_TEMP;
warnings.add("온도에 맞지 않는 옷일 가능성");
}
// 3. 어제 입은 옷
if (violatesYesterdayRule(clothes, yesterdayClothIds)) {
score -= PENALTY_YESTERDAY;
warnings.add("어제 입은 옷이 포함되어 있음");
}
점수 차감 정책은 다음과 같다.