Skip to content

Commit

Permalink
Merge pull request #337 from softeerbootcamp-2nd/dev
Browse files Browse the repository at this point in the history
main merge
  • Loading branch information
dydwo0740 committed Aug 20, 2023
2 parents c01c518 + bae927a commit 35e73bd
Show file tree
Hide file tree
Showing 35 changed files with 740 additions and 292 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ jobs:
touch ./src/main/resources/application.yml
echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml
- name: 테스트 커버리지를 PR에 코멘트로 등록합니다
id: jacoco
uses: madrapps/[email protected]
with:
title: 📝 테스트 커버리지 리포트입니다
paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 50
min-coverage-changed-files: 50


- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down
40 changes: 0 additions & 40 deletions backend-recommend/.gitignore

This file was deleted.

55 changes: 55 additions & 0 deletions backend-recommend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 추천 알고리즘 프로그램
실제 판매 견적들과 현재 선택완료된 견적을 비교하여 유사 견적을 추천해주는 알고리즘을 구현하였습니다.
현재 알고리즘은 가장 기본적인 연관분석 기법을 적용하기 위해 A-Priori 알고리즘을 이용하였습니다.
<br/> 유사도 판단의 기준은 아래와 같습니다.
1. 동일한 트림에서 모델타입(파워트레인, 바디타입, 구동방식)은 사용자가 변경을 결정할 가능성이 적으므로 해당 부분들이 동일한 견적에 대해서만 계산을 진행합니다.
2. 현재 선택한 추가옵션 리스트와 비교하여 어떠한 set이 존재할 때 다른 item or set이 존재할 가능성(confidence)를 높은 순으로 나열하여 상위 4개를 추립니다.
3. 이때 antecedent + consequent가 모두 자신이 선택한 추가옵션 안에 포함된다면 추천에서 제외하게 됩니다.
4. A-Priori 알고리즘의 threshold는 0.05로 잡았고, confidence는 0.05 이상인 데이터들만 추출하였습니다.

> 알고리즘은 추천 알고리즘으로 가장 기본적이라 할 수 있는 상관분석 기법과 연관분석 기법을 모두 적용해보고자 노력했으나, 임의로 만든 데이터의 한계로 상관분석 기법을 적용하기엔 각 아이템 사이의 연관관계가 너무 적게 나오는(우연적 관계 수준으로) 문제가 존재하였습니다.
> 따라서 이 부분을 이용하기엔 추천의 신뢰도가 낮다고 판단하여 적용하지 않게 되었습니다.
## 파일 설명(데이터 생성 부분)

### createData.py
실제 판매견적 데이터가 존재하지 않아 Dummy Data를 만들어줄 코드입니다.
해당 파일의 결과로 아래와 같이 나옵니다.

salesData.csv

| history_id | PowerTrain | BodyType | Operation | CarOptionList | count |
|------------|----|----------|-----------|---------------|-------|
| 1 | 1 | 3 | 5 | | 70 |
| 2 | 1 | 3 | 6 | | 75 |
| 3 | 1 | 4 | 5 | | 80 |
| 4 | 1 | 5 | 6 | | 77 |


<br/>

### csvForDB.py
createData 코드에서 생성된 파일을 이용하여 Cartag서비스의 DB에 저장할 수 있도록 csv 파일을 생성합니다.
SalesHistory 테이블과 SalesModelMapper 테이블에 코드의 결과로 나온 csv파일을 Bulk Insert하게 됩니다.

saleHistory.csv

| history_id | car_id | sold_count | sold_options_id |
|------------|----|----------|-----------|
|9|1|72|1|
|10|1|85|1|
|139|1|159|1,3|

saleModelMapper.csv

| history_model_mapper_id | model_id | history_id |
|------------|----|----------|
|0|1|1|
|1|3|1|
|2|5|1|

-------------

## 파일 설명(알고리즘 부분)

.. 파일 완료 후 추가 예정
7 changes: 0 additions & 7 deletions backend-recommend/RecommendApplication.java

This file was deleted.

62 changes: 62 additions & 0 deletions backend-recommend/createData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import random
import numpy as np
import pandas as pd
from itertools import combinations

newData = []

mean = 600
std_dev = 10

meanV1 = 80
meanV2 = 150
meanV3 = 400

dict = {}
dict[0] = [1,3,5]
dict[1] = [1,3,6]
dict[2] = [1,4,5]
dict[3] = [1,4,6]
dict[4] = [2,3,5]
dict[5] = [2,3,6]
dict[6] = [2,4,5]
dict[7] = [2,4,6]

numbers = []
numbers.extend((69,70,71,72,73,74,84,85,86,87,88,89,90,91,92)) # 1부터 15까지의 숫자 리스트
for selected_count in range(13): # 뽑을 숫자의 개수 # 특정 숫자
for comb in combinations(numbers, selected_count):
if 90 in comb and 91 in comb:
continue
if 90 in comb and 92 in comb:
continue
if 91 in comb and 92 in comb:
continue
subLists = list(comb)
string_S = ""
for i in range(len(subLists)):
sub = str(subLists[i])
string_S += sub
if(i == len(subLists) - 1):
break
string_S += ","
for i in range(8):
all_combinations = []
subsub = dict[i]
all_combinations.append(subsub[0])
all_combinations.append(subsub[1])
all_combinations.append(subsub[2])
all_combinations.append(string_S)
if len(subLists) == 0 or len(subLists) == 1 or len(subLists) == 12 or len(subLists) == 13:
all_combinations.append(int(np.random.normal(meanV1, std_dev)))
elif len(subLists) == 2 or len(subLists) == 3 or len(subLists) == 10 or len(subLists) == 11:
all_combinations.append(int(np.random.normal(meanV2, std_dev)))
elif len(subLists) == 4 or len(subLists) == 5 or len(subLists) == 8 or len(subLists) == 9:
all_combinations.append(int(np.random.normal(meanV3, std_dev)))
else:
all_combinations.append(int(np.random.normal(mean, std_dev)))
newData.append(all_combinations)

df = pd.DataFrame(newData, columns=['PowerTrain', 'BodyType', 'Operation', 'CarOptionList', 'count'])
df.index = df.index + 1
df.to_csv('salesData.csv', index=True, sep=',', index_label='history_id')
35 changes: 35 additions & 0 deletions backend-recommend/csvForDB.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pandas as pd

# CSV 파일 경로
csv_file_path = 'salesData.csv'

# CSV 파일 데이터 읽기
data = pd.read_csv(csv_file_path, dtype={'CarOptionList': str})

idx = 0

sales_data = []
mapper_data = []

# SalesHistory 테이블에 데이터 삽입
for index, row in data.iterrows():
car_id = 1
sold_count = int(row['count'])
sold_options_id = str(row['CarOptionList']) if not pd.isna(row['CarOptionList']) else ''
history_id = row['history_id']

sales_data.append((history_id, car_id, sold_count, sold_options_id))

power_train_id = int(row['PowerTrain'])
body_type_id = int(row['BodyType'])
operation_id = int(row['Operation'])

mapper_data.append((idx, power_train_id, history_id))
mapper_data.append((idx + 1, body_type_id, history_id))
mapper_data.append((idx + 2, operation_id, history_id))
idx += 3

df1 = pd.DataFrame(sales_data, columns=['history_id', 'car_id', 'sold_count', 'sold_options_id'])
df2 = pd.DataFrame(mapper_data, columns=['history_model_mapper_id', 'model_id', 'history_id'])
df1.to_csv('saleHistory.csv', sep=',', index=False)
df2.to_csv('saleModelMapper.csv', sep=',', index=False)
18 changes: 18 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '2.7.14'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'jacoco'
}

group = 'autoever2'
Expand Down Expand Up @@ -34,6 +35,23 @@ dependencies {

}

test {
finalizedBy jacocoTestReport
}

jacoco {
toolVersion = "0.8.8" // 버전 명시
}

jacocoTestReport {
dependsOn test // 리포트 생성 전에 test를 반드시 수행해야 한다!
reports { // 어떤 파일들을 생성할지, 어디에 생성할지 설정
xml.enabled true // xml과 html형식으로 결과물을 만들어내라!
html.enabled true
// 경로 명시 안 할 경우 기본 경로는 build/reports/jacoco 이하 경로
}
}

jar {
enabled = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class CartagApplication {
public static void main(String[] args) {
SpringApplication.run(CartagApplication.class, args);


}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package autoever2.cartag.controller;

import autoever2.cartag.domain.car.CarDefaultDto;
import autoever2.cartag.domain.car.CarDto;
import autoever2.cartag.service.CarService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -35,5 +36,13 @@ public List<CarDto> carTrimInfo(@Parameter(description = "선택한 car_type") @
return service.findCarByCarType(carType);
}

@Operation(summary = "차량 기본 정보 조회", description = "차량 기본 정보 조회 method")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = CarDefaultDto.class))),
})
@GetMapping("/infos/defaults")
public CarDefaultDto carDefaultDto(@Parameter(description = "선택한 car_id") @RequestParam("carid") int carId) {
return service.findCarDefaultDtoByCarId(carId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package autoever2.cartag.domain.car;

import autoever2.cartag.domain.color.InnerColorDto;
import autoever2.cartag.domain.color.OuterColorDto;
import autoever2.cartag.domain.model.ModelDefaultDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
@Schema(description = "차량 Default value를 반환하는 dto")
public class CarDefaultDto {
@Schema(description = "선택 차량의 타입", example = "펠리세이드")
private String carType;
@Schema(description = "선택 차량의 트림 명", example = "Le Blanc")
private String trim;
@Schema(description = "선택된 차량 트림의 기본 가격")
private int carDefaultPrice;

@Schema(description = "기본 powerTrain의 이름", example = "디젤 2.2")
private String powerTrainName;
@Schema(description = "기본 powerTrain의 이미지 url")
private String powerTrainImage;
@Schema(description = "기본 powerTrain의 가격")
private Long powerTrainPrice;

@Schema(description = "기본 bodyType의 이름", example = "7인승")
private String bodyTypeName;
@Schema(description = "기본 bodyType의 이미지 url")
private String bodyTypeImage;
@Schema(description = "기본 bodyType의 가격")
private Long bodyTypePrice;

@Schema(description = "기본 operation의 이름", example = "2WD")
private String operationName;
@Schema(description = "기본 operation의 이미지 url")
private String operationImage;
@Schema(description = "기본 operation의 가격")
private Long operationPrice;

@Schema(description = "기본 외장색상 이미지 url")
private String colorOuterImage;
@Schema(description = "기본 외장색상이 적용된 차량 url")
private String colorCarOuterImage;
@Schema(description = "기본 외장색상 가격")
private Long colorOuterPrice;
@Schema(description = "기본 외장색상 이름")
private String colorOuterImageName;
@Schema(description = "기본 내장색상 이미지 url")
private String colorInnerImage;
@Schema(description = "기본 내장색상이 적용된 차량 url")
private String colorCarInnerImage;
@Schema(description = "기본 내장색상 가격")
private Long colorInnerPrice;
@Schema(description = "기본 내장색상 이름")
private String colorInnerImageName;

public static CarDefaultDto toDefault(CarDefaultInfoDto carDefaultInfoDto, OuterColorDto outerColorDto, InnerColorDto innerColorDto, List<ModelDefaultDto> modelDefaultDto, String colorCarOuterImage) {
return CarDefaultDto.builder()
.carType(carDefaultInfoDto.getCarType())
.trim(carDefaultInfoDto.getTrim())
.carDefaultPrice(carDefaultInfoDto.getCarDefaultPrice())
.powerTrainName(modelDefaultDto.get(0).getModelName())
.powerTrainImage(modelDefaultDto.get(0).getModelImage())
.powerTrainPrice(modelDefaultDto.get(0).getModelPrice())
.bodyTypeName(modelDefaultDto.get(1).getModelName())
.bodyTypeImage(modelDefaultDto.get(1).getModelImage())
.bodyTypePrice(modelDefaultDto.get(1).getModelPrice())
.operationName(modelDefaultDto.get(2).getModelName())
.operationImage(modelDefaultDto.get(2).getModelImage())
.operationPrice(modelDefaultDto.get(2).getModelPrice())
.colorOuterImage(outerColorDto.getColorImage())
.colorCarOuterImage(colorCarOuterImage)
.colorOuterPrice(outerColorDto.getColorPrice())
.colorOuterImageName(outerColorDto.getColorName())
.colorInnerImage(innerColorDto.getColorImage())
.colorCarInnerImage(innerColorDto.getColorCarImage())
.colorInnerPrice(innerColorDto.getColorPrice())
.colorInnerImageName(innerColorDto.getColorName())
.build();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package autoever2.cartag.domain.car;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CarDefaultInfoDto {
@Schema(description = "선택 차량의 타입", example = "펠리세이드")
private String carType;
@Schema(description = "선택 차량의 트림 명", example = "Le Blanc")
private String trim;
@Schema(description = "선택된 차량 트림의 기본 가격")
private int carDefaultPrice;
}
Loading

0 comments on commit 35e73bd

Please sign in to comment.