올해는 윤년입니다.

2024년 8월 23일 금요일

Today I Learned

날짜

2024년 8월 23일 금요일

내용

방정식

데이터 생성 테스트 중에, 1년이 지난 데이터는 생성되지 않는다는 오류를 발견했다. 아니 그동안은 잘 주다가 갑자기 왜… 문서상으론 1년이 최대라고 하지만 그동안은 호출하면 잘 줬으면서 갑자기 왜 안주는 걸까. 주다 안주니 섭섭하게 짝이 없다.

어쩔수 없이 1년치만 가져오게 하려고 했는데… 정확히는 하루 전부터 364일 전까지 만 가져올 수 있다. 오늘과 1년 전의 오늘것은 가져올 수 없다. 이때부터 머리가 상당히 복잡해졌다. 반복문 에서 루프 한번 당 몇일치씩 요청을 보내야할까?

  1. 한번에 50일치 이상의 요청을 보낼순 없다. 매일 2개씩 보고서를 생성하는데 100개 이상이 되는 순간 가장 처음 만들어진 보고서가 삭제된다.
  2. 범위 밖의 날짜로 요청을 보내면 오류가 발생한다.
  3. 한번에 가능한한 많은 보고서를 만들어야 한다. 그래야 조회를 위한 API 호출이 줄어든다.

뭔 방정식도 아니고… 하나씩 헤보다 겨우 정답을 찾았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
day_limit = 364
batch_days = 26

target_date = today 
end_date = target_date - timedelta(days=day_limit) # 마지막 요청날

# 364일 보다 더 과거는 탐색하지 않음
while target_date > end_date:
	
	# 생성 요청을 묶어서 보내기 위한 반복문: 26회
	for i in range(1, batch_days+1):
		# 생성할 보고서의 날짜
		check_date = target_date - timedelta(days=i)
		
		# 보고서 생성
		create_naver_stat_report(check_date)
		
	# 26회 반복요청이 끝나면 보고서 조회
	get_naver_stat_reports()
	
	...
	

뿌듯함을 느끼면서 테스트를 돌려봤는데, 오늘(24년 8월 23일)을 기준으로 363일 전인 23년 8월 25일까지만 조회한다. 왜 하루가 빠지지..? 반복문은 364번 도는게 맞는데..

올해는 2월 29일이 있는 윤년이라 1년이 366일이었다…. 해가 4로 나눠지면 윤년이니 하루 더 조회하도록 코드에 추가했다..

4년 후에 유지보수하는 사람은 나에게 감사해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
day_limit = 364
batch_days = 26

target_date = today 
end_date = target_date - timedelta(days=day_limit)

  # 윤년일 경우, 2월 29일이 포함되어 364일전 데이터가 반복문에 포함되지 않음
  if today.year % 4 == 0 and today.year % 100 != 0 or today.year % 400 == 0:
      for stat_report_type in report_types:
          create_naver_stat_report(
              target_date=today - timedelta(days=364),
              naver_account=self.naver_account,
              stat_report_type=stat_report_type,
              db=self.db,
          )

while target_date > end_date:
	for i in range(1, batch_days+1):
		check_date = target_date - timedelta(days=i)
		create_naver_stat_report(check_date)
		
	get_naver_stat_reports()

Stibee로 이메일 발송하기

Imreport에서 데이터를 가져오는게 너무 오래걸린다. 로직이 끝나면 유저에게 메일로 보내줘야 한다. 기존 알파플러스에선 AWS SES를 썼는데, 이번엔 Stibee를 사용하게 됐다. 이메일 형식읆 만들어 놓고, 발송 조건을 API 호출로 쏘면 된다.

한가지 아쉬운점은 주소록에 없는 유저에겐 보낼 수 없다. 그래서 우선 이메일을 발송할 유저를 주소록에 등록하고 발송한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class SendEmailByStibee:
    """
    Send email by stibee
    """

    def __init__(self, endpoint_url: str, subscriber: str, data: dict):
        self.endpoint_url = endpoint_url
        self.access_token = STIBEE_API_KEY
        self.subscriber = subscriber
        self.data = data
        self.listId = {주소록 고유 ID}
        self.base_url = "https://api.stibee.com/v1"

    def save_in_address_book(self):
        """
        스티비 주소록에 저장합니다.
        저장한 이메일 주소로만 이메일을 보낼 수 있습니다.
        """
        headers = {
            "AccessToken": self.access_token,
        }
        data = {
            "eventOccurredBy": "MANUAL", # 관리자가 등록한 주소로 설정
            "confirmEmailYN": "N", # Y로하면 유저가 받은 메일로 승낙해야 등록됨
            "subscribers": [{"email": self.subscriber, "name": self.subscriber}],
        }

        res = requests.post(
            url=self.base_url + f"/lists/{self.listId}/subscribers",
            headers=headers,
            json=data,
        )
        if res.status_code == 200:
            res = res.json()
            if res["Ok"]:
                res = {"result": True, "message": "success"}
            else:
                res = {"result": False, "message": res}
        else:
            res = {"result": False, "message": res.text}

        return res

    def send_email(self):
        """
        이메일을 보냅니다.
        """
        res = self.save_in_address_book()

        # 주소록 저장 실패
        if res["result"] == False:
            return {"result": False, "message": res["message"]}

        headers = {
            "AccessToken": self.access_token,
            "Content-Type": "application/json",
        }

        # 수신자 설정
        self.data["subscriber"] = self.subscriber

        res = requests.post(
            url=self.endpoint_url,
            headers=headers,
            json=self.data,
        )
        if res.status_code == 200:
            res = True
        else:
            res = False
        return res

이메일을 보낼때 data에는 메일 내에 담길 변수들을 집어넣는다. $%brand_name%$, $%report_type%$ 이라면

data = {"brand_name": 'Nike', "report_type": '캠페인 성과'} 같은 방식이다. 스티비에 장점 중 하나는 HTML 언어를 몰라도 이메일 템플릿을 만들 수 있다는 점이였는데, 한 가지 문제는 버튼에 개인화된 링크를 담을 수가 없었다. 연동이 완료되면 그 스프레드시트로 보내줄려고 했는데 버튼 링크에는 미리 설정한 링크만 연결할 수 있고 위에 data 처럼 발송 떄마다 입력한 값으론 설정할 수 없다.

어쩌나 싶다가 HTML 태그를 추가할 수 있길래, 직접 <a></a> 태그를 넣어줬다. 물론 만드는건 옆자리 프론트엔드 개발자가 해줬다.

회고

이제 겸상 가능