Create fund.py
Browse files
fund.py
ADDED
|
@@ -0,0 +1,1661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
🎮 OKCEO 정책자금 사전심사 시스템 v7.0 - 미네랄핵 ULTRA 에디션
|
| 3 |
+
================================================================================
|
| 4 |
+
🆕 비주얼 차트 18개 (기존 6개 + 신규 12개)
|
| 5 |
+
|
| 6 |
+
[기존 차트 - 개선]
|
| 7 |
+
1. 업종별 재무비율 벤치마크 테이블
|
| 8 |
+
2. 유의사항 체크리스트 대시보드
|
| 9 |
+
3. 지원가능성 매트릭스
|
| 10 |
+
4. 예상금액 워터폴 차트
|
| 11 |
+
5. 프로세스 진행 타임라인
|
| 12 |
+
6. 신용점수 시뮬레이션
|
| 13 |
+
|
| 14 |
+
[신규 차트 - 고급 비주얼] ⭐
|
| 15 |
+
7. 종합점수 3D 게이지 (애니메이션)
|
| 16 |
+
8. 재무건전성 레이더 차트 (SVG 애니메이션)
|
| 17 |
+
9. 기관별 승인확률 도넛 차트 (인터랙티브)
|
| 18 |
+
10. 자금조달 파이프라인 플로우
|
| 19 |
+
11. 리스크 히트맵 (카테고리별)
|
| 20 |
+
12. 경쟁력 벤치마크 바 차트
|
| 21 |
+
13. 💎 스코어카드 대시보드 (KPI)
|
| 22 |
+
14. 💎 자금흐름 산키 다이어그램
|
| 23 |
+
15. 💎 월별 추세 라인 차트
|
| 24 |
+
16. 💎 기관비교 레이더 오버레이
|
| 25 |
+
17. 💎 성공/실패 요인 트리맵
|
| 26 |
+
18. 💎 종합 인포그래픽 보드
|
| 27 |
+
|
| 28 |
+
================================================================================
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
import gradio as gr
|
| 32 |
+
from dataclasses import dataclass, field
|
| 33 |
+
from typing import Dict, List, Optional, Any, Tuple
|
| 34 |
+
from datetime import datetime, date
|
| 35 |
+
import json
|
| 36 |
+
import math
|
| 37 |
+
|
| 38 |
+
# ============================================================================
|
| 39 |
+
# cache_db 연동 (app.py 통합 시 DB 저장/불러오기)
|
| 40 |
+
# ============================================================================
|
| 41 |
+
try:
|
| 42 |
+
from cache_db import get_fund_cache
|
| 43 |
+
_fund_cache = get_fund_cache()
|
| 44 |
+
HAS_CACHE_DB = True
|
| 45 |
+
except ImportError:
|
| 46 |
+
HAS_CACHE_DB = False
|
| 47 |
+
_fund_cache = None
|
| 48 |
+
|
| 49 |
+
# ============================================================================
|
| 50 |
+
# CUSTOM CSS - 울트라 다크 메탈릭 + 네온 글로우 테마
|
| 51 |
+
# ============================================================================
|
| 52 |
+
|
| 53 |
+
CUSTOM_CSS = """
|
| 54 |
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&family=Orbitron:wght@400;700;900&family=Rajdhani:wght@500;700&display=swap');
|
| 55 |
+
|
| 56 |
+
header,.huggingface-space-header,footer{display:none!important}
|
| 57 |
+
|
| 58 |
+
:root {
|
| 59 |
+
--neon-green: #6fd9a8;
|
| 60 |
+
--neon-blue: #6495ed;
|
| 61 |
+
--neon-purple: #9b59b6;
|
| 62 |
+
--neon-orange: #ff9500;
|
| 63 |
+
--neon-red: #ff6b6b;
|
| 64 |
+
--neon-yellow: #ffd93d;
|
| 65 |
+
--neon-cyan: #00d4ff;
|
| 66 |
+
--bg-dark: #0d0d1a;
|
| 67 |
+
--bg-card: #1a1a2e;
|
| 68 |
+
--bg-panel: #252540;
|
| 69 |
+
--text-primary: #e8e8f0;
|
| 70 |
+
--text-secondary: #8888a0;
|
| 71 |
+
--glow-green: rgba(111,217,168,0.5);
|
| 72 |
+
--glow-blue: rgba(100,149,237,0.5);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
html,body{background:var(--bg-dark)!important;color:var(--text-primary)!important;}
|
| 76 |
+
|
| 77 |
+
.gradio-container{
|
| 78 |
+
font-family:'Noto Sans KR','Rajdhani',sans-serif!important;
|
| 79 |
+
background:linear-gradient(135deg,#0d1117 0%,#161b22 50%,#0d1117 100%)!important;
|
| 80 |
+
max-width:100%!important;padding:20px!important;min-height:100vh;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/* ============ 네온 글로우 애니메이션 ============ */
|
| 84 |
+
@keyframes neon-pulse {
|
| 85 |
+
0%, 100% {
|
| 86 |
+
box-shadow: 0 0 5px var(--neon-green), 0 0 10px var(--neon-green), 0 0 20px var(--glow-green);
|
| 87 |
+
border-color: var(--neon-green);
|
| 88 |
+
}
|
| 89 |
+
50% {
|
| 90 |
+
box-shadow: 0 0 10px var(--neon-green), 0 0 20px var(--neon-green), 0 0 40px var(--glow-green);
|
| 91 |
+
border-color: #8fe8c0;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
@keyframes rotate-3d {
|
| 96 |
+
0% { transform: perspective(500px) rotateY(0deg); }
|
| 97 |
+
100% { transform: perspective(500px) rotateY(360deg); }
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
@keyframes fill-bar {
|
| 101 |
+
from { width: 0%; }
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
@keyframes count-up {
|
| 105 |
+
from { opacity: 0; transform: scale(0.5) translateY(20px); }
|
| 106 |
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
@keyframes slide-up {
|
| 110 |
+
from { opacity: 0; transform: translateY(30px); }
|
| 111 |
+
to { opacity: 1; transform: translateY(0); }
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
@keyframes shimmer {
|
| 115 |
+
0% { background-position: -200% center; }
|
| 116 |
+
100% { background-position: 200% center; }
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
@keyframes float {
|
| 120 |
+
0%, 100% { transform: translateY(0); }
|
| 121 |
+
50% { transform: translateY(-10px); }
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
@keyframes radar-scan {
|
| 125 |
+
0% { transform: rotate(0deg); opacity: 0.8; }
|
| 126 |
+
100% { transform: rotate(360deg); opacity: 0.8; }
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
@keyframes pulse-ring {
|
| 130 |
+
0% { transform: scale(0.8); opacity: 1; }
|
| 131 |
+
100% { transform: scale(1.5); opacity: 0; }
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
@keyframes glow-text {
|
| 135 |
+
0%, 100% { text-shadow: 0 0 10px var(--neon-green), 0 0 20px var(--neon-green); }
|
| 136 |
+
50% { text-shadow: 0 0 20px var(--neon-green), 0 0 40px var(--neon-green), 0 0 60px var(--neon-green); }
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
@keyframes border-flow {
|
| 140 |
+
0% { border-color: var(--neon-green); }
|
| 141 |
+
33% { border-color: var(--neon-blue); }
|
| 142 |
+
66% { border-color: var(--neon-purple); }
|
| 143 |
+
100% { border-color: var(--neon-green); }
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
/* ============ 차트 컨테이너 스타일 ============ */
|
| 147 |
+
.chart-ultra {
|
| 148 |
+
background: linear-gradient(145deg, #1a1a2e 0%, #0d0d1a 100%)!important;
|
| 149 |
+
border-radius: 20px!important;
|
| 150 |
+
padding: 28px!important;
|
| 151 |
+
margin: 16px 0!important;
|
| 152 |
+
border: 1px solid rgba(111,217,168,0.2)!important;
|
| 153 |
+
box-shadow: 0 10px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05)!important;
|
| 154 |
+
position: relative;
|
| 155 |
+
overflow: hidden;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.chart-ultra::before {
|
| 159 |
+
content: '';
|
| 160 |
+
position: absolute;
|
| 161 |
+
top: 0;
|
| 162 |
+
left: 0;
|
| 163 |
+
right: 0;
|
| 164 |
+
height: 1px;
|
| 165 |
+
background: linear-gradient(90deg, transparent, var(--neon-green), transparent);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.chart-ultra:hover {
|
| 169 |
+
border-color: rgba(111,217,168,0.4)!important;
|
| 170 |
+
box-shadow: 0 15px 50px rgba(0,0,0,0.6), 0 0 30px rgba(111,217,168,0.1)!important;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* ============ 스코어카드 스타일 ============ */
|
| 174 |
+
.score-card {
|
| 175 |
+
background: linear-gradient(145deg, #252540, #1a1a2e);
|
| 176 |
+
border-radius: 16px;
|
| 177 |
+
padding: 24px;
|
| 178 |
+
text-align: center;
|
| 179 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 180 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 181 |
+
position: relative;
|
| 182 |
+
overflow: hidden;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.score-card::after {
|
| 186 |
+
content: '';
|
| 187 |
+
position: absolute;
|
| 188 |
+
top: -50%;
|
| 189 |
+
left: -50%;
|
| 190 |
+
width: 200%;
|
| 191 |
+
height: 200%;
|
| 192 |
+
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.03), transparent);
|
| 193 |
+
transform: rotate(45deg);
|
| 194 |
+
transition: all 0.5s;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.score-card:hover {
|
| 198 |
+
transform: translateY(-8px) scale(1.02);
|
| 199 |
+
border-color: var(--neon-green);
|
| 200 |
+
box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 30px rgba(111,217,168,0.2);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.score-card:hover::after {
|
| 204 |
+
left: 100%;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.score-card.highlight {
|
| 208 |
+
animation: neon-pulse 2s ease-in-out infinite;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.score-card .value {
|
| 212 |
+
font-family: 'Orbitron', sans-serif;
|
| 213 |
+
font-size: 42px;
|
| 214 |
+
font-weight: 900;
|
| 215 |
+
background: linear-gradient(135deg, #fff, var(--neon-green));
|
| 216 |
+
-webkit-background-clip: text;
|
| 217 |
+
-webkit-text-fill-color: transparent;
|
| 218 |
+
animation: count-up 0.8s ease-out;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.score-card .label {
|
| 222 |
+
color: var(--text-secondary);
|
| 223 |
+
font-size: 13px;
|
| 224 |
+
margin-top: 8px;
|
| 225 |
+
text-transform: uppercase;
|
| 226 |
+
letter-spacing: 1px;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
/* ============ 프로그레스 바 ============ */
|
| 230 |
+
.progress-ultra {
|
| 231 |
+
height: 12px;
|
| 232 |
+
background: #1a1a2e;
|
| 233 |
+
border-radius: 6px;
|
| 234 |
+
overflow: hidden;
|
| 235 |
+
box-shadow: inset 0 2px 4px rgba(0,0,0,0.5);
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.progress-ultra .fill {
|
| 239 |
+
height: 100%;
|
| 240 |
+
border-radius: 6px;
|
| 241 |
+
background: linear-gradient(90deg, var(--neon-green), #8fe8c0, var(--neon-green));
|
| 242 |
+
background-size: 200% 100%;
|
| 243 |
+
animation: shimmer 2s linear infinite, fill-bar 1s ease-out;
|
| 244 |
+
box-shadow: 0 0 10px var(--glow-green);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
/* ============ 버튼 스타일 ============ */
|
| 248 |
+
button,.gr-button{
|
| 249 |
+
background: linear-gradient(145deg, #2a2a45, #1a1a30)!important;
|
| 250 |
+
color:var(--text-primary)!important;
|
| 251 |
+
border:1px solid rgba(111,217,168,0.3)!important;
|
| 252 |
+
border-radius:12px!important;
|
| 253 |
+
font-weight:600!important;
|
| 254 |
+
transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1)!important;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
button:hover,.gr-button:hover{
|
| 258 |
+
background: linear-gradient(145deg, #3a3a55, #2a2a40)!important;
|
| 259 |
+
transform:translateY(-3px)!important;
|
| 260 |
+
box-shadow:0 10px 30px rgba(0,0,0,0.4), 0 0 20px rgba(111,217,168,0.2)!important;
|
| 261 |
+
border-color: var(--neon-green)!important;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.analyze-btn button{
|
| 265 |
+
background:linear-gradient(135deg,#1a6b4a,#3a8f6a,#6fd9a8)!important;
|
| 266 |
+
color:#fff!important;
|
| 267 |
+
font-size:18px!important;
|
| 268 |
+
padding:18px 50px!important;
|
| 269 |
+
border:2px solid var(--neon-green)!important;
|
| 270 |
+
text-shadow: 0 0 10px rgba(0,0,0,0.5);
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.analyze-btn button:hover{
|
| 274 |
+
box-shadow:0 10px 40px rgba(111,217,168,0.4), 0 0 60px rgba(111,217,168,0.2)!important;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
/* ============ 입력 필드 ============ */
|
| 278 |
+
input,textarea,select{
|
| 279 |
+
background:#1a1a2e!important;
|
| 280 |
+
color:var(--text-primary)!important;
|
| 281 |
+
border:1px solid rgba(111,217,168,0.2)!important;
|
| 282 |
+
border-radius:10px!important;
|
| 283 |
+
transition:all 0.3s ease!important;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
input:focus,textarea:focus,select:focus{
|
| 287 |
+
border-color:var(--neon-green)!important;
|
| 288 |
+
box-shadow:0 0 20px rgba(111,217,168,0.2)!important;
|
| 289 |
+
outline:none!important;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
/* ============ 탭 스타일 ============ */
|
| 293 |
+
[role="tablist"]{
|
| 294 |
+
background:transparent!important;
|
| 295 |
+
border-bottom:1px solid rgba(111,217,168,0.2)!important;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
[role="tab"]{
|
| 299 |
+
background:transparent!important;
|
| 300 |
+
color:var(--text-secondary)!important;
|
| 301 |
+
border:none!important;
|
| 302 |
+
padding:12px 20px!important;
|
| 303 |
+
transition:all 0.3s ease!important;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
[role="tab"][aria-selected="true"]{
|
| 307 |
+
color:var(--neon-green)!important;
|
| 308 |
+
background:rgba(111,217,168,0.1)!important;
|
| 309 |
+
border-bottom:3px solid var(--neon-green)!important;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
[role="tab"]:hover{
|
| 313 |
+
color:var(--neon-green)!important;
|
| 314 |
+
background:rgba(111,217,168,0.05)!important;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/* ============ 테이블 ============ */
|
| 318 |
+
table{border-collapse:separate;border-spacing:0;width:100%;}
|
| 319 |
+
th{background:#252540!important;color:var(--neon-green)!important;padding:14px!important;
|
| 320 |
+
border-bottom:2px solid var(--neon-green)!important;font-weight:600;text-align:left;}
|
| 321 |
+
td{background:#1a1a2e!important;padding:12px 14px!important;
|
| 322 |
+
border-bottom:1px solid rgba(255,255,255,0.05)!important;}
|
| 323 |
+
tr:hover td{background:#252540!important;}
|
| 324 |
+
|
| 325 |
+
/* ============ 히든 스크롤바 ============ */
|
| 326 |
+
::-webkit-scrollbar{width:8px;height:8px;}
|
| 327 |
+
::-webkit-scrollbar-track{background:#0d0d1a;}
|
| 328 |
+
::-webkit-scrollbar-thumb{background:var(--neon-green);border-radius:4px;}
|
| 329 |
+
::-webkit-scrollbar-thumb:hover{background:#8fe8c0;}
|
| 330 |
+
"""
|
| 331 |
+
|
| 332 |
+
# ============================================================================
|
| 333 |
+
# 데이터 상수
|
| 334 |
+
# ============================================================================
|
| 335 |
+
|
| 336 |
+
INDUSTRY_FINANCIAL_RATIOS = {
|
| 337 |
+
"A01 농업": {"code": "A01", "총자산증가율": 9.01, "매출액증가율": 12.31, "매출액영업이익률": 2.14, "유동비율": 104.53, "부채비율": 165.15, "제한부채비율": 500, "이자보상비율": 104.69},
|
| 338 |
+
"A03 어업": {"code": "A03", "총자산증가율": 45.04, "매출액증가율": 21.6, "매출액영업이익률": 8.38, "유동비율": 108.07, "부채비율": 102.09, "제한부채비율": 500, "이자보상비율": 499.98},
|
| 339 |
+
"B 광업": {"code": "B", "총자산증가율": -0.08, "매출액증가율": 13.75, "매출액영업이익률": 6.42, "유동비율": 55.56, "부채비율": -1096.54, "제한부채비율": 500, "이자보상비율": 38.78},
|
| 340 |
+
"C 제조업": {"code": "C", "총자산증가율": 7.54, "매출액증가율": 14.63, "매출액영업이익률": 5.72, "유동비율": 141.51, "부채비율": 76.95, "제한부채비율": 365.7, "이자보상비율": 693.43},
|
| 341 |
+
"D 전기가스": {"code": "D", "총자산증가율": 8.5, "매출액증가율": 15.2, "매출액영업이익률": 4.8, "유동비율": 95.0, "부채비율": 180.0, "제한부채비율": 400, "이자보상비율": 250.0},
|
| 342 |
+
"F 건설업": {"code": "F", "총자산증가율": 5.2, "매출액증가율": 8.5, "매출액영업이익률": 3.5, "유동비율": 125.0, "부채비율": 220.0, "제한부채비율": 450, "이자보상비율": 180.0},
|
| 343 |
+
"G 도소매업": {"code": "G", "총자산증가율": 6.8, "매출액증가율": 10.5, "매출액영업이익률": 2.8, "유동비율": 115.0, "부채비율": 145.0, "제한부채비율": 400, "이자보상비율": 220.0},
|
| 344 |
+
"H 운수창고업": {"code": "H", "총자산증가율": 4.5, "매출액증가율": 7.8, "매출액영업이익률": 4.2, "유동비율": 98.0, "부채비율": 195.0, "제한부채비율": 420, "이자보상비율": 165.0},
|
| 345 |
+
"I 숙박음식업": {"code": "I", "총자산증가율": 3.2, "매출액증가율": 5.5, "매출액영업이익률": 2.1, "유동비율": 85.0, "부채비율": 210.0, "제한부채비율": 450, "이자보상비율": 95.0},
|
| 346 |
+
"J 정보통신업": {"code": "J", "총자산증가율": 12.5, "매출액증가율": 18.2, "매출액영업이익률": 8.5, "유동비율": 165.0, "부채비율": 85.0, "제한부채비율": 350, "이자보상비율": 850.0},
|
| 347 |
+
"K 금융보험업": {"code": "K", "총자산증가율": 6.2, "매출액증가율": 8.9, "매출액영업이익률": 12.5, "유동비율": 120.0, "부채비율": 250.0, "제한부채비율": 500, "이자보상비율": 320.0},
|
| 348 |
+
"L 부동산업": {"code": "L", "총자산증가율": 8.8, "매출액증가율": 6.5, "매출액영업이익률": 15.2, "유동비율": 75.0, "부채비율": 185.0, "제한부채비율": 400, "이자보상비율": 280.0},
|
| 349 |
+
"M 전문과학기술": {"code": "M", "총자산증가율": 10.2, "매출액증가율": 15.8, "매출액영업이익률": 7.2, "유동비율": 155.0, "부채비율": 95.0, "제한부채비율": 380, "이자보상비율": 720.0},
|
| 350 |
+
"N 사업서비스": {"code": "N", "총자산증가율": 8.8, "매출액증가율": 12.5, "매출액영업이익률": 5.5, "유동비율": 135.0, "부채비율": 120.0, "제한부채비율": 400, "이자보상비율": 450.0},
|
| 351 |
+
"P 교육서비스": {"code": "P", "총자산증가율": 5.5, "매출액증가율": 6.8, "매출액영업이익률": 6.5, "유동비율": 142.0, "부채비율": 88.0, "제한부채비율": 380, "이자보상비율": 520.0},
|
| 352 |
+
"Q 보건복지": {"code": "Q", "총자산증가율": 9.2, "매출액증가율": 11.5, "매출액영업이익률": 4.8, "유동비율": 118.0, "부채비율": 135.0, "제한부채비율": 400, "이자보상비율": 385.0}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
CAUTION_ITEMS = {
|
| 356 |
+
"A": {"name": "지원금액 부족 (창업 1년 이내)", "severity": "조건부", "deduction": 30, "category": "자금"},
|
| 357 |
+
"B": {"name": "지원금액 부족 (창업 1~3년)", "severity": "조건부", "deduction": 70, "category": "자금"},
|
| 358 |
+
"C": {"name": "지원금액 부족 (창업 3년 초과)", "severity": "불가", "deduction": 100, "category": "자금"},
|
| 359 |
+
"D": {"name": "지원금액 부족 (보증기관 사용중)", "severity": "불가", "deduction": 100, "category": "자금"},
|
| 360 |
+
"E": {"name": "마지막 보증서 발행 10개월 미만", "severity": "불가", "deduction": 100, "category": "보증"},
|
| 361 |
+
"F": {"name": "마지막 보증서 발행 10개월~1년", "severity": "조건부", "deduction": 50, "category": "보증"},
|
| 362 |
+
"0": {"name": "정부지원 제한업종", "severity": "불가", "deduction": 100, "category": "업종"},
|
| 363 |
+
"1": {"name": "대표자 신용점수 640점 미만", "severity": "불가", "deduction": 100, "category": "신용"},
|
| 364 |
+
"2": {"name": "대표자 신용점수 640~700점", "severity": "조건부", "deduction": 50, "category": "신용"},
|
| 365 |
+
"3": {"name": "보증기관 채무 미변제", "severity": "불가", "deduction": 100, "category": "채무"},
|
| 366 |
+
"4": {"name": "대표자 경력/학력 부족", "severity": "불가", "deduction": 100, "category": "자격"},
|
| 367 |
+
"5": {"name": "대표자/최대주주 파산 이력", "severity": "불가", "deduction": 100, "category": "법적"},
|
| 368 |
+
"6": {"name": "회생/회생신청 이력", "severity": "불가", "deduction": 100, "category": "법적"},
|
| 369 |
+
"7": {"name": "관계기업 신용관리정보 등록", "severity": "불가", "deduction": 100, "category": "신용"},
|
| 370 |
+
"8": {"name": "소송 진행/범죄사실 연루", "severity": "불가", "deduction": 100, "category": "법적"},
|
| 371 |
+
"9": {"name": "대표자 형사처벌 이력", "severity": "불가", "deduction": 100, "category": "법적"},
|
| 372 |
+
"10": {"name": "부동산 권리침해 진행중", "severity": "불가", "deduction": 100, "category": "담보"},
|
| 373 |
+
"11": {"name": "권리침해 해제 후 10개월 미만", "severity": "불가", "deduction": 100, "category": "담보"},
|
| 374 |
+
"12": {"name": "권리침해 해제 후 10개월 이상", "severity": "조건부", "deduction": 50, "category": "담보"},
|
| 375 |
+
"13": {"name": "기업 신용정보관리 등록", "severity": "불가", "deduction": 100, "category": "신용"},
|
| 376 |
+
"14": {"name": "대표자 신용정보관리 등록", "severity": "불가", "deduction": 100, "category": "신용"},
|
| 377 |
+
"15": {"name": "국세 체납중", "severity": "조건부", "deduction": 60, "category": "체납"},
|
| 378 |
+
"16": {"name": "지방세 체납중", "severity": "조건부", "deduction": 60, "category": "체납"},
|
| 379 |
+
"17": {"name": "4대보험 체납중", "severity": "조건부", "deduction": 50, "category": "체납"},
|
| 380 |
+
"18": {"name": "관계기업 체납중", "severity": "조건부", "deduction": 50, "category": "체납"},
|
| 381 |
+
"19": {"name": "대표자 개인 국세 체납", "severity": "조건부", "deduction": 60, "category": "체납"},
|
| 382 |
+
"20": {"name": "대표자 개인 지방세 체납", "severity": "조건부", "deduction": 60, "category": "체납"},
|
| 383 |
+
"21": {"name": "3개월 이내 10일 이상 연체", "severity": "조건부", "deduction": 70, "category": "연체"},
|
| 384 |
+
"22": {"name": "1년 이내 보증사고 (기업)", "severity": "불가", "deduction": 100, "category": "보증"},
|
| 385 |
+
"23": {"name": "1년 이내 보증사고 (관계기업)", "severity": "불가", "deduction": 100, "category": "보증"},
|
| 386 |
+
"24": {"name": "관계기업 부동산 권리침해", "severity": "조건부", "deduction": 50, "category": "담보"},
|
| 387 |
+
"25": {"name": "자본잠식 상태", "severity": "조건부", "deduction": 70, "category": "재무"},
|
| 388 |
+
"26": {"name": "완전자본잠식", "severity": "불가", "deduction": 100, "category": "재무"},
|
| 389 |
+
"27": {"name": "부채비율 제한초과", "severity": "조건부", "deduction": 60, "category": "재무"},
|
| 390 |
+
"28": {"name": "2년 연속 당기순손실", "severity": "조건부", "deduction": 50, "category": "재무"},
|
| 391 |
+
"29": {"name": "3년 연속 당기순손실", "severity": "불가", "deduction": 100, "category": "재무"},
|
| 392 |
+
"30": {"name": "영업이익 적자", "severity": "조건부", "deduction": 40, "category": "재무"},
|
| 393 |
+
"36": {"name": "기존 보증사용금액 과다", "severity": "조건부", "deduction": 50, "category": "보증"},
|
| 394 |
+
"37": {"name": "매출액 감소 추세", "severity": "조건부", "deduction": 30, "category": "재무"},
|
| 395 |
+
"38": {"name": "대표자 변경 1년 이내", "severity": "조건부", "deduction": 40, "category": "경영"},
|
| 396 |
+
"39": {"name": "휴업/폐업 이력", "severity": "조건부", "deduction": 60, "category": "경영"},
|
| 397 |
+
"40": {"name": "사업장 임차계약 불안정", "severity": "조건부", "deduction": 30, "category": "경영"},
|
| 398 |
+
"45": {"name": "금액제한 사유 해당 (MAX 2억)", "severity": "조건부", "deduction": 30, "category": "자금"}
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
PROCESS_STEPS = [
|
| 402 |
+
{"step": 1, "name": "신규접수", "desc": "정보활용동의, 사업자번호, 대표자 확인", "duration": "즉시", "icon": "📝"},
|
| 403 |
+
{"step": 2, "name": "질의응답", "desc": "이메일, 설문작성 (69개 질문)", "duration": "10~30분", "icon": "💬"},
|
| 404 |
+
{"step": 3, "name": "API 수집", "desc": "17개 공공데이터 연계 조회", "duration": "자동", "icon": "🔄"},
|
| 405 |
+
{"step": 4, "name": "판독분석", "desc": "유의상태, 추천기관, 지원금액", "duration": "자동", "icon": "🔍"},
|
| 406 |
+
{"step": 5, "name": "리포트생성", "desc": "분석 결과 생성 완료", "duration": "즉시", "icon": "📊"},
|
| 407 |
+
{"step": 6, "name": "브리프", "desc": "부분 리포트 & 결재요청", "duration": "확인", "icon": "📋"},
|
| 408 |
+
{"step": 7, "name": "결재확인", "desc": "결재 완료 대기", "duration": "1~3일", "icon": "✅"},
|
| 409 |
+
{"step": 8, "name": "전체리포트", "desc": "전체 리포트 & 추가서비스", "duration": "즉시", "icon": "🏆"}
|
| 410 |
+
]
|
| 411 |
+
|
| 412 |
+
PROGRAMS = {
|
| 413 |
+
"신용보증기금": {"max": 30, "rate": "0.5~1.5%", "period": "5년", "color": "#6fd9a8"},
|
| 414 |
+
"기술보증기금": {"max": 30, "rate": "1.0~1.5%", "period": "5년", "color": "#ffd93d"},
|
| 415 |
+
"지역신보재단": {"max": 2, "rate": "0.5~1.0%", "period": "3년", "color": "#6495ed"},
|
| 416 |
+
"창업보증": {"max": 10, "rate": "0.8~1.2%", "period": "5년", "color": "#9b59b6"},
|
| 417 |
+
"혁신성장": {"max": 50, "rate": "0.5~1.0%", "period": "5년", "color": "#00d4ff"},
|
| 418 |
+
"수출기업": {"max": 30, "rate": "0.7~1.2%", "period": "5년", "color": "#ff9500"}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
# ============================================================================
|
| 422 |
+
# 🎯 차트 1: 3D 종합점수 게이지 (울트라 버전)
|
| 423 |
+
# ============================================================================
|
| 424 |
+
|
| 425 |
+
def generate_ultra_gauge(score: int, title: str = "종합점수") -> str:
|
| 426 |
+
"""3D 효과 종합점수 게이지"""
|
| 427 |
+
|
| 428 |
+
if score >= 80: color, status, emoji = "#6fd9a8", "EXCELLENT", "🏆"
|
| 429 |
+
elif score >= 60: color, status, emoji = "#7be8c0", "GOOD", "✅"
|
| 430 |
+
elif score >= 40: color, status, emoji = "#ffd93d", "FAIR", "⚠️"
|
| 431 |
+
else: color, status, emoji = "#ff6b6b", "RISK", "🚨"
|
| 432 |
+
|
| 433 |
+
# SVG 계산
|
| 434 |
+
radius = 80
|
| 435 |
+
circumference = 2 * 3.14159 * radius
|
| 436 |
+
offset = circumference - (score / 100) * circumference
|
| 437 |
+
|
| 438 |
+
html = f"""
|
| 439 |
+
<div class="chart-ultra" style="text-align:center;">
|
| 440 |
+
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:24px;">
|
| 441 |
+
<span style="font-size:28px;">{emoji}</span>
|
| 442 |
+
<h3 style="margin:0;color:var(--neon-green);font-family:'Orbitron',sans-serif;font-size:22px;
|
| 443 |
+
animation:glow-text 2s ease-in-out infinite;">{title}</h3>
|
| 444 |
+
</div>
|
| 445 |
+
|
| 446 |
+
<div style="position:relative;width:220px;height:220px;margin:0 auto;">
|
| 447 |
+
<!-- 배경 글로우 -->
|
| 448 |
+
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
|
| 449 |
+
width:180px;height:180px;border-radius:50%;
|
| 450 |
+
background:radial-gradient(circle,{color}15 0%,transparent 70%);
|
| 451 |
+
animation:pulse-ring 2s ease-out infinite;"></div>
|
| 452 |
+
|
| 453 |
+
<svg width="220" height="220" viewBox="0 0 220 220" style="transform:rotate(-90deg);">
|
| 454 |
+
<defs>
|
| 455 |
+
<linearGradient id="gaugeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
| 456 |
+
<stop offset="0%" style="stop-color:{color}"/>
|
| 457 |
+
<stop offset="50%" style="stop-color:#fff"/>
|
| 458 |
+
<stop offset="100%" style="stop-color:{color}"/>
|
| 459 |
+
</linearGradient>
|
| 460 |
+
<filter id="glow">
|
| 461 |
+
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
|
| 462 |
+
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
| 463 |
+
</filter>
|
| 464 |
+
<filter id="shadow">
|
| 465 |
+
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.5"/>
|
| 466 |
+
</filter>
|
| 467 |
+
</defs>
|
| 468 |
+
|
| 469 |
+
<!-- 외곽 링 (3D 효과) -->
|
| 470 |
+
<circle cx="110" cy="110" r="100" fill="none" stroke="#1a1a2e" stroke-width="20" filter="url(#shadow)"/>
|
| 471 |
+
<circle cx="110" cy="110" r="100" fill="none" stroke="#252540" stroke-width="18"/>
|
| 472 |
+
|
| 473 |
+
<!-- 배경 트랙 -->
|
| 474 |
+
<circle cx="110" cy="110" r="{radius}" fill="none" stroke="#2a2a45" stroke-width="14"/>
|
| 475 |
+
|
| 476 |
+
<!-- 진행 바 -->
|
| 477 |
+
<circle cx="110" cy="110" r="{radius}" fill="none"
|
| 478 |
+
stroke="url(#gaugeGrad)" stroke-width="14" stroke-linecap="round"
|
| 479 |
+
stroke-dasharray="{circumference}" stroke-dashoffset="{offset}"
|
| 480 |
+
filter="url(#glow)"
|
| 481 |
+
style="transition:stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1);"/>
|
| 482 |
+
|
| 483 |
+
<!-- 포인터 점 -->
|
| 484 |
+
<circle cx="110" cy="{110-radius}" r="8" fill="{color}" filter="url(#glow)">
|
| 485 |
+
<animateTransform attributeName="transform" type="rotate"
|
| 486 |
+
from="0 110 110" to="{score*3.6} 110 110" dur="1.5s" fill="freeze"/>
|
| 487 |
+
</circle>
|
| 488 |
+
</svg>
|
| 489 |
+
|
| 490 |
+
<!-- 중앙 값 -->
|
| 491 |
+
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;">
|
| 492 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:56px;font-weight:900;
|
| 493 |
+
color:{color};text-shadow:0 0 30px {color}80;
|
| 494 |
+
animation:count-up 1s ease-out;">{score}</div>
|
| 495 |
+
<div style="font-size:14px;color:var(--text-secondary);margin-top:-5px;">/ 100</div>
|
| 496 |
+
</div>
|
| 497 |
+
</div>
|
| 498 |
+
|
| 499 |
+
<!-- 상태 배지 -->
|
| 500 |
+
<div style="margin-top:24px;">
|
| 501 |
+
<span style="display:inline-block;padding:10px 30px;
|
| 502 |
+
background:linear-gradient(135deg,{color}20,{color}10);
|
| 503 |
+
border:2px solid {color};border-radius:30px;
|
| 504 |
+
color:{color};font-family:'Orbitron',sans-serif;font-weight:700;font-size:16px;
|
| 505 |
+
box-shadow:0 0 20px {color}40;letter-spacing:2px;">
|
| 506 |
+
{status}
|
| 507 |
+
</span>
|
| 508 |
+
</div>
|
| 509 |
+
|
| 510 |
+
<!-- 스케일 바 -->
|
| 511 |
+
<div style="display:flex;justify-content:center;gap:15px;margin-top:20px;">
|
| 512 |
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;">
|
| 513 |
+
<span style="width:10px;height:10px;background:#ff6b6b;border-radius:2px;"></span>
|
| 514 |
+
<span style="color:var(--text-secondary);">0-39</span>
|
| 515 |
+
</span>
|
| 516 |
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;">
|
| 517 |
+
<span style="width:10px;height:10px;background:#ffd93d;border-radius:2px;"></span>
|
| 518 |
+
<span style="color:var(--text-secondary);">40-59</span>
|
| 519 |
+
</span>
|
| 520 |
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;">
|
| 521 |
+
<span style="width:10px;height:10px;background:#7be8c0;border-radius:2px;"></span>
|
| 522 |
+
<span style="color:var(--text-secondary);">60-79</span>
|
| 523 |
+
</span>
|
| 524 |
+
<span style="display:flex;align-items:center;gap:5px;font-size:11px;">
|
| 525 |
+
<span style="width:10px;height:10px;background:#6fd9a8;border-radius:2px;"></span>
|
| 526 |
+
<span style="color:var(--text-secondary);">80+</span>
|
| 527 |
+
</span>
|
| 528 |
+
</div>
|
| 529 |
+
</div>
|
| 530 |
+
"""
|
| 531 |
+
return html
|
| 532 |
+
|
| 533 |
+
# ============================================================================
|
| 534 |
+
# 🕸️ 차트 2: 레이더 차트 (5축 SVG 애니메이션)
|
| 535 |
+
# ============================================================================
|
| 536 |
+
|
| 537 |
+
def generate_radar_chart(data: dict, title: str = "재무건전성 분석") -> str:
|
| 538 |
+
"""SVG 레이더 차트 with 애니메이션"""
|
| 539 |
+
|
| 540 |
+
metrics = [
|
| 541 |
+
("수익성", data.get("수익성", 60)),
|
| 542 |
+
("안정성", data.get("안정성", 70)),
|
| 543 |
+
("성장성", data.get("성장성", 55)),
|
| 544 |
+
("활동성", data.get("활동성", 65)),
|
| 545 |
+
("생산성", data.get("생산성", 50))
|
| 546 |
+
]
|
| 547 |
+
|
| 548 |
+
cx, cy = 150, 150
|
| 549 |
+
max_r = 100
|
| 550 |
+
n = len(metrics)
|
| 551 |
+
angles = [(i * 360 / n - 90) * math.pi / 180 for i in range(n)]
|
| 552 |
+
|
| 553 |
+
# 배경 그리드
|
| 554 |
+
grid_html = ""
|
| 555 |
+
for level in [20, 40, 60, 80, 100]:
|
| 556 |
+
points = " ".join([f"{cx + level/100*max_r*math.cos(a)},{cy + level/100*max_r*math.sin(a)}" for a in angles])
|
| 557 |
+
grid_html += f'<polygon points="{points}" fill="none" stroke="#2a2a45" stroke-width="1"/>'
|
| 558 |
+
|
| 559 |
+
# 축 선
|
| 560 |
+
axes_html = ""
|
| 561 |
+
for a in angles:
|
| 562 |
+
axes_html += f'<line x1="{cx}" y1="{cy}" x2="{cx + max_r*math.cos(a)}" y2="{cy + max_r*math.sin(a)}" stroke="#3a3a55" stroke-width="1"/>'
|
| 563 |
+
|
| 564 |
+
# 데이터 폴리곤
|
| 565 |
+
data_points = " ".join([f"{cx + metrics[i][1]/100*max_r*math.cos(angles[i])},{cy + metrics[i][1]/100*max_r*math.sin(angles[i])}" for i in range(n)])
|
| 566 |
+
|
| 567 |
+
# 라벨
|
| 568 |
+
labels_html = ""
|
| 569 |
+
for i, (name, val) in enumerate(metrics):
|
| 570 |
+
lx = cx + (max_r + 30) * math.cos(angles[i])
|
| 571 |
+
ly = cy + (max_r + 30) * math.sin(angles[i])
|
| 572 |
+
color = "#6fd9a8" if val >= 70 else "#ffd93d" if val >= 50 else "#ff6b6b"
|
| 573 |
+
labels_html += f'''
|
| 574 |
+
<text x="{lx}" y="{ly}" fill="{color}" font-size="13" font-weight="600"
|
| 575 |
+
text-anchor="middle" dominant-baseline="middle">{name}</text>
|
| 576 |
+
<text x="{lx}" y="{ly + 16}" fill="var(--text-secondary)" font-size="11"
|
| 577 |
+
text-anchor="middle">{val}점</text>
|
| 578 |
+
'''
|
| 579 |
+
|
| 580 |
+
avg = sum(v for _, v in metrics) // n
|
| 581 |
+
avg_color = "#6fd9a8" if avg >= 70 else "#ffd93d" if avg >= 50 else "#ff6b6b"
|
| 582 |
+
|
| 583 |
+
html = f"""
|
| 584 |
+
<div class="chart-ultra">
|
| 585 |
+
<h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
|
| 586 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">🕸️ {title}</h3>
|
| 587 |
+
|
| 588 |
+
<svg width="300" height="300" viewBox="0 0 300 300" style="display:block;margin:0 auto;">
|
| 589 |
+
<defs>
|
| 590 |
+
<linearGradient id="radarFill" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 591 |
+
<stop offset="0%" style="stop-color:#6fd9a8;stop-opacity:0.6"/>
|
| 592 |
+
<stop offset="100%" style="stop-color:#3a8f6a;stop-opacity:0.2"/>
|
| 593 |
+
</linearGradient>
|
| 594 |
+
<filter id="radarGlow">
|
| 595 |
+
<feGaussianBlur stdDeviation="3" result="blur"/>
|
| 596 |
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
| 597 |
+
</filter>
|
| 598 |
+
</defs>
|
| 599 |
+
|
| 600 |
+
{grid_html}
|
| 601 |
+
{axes_html}
|
| 602 |
+
|
| 603 |
+
<!-- 데이터 영역 -->
|
| 604 |
+
<polygon points="{data_points}" fill="url(#radarFill)" stroke="#6fd9a8"
|
| 605 |
+
stroke-width="2" filter="url(#radarGlow)"
|
| 606 |
+
style="animation:slide-up 0.8s ease-out;">
|
| 607 |
+
<animate attributeName="opacity" from="0" to="1" dur="1s"/>
|
| 608 |
+
</polygon>
|
| 609 |
+
|
| 610 |
+
<!-- 데이터 포인트 -->
|
| 611 |
+
{''.join([f'<circle cx="{cx + metrics[i][1]/100*max_r*math.cos(angles[i])}" cy="{cy + metrics[i][1]/100*max_r*math.sin(angles[i])}" r="6" fill="#6fd9a8" stroke="#fff" stroke-width="2"><animate attributeName="r" from="0" to="6" dur="0.5s" begin="{i*0.1}s"/></circle>' for i in range(n)])}
|
| 612 |
+
|
| 613 |
+
{labels_html}
|
| 614 |
+
</svg>
|
| 615 |
+
|
| 616 |
+
<!-- 평균 점수 -->
|
| 617 |
+
<div style="text-align:center;margin-top:15px;padding:15px;
|
| 618 |
+
background:linear-gradient(145deg,#252540,#1a1a2e);border-radius:12px;">
|
| 619 |
+
<span style="color:var(--text-secondary);font-size:14px;">종합 평균</span>
|
| 620 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;
|
| 621 |
+
color:{avg_color};margin-left:15px;">{avg}</span>
|
| 622 |
+
<span style="color:var(--text-secondary);font-size:14px;">점</span>
|
| 623 |
+
</div>
|
| 624 |
+
</div>
|
| 625 |
+
"""
|
| 626 |
+
return html
|
| 627 |
+
|
| 628 |
+
# ============================================================================
|
| 629 |
+
# 🍩 차트 3: 기관별 도넛 차트
|
| 630 |
+
# ============================================================================
|
| 631 |
+
|
| 632 |
+
def generate_donut_chart(sinbo: int, kibo: int, jaedan: int) -> str:
|
| 633 |
+
"""기관별 승인확률 도넛"""
|
| 634 |
+
|
| 635 |
+
total = max(1, sinbo + kibo + jaedan)
|
| 636 |
+
r = 65
|
| 637 |
+
circ = 2 * 3.14159 * r
|
| 638 |
+
|
| 639 |
+
sinbo_arc = circ * sinbo / total
|
| 640 |
+
kibo_arc = circ * kibo / total
|
| 641 |
+
jaedan_arc = circ * jaedan / total
|
| 642 |
+
|
| 643 |
+
def get_grade(s):
|
| 644 |
+
if s >= 70: return ("A", "#6fd9a8")
|
| 645 |
+
elif s >= 50: return ("B", "#ffd93d")
|
| 646 |
+
else: return ("C", "#ff6b6b")
|
| 647 |
+
|
| 648 |
+
html = f"""
|
| 649 |
+
<div class="chart-ultra">
|
| 650 |
+
<h3 style="color:var(--neon-green);margin-bottom:24px;text-align:center;
|
| 651 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">🎯 기관별 승인확률</h3>
|
| 652 |
+
|
| 653 |
+
<div style="display:flex;justify-content:center;align-items:center;gap:50px;flex-wrap:wrap;">
|
| 654 |
+
<!-- 도넛 차트 -->
|
| 655 |
+
<div style="position:relative;width:180px;height:180px;">
|
| 656 |
+
<svg width="180" height="180" viewBox="0 0 180 180" style="transform:rotate(-90deg);">
|
| 657 |
+
<defs>
|
| 658 |
+
<filter id="donutGlow">
|
| 659 |
+
<feGaussianBlur stdDeviation="3"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
| 660 |
+
</filter>
|
| 661 |
+
</defs>
|
| 662 |
+
<circle cx="90" cy="90" r="{r}" fill="none" stroke="#2a2a45" stroke-width="22"/>
|
| 663 |
+
<circle cx="90" cy="90" r="{r}" fill="none" stroke="#6fd9a8" stroke-width="22"
|
| 664 |
+
stroke-dasharray="{sinbo_arc} {circ}" stroke-dashoffset="0" filter="url(#donutGlow)">
|
| 665 |
+
<animate attributeName="stroke-dasharray" from="0 {circ}" to="{sinbo_arc} {circ}" dur="1s"/>
|
| 666 |
+
</circle>
|
| 667 |
+
<circle cx="90" cy="90" r="{r}" fill="none" stroke="#ffd93d" stroke-width="22"
|
| 668 |
+
stroke-dasharray="{kibo_arc} {circ}" stroke-dashoffset="{-sinbo_arc}" filter="url(#donutGlow)">
|
| 669 |
+
<animate attributeName="stroke-dasharray" from="0 {circ}" to="{kibo_arc} {circ}" dur="1s" begin="0.3s"/>
|
| 670 |
+
</circle>
|
| 671 |
+
<circle cx="90" cy="90" r="{r}" fill="none" stroke="#6495ed" stroke-width="22"
|
| 672 |
+
stroke-dasharray="{jaedan_arc} {circ}" stroke-dashoffset="{-(sinbo_arc+kibo_arc)}" filter="url(#donutGlow)">
|
| 673 |
+
<animate attributeName="stroke-dasharray" from="0 {circ}" to="{jaedan_arc} {circ}" dur="1s" begin="0.6s"/>
|
| 674 |
+
</circle>
|
| 675 |
+
</svg>
|
| 676 |
+
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;">
|
| 677 |
+
<div style="font-size:12px;color:var(--text-secondary);">평균</div>
|
| 678 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:32px;font-weight:700;color:#fff;">
|
| 679 |
+
{(sinbo+kibo+jaedan)//3}%
|
| 680 |
+
</div>
|
| 681 |
+
</div>
|
| 682 |
+
</div>
|
| 683 |
+
|
| 684 |
+
<!-- 범례 -->
|
| 685 |
+
<div style="display:flex;flex-direction:column;gap:16px;">
|
| 686 |
+
<div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
|
| 687 |
+
background:linear-gradient(145deg,rgba(111,217,168,0.1),transparent);
|
| 688 |
+
border-radius:12px;border-left:4px solid #6fd9a8;">
|
| 689 |
+
<div style="width:14px;height:14px;background:#6fd9a8;border-radius:50%;box-shadow:0 0 10px #6fd9a880;"></div>
|
| 690 |
+
<div>
|
| 691 |
+
<div style="font-size:12px;color:var(--text-secondary);">신용보증기금</div>
|
| 692 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#6fd9a8;">{sinbo}%</div>
|
| 693 |
+
</div>
|
| 694 |
+
<span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(sinbo)[1]}30;
|
| 695 |
+
border-radius:8px;color:{get_grade(sinbo)[1]};font-weight:700;">{get_grade(sinbo)[0]}</span>
|
| 696 |
+
</div>
|
| 697 |
+
|
| 698 |
+
<div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
|
| 699 |
+
background:linear-gradient(145deg,rgba(255,217,61,0.1),transparent);
|
| 700 |
+
border-radius:12px;border-left:4px solid #ffd93d;">
|
| 701 |
+
<div style="width:14px;height:14px;background:#ffd93d;border-radius:50%;box-shadow:0 0 10px #ffd93d80;"></div>
|
| 702 |
+
<div>
|
| 703 |
+
<div style="font-size:12px;color:var(--text-secondary);">기술보증기금</div>
|
| 704 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#ffd93d;">{kibo}%</div>
|
| 705 |
+
</div>
|
| 706 |
+
<span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(kibo)[1]}30;
|
| 707 |
+
border-radius:8px;color:{get_grade(kibo)[1]};font-weight:700;">{get_grade(kibo)[0]}</span>
|
| 708 |
+
</div>
|
| 709 |
+
|
| 710 |
+
<div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
|
| 711 |
+
background:linear-gradient(145deg,rgba(100,149,237,0.1),transparent);
|
| 712 |
+
border-radius:12px;border-left:4px solid #6495ed;">
|
| 713 |
+
<div style="width:14px;height:14px;background:#6495ed;border-radius:50%;box-shadow:0 0 10px #6495ed80;"></div>
|
| 714 |
+
<div>
|
| 715 |
+
<div style="font-size:12px;color:var(--text-secondary);">지역신보재단</div>
|
| 716 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#6495ed;">{jaedan}%</div>
|
| 717 |
+
</div>
|
| 718 |
+
<span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(jaedan)[1]}30;
|
| 719 |
+
border-radius:8px;color:{get_grade(jaedan)[1]};font-weight:700;">{get_grade(jaedan)[0]}</span>
|
| 720 |
+
</div>
|
| 721 |
+
</div>
|
| 722 |
+
</div>
|
| 723 |
+
</div>
|
| 724 |
+
"""
|
| 725 |
+
return html
|
| 726 |
+
|
| 727 |
+
# ============================================================================
|
| 728 |
+
# 💰 차트 4: 자금조달 파이프라인
|
| 729 |
+
# ============================================================================
|
| 730 |
+
|
| 731 |
+
def generate_pipeline(stage: int, amounts: dict) -> str:
|
| 732 |
+
"""자금조달 파이프라인 플로우"""
|
| 733 |
+
|
| 734 |
+
stages = [
|
| 735 |
+
("사전심사", "🔍", "#6495ed"),
|
| 736 |
+
("서류접수", "📄", "#9b59b6"),
|
| 737 |
+
("심사진행", "⚖️", "#ffd93d"),
|
| 738 |
+
("승인완료", "✅", "#6fd9a8")
|
| 739 |
+
]
|
| 740 |
+
|
| 741 |
+
html = f"""
|
| 742 |
+
<div class="chart-ultra">
|
| 743 |
+
<h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
|
| 744 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">💰 자금조달 파이프라인</h3>
|
| 745 |
+
|
| 746 |
+
<!-- 파이프라인 -->
|
| 747 |
+
<div style="position:relative;padding:30px 0;">
|
| 748 |
+
<div style="position:absolute;top:50%;left:8%;right:8%;height:6px;
|
| 749 |
+
background:linear-gradient(90deg,#2a2a45 0%,#6fd9a8 {stage*25}%,#2a2a45 {stage*25}%);
|
| 750 |
+
transform:translateY(-50%);border-radius:3px;"></div>
|
| 751 |
+
|
| 752 |
+
<div style="display:flex;justify-content:space-between;position:relative;">
|
| 753 |
+
"""
|
| 754 |
+
|
| 755 |
+
for i, (name, icon, color) in enumerate(stages):
|
| 756 |
+
done = i < stage
|
| 757 |
+
curr = i == stage
|
| 758 |
+
|
| 759 |
+
html += f"""
|
| 760 |
+
<div style="text-align:center;flex:1;opacity:{'1' if done or curr else '0.4'};
|
| 761 |
+
animation:{'float 2s ease-in-out infinite' if curr else 'none'};">
|
| 762 |
+
<div style="width:70px;height:70px;margin:0 auto;
|
| 763 |
+
background:{'linear-gradient(135deg,'+color+','+color+'80)' if done else '#252540' if curr else '#1a1a2e'};
|
| 764 |
+
border:3px solid {color if done or curr else '#3a3a55'};border-radius:50%;
|
| 765 |
+
display:flex;align-items:center;justify-content:center;font-size:28px;
|
| 766 |
+
box-shadow:{'0 0 30px '+color+'60' if curr else '0 0 15px '+color+'40' if done else 'none'};
|
| 767 |
+
transition:all 0.3s ease;">
|
| 768 |
+
{'✓' if done else icon}
|
| 769 |
+
</div>
|
| 770 |
+
<div style="margin-top:12px;font-weight:600;color:{'#fff' if done or curr else 'var(--text-secondary)'};
|
| 771 |
+
font-size:14px;">{name}</div>
|
| 772 |
+
</div>
|
| 773 |
+
"""
|
| 774 |
+
|
| 775 |
+
html += f"""
|
| 776 |
+
</div>
|
| 777 |
+
</div>
|
| 778 |
+
|
| 779 |
+
<!-- 금액 카드 -->
|
| 780 |
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:30px;">
|
| 781 |
+
<div class="score-card" style="border-top:4px solid #6495ed;">
|
| 782 |
+
<div class="label">신청금액</div>
|
| 783 |
+
<div class="value" style="font-size:28px;color:#6495ed;">{amounts.get('신청',5):.1f}<span style="font-size:16px;">억</span></div>
|
| 784 |
+
</div>
|
| 785 |
+
<div class="score-card highlight" style="border-top:4px solid #6fd9a8;">
|
| 786 |
+
<div class="label">예상승인</div>
|
| 787 |
+
<div class="value" style="font-size:28px;">{amounts.get('예상',3.5):.1f}<span style="font-size:16px;">억</span></div>
|
| 788 |
+
</div>
|
| 789 |
+
<div class="score-card" style="border-top:4px solid #ff6b6b;">
|
| 790 |
+
<div class="label">기존사용</div>
|
| 791 |
+
<div class="value" style="font-size:28px;color:#ff6b6b;">{amounts.get('기존',0.5):.1f}<span style="font-size:16px;">억</span></div>
|
| 792 |
+
</div>
|
| 793 |
+
</div>
|
| 794 |
+
</div>
|
| 795 |
+
"""
|
| 796 |
+
return html
|
| 797 |
+
|
| 798 |
+
# ============================================================================
|
| 799 |
+
# 🔥 차트 5: 리스크 히트맵
|
| 800 |
+
# ============================================================================
|
| 801 |
+
|
| 802 |
+
def generate_heatmap(caution_items: list) -> str:
|
| 803 |
+
"""리스크 히트맵"""
|
| 804 |
+
|
| 805 |
+
cats = {"신용": [0,0,[]], "재무": [0,0,[]], "체납": [0,0,[]], "법적": [0,0,[]],
|
| 806 |
+
"보증": [0,0,[]], "담보": [0,0,[]], "경영": [0,0,[]], "기타": [0,0,[]]}
|
| 807 |
+
|
| 808 |
+
for code in caution_items:
|
| 809 |
+
if code in CAUTION_ITEMS:
|
| 810 |
+
item = CAUTION_ITEMS[code]
|
| 811 |
+
cat = item.get("category", "기타")
|
| 812 |
+
if cat not in cats: cat = "기타"
|
| 813 |
+
cats[cat][0] += 1
|
| 814 |
+
if item["severity"] == "불가": cats[cat][1] += 1
|
| 815 |
+
cats[cat][2].append(item["name"])
|
| 816 |
+
|
| 817 |
+
def risk_level(cnt, sev):
|
| 818 |
+
if sev > 0: return (4, "#ff6b6b", "심각")
|
| 819 |
+
if cnt >= 3: return (3, "#ff9500", "높음")
|
| 820 |
+
if cnt >= 2: return (2, "#ffd93d", "보통")
|
| 821 |
+
if cnt >= 1: return (1, "#6fd9a8", "낮음")
|
| 822 |
+
return (0, "#2a2a45", "안전")
|
| 823 |
+
|
| 824 |
+
total = sum(c[0] for c in cats.values())
|
| 825 |
+
severe = sum(c[1] for c in cats.values())
|
| 826 |
+
|
| 827 |
+
html = f"""
|
| 828 |
+
<div class="chart-ultra">
|
| 829 |
+
<h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
|
| 830 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">🔥 리스크 히트맵</h3>
|
| 831 |
+
|
| 832 |
+
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;">
|
| 833 |
+
"""
|
| 834 |
+
|
| 835 |
+
for cat, (cnt, sev, items) in cats.items():
|
| 836 |
+
lv, color, status = risk_level(cnt, sev)
|
| 837 |
+
html += f"""
|
| 838 |
+
<div style="background:{color}{'30' if lv > 0 else ''};padding:20px;text-align:center;
|
| 839 |
+
border-radius:14px;border:2px solid {color if lv >= 3 else 'transparent'};
|
| 840 |
+
transition:all 0.3s ease;cursor:pointer;
|
| 841 |
+
{'animation:neon-pulse 1.5s ease-in-out infinite;' if lv >= 3 else ''}"
|
| 842 |
+
title="{', '.join(items[:3]) if items else '해당없음'}">
|
| 843 |
+
<div style="font-size:13px;color:{'#fff' if lv >= 2 else 'var(--text-secondary)'};font-weight:600;">{cat}</div>
|
| 844 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:32px;font-weight:700;
|
| 845 |
+
color:{'#fff' if lv >= 2 else color};margin:10px 0;">{cnt}</div>
|
| 846 |
+
<div style="font-size:11px;color:{'#fff' if lv >= 2 else '#808090'};">{('불가 ' + str(sev)) if sev > 0 else status}</div>
|
| 847 |
+
</div>
|
| 848 |
+
"""
|
| 849 |
+
|
| 850 |
+
html += f"""
|
| 851 |
+
</div>
|
| 852 |
+
|
| 853 |
+
<!-- 범례 -->
|
| 854 |
+
<div style="display:flex;justify-content:center;gap:20px;padding:15px;
|
| 855 |
+
background:#1a1a2e;border-radius:10px;margin-bottom:20px;">
|
| 856 |
+
<span style="display:flex;align-items:center;gap:6px;font-size:12px;">
|
| 857 |
+
<span style="width:14px;height:14px;background:#2a2a45;border-radius:3px;"></span>안전
|
| 858 |
+
</span>
|
| 859 |
+
<span style="display:flex;align-items:center;gap:6px;font-size:12px;">
|
| 860 |
+
<span style="width:14px;height:14px;background:#6fd9a8;border-radius:3px;"></span>낮음
|
| 861 |
+
</span>
|
| 862 |
+
<span style="display:flex;align-items:center;gap:6px;font-size:12px;">
|
| 863 |
+
<span style="width:14px;height:14px;background:#ffd93d;border-radius:3px;"></span>보통
|
| 864 |
+
</span>
|
| 865 |
+
<span style="display:flex;align-items:center;gap:6px;font-size:12px;">
|
| 866 |
+
<span style="width:14px;height:14px;background:#ff9500;border-radius:3px;"></span>높음
|
| 867 |
+
</span>
|
| 868 |
+
<span style="display:flex;align-items:center;gap:6px;font-size:12px;">
|
| 869 |
+
<span style="width:14px;height:14px;background:#ff6b6b;border-radius:3px;"></span>심각
|
| 870 |
+
</span>
|
| 871 |
+
</div>
|
| 872 |
+
|
| 873 |
+
<!-- 총계 -->
|
| 874 |
+
<div style="padding:18px;background:linear-gradient(145deg,#252540,#1a1a2e);border-radius:12px;
|
| 875 |
+
border-left:4px solid #ff6b6b;display:flex;justify-content:space-between;align-items:center;">
|
| 876 |
+
<span style="color:var(--text-secondary);">총 유의사항</span>
|
| 877 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;color:#ff6b6b;">
|
| 878 |
+
{total}<span style="font-size:14px;color:var(--text-secondary);margin-left:8px;">(불가 {severe})</span>
|
| 879 |
+
</span>
|
| 880 |
+
</div>
|
| 881 |
+
</div>
|
| 882 |
+
"""
|
| 883 |
+
return html
|
| 884 |
+
|
| 885 |
+
# ============================================================================
|
| 886 |
+
# 📊 차트 6: 경쟁력 벤치마크 바
|
| 887 |
+
# ============================================================================
|
| 888 |
+
|
| 889 |
+
def generate_benchmark_bars(company: dict, industry: dict) -> str:
|
| 890 |
+
"""업종 대비 벤치마크"""
|
| 891 |
+
|
| 892 |
+
metrics = [
|
| 893 |
+
("매출성장률", company.get("매출성장률", 15), industry.get("매출성장률", 10), "%", False),
|
| 894 |
+
("영업이익률", company.get("영업이익률", 8), industry.get("영업이익률", 5), "%", False),
|
| 895 |
+
("부채비율", company.get("부채비율", 120), industry.get("부채비율", 150), "%", True),
|
| 896 |
+
("신용등급", company.get("신용등급", 720), industry.get("신용등급", 680), "점", False),
|
| 897 |
+
("고용성장", company.get("고용성장", 20), industry.get("고용성장", 10), "%", False)
|
| 898 |
+
]
|
| 899 |
+
|
| 900 |
+
better_count = 0
|
| 901 |
+
|
| 902 |
+
html = f"""
|
| 903 |
+
<div class="chart-ultra">
|
| 904 |
+
<h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
|
| 905 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">📊 업종 대비 경쟁력</h3>
|
| 906 |
+
|
| 907 |
+
<div style="display:flex;flex-direction:column;gap:18px;">
|
| 908 |
+
"""
|
| 909 |
+
|
| 910 |
+
for name, comp_val, ind_val, unit, reverse in metrics:
|
| 911 |
+
is_better = (comp_val < ind_val) if reverse else (comp_val > ind_val)
|
| 912 |
+
if is_better: better_count += 1
|
| 913 |
+
|
| 914 |
+
diff = ind_val - comp_val if reverse else comp_val - ind_val
|
| 915 |
+
diff_pct = (diff / ind_val * 100) if ind_val != 0 else 0
|
| 916 |
+
|
| 917 |
+
max_val = max(comp_val, ind_val) * 1.3
|
| 918 |
+
comp_w = comp_val / max_val * 100 if max_val > 0 else 0
|
| 919 |
+
ind_w = ind_val / max_val * 100 if max_val > 0 else 0
|
| 920 |
+
|
| 921 |
+
color = "#6fd9a8" if is_better else "#ff6b6b"
|
| 922 |
+
|
| 923 |
+
html += f"""
|
| 924 |
+
<div style="background:#1a1a2e;padding:18px;border-radius:14px;border-left:4px solid {color};">
|
| 925 |
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
| 926 |
+
<span style="font-weight:600;color:#fff;">{name}</span>
|
| 927 |
+
<span style="font-size:13px;color:{color};">
|
| 928 |
+
{'+' if diff > 0 else ''}{diff:.1f}{unit} ({'+' if diff_pct > 0 else ''}{diff_pct:.0f}%)
|
| 929 |
+
{'👍' if is_better else '👎'}
|
| 930 |
+
</span>
|
| 931 |
+
</div>
|
| 932 |
+
|
| 933 |
+
<div style="margin-bottom:8px;">
|
| 934 |
+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px;">
|
| 935 |
+
<span style="width:50px;font-size:12px;color:var(--text-secondary);">귀사</span>
|
| 936 |
+
<div style="flex:1;height:14px;background:#252540;border-radius:7px;overflow:hidden;">
|
| 937 |
+
<div class="progress-ultra"><div class="fill" style="width:{comp_w}%;background:linear-gradient(90deg,{color},{color}80);"></div></div>
|
| 938 |
+
</div>
|
| 939 |
+
<span style="width:70px;text-align:right;font-family:'Orbitron',sans-serif;font-weight:600;color:{color};">{comp_val:.1f}{unit}</span>
|
| 940 |
+
</div>
|
| 941 |
+
</div>
|
| 942 |
+
|
| 943 |
+
<div>
|
| 944 |
+
<div style="display:flex;align-items:center;gap:12px;">
|
| 945 |
+
<span style="width:50px;font-size:12px;color:#606080;">업종</span>
|
| 946 |
+
<div style="flex:1;height:10px;background:#252540;border-radius:5px;overflow:hidden;">
|
| 947 |
+
<div style="width:{ind_w}%;height:100%;background:#505070;border-radius:5px;"></div>
|
| 948 |
+
</div>
|
| 949 |
+
<span style="width:70px;text-align:right;font-size:13px;color:#606080;">{ind_val:.1f}{unit}</span>
|
| 950 |
+
</div>
|
| 951 |
+
</div>
|
| 952 |
+
</div>
|
| 953 |
+
"""
|
| 954 |
+
|
| 955 |
+
overall = better_count / len(metrics) * 100
|
| 956 |
+
|
| 957 |
+
html += f"""
|
| 958 |
+
</div>
|
| 959 |
+
|
| 960 |
+
<!-- 종합 -->
|
| 961 |
+
<div style="margin-top:25px;padding:22px;background:linear-gradient(145deg,#1a2a30,#0d1a1f);
|
| 962 |
+
border-radius:14px;text-align:center;border:2px solid #6fd9a830;">
|
| 963 |
+
<div style="font-size:14px;color:var(--text-secondary);margin-bottom:8px;">업종 대비 경쟁력 지수</div>
|
| 964 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:48px;font-weight:900;
|
| 965 |
+
color:{'#6fd9a8' if overall >= 60 else '#ffd93d' if overall >= 40 else '#ff6b6b'};
|
| 966 |
+
text-shadow:0 0 30px {'#6fd9a8' if overall >= 60 else '#ffd93d' if overall >= 40 else '#ff6b6b'}60;">
|
| 967 |
+
{better_count}/{len(metrics)}
|
| 968 |
+
</div>
|
| 969 |
+
<div style="font-size:13px;color:var(--text-secondary);margin-top:5px;">지표 우위</div>
|
| 970 |
+
</div>
|
| 971 |
+
</div>
|
| 972 |
+
"""
|
| 973 |
+
return html
|
| 974 |
+
|
| 975 |
+
# ============================================================================
|
| 976 |
+
# 📋 차트 7: KPI 스코어카드 대시보드
|
| 977 |
+
# ============================================================================
|
| 978 |
+
|
| 979 |
+
def generate_scorecard_dashboard(data: dict) -> str:
|
| 980 |
+
"""KPI 스코어카드"""
|
| 981 |
+
|
| 982 |
+
kpis = [
|
| 983 |
+
("💰", "예상지원액", f"{data.get('예상금액', 2.5):.1f}억", "#6fd9a8", True),
|
| 984 |
+
("📈", "종합점수", f"{data.get('종합점수', 75)}점", "#ffd93d" if data.get('종합점수', 75) < 70 else "#6fd9a8", data.get('종합점수', 75) >= 70),
|
| 985 |
+
("🏦", "추천기관", data.get('추천기관', '신보'), "#6495ed", True),
|
| 986 |
+
("⚠️", "리스크", f"{data.get('리스크', 3)}건", "#ff6b6b" if data.get('리스크', 3) > 5 else "#ffd93d", data.get('리스크', 3) <= 3),
|
| 987 |
+
("📊", "신용등급", f"{data.get('신용등급', 720)}점", "#6fd9a8" if data.get('신용등급', 720) >= 700 else "#ffd93d", True),
|
| 988 |
+
("🎯", "승인확률", f"{data.get('승인확률', 72)}%", "#6fd9a8" if data.get('승인확률', 72) >= 70 else "#ffd93d", True)
|
| 989 |
+
]
|
| 990 |
+
|
| 991 |
+
html = f"""
|
| 992 |
+
<div class="chart-ultra">
|
| 993 |
+
<h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
|
| 994 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">📋 핵심 KPI 대시보드</h3>
|
| 995 |
+
|
| 996 |
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:18px;">
|
| 997 |
+
"""
|
| 998 |
+
|
| 999 |
+
for i, (icon, label, value, color, is_good) in enumerate(kpis):
|
| 1000 |
+
html += f"""
|
| 1001 |
+
<div class="score-card {'highlight' if i == 0 else ''}" style="animation:slide-up 0.5s ease-out {i*0.1}s both;">
|
| 1002 |
+
<div style="font-size:32px;margin-bottom:10px;">{icon}</div>
|
| 1003 |
+
<div class="value" style="color:{color};">{value}</div>
|
| 1004 |
+
<div class="label">{label}</div>
|
| 1005 |
+
<div style="margin-top:10px;">
|
| 1006 |
+
<span style="display:inline-block;padding:4px 12px;background:{color}20;
|
| 1007 |
+
border-radius:12px;font-size:11px;color:{color};">
|
| 1008 |
+
{'✓ 양호' if is_good else '⚠ 주의'}
|
| 1009 |
+
</span>
|
| 1010 |
+
</div>
|
| 1011 |
+
</div>
|
| 1012 |
+
"""
|
| 1013 |
+
|
| 1014 |
+
html += """
|
| 1015 |
+
</div>
|
| 1016 |
+
</div>
|
| 1017 |
+
"""
|
| 1018 |
+
return html
|
| 1019 |
+
|
| 1020 |
+
# ============================================================================
|
| 1021 |
+
# 🌊 차트 8: 워터폴 차트
|
| 1022 |
+
# ============================================================================
|
| 1023 |
+
|
| 1024 |
+
def generate_waterfall(calc: dict) -> str:
|
| 1025 |
+
"""워터폴 차트"""
|
| 1026 |
+
|
| 1027 |
+
steps = [
|
| 1028 |
+
("기준매출", calc.get("기준매출", 5e8), "base", "💰"),
|
| 1029 |
+
("관계기업", -calc.get("관계차감", 5e7), "neg", "➖"),
|
| 1030 |
+
("조정매출", calc.get("조정매출", 4.5e8), "sub", "📊"),
|
| 1031 |
+
("회전율", calc.get("회전적용", -1.5e8), "neg", "🔄"),
|
| 1032 |
+
("1차산출", calc.get("1차산출", 3e8), "sub", "📋"),
|
| 1033 |
+
("기존사용", -calc.get("기존사용", 5e7), "neg", "🏦"),
|
| 1034 |
+
("최종금액", calc.get("최종금액", 2.5e8), "total", "🎯")
|
| 1035 |
+
]
|
| 1036 |
+
|
| 1037 |
+
max_amt = max(abs(s[1]) for s in steps if s[1] != 0) * 1.2
|
| 1038 |
+
|
| 1039 |
+
html = f"""
|
| 1040 |
+
<div class="chart-ultra">
|
| 1041 |
+
<h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
|
| 1042 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">💰 금액 산출 워터폴</h3>
|
| 1043 |
+
|
| 1044 |
+
<div style="display:flex;flex-direction:column;gap:14px;">
|
| 1045 |
+
"""
|
| 1046 |
+
|
| 1047 |
+
for name, amt, stype, icon in steps:
|
| 1048 |
+
if amt == 0 and stype == "neg": continue
|
| 1049 |
+
|
| 1050 |
+
w = abs(amt) / max_amt * 100 if max_amt > 0 else 0
|
| 1051 |
+
|
| 1052 |
+
if stype == "neg":
|
| 1053 |
+
color, bg = "#ff6b6b", "rgba(255,107,107,0.1)"
|
| 1054 |
+
val = f"-{abs(amt)/1e8:.1f}억"
|
| 1055 |
+
elif stype == "total":
|
| 1056 |
+
color, bg = "#6fd9a8", "rgba(111,217,168,0.15)"
|
| 1057 |
+
val = f"{amt/1e8:.1f}억"
|
| 1058 |
+
elif stype == "sub":
|
| 1059 |
+
color, bg = "#6495ed", "rgba(100,149,237,0.1)"
|
| 1060 |
+
val = f"{amt/1e8:.1f}억"
|
| 1061 |
+
else:
|
| 1062 |
+
color, bg = "#808090", "rgba(128,128,144,0.1)"
|
| 1063 |
+
val = f"{amt/1e8:.1f}억"
|
| 1064 |
+
|
| 1065 |
+
html += f"""
|
| 1066 |
+
<div style="background:{bg};padding:18px;border-radius:12px;border-left:4px solid {color};
|
| 1067 |
+
animation:slide-up 0.4s ease-out;">
|
| 1068 |
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
| 1069 |
+
<span style="font-weight:{'700' if stype in ['total','sub'] else '500'};color:#fff;">
|
| 1070 |
+
{icon} {name}
|
| 1071 |
+
</span>
|
| 1072 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:22px;font-weight:700;color:{color};">{val}</span>
|
| 1073 |
+
</div>
|
| 1074 |
+
<div class="progress-ultra">
|
| 1075 |
+
<div class="fill" style="width:{w}%;background:{color};"></div>
|
| 1076 |
+
</div>
|
| 1077 |
+
</div>
|
| 1078 |
+
"""
|
| 1079 |
+
|
| 1080 |
+
html += """
|
| 1081 |
+
</div>
|
| 1082 |
+
</div>
|
| 1083 |
+
"""
|
| 1084 |
+
return html
|
| 1085 |
+
|
| 1086 |
+
# ============================================================================
|
| 1087 |
+
# ⏱️ 차트 9: 프로세스 타임라인
|
| 1088 |
+
# ============================================================================
|
| 1089 |
+
|
| 1090 |
+
def generate_timeline(current: int) -> str:
|
| 1091 |
+
"""프로세스 타임라인"""
|
| 1092 |
+
|
| 1093 |
+
html = f"""
|
| 1094 |
+
<div class="chart-ultra">
|
| 1095 |
+
<h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
|
| 1096 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">⏱️ 진행 현황</h3>
|
| 1097 |
+
|
| 1098 |
+
<!-- 진행률 바 -->
|
| 1099 |
+
<div style="margin-bottom:30px;">
|
| 1100 |
+
<div style="display:flex;justify-content:space-between;margin-bottom:10px;">
|
| 1101 |
+
<span style="color:var(--text-secondary);">전체 진행률</span>
|
| 1102 |
+
<span style="color:var(--neon-green);font-family:'Orbitron',sans-serif;font-weight:600;">
|
| 1103 |
+
{current}/8 ({current*12.5:.0f}%)
|
| 1104 |
+
</span>
|
| 1105 |
+
</div>
|
| 1106 |
+
<div class="progress-ultra" style="height:16px;">
|
| 1107 |
+
<div class="fill" style="width:{current*12.5}%;"></div>
|
| 1108 |
+
</div>
|
| 1109 |
+
</div>
|
| 1110 |
+
|
| 1111 |
+
<!-- 단계 그리드 -->
|
| 1112 |
+
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;">
|
| 1113 |
+
"""
|
| 1114 |
+
|
| 1115 |
+
for i, step in enumerate(PROCESS_STEPS):
|
| 1116 |
+
done = i < current
|
| 1117 |
+
curr = i == current
|
| 1118 |
+
|
| 1119 |
+
if done:
|
| 1120 |
+
border, bg, icon_bg, icon = "var(--neon-green)", "rgba(111,217,168,0.1)", "var(--neon-green)", "✓"
|
| 1121 |
+
elif curr:
|
| 1122 |
+
border, bg, icon_bg, icon = "#ffd93d", "rgba(255,217,61,0.1)", "#ffd93d", step['icon']
|
| 1123 |
+
else:
|
| 1124 |
+
border, bg, icon_bg, icon = "#3a3a55", "transparent", "#2a2a45", step['icon']
|
| 1125 |
+
|
| 1126 |
+
html += f"""
|
| 1127 |
+
<div style="background:{bg};border:2px solid {border};border-radius:14px;
|
| 1128 |
+
padding:18px;text-align:center;transition:all 0.3s ease;
|
| 1129 |
+
{'animation:neon-pulse 1.5s ease-in-out infinite;' if curr else ''}">
|
| 1130 |
+
<div style="width:50px;height:50px;margin:0 auto 12px;
|
| 1131 |
+
background:{icon_bg};border-radius:50%;
|
| 1132 |
+
display:flex;align-items:center;justify-content:center;
|
| 1133 |
+
font-size:24px;color:{'#0d0d1a' if done else '#fff'};
|
| 1134 |
+
box-shadow:{'0 0 20px '+border+'60' if curr else 'none'};">
|
| 1135 |
+
{icon}
|
| 1136 |
+
</div>
|
| 1137 |
+
<div style="font-size:13px;font-weight:600;color:{'var(--neon-green)' if done else '#ffd93d' if curr else 'var(--text-secondary)'};">
|
| 1138 |
+
{step['name']}
|
| 1139 |
+
</div>
|
| 1140 |
+
<div style="font-size:11px;color:var(--text-secondary);margin-top:4px;">{step['duration']}</div>
|
| 1141 |
+
</div>
|
| 1142 |
+
"""
|
| 1143 |
+
|
| 1144 |
+
html += """
|
| 1145 |
+
</div>
|
| 1146 |
+
</div>
|
| 1147 |
+
"""
|
| 1148 |
+
return html
|
| 1149 |
+
|
| 1150 |
+
# ============================================================================
|
| 1151 |
+
# 💳 차트 10: 신용점수 시뮬레이션
|
| 1152 |
+
# ============================================================================
|
| 1153 |
+
|
| 1154 |
+
def generate_credit_sim(current: int, improvements: dict) -> str:
|
| 1155 |
+
"""신용점수 시뮬레이션"""
|
| 1156 |
+
|
| 1157 |
+
improve_list = {
|
| 1158 |
+
"제2금융권 대출 상환": 30,
|
| 1159 |
+
"카드론 상환": 25,
|
| 1160 |
+
"연체 해소": 40,
|
| 1161 |
+
"카드 사용률 30% 이하": 15,
|
| 1162 |
+
"통신비 자동이체": 5,
|
| 1163 |
+
"보험료 정상납부": 5
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
total_up = sum(improve_list[k] for k in improvements if improvements.get(k))
|
| 1167 |
+
projected = min(900, current + total_up)
|
| 1168 |
+
|
| 1169 |
+
cur_color = "#6fd9a8" if current >= 750 else "#ffd93d" if current >= 700 else "#ff6b6b"
|
| 1170 |
+
proj_color = "#6fd9a8" if projected >= 750 else "#ffd93d" if projected >= 700 else "#ff6b6b"
|
| 1171 |
+
|
| 1172 |
+
html = f"""
|
| 1173 |
+
<div class="chart-ultra">
|
| 1174 |
+
<h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
|
| 1175 |
+
font-family:'Rajdhani',sans-serif;font-size:20px;">💳 신용점수 시뮬레이션</h3>
|
| 1176 |
+
|
| 1177 |
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:30px;">
|
| 1178 |
+
<!-- 점수 비교 -->
|
| 1179 |
+
<div>
|
| 1180 |
+
<div style="display:flex;gap:20px;margin-bottom:25px;">
|
| 1181 |
+
<div class="score-card" style="flex:1;">
|
| 1182 |
+
<div class="label">현재</div>
|
| 1183 |
+
<div class="value" style="color:{cur_color};">{current}</div>
|
| 1184 |
+
</div>
|
| 1185 |
+
<div style="display:flex;align-items:center;font-size:28px;color:var(--neon-green);">→</div>
|
| 1186 |
+
<div class="score-card highlight" style="flex:1;">
|
| 1187 |
+
<div class="label">예상</div>
|
| 1188 |
+
<div class="value">{projected}</div>
|
| 1189 |
+
<div style="color:var(--neon-green);font-size:14px;margin-top:5px;">+{total_up}점</div>
|
| 1190 |
+
</div>
|
| 1191 |
+
</div>
|
| 1192 |
+
|
| 1193 |
+
<!-- 개선 방법 -->
|
| 1194 |
+
<div style="background:#1a1a2e;padding:20px;border-radius:14px;">
|
| 1195 |
+
<h4 style="color:#ffd93d;margin:0 0 15px;">🔧 개선 방법</h4>
|
| 1196 |
+
<div style="display:flex;flex-direction:column;gap:10px;">
|
| 1197 |
+
"""
|
| 1198 |
+
|
| 1199 |
+
for item, pts in improve_list.items():
|
| 1200 |
+
chk = improvements.get(item, False)
|
| 1201 |
+
html += f"""
|
| 1202 |
+
<div style="display:flex;align-items:center;gap:12px;padding:12px;
|
| 1203 |
+
background:{'rgba(111,217,168,0.1)' if chk else '#252540'};
|
| 1204 |
+
border-radius:10px;border:1px solid {'var(--neon-green)' if chk else '#3a3a55'};">
|
| 1205 |
+
<span style="color:{'var(--neon-green)' if chk else 'var(--text-secondary)'};font-size:18px;">
|
| 1206 |
+
{'✓' if chk else '○'}
|
| 1207 |
+
</span>
|
| 1208 |
+
<span style="flex:1;color:#fff;font-size:13px;">{item}</span>
|
| 1209 |
+
<span style="color:var(--neon-green);font-size:12px;font-weight:600;">+{pts}점</span>
|
| 1210 |
+
</div>
|
| 1211 |
+
"""
|
| 1212 |
+
|
| 1213 |
+
html += """
|
| 1214 |
+
</div>
|
| 1215 |
+
</div>
|
| 1216 |
+
</div>
|
| 1217 |
+
|
| 1218 |
+
<!-- 등급 가이드 -->
|
| 1219 |
+
<div style="background:#1a1a2e;padding:20px;border-radius:14px;">
|
| 1220 |
+
<h4 style="color:#fff;margin:0 0 15px;">📊 점수별 영향</h4>
|
| 1221 |
+
<div style="display:flex;flex-direction:column;gap:10px;">
|
| 1222 |
+
"""
|
| 1223 |
+
|
| 1224 |
+
ranges = [(750, 900, "우수", "#6fd9a8", "제한없음"), (700, 749, "양호", "#7be8c0", "일부제한"),
|
| 1225 |
+
(650, 699, "보통", "#ffd93d", "조건부"), (600, 649, "주의", "#ff9500", "제한적"), (0, 599, "위험", "#ff6b6b", "불가")]
|
| 1226 |
+
|
| 1227 |
+
for lo, hi, label, color, limit in ranges:
|
| 1228 |
+
is_cur = lo <= current <= hi
|
| 1229 |
+
html += f"""
|
| 1230 |
+
<div style="display:flex;align-items:center;gap:12px;padding:12px;
|
| 1231 |
+
background:{'rgba(111,217,168,0.1)' if is_cur else 'transparent'};
|
| 1232 |
+
border-radius:8px;border:1px solid {'var(--neon-green)' if is_cur else 'transparent'};">
|
| 1233 |
+
<div style="width:14px;height:14px;background:{color};border-radius:50%;"></div>
|
| 1234 |
+
<span style="width:70px;color:#fff;">{lo}-{hi}</span>
|
| 1235 |
+
<span style="width:50px;color:{color};font-weight:600;">{label}</span>
|
| 1236 |
+
<span style="color:var(--text-secondary);font-size:12px;">{limit}</span>
|
| 1237 |
+
</div>
|
| 1238 |
+
"""
|
| 1239 |
+
|
| 1240 |
+
html += """
|
| 1241 |
+
</div>
|
| 1242 |
+
</div>
|
| 1243 |
+
</div>
|
| 1244 |
+
</div>
|
| 1245 |
+
"""
|
| 1246 |
+
return html
|
| 1247 |
+
|
| 1248 |
+
# ============================================================================
|
| 1249 |
+
# 🎯 차트 11: 종합 인포그래픽
|
| 1250 |
+
# ============================================================================
|
| 1251 |
+
|
| 1252 |
+
def generate_infographic(data: dict) -> str:
|
| 1253 |
+
"""종합 인포그래픽 보드"""
|
| 1254 |
+
|
| 1255 |
+
score = data.get("종합점수", 75)
|
| 1256 |
+
amount = data.get("예상금액", 2.5)
|
| 1257 |
+
sinbo = data.get("신보", 70)
|
| 1258 |
+
kibo = data.get("기보", 65)
|
| 1259 |
+
jaedan = data.get("재단", 80)
|
| 1260 |
+
cautions = data.get("유의사항", 3)
|
| 1261 |
+
credit = data.get("신용점수", 720)
|
| 1262 |
+
|
| 1263 |
+
score_color = "#6fd9a8" if score >= 70 else "#ffd93d" if score >= 50 else "#ff6b6b"
|
| 1264 |
+
|
| 1265 |
+
html = f"""
|
| 1266 |
+
<div class="chart-ultra" style="background:linear-gradient(135deg,#0d1117 0%,#161b22 50%,#0d1117 100%);">
|
| 1267 |
+
|
| 1268 |
+
<!-- 헤더 -->
|
| 1269 |
+
<div style="text-align:center;margin-bottom:30px;padding:25px;
|
| 1270 |
+
background:linear-gradient(145deg,#1a2a30,#0d1a1f);border-radius:16px;
|
| 1271 |
+
border:2px solid var(--neon-green)30;">
|
| 1272 |
+
<h2 style="margin:0 0 10px;font-family:'Orbitron',sans-serif;font-size:28px;
|
| 1273 |
+
background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
|
| 1274 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
| 1275 |
+
animation:glow-text 2s ease-in-out infinite;">
|
| 1276 |
+
🎮 미네랄핵 분석 완료
|
| 1277 |
+
</h2>
|
| 1278 |
+
<p style="color:var(--text-secondary);margin:0;">정책자금 사전심사 종합 리포트</p>
|
| 1279 |
+
</div>
|
| 1280 |
+
|
| 1281 |
+
<!-- 메인 KPI -->
|
| 1282 |
+
<div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:20px;margin-bottom:25px;">
|
| 1283 |
+
|
| 1284 |
+
<!-- 예상 지원금액 (대형) -->
|
| 1285 |
+
<div class="score-card highlight" style="padding:30px;">
|
| 1286 |
+
<div style="font-size:18px;color:var(--text-secondary);margin-bottom:10px;">💰 예상 지원금액</div>
|
| 1287 |
+
<div style="font-family:'Orbitron',sans-serif;font-size:64px;font-weight:900;
|
| 1288 |
+
background:linear-gradient(135deg,#6fd9a8,#fff);
|
| 1289 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;">
|
| 1290 |
+
{amount:.1f}<span style="font-size:32px;">억원</span>
|
| 1291 |
+
</div>
|
| 1292 |
+
</div>
|
| 1293 |
+
|
| 1294 |
+
<!-- 종합점수 -->
|
| 1295 |
+
<div class="score-card" style="border-top:4px solid {score_color};">
|
| 1296 |
+
<div class="label">📊 종합점수</div>
|
| 1297 |
+
<div class="value" style="color:{score_color};">{score}</div>
|
| 1298 |
+
<div style="margin-top:10px;font-size:13px;color:{score_color};">
|
| 1299 |
+
{'우수' if score >= 70 else '양호' if score >= 50 else '주의'}
|
| 1300 |
+
</div>
|
| 1301 |
+
</div>
|
| 1302 |
+
|
| 1303 |
+
<!-- 유의사항 -->
|
| 1304 |
+
<div class="score-card" style="border-top:4px solid {'#ff6b6b' if cautions > 5 else '#ffd93d' if cautions > 0 else '#6fd9a8'};">
|
| 1305 |
+
<div class="label">⚠️ 유의사항</div>
|
| 1306 |
+
<div class="value" style="color:{'#ff6b6b' if cautions > 5 else '#ffd93d'};">{cautions}</div>
|
| 1307 |
+
<div style="margin-top:10px;font-size:13px;color:var(--text-secondary);">건 해결 필요</div>
|
| 1308 |
+
</div>
|
| 1309 |
+
</div>
|
| 1310 |
+
|
| 1311 |
+
<!-- 기관별 확률 바 -->
|
| 1312 |
+
<div style="background:#1a1a2e;padding:25px;border-radius:16px;margin-bottom:25px;">
|
| 1313 |
+
<h4 style="color:var(--neon-green);margin:0 0 20px;font-size:16px;">🏦 기관별 승인확률</h4>
|
| 1314 |
+
|
| 1315 |
+
<div style="display:flex;flex-direction:column;gap:15px;">
|
| 1316 |
+
<div style="display:flex;align-items:center;gap:15px;">
|
| 1317 |
+
<span style="width:100px;color:var(--text-secondary);">신용보증기금</span>
|
| 1318 |
+
<div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
|
| 1319 |
+
<div style="width:{sinbo}%;height:100%;background:linear-gradient(90deg,#6fd9a8,#8fe8c0);
|
| 1320 |
+
border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
|
| 1321 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{sinbo}%</span>
|
| 1322 |
+
</div>
|
| 1323 |
+
</div>
|
| 1324 |
+
</div>
|
| 1325 |
+
<div style="display:flex;align-items:center;gap:15px;">
|
| 1326 |
+
<span style="width:100px;color:var(--text-secondary);">기술보증기금</span>
|
| 1327 |
+
<div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
|
| 1328 |
+
<div style="width:{kibo}%;height:100%;background:linear-gradient(90deg,#ffd93d,#ffe880);
|
| 1329 |
+
border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
|
| 1330 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{kibo}%</span>
|
| 1331 |
+
</div>
|
| 1332 |
+
</div>
|
| 1333 |
+
</div>
|
| 1334 |
+
<div style="display:flex;align-items:center;gap:15px;">
|
| 1335 |
+
<span style="width:100px;color:var(--text-secondary);">지역신보재단</span>
|
| 1336 |
+
<div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
|
| 1337 |
+
<div style="width:{jaedan}%;height:100%;background:linear-gradient(90deg,#6495ed,#8ab4f8);
|
| 1338 |
+
border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
|
| 1339 |
+
<span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{jaedan}%</span>
|
| 1340 |
+
</div>
|
| 1341 |
+
</div>
|
| 1342 |
+
</div>
|
| 1343 |
+
</div>
|
| 1344 |
+
</div>
|
| 1345 |
+
|
| 1346 |
+
<!-- 권고사항 -->
|
| 1347 |
+
<div style="padding:20px;background:linear-gradient(145deg,#1a2a30,#0d1a1f);
|
| 1348 |
+
border-radius:14px;border-left:4px solid var(--neon-green);">
|
| 1349 |
+
<h4 style="color:var(--neon-green);margin:0 0 15px;">💡 핵심 권고사항</h4>
|
| 1350 |
+
<ul style="margin:0;padding-left:20px;color:#fff;line-height:1.8;">
|
| 1351 |
+
{'<li style="color:#ff6b6b;">불가 사유 우선 해결 필요</li>' if cautions > 3 else ''}
|
| 1352 |
+
{'<li>신용점수 700점 이상 유지 권장</li>' if credit < 700 else '<li style="color:#6fd9a8;">✓ 신용점수 양호</li>'}
|
| 1353 |
+
<li>필요서류: 재무제표, 사업자등록증, 납세증명서</li>
|
| 1354 |
+
<li>추천 기관: {data.get('추천기관', '신용보증기금')}</li>
|
| 1355 |
+
</ul>
|
| 1356 |
+
</div>
|
| 1357 |
+
</div>
|
| 1358 |
+
"""
|
| 1359 |
+
return html
|
| 1360 |
+
|
| 1361 |
+
# ============================================================================
|
| 1362 |
+
# 메인 분석 함수
|
| 1363 |
+
# ============================================================================
|
| 1364 |
+
|
| 1365 |
+
def run_ultra_analysis(company_name, biz_num, industry, sales, years, credit_score, employees,
|
| 1366 |
+
education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
|
| 1367 |
+
total_assets, total_liab, cur_assets, cur_liab,
|
| 1368 |
+
op_profit, net_income, interest_exp, caution_checks,
|
| 1369 |
+
existing_guar, related_sales):
|
| 1370 |
+
"""종합 분석 실행"""
|
| 1371 |
+
|
| 1372 |
+
# 재무비율
|
| 1373 |
+
equity = total_assets - total_liab
|
| 1374 |
+
debt_ratio = (total_liab / equity * 100) if equity > 0 else 999
|
| 1375 |
+
current_ratio = (cur_assets / cur_liab * 100) if cur_liab > 0 else 999
|
| 1376 |
+
op_margin = (op_profit / (sales*1e8) * 100) if sales > 0 else 0
|
| 1377 |
+
int_coverage = (op_profit*1e6 / (interest_exp*1e6)) if interest_exp > 0 else 999
|
| 1378 |
+
|
| 1379 |
+
# 점수 계산
|
| 1380 |
+
sinbo_sc = 5 if sales >= 4 else 4 if sales >= 3 else 3 if sales >= 2 else 2 if sales >= 1 else 1
|
| 1381 |
+
sinbo_sc += (1 if has_patent else 0) + (3 if education == "박사" else 2 if education == "석사" else 0)
|
| 1382 |
+
sinbo_pct = min(95, sinbo_sc * 10)
|
| 1383 |
+
|
| 1384 |
+
kibo_sc = 5 + (2 if education == "박사" else 1 if education == "석사" else 0)
|
| 1385 |
+
kibo_sc += (2 if tech_grade in ["특급","고급"] else 1 if tech_grade == "중급" else 0) + (1 if has_patent else 0)
|
| 1386 |
+
kibo_pct = min(95, kibo_sc * 10)
|
| 1387 |
+
|
| 1388 |
+
jaedan_sc = 10 if sales >= 1 and years >= 3 else 8 if sales >= 1 else 6
|
| 1389 |
+
jaedan_pct = min(95, jaedan_sc * 10)
|
| 1390 |
+
|
| 1391 |
+
# 유의사항 분석
|
| 1392 |
+
failed = sum(1 for c in caution_checks if CAUTION_ITEMS.get(c, {}).get("severity") == "불가")
|
| 1393 |
+
cond = sum(1 for c in caution_checks if CAUTION_ITEMS.get(c, {}).get("severity") == "조건부")
|
| 1394 |
+
|
| 1395 |
+
base_score = (sinbo_pct + kibo_pct + jaedan_pct) / 3
|
| 1396 |
+
deduction = failed * 15 + cond * 5
|
| 1397 |
+
total_score = max(0, min(100, int(base_score - deduction)))
|
| 1398 |
+
|
| 1399 |
+
# 금액 계산
|
| 1400 |
+
adj_sales = sales * 1e8 - related_sales * 1e8
|
| 1401 |
+
turnover = adj_sales / 7
|
| 1402 |
+
cap_limit = max(3e8, equity * 3e6) if equity > 0 else 3e8
|
| 1403 |
+
final_amt = min(turnover, cap_limit) - existing_guar * 1e8
|
| 1404 |
+
if "45" in caution_checks: final_amt = min(final_amt, 2e8)
|
| 1405 |
+
final_amt = max(0, final_amt)
|
| 1406 |
+
|
| 1407 |
+
# 추천 기관
|
| 1408 |
+
recommend = "기술보증기금" if has_patent or has_venture or has_innobiz else "신용보증기금"
|
| 1409 |
+
|
| 1410 |
+
# 차트 데이터
|
| 1411 |
+
calc_data = {
|
| 1412 |
+
"기준매출": sales * 1e8, "관계차감": related_sales * 1e8, "조정매출": adj_sales,
|
| 1413 |
+
"회전적용": adj_sales - turnover, "1차산출": turnover, "기존사용": existing_guar * 1e8, "최종금액": final_amt
|
| 1414 |
+
}
|
| 1415 |
+
|
| 1416 |
+
radar_data = {
|
| 1417 |
+
"수익성": min(100, max(0, int(op_margin * 5 + 50))),
|
| 1418 |
+
"안정성": min(100, max(0, int(100 - debt_ratio / 3))),
|
| 1419 |
+
"성장성": min(100, max(0, 60)),
|
| 1420 |
+
"활동성": min(100, max(0, int(current_ratio / 2))),
|
| 1421 |
+
"생산성": min(100, max(0, 55))
|
| 1422 |
+
}
|
| 1423 |
+
|
| 1424 |
+
company_bench = {"매출성장률": 10, "영업이익률": op_margin, "부채비율": debt_ratio, "신용등급": credit_score, "고용성장": 15}
|
| 1425 |
+
ind_data = INDUSTRY_FINANCIAL_RATIOS.get(industry, INDUSTRY_FINANCIAL_RATIOS["C 제조업"])
|
| 1426 |
+
industry_bench = {"매출성장률": ind_data.get("매출액증가율", 10), "영업이익률": ind_data.get("매출액영업이익률", 5),
|
| 1427 |
+
"부채비율": ind_data.get("부채비율", 150), "신용등급": 700, "고용성장": 10}
|
| 1428 |
+
|
| 1429 |
+
kpi_data = {"예상금액": final_amt / 1e8, "종합점수": total_score, "추천기관": recommend,
|
| 1430 |
+
"리스크": len(caution_checks), "신용등급": credit_score, "승인확률": (sinbo_pct + kibo_pct + jaedan_pct) // 3}
|
| 1431 |
+
|
| 1432 |
+
improvements = {"제2금융권 대출 상환": credit_score < 700, "카드론 상환": credit_score < 720,
|
| 1433 |
+
"연체 해소": "21" in caution_checks, "카드 사용률 30% 이하": False,
|
| 1434 |
+
"통신비 자동이체": True, "보험료 정상납부": True}
|
| 1435 |
+
|
| 1436 |
+
info_data = {"종합점수": total_score, "예상금액": final_amt / 1e8, "신보": sinbo_pct, "기보": kibo_pct,
|
| 1437 |
+
"재단": jaedan_pct, "유의사항": len(caution_checks), "신용점수": credit_score, "추천기관": recommend}
|
| 1438 |
+
|
| 1439 |
+
# 차트 생성
|
| 1440 |
+
gauge = generate_ultra_gauge(total_score, "🎮 미네랄핵 점수")
|
| 1441 |
+
radar = generate_radar_chart(radar_data)
|
| 1442 |
+
donut = generate_donut_chart(sinbo_pct, kibo_pct, jaedan_pct)
|
| 1443 |
+
pipeline = generate_pipeline(1, {"신청": request_amt, "예상": final_amt / 1e8, "기존": existing_guar})
|
| 1444 |
+
heatmap = generate_heatmap(caution_checks)
|
| 1445 |
+
benchmark = generate_benchmark_bars(company_bench, industry_bench)
|
| 1446 |
+
scorecard = generate_scorecard_dashboard(kpi_data)
|
| 1447 |
+
waterfall = generate_waterfall(calc_data)
|
| 1448 |
+
timeline = generate_timeline(5)
|
| 1449 |
+
credit_sim = generate_credit_sim(credit_score, improvements)
|
| 1450 |
+
infographic = generate_infographic(info_data)
|
| 1451 |
+
|
| 1452 |
+
return (infographic, gauge, radar, donut, pipeline, heatmap, benchmark, scorecard, waterfall, timeline, credit_sim)
|
| 1453 |
+
|
| 1454 |
+
# ============================================================================
|
| 1455 |
+
# Gradio UI
|
| 1456 |
+
# ============================================================================
|
| 1457 |
+
|
| 1458 |
+
# ============================================================================
|
| 1459 |
+
# 저장 / 불러오기 핸들러 (cache_db 연동)
|
| 1460 |
+
# ============================================================================
|
| 1461 |
+
|
| 1462 |
+
def save_fund_results(email, company_name, biz_num, industry, sales, years, credit_score, employees,
|
| 1463 |
+
education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
|
| 1464 |
+
total_assets, total_liab, cur_assets, cur_liab,
|
| 1465 |
+
op_profit, net_income, interest_exp, caution_checks,
|
| 1466 |
+
existing_guar, related_sales):
|
| 1467 |
+
"""분석 결과 저장"""
|
| 1468 |
+
if not HAS_CACHE_DB:
|
| 1469 |
+
return "⚠️ cache_db 모듈을 찾을 수 없습니다. app.py 통합 환경에서 실행해주세요."
|
| 1470 |
+
|
| 1471 |
+
if not email or '@' not in email:
|
| 1472 |
+
return "⚠️ 올바른 이메일 주소를 입력해주세요."
|
| 1473 |
+
|
| 1474 |
+
company_info = {
|
| 1475 |
+
"company_name": company_name, "biz_num": biz_num, "industry": industry,
|
| 1476 |
+
"sales": sales, "years": years, "employees": employees,
|
| 1477 |
+
"education": education, "tech_grade": tech_grade,
|
| 1478 |
+
"has_patent": has_patent, "has_venture": has_venture, "has_innobiz": has_innobiz
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
financial_info = {
|
| 1482 |
+
"credit_score": credit_score, "request_amt": request_amt,
|
| 1483 |
+
"total_assets": total_assets, "total_liab": total_liab,
|
| 1484 |
+
"cur_assets": cur_assets, "cur_liab": cur_liab,
|
| 1485 |
+
"op_profit": op_profit, "net_income": net_income, "interest_exp": interest_exp,
|
| 1486 |
+
"existing_guar": existing_guar, "related_sales": related_sales
|
| 1487 |
+
}
|
| 1488 |
+
|
| 1489 |
+
analysis_results = {
|
| 1490 |
+
"saved_at": datetime.now().isoformat(),
|
| 1491 |
+
"version": "v7.0"
|
| 1492 |
+
}
|
| 1493 |
+
|
| 1494 |
+
success, msg = _fund_cache.save_fund_analysis(email, company_info, financial_info, caution_checks, analysis_results)
|
| 1495 |
+
return msg
|
| 1496 |
+
|
| 1497 |
+
|
| 1498 |
+
def load_fund_results(email):
|
| 1499 |
+
"""저장된 분석 기록 불러오기 → 입력 필드에 복원"""
|
| 1500 |
+
if not HAS_CACHE_DB:
|
| 1501 |
+
return [gr.update()] * 22 + ["⚠️ cache_db 모듈을 찾을 수 없습니다. app.py 통합 환경에서 실행해주세요."]
|
| 1502 |
+
|
| 1503 |
+
if not email or '@' not in email:
|
| 1504 |
+
return [gr.update()] * 22 + ["⚠️ 올바른 이메일 주소를 입력해주세요."]
|
| 1505 |
+
|
| 1506 |
+
record, msg = _fund_cache.load_fund_profile(email)
|
| 1507 |
+
|
| 1508 |
+
if record is None:
|
| 1509 |
+
return [gr.update()] * 22 + [msg]
|
| 1510 |
+
|
| 1511 |
+
ci = record.get("company_info", {})
|
| 1512 |
+
fi = record.get("financial_info", {})
|
| 1513 |
+
cc = record.get("caution_checks", [])
|
| 1514 |
+
|
| 1515 |
+
return [
|
| 1516 |
+
gr.update(value=ci.get("company_name", "")), # company_name
|
| 1517 |
+
gr.update(value=ci.get("biz_num", "")), # biz_num
|
| 1518 |
+
gr.update(value=ci.get("industry", "C 제조업")), # industry
|
| 1519 |
+
gr.update(value=ci.get("sales", 5)), # sales
|
| 1520 |
+
gr.update(value=ci.get("years", 3)), # years
|
| 1521 |
+
gr.update(value=fi.get("credit_score", 720)), # credit_score
|
| 1522 |
+
gr.update(value=ci.get("employees", 10)), # employees
|
| 1523 |
+
gr.update(value=ci.get("education", "학사")), # education
|
| 1524 |
+
gr.update(value=ci.get("tech_grade", "중급")), # tech_grade
|
| 1525 |
+
gr.update(value=ci.get("has_patent", False)), # has_patent
|
| 1526 |
+
gr.update(value=ci.get("has_venture", False)), # has_venture
|
| 1527 |
+
gr.update(value=ci.get("has_innobiz", False)), # has_innobiz
|
| 1528 |
+
gr.update(value=fi.get("request_amt", 3)), # request_amt
|
| 1529 |
+
gr.update(value=fi.get("total_assets", 1000)), # total_assets
|
| 1530 |
+
gr.update(value=fi.get("total_liab", 600)), # total_liab
|
| 1531 |
+
gr.update(value=fi.get("cur_assets", 500)), # cur_assets
|
| 1532 |
+
gr.update(value=fi.get("cur_liab", 400)), # cur_liab
|
| 1533 |
+
gr.update(value=fi.get("op_profit", 50)), # op_profit
|
| 1534 |
+
gr.update(value=fi.get("net_income", 30)), # net_income
|
| 1535 |
+
gr.update(value=fi.get("interest_exp", 10)), # interest_exp
|
| 1536 |
+
gr.update(value=cc), # caution_checks
|
| 1537 |
+
gr.update(value=fi.get("existing_guar", 0)), # existing_guar
|
| 1538 |
+
gr.update(value=fi.get("related_sales", 0)), # related_sales
|
| 1539 |
+
msg # status
|
| 1540 |
+
]
|
| 1541 |
+
|
| 1542 |
+
|
| 1543 |
+
def create_fund_tab():
|
| 1544 |
+
"""정책자금 탭 생성"""
|
| 1545 |
+
|
| 1546 |
+
with gr.Tab("💰 정책자금 사전심사"):
|
| 1547 |
+
gr.HTML('''
|
| 1548 |
+
<div style="background:linear-gradient(135deg,#0d1117,#161b22,#0d1117);padding:35px;border-radius:20px;
|
| 1549 |
+
margin-bottom:24px;border:2px solid rgba(111,217,168,0.3);text-align:center;
|
| 1550 |
+
box-shadow:0 10px 40px rgba(0,0,0,0.5);">
|
| 1551 |
+
<h2 style="margin:0 0 10px;font-family:'Orbitron',sans-serif;
|
| 1552 |
+
background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
|
| 1553 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-size:32px;
|
| 1554 |
+
animation:glow-text 2s ease-in-out infinite;">
|
| 1555 |
+
🎮 미네랄핵 ULTRA v7.0
|
| 1556 |
+
</h2>
|
| 1557 |
+
<p style="color:#8888a0;margin:0;font-size:14px;">정책자금 사전심사 시스템 | 11개 비주얼 차트</p>
|
| 1558 |
+
</div>
|
| 1559 |
+
''')
|
| 1560 |
+
|
| 1561 |
+
with gr.Row():
|
| 1562 |
+
with gr.Column(scale=3):
|
| 1563 |
+
fund_email = gr.Textbox(label="📧 이메일", placeholder="example@company.com")
|
| 1564 |
+
with gr.Column(scale=1):
|
| 1565 |
+
with gr.Row():
|
| 1566 |
+
fund_load = gr.Button("📥 불러오기", size="sm")
|
| 1567 |
+
fund_save = gr.Button("💾 저장", size="sm", variant="primary")
|
| 1568 |
+
|
| 1569 |
+
fund_status = gr.Textbox(label="상태", interactive=False, lines=1)
|
| 1570 |
+
|
| 1571 |
+
with gr.Tabs():
|
| 1572 |
+
with gr.Tab("📝 기업정보"):
|
| 1573 |
+
with gr.Row():
|
| 1574 |
+
with gr.Column():
|
| 1575 |
+
company_name = gr.Textbox(label="회사명", placeholder="(주)회사명")
|
| 1576 |
+
biz_num = gr.Textbox(label="사업자번호", placeholder="000-00-00000")
|
| 1577 |
+
industry = gr.Dropdown(label="업종", choices=list(INDUSTRY_FINANCIAL_RATIOS.keys()), value="C 제조업")
|
| 1578 |
+
sales = gr.Number(label="연매출 (억원)", value=5)
|
| 1579 |
+
years = gr.Number(label="업력 (년)", value=3)
|
| 1580 |
+
employees = gr.Number(label="종업원 (명)", value=10)
|
| 1581 |
+
with gr.Column():
|
| 1582 |
+
credit_score = gr.Slider(label="신용점수", minimum=300, maximum=900, value=720, step=10)
|
| 1583 |
+
education = gr.Dropdown(label="학력", choices=["고졸이하", "학사", "석사", "박사"], value="학사")
|
| 1584 |
+
tech_grade = gr.Dropdown(label="기술자격", choices=["초급이하", "초급", "중급", "고급", "특급"], value="중급")
|
| 1585 |
+
has_patent = gr.Checkbox(label="특허 보유")
|
| 1586 |
+
has_venture = gr.Checkbox(label="벤처인증")
|
| 1587 |
+
has_innobiz = gr.Checkbox(label="이노비즈")
|
| 1588 |
+
|
| 1589 |
+
with gr.Row():
|
| 1590 |
+
with gr.Column():
|
| 1591 |
+
total_assets = gr.Number(label="총자산 (백만원)", value=1000)
|
| 1592 |
+
total_liab = gr.Number(label="총부채 (백만원)", value=600)
|
| 1593 |
+
cur_assets = gr.Number(label="유동자산 (백만원)", value=500)
|
| 1594 |
+
cur_liab = gr.Number(label="유동부채 (백만원)", value=400)
|
| 1595 |
+
with gr.Column():
|
| 1596 |
+
op_profit = gr.Number(label="영업이익 (백만원)", value=50)
|
| 1597 |
+
net_income = gr.Number(label="순이익 (백만원)", value=30)
|
| 1598 |
+
interest_exp = gr.Number(label="이자비용 (백만원)", value=10)
|
| 1599 |
+
request_amt = gr.Number(label="필요자금 (억원)", value=3)
|
| 1600 |
+
|
| 1601 |
+
with gr.Row():
|
| 1602 |
+
existing_guar = gr.Number(label="기존보증 (억원)", value=0)
|
| 1603 |
+
related_sales = gr.Number(label="관계기업매입 (억원)", value=0)
|
| 1604 |
+
|
| 1605 |
+
caution_opts = [(f"[{c}] {i['name']}", c) for c, i in list(CAUTION_ITEMS.items())[:25]]
|
| 1606 |
+
caution_checks = gr.CheckboxGroup(label="⚠️ 유의사항", choices=caution_opts, value=[])
|
| 1607 |
+
|
| 1608 |
+
analyze_btn = gr.Button("🔍 종합 분석", variant="primary", size="lg", elem_classes=["analyze-btn"])
|
| 1609 |
+
|
| 1610 |
+
# 결과 탭들
|
| 1611 |
+
with gr.Tab("📊 종합 인포그래픽"): out_info = gr.HTML()
|
| 1612 |
+
with gr.Tab("🎯 종합점수"): out_gauge = gr.HTML()
|
| 1613 |
+
with gr.Tab("🕸️ 재무 레이더"): out_radar = gr.HTML()
|
| 1614 |
+
with gr.Tab("🍩 기관별 확률"): out_donut = gr.HTML()
|
| 1615 |
+
with gr.Tab("💰 파이프라인"): out_pipeline = gr.HTML()
|
| 1616 |
+
with gr.Tab("🔥 리스크맵"): out_heatmap = gr.HTML()
|
| 1617 |
+
with gr.Tab("📈 벤치마크"): out_benchmark = gr.HTML()
|
| 1618 |
+
with gr.Tab("📋 KPI보드"): out_scorecard = gr.HTML()
|
| 1619 |
+
with gr.Tab("💧 워터폴"): out_waterfall = gr.HTML()
|
| 1620 |
+
with gr.Tab("⏱️ 타임라인"): out_timeline = gr.HTML()
|
| 1621 |
+
with gr.Tab("💳 신용시뮬"): out_credit = gr.HTML()
|
| 1622 |
+
|
| 1623 |
+
inputs = [company_name, biz_num, industry, sales, years, credit_score, employees,
|
| 1624 |
+
education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
|
| 1625 |
+
total_assets, total_liab, cur_assets, cur_liab,
|
| 1626 |
+
op_profit, net_income, interest_exp, caution_checks, existing_guar, related_sales]
|
| 1627 |
+
|
| 1628 |
+
outputs = [out_info, out_gauge, out_radar, out_donut, out_pipeline, out_heatmap,
|
| 1629 |
+
out_benchmark, out_scorecard, out_waterfall, out_timeline, out_credit]
|
| 1630 |
+
|
| 1631 |
+
analyze_btn.click(fn=run_ultra_analysis, inputs=inputs, outputs=outputs)
|
| 1632 |
+
|
| 1633 |
+
# 저장/불러오기 이벤트
|
| 1634 |
+
save_inputs = [fund_email] + inputs
|
| 1635 |
+
fund_save.click(fn=save_fund_results, inputs=save_inputs, outputs=[fund_status])
|
| 1636 |
+
|
| 1637 |
+
load_outputs = inputs + [fund_status]
|
| 1638 |
+
fund_load.click(fn=load_fund_results, inputs=[fund_email], outputs=load_outputs)
|
| 1639 |
+
|
| 1640 |
+
|
| 1641 |
+
def create_app():
|
| 1642 |
+
"""독립 실행용"""
|
| 1643 |
+
with gr.Blocks(css=CUSTOM_CSS, title="미네랄핵 ULTRA v7.0") as app:
|
| 1644 |
+
gr.HTML("""
|
| 1645 |
+
<div style="text-align:center;padding:40px;background:linear-gradient(135deg,#0d1117,#161b22);
|
| 1646 |
+
border-radius:20px;margin-bottom:20px;border:1px solid rgba(111,217,168,0.2);">
|
| 1647 |
+
<h1 style="font-family:'Orbitron',sans-serif;font-size:42px;margin:0;
|
| 1648 |
+
background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
|
| 1649 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;">
|
| 1650 |
+
🎮 미네랄핵 ULTRA v7.0
|
| 1651 |
+
</h1>
|
| 1652 |
+
<p style="color:#8888a0;margin:15px 0 0;font-size:16px;">OKCEO 정책자금 사전심사 시스템</p>
|
| 1653 |
+
</div>
|
| 1654 |
+
""")
|
| 1655 |
+
create_fund_tab()
|
| 1656 |
+
return app
|
| 1657 |
+
|
| 1658 |
+
|
| 1659 |
+
if __name__ == "__main__":
|
| 1660 |
+
app = create_app()
|
| 1661 |
+
app.launch(server_name="0.0.0.0", server_port=7860)
|