기업사회 맞춤형 캡스톤디자인(스마트양식)-week9~week12(Final) / 최종 결과

YUNU·2023년 6월 22일
0
post-thumbnail

🎓 기업사회 맞춤형 캡스톤 디자인

🐟 구현 단계


1. 수질 데이터 출력

ELK를 활용하여 수온, 암모니아, 습도, 탁도, 용존산소량에 대한 변화 추이를 그래프형태로 보이는 것이 본래의 계획이었으나 가지고 있는 데이터의 개수가 너무 적어 최종 결과물에는 표로 나타내었다.


2. 일출/일몰 시간 출력

일출/일몰 시간은 '한국 천문 연구원 출몰 시간 정보 API'를 사용하여 구하였다.
'한국 천문 연구원 출몰 시간 정보 API' 사용에 필요한 위도, 경도 정보는
'Google Geocoding API' 사용하여 구하였다.

주소가 입력한 것과 다르게 백령도라고 뜨는 것은 위도 경도가 String type으로 소숫점 없이 입력되어 그런 것이나 longitudeNum과 latitudeNum에 해당하는 위도경도의 일몰,일출 시간을 가져다 쓸 것이다.

3. 급이 기록 출력

급이 기록은 웹페이지에 출력하는 것 외에도 GPT에 input 데이터로 넣어줘야 한다.
'국립 수산 과학원 넙치 양식 매뉴얼'에 따르면 '겨울철 먹이활동이 좋지 못한 넙치는 이듬해 봄에 성장률이 저조하다' 라는 내용이 있어 성장률을 체크하기 위해서는 적어도 6달의 데이터가 필요하다 생각하였다. 따라서 6달의 급이 기록을 반환하는 API를 만들었으나 GPT에 input으로 넣을 때 overflow가 발생해 대략 일주일간의 기록만 반환하도록 하였다.

// table을 생성하여 급이 기록을 출력하였다.

    fetch('http://localhost:8080/fish-info/food-record')
        .then(response => {
            if (response.ok) {
                return response.json();
            } else {
                throw new Error('Error: ' + response.status);
            }
        })
        .then(data => {
            const table = document.getElementById('foodRecordTable');
            let row = document.createElement('tr');
            for (let i = 0; i < data.length; i++) {
                const item = data[i];
                const date = item.date;
                const count = item.count;

                const dateCell = document.createElement('td');
                dateCell.textContent = date;
                row.appendChild(dateCell);

                const countCell = document.createElement('td');
                countCell.textContent = count;
                row.appendChild(countCell);

                if ((i + 1) % 4 === 0) {
                    table.appendChild(row);
                    row = document.createElement('tr');
                }
            }
            // 마지막 행이 남은 경우 추가
            if (row.childElementCount > 0) {
                table.appendChild(row);
            }
        })
        .catch(error => {
            console.error(error);
        });

4. 수질 기록

수질기록은 넙치의 성장률 피드백을 위해 필요한 데이터로 초기에는 1년간의 데이터를 넣어주려 하였다. (실시간 모니터링, 빠른 대응, 이상 탐지를 위하여 데이터 수집 주기를 짧게 하되 데이터 부하, 센서의 에너지 소모, 데이터 처리의 복잡성 등을 고려하여 수집 주기를 1분으로 함)

그러나 1분 주기의 1년 간의 데이터 양은 너무 방대하여 overflow를 발생시킴

부득이하게 일단은 5분 주기로 1일간의 데이터만 반환하는 API를 만들어 GPT에 input 하였음
(일평균 혹은 하루에서 몇몇 특정 시간 대의 수질데이터만 넘겨주는 것으로 수정 필요)

5. ChatGPT 연동

본래는 GPT4.0 API를 사용하려 하였으나 2주가 지나도 GPT4.0 사용 승인이 나지 않아 어쩔 수 없이 GPT3.5를 사용하였다.

// 수질 데이터
  public String getWaterInfo(String id) {
        String url = "http://localhost:8080/waterinfo/yearly/" + id;
        try {
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
            if (response.getStatusCode() == HttpStatus.OK) {
                String waterInfo = response.getBody();

                String waterInfoWithoutId = waterInfo.replace("<waterId>" + id + "</waterId>", "");
                return waterInfoWithoutId;
            } else {
                throw new RuntimeException("Failed to retrieve water info: " + response.getStatusCode());
            }
        } catch (Exception e) {
            throw new RuntimeException("Error retrieving water info: " + e.getMessage());
        }
    }

// 급이 기록
    public String getFoodRecord() {
        String url = "http://localhost:8080/fish-info/food-record";
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
        return response.getBody();
    }

// 일출 일몰 시간
    public String getSunriseSunset() {
        String url = "http://localhost:8080/weather/sunrise-sunset";
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
        return response.getBody();
    }

// 질문 전에 수질 데이터, 급이 기록, 일출.일몰 시간을 Input
    public String prepareMessage(String waterInfo, String foodRecord, String sunriseSunset) {
        return "Here is the water info for the year: " + waterInfo + "\n"
                + "Here is the feeding record: " + foodRecord + "\n"
                + "Here is the sunrise and sunset info for today: " + sunriseSunset + "\n";
    }

    private SunriseSunsetData parseSunriseSunset(String sunriseSunset) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(sunriseSunset, SunriseSunsetData.class);
        } catch (IOException e) {
            throw new RuntimeException("일출 시간 및 일몰 시간 파싱 오류", e);
        }
    }

// 질문 매뉴얼
    public String askGPT(String question) {
        try {
            String waterInfo = getWaterInfo("1"); 
            String foodRecord = getFoodRecord();
            String sunriseSunset = getSunriseSunset();
            String preMessage = prepareMessage(waterInfo, foodRecord, sunriseSunset);

            // Logging the data before sending the question
            System.out.println("Prepared message: " + preMessage);
            System.out.println("Question: " + question);

            String modifiedQuestion = question.toLowerCase();
            if (modifiedQuestion.contains("밥을 언제 줄까?")) {
                SunriseSunsetData sunriseSunsetData = parseSunriseSunset(sunriseSunset);
                if (sunriseSunsetData != null) {
                    LocalDateTime sunriseDateTime = sunriseSunsetData.getSunriseDateTime();
                    LocalDateTime sunsetDateTime = sunriseSunsetData.getSunsetDateTime();

                    // 밥 주는 시간 계산하기
                    LocalDateTime feedingTime1 = sunriseDateTime.minusMinutes(30);
                    LocalDateTime feedingTime2 = sunsetDateTime.minusMinutes(30);

                    // 밥 주는 시간을 문자열로 포맷팅
                    String formattedFeedingTime1 = feedingTime1.format(DateTimeFormatter.ofPattern("hh:mm a"));
                    String formattedFeedingTime2 = feedingTime2.format(DateTimeFormatter.ofPattern("hh:mm a"));

                    // 응답에 밥 주는 시간 추가하기
                    preMessage += "광어에게 밥을 줄 시간은 아침: " + formattedFeedingTime1 + ", 저녁: " + formattedFeedingTime2 + "입니다.";
                } else {
                    // 오류 처리 로직 추가
                    throw new RuntimeException("일출 시간 파싱 오류");
                }
            } else if (modifiedQuestion.contains("급이 기록")) {
                preMessage = "Here is the feeding record for your reference:\n" + foodRecord;
            } else if (modifiedQuestion.contains("provide the sunrise and sunset info")) {
                preMessage = "Here is the sunrise and sunset info for today:\n" + sunriseSunset;
            } else if (modifiedQuestion.contains("provide the water info")) {
                preMessage += "Here is the water information:\n" + waterInfo;

             } else if (modifiedQuestion.contains("수질 관리")) {
             // 답변 매뉴얼 
             	...
             }
             ...
             

6. 넙치의 사진

GPT4.0 API를 사용하지 못하여 넙치의 이미지는 활용하지 못하였음

7. 결과 화면

- MainPage

- GPT page

  • "오늘은 밥을 언제 줄까?" 라고 질문한 결과

  • "수질 관리 어때?" 라고 질문한 결과


실제 구현 기간은 2주도 채 안되었고(DB 싱크 맞추는데만 1주일...), 캡스톤디자인 마무리 시기에 겹쳐서 모든 것을 혼자 구현하다보니 허점이 너무 많고 완성도도 좋지 못하였다.

그래도 스프링부트를 처음 사용해보았음에도 기간 내에 어떻게든 결과물을 만들어냈음에 안도하며, IT가 양식과 같이 다른 산업에 적용될 때에 기술적인 것만 생각해선 안되고 고려할 사항들이 정말 많다는 것을 깨달을 수 있었다. 이 덕에 나의 편협했던 시야를 넓힐 수 있는 좋은 경험을 할 수 있었던 것 같다.

날씨 데이터, 수조 내의 데이터, 양식장 내부/수조 바깥의 데이터, 양성과 관련된 데이터, 디바이스와 관련된 데이터 등등 양식장에선 무수히 많은 데이터들이 생성되고 이들을 어떻게 분류하고 다룰 것인지

내가 만드려는 서비스가 클라이언트에게 정말 필요한지, 어떤 도움을 주기 위한 서비스인지
이 서비스가 환경적으로, 경제적으로, 산업적으로 어떤 의미가 있는지

건식사료와 습식사료를 두고 생기는 어민들의 생존권과 관련된 갈등 문제 상황과 같은 사회적 문제로 인해 자동급이를 위한 건식사료를 실제 양식장에 적용할 수 있을지 등등

실제 산업에 서비스를 도입하려 한다면 해야 할 많은 고려사항들에 대해 고민하고 생각해 볼 수 있는 좋은 기회였다.

profile
DDeo99

0개의 댓글