The Flex
경력근거: Develop operational dashboards, workflow engines, management platforms
※ 실제 산출물이 아닌 AI 예측 시안입니다.
The Flex는 Base360.ai라는 자체 플랫폼으로 임대 운영을 자동화한다고 공고에 적었고, Airbnb·Hostaway 연동과 운영 대시보드 제작을 업무로 둡니다. 채널을 여럿 붙이면 같은 집이 채널마다 다른 이름으로 들어오는데, 그 표기를 맞춰야 예약이 이어 붙고 그래야 청소 간격이 계산됩니다. 이 순서는 채널을 여러 개 물고 있는 회사에서만 문제가 됩니다. 공고 업무는 제품 개발부터 외부 연동, AI 기능까지 걸쳐 있는데 1주 범위에서 고를 수 있는 것은 하나라 운영 판정 축을 골랐습니다. 결제와 문자 발송은 사업자 계정이 있어야 열리는 기능이라 제품 골조에서 뺐습니다. 이 포트폴리오는 원문 표기를 표준 이름으로 맞추고 규칙으로 판정해 근거를 보여주는 기술을 보여줍니다. 단기임대 운영 자체의 전문성은 주장하지 않습니다. 현장에서 청소 인력을 어떻게 배차하는지 같은 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
주요 업무에 운영 대시보드와 워크플로 엔진, 관리 플랫폼을 만드는 일이 있습니다. 예약 사이 청소 간격을 보는 화면이 그 운영 대시보드의 한 판입니다.
수작업을 없애 회사가 확장되게 하는 시스템 설계가 업무로 적혀 있습니다. 채널별 예약을 눈으로 맞춰 보던 일을 규칙 판정으로 옮기는 것이 그 첫 단계입니다.
Airbnb·Hostaway 같은 외부 플랫폼 연동이 업무에 있습니다. 채널마다 다르게 적힌 숙소 표기를 표준 이름으로 맞추는 일이 그 연동의 실제 내용입니다.
채널에서 내려받은 예약 목록과 숙소별 청소 소요를 대조해, 앞 손님이 나간 뒤 다음 손님이 들어오기까지 청소할 시간이 남는지를 판정해 보여주는 관리자 페이지입니다. 판정은 퇴실 시각과 입실 시각의 간격을 청소 소요와 준비 여유에 견주는 계산으로 정합니다.
여러 채널에서 들어온 예약을 한 곳에 모아 보는 운영 담당자입니다. 채널마다 내려받은 표를 엑셀에 붙여 놓고 같은 숙소끼리 정렬한 다음 퇴실과 입실 시각을 눈으로 훑는데, 숙소명이 채널마다 다르게 적혀 있어 어느 줄이 같은 집인지부터 맞추느라 오전을 씁니다.
숙소마다 청소에 걸리는 시간이 정해져 있으면, 다음 손님을 받을 수 있는지는 사람이 표를 훑지 않아도 두 시각의 간격 계산으로 갈라집니다.
채널 예약표와 숙소 정비 기준표를 올리면 숙소별로 예약을 이어 붙여 청소 시간이 모자란 건을 사유별로 보여주는 관리자 페이지입니다. 채널 API 연동은 두지 않고 내려받은 파일만 읽습니다.
판정 로직을 FastAPI로 분리해 화면과 나눠 놓고 AWS로 배포·운영합니다. 같은 파일을 올리면 화면과 API가 같은 판정을 냅니다.
표준 이름을 찾지 못한 원문 표기를 모아 후보를 제시하고, 담당자가 고른 것을 사전에 저장해 다음 실행부터 자동으로 맞추는 화면입니다.
담당자가 예약 40건과 숙소 기준표를 올려 청소 시간이 모자란 건이 사유별로 나뉜 화면을 보는 것을 시연합니다.
결측·중복·미등록 표기가 섞인 입력 30종을 올려도 화면이 깨지지 않고, 판정하지 못한 예약은 사유가 붙어 남습니다.
청소 소요가 비어 있는 숙소는 통과로 세지 않고 기준 미입력으로 따로 답니다. 재지 못한 것을 문제없음으로 만들지 않습니다.
판정과 손으로 만든 정답표를 맞춰 사유별로 어긋난 건을 셉니다. 단, 예약과 정답을 모두 작성자가 만든 합성 기준이라 성능 측정이 아니라 데모 조건 동작입니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 더미데이터 자가채점이며, 실제 판정 정확도는 The Flex의 실제 채널 예약과 실제 정비 기준이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 이 의심을 지우려고 숙소명이 채널마다 다르게 적힌 건과 청소 기준이 빠진 숙소, 같은 예약번호가 두 번 들어온 건을 더미에 심어 뒀습니다.
간격이 남는다고 해서 실제로 청소가 끝난다고 단정할 수 없습니다. 인력 배차와 이동 시간은 이 도구가 보지 않으므로, 판정은 시간이 물리적으로 가능한지까지만 읽어야 합니다.
채널마다 시각을 적는 기준이 다를 수 있습니다. 표준 시간대와 현지 시각이 섞여 들어오면 간격이 통째로 어긋나므로, 적재 단계에서 기준을 하나로 맞추는 일이 도입 단계의 실제 과제입니다.
결제와 문자 발송은 사업자 계정이 있어야 열리는 기능이라 이 포트폴리오에 넣지 않았습니다. 공고의 Stripe·Twilio가 담당하는 층은 제품이 맡고, 여기서는 그 앞단의 판정만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
Airbnb·Hostaway 같은 채널에서 내려받은 예약 목록입니다. 숙소명과 채널명이 채널 내보내기마다 대소문자·띄어쓰기로 흔들리고, 마스터에 없는 신규 숙소와 같은 예약번호가 두 번 들어온 건을 일부러 섞었습니다.
| booking_no | channel_raw | property_name_raw | checkin_at | checkout_at | guest_count |
|---|---|---|---|---|---|
| BK-1001 | Airbnb | Gangnam Loft 301 | 2026-08-18T15:00 | 2026-08-20T11:00 | 2 |
| BK-1002 | Airbnb | 강남 로프트 301 | 2026-08-20T15:00 | 2026-08-22T11:00 | 3 |
| BK-1003 | Hostaway | Hongdae Studio 502 | 2026-08-18T16:00 | 2026-08-21T10:00 | 1 |
| BK-1004 | hostaway | HONGDAE STUDIO 502 | 2026-08-21T11:00 | 2026-08-23T10:00 | 2 |
| BK-1005 | Airbnb | Seongsu Flat 12 | 2026-08-19T15:00 | 2026-08-21T11:00 | 4 |
아래 JSON Schema에 맞는 채널 예약 행을 더 만들어 주세요. {"type":"object","properties":{"booking_no":{"type":"string"},"channel_raw":{"type":"string"},"property_name_raw":{"type":"string"},"checkin_at":{"type":"string"},"checkout_at":{"type":"string"},"guest_count":{"type":"integer"}},"required":["booking_no","channel_raw","property_name_raw","checkin_at","checkout_at","guest_count"]} 한 숙소에 예약이 두 건 이상 이어지도록 만들어 주세요. 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 미매핑으로 property_name_raw가 마스터에 없어 property_id가 null인 행, 둘째 표기흔들림으로 같은 값을 대소문자와 띄어쓰기만 다르게 적은 행 쌍(Hongdae Studio 502와 HONGDAE STUDIO 502, Airbnb와 airbnb), 셋째 중복으로 같은 booking_no가 두 행에 있는 재등록 건, 넷째 경계극단으로 앞 예약 퇴실과 뒤 예약 입실이 같은 시각인 행 쌍과 뒤 예약 입실이 앞 예약 퇴실보다 이른 행 쌍. 또한 형식 깨뜨리기 시도 3건을 별도 파일로 만들어 guest_count에 문자열을 넣거나 checkout_at을 빼서 스키마 검증이 걸러내는지 확인하게 해 주세요.{"type":"object","properties":{"booking_no":{"type":"string"},"channel_raw":{"type":"string"},"property_name_raw":{"type":"string"},"checkin_at":{"type":"string"},"checkout_at":{"type":"string"},"guest_count":{"type":"integer"}},"required":["booking_no","channel_raw","property_name_raw","checkin_at","checkout_at","guest_count"]}숙소별 청소 소요와 준비 여유를 담은 정비 기준표입니다. 기준이 아직 입력되지 않은 숙소를 일부러 섞었습니다.
| property_id | property_name | cleaning_minutes | buffer_minutes | city |
|---|---|---|---|---|
| PR-001 | 강남 로프트 301 | 180 | 30 | 서울 |
| PR-002 | 홍대 스튜디오 502 | 90 | 30 | 서울 |
| PR-003 | 성수 플랫 12 | 120 | 30 | 서울 |
| PR-004 | 이태원 하우스 7 | 240 | 60 | 서울 |
| PR-005 | 마포 테라스 9 | 150 | 30 | 서울 |
아래 JSON Schema에 맞는 숙소 정비 기준 행을 더 만들어 주세요. {"type":"object","properties":{"property_id":{"type":"string"},"property_name":{"type":"string"},"cleaning_minutes":{"type":["integer","null"]},"buffer_minutes":{"type":"integer"},"city":{"type":"string"}},"required":["property_id","property_name","cleaning_minutes","buffer_minutes","city"]} property_name은 한글 표준 이름으로 적어 주세요. 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 결측으로 cleaning_minutes가 null인 기준 미입력 숙소, 둘째 경계극단으로 cleaning_minutes가 60인 가장 짧은 숙소와 300인 가장 긴 숙소. 또한 형식 깨뜨리기 시도 3건을 별도 파일로 만들어 buffer_minutes에 문자열을 넣거나 property_id를 빼서 스키마 검증이 걸러내는지 확인하게 해 주세요.{"type":"object","properties":{"property_id":{"type":"string"},"property_name":{"type":"string"},"cleaning_minutes":{"type":["integer","null"]},"buffer_minutes":{"type":"integer"},"city":{"type":"string"}},"required":["property_id","property_name","cleaning_minutes","buffer_minutes","city"]}| Python | 예약을 숙소별로 이어 붙이고 청소 간격을 계산하는 판정 본체입니다. |
| FastAPI | 판정을 화면에서 떼어 낸 2주차의 판정 API입니다. 파일을 받아 판정 결과를 돌려줍니다. |
| React | 사유별로 나뉜 판정 결과와 근거를 담당자가 보는 관리자 화면입니다. |
| TypeScript | 판정 결과의 형태를 화면 쪽에서도 타입으로 묶어 사유 값이 늘어날 때 빠뜨린 화면을 컴파일 단계에서 잡습니다. |
| PostgreSQL | 숙소 마스터와 표기 사전, 실행 이력을 저장합니다. |
| Supabase | PostgreSQL을 띄우고 화면에서 바로 읽는 경로로 씁니다. 1주차 저장소가 여기입니다. |
| AWS | 2주차부터 판정 API와 화면을 AWS로 배포해 운영합니다. |
| OpenAI APIs | 표준 이름을 못 찾은 원문 숙소 표기에 대해 후보를 제시하는 3주차 보조입니다. 판정에는 쓰지 않습니다. |
| GitHub Actions | 판정 규칙을 고칠 때마다 더미 예약으로 회귀 테스트를 돌립니다. |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 판정을 API로 떼어 내고 AWS에 올립니다.
✓ pass → 판정과 화면 분리까지가 한 덩어리입니다. 더 키우려면 표기 맞추기를 사람이 확인해 사전에 쌓습니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · booking_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
feasible받을 수 있음gap_too_short청소 시간 부족overlap예약 겹침spec_missing기준 미입력property_unmapped숙소 미매핑duplicate_booking예약번호 중복같은 숙소에서 이어지는 두 예약 사이의 판정 결과입니다. 다음 손님을 받을 수 있는지와 그렇지 않다면 왜인지를 근거와 함께 담습니다.
{"type":"object","properties":{"booking_no":{"type":"string"},"verdict":{"type":"string","enum":["feasible","gap_too_short","overlap","spec_missing","property_unmapped","duplicate_booking"]},"available_minutes":{"type":["integer","null"]},"required_minutes":{"type":["integer","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{},"quote":{"type":["string","null"]}},"required":["field","value"]}},"next_action":{"type":"string"}},"required":["booking_no","verdict","available_minutes","required_minutes","evidence","next_action"]}[
{
"booking_no": "BK-1002",
"verdict": "feasible",
"available_minutes": 240,
"required_minutes": 210,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-20T11:00"
},
{
"field": "checkin_at",
"value": "2026-08-20T15:00"
},
{
"field": "cleaning_minutes",
"value": 180
},
{
"field": "buffer_minutes",
"value": 30
}
],
"next_action": "청소 시간이 남습니다. 그대로 배차합니다."
},
{
"booking_no": "BK-1016",
"verdict": "feasible",
"available_minutes": 240,
"required_minutes": 240,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-20T11:00"
},
{
"field": "checkin_at",
"value": "2026-08-20T15:00"
},
{
"field": "cleaning_minutes",
"value": 210
},
{
"field": "buffer_minutes",
"value": 30
}
],
"next_action": "필요한 시간과 정확히 같습니다. 지연이 생기면 바로 늦어지므로 우선 배차합니다."
},
{
"booking_no": "BK-1004",
"verdict": "gap_too_short",
"available_minutes": 60,
"required_minutes": 120,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-21T10:00"
},
{
"field": "checkin_at",
"value": "2026-08-21T11:00"
},
{
"field": "cleaning_minutes",
"value": 90
},
{
"field": "buffer_minutes",
"value": 30
}
],
"next_action": "청소 시간이 한 시간 모자랍니다. 입실을 늦추거나 청소 인력을 둘로 붙입니다."
},
{
"booking_no": "BK-1006",
"verdict": "gap_too_short",
"available_minutes": 0,
"required_minutes": 150,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-21T11:00"
},
{
"field": "checkin_at",
"value": "2026-08-21T11:00"
},
{
"field": "cleaning_minutes",
"value": 120
},
{
"field": "buffer_minutes",
"value": 30
}
],
"next_action": "퇴실과 입실이 같은 시각입니다. 입실을 뒤로 미뤄야 합니다."
},
{
"booking_no": "BK-1008",
"verdict": "overlap",
"available_minutes": -60,
"required_minutes": 300,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-20T11:00"
},
{
"field": "checkin_at",
"value": "2026-08-20T10:00"
},
{
"field": "cleaning_minutes",
"value": 240
},
{
"field": "buffer_minutes",
"value": 60
}
],
"next_action": "다음 손님 입실이 앞 손님 퇴실보다 이릅니다. 예약이 겹쳤으므로 채널 쪽 일정을 먼저 고칩니다."
},
{
"booking_no": "BK-1014",
"verdict": "spec_missing",
"available_minutes": 60,
"required_minutes": null,
"evidence": [
{
"field": "checkout_at",
"value": "2026-08-20T11:00"
},
{
"field": "checkin_at",
"value": "2026-08-20T12:00"
},
{
"field": "cleaning_minutes",
"value": null
},
{
"field": "property_id",
"value": "PR-006"
}
],
"next_action": "이 숙소의 청소 소요가 기준표에 없습니다. 통과로 세지 않고 기준을 먼저 채웁니다."
},
{
"booking_no": "BK-1012",
"verdict": "property_unmapped",
"available_minutes": null,
"required_minutes": null,
"evidence": [
{
"field": "property_name_raw",
"value": "Yeouido Suite 21",
"quote": "Yeouido Suite"
}
],
"next_action": "마스터에 없는 숙소 표기입니다. 사전에 추가하기 전에는 숙소별 집계에서 빼 둡니다."
},
{
"booking_no": "BK-1005",
"verdict": "duplicate_booking",
"available_minutes": null,
"required_minutes": null,
"evidence": [
{
"field": "booking_no",
"agg": "count",
"value": 2
}
],
"next_action": "같은 예약번호가 두 번 들어왔습니다. 앞 건으로만 판정하고 뒤 건을 목록에서 뺍니다."
}
][자동 검증 · 1주차] 1. verdict가 enum 여섯 값 안에 있는지 확인합니다. 2. booking_no·verdict·available_minutes·required_minutes·evidence·next_action이 모두 있는지 확인합니다. 3. guest_count가 1 이상 16 이하인지 확인합니다. 4. cleaning_minutes가 null이면 verdict가 spec_missing인지 확인합니다. 5. evidence의 field가 두 입력 표의 컬럼에 실재하는지 확인합니다. 6. evidence의 quote가 그 행 property_name_raw의 일부인지 확인합니다. 7. next_action이 존댓말로 끝나는지 확인합니다. [판정 규칙] 1. 숙소명은 대소문자와 띄어쓰기, 구분자를 없애 소문자로 모은 뒤 property_service_specs의 property_id와 맞춥니다. 맞는 것이 없으면 미매핑으로 남기고 그 행은 나머지 판정에서 뺍니다. 2. 같은 행 번호가 두 번 들어오면 앞 건만 남기고 뒤 건은 적재 단계에서 사유와 함께 뺍니다. [수동 검증] 1. 청소 시간이 모자란다고 판정된 건을 운영 담당자가 읽어 실제로 배차가 불가능한지 확인합니다. 2. BK-1002처럼 표기가 흔들렸지만 사전으로 맞춰진 예약은 미매핑 사유를 달지 않는 것이 맞는지 확인합니다. [평가 러너 · 2주차] 1. 손으로 만든 정답표와 맞춰 사유별로 어긋난 건을 세고 어느 사유에서 많이 틀리는지 나눕니다. 2. 예약과 정답을 작성자가 만든 합성 기준이라 이 수치는 성능 측정이 아니라 데모 조건 동작입니다.
근거: Develop operational dashboards, workflow engines, management platforms