ํ”„๋กœ์ ํŠธ/DB ์ž๋™ํ™”

์ˆ˜๋ฐฑ ๋Œ€ DB ์ ๊ฒ€ ๋ฆฌํฌํŠธ๋ฅผ ๋‹ด๋‹น์ž๋ณ„๋กœ ๋‚˜๋ˆ  ๋ณด๋‚ด๊ธฐ

THE NICOLE 2026. 7. 15. 20:25

ํ•ต์‹ฌ ์š”์•ฝ

DB ์ผ์ผ ์ ๊ฒ€ ๋ฆฌํฌํŠธ๋ฅผ ์ฒ˜์Œ์—๋Š” ๋ช‡ ๋Œ€๋งŒ ๋Œ€์ƒ์œผ๋กœ, ํ•œ ํ†ต์˜ ๋ฉ”์ผ์— ์ „๋ถ€ ๋‹ด์•„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ ๊ฒ€ ๋Œ€์ƒ์ด ์ˆ˜๋ฐฑ ๋Œ€๋กœ ๋Š˜๋ฉด์„œ ํ•œ ํ†ต์— ๋‹ค ๋‹ด์„ ์ˆ˜ ์—†๊ฒŒ ๋์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ๋‹ด๋‹น์ž๊ฐ€ ๋‹ค๋ฅด๊ณ , ํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ํšŒ์‚ฌ ๋‹ด๋‹น์ž์™€ ํ˜‘๋ ฅ์‚ฌ ๋‹ด๋‹น์ž๊ฐ€ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ "๋‹ด๋‹น์ž(์ˆ˜์‹ ์ž) ๋‹จ์œ„๋กœ ๋ฆฌํฌํŠธ๋ฅผ ๋‚˜๋ˆ , ๊ฐ์ž ์ž๊ธฐ ๋‹ด๋‹น๋ถ„๋งŒ ๋ฐ›์•„๋ณด๊ฒŒ" ํ•˜๋Š” ๋ผ์šฐํŒ… ๊ณ„์ธต์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์€ ๊ทธ ๊ณผ์ •์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€, ์‹ค์ œ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํฐ ์ค„๊ธฐ๋Š” ๋‹ค์„ฏ ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.

  1. ์ง€์ €๋ถ„ํ•œ ๋‹ด๋‹น์ž ๋งคํ•‘ ์—‘์…€์„ ํ‘œ์ค€ ํ•œ ์‹œํŠธ๋กœ ์ •์ œํ•œ๋‹ค.
  2. ํ‘œ์ค€ ์‹œํŠธ๋ฅผ ์ฝ์–ด ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๋งŒ๋“ ๋‹ค(์ œ์™ธยท๋ฌดํšจ ์ด๋ฉ”์ผยท์ง๊ธ‰ ์ •๋ฆฌ ํฌํ•จ).
  3. ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก์„ ๋‹ด๋‹น์ž ๊ธฐ์ค€์œผ๋กœ ๋’ค์ง‘์–ด ์ˆ˜์‹ ์ž๋ณ„๋กœ ๋ฌถ๋Š”๋‹ค.
  4. ๋‹ด๋‹น์ž ํ•œ ๋ช…๋ถ„ ๋ฆฌํฌํŠธ๋ฅผ ์กฐ๋ฆฝํ•œ๋‹ค(๊ธฐ์กด ๋‹จ์ผ ๋ฆฌํฌํŠธ ๋ Œ๋”๋Ÿฌ๋ฅผ ํ†ต์งธ๋กœ ์žฌ์‚ฌ์šฉ).
  5. ์ˆ˜์‹ ์ž ์ „์›์„ ์ผ๊ด„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐœ์†ก ์ด๋ ฅ์„ ๋‚จ๊ธด๋‹ค.

์ค‘๊ฐ„์— ํ•œ ๋ฒˆ ํ—ท๊ฐˆ๋ ธ๋˜ ์ง€์  โ€” "๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‘ ์‚ฌ๋žŒ์—๊ฒŒ ๋ณด๋‚ด๋ฉด ์ ๊ฒ€ ๊ฑด์ˆ˜๊ฐ€ ๋‘ ๋ฐฐ๋กœ ์žกํžˆ๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€" โ€” ์„ ์–ด๋–ป๊ฒŒ ์ •๋ฆฌํ–ˆ๋Š”์ง€๋„ ๋‹ด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ "์นด์šดํŠธ์˜ ํ•จ์ •"์€ ์‹ค๋ฌด์—์„œ ์€๊ทผํžˆ ์ž์ฃผ ๋‚˜์˜ค๋Š” ์ฃผ์ œ๋ผ, ๋น„์Šทํ•œ ์ƒํ™ฉ์„ ๋งŒ๋‚˜๋Š” ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆผ1


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 โ€” ์‚ฌ์‹ค์ƒ ๊ฐ™์€ ๊ณ„์—ด์ธ๋ฐ ๋ฌธ์ž์—ด์ด ๋‹ค ๋‹ฌ๋ž์Šต๋‹ˆ๋‹ค.

์ด๊ฑธ ๊ทธ๋Œ€๋กœ ํ”„๋กœ๊ทธ๋žจ์ด ์ฝ๊ฒŒ ํ•˜๋ฉด, ํŒŒ์‹ฑ ์ฝ”๋“œ๊ฐ€ ๋‘ ์‹œํŠธ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ๊ทœ์น™์„ ์ „๋ถ€ ํก์ˆ˜ํ•ด์•ผ ํ•ด์„œ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋ณต์žกํ•œ ์ฝ”๋“œ๋ฅผ ๋งค๋ฒˆ ๋Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐฉํ–ฅ์„ ์ด๋ ‡๊ฒŒ ์žก์•˜์Šต๋‹ˆ๋‹ค.

  1. ์ •๊ทœํ™”(ํ•œ ๋ฒˆ๋งŒ) โ€” ์ง€์ €๋ถ„ํ•œ ์›๋ณธ์„ ์ฝ์–ด, ํ‘œ์ค€ ํ•œ ์‹œํŠธ ์–‘์‹์œผ๋กœ ๋‹ค์‹œ ๋ฝ‘์•„๋‚ธ๋‹ค.
  2. ์ฝ๊ธฐ(์ƒ์‹œ) โ€” ์•ž์œผ๋กœ๋Š” ๊ทธ ํ‘œ์ค€ ์–‘์‹๋งŒ ์ฝ๋Š”๋‹ค. ์ด๊ฑด ์งง์€ ์ฝ”๋“œ๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค.

์ง€์ €๋ถ„ํ•จ์„ ํก์ˆ˜ํ•˜๋Š” ๋ฌด๊ฑฐ์šด ์ฝ”๋“œ๋Š” ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์“ฐ๊ณ , ์ดํ›„์—” ๊น”๋”ํ•œ ํ‘œ์ค€๋งŒ ์ƒ๋Œ€ํ•˜์ž๋Š” ๊ฒ๋‹ˆ๋‹ค. ๋งˆ์น˜ ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ์†๊ธ€์”จ๋กœ ์ œ๊ฐ๊ฐ ์จ ์˜จ ์‹ ์ฒญ์„œ๋ฅผ, ํ•œ ๋ฒˆ ํ‘œ์ค€ ์–‘์‹์— ์˜ฎ๊ฒจ ์ ๊ณ  ๋‚˜๋ฉด ๊ทธ๋‹ค์Œ๋ถ€ํ„ฐ๋Š” ๊ทธ ํ‘œ์ค€ ์–‘์‹๋งŒ ๋ณด๋ฉด ๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆผ2

์—”์ง„ ์ด๋ฆ„์„ '๊ณ„์—ด'๋กœ ๋ฌถ๊ธฐ

์ •๊ทœํ™”์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฑด ์—”์ง„ ์ด๋ฆ„ ํ†ต์ผ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ž„๊ณ„๊ฐ’์ด๋‚˜ ์ ๊ฒ€ ์ง€ํ‘œ๋Š” ๊ฐœ๋ณ„ ํ‘œ๊ธฐ(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 ์ฑ…์ž„ ๋‹˜"์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๋ถ™์–ด ์žˆ์–ด๋„ ๋‹ค ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ์˜ ๋์„ ํ•˜๋‚˜์”ฉ ๋ฒ—๊ฒจ ๋‚ด๋Š” ์…ˆ์ž…๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์—๋Š” ๋œป๋ฐ–์˜ ๋ณด๋„ˆ์Šค๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ์‚ฌ๋žŒ์ด ์–ด๋–ค ํ–‰์—๋Š” "ํ™๊ธธ๋™", ์–ด๋–ค ํ–‰์—๋Š” "ํ™๊ธธ๋™ ์™ธ"๋กœ ๋‹ค๋ฅด๊ฒŒ ์ ํ˜€ ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ์ง๊ธ‰ยท๊ผฌ๋ฆฌํ‘œ๋ฅผ ๋–ผ๊ณ  ๋‚˜๋‹ˆ ๋‘˜ ๋‹ค "ํ™๊ธธ๋™"์ด ๋˜์–ด, ๋‚˜์ค‘์— ์ด๋ฉ”์ผ๋กœ ๋ฌถ์„ ๋•Œ ํ•œ ๋ช…์œผ๋กœ ๊น”๋”ํ•˜๊ฒŒ ํ•ฉ์ณ์กŒ์Šต๋‹ˆ๋‹ค. ์›๋ž˜ ๋…ธ๋ฆฌ๋˜ ๊ฑด "์ง๊ธ‰ ์ œ๊ฑฐ"์˜€๋Š”๋ฐ, ๋ค์œผ๋กœ "ํ‘œ๊ธฐ ํ”๋“ค๋ฆผ ์ •๋ฆฌ"๊นŒ์ง€ ๋œ ์…ˆ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆผ3


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)๋กœ ์ค‘๋ณต์„ ๊ฑฐ๋ฅด๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ž…์žฅํ•  ๋•Œ ๋ช…๋‹จ์— ์ฒดํฌ ํ‘œ์‹œ๋ฅผ ํ•˜๊ณ , ์ด๋ฏธ ์ฒดํฌ๋œ ์‚ฌ๋žŒ์€ ๋‹ค์‹œ ์•ˆ ์„ธ๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ ๋ฐ์ดํ„ฐ์— ์‹ค์ œ๋กœ ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๋Š” ์—†์—ˆ์ง€๋งŒ, ์ด๋Ÿฐ ๋ฐฉ์–ด๋Š” ์ง€๊ธˆ ๋ฌธ์ œ๊ฐ€ ์—†์–ด๋„ ๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์กฐ์šฉํžˆ ํ‹€๋ฆฐ ์ˆซ์ž๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์–ด ๋ฏธ๋ฆฌ ๋ง‰์•„ ๋‘๋Š” ํŽธ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค. ์ˆซ์ž๊ฐ€ ํ‹€๋ฆฌ๋ฉด ๋ˆˆ์— ์ž˜ ์•ˆ ๋„๊ณ , ๋‚˜์ค‘์— "์™œ ์ด ์‚ฌ๋žŒ ๋‹ด๋‹น ๋Œ€์ˆ˜๊ฐ€ ์ด์ƒํ•˜์ง€?" ํ•˜๋ฉฐ ํ•œ์ฐธ์„ ํ—ค๋งค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆผ4

ํ—ท๊ฐˆ๋ ธ๋˜ ์ง€์  โ€” ๋‘ ์‚ฌ๋žŒ์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๊ฑด์ˆ˜๊ฐ€ ๋‘ ๋ฐฐ?

์—ฌ๊ธฐ์„œ ์ž ๊น ๋ฉˆ์นซํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ํšŒ์‚ฌ ๋‹ด๋‹น์ž์™€ ํ˜‘๋ ฅ์‚ฌ ๋‹ด๋‹น์ž ๋‘˜์—๊ฒŒ ๋ณด๋‚ด๋ฉด, ๊ฐ™์€ ๋‚ด์šฉ์„ ๋‘ ๋ฒˆ ์„ผ ๊ฒŒ ๋˜๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€? ํ•˜๋Š” ์˜๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
์ •๋ฆฌํ•ด ๋ณด๋‹ˆ, ๋‘ ๊ฐœ๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ์ง€ํ‘œ๋กœ ๋ณด๋ฉด ๊น”๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ ๊ฒ€ํ•œ ์ธ์Šคํ„ด์Šค ์ˆ˜์œ ๋‹ˆํฌ ์ธ์Šคํ„ด์Šค๋Œ€์ƒ ์ „์ฒด (์ค‘๋ณต ์—†์Œ)
๋ฐœ์†กํ•œ ๋ฉ”์ผ ํ†ต์ˆ˜์ˆ˜์‹ ์ž(์‚ฌ๋žŒ)๋‹ด๋‹น์ž ์ˆ˜๋งŒํผ

"์˜ค๋Š˜ ๋ช‡ ๋Œ€๋ฅผ ์ ๊ฒ€ํ–ˆ๋‚˜"๋Š” ์ธ์Šคํ„ด์Šค ๊ธฐ์ค€์ด๋ผ, ๊ทธ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ช‡ ๋ช…์—๊ฒŒ ํ†ต์ง€๋˜๋“  ํ•œ ๋ฒˆ๋งŒ ์…‰๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ˆ˜์ง‘ยทํƒ์ง€๋„ ์ธ์Šคํ„ด์Šค๋‹น ํ•œ ๋ฒˆ๋งŒ ๋Œ๋ฆฌ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์—ฌ๋Ÿฌ ๋ฆฌํฌํŠธ๊ฐ€ ๋‚˜๋ˆ  ์”๋‹ˆ๋‹ค.
๋ฐ˜๋ฉด "๋ฉ”์ผ ๋ช‡ ํ†ต ๋ณด๋ƒˆ๋‚˜"๋Š” ๋ฐ›๋Š” ์‚ฌ๋žŒ ๊ธฐ์ค€์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ๋‚ด์šฉ์ด๋ผ๋„ ํšŒ์‚ฌ ๋‹ด๋‹น์ž์—๊ฒŒ 1ํ†ต, ํ˜‘๋ ฅ์‚ฌ ๋‹ด๋‹น์ž์—๊ฒŒ 1ํ†ต์ด๋ฉด ๋ฐฐ๋‹ฌ์€ 2๊ฑด์ž…๋‹ˆ๋‹ค.
๋น„์œ ํ•˜์ž๋ฉด, ๊ฐ™์€ ๊ณต์ง€๋ฌธ์„ ๋ถ€์„œ์žฅ๊ณผ ์‹ค๋ฌด์ž์—๊ฒŒ ๊ฐ๊ฐ ๋ณด๋‚ด๋ฉด "๋ฐœ์†ก 2๊ฑด"์ด์ง€ "๊ณต์ง€ 2๊ฐœ"๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ณต์ง€ ๋‚ด์šฉ์€ ํ•˜๋‚˜์ง€๋งŒ ๋ฐฐ๋‹ฌ์€ ๋‘˜์ž…๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ๋Š” ๋‘ ์‚ฌ๋žŒ์˜ ๋ฆฌํฌํŠธ๊ฐ€ ์™„์ „ํžˆ ๊ฐ™์ง€๋„ ์•Š์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ ๋‹ด๋‹น์ž๊ฐ€ ํ˜‘๋ ฅ์‚ฌ ๋‹ด๋‹น์ž๋ณด๋‹ค ์„œ๋น„์Šค๋ฅผ ํ•˜๋‚˜ ๋” ๋งก๊ณ  ์žˆ์œผ๋ฉด ๋‹ด๋‹น ๋Œ€์ˆ˜ ์ž์ฒด๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ "๊ฐ™์€ ๋‚ด์šฉ ๋‘ ๋ฒˆ"์ด ์•„๋‹ˆ๋ผ "๋ฒ”์œ„๊ฐ€ ๋‹ค๋ฅธ ๋‘ ๋ฆฌํฌํŠธ๋ฅผ ๊ฐ์ž์—๊ฒŒ"๊ฐ€ ์ •ํ™•ํ•œ ํ‘œํ˜„์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆผ5


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์„ ํŒŒ์ผ๋กœ ๋ฝ‘์•„ ๊ทธ๋Œ€๋กœ ๋น„๊ตํ•ด ์™„์ „ํžˆ ๊ฐ™์€์ง€ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. "๋ฆฌํŒฉํ„ฐ๋งํ–ˆ๋Š”๋ฐ ์ถœ๋ ฅ์ด ๋˜‘๊ฐ™๋‹ค"๋ฅผ ๋ˆˆ์œผ๋กœ ์ฆ๋ช…ํ•œ ์…ˆ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋Œ€์ฒด ์ž‘์—…์€ "๋™์ž‘์ด ์•ˆ ๋ฐ”๋€Œ์—ˆ๋‹ค"๋ฅผ ๋ง๋กœ๋งŒ ํ•˜์ง€ ๋ง๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์‹ค์ œ๋กœ ๋น„๊ตํ•ด ๋‘๋Š” ๊ฒŒ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
๋‹ด๋‹น์ž ๋ฆฌํฌํŠธ์˜ ์ตœ์ข… ๊ตฌ์กฐ๋Š” ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

  1. ๋‹ด๋‹น์ž ํ—ค๋” + ์ „์ฒด ์š”์•ฝ โ€” ๋‹ด๋‹น ๋Œ€์ˆ˜, ์œ„ํ—˜/์ฃผ์˜/์ •์ƒ/์ˆ˜์ง‘๋Œ€๊ธฐ ๊ฐœ์ˆ˜. (์ œ๋ชฉ์€ ํ‰์†Œ ๋ฆฌํฌํŠธ์™€ ๊ฐ™๊ฒŒ ๋‘๊ณ , ์ˆ˜์‹ ์ž ์ด๋ฆ„์€ ์šฐ์ธก์— ์ž‘๊ฒŒ)
  2. ์„œ๋น„์Šค๋ณ„ ์š”์•ฝ ํ‘œ โ€” ๋‹ด๋‹นํ•œ ์„œ๋น„์Šค๋งˆ๋‹ค ๋Œ€์ˆ˜์™€ ๋“ฑ๊ธ‰ ๋ถ„ํฌ, ์—”์ง„ ๊ตฌ์„ฑ.
  3. ์„œ๋น„์Šค๋ณ„ ์ƒ์„ธ โ€” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์„œ๋น„์Šค๋งˆ๋‹ค ๋‹จ์ผ ๋ฆฌํฌํŠธ ๋ณธ๋ฌธ์„ ํ†ต์œผ๋กœ(์ปค๋ฒ„๋ฆฌ์ง€ยท์˜ค๋Š˜์˜ ํ•ต์‹ฌยท์ „์ผ ๋Œ€๋น„ยท์œ„ํ—˜/์ฃผ์˜ ์ƒ์„ธยท์ •์ƒ๊นŒ์ง€) ํŽผ์นจ. ์•„์ง ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ์ธ์Šคํ„ด์Šค๋Š” '์ˆ˜์ง‘ ๋Œ€๊ธฐ'๋กœ.
  4. ์ฃผ๊ฐ„ยท์›”๊ฐ„ ์•ˆ๋‚ด์™€ ํ‘ธํ„ฐ.

์ „์ฒด ์š”์•ฝ์˜ ๊ฐœ์ˆ˜๋ฅผ ์„ธ๋Š” ๋ถ€๋ถ„์€ ์ด๋ ‡๊ฒŒ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. ํƒ์ง€ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด ๋“ฑ๊ธ‰์œผ๋กœ, ์—†์œผ๋ฉด '์ˆ˜์ง‘ ๋Œ€๊ธฐ'๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค.

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


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


7. ์ง„ํ–‰ํ•˜๋ฉฐ ๋ง‰ํ˜”๋˜ ๊ฒƒ๋“ค

๋ช‡ ๊ฐ€์ง€ ์‹ค๋ฌด์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์”ฉ ํ’€์–ด ๊ฐ€๋Š” ๊ณผ์ •์ด ์˜คํžˆ๋ ค ์„ค๊ณ„๋ฅผ ๋‹ค๋“ฌ์–ด ์คฌ์Šต๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์•ˆ ๋‹ฟ์Œ. ํ˜„์žฅ ์—ฐ๋™์€ ์‚ฌ๋‚ด๋ง์˜ ๋ชจ๋‹ˆํ„ฐ๋ง API๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ, ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ทธ ์ฃผ์†Œ์— ๋‹ฟ์ง€ ์•Š์•„ ํ˜ธ์ถœ์ด ํƒ€์ž„์•„์›ƒ๋‚ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์•„๋ž˜ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ์Šต๋‹ˆ๋‹ค.

urllib.error.URLError: <urlopen error [WinError 10060]
  ์—ฐ๊ฒฐ๋œ ๊ตฌ์„ฑ์›์ด ์‘๋‹ต์„ ํ•˜์ง€ ์•Š์•„ ์—ฐ๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค ...>

๊ทธ๋ž˜์„œ ๊ฐœ๋ฐœ ๊ฒ€์ฆ์šฉ์œผ๋กœ๋Š” ๋ฏธ๋ฆฌ ๋ฐ›์•„ ๋‘” ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋„๋ก ์†Œ์Šค๋งŒ ๋ฐ”๊ฟ” ๋Œ๋ ธ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ค์ • ํ•œ ์ค„๋กœ ๊ฐˆ์•„ ๋ผ์šฐ๋„๋ก ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•ด ๋‘” ๊ฒŒ ์—ฌ๊ธฐ์„œ ๋„์›€์ด ๋์Šต๋‹ˆ๋‹ค. ์ˆ˜์ง‘ ๋ฐฉ์‹์ด ๋ฐ”๋€Œ์–ด๋„ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค. ์—ฌํ–‰์šฉ ๋ฉ€ํ‹ฐ ์–ด๋Œ‘ํ„ฐ์ฒ˜๋Ÿผ, ์ฝ˜์„ผํŠธ ๋ชจ์–‘๋งŒ ๊ฐˆ์•„ ๋ผ์šฐ๋ฉด ๊ธฐ๊ธฐ๋Š” ๊ทธ๋Œ€๋กœ ์“ฐ๋Š” ์…ˆ์ž…๋‹ˆ๋‹ค.

์˜์กด์„ฑ ํ™˜๊ฒฝ. ํƒ์ง€์— ์“ฐ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์‹œ์Šคํ…œ ๊ธฐ๋ณธ ํŒŒ์ด์ฌ์—๋Š” ์—†์–ด์„œ, ํ”„๋กœ์ ํŠธ ์ „์šฉ ๊ฐ€์ƒํ™˜๊ฒฝ์œผ๋กœ ๋Œ๋ ค์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฒ˜์Œ ์‹คํ–‰ํ•  ๋•Œ ModuleNotFoundError๋กœ ํ•œ ๋ฒˆ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค. ์ „์šฉ ๊ฐ€์ƒํ™˜๊ฒฝ์˜ ํŒŒ์ด์ฌ์œผ๋กœ ์‹คํ–‰ํ•˜๋‹ˆ ํ•ด๊ฒฐ๋์Šต๋‹ˆ๋‹ค.

์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋น ์ง. ์•ž์—์„œ ์ด์•ผ๊ธฐํ•œ, ๋ถ€ํ’ˆ๋งŒ ๊ณจ๋ผ ์˜ฎ๊ธฐ๋‹ค ์ปค๋ฒ„๋ฆฌ์ง€ ์นด๋“œ๋ฅผ ํ˜๋ฆฐ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ๋ณธ๋ฌธ ์ „์ฒด๋ฅผ ๋นŒ๋”๋กœ ๋–ผ์–ด ํ†ต์งธ ์žฌ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์œผ๋กœ ๋ฐฉํ–ฅ์„ ๋ฐ”๊ฟ” ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. "์ผ๋ถ€๋งŒ ์žฌ์‚ฌ์šฉ"์ด ์˜คํžˆ๋ ค ํ•จ์ •์ด์—ˆ๊ณ , "์ „์ฒด๋ฅผ ์žฌ์‚ฌ์šฉ"์ด ๋” ์•ˆ์ „ํ–ˆ์Šต๋‹ˆ๋‹ค.
์ค‘๋ณต ์นด์šดํŠธ. ์œ„์—์„œ ์„ค๋ช…ํ•œ, ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•œ ์ˆ˜์‹ ์ž ๋ฐ”๊ตฌ๋‹ˆ์— ๋‘ ๋ฒˆ ๋“ค์–ด๊ฐˆ ๊ฐ€๋Šฅ์„ฑ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ๊ทธ๋Ÿฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์ง„ ์•Š์•˜์ง€๋งŒ, ํšŒ์‚ฌยทํ˜‘๋ ฅ์‚ฌ ์ด๋ฉ”์ผ์ด ์šฐ์—ฐํžˆ ๊ฐ™์„ ๋•Œ๋ฅผ ๋Œ€๋น„ํ•ด ๋ฐฉ์–ด๋ฅผ ๋„ฃ์–ด ๋’€์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ์–ด๋Š” ์ง€๊ธˆ ๋‹น์žฅ ๋ฌธ์ œ๊ฐ€ ์—†์–ด๋„, ๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์กฐ์šฉํžˆ ํ‹€๋ฆฐ ์ˆซ์ž๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์–ด ๋ฏธ๋ฆฌ ๋ง‰์•„ ๋‘๋Š” ํŽธ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


๋งˆ๋ฌด๋ฆฌ

์ •๋ฆฌํ•˜๋ฉด, ์ˆ˜๋ฐฑ ๋Œ€ ๊ทœ๋ชจ์˜ ์ ๊ฒ€ ๋ฆฌํฌํŠธ๋ฅผ ๋‹ด๋‹น์ž๋ณ„๋กœ ๋‚˜๋ˆ  ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด ๋‹ค์Œ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์ง€์ €๋ถ„ํ•œ ๋งคํ•‘ ์›๋ณธ์„ ํ‘œ์ค€ ํ•œ ์‹œํŠธ๋กœ ์ •๊ทœํ™”ํ•˜๊ณ , ์ดํ›„์—๋Š” ๊ทธ ํ‘œ์ค€๋งŒ ์ฝ๋Š” ์งง์€ ์ฝ”๋“œ๋กœ ๋‹จ์ˆœํ™”. ๋ฌด๊ฑฐ์šด ์ฝ”๋“œ๋Š” ํ•œ ๋ฒˆ๋งŒ.
  • ์ธ์Šคํ„ด์Šค๋ฅผ ๋‹ด๋‹น์ž ๊ธฐ์ค€์œผ๋กœ ๋’ค์ง‘์–ด(๋ผ์šฐํŒ…) ์ˆ˜์‹ ์ž๋ณ„๋กœ ๋ฌถ๊ณ , ํ•œ ์ˆ˜์‹ ์ž ์•ˆ์—์„œ๋Š” ์ค‘๋ณต์„ ๋ฐฉ์ง€.
  • ๋ฐœ์†ก์€ ์ˆ˜์‹ ์ž ๋‹จ์œ„, ์ ๊ฒ€์€ ์ธ์Šคํ„ด์Šค ์œ ๋‹ˆํฌ ๋ผ๋Š” ๋‘ ์ง€ํ‘œ๋ฅผ ๋ถ„๋ฆฌํ•ด ์นด์šดํŠธ ํ˜ผ๋™์„ ์ œ๊ฑฐ.
  • ๋‹ด๋‹น์ž ํ•œ ๋ช…๋ถ„ ๋ฆฌํฌํŠธ๋ฅผ ๋‹จ์ผ ๋ฆฌํฌํŠธ ๋ณธ๋ฌธ ๋ Œ๋”๋Ÿฌ๋ฅผ ์„œ๋น„์Šค๋ณ„๋กœ ํ†ต์งธ ์žฌ์‚ฌ์šฉํ•ด ์กฐ๋ฆฝํ•˜๊ณ (์ปค๋ฒ„๋ฆฌ์ง€ยท์˜ค๋Š˜์˜ ํ•ต์‹ฌยท์ „์ผ ๋Œ€๋น„๊นŒ์ง€ ๊ทธ๋Œ€๋กœ), ์•„์ง ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ์ธ์Šคํ„ด์Šค๋Š” '์ˆ˜์ง‘ ๋Œ€๊ธฐ'๋กœ ์ •์งํ•˜๊ฒŒ ํ‘œ๊ธฐ.
  • ์ˆ˜์‹ ์ž ์ „์›์„ ์ผ๊ด„ ์ƒ์„ฑํ•˜๊ณ , ๋‚ ์งœ๋ณ„ยท์ˆ˜์‹ ์ž๋ณ„ ํŒŒ์ผ๊ณผ ์ด๋ ฅ ์ธ๋ฑ์Šค๋กœ ๋ฐœ์†ก ๊ธฐ๋ก์„ ๋‚จ๊น€.

์•„์ง ๋‚จ์€ ์ผ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ์‹ค์ œ๋กœ ์ˆ˜์ง‘ํ•˜๋ ค๋ฉด ์—”์ง„๋ณ„ ์ž„๊ณ„๊ฐ’๊ณผ ์ˆ˜์ง‘ ๋Œ€์ƒ ์ง€ํ‘œ๋ฅผ ํ™•๋ณดํ•ด์•ผ ํ•˜๊ณ , ์‹ค์ œ ๋ฉ”์ผ ๋ฐœ์†ก์€ ์‚ฌ๋‚ด ๋ฉ”์ผ ์„œ๋ฒ„๊ฐ€ ํ™•์ •๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๋ผ์šฐํŒ…๊ณผ ๋ฆฌํฌํŠธ ์กฐ๋ฆฝ, ์ด๋ ฅ๊นŒ์ง€์˜ ๋ผˆ๋Œ€๋Š” ์™„์„ฑ๋ผ์„œ, ๋‚จ์€ ๊ฑด ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์›Œ ๋„ฃ๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ํ‹€์ด ์„œ ์žˆ์œผ๋ฉด, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ™๋Š” ์ˆœ๊ฐ„ ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ง€๊ธˆ์˜ ์ƒ˜ํ”Œ์ฒ˜๋Ÿผ ๊ฝ‰ ์ฐฌ ์ƒ์„ธ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค.
๊ฐœ์ธ์ ์œผ๋กœ ์ด๋ฒˆ ์ž‘์—…์—์„œ ๊ฐ€์žฅ ์˜ค๋ž˜ ๋‚จ์€ ๊ตํ›ˆ์€ ๋‘ ๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” "์ง€์ €๋ถ„ํ•œ ์ž…๋ ฅ์€ ๊ฒฝ๊ณ„์—์„œ ํ•œ ๋ฒˆ์— ์ •๋ฆฌํ•˜๋ผ" โ€” ํ‘œ์ค€์œผ๋กœ ๋ฐ”๊ฟ” ๋‘๋ฉด ์•ˆ์ชฝ ์ฝ”๋“œ๊ฐ€ ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” "์ผ๋ถ€๋งŒ ์žฌ์‚ฌ์šฉํ•˜๋ ค๋‹ค ํ˜๋ฆฌ๋А๋‹ˆ ์ „์ฒด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋ผ" โ€” ๋ถ€ํ’ˆ์„ ๊ณจ๋ผ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋ณด๋‹ค, ๊ฒ€์ฆ๋œ ๋ฉ์–ด๋ฆฌ๋ฅผ ํ†ต์งธ๋กœ ๋‹ค์‹œ ์“ฐ๋Š” ํŽธ์ด ๋น ์ง€๋Š” ๊ฒŒ ์—†์Šต๋‹ˆ๋‹ค.
๊ธฐ์ˆ  ์Šคํƒ: Python ยท openpyxl(์—‘์…€ ํŒŒ์‹ฑ) ยท PyYAML(์ค‘๊ฐ„ ์„ค์ •). ๋ฐ์ดํ„ฐ ์†Œ์Šค๋Š” ์„ค์ • ํ•œ ์ค„๋กœ ๊ต์ฒด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ถ„๋ฆฌ, ๋ฆฌํฌํŠธ ๋ Œ๋”๋ง์€ ๋‹จ์ผ ๋ฆฌํฌํŠธ ๋ณธ๋ฌธ ๋นŒ๋”๋ฅผ ๋‹ด๋‹น์ž ๋ฆฌํฌํŠธ๊ฐ€ ์„œ๋น„์Šค๋ณ„๋กœ ์žฌ์‚ฌ์šฉ.