Skip to content

Commit

Permalink
Merge branch 'backend/Feature/137-gpt-add-price' into dev_backend
Browse files Browse the repository at this point in the history
  • Loading branch information
yo0oni committed Aug 13, 2024
2 parents e6cd2c5 + 488b2da commit 40f4717
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,44 @@ public class GptConfig {
2. 하루에 최소 세 개의 명소를 방문하고 싶어.
3. 하루에 최소 9시간을 돌아다닐거야.
4. 3일 간의 여행이면 3일짜리 계획을 만들어주면 돼.
5. 장소의 좌푯값도 소수점까지 함께 알려줘
6. 특수 문자를 사용하지 않고, 아래 예시대로 일정을 작성해줘
5. 장소의 좌푯값도 소수점까지 함께 알려줘.
6. 일정마다 입장료나 이용 비용을 여행지 화폐에 맞춰 작성해줘.
8. 식사 비용이나 입장료 같은 경우 (0.0)으로 작성해줘.
9. 특수 문자를 사용하지 않고, 아래 예시대로 일정을 작성해줘.
10. 일정 외의 다른 주의사항이나 추가 문장은 추가하지마.
11. 비용은 반드시 숫자로만 추가해줘.
4번의 예시는 아래와 같아
<2024.02.06>
- 세비야 대성당 37.38610100, -5.99220400
- 알카사르 세비야 37.38338500, -5.99051600
- 점심식사 에스피넬리 글로리아 37.39404600, -5.99379500
- 히랄다 탑 37.38603000, -5.99303300
- 스페인 광장 37.37722200, -5.98694400
- 저녁식사 바루로 37.37734300, -5.98742700
- 쇼핑 세비야 엘 코르테 잉글레스 37.38942400, -5.99407200
- 세비야 대성당:37.38610100, -5.99220400 (11.0)
- 알카사르 세비야:37.38338500, -5.99051600 (12.0)
- 점심식사 에스피넬리 글로리아: 37.39404600, -5.99379500 (0.0)
- 히랄다 탑:37.38603000, -5.99303300 (13.0)
- 스페인 광장:37.37722200, -5.98694400 (0.0)
- 저녁식사 바루로:37.37734300, -5.98742700 (0.0)
- 쇼핑 세비야 엘 코르테 잉글레스:37.38942400, -5.99407200 (0.0)
<2024.02.07>
- 세비야 대성당 37.38610100, -5.99220400
- 알카사르 세비야 37.38338500, -5.99051600
- 점심식사 에스피넬리 글로리아 37.39404600, -5.99379500
- 히랄다 탑 37.38603000, -5.99303300
- 스페인 광장 37.37722200, -5.98694400
- 저녁식사 바루로 37.37734300, -5.98742700
- 쇼핑 세비야 엘 코르테 잉글레스 37.38942400, -5.99407200
- 세비야 대성당:37.38610100, -5.99220400 (11.0)
- 알카사르 세비야:37.38338500, -5.99051600 (12.0)
- 점심식사 에스피넬리 글로리아: 37.39404600, -5.99379500 (0.0)
- 히랄다 탑:37.38603000, -5.99303300 (13.0)
- 스페인 광장:37.37722200, -5.98694400 (0.0)
- 저녁식사 바루로:37.37734300, -5.98742700 (0.0)
- 쇼핑 세비야 엘 코르테 잉글레스:37.38942400, -5.99407200 (0.0)
<2024.02.08>
- 세비야 대성당 37.38610100, -5.99220400
- 알카사르 세비야 37.38338500, -5.99051600
- 점심식사 에스피넬리 글로리아 37.39404600, -5.99379500
- 히랄다 탑 37.38603000, -5.99303300
- 스페인 광장 37.37722200, -5.98694400
- 저녁식사 바루로 37.37734300, -5.98742700
- 쇼핑 세비야 엘 코르테 잉글레스 37.38942400, -5.99407200
- 세비야 대성당:37.38610100, -5.99220400 (11.0)
- 알카사르 세비야:37.38338500, -5.99051600 (12.0)
- 점심식사 에스피넬리 글로리아: 37.39404600, -5.99379500 (0.0)
- 히랄다 탑:37.38603000, -5.99303300 (13.0)
- 스페인 광장:37.37722200, -5.98694400 (0.0)
- 저녁식사 바루로:37.37734300, -5.98742700 (0.0)
- 쇼핑 세비야 엘 코르테 잉글레스:37.38942400, -5.99407200 (0.0)
날짜 형식은 반드시 <YYYY.MM.DD> 을 지켜줘.
매일 점심과 저녁은 레스토랑에서 먹을 거니까, 실제로 맛있고 유명한 레스토랑도 일정에 포함시켜줘.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
package com.isp.backend.domain.gpt.constant;

import java.util.List;

public class ParsingConstants {
public static final String DATE_REGEX = "(\\d{4}\\.\\d{2}\\.\\d{2})";
public static final String NEW_LINE_REGEX = "\n";
public static final String CURRENT_DATE = "";
public static final String COMMA = ", ";
public static final List<String> FILTER_STRINGS = List.of(
"Message(role=assistant, content=", ")"
);
import lombok.Getter;

import javax.swing.*;

@Getter
public enum ParsingConstants {
COMMA(", "),
ENTRY_SEPARATOR("<"),
LINE_SEPARATOR("\n"),
DATE_SUFFIX(">"),
PRICE_PREFIX("("),
PRICE_SUFFIX(")"),
DETAIL_PREFIX("- "),
PRICE_FREE("무료"),
PRICE_VAR("변동"),
DEFAULT_PRICE(0.0),
DETAIL_SUFFIX(":"),
DEFAULT_COORDINATE(0.0);
private final String stringValue;
private final Double doubleValue;

ParsingConstants(String stringValue) {
this.stringValue = stringValue;
this.doubleValue = null;
}

ParsingConstants(Double doubleValue) {
this.stringValue = null;
this.doubleValue = doubleValue;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
@NoArgsConstructor
public class GptScheduleDetail {
private String detail;
private Double price;
private Coordinate coordinate;

public GptScheduleDetail(String detail, Coordinate coordinate) {
public GptScheduleDetail(String detail, Double price, Coordinate coordinate) {
this.detail = detail;
this.price = price;
this.coordinate = coordinate;
}
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,95 @@
package com.isp.backend.domain.gpt.entity;

import com.isp.backend.domain.gpt.constant.ParsingConstants;
import com.isp.backend.global.exception.gpt.NonValidatedParsingException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RequiredArgsConstructor
@Component
public class GptScheduleParser {

private static final Pattern LINE_PATTERN = Pattern.compile("-\\s*(.*?):\\s*([\\d.]+,\\s*[\\d.]+)?\\s*\\((.*?)\\)");

public List<GptSchedule> parseScheduleText(String scheduleText) {
System.out.println(scheduleText);
List<GptSchedule> schedules = new ArrayList<>();

String[] entries = scheduleText.split("<");
String[] entries = scheduleText.split(ParsingConstants.ENTRY_SEPARATOR.getStringValue());

for (String entry : entries) {
if (entry.trim().isEmpty()) continue;

String[] lines = entry.split("\n");
String date = lines[0].trim().replace(">", "");
String[] lines = entry.split(ParsingConstants.LINE_SEPARATOR.getStringValue());
String date = parseDate(lines[0]);

List<GptScheduleDetail> scheduleDetails = new ArrayList<>();

for (int i = 1; i < lines.length; i++) {
String line = lines[i].trim();
if (line.isEmpty()) continue;

String[] parts = line.split(" ");
StringBuilder detail = new StringBuilder();
double latitude = 0.0;
double longitude = 0.0;

for (int j = 0; j < parts.length; j++) {
try {
if (j == parts.length - 2) {
latitude = Double.parseDouble(parts[j].replace(",", ""));
} else if (j == parts.length - 1) {
longitude = Double.parseDouble(parts[j]);
} else {
if (!detail.isEmpty()) detail.append(" ");
detail.append(parts[j]);
}
} catch (NumberFormatException e) {
if (!detail.isEmpty()) detail.append(" ");
detail.append(parts[j]);
}
}
Matcher matcher = LINE_PATTERN.matcher(line);
if (matcher.find()) {
String detail = matcher.group(1);
String coordinatesStr = matcher.group(2);
String priceStr = matcher.group(3);

String detailString = detail.toString().trim();
if (detailString.startsWith("-")) {
detailString = detailString.substring(1).trim();
}
Coordinate coordinate = parseCoordinates(coordinatesStr);
double price = parsePriceString(priceStr);

scheduleDetails.add(new GptScheduleDetail(detailString, new Coordinate(latitude, longitude)));
scheduleDetails.add(new GptScheduleDetail(detail, price, coordinate));
} else {
System.out.println("767867889");
throw new NonValidatedParsingException();
}
}

schedules.add(new GptSchedule(date, scheduleDetails));
}

return schedules;
}

private String parseDate(String line) {
return line.trim().replace(ParsingConstants.DATE_SUFFIX.getStringValue(), "");
}

private Coordinate parseCoordinates(String coordinatesStr) {
if (coordinatesStr == null || coordinatesStr.isEmpty()) {
return new Coordinate(ParsingConstants.DEFAULT_COORDINATE.getDoubleValue(), ParsingConstants.DEFAULT_COORDINATE.getDoubleValue());
}

String[] parts = coordinatesStr.split(",");
if (parts.length != 2) {
System.out.println("1546351");
throw new NonValidatedParsingException();
}

try {
double latitude = Double.parseDouble(parts[0].trim());
double longitude = Double.parseDouble(parts[1].trim());
return new Coordinate(latitude, longitude);
} catch (NumberFormatException e) {
System.out.println("1234");
throw new NonValidatedParsingException();
}
}

private double parsePriceString(String priceStr) {
if (ParsingConstants.PRICE_FREE.getStringValue().equals(priceStr)) {
return 0.0;
}

try {
return Double.parseDouble(priceStr.replaceAll("[^\\d.]", ""));
} catch (NumberFormatException e) {
System.out.println("5678");
return ParsingConstants.DEFAULT_PRICE.getDoubleValue();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.isp.backend.domain.gpt.service;

import com.isp.backend.domain.country.entity.Country;
import com.isp.backend.domain.country.repository.CountryRepository;
import com.isp.backend.domain.gpt.config.GptConfig;
import com.isp.backend.domain.gpt.constant.ParsingConstants;
import com.isp.backend.domain.gpt.dto.request.GptRequest;
Expand All @@ -12,6 +13,7 @@
import com.isp.backend.domain.gpt.entity.GptSchedule;
import com.isp.backend.domain.gpt.entity.GptScheduleParser;
import com.isp.backend.domain.schedule.service.ScheduleService;
import com.isp.backend.global.exception.schedule.CountryNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
Expand All @@ -34,6 +36,7 @@
public class GptService {

private final GptScheduleParser gptScheduleParser;
private final CountryRepository countryRepository;
private final ScheduleService scheduleService;
private final WebClient webClient;

Expand All @@ -49,7 +52,6 @@ public GptScheduleResponse getResponse(HttpHeaders headers, GptRequest gptReques
.retrieve()
.bodyToMono(GptResponse.class)
.block();

List<GptSchedule> gptSchedules = gptScheduleParser.parseScheduleText(extractScheduleText(response));
return new GptScheduleResponse(gptSchedules);
}
Expand All @@ -61,9 +63,8 @@ private String extractScheduleText(GptResponse gptResponse) {
@Async
public CompletableFuture<GptSchedulesResponse> askQuestion(GptScheduleRequest questionRequest) {
String question = formatQuestion(questionRequest);
Country country = countryRepository.findAirportCodeByCity(questionRequest.getDestination()).orElseThrow(CountryNotFoundException::new); String countryImage = country.getImageUrl();
List<GptMessage> messages = Collections.singletonList(createMessage(question));
Country country = scheduleService.validateCountry(questionRequest.getDestination());
String countryImage = country.getImageUrl();

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<CompletableFuture<GptScheduleResponse>> futures = Arrays.asList(
Expand Down Expand Up @@ -103,7 +104,7 @@ private String formatQuestion(GptScheduleRequest questionRequest) {
questionRequest.getExcludedActivities(),
questionRequest.getDepartureDate(),
questionRequest.getReturnDate(),
String.join(ParsingConstants.COMMA, questionRequest.getPurpose())
String.join(ParsingConstants.COMMA.getStringValue(), questionRequest.getPurpose())
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ public enum ErrorCode {
NOT_YOUR_FLIGHT(HttpStatus.UNAUTHORIZED, "F004", "사용자의 항공권이 아닙니다"),
OPEN_WEATHER_SEARCH_FAILED(HttpStatus.NOT_FOUND,"F005", "날씨 정보 파싱에 실패하였습니다"),
EXCHANGE_RATE_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F006", "환율 요청을 가져오는 중 오류를 발생했습니다."),
EXCHANGE_RATE_IS_FAILED(HttpStatus.BAD_REQUEST,"F007", "환율 DB를 저장하던 중 오류가 발생하였습니다.");
EXCHANGE_RATE_IS_FAILED(HttpStatus.BAD_REQUEST,"F007", "환율 DB를 저장하던 중 오류가 발생하였습니다."),

// Gpt
PARSING_IS_NOT_VALIDATED(HttpStatus.BAD_REQUEST, "G001", "GPT 응답을 파싱하던 중 오류가 발생했습니다.");


private HttpStatus status;
Expand All @@ -52,4 +55,4 @@ public enum ErrorCode {
this.message = message;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.isp.backend.global.exception.gpt;

import com.isp.backend.global.exception.CustomException;
import com.isp.backend.global.exception.ErrorCode;

public class NonValidatedParsingException extends CustomException {
public NonValidatedParsingException() {
super(ErrorCode.PARSING_IS_NOT_VALIDATED);
}
}

0 comments on commit 40f4717

Please sign in to comment.