설문조사 관련 아키텍처
draw.io Diagram | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
1. 설문 데이터 수집 만들기
관련 링크: 설문조사 만들기 - https://github.com/tstachlewski/serverless-survey/
AWS 관리 콘솔의 Lambda 함수를 만들 때 SAM(Serverless Application Model)을 이용해서 기존에 만들어진 Survey를 차용하여 사용할 수 있습니다.
https://y85teb42e2.execute-api.us-east-1.amazonaws.com/Prod/newsurvey
Lambda 함수 코드 1
코드 블럭 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
#-*- coding: utf-8 -*-
import yaml
import sys
import json
from yattag import Doc
def lambda_handler(event, context):
# print(event['requestContext']['queryStringParameters']['email'])
print(json.dumps(event))
if event['queryStringParameters'] is not None:
if event['queryStringParameters'].has_key('email'):
email = event['queryStringParameters']['email']
else:
email = 'anonymous'
else:
email = 'anonymous'
sourceIp = event['requestContext']['identity']['sourceIp']
requesTime = event['requestContext']['requestTimeEpoch']
configuration = yaml.load(open("config.yaml").read())
questions = configuration['Questions'];
title = configuration['Title'];
author = configuration['Author'];
image1 = configuration['Image1'];
image2 = configuration['Image2'];
theme = configuration['Theme'];
questionsNames = list()
for questionIterator in questions:
questionsNames.append(questionIterator)
questionsNames.sort()
doc, tag, text = Doc().tagtext()
with tag('html'):
with tag('body'):
doc.stag('br')
with tag('div', align='center'):
with doc.tag('div', style="font-size: medium;font-weight: bold; font-family: verdana; color:#" + str(theme) + ";"):
text(title)
doc.stag('br')
with doc.tag('div', style="font-size: small; font-weight: bold; font-family: verdana;"):
text("by " + author)
doc.stag('br')
doc.stag('img', src=image1, width="300")
doc.stag('br')
doc.stag('br')
with tag('form', action = "submitsurvey", style="margin-left: auto; margin-right: auto; width: 70%;"):
doc.input(name = 'title', type = 'hidden', value = title)
doc.input(name = 'email', type = 'hidden', value = email)
doc.input(name = 'sourceIp', type = 'hidden', value = sourceIp)
doc.input(name = 'requesTime', type = 'hidden', value = requesTime)
for questionName in questionsNames:
with tag('div'):
questionLabel = questions[questionName]['Label']
questionType = questions[questionName]['Type']
questionNumber = questions[questionName]['Qnumber']
#doc.stag('font', size="4", style="font-weight: bold; font-family: verdana; color:#" + str(theme) + ";")
with doc.tag('div',style="font-size: medium;font-weight: bold; font-family: verdana; color:#" + str(theme) + ";"):
with doc.tag('p'):
with doc.tag('span', style="font-family: arial, helvetica, sans-serif;padding: 3px 10px 3px 10px;border-radius: 25px;text-align: center;width: 50px;background-color: #377F9F;color: white;font-size : 14px;"):
doc.asis("Q " + questionNumber)
with doc.tag('span'):
doc.asis(" " + questionLabel)
if (questionType == "Label"):
value = questions[questionName]['Value']
with doc.tag('a', href=value, target="_blank",):
doc.asis(value)
doc.stag('br')
pass
if (questionType == "Text"):
with doc.textarea(name = questionNumber, style="width: 100%; border-color: #" + str(theme) + "; " , rows="5", placeholder="최대 한글 300자까지 가능합니다."):
pass
if (questionType == "ShortText"):
with doc.textarea(name = questionNumber, style="width: 100%; border-color: #" + str(theme) + "; " , rows="1"):
pass
if (questionType == "Radio"):
values = questions[questionName]['Values']
with doc.tag('table', style="table-layout: fixed;width: 100%;border-collapse: collapse;border: 1px solid gray;"):
with doc.tag('tr'):
for valueIterator in values:
value = questions[questionName]['Values'][valueIterator]
with doc.tag('td', style="font-family: arial, helvetica, sans-serif;text-align: center;width:20%;border: 1px solid gray;padding:10;"):
doc.asis(value);
with doc.tag('tr'):
for valueIterator in values:
value = questions[questionName]['Values'][valueIterator]
with doc.tag('td', style="font-family: arial, helvetica, sans-serif;text-align: center;width:20%;border: 1px solid gray;padding:10;"):
doc.input(name = questionNumber, type = 'radio', value = value, style="border-color: #" + str(theme) + "; ")
# for valueIterator in values:
# value = questions[questionName]['Values'][valueIterator]
# with doc.tag('div', style="font-size: small; font-weight: normal; font-family: verdana; color:black;"):
# doc.input(name = questionName, type = 'radio', value = value, style="border-color: #" + str(theme) + "; ")
# text(" "+str(value))
# doc.stag('br')
if (questionType == "CheckBox"):
with tag('fieldset',style="border: 0px; padding: 0px; font-size: small; font-weight: normal; font-family: verdana; color:black;"):
values = list(questions[questionName]['Values'])
for valueIterator in values:
value = questions[questionName]['Values'][valueIterator]
field_name = questionName + "_" + "".join([ c if c.isalnum() else "_" for c in value.lower() ])
doc.input(name = questionNumber, type = 'hidden', value = "0",style="border-color: #" + str(theme) + "; ")
doc.input(name = questionNumber, id = field_name , type = 'checkbox', value = "1", style="border-color: #" + str(theme) + "; ")
text(" "+str(value))
doc.stag('br')
doc.stag('br')
doc.stag('br')
with doc.tag('div', style="text-align: center;"):
doc.stag('input', type = "submit", value = "설문 완료", style="background-color:#50AEEB;border:none;color:white;padding:10px 30px;text-align: center; text-decoration: none; display: inline-block;font-size: 16px;margin: 4px 2px;border-radius: 12px;cursor: pointer;")
htmlResult = doc.getvalue()
return {
'statusCode': "200",
'body': htmlResult,
'headers': {
'Content-Type': 'text/html; charset=utf-8',
}
}
|
Lambda 함수 코드 2
코드 블럭 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
Title: AWS 2020년 2월 19일 미팅 설문조사
Author: Solutions Architect 김현수
Image1: https://a0.awsstatic.com/libra-css/images/logos/aws_logo_smile_1200x630.png
Image2: https://a0.awsstatic.com/libra-css/images/logos/aws_logo_smile_1200x630.png
Theme: 282828
Questions:
q1:
Type: Radio
Qnumber: 1-1
Label: AWS와의 금일 미팅에 대해서 만족하십니까?
Values:
Value5: 매우 만족
Value4: 만족
Value3: 중립
Value2: 불만족
Value1: 매우 불만족
q2:
Type: Text
Qnumber: 1-2
Label: AWS와의 미팅에서 만족한 이유 또는 개선해야 할 점이 있다면 적어주세요.
q3:
Type: Radio
Qnumber: 2-1
Label: 미팅 시간은 적절하였나요?
Values:
Value5: 매우 적절
Value4: 적절
Value3: 중립
Value2: 시간 부족
q4:
Type: CheckBox
Qnumber: 3-1
Label: 현재 프로젝트와 관련하여 어떤 서비스가 도움이 될 것이라고 생각하십니까?
Values:
Value8: 잘 모르겠다
Value7: Amazon Pinpoint (마케팅 도구, 세그먼테이션, 문자/이메일/푸시 알림 발송)
Value6: Amazon API Gateway (API Endpoint 제공)
Value5: AWS Lambda (API 비지니스 로직을 개발 언어를 이용하여 함수로 작성)
Value4: Amazon DynamoDB (NoSQL 데이터베이스)
Value3: Amazon QuickSight (Visualization을 위한 BI 도구)
Value2: Amazon Comprehend (텍스트의 키프레이즈/감정 분석을 위한 도구)
Value1: Amazon Comprehend ()
q5:
Type: Radio
Qnumber: 4-1
Label: AWS의 Solutions Architect와의 미팅을 다른 고객들에게 추천하시겠습니까?
Values:
Value5: 적극 추천
Value4: 추천
Value3: 중립
Value2: 비추천
Value1: 절대 비추천
q6:
Type: Text
Qnumber: 4-2
Label: AWS의 Solutions Architect와의 미팅을 추천하는 이유 또는 개선해야 할 점이 있다면 적어주세요.
q7:
Type: Text
Qnumber: 5-1
Label: AWS에 요청하고 싶은 내용이 있다면 적어주세요.
q8:
Type: Label
Qnumber: 6-1
Label: 아래 링크에서 금일 미팅 관련 정보를 제공해 드립니다.
Value: http://wiki.studydev.com/pages/viewpage.action?pageId=48923317
|
추가 개선 가능한 부분
동시에 여러명의 서베이 요청을 수행하기 위해서는 DynamoDB의 WCU를 고려해야 합니다.
따라서 아래와 같은 형태의 아키텍쳐를 고려할 수 있습니다.
관련 블로그DynamoDB의 WCU를 고려해 볼 수 있는 아키텍쳐: Things to Consider When You Build REST APIs with Amazon API Gateway
2. 캠페인 영역
2.1 Segment를 만듭니다.
csv, json 포맷으로 작성하고 등록하면 됩니다. (아래와 같은 형태)
2.2 이메일 템플릿을 제작합니다.
코드 블럭 |
---|
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<font face="Arial">
<table cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td width="100">
<img src="https://a0.awsstatic.com/libra-css/images/logos/aws_logo_smile_1200x630.png" alt="Amazon Web Services Logo" height="50">
</td>
<td style="text-align:right;padding-right:10px">
<font size="2"><a href="https://aws.amazon.com/">aws.amazon.com</a>
</font>
</td>
</tr>
</tbody>
</table>
<hr>
<br>
<div style="height: 200px; overflow: hidden;text-align:center">
<img src="https://d1.awsstatic.com/product-marketing/Pinpoint/Pinpoint%20Web%20Illustrations_EngagementManagement-Editorial.08da4e3b599fbbcd18ab5a85b991b4b8077fe509.png" alt="Aamazon Pinpoint Journey" style="height:200px; margin:0px 0 0 0;">
</div>
<br>
<div style="padding:20px;">
<h4>AWS와의 미팅은 즐거우셨나요?</h4>
<div> 고객님의 소중한 의견을 모아 더 나은 서비스로 보답하고자 설문조사를 실시하고 있습니다. 잠시만 시간을 내주시면 감사하겠습니다. </div>
<br>
<h4>We Hope you had a pleasant trip with Amazon Web Services.</h4>
<div> Please answer this short survey to share your experience with us. Your valuable feedback will be userd to futher enhance our services. </div>
</div>
<br>
<div style="text-align:center">
<a href="https://y85teb42e2.execute-api.us-east-1.amazonaws.com/Prod/newsurvey?email={{Address}}">
<button style="background-color:#50AEEB;border:none;color:white;padding:15px;text-align: center; text-decoration: none; display: inline-block;font-size: 16px;margin: 4px 2px;border-radius: 12px;">설문조사 바로가기 / Start Survey</button>
</a>
</div>
<br>
<font color="white">
<table bgcolor="#757F88" cellpadding="0" cellspacing="0" width="100%" style="font-size:75%">
<tbody>
<tr>
<th width="20"> </th>
<th> </th>
<th width="20"> </th>
</tr>
<tr>
<td> </td>
<td>
<ul style="padding-left:20px;">
<li> <b>E-MAIL 발송 정보</b> </li>
<div> 본 이메일은 금번 미팅에 참석해 주신 분들께만 발송되었습니다. 메일을 더 이상 받지 않으시려면 [<a href="https://aws.amazon.com/">수신거부</a>]를 눌러주시기 바랍니다. </div>
</ul>
<or>
</or>
</td>
<td> </td>
</tr>
<tr>
<td></td>
<td>
<div style="white-space:nowrap;text-align: right;">
<a href="https://www.facebook.com/amazonwebservices.ko/?brand_redir=153063591397681" target="_blank"><img src="https://www.koreanair.com/etc/clientlibs/koreanair/images/components/footer/icon-fb.png" alt="AWS 페이스북"></a>
<a href="https://twitter.com/awscloud" target="_blank"><img src="https://www.koreanair.com/etc/clientlibs/koreanair/images/components/footer/icon-tw.png" alt="AWS 트위터"></a>
</div>
</td>
<td></td>
</tr>
<tr>
<td colspan="3"> </td>
</tr>
<tr>
<td colspan="3"> </td>
</tr>
</tbody>
</table>
</font>
</font>
</body>
</html> |
2.3 캠페인을 생성합니다. (채널: 이메일)
동일한 캠페인을 보냈지만, 서로 상이한 이름과 지역 미팅 목적으로 다르게 메시지가 나가는 것을 확인 할 수 있습니다.
2.4 이메일 수신 여부를 확인합니다. 설문 조사 결과도 저장 되는지 확인 합니다.
2.5 캠페인을 모니터랑 합니다.
3. 캠페인 데이터 수집
캠페인을 진행해서 발송한 고객 중 응답하는 고객이 있을 수 있고 그렇지 않은 고객이 있을 수 있습니다.
- 문자의 경우 스팸 처리가 되거나 또는 읽지 않거나 읽더라도 설문 조사를 참여하지 않는 경우입니다.
- 이메일의 경우 스팸 처리가 되거나 또는 읽더라도 설문 조사를 참여하지 않는 경우입니다. 해당 사용자가 몇 번의 캠페인 요청에도 응답이 없다면 다음 캠페인에서도 동일한 반응이 예상될 수 있습니다. 이럴 때는 발송을 하지 않거나 다시 돌아올 수 있도록 프로모션을 하는 것이 더 좋은 방법이 될 수 있습니다.
- 100만명의 고객이 있다 하더라도 실제 Active하게 반응 하는 고객을 추려 낼 수 있으며, RAW 데이터를 보고 싶을 경우, 이메일 캠페인에 대한 분석 설정을 진행할 수 있습니다.
실습 문서:
- 200 - Amazon Pinpoint 서비스를 이용한 이메일 발송 시스템 구축하기 (v0.9)
- 이메일 Journey를 만들고 싶을 경우: AMAZON PINPOINT WORKSHOP
- 모바일용 푸시 알람을 만들고 싶을 경우: Create serverless-based social Android apps using Amplify and AI services
4. 캠페인 / 설문 데이터 분석
설문 조사 결과를 분석하고 QuickSight를 통하여 시각화를 할 수 있습니다. 예시로 아래와 같은 방법을 활용할 수 있습니다.
관련 블로그: 2. 설문조사 분석하기 - https://aws.amazon.com/blogs/database/how-to-perform-advanced-analytics-and-build-visualizations-of-your-amazon-dynamodb-data-by-using-amazon-athena/
3. 설문조사 발송 이메일 - Pinpoint
3.1 Segment 만들기
3.2 Segment 배치로 업데이트 하기
3.3 Segment 필터에서 이메일 발송하기
참조 할 만한 정보
...