Files
JH/exports/create_erd_clean_image.py

63 lines
4.9 KiB
Python

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
OUT=Path('/home/hyein/jh-mh/장헌산업/exports')
PNG=OUT/'work_data_erd_clean.png'
W,H=1700,1150
img=Image.new('RGB',(W,H),'#151718'); d=ImageDraw.Draw(img)
font=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',17)
font_b=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',20)
font_t=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',30)
font_s=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',14)
BG='#151718'; CARD='#1f2426'; HEADER='#252b2e'; BORDER='#59636a'; TEXT='#edf2f4'; MUTED='#a9b2b8'; PK='#f5d76e'; FK='#87c8ff'
BLUE='#60d7f2'; GREEN='#80e27e'; PURPLE='#b99bff'; GRAY='#9ca7ad'
entities={
'member':(70,150,300,['PK MemberNo','korName','teamName','rankName','groupCode','rankCode','isRetired']),
'dallyproject':(70,490,330,['PK id','FK MemberNo','WorkDate','EntryPCode','EntryTime / LeaveTime','OverTime','TotalHours','RegularHours','OvertimeHours']),
'project_alias':(70,875,330,['PK projectCode','shortName','bridge/project name']),
'site_worksheet_worker_cache':(590,120,390,['PK projectCode + workDate','PK korName + jobType','workText','note','personCount','syncedAt']),
'site_worksheet_record':(590,455,390,['PK projectCode + workDate','PK memberNo + korName','FK memberNo','jobType','workText','note','personCount']),
'site_worksheet_day_sync':(590,760,390,['PK projectCode + workDate','syncedAt']),
'site_worksheet_menu_sync':(590,940,390,['PK projectCode + workDate','PK selMenu','2 = normal worksheet','3 = tension/temp works','syncedAt']),
'work_calendar_detail':(1190,255,370,['PK id','source: sql / site','FK memberNo','workDate','projectCode','projectName','workText','jobType','hours','regularHours','overtimeHours','personCount']),
'work_calendar_day':(1190,745,370,['PK memberNo + workDate','korName / teamName / rankName','sqlHours','sqlProjectCodes','siteCount','siteProjectCodes','siteWorkTexts','hasSql / hasSite'])}
heights={k:52+len(v[3])*25+18 for k,v in entities.items()}
def card(n):
x,y,w,fs=entities[n]; h=heights[n]
d.rounded_rectangle((x,y,x+w,y+h),radius=8,fill=CARD,outline=BORDER,width=2)
d.rectangle((x,y,x+w,y+42),fill=HEADER); d.line((x,y+42,x+w,y+42),fill=BORDER,width=1)
d.text((x+16,y+11),n,font=font_b,fill=TEXT); yy=y+58
for f in fs:
c=PK if f.startswith('PK') else FK if f.startswith('FK') else TEXT
d.text((x+18,yy),f,font=font_s,fill=c); yy+=25
def a(n,s,off=0):
x,y,w,_=entities[n]; h=heights[n]
return {'r':(x+w,y+h//2+off),'l':(x,y+h//2+off),'t':(x+w//2+off,y),'b':(x+w//2+off,y+h)}[s]
def arrow(points,label,color):
d.line(points,fill=color,width=4,joint='curve')
x,y=points[-1]; d.polygon([(x,y),(x-10,y-6),(x-10,y+6)],fill=color)
mx,my=points[len(points)//2]
tw=d.textlength(label,font=font_s)
d.rounded_rectangle((mx-tw/2-8,my-14,mx+tw/2+8,my+14),radius=5,fill=BG,outline='#4b5358')
d.text((mx-tw/2,my-9),label,font=font_s,fill=MUTED)
# columns
for x1,x2,title in [(40,430,'SOURCE'),(540,1030,'ERP CACHE / MATCH'),(1140,1620,'CALENDAR')]:
d.rounded_rectangle((x1,105,x2,1080),radius=12,fill='#181b1c',outline='#2c3235')
d.text((x1+22,126),title,font=font_b,fill='#7f8a90')
d.text((60,38),'Work Data ERD',font=font_t,fill=TEXT)
d.text((60,75),'Clean column layout with primary data-flow lines',font=font_s,fill=MUTED)
# main flow only
arrow([a('member','r',0),(500,a('member','r')[1]),(500,a('site_worksheet_record','l')[1]),a('site_worksheet_record','l')],'employee match',BLUE)
arrow([a('dallyproject','r',0),(1100,a('dallyproject','r')[1]),(1100,a('work_calendar_detail','l',-55)[1]),a('work_calendar_detail','l',-55)],'SQL work -> detail',BLUE)
arrow([a('project_alias','r',0),(520,a('project_alias','r')[1]),(520,a('site_worksheet_worker_cache','l',-20)[1]),a('site_worksheet_worker_cache','l',-20)],'project code/name',GREEN)
arrow([a('site_worksheet_worker_cache','b'),(785,420),a('site_worksheet_record','t')],'raw rows -> matched rows',PURPLE)
arrow([a('site_worksheet_day_sync','t'),(980,700),(980,300),a('site_worksheet_worker_cache','r',35)],'date fetched',GRAY)
arrow([a('site_worksheet_menu_sync','t'),(1030,880),(1030,320),a('site_worksheet_worker_cache','r',75)],'menu 2/3 fetched',GRAY)
arrow([a('site_worksheet_record','r'),(1100,a('site_worksheet_record','r')[1]),(1100,a('work_calendar_detail','l',45)[1]),a('work_calendar_detail','l',45)],'site work -> detail',PURPLE)
arrow([a('project_alias','r',40),(1135,a('project_alias','r')[1]+40),(1135,a('work_calendar_detail','l',95)[1]),a('work_calendar_detail','l',95)],'projectName',GREEN)
arrow([a('work_calendar_detail','b'),(1375,710),a('work_calendar_day','t')],'daily summary',GRAY)
for n in entities: card(n)
# legend
d.rounded_rectangle((60,1090,1600,1138),radius=8,fill='#1d2123',outline='#3a4247')
d.text((80,1103),'Blue employee/SQL Green project name Purple ERP worksheet match Gray sync/calendar flow',font=font_s,fill=MUTED)
img.save(PNG); print(PNG)