개발 진행 #4

Luzern·2026년 1월 27일

Project

목록 보기
4/6

깃허브 브랜치와 로컬 연동

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("어제 입은 옷이 포함되어 있음");
        }

점수 차감 정책은 다음과 같다.

profile
그냥 기록용...

0개의 댓글