Files
JH/exports/create_erd_image.py

110 lines
6.2 KiB
Python

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
OUT = Path('/home/hyein/jh-mh/장헌산업/exports')
PNG = OUT / 'work_data_erd.png'
SVG = OUT / 'work_data_erd.svg'
W, H = 1900, 1280
img = Image.new('RGB', (W, H), '#f6f8fb')
d = ImageDraw.Draw(img)
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 18)
font_b = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 20)
font_title = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 34)
font_small = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 15)
entities = {
'member': (70, 110, 330, 330, ['PK MemberNo', 'korName', 'teamName', 'rankName', 'groupCode', 'rankCode', 'isRetired']),
'dallyproject': (470, 80, 780, 360, ['PK id', 'FK MemberNo', 'WorkDate', 'EntryPCode', 'EntryTime / LeaveTime', 'TotalHours', 'RegularHours', 'OvertimeHours']),
'project_alias': (1040, 110, 1330, 280, ['PK projectCode', 'shortName (bridge/project name)']),
'site_worksheet_worker_cache': (1360, 360, 1810, 610, ['PK projectCode + workDate + korName', 'jobType', 'workText', 'note', 'personCount', 'syncedAt']),
'site_worksheet_record': (900, 430, 1260, 690, ['PK projectCode + workDate + memberNo + korName', 'FK memberNo', 'jobType', 'workText', 'personCount']),
'site_worksheet_day_sync': (1370, 720, 1740, 870, ['PK projectCode + workDate', 'syncedAt']),
'site_worksheet_menu_sync': (1370, 930, 1740, 1090, ['PK projectCode + workDate + selMenu', 'selMenu 2 = normal', 'selMenu 3 = tension/temp works', 'syncedAt']),
'work_calendar_detail': (520, 800, 900, 1110, ['PK id', 'source: sql / site', 'FK memberNo', 'workDate', 'projectCode', 'projectName', 'workText', 'hours / regular / overtime', 'personCount']),
'work_calendar_day': (120, 770, 430, 1080, ['PK memberNo + workDate', 'korName / teamName / rankName', 'sqlHours', 'sqlProjectCodes', 'siteCount', 'siteProjectCodes', 'siteWorkTexts', 'hasSql / hasSite']),
}
colors = {
'member': ('#e0f2fe', '#0369a1'),
'dallyproject': ('#ecfdf5', '#047857'),
'project_alias': ('#fff7ed', '#c2410c'),
'site_worksheet_worker_cache': ('#fef3c7', '#b45309'),
'site_worksheet_record': ('#ede9fe', '#6d28d9'),
'site_worksheet_day_sync': ('#f1f5f9', '#475569'),
'site_worksheet_menu_sync': ('#f1f5f9', '#475569'),
'work_calendar_detail': ('#fee2e2', '#b91c1c'),
'work_calendar_day': ('#dbeafe', '#1d4ed8'),
}
def box(name, rect, fields):
x1,y1,x2,y2 = rect
fill, stroke = colors[name]
d.rounded_rectangle(rect, radius=14, fill=fill, outline=stroke, width=3)
d.rectangle((x1, y1, x2, y1+42), fill=stroke)
d.text((x1+14, y1+10), name, fill='white', font=font_b)
y = y1 + 55
for f in fields:
d.text((x1+16, y), f, fill='#0f172a', font=font_small if len(f) > 34 else font)
y += 26
def center(name, side):
x1,y1,x2,y2 = entities[name][:4]
if side == 'right': return (x2, (y1+y2)//2)
if side == 'left': return (x1, (y1+y2)//2)
if side == 'top': return ((x1+x2)//2, y1)
if side == 'bottom': return ((x1+x2)//2, y2)
def line(a, aside, b, bside, label, color='#334155'):
p1 = center(a, aside); p2 = center(b, bside)
d.line((p1, p2), fill=color, width=3)
# endpoint dots
for p in (p1, p2):
d.ellipse((p[0]-5,p[1]-5,p[0]+5,p[1]+5), fill=color)
mx, my = (p1[0]+p2[0])//2, (p1[1]+p2[1])//2
tw = d.textlength(label, font=font_small)
d.rounded_rectangle((mx-tw/2-8, my-13, mx+tw/2+8, my+13), radius=7, fill='#ffffff', outline='#cbd5e1')
d.text((mx-tw/2, my-9), label, fill=color, font=font_small)
# title
d.text((70, 30), 'Work Data ERD: SQL + Site Worksheet Integration', fill='#0f172a', font=font_title)
d.text((72, 72), 'SQLite DB: /home/hyein/jh-mh/장헌산업/matching.db', fill='#475569', font=font_small)
for name, data in entities.items():
box(name, data[:4], data[4])
# relationships
line('member','right','dallyproject','left','MemberNo')
line('member','right','site_worksheet_record','left','MemberNo')
line('member','bottom','work_calendar_day','top','MemberNo + workDate')
line('dallyproject','bottom','work_calendar_detail','top','source=sql')
line('site_worksheet_record','bottom','work_calendar_detail','right','source=site')
line('work_calendar_detail','left','work_calendar_day','right','daily summary')
line('project_alias','bottom','site_worksheet_record','top','projectCode')
line('project_alias','right','site_worksheet_worker_cache','top','projectCode')
line('site_worksheet_worker_cache','left','site_worksheet_record','right','match by name/date/project')
line('site_worksheet_day_sync','top','site_worksheet_worker_cache','bottom','project+date fetched')
line('site_worksheet_menu_sync','top','site_worksheet_worker_cache','bottom','menu 2/3 fetched')
line('project_alias','left','dallyproject','right','EntryPCode')
line('project_alias','bottom','work_calendar_detail','right','projectName')
# legend
lx, ly = 70, 1140
d.rounded_rectangle((lx, ly, 760, ly+95), radius=12, fill='#ffffff', outline='#cbd5e1', width=2)
d.text((lx+18, ly+14), 'Flow', font=font_b, fill='#0f172a')
d.text((lx+18, ly+43), 'dallyproject = SQL work records / site_worksheet_worker_cache = ERP site worksheet raw rows', font=font_small, fill='#334155')
d.text((lx+18, ly+68), 'site_worksheet_record = matched employee rows / work_calendar_* = calendar-ready integrated data', font=font_small, fill='#334155')
img.save(PNG)
# simple SVG wrapper embeds the PNG path as text fallback is not needed; create standalone SVG rectangles too minimal
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">
<rect width="100%" height="100%" fill="#f6f8fb"/>
<text x="70" y="55" font-family="DejaVu Sans, Arial" font-size="34" font-weight="700" fill="#0f172a">Work Data ERD: SQL + Site Worksheet Integration</text>
<text x="72" y="82" font-family="DejaVu Sans, Arial" font-size="15" fill="#475569">PNG version generated at {PNG}</text>
<image href="{PNG.name}" x="0" y="0" width="{W}" height="{H}" opacity="0"/>
<text x="70" y="150" font-family="DejaVu Sans, Arial" font-size="22" fill="#334155">Open the PNG file for the full ERD diagram.</text>
</svg>'''
SVG.write_text(svg, encoding='utf-8')
print(PNG)
print(SVG)