ํต์ฌ ์์ฝ
DB ์ผ์ผ ์ ๊ฒ ๋ฆฌํฌํธ๋ฅผ ์ฒ์์๋ ๋ช ๋๋ง ๋์์ผ๋ก, ํ ํต์ ๋ฉ์ผ์ ์ ๋ถ ๋ด์ ๋ณด๋์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ์ ๊ฒ ๋์์ด ์๋ฐฑ ๋๋ก ๋๋ฉด์ ํ ํต์ ๋ค ๋ด์ ์ ์๊ฒ ๋์ต๋๋ค. ๊ฒ๋ค๊ฐ ์ธ์คํด์ค๋ง๋ค ๋ด๋น์๊ฐ ๋ค๋ฅด๊ณ , ํ ์ธ์คํด์ค๋ฅผ ํ์ฌ ๋ด๋น์์ ํ๋ ฅ์ฌ ๋ด๋น์๊ฐ ํจ๊ป ๊ด๋ฆฌํ๋ ๊ตฌ์กฐ์์ต๋๋ค.
๊ทธ๋์ "๋ด๋น์(์์ ์) ๋จ์๋ก ๋ฆฌํฌํธ๋ฅผ ๋๋ , ๊ฐ์ ์๊ธฐ ๋ด๋น๋ถ๋ง ๋ฐ์๋ณด๊ฒ" ํ๋ ๋ผ์ฐํ
๊ณ์ธต์ ๋ง๋ค์์ต๋๋ค. ์ด ๊ธ์ ๊ทธ ๊ณผ์ ์ ์ฒ์๋ถํฐ ๋๊น์ง, ์ค์ ์ฝ๋์ ํจ๊ป ์ ๋ฆฌํ ๊ฒ์
๋๋ค.
ํฐ ์ค๊ธฐ๋ ๋ค์ฏ ๋จ๊ณ์
๋๋ค.
- ์ง์ ๋ถํ ๋ด๋น์ ๋งคํ ์์ ์ ํ์ค ํ ์ํธ๋ก ์ ์ ํ๋ค.
- ํ์ค ์ํธ๋ฅผ ์ฝ์ด ์ธ์คํด์ค ๋ชฉ๋ก์ ๋ง๋ ๋ค(์ ์ธยท๋ฌดํจ ์ด๋ฉ์ผยท์ง๊ธ ์ ๋ฆฌ ํฌํจ).
- ์ธ์คํด์ค ๋ชฉ๋ก์ ๋ด๋น์ ๊ธฐ์ค์ผ๋ก ๋ค์ง์ด ์์ ์๋ณ๋ก ๋ฌถ๋๋ค.
- ๋ด๋น์ ํ ๋ช ๋ถ ๋ฆฌํฌํธ๋ฅผ ์กฐ๋ฆฝํ๋ค(๊ธฐ์กด ๋จ์ผ ๋ฆฌํฌํธ ๋ ๋๋ฌ๋ฅผ ํต์งธ๋ก ์ฌ์ฌ์ฉ).
- ์์ ์ ์ ์์ ์ผ๊ด ์์ฑํ๊ณ ๋ฐ์ก ์ด๋ ฅ์ ๋จ๊ธด๋ค.
์ค๊ฐ์ ํ ๋ฒ ํท๊ฐ๋ ธ๋ ์ง์ โ "๊ฐ์ ์ธ์คํด์ค๋ฅผ ๋ ์ฌ๋์๊ฒ ๋ณด๋ด๋ฉด ์ ๊ฒ ๊ฑด์๊ฐ ๋ ๋ฐฐ๋ก ์กํ๋ ๊ฒ ์๋๊ฐ" โ ์ ์ด๋ป๊ฒ ์ ๋ฆฌํ๋์ง๋ ๋ด์์ต๋๋ค. ์ด๋ฐ "์นด์ดํธ์ ํจ์ "์ ์ค๋ฌด์์ ์๊ทผํ ์์ฃผ ๋์ค๋ ์ฃผ์ ๋ผ, ๋น์ทํ ์ํฉ์ ๋ง๋๋ ๋ถ๋ค๊ป ๋์์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
1. ๋ฐฐ๊ฒฝ โ ํ ํต์ผ๋ก๋ ๊ฐ๋น์ด ์ ๋ฉ๋๋ค
์ด๊ธฐ ๊ตฌ์กฐ๋ ๋จ์ํ์ต๋๋ค. ์ค์ ํ์ผ์ ์ ๊ฒํ DB๋ฅผ ๋ช ๊ฐ ์ ์ด ๋๋ฉด, ํ๋ก๊ทธ๋จ์ด ์์๋๋ก ์์ง โ ์ด์ ํ์ง โ ๋ฆฌํฌํธ๋ฅผ ๋ง๋ค์ด ์ ํด์ง ์ฃผ์๋ก ํ ํต ๋ณด๋์ต๋๋ค. ๋์์ด ์๋ ๋์ผ ๋๋ ์ด๊ฒ ์ ์ผ ๊น๋ํฉ๋๋ค. ๋ฆฌํฌํธ ํ๋์ ์ ๋ถ ๋ค์ด๊ฐ๊ณ , ๋ฐ๋ ์ฌ๋๋ ํ ๋ช
์ด๋ฉด ๋ฉ๋๋ค.
๊ทธ๋ฐ๋ฐ ์ค์ ์ด์ ๋์์ ์๋ฐฑ ๋์์ต๋๋ค. ์ด๊ฑธ ํ ํต์ ๋ค ๋ฃ์ผ๋ฉด ์คํฌ๋กค์ด ๋์์ด ๊ธธ์ด์ ธ์, ์ ์ ๋ด์ผ ํ ์ฌ๋์ ์๊ธฐ ๊ฒ์ ์ฐพ์ง ๋ชปํฉ๋๋ค. ๋ ์ค์ํ ๊ฑด ๋ด๋น์๊ฐ ์ธ์คํด์ค๋ง๋ค ๋ค๋ฅด๋ค๋ ์ ์ด์์ต๋๋ค. ์ด๋ค ์๋น์ค๋ A๊ฐ, ์ด๋ค ์๋น์ค๋ B๊ฐ ๋ด๋นํ๊ณ , ํ ์ธ์คํด์ค๋ฅผ ํ์ฌ ์ชฝ ๋ด๋น์์ ์ค์ ์ด์์ ๋งก์ ํ๋ ฅ์ฌ ๋ด๋น์๊ฐ ๊ฐ์ด ๋ด
๋๋ค.
์ฌ๊ธฐ์ ์๊ตฌ์ฌํญ์ด ์์ฐ์ค๋ฝ๊ฒ ์ ๋ฆฌ๋์ต๋๋ค.
- ๋ด๋น์๋ ์๊ธฐ๊ฐ ๋งก์ ์ธ์คํด์ค๋ง ๋ด๊ธด ๋ฆฌํฌํธ๋ฅผ ๋ฐ๋๋ค.
- ํ ๋ด๋น์๊ฐ ์ฌ๋ฌ ์๋น์ค๋ฅผ ๋งก์ผ๋ฉด, ๊ทธ ํ ํต ์์์ ์๋น์ค๋ณ๋ก ๋๋ ๋ณด์ฌ์ค๋ค.
- ํ ์ธ์คํด์ค์ ๋ด๋น์๊ฐ ๋(ํ์ฌยทํ๋ ฅ์ฌ)์ด๋ฉด ๋ ๋ค ๋ฐ๋๋ค.
- ๋๊ตฌ์๊ฒ ์ธ์ ๋ฌด์์ ๋ณด๋๋์ง ์ด๋ ฅ์ด ๋จ์์ผ ํ๋ค.
๋น์ ํ์๋ฉด, ๋ํ ์ํํธ ๋จ์ง์ ๊ด๋ฆฌ์ฌ๋ฌด์์์ "์ค๋ ์ ๊ฒ ๊ฒฐ๊ณผ"๋ฅผ ์ฃผ๋ฏผ ์ ์ฒด์๊ฒ ํ ์ฅ์ผ๋ก ๋ถ์ด๋ ๋์ , ๋ยทํธ์๋ณ ๋ด๋น ๋ผ์ธ์ ๋ง์ถฐ ๊ฐ์์๊ฒ ํ์ํ ๋ถ๋ถ๋ง ์ ๋ฌํ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. ์ ์ฒด ๊ณต์ง ํ ์ฅ์ ์๋ฌด๋ ์๊ธฐ ๊ฒ์ ๋ชป ์ฐพ์ง๋ง, ๋ผ์ธ๋ณ๋ก ์๋ผ ์ฃผ๋ฉด ๊ฐ์ ์๊ธฐ ๊ฒ๋ง ๋ณด๋ฉด ๋ฉ๋๋ค.
์ด ๊ธ์์ ๋ง๋๋ ํ์ผ๋ค์ ๋จผ์ ํ ์ค์ฉ ์๊ฐํ๋ฉด ์ด๋ ์ต๋๋ค. ๋ค์์ ํ๋์ฉ ์์ธํ ๋ค๋ฃน๋๋ค.
gen_routing.pyโ (์ผํ์ฑ) ์ง์ ๋ถํ ์๋ณธ ๋งคํ โ ํ์ค ํ ์ํธ๋ก ์ ์ .build_accounts.pyโ (์์) ํ์ค ์ํธ โ ์ธ์คํด์ค ๋ชฉ๋ก(accounts.yaml).routing.pyโ ์ธ์คํด์ค ๋ชฉ๋ก์ ๋ด๋น์ ๊ธฐ์ค์ผ๋ก ๋ค์ง์ด ์์ ์๋ณ๋ก ๋ฌถ์.recipient_report.pyโ ๋ด๋น์ ํ ๋ช ๋ถ ๋ฆฌํฌํธ ์กฐ๋ฆฝ.run_routing.pyโ ์์ ์ ์ ์ ์ผ๊ด ์์ฑ + ๋ฐ์ก ์ด๋ ฅ.
2. ์ฒซ ๋ฒ์งธ ๋ฌธ์ โ ๋งคํ ๋ฐ์ดํฐ๊ฐ ์ง์ ๋ถํฉ๋๋ค
๋ด๋น์ ๋งคํ์ ์์ ๋ก ๋ฐ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด ํ์ผ์ด ์ฌ๋์ด ์์ผ๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ผ ํํ๊ฐ ์ ๊ฐ๊ฐ์ด์์ต๋๋ค. ์ค์ ๋ก ๋ง์ฃผ์น ๋ฌธ์ ๋ค์ ๋์ดํ๋ฉด ์ด๋ ์ต๋๋ค.
- ์ํธ๊ฐ ๋ ๊ฐ์์ต๋๋ค. ํด๋ผ์ฐ๋ ์ข ๋ฅ์ ๋ฐ๋ผ ํ ์ํธ๋ ์ด๋ฐ ์ปฌ๋ผ, ๋ค๋ฅธ ์ํธ๋ ์ ๋ฐ ์ปฌ๋ผ์ด์์ต๋๋ค.
- ํ ์ํธ์๋ '์ ์ธ' ์ปฌ๋ผ์ด ์๊ณ , ๋ค๋ฅธ ์ํธ์๋ ์์์ต๋๋ค.
- ์๋น์ค ์ด๋ฆ์ด ๊ทธ๋ฃน์ ์ฒซ ํ์๋ง ์ ํ ์๊ณ , ์๋ ํ๋ค์ ๋น์ด ์์์ต๋๋ค(์์ ์์ ํํ ๋ณด๋, ๋ณํฉ์ ์ฒ๋ผ ์์๋ง ์ฐ๊ณ ์๋๋ก ์ด์ด์ง๋ ํ๊ธฐ).
- ์์ง ์ด๋ฆ ํ๊ธฐ๊ฐ ์ ๊ฐ๊ฐ์ด์์ต๋๋ค.
aurora-mysql,MariaDB,Azure MySQL,MySQLโ ์ฌ์ค์ ๊ฐ์ ๊ณ์ด์ธ๋ฐ ๋ฌธ์์ด์ด ๋ค ๋ฌ๋์ต๋๋ค.
์ด๊ฑธ ๊ทธ๋๋ก ํ๋ก๊ทธ๋จ์ด ์ฝ๊ฒ ํ๋ฉด, ํ์ฑ ์ฝ๋๊ฐ ๋ ์ํธ์ ์๋ก ๋ค๋ฅธ ๊ท์น์ ์ ๋ถ ํก์ํด์ผ ํด์ ๋ณต์กํด์ง๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ณต์กํ ์ฝ๋๋ฅผ ๋งค๋ฒ ๋๋ ค์ผ ํฉ๋๋ค. ๊ทธ๋์ ๋ฐฉํฅ์ ์ด๋ ๊ฒ ์ก์์ต๋๋ค.
- ์ ๊ทํ(ํ ๋ฒ๋ง) โ ์ง์ ๋ถํ ์๋ณธ์ ์ฝ์ด, ํ์ค ํ ์ํธ ์์์ผ๋ก ๋ค์ ๋ฝ์๋ธ๋ค.
- ์ฝ๊ธฐ(์์) โ ์์ผ๋ก๋ ๊ทธ ํ์ค ์์๋ง ์ฝ๋๋ค. ์ด๊ฑด ์งง์ ์ฝ๋๋ฉด ์ถฉ๋ถํ๋ค.
์ง์ ๋ถํจ์ ํก์ํ๋ ๋ฌด๊ฑฐ์ด ์ฝ๋๋ ๋ฑ ํ ๋ฒ๋ง ์ฐ๊ณ , ์ดํ์ ๊น๋ํ ํ์ค๋ง ์๋ํ์๋ ๊ฒ๋๋ค. ๋ง์น ์ฌ๋ฌ ์ฌ๋์ด ์๊ธ์จ๋ก ์ ๊ฐ๊ฐ ์จ ์จ ์ ์ฒญ์๋ฅผ, ํ ๋ฒ ํ์ค ์์์ ์ฎ๊ฒจ ์ ๊ณ ๋๋ฉด ๊ทธ๋ค์๋ถํฐ๋ ๊ทธ ํ์ค ์์๋ง ๋ณด๋ฉด ๋๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
์์ง ์ด๋ฆ์ '๊ณ์ด'๋ก ๋ฌถ๊ธฐ
์ ๊ทํ์์ ๊ฐ์ฅ ์ค์ํ ๊ฑด ์์ง ์ด๋ฆ ํต์ผ์ด์์ต๋๋ค. ์๊ณ๊ฐ์ด๋ ์ ๊ฒ ์งํ๋ ๊ฐ๋ณ ํ๊ธฐ(aurora-mysql)๊ฐ ์๋๋ผ ๊ณ์ด(mysql) ๊ธฐ์ค์ผ๋ก ์ ์ฉ๋๊ธฐ ๋๋ฌธ์
๋๋ค. aurora-mysql์ด๋ MariaDB๋ Azure MySQL์ด๋ , ์ฐ๋ฆฌ ์
์ฅ์์๋ "MySQL ๊ณ์ด"๋ก ๋ฌถ์ด ๊ฐ์ ์๊ณ๊ฐ ์ธํธ๋ฅผ ์๋๋ค.
๊ทธ๋์ ๋ฌธ์์ด์ ๋ณด๊ณ ๊ณ์ด์ ํ์ ํ๋ ํจ์๋ฅผ ๋ง๋ค์์ต๋๋ค.
def engine_family(raw: str) -> str:
"""์๋ณธ ์์ง๋ช
โ ์๊ณ๊ฐยท๋ชจ๋ํฐ๋ง์ ๋ฌถ๋ ๊ณ์ด. (postgres ๋ฅผ aurora/mysql ๋ณด๋ค ๋จผ์ ๊ฒ์ฌ)"""
s = (raw or "").strip().lower()
if not s:
return "unknown"
if "oracle" in s:
return "oracle"
if "postgres" in s:
return "postgresql"
if "docdb" in s or "documentdb" in s or "mongo" in s:
return "documentdb"
if "sql server" in s or "sqlserver" in s or "mssql" in s:
return "sqlserver"
if "mysql" in s or "maria" in s or "aurora" in s:
return "mysql"
return "other"
์ฌ๊ธฐ์ ์์๊ฐ ์ค์ํฉ๋๋ค. aurora-postgresql์๋ aurora๋ ๋ค์ด ์๊ณ postgres๋ ๋ค์ด ์์ต๋๋ค. ๋ง์ฝ aurora โ mysql ๊ท์น์ ๋จผ์ ๋๋ฉด, aurora-postgresql์ด "aurora๊ฐ ๋ค์ด ์๋ค" ํ๋ฉฐ MySQL๋ก ์๋ชป ๋ถ๋ฅ๋ฉ๋๋ค. ๊ทธ๋์ postgres ๊ฒ์ฌ๋ฅผ aurora๋ณด๋ค ์์ ๋์ต๋๋ค.
์ด๋ฐ "๋ฌธ์์ด ํฌํจ ๊ฒ์ฌ"๋ ์์์ ๋ฏผ๊ฐํฉ๋๋ค. ๊ฒน์น๋ ํค์๋๊ฐ ์์ ๋๋ ๋ ๊ตฌ์ฒด์ ์ธ(ํน์ ๋ ์ข์) ์กฐ๊ฑด์ ๋จผ์ ๊ฑธ๋ฌ์ผ ํฉ๋๋ค. ๋ง์น ์ฐํธ๋ฌผ์ ๋ถ๋ฅํ ๋ "์์ธ์"๋ก ๋จผ์ ๋๋๋ฉด "์์ธ์ ๊ฐ๋จ๊ตฌ"๊ฐ ์๋ฑํ ์นธ์ ๋ค์ด๊ฐ๋ ๊ฒ๊ณผ ๊ฐ์์, ์ข์ ์กฐ๊ฑด(๊ตฌยท๋)๋ถํฐ ๊ฑธ๋ฌ์ผ ํ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค.
์ค์ ๋ก ์๋ณธ์๋ ์ด๋ฐ ํ๊ธฐ๋ค์ด ์์ฌ ์์๊ณ , ์ ํจ์๋ฅผ ๊ฑฐ์น๋ฉด ์๋์ฒ๋ผ ์ ๋ฆฌ๋ฉ๋๋ค.
aurora-mysql โ mysql
MariaDB โ mysql
Azure MySQL โ mysql
aurora-postgresql โ postgresql (aurora ๊ฐ ๋ค์ด ์์ด๋ postgres ์ฐ์ )
oracle-se2 โ oracle
docdb โ documentdb
Azure SQL Server โ sqlserver
ํ์ค ์์์ ์์ ๋ก ๋ค์ ๋ด๋ณด๋ด๊ธฐ
์ ๊ทํ ๊ฒฐ๊ณผ๋ ํ๋ก๊ทธ๋จ ๋ด๋ถ ์๋ฃ๋ก๋ง ์ฐ์ง ์๊ณ , ์์ ๋ก๋ ๋ด๋ณด๋์ต๋๋ค. ์์ผ๋ก ๋ด๋น์๋ ์ ์ธ ์ฌ๋ถ๊ฐ ๋ฐ๋๋ฉด ์ฌ๋์ด ์ด ํ์ค ํ ์ํธ๋ง ๊ณ ์น๊ณ ๋ค์ ์ฝ์ผ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฝ๋๋ ์ด๋ ๊ฒ, ํ์ค ์ปฌ๋ผ ์์๋ก ํ ์ค์ฉ ์ฑ์ ์ ์ฅํฉ๋๋ค.
def dump_xlsx(insts, path):
"""์ ์ ๋ ๋งคํ์ 'ํ ์ํธ' ํ์ค ์์ ์์
๋ก ์ ์ฅ (์ ์ธ ํฌํจ ์ ์ฒด)."""
wb = Workbook()
ws = wb.active
ws.title = "์ธ์คํด์ค_๋ด๋น์_๋งคํ"
headers = ["์ฐ๋ฒ", "ํด๋ผ์ฐ๋", "์๋น์ค(Account)", "์ธ์คํด์ค ID", "์์ง", "์์ง๊ณ์ด",
"ํ์ฌ ๋ด๋น์", "ํ์ฌ ์ด๋ฉ์ผ", "ํ๋ ฅ์ฌ ๋ด๋น์", "ํ๋ ฅ์ฌ ์ด๋ฉ์ผ", "์ ์ธ"]
# ... ํค๋ ์คํ์ผ ์ง์ ...
for i, inst in enumerate(sorted(insts, key=lambda x: (x["account"], x["id"])), start=1):
jj_name, jj_mail = _role_pair(inst["contacts"], "ํ์ฌ")
hp_name, hp_mail = _role_pair(inst["contacts"], "ํ๋ ฅ์ฌ")
vals = [i, inst["cloud"], inst["account"], inst["id"], inst["engine"],
inst["engine_family"], jj_name, jj_mail, hp_name, hp_mail,
inst.get("_exclude_raw", "")]
# ... ํ ํ์ฉ ๊ธฐ๋ก, ์ ์ธ ํ์ ํ์ ์ฒ๋ฆฌ ...
wb.save(path)
์ฌ๊ธฐ์ ํ ๊ฐ์ง ๊ฒฐ์ ์ด ์์์ต๋๋ค. ์ ์ธ๋ ์ธ์คํด์ค๋ ๋ฒ๋ฆฌ์ง ์๊ณ , '์ ์ธ' ํ์๋ง ๋จ๊ธด ์ฑ ํจ๊ป ์ ์ฅํ์ต๋๋ค. ๋์ค์ ๋ด๋น์๊ฐ "์ด๊ฑด ๋ค์ ์ ๊ฒ ๋์์ ๋ฃ์"๋ผ๊ณ ํ๋ฉด, ์ ์ธ ํ์๋ง ์ง์ฐ๋ฉด ๋๋๋ก์. ์ง์ ๋ฒ๋ฆฌ๋ฉด ๋์ด๋ฆด ๋ ๋ค์ ์ฐพ์ ์ ์ด์ผ ํ์ง๋ง, ํ์๋ง ๋จ๊ธฐ๋ฉด ํ ๊ธ ํ ๋ฒ์ด๋ฉด ๋ฉ๋๋ค.
์ด ์ ๊ทํ ์คํฌ๋ฆฝํธ(gen_routing.py)๋ ํ์ค ์์์ ์ฒ์ ๋ง๋ค์ด ๋ด๋ '๋ถํธ์คํธ๋ฉ' ์ญํ ๋ง ํ๊ณ ๋ฌผ๋ฌ๋ฉ๋๋ค. ํ ๋ฒ ํ์ค์ ๋ง๋ค์ด ๋๋ฉด, ๊ทธ๋ค์๋ถํฐ ์์๋ก ์ฐ๋ ๊ฑด ์๋ 3์ ์ ์งง์ reader์
๋๋ค.
3. ํ์ค ์์์ ์ฝ์ด ์ธ์คํด์ค ๋ชฉ๋ก ๋ง๋ค๊ธฐ
ํ์ค ์์์ด ์๊ธฐ๊ณ ๋๋, ๊ทธ๊ฑธ ์ฝ์ด ํ๋ก๊ทธ๋จ์ด ์ธ ํํ๋ก ๋ฐ๊พธ๋ ์ฝ๋๋ ์์ฃผ ์งง์์ก์ต๋๋ค. ์ด๊ฒ ๋ฐ๋ก "๋ฌด๊ฑฐ์ด ์ฝ๋๋ ํ ๋ฒ๋ง" ์ ๋ต์ ๋ณด์์ ๋๋ค.
def load_accounts(xlsx):
"""์ ์ ์์ ์์
ํ ์ํธ๋ฅผ ์ฝ์ด ํ์ฑ ์ธ์คํด์ค ๋ ์ฝ๋ ๋ฆฌ์คํธ๋ก ๋ฐํ."""
ws = load_workbook(xlsx, read_only=True, data_only=True).active
out = []
for r in ws.iter_rows(min_row=2, values_only=True):
cloud, account, iid, engine, fam = _s(r[1]), _s(r[2]), _s(r[3]), _s(r[4]), _s(r[5])
jj_name, jj_mail, hp_name, hp_mail, ex = _s(r[6]), _s(r[7]), _s(r[8]), _s(r[9]), _s(r[10])
if not iid:
continue
if ex.upper() == "O": # ์ ์ธ ํ์๋ ์ธ์คํด์ค๋ ๋์์์ ๋บ๋ค
continue
contacts = []
if jj_mail and jj_mail != "0":
contacts.append({"role": "ํ์ฌ", "name": _clean_name(jj_name), "email": jj_mail})
if hp_mail and hp_mail != "0":
contacts.append({"role": "ํ๋ ฅ์ฌ", "name": _clean_name(hp_name), "email": hp_mail})
out.append({
"id": iid, "account": account, "cloud": cloud,
"engine": engine, "engine_family": fam, "contacts": contacts,
})
return out
์ฌ๊ธฐ์ ์ธ ๊ฐ์ง๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
- ์ ์ธ(O) ํ์๋ ์ธ์คํด์ค๋ ๋ฐ์ก ๋์์์ ๋บ๋๋ค. ์ ์ฒด ๋์์์ ์ ์ธ๋ฅผ ๋นผ๋ฉด ์ค์ ํ์ฑ ๋์๊ฐ ๋์ต๋๋ค. ์๋ฅผ ๋ค์ด ํ์ 322ํ์ด ์๊ณ ๊ทธ์ค 132ํ์ด ์ ์ธ๋ผ๋ฉด, ํ์ฑ์ 190๋์ ๋๋ค.
- ์ด๋ฉ์ผ์ด ๋น์๊ฑฐ๋
0(์์ง ์ ์ ํด์ง)์ธ ๋ด๋น์๋ ์ฐ๋ฝ์ฒ์์ ๋บ๋๋ค. ๋์ค์ ๋ฌดํจ ์ฃผ์๋ก ๋ฐ์ก์ ์๋ํ๋ค ์คํจํ์ง ์๊ธฐ ์ํด์์ ๋๋ค. ๋ด๋น์๊ฐ ์์ง ์ ํด์ง์ง ์์ ์๋ฆฌ๋ฅผ0์ผ๋ก ํ์ํด ๋ ์นธ์ด ์ค์ ๋ก ์์์ต๋๋ค. - ์ด๋ฆ์ ๋ถ์ ์ง๊ธ์ ๋๋๋ค(๋ฐ๋ก ์๋์์ ์ค๋ช ).
์ด๋ ๊ฒ ๋์จ ์ธ์คํด์ค ํ๋๋ ์ด๋ฐ ๋ชจ์์ ๋๋ค. ์ด๊ฒ ์ดํ ๋ชจ๋ ๋จ๊ณ์ ๊ธฐ๋ณธ ๋จ์์ ๋๋ค.
{
"id": "svc-db-01", # ์ธ์คํด์ค ๊ณ ์ id
"account": "์๋น์คA", # ์ด๋ ์๋น์ค(Account) ์์์ธ์ง
"cloud": "aws",
"engine": "aurora-mysql", # ์๋ณธ ์์ง๋ช
(๊ทธ๋๋ก ๋ณด์กด)
"engine_family": "mysql", # ๊ณ์ด(์๊ณ๊ฐยท์งํ ์ ์ฉ ๊ธฐ์ค)
"contacts": [
{"role": "ํ์ฌ", "name": "๊นOO", "email": "kim@example.com"},
{"role": "ํ๋ ฅ์ฌ", "name": "์ดOO", "email": "lee@partner.com"},
],
}
์ด๋ฆ์์ ์ง๊ธ ๋ผ๊ธฐ
์๋ณธ ๋ด๋น์ ์ด๋ฆ์๋ "OOO ๋ถ์ฅ", "OOO ์ธ" ์ฒ๋ผ ์ง๊ธ์ด๋ ๊ผฌ๋ฆฌํ๊ฐ ๋ถ์ด ์๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค. ๋ฆฌํฌํธ์๋ ์ง๊ธ ์์ด ์ด๋ฆ๋ง ๋์ค๋ ํธ์ด ๊น๋ํด์, ์ด๋ฆ ๋ค์ ๋ถ์ ์ง๊ธยท๊ฒฝ์นญ ํ ํฐ์ ๋ผ์ด๋์ต๋๋ค.
_TITLES = {"๋ถ์ฅ", "์ฐจ์ฅ", "๊ณผ์ฅ", "๋๋ฆฌ", "์ฑ
์", "์ ์", "ํ์ฅ", "๋", "์จ", "์ธ", ...}
def _clean_name(name):
"""'ํ๊ธธ๋ ๋ถ์ฅ' โ 'ํ๊ธธ๋', 'ํ๊ธธ๋ ์ธ' โ 'ํ๊ธธ๋'."""
parts = name.split()
while parts and parts[-1] in _TITLES: # ๋ค์์๋ถํฐ ์ง๊ธ ํ ํฐ์ด๋ฉด ํ๋์ฉ ๋๋ค
parts.pop()
return " ".join(parts)
๋ค์์๋ถํฐ ํ์ด ์ง๊ธ์ด๋ฉด ๋ผ๋ ๋ฐฉ์์ด๋ผ, "OOO ์ฑ
์ ๋"์ฒ๋ผ ์ฌ๋ฌ ๊ฐ๊ฐ ๋ถ์ด ์์ด๋ ๋ค ์ ๋ฆฌ๋ฉ๋๋ค. ๋ฆฌ์คํธ์ ๋์ ํ๋์ฉ ๋ฒ๊ฒจ ๋ด๋ ์
์
๋๋ค.
์ฌ๊ธฐ์๋ ๋ป๋ฐ์ ๋ณด๋์ค๊ฐ ์์์ต๋๋ค. ๊ฐ์ ์ฌ๋์ด ์ด๋ค ํ์๋ "ํ๊ธธ๋", ์ด๋ค ํ์๋ "ํ๊ธธ๋ ์ธ"๋ก ๋ค๋ฅด๊ฒ ์ ํ ์๋ ๊ฒฝ์ฐ๊ฐ ์์๋๋ฐ, ์ง๊ธยท๊ผฌ๋ฆฌํ๋ฅผ ๋ผ๊ณ ๋๋ ๋ ๋ค "ํ๊ธธ๋"์ด ๋์ด, ๋์ค์ ์ด๋ฉ์ผ๋ก ๋ฌถ์ ๋ ํ ๋ช
์ผ๋ก ๊น๋ํ๊ฒ ํฉ์ณ์ก์ต๋๋ค. ์๋ ๋
ธ๋ฆฌ๋ ๊ฑด "์ง๊ธ ์ ๊ฑฐ"์๋๋ฐ, ๋ค์ผ๋ก "ํ๊ธฐ ํ๋ค๋ฆผ ์ ๋ฆฌ"๊น์ง ๋ ์
์
๋๋ค.
4. ๋ผ์ฐํ โ ํ๋ฅผ '๋ค์ง์ด' ์์ ์๋ณ๋ก ๋ฌถ๊ธฐ
์ด ๋จ๊ณ๊ฐ ๋ผ์ฐํ
์ ํต์ฌ์
๋๋ค. ํ ๋ฌธ์ฅ์ผ๋ก ์ค์ด๋ฉด "ํ๋ฅผ ๋ค์ง๋๋ค" ์
๋๋ค.
๋ฐ์ ๋งคํ ํ๋ DB(์ธ์คํด์ค) ๊ธฐ์ค์
๋๋ค. "์ด DB๋ ๋๊ฐ ๋ด๋นํ๋ค"๊ฐ ํ ์ค์ฉ ์ ํ ์์ต๋๋ค.
[์๋ณธ = DB ๊ธฐ์ค]
DB-A โ ํ์ฌ๋ด๋น ๊นOO, ํ๋ ฅ์ฌ๋ด๋น ์ดOO
DB-B โ ํ์ฌ๋ด๋น ๊นOO, ํ๋ ฅ์ฌ๋ด๋น ์ดOO
DB-C โ ํ์ฌ๋ด๋น ๋ฐOO, ํ๋ ฅ์ฌ๋ด๋น ์ตOO
๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ๊ฐ ๋ณด๋ด๋ ๊ฑด ์ฌ๋๋ง๋ค ๋ฉ์ผ ํ ํต์ ๋๋ค. ๊ทธ๋ฌ๋ ์ด ํ๋ฅผ ๋ด๋น์ ๊ธฐ์ค์ผ๋ก ๋ค์ง์ด์ผ ํฉ๋๋ค. "๋ด๋น์ โ ๊ทธ๊ฐ ๋งก์ DB๋ค"๋ก์.
[๋ค์ง์ ๊ฒ = ๋ด๋น์ ๊ธฐ์ค]
๊นOO โ [DB-A, DB-B, ...] โ ๊นOO ์๊ฒ ์ด DB๋ค์ ๋ด์ ํ ํต
์ดOO โ [DB-A, DB-B] โ ์ดOO ์๊ฒ ํ ํต
๋ฐOO โ [DB-C, ...] โ ๋ฐOO ์๊ฒ ํ ํต
'๋งคํ'์ด ํท๊ฐ๋ฆฐ๋ค๋ฉด ๋๊ฐ ์ด ๋ ๋ฐฉํฅ ๋๋ฌธ์
๋๋ค. ์๋ณธ์ DB๊ฐ ์ฃผ์ธ๊ณต(DB๋ง๋ค ๋ด๋น์๊ฐ ๋ถ์), ์ฐ๋ฆฌ๊ฐ ๋ง๋๋ ๊ฑด ์ฌ๋์ด ์ฃผ์ธ๊ณต(์ฌ๋๋ง๋ค DB ๋ชฉ๋ก). ์ด ๋ค์ง๊ธฐ ํ ๋ฒ์ด ๋ผ์ฐํ
์ ์ ๋ถ๋ผ๊ณ ๋ด๋ ๋ฉ๋๋ค.
๋น์ ํ์๋ฉด, ์ ํ๋ฒํธ๋ถ๋ฅผ "๋ฒํธ โ ์ด๋ฆ" ์์์์ "์ด๋ฆ โ ๊ทธ ์ฌ๋์ ๋ฒํธ๋ค"๋ก ๋ค์ ์ฎ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๋๋ ๋ฌผ๊ฑด๋ง๋ค ์ฃผ์ธ ์คํฐ์ปค๊ฐ ๋ถ์ด ์๋ ์ฐฝ๊ณ ์์, ์ด๋ฒ์ ์ฃผ์ธ๋ณ๋ก ๋ฌผ๊ฑด์ ํ ๋ฐ๊ตฌ๋์ฉ ๋ชจ์ผ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
๋ฐฉํฅ์ ๋ช
ํํ ์ก๊ณ ๊ฐ๋๋ค.
- ๋ฐ์กยท๊ธฐ๋ก์ ๋จ์๋ ์์ ์(์ฌ๋)์ ๋๋ค. ํ ์ฌ๋ = ํ ํต.
- ์๋น์ค๋ ๊ทธ ์ฌ๋ ๋ฆฌํฌํธ ์์์์ ์๋ถ๋ฅ์ผ ๋ฟ, ๋ฐ์ก ๋จ์๊ฐ ์๋๋๋ค.
๋ด๋น์๋ฅผ ํํํ๋ ์์ ํด๋์ค
๋ค์ง์ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ ๊ทธ๋ฆ์ผ๋ก, ๋ด๋น์ ํ ๋ช ์ ๋ํ๋ด๋ ์์ ํด๋์ค๋ฅผ ๋์ต๋๋ค. ์ด๋ฉ์ผ์ด ์ ์์ด๊ณ , ์ด๋ฆยท์ญํ ์ ํ์์ฉ์ด๋ฉฐ, ๊ทธ๊ฐ ๋งก์ ์ธ์คํด์ค ๋ชฉ๋ก์ ๋ค๊ณ ์์ต๋๋ค.
class Recipient:
"""ํ ๋ช
์ ์์ ๋ด๋น์์ ๊ทธ๊ฐ ๋ฐ์๋ณผ ์ธ์คํด์ค๋ค."""
def __init__(self, email: str):
self.email = email
self.names: set = set() # ํ๊ธฐ ์ด๋ฆ(๊ฐ์ ์ฌ๋์ด ์ฌ๋ฌ ํ๊ธฐ๋ฉด ํฉ์ณ์ง)
self.roles: set = set() # ํ์ฌ / ํ๋ ฅ์ฌ (์์ชฝ ๋ค์ผ ์ ์์)
self.instances: list = []
self._seen_ids: set = set() # ์์ ์ ๋ด ์ธ์คํด์ค ์ค๋ณต ๋ฐฉ์ง(id ๊ธฐ์ค)
def add_instance(self, inst: dict) -> bool:
"""์ธ์คํด์ค๋ฅผ ๋ด๋, ๊ฐ์ ์์ ์ ์์์ ๊ฐ์ ์ธ์คํด์ค๋ ํ ๋ฒ๋ง(์ค๋ณต ์นด์ดํธ ๋ฐฉ์ง)."""
iid = inst.get("id", "")
if iid in self._seen_ids:
return False
self._seen_ids.add(iid)
self.instances.append(inst)
return True
@property
def services(self):
"""์๋น์ค๋ช
โ ์ธ์คํด์ค ๋ฆฌ์คํธ. (๋ฆฌํฌํธ ์์์ ์๋น์ค๋ณ๋ก ๋๋ ๋ณด์ฌ์ฃผ๊ธฐ ์ํ ์๋ถ๋ฅ)"""
svc = OrderedDict()
for inst in sorted(self.instances, key=lambda x: (x["account"], x["id"])):
svc.setdefault(inst["account"], []).append(inst)
return svc
names์ roles๋ฅผ set์ผ๋ก ๋ ์ด์ ๊ฐ ์์ต๋๋ค. ๊ฐ์ ์ด๋ฉ์ผ์ด ์ฌ๋ฌ ํ์ ๋ฑ์ฅํ๋ฉด์ ์ด๋ฆ ํ๊ธฐ๊ฐ ์กฐ๊ธ์ฉ ๋ค๋ฅด๊ฑฐ๋, ์ด๋ค ์๋น์ค์์๋ ํ์ฌ ๋ด๋นยท๋ค๋ฅธ ์๋น์ค์์๋ ํ๋ ฅ์ฌ ๋ด๋น์ผ๋ก ์กํ ์ ์์ต๋๋ค. set์ ๊ทธ๋๊ทธ๋ ๋ฃ์ผ๋ฉด ์๋์ผ๋ก ์ค๋ณต์ด ํฉ์ณ์ง๋๋ค.
๋ค์ง๋ ์ฝ๋
์ค์ ๋ก ๋ค์ง๋ ์ฝ๋๋ ๋จ์ํฉ๋๋ค. ์ธ์คํด์ค๋ฅผ ํ๋์ฉ ํ์ผ๋ฉด์, ๊ทธ ์ธ์คํด์ค์ ๋ด๋น์๋ง๋ค "๊ทธ ๋ด๋น์ ๋ฐ๊ตฌ๋(Recipient)"์ ์ธ์คํด์ค๋ฅผ ๋ฃ์ต๋๋ค. ํ ์ธ์คํด์ค์ ๋ด๋น์๊ฐ ๋(ํ์ฌยทํ๋ ฅ์ฌ)์ด๋ฉด ๋ ๋ฐ๊ตฌ๋์ ๊ฐ๊ฐ ๋ค์ด๊ฐ๋๋ค.
def group_by_recipient(instances: list):
"""์ธ์คํด์ค๋ค์ ๋ด๋น์(์ด๋ฉ์ผ)๋ณ Recipient ๋ก ๋ฌถ๋๋ค.
ํ ์ธ์คํด์ค๋ ์์ ์ ๋ชจ๋ ๋ด๋น์(ํ์ฌยทํ๋ ฅ์ฌ) ๊ทธ๋ฃน์ ๊ฐ๊ฐ ํฌํจ๋๋ค.
์ด๋ฉ์ผ์ด ์๊ฑฐ๋ '0'(๋ฏธ์ )์ธ ๋ด๋น์๋ ๊ฑด๋๋ด๋ค."""
recips = OrderedDict()
for inst in instances:
for c in inst.get("contacts", []):
email = (c.get("email") or "").strip()
if not email or email == "0":
continue
r = recips.get(email)
if r is None:
r = recips[email] = Recipient(email)
r.names.add((c.get("name") or "").strip())
r.roles.add((c.get("role") or "").strip())
r.add_instance(inst) # id ๊ธฐ์ค dedup(๊ฐ์ ์์ ์ ๋ด ์ค๋ณต ๋ฐฉ์ง)
return OrderedDict(sorted(recips.items()))
์ฌ๊ธฐ์ ์ด๋ฉ์ผ์ key๋ก ์๋๋ค. ์ฌ๋ ์ด๋ฆ์ด ์๋๋ผ ์ด๋ฉ์ผ ์ฃผ์๊ฐ ์ง์ง ์๋ณ์์
๋๋ค. ์ด๋ฆ์ ํ๊ธฐ๊ฐ ์กฐ๊ธ์ฉ ๋ค๋ฅผ ์ ์์ด๋(์: "ํ๊ธธ๋"๊ณผ "ํ๊ธธ๋ ์ธ"), ์ด๋ฉ์ผ์ด ๊ฐ์ผ๋ฉด ๊ฐ์ ์ฌ๋์
๋๋ค. ๊ทธ๋์ ๊ฐ์ ์ฌ๋์ด ์ฌ๋ฌ ์๋น์ค๋ฅผ ๋งก์ ์ฌ๋ฌ ํ์ ๋ฑ์ฅํด๋, ์ด๋ฉ์ผ๋ก ๋ฌถ์ผ๋ฉด ํ ๋ช
์ผ๋ก ํฉ์ณ์ ธ ๊ฒฐ๊ตญ ํ ํต์ด ๋ฉ๋๋ค.
์ ๋ฆฌํ๋ฉด, ์๋ฐฑ ๋(DB ๊ธฐ์ค)๋ฅผ ๋ค์ง์ด ์์์ ๋ด๋น์(์ฌ๋ ๊ธฐ์ค)๋ก ๋ฌถ๋ ๊ฒ๋๋ค. ์ค์ ๋ก ์ด ํ๋ก์ ํธ์์๋ ์๋ฐฑ ๋๊ฐ ์ด ๋ช
๋จ์ง์ ๋ด๋น์๋ก ๋ชจ์์ต๋๋ค. ์์์ ์ด์์๊ฐ ์ฌ๋ฌ ์ธ์คํด์ค๋ฅผ ๋ด๋นํ๋, ํํ ๊ตฌ์กฐ์
๋๋ค. ํ ์ฌ๋์ด ์์ญ ๋๋ฅผ ๋งก๊ธฐ๋ ํฉ๋๋ค.
์ค๋ณต ๋ฐฉ์ง โ ํ ์์ ์ ์์์ ๊ฐ์ ์ธ์คํด์ค๋ ํ ๋ฒ๋ง
์ add_instance์ _seen_ids๋ผ๋ ๋ฐฉ์ด๋ฅผ ๋ฃ์ ์ด์ ๋ฅผ ์ง๊ณ ๋์ด๊ฐ๊ฒ ์ต๋๋ค. ๋ง์ฝ ์ด๋ค ํ์์ ํ์ฌ ๋ด๋น์์ ํ๋ ฅ์ฌ ๋ด๋น์์ ์ด๋ฉ์ผ์ด ์ฐ์ฐํ ๊ฐ๋ค๋ฉด, ๊ทธ ์ธ์คํด์ค๊ฐ ๊ฐ์ ์ฌ๋ ๋ฐ๊ตฌ๋์ ๋ ๋ฒ ๋ค์ด๊ฐ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ๊ทธ ์ฌ๋์ ๋ด๋น ๋์๊ฐ ์ค์ ๋ณด๋ค ๋ถํ๋ ค์ง๋๋ค.
๊ทธ๋์ ๋ฐ๊ตฌ๋์ ๋ฃ๊ธฐ ์ ์ "์ด๋ฏธ ๋ฃ์ id์ธ๊ฐ?"๋ฅผ ํ ๋ฒ ํ์ธํฉ๋๋ค. ์๋ฐ๋ก ์น๋ฉด Set<String> seen์ addํ ๊ฒฐ๊ณผ(์ด๋ฏธ ์์ผ๋ฉด false)๋ก ์ค๋ณต์ ๊ฑฐ๋ฅด๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ์
์ฅํ ๋ ๋ช
๋จ์ ์ฒดํฌ ํ์๋ฅผ ํ๊ณ , ์ด๋ฏธ ์ฒดํฌ๋ ์ฌ๋์ ๋ค์ ์ ์ธ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค.
์ง๊ธ ๋ฐ์ดํฐ์ ์ค์ ๋ก ๊ทธ๋ฐ ๊ฒฝ์ฐ๋ ์์์ง๋ง, ์ด๋ฐ ๋ฐฉ์ด๋ ์ง๊ธ ๋ฌธ์ ๊ฐ ์์ด๋ ๋์ค์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋๋ฉด ์กฐ์ฉํ ํ๋ฆฐ ์ซ์๋ฅผ ๋ผ ์ ์์ด ๋ฏธ๋ฆฌ ๋ง์ ๋๋ ํธ์ด ์์ ํฉ๋๋ค. ์ซ์๊ฐ ํ๋ฆฌ๋ฉด ๋์ ์ ์ ๋๊ณ , ๋์ค์ "์ ์ด ์ฌ๋ ๋ด๋น ๋์๊ฐ ์ด์ํ์ง?" ํ๋ฉฐ ํ์ฐธ์ ํค๋งค๊ฒ ๋ฉ๋๋ค.
ํท๊ฐ๋ ธ๋ ์ง์ โ ๋ ์ฌ๋์๊ฒ ๋ณด๋ด๋ฉด ๊ฑด์๊ฐ ๋ ๋ฐฐ?
์ฌ๊ธฐ์ ์ ๊น ๋ฉ์นซํ์ต๋๋ค. ๊ฐ์ ์ธ์คํด์ค๋ฅผ ํ์ฌ ๋ด๋น์์ ํ๋ ฅ์ฌ ๋ด๋น์ ๋์๊ฒ ๋ณด๋ด๋ฉด, ๊ฐ์ ๋ด์ฉ์ ๋ ๋ฒ ์ผ ๊ฒ ๋๋ ๊ฒ ์๋๊ฐ? ํ๋ ์๋ฌธ์ด์์ต๋๋ค.
์ ๋ฆฌํด ๋ณด๋, ๋ ๊ฐ๋ฅผ ์๋ก ๋ค๋ฅธ ์งํ๋ก ๋ณด๋ฉด ๊น๋ํ์ต๋๋ค.
| ์ ๊ฒํ ์ธ์คํด์ค ์ | ์ ๋ํฌ ์ธ์คํด์ค | ๋์ ์ ์ฒด (์ค๋ณต ์์) |
| ๋ฐ์กํ ๋ฉ์ผ ํต์ | ์์ ์(์ฌ๋) | ๋ด๋น์ ์๋งํผ |
"์ค๋ ๋ช ๋๋ฅผ ์ ๊ฒํ๋"๋ ์ธ์คํด์ค ๊ธฐ์ค์ด๋ผ, ๊ทธ ์ธ์คํด์ค๊ฐ ๋ช ๋ช
์๊ฒ ํต์ง๋๋ ํ ๋ฒ๋ง ์
๋๋ค. ์ค์ ๋ก ์์งยทํ์ง๋ ์ธ์คํด์ค๋น ํ ๋ฒ๋ง ๋๋ฆฌ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ฌ๋ฌ ๋ฆฌํฌํธ๊ฐ ๋๋ ์๋๋ค.
๋ฐ๋ฉด "๋ฉ์ผ ๋ช ํต ๋ณด๋๋"๋ ๋ฐ๋ ์ฌ๋ ๊ธฐ์ค์
๋๋ค. ๊ฐ์ ๋ด์ฉ์ด๋ผ๋ ํ์ฌ ๋ด๋น์์๊ฒ 1ํต, ํ๋ ฅ์ฌ ๋ด๋น์์๊ฒ 1ํต์ด๋ฉด ๋ฐฐ๋ฌ์ 2๊ฑด์
๋๋ค.
๋น์ ํ์๋ฉด, ๊ฐ์ ๊ณต์ง๋ฌธ์ ๋ถ์์ฅ๊ณผ ์ค๋ฌด์์๊ฒ ๊ฐ๊ฐ ๋ณด๋ด๋ฉด "๋ฐ์ก 2๊ฑด"์ด์ง "๊ณต์ง 2๊ฐ"๊ฐ ์๋๋๋ค. ๊ณต์ง ๋ด์ฉ์ ํ๋์ง๋ง ๋ฐฐ๋ฌ์ ๋์
๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก๋ ๋ ์ฌ๋์ ๋ฆฌํฌํธ๊ฐ ์์ ํ ๊ฐ์ง๋ ์์ต๋๋ค. ํ์ฌ ๋ด๋น์๊ฐ ํ๋ ฅ์ฌ ๋ด๋น์๋ณด๋ค ์๋น์ค๋ฅผ ํ๋ ๋ ๋งก๊ณ ์์ผ๋ฉด ๋ด๋น ๋์ ์์ฒด๊ฐ ๋ค๋ฆ
๋๋ค. ๊ทธ๋์ "๊ฐ์ ๋ด์ฉ ๋ ๋ฒ"์ด ์๋๋ผ "๋ฒ์๊ฐ ๋ค๋ฅธ ๋ ๋ฆฌํฌํธ๋ฅผ ๊ฐ์์๊ฒ"๊ฐ ์ ํํ ํํ์
๋๋ค.
5. ๋ด๋น์ ํ ๋ช ๋ถ ๋ฆฌํฌํธ ์กฐ๋ฆฝํ๊ธฐ
์ด์ ๋ด๋น์ ํ ๋ช
์ด ๋ฐ์ ๋ฆฌํฌํธ๋ฅผ ๋ง๋ญ๋๋ค. ์ฌ๊ธฐ์ ํ ๊ฐ์ง ๊ฒฐ์ ์ ํ์ต๋๋ค. ๊ธฐ์กด ๋จ์ผ ๋ฆฌํฌํธ(ํ ๋์ง๋ฆฌ)๋ฅผ ๊ทธ๋ฆฌ๋ ์ฝ๋๊ฐ ์ด๋ฏธ ์ ๊ฒ์ฆ๋ผ ์์ผ๋, ๊ทธ ๋ ๋๋ฌ๋ฅผ ํต์งธ๋ก ์ฌ์ฌ์ฉํ๊ธฐ๋ก ํ์ต๋๋ค.
์ฒ์์๋ ํยท๊ทธ๋ํยท์ง๋จ ๊ฐ์ ๋ถํ๋ง ๊ณจ๋ผ ์ฎ๊ฒจ ๋ด์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ทธ๋ฌ๋ค ๋ณด๋ "์์ง ์ปค๋ฒ๋ฆฌ์ง ์นด๋"๋ "์ค๋์ ํต์ฌ" ๊ฐ์ ์์๊ฐ ์ฌ๊ทธ๋จธ๋ ๋น ์ก์ต๋๋ค. ์์ผ๋ก ํ๋์ฉ ์ฎ๊ธฐ๋ค ๋ณด๋ฉด ๊ผญ ๋ญ๊ฐ๋ฅผ ํ๋ฆฝ๋๋ค. ์ด์ฌํ ๋ ๋ฌผ๊ฑด์ ํ๋์ฉ ๋๋ฅด๋ฉด ๊ผญ ์๋ ํ๋๋ฅผ ๋๊ณ ์ค๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
๊ทธ๋์ ๋ฐฉํฅ์ ๋ฐ๊ฟจ์ต๋๋ค. ๋จ์ผ ๋ฆฌํฌํธ์์ ๋ณธ๋ฌธ์ ๋ง๋๋ ๋ถ๋ถ๋ง ๋ณ๋ ํจ์(๋ณธ๋ฌธ ๋น๋)๋ก ๋ถ๋ฆฌํ๊ณ , ๋ด๋น์ ๋ฆฌํฌํธ๋ ์๋น์ค๋ง๋ค ์ด ๋ณธ๋ฌธ ๋น๋๋ฅผ ํ ๋ฒ์ฉ ํธ์ถํ๋๋ก ํ์ต๋๋ค. ์๋์งธ ํต์ผ๋ก ์ฎ๊ธฐ๋ ํ๋ฆด ๊ฒ ์์ต๋๋ค.
# ๋ด๋น์ ๋ฆฌํฌํธ = ๋ด๋น์ ํค๋ + ์๋น์ค๋ณ ์์ฝ ํ + (์๋น์ค๋ง๋ค) ๋จ์ผ ๋ฆฌํฌํธ ๋ณธ๋ฌธ
for svc, sinsts in recipient.services.items():
svc_dets, svc_mets, pend = {}, {}, 0
for inst in sinsts:
d = det_by_id.get(inst.get("id"))
if d is not None and getattr(d, "has_data", False):
svc_dets[inst["id"]] = d # ๋ฐ์ดํฐ ์๋ ์ธ์คํด์ค๋ง ์์ธ๋ก
svc_mets[inst["id"]] = metrics_by_id.get(inst["id"])
else:
pend += 1 # ๋๋จธ์ง๋ '์์ง ๋๊ธฐ'๋ก ์
if svc_dets:
parts.extend(build_report_parts(
svc_dets, svc_mets, config, target_date, out_dir, images,
title=svc, kpi_label="์ธ์คํด์ค", include_footer=False))
else:
parts.append(f"... {svc} ยท ์์ง ๋๊ธฐ {pend}๋ ...")
๋ถ๋ฆฌํ ๋ณธ๋ฌธ ๋น๋์ ์๊ทธ๋์ฒ๋ ์ด๋ ๊ฒ ์๊ฒผ์ต๋๋ค. ์ ๋ชฉยท๋ผ๋ฒจยท๋ธ๋๋๋ฅผ ์ธ์๋ก ๋ฐ์, ๋จ์ผ ๋ฆฌํฌํธ๋ก๋ ์๋น์ค ๋จ์ ๋ถ๋ถ์ผ๋ก๋ ์ธ ์ ์๊ฒ ํ์ต๋๋ค.
def build_report_parts(detections, db_metrics, config, target_date, out_dir, images,
title=None, subtitle=None, kpi_label="์ ๊ฒ DB",
include_footer=True, brand="...๊ธฐ๋ณธ ๋ธ๋๋..."):
"""๋ฆฌํฌํธ ๋ณธ๋ฌธ ํํธ ๋ฆฌ์คํธ(ํค๋ KPI ~ ํธํฐ) ์์ฑ โ ๋จ์ผ ๋ฆฌํฌํธ์ ๋ด๋น์ ๋ฆฌํฌํธ๊ฐ ๊ณต์ฉ.
title/subtitle/kpi_label ๋ก ํค๋๋ฅผ ๋ฐ๊ฟ ์๋น์ค ๋จ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ."""
...
์ด๋ ๊ฒ ํ๋ ์ปค๋ฒ๋ฆฌ์ง ์นด๋, ์ค๋์ ํต์ฌ, ์ ์ผ ๋๋น ๋ณํ ๊ฐ์ ์์๊ฐ ์์ผ๋ก ์ฎ๊ธฐ๋ค ๋น ์ง ์ผ ์์ด ์๋น์ค๋ณ๋ก ๊ทธ๋๋ก ๋ค ๋ค์ด๊ฐ๋๋ค. ๊ฐ ์๋น์ค ๋ธ๋ก์ ์ฌ์ค์ "ํ ์ฅ์ง๋ฆฌ ๋จ์ผ ๋ฆฌํฌํธ"๊ฐ ๋ฉ๋๋ค.
ํ ๊ฐ์ง ํนํ ์ฃผ์ํ ๊ฑด, ๋ณธ๋ฌธ ๋น๋๋ฅผ ๋ถ๋ฆฌํ๋ฉด์ ์๋ ๋จ์ผ ๋ฆฌํฌํธ์ ์ถ๋ ฅ์ด ํ ๊ธ์๋ ๋ฐ๋์ง ์๊ฒ ํ ๊ฒ์
๋๋ค. ์ด๋ฏธ ๋ฐฐํฌ๋ผ ๋งค์ผ ๋๊ณ ์๋ ๋ฆฌํฌํธ๋ผ, ๋ถ๋ฆฌ ์ ํ์ ๊ฒฐ๊ณผ HTML์ ํ์ผ๋ก ๋ฝ์ ๊ทธ๋๋ก ๋น๊ตํด ์์ ํ ๊ฐ์์ง ํ์ธํ์ต๋๋ค. "๋ฆฌํฉํฐ๋งํ๋๋ฐ ์ถ๋ ฅ์ด ๋๊ฐ๋ค"๋ฅผ ๋์ผ๋ก ์ฆ๋ช
ํ ์
์
๋๋ค. ์ด๋ฐ ๋์ฒด ์์
์ "๋์์ด ์ ๋ฐ๋์๋ค"๋ฅผ ๋ง๋ก๋ง ํ์ง ๋ง๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ค์ ๋ก ๋น๊ตํด ๋๋ ๊ฒ ์์ ํฉ๋๋ค.
๋ด๋น์ ๋ฆฌํฌํธ์ ์ต์ข
๊ตฌ์กฐ๋ ์ด๋ ์ต๋๋ค.
- ๋ด๋น์ ํค๋ + ์ ์ฒด ์์ฝ โ ๋ด๋น ๋์, ์ํ/์ฃผ์/์ ์/์์ง๋๊ธฐ ๊ฐ์. (์ ๋ชฉ์ ํ์ ๋ฆฌํฌํธ์ ๊ฐ๊ฒ ๋๊ณ , ์์ ์ ์ด๋ฆ์ ์ฐ์ธก์ ์๊ฒ)
- ์๋น์ค๋ณ ์์ฝ ํ โ ๋ด๋นํ ์๋น์ค๋ง๋ค ๋์์ ๋ฑ๊ธ ๋ถํฌ, ์์ง ๊ตฌ์ฑ.
- ์๋น์ค๋ณ ์์ธ โ ๋ฐ์ดํฐ๊ฐ ์๋ ์๋น์ค๋ง๋ค ๋จ์ผ ๋ฆฌํฌํธ ๋ณธ๋ฌธ์ ํต์ผ๋ก(์ปค๋ฒ๋ฆฌ์งยท์ค๋์ ํต์ฌยท์ ์ผ ๋๋นยท์ํ/์ฃผ์ ์์ธยท์ ์๊น์ง) ํผ์นจ. ์์ง ๋ฐ์ดํฐ๊ฐ ์๋ ์ธ์คํด์ค๋ '์์ง ๋๊ธฐ'๋ก.
- ์ฃผ๊ฐยท์๊ฐ ์๋ด์ ํธํฐ.
์ ์ฒด ์์ฝ์ ๊ฐ์๋ฅผ ์ธ๋ ๋ถ๋ถ์ ์ด๋ ๊ฒ ๋จ์ํฉ๋๋ค. ํ์ง ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋ฑ๊ธ์ผ๋ก, ์์ผ๋ฉด '์์ง ๋๊ธฐ'๋ก ๋๋๋๋ค.
for inst in insts:
det = det_by_id.get(inst.get("id"))
if det is None or not getattr(det, "has_data", False):
n_pending += 1
elif det.worst_grade == "critical":
n_crit += 1
elif det.worst_grade == "warning":
n_warn += 1
else:
n_normal += 1
'์์ง ๋๊ธฐ'๋ผ๋ ์ํ๋ฅผ ๋ ์ด์
์ค์ ๋ฐ์ดํฐ๊ฐ ์ค๋น๋ ์ธ์คํด์ค๋ ์ผ๋ถ์ด๊ณ , ๋๋จธ์ง๋ ์ ๊ท ์๋ฒ ์ฐ๋๊ณผ ์์ง๋ณ ์๋ฃ ํ๋ณด๊ฐ ๋๋์ผ ์ค๋ฐ์ดํฐ๋ก ์ ํ๋ฉ๋๋ค. ๊ทธ๋๊น์ง๋ ๋ฆฌํฌํธ์ "์ด ์ธ์คํด์ค๋ ๋ด๋น ๋ชฉ๋ก์๋ ์์ง๋ง ์์ง ์์ง ์ "์์ ์ ์งํ๊ฒ ํ์ํ๋ ํธ์ด ๋ซ์ต๋๋ค.
๋ด๋น์ ์
์ฅ์์๋ ์๊ธฐ ๋ด๋น ์ ์ฒด ๋์์, ๊ทธ์ค ์ง๊ธ ์ค์ ๋ก ์ ๊ฒ๋๊ณ ์๋ ๋์๋ฅผ ํจ๊ป ๋ณด๊ฒ ๋ฉ๋๋ค. "40๋ ๋ด๋น์ธ๋ฐ ์ง๊ธ 3๋๋ง ์ ๊ฒ ์ค์ด๊ตฌ๋"๊ฐ ํ๋์ ๋ณด์ด๋ฉด, ๋๋จธ์ง๊ฐ ์ธ์ ๋ถ๋์ง ๋ฌผ์ด๋ณผ ์ ์์ต๋๋ค. ์์ง ์ ๋๋ ๊ฑธ ์จ๊ธฐ๋ ๊ฒ๋ณด๋ค, ์ด๋๊น์ง ๋๋์ง ๋๋ฌ๋ด๋ ๊ฒ ์ ๋ขฐ๋ฅผ ์ค๋๋ค.
ํ์ง ๊ฒฐ๊ณผ๋ฅผ ์ธ์คํด์ค์ ์ฐ๊ฒฐํ๊ธฐ
์์งยทํ์ง๋ ์ค์ ํ์ผ ๊ธฐ์ค์ผ๋ก ๋์, ๊ฒฐ๊ณผ๊ฐ ์ค์ ์์ ์ด๋ฆ(์: mysql)์ผ๋ก ๋์ต๋๋ค. ์ด๊ฑธ ๋งคํ์์ ์ธ์คํด์ค id์ ์ฐ๊ฒฐํด์ผ ํฉ๋๋ค. ์ค์ ์ ๊ฐ DB์ ์ค์ ์ธ์คํด์ค id๊ฐ ์ ํ ์์ผ๋, ๊ทธ๊ฑธ ๋ค๋ฆฌ ์ผ์ "์ค์ ์ด๋ฆ โ ์ธ์คํด์ค id"๋ก ๊ฒฐ๊ณผ๋ฅผ ์ฎ๊น๋๋ค.
def _bridge_by_instance(detections, metrics, config):
"""์ค์ ์ผ๋ก ์์งยทํ์ง๋ ๊ฒฐ๊ณผ๋ฅผ ๋งคํ์์ ์ธ์คํด์ค id ๊ธฐ์ค์ผ๋ก ์ฎ๊ธด๋ค."""
id_by_db = {name: db.get("instance") for name, db in config["databases"].items()}
det_by_id = {id_by_db[n]: d for n, d in detections.items() if id_by_db.get(n)}
met_by_id = {id_by_db[n]: m for n, m in metrics.items() if id_by_db.get(n)}
return det_by_id, met_by_id
์ด ๋ค๋ฆฌ(bridge)๋ฅผ ๋ ๋๋ถ์, ์ง๊ธ ๋ฐ์ดํฐ๊ฐ ์๋ ์ธ์คํด์ค๋ ์ค์ ํ์ง ๊ฒฐ๊ณผ๊ฐ ๋ฆฌํฌํธ์ ๋ค์ด๊ฐ๊ณ , ๋๋จธ์ง๋ ์์ฐ์ค๋ฝ๊ฒ '์์ง ๋๊ธฐ'๋ก ๋น ์ง๋๋ค. ์ด๋ฆํ๋ง ๊ฐ์ ๋ผ์ฐ๋ ์์ ์ธต์ด๋ผ, ๋์ค์ ์ ์ฒด ์ธ์คํด์ค๋ฅผ ์ค์ ๋ก ์์งํ๊ฒ ๋๋ฉด ์ด ์ฐ๊ฒฐ ์ง์ ๋ง ํ์ฅํ๋ฉด ๋ฉ๋๋ค.
6. ์ผ๊ด ์์ฑ๊ณผ ๋ฐ์ก ์ด๋ ฅ
๋ง์ง๋ง์ผ๋ก, ์์ ์ ์ ์์ ํ ๋ฒ์ ๋๋ฆฌ๊ณ ์ด๋ ฅ์ ๋จ๊ธฐ๋ ๋ฌ๋๋ฅผ ๋ง๋ค์์ต๋๋ค. ๋ค์ง์ด ๋์ ๋ด๋น์ ๋ชฉ๋ก์ ๊ทธ๋ฅ ์ํํ๋ฉด ๋ฉ๋๋ค.
for email, r in recips.items():
res = generate_recipient_report(r, det_by_id, met_by_id, cfg, target, out_dir=out_dir)
fname = f"{target.isoformat()}_{_code(email)}.html" # ๋ ์ง + ์์ ์์ฝ๋
_save_html(res, os.path.join(out_dir, fname))
c = _counts(r, det_by_id) # ๋ฑ๊ธ๋ณ ๊ฐ์ ์ง๊ณ
rows.append({...}) # ์ด๋ ฅ ์ธ๋ฑ์ค์ฉ ํ ์ค
if args.send:
from mailer import send_report
send_report(res, cfg, target, recipients=[email]) # ์ค์ ๋ฐ์ก(์ต์
)
๊ฐ ์์ ์ ๋ฆฌํฌํธ๋ {๋ ์ง}_{์์ ์์ฝ๋}.html ๋ก ์ ์ฅํฉ๋๋ค. ์์ ์์ฝ๋๋ ์ด๋ฉ์ผ์ ์๋ถ๋ถ(์: kim.example)์ ์๋๋ค. ์ฌ๋ ์ด๋์
๊ฐ์ ๊ฑธ ์ธ๊น๋ ๊ณ ๋ฏผํ์ง๋ง, ์ด๋์
์ ๋๋ช
์ด์ธ์ด ๊ฒน์น ์ ์์ด ์ด๋ฉ์ผ ์๋ถ๋ถ์ ์๋ณ์๋ก ์ผ์์ต๋๋ค. ์ด๋ฉ์ผ์ ์ ์ผํ๋๊น์. ๋ ์ง๋ฅผ ์ ๋๋ก ๋์ด์, ๋งค์ผ ํ์ผ์ด ์์ด๋ฉด ๊ทธ ์์ฒด๊ฐ ๋ฐ์ก ์ด๋ ฅ์ด ๋ฉ๋๋ค. ํด๋๋ฅผ ์ด๋ฉด ๋ ์ง์์ผ๋ก "์ธ์ ๋๊ตฌ์๊ฒ" ๋ณด๋๋์ง๊ฐ ๊ทธ๋๋ก ๋จ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ๋ ๋ฐ์ก ์ ์ฒด๋ฅผ ํ๋์ ๋ณด๋ ์ด๋ ฅ ์ธ๋ฑ์ค๋ฅผ ๋ฐ๋ก ๋ง๋ญ๋๋ค. ์์ ์๋ณ๋ก ๋ด๋น ๋์์ ์ํ/์ฃผ์/์ ์/๋๊ธฐ ๊ฐ์๋ฅผ ํ๋ก ๋ณด์ฌ์ฃผ๊ณ , ๊ฐ ํ์์ ๊ทธ ์ฌ๋ ๋ฆฌํฌํธ๋ก ๋ฐ๋ก ๋์ด๊ฐ ์ ์๊ฒ ๋งํฌ๋ฅผ ๊ฒ๋๋ค.
def build_index(rows, target, path, unique_total, sent):
"""๋ฐ์ก ์ด๋ ฅ ์ธ๋ฑ์ค HTML โ ์์ ์๋ณ ๋ฐ์ก ํํฉ ํ + ๋งํฌ."""
trs = []
for r in sorted(rows, key=lambda x: (-x["critical"], -x["warning"], x["name"])):
trs.append(
f'<tr ...>'
f'<td ...><a href="{r["file"]}">{r["name"]}</a></td>' # ์ด๋ฆ = ๋ฆฌํฌํธ ๋งํฌ
f'<td ...>{r["roles"]}</td>'
f'<td ...>{r["email"]}</td>'
f'<td ...>{r["total"]}</td>' # ๋ด๋น ๋์
f'<td ...>{_n(r["critical"], C_CRIT)}</td>' # ์ํ
f'<td ...>{_n(r["warning"], C_WARN)}</td>' # ์ฃผ์
f'<td ...>{_n(r["normal"], C_OK)}</td>' # ์ ์
f'<td ...>{r["pending"] or ""}</td>' # ์์ง ๋๊ธฐ
f'<td ...><a href="{r["file"]}">๋ฆฌํฌํธ</a></td></tr>')
# ... ์๋จ KPI: ์์ ์ N๋ช
(=๋ฐ์ก ํต์) / ์ ๊ฒ ์ธ์คํด์ค ์ ๋ํฌ ์ ...
์ธ๋ฑ์ค๋ฅผ ์ํ ๋ง์ ์์ผ๋ก ์ ๋ ฌํ ๊ฑด ์๋์ ์
๋๋ค(-critical, -warning ์). ๋ฐ์ก ์ด๋ ฅ์ ์ด์์ ๋ ์์ด ๊ธํ ๋ด๋น์๊ฐ ์๋ก ์ค๊ฒ ํ๋ ค๋ ๊ฒ๋๋ค. ๋งจ ์์๋ "์์ ์ ๋ช ๋ช
(=๋ฐ์ก ํต์)"๊ณผ "์ ๊ฒ ์ธ์คํด์ค ๋ช ๋(์ ๋ํฌ)"๋ฅผ ๋๋ํ ๋์ด, 4์ ์์ ์ ๋ฆฌํ ๋ ์งํ๋ฅผ ๊ทธ๋๋ก ๋ฐ์ํ์ต๋๋ค. ์ด๋ ฅ ํ๋ฉด๋ง ๋ด๋ "๋ฐ์ก์ ์ฌ๋ ์, ์ ๊ฒ์ ์ธ์คํด์ค ์"๋ผ๋ ๊ตฌ๋ถ์ด ๋์ ๋ค์ด์ต๋๋ค.
7. ์งํํ๋ฉฐ ๋งํ๋ ๊ฒ๋ค
๋ช ๊ฐ์ง ์ค๋ฌด์ ์ธ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. ํ๋์ฉ ํ์ด ๊ฐ๋ ๊ณผ์ ์ด ์คํ๋ ค ์ค๊ณ๋ฅผ ๋ค๋ฌ์ด ์คฌ์ต๋๋ค.
๋ฐ์ดํฐ ์์ค๊ฐ ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ ๋ฟ์. ํ์ฅ ์ฐ๋์ ์ฌ๋ด๋ง์ ๋ชจ๋ํฐ๋ง API๋ฅผ ํธ์ถํ๋๋ฐ, ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ๊ทธ ์ฃผ์์ ๋ฟ์ง ์์ ํธ์ถ์ด ํ์์์๋ฉ๋๋ค. ์ค์ ๋ก ์๋ ๊ฐ์ ์๋ฌ๊ฐ ๋ฌ์ต๋๋ค.
urllib.error.URLError: <urlopen error [WinError 10060]
์ฐ๊ฒฐ๋ ๊ตฌ์ฑ์์ด ์๋ต์ ํ์ง ์์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค ...>
๊ทธ๋์ ๊ฐ๋ฐ ๊ฒ์ฆ์ฉ์ผ๋ก๋ ๋ฏธ๋ฆฌ ๋ฐ์ ๋ ํ์ผ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋๋ก ์์ค๋ง ๋ฐ๊ฟ ๋๋ ธ์ต๋๋ค. ๋ฐ์ดํฐ ์์ค๋ฅผ ์ค์ ํ ์ค๋ก ๊ฐ์ ๋ผ์ฐ๋๋ก ์ฒ์๋ถํฐ ๋ถ๋ฆฌํด ๋ ๊ฒ ์ฌ๊ธฐ์ ๋์์ด ๋์ต๋๋ค. ์์ง ๋ฐฉ์์ด ๋ฐ๋์ด๋ ๋๋จธ์ง ์ฝ๋๋ ๊ทธ๋๋ก์ ๋๋ค. ์ฌํ์ฉ ๋ฉํฐ ์ด๋ํฐ์ฒ๋ผ, ์ฝ์ผํธ ๋ชจ์๋ง ๊ฐ์ ๋ผ์ฐ๋ฉด ๊ธฐ๊ธฐ๋ ๊ทธ๋๋ก ์ฐ๋ ์ ์ ๋๋ค.
์์กด์ฑ ํ๊ฒฝ. ํ์ง์ ์ฐ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์คํ
๊ธฐ๋ณธ ํ์ด์ฌ์๋ ์์ด์, ํ๋ก์ ํธ ์ ์ฉ ๊ฐ์ํ๊ฒฝ์ผ๋ก ๋๋ ค์ผ ํ์ต๋๋ค. ์คํฌ๋ฆฝํธ๋ฅผ ์ฒ์ ์คํํ ๋ ModuleNotFoundError๋ก ํ ๋ฒ ๊ฑธ๋ ธ์ต๋๋ค. ์ ์ฉ ๊ฐ์ํ๊ฒฝ์ ํ์ด์ฌ์ผ๋ก ์คํํ๋ ํด๊ฒฐ๋์ต๋๋ค.
์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋น ์ง. ์์์ ์ด์ผ๊ธฐํ, ๋ถํ๋ง ๊ณจ๋ผ ์ฎ๊ธฐ๋ค ์ปค๋ฒ๋ฆฌ์ง ์นด๋๋ฅผ ํ๋ฆฐ ๋ฌธ์ ์
๋๋ค. ๊ฒฐ๊ตญ ๋ณธ๋ฌธ ์ ์ฒด๋ฅผ ๋น๋๋ก ๋ผ์ด ํต์งธ ์ฌ์ฌ์ฉํ๋ ์ชฝ์ผ๋ก ๋ฐฉํฅ์ ๋ฐ๊ฟ ํด๊ฒฐํ์ต๋๋ค. "์ผ๋ถ๋ง ์ฌ์ฌ์ฉ"์ด ์คํ๋ ค ํจ์ ์ด์๊ณ , "์ ์ฒด๋ฅผ ์ฌ์ฌ์ฉ"์ด ๋ ์์ ํ์ต๋๋ค.
์ค๋ณต ์นด์ดํธ. ์์์ ์ค๋ช
ํ, ๊ฐ์ ์ธ์คํด์ค๊ฐ ํ ์์ ์ ๋ฐ๊ตฌ๋์ ๋ ๋ฒ ๋ค์ด๊ฐ ๊ฐ๋ฅ์ฑ์
๋๋ค. ์ค์ ๋ก ๊ทธ๋ฐ ๋ฐ์ดํฐ๊ฐ ์์ง ์์์ง๋ง, ํ์ฌยทํ๋ ฅ์ฌ ์ด๋ฉ์ผ์ด ์ฐ์ฐํ ๊ฐ์ ๋๋ฅผ ๋๋นํด ๋ฐฉ์ด๋ฅผ ๋ฃ์ด ๋์ต๋๋ค. ์ด๋ฐ ๋ฐฉ์ด๋ ์ง๊ธ ๋น์ฅ ๋ฌธ์ ๊ฐ ์์ด๋, ๋์ค์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋๋ฉด ์กฐ์ฉํ ํ๋ฆฐ ์ซ์๋ฅผ ๋ผ ์ ์์ด ๋ฏธ๋ฆฌ ๋ง์ ๋๋ ํธ์ด ์์ ํฉ๋๋ค.
๋ง๋ฌด๋ฆฌ
์ ๋ฆฌํ๋ฉด, ์๋ฐฑ ๋ ๊ท๋ชจ์ ์ ๊ฒ ๋ฆฌํฌํธ๋ฅผ ๋ด๋น์๋ณ๋ก ๋๋ ๋ณด๋ด๊ธฐ ์ํด ๋ค์์ ๋ง๋ค์์ต๋๋ค.
- ์ง์ ๋ถํ ๋งคํ ์๋ณธ์ ํ์ค ํ ์ํธ๋ก ์ ๊ทํํ๊ณ , ์ดํ์๋ ๊ทธ ํ์ค๋ง ์ฝ๋ ์งง์ ์ฝ๋๋ก ๋จ์ํ. ๋ฌด๊ฑฐ์ด ์ฝ๋๋ ํ ๋ฒ๋ง.
- ์ธ์คํด์ค๋ฅผ ๋ด๋น์ ๊ธฐ์ค์ผ๋ก ๋ค์ง์ด(๋ผ์ฐํ ) ์์ ์๋ณ๋ก ๋ฌถ๊ณ , ํ ์์ ์ ์์์๋ ์ค๋ณต์ ๋ฐฉ์ง.
- ๋ฐ์ก์ ์์ ์ ๋จ์, ์ ๊ฒ์ ์ธ์คํด์ค ์ ๋ํฌ ๋ผ๋ ๋ ์งํ๋ฅผ ๋ถ๋ฆฌํด ์นด์ดํธ ํผ๋์ ์ ๊ฑฐ.
- ๋ด๋น์ ํ ๋ช ๋ถ ๋ฆฌํฌํธ๋ฅผ ๋จ์ผ ๋ฆฌํฌํธ ๋ณธ๋ฌธ ๋ ๋๋ฌ๋ฅผ ์๋น์ค๋ณ๋ก ํต์งธ ์ฌ์ฌ์ฉํด ์กฐ๋ฆฝํ๊ณ (์ปค๋ฒ๋ฆฌ์งยท์ค๋์ ํต์ฌยท์ ์ผ ๋๋น๊น์ง ๊ทธ๋๋ก), ์์ง ๋ฐ์ดํฐ๊ฐ ์๋ ์ธ์คํด์ค๋ '์์ง ๋๊ธฐ'๋ก ์ ์งํ๊ฒ ํ๊ธฐ.
- ์์ ์ ์ ์์ ์ผ๊ด ์์ฑํ๊ณ , ๋ ์ง๋ณยท์์ ์๋ณ ํ์ผ๊ณผ ์ด๋ ฅ ์ธ๋ฑ์ค๋ก ๋ฐ์ก ๊ธฐ๋ก์ ๋จ๊น.
์์ง ๋จ์ ์ผ๋ ์์ต๋๋ค. ์ ์ฒด ์ธ์คํด์ค๋ฅผ ์ค์ ๋ก ์์งํ๋ ค๋ฉด ์์ง๋ณ ์๊ณ๊ฐ๊ณผ ์์ง ๋์ ์งํ๋ฅผ ํ๋ณดํด์ผ ํ๊ณ , ์ค์ ๋ฉ์ผ ๋ฐ์ก์ ์ฌ๋ด ๋ฉ์ผ ์๋ฒ๊ฐ ํ์ ๋ผ์ผ ํฉ๋๋ค. ๋ค๋ง ๋ผ์ฐํ
๊ณผ ๋ฆฌํฌํธ ์กฐ๋ฆฝ, ์ด๋ ฅ๊น์ง์ ๋ผ๋๋ ์์ฑ๋ผ์, ๋จ์ ๊ฑด ๋ฐ์ดํฐ๋ฅผ ์ฑ์ ๋ฃ๋ ์ผ์
๋๋ค. ํ์ด ์ ์์ผ๋ฉด, ๋ฐ์ดํฐ๊ฐ ๋ถ๋ ์๊ฐ ๊ฐ ์๋น์ค๊ฐ ์ง๊ธ์ ์ํ์ฒ๋ผ ๊ฝ ์ฐฌ ์์ธ๋ก ์ฑ์์ง๋๋ค.
๊ฐ์ธ์ ์ผ๋ก ์ด๋ฒ ์์
์์ ๊ฐ์ฅ ์ค๋ ๋จ์ ๊ตํ์ ๋ ๊ฐ์ง์์ต๋๋ค. ํ๋๋ "์ง์ ๋ถํ ์
๋ ฅ์ ๊ฒฝ๊ณ์์ ํ ๋ฒ์ ์ ๋ฆฌํ๋ผ" โ ํ์ค์ผ๋ก ๋ฐ๊ฟ ๋๋ฉด ์์ชฝ ์ฝ๋๊ฐ ๋จ์ํด์ง๋๋ค. ๋ค๋ฅธ ํ๋๋ "์ผ๋ถ๋ง ์ฌ์ฌ์ฉํ๋ ค๋ค ํ๋ฆฌ๋๋ ์ ์ฒด๋ฅผ ์ฌ์ฌ์ฉํ๋ผ" โ ๋ถํ์ ๊ณจ๋ผ ์ฎ๊ธฐ๋ ๊ฒ๋ณด๋ค, ๊ฒ์ฆ๋ ๋ฉ์ด๋ฆฌ๋ฅผ ํต์งธ๋ก ๋ค์ ์ฐ๋ ํธ์ด ๋น ์ง๋ ๊ฒ ์์ต๋๋ค.
๊ธฐ์ ์คํ: Python ยท openpyxl(์์
ํ์ฑ) ยท PyYAML(์ค๊ฐ ์ค์ ). ๋ฐ์ดํฐ ์์ค๋ ์ค์ ํ ์ค๋ก ๊ต์ฒด ๊ฐ๋ฅํ๋๋ก ๋ถ๋ฆฌ, ๋ฆฌํฌํธ ๋ ๋๋ง์ ๋จ์ผ ๋ฆฌํฌํธ ๋ณธ๋ฌธ ๋น๋๋ฅผ ๋ด๋น์ ๋ฆฌํฌํธ๊ฐ ์๋น์ค๋ณ๋ก ์ฌ์ฌ์ฉ.