Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,1343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
허깅페이스 배포용 Agentic AI 글로벌 파트너 DB 분석 대시보드
|
| 6 |
+
✅ Gmail SMTP 이메일 발송
|
| 7 |
+
✅ 파스텔톤 차트 색상
|
| 8 |
+
✅ 생태계별 최적화된 대시보드
|
| 9 |
+
✅ jeongkee10@gmission.com 수신
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import subprocess
|
| 14 |
+
import sys
|
| 15 |
+
import datetime
|
| 16 |
+
import traceback
|
| 17 |
+
import gc
|
| 18 |
+
import smtplib
|
| 19 |
+
from email.mime.text import MIMEText
|
| 20 |
+
from email.mime.multipart import MIMEMultipart
|
| 21 |
+
import ssl
|
| 22 |
+
import re
|
| 23 |
+
|
| 24 |
+
# 패키지 설치 함수
|
| 25 |
+
def install_packages():
|
| 26 |
+
packages = ['gradio>=4.0.0', 'plotly>=5.0.0', 'pandas>=2.0.0', 'openpyxl>=3.0.0']
|
| 27 |
+
for package in packages:
|
| 28 |
+
try:
|
| 29 |
+
pkg_name = package.split('>=')[0]
|
| 30 |
+
__import__(pkg_name)
|
| 31 |
+
except ImportError:
|
| 32 |
+
print(f"Installing {package}...")
|
| 33 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
| 34 |
+
|
| 35 |
+
# 패키지 설치 실행
|
| 36 |
+
try:
|
| 37 |
+
install_packages()
|
| 38 |
+
print("✅ 패키지 설치 완료")
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"⚠️ 패키지 설치 오류: {e}")
|
| 41 |
+
|
| 42 |
+
import pandas as pd
|
| 43 |
+
import gradio as gr
|
| 44 |
+
|
| 45 |
+
# Gradio 버전 호환성 확인
|
| 46 |
+
try:
|
| 47 |
+
gradio_version = gr.__version__
|
| 48 |
+
print(f"✅ Gradio 버전: {gradio_version}")
|
| 49 |
+
except:
|
| 50 |
+
print("⚠️ Gradio 버전 확인 불가")
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
import plotly.express as px
|
| 54 |
+
import plotly.graph_objects as go
|
| 55 |
+
from plotly.subplots import make_subplots
|
| 56 |
+
PLOTLY_AVAILABLE = True
|
| 57 |
+
print("✅ Plotly 사용 가능")
|
| 58 |
+
except ImportError:
|
| 59 |
+
print("⚠️ Plotly 없음 - 차트 기능 제한")
|
| 60 |
+
PLOTLY_AVAILABLE = False
|
| 61 |
+
|
| 62 |
+
from typing import Tuple, List, Dict, Any, Optional
|
| 63 |
+
|
| 64 |
+
# ==================== 설정 및 색상 정의 ====================
|
| 65 |
+
|
| 66 |
+
# 파스텔톤 색상 팔레트
|
| 67 |
+
PASTEL_COLORS = {
|
| 68 |
+
'primary_palette': ['#FFB3BA', '#FFDFBA', '#FFFFBA', '#BAFFC9', '#BAE1FF', '#E5BAFF', '#FFBAE5'],
|
| 69 |
+
'secondary_palette': ['#FFD6D6', '#FFEBD6', '#FFFFD6', '#D6FFE0', '#D6EBFF', '#E0D6FF', '#FFD6EB'],
|
| 70 |
+
'ecosystem_colors': {
|
| 71 |
+
'Infra': '#FFB3BA', # 파스텔 핑크
|
| 72 |
+
'Foundation Model': '#BAFFC9', # 파스텔 그린
|
| 73 |
+
'Platform / Agentic AI Pipeline': '#BAE1FF', # 파스텔 블루
|
| 74 |
+
'Application': '#FFDFBA', # 파스텔 오렌지
|
| 75 |
+
'전체': '#E5BAFF' # 파스텔 퍼플
|
| 76 |
+
},
|
| 77 |
+
'importance_colors': {
|
| 78 |
+
5: '#FFB3BA', # S급 - 파스텔 핑크
|
| 79 |
+
4: '#FFDFBA', # A급 - 파스텔 오렌지
|
| 80 |
+
3: '#BAE1FF', # B급 - 파스텔 블루
|
| 81 |
+
2: '#BAFFC9', # C급 - 파스텔 그린
|
| 82 |
+
1: '#E5E5E5' # D급 - 파스텔 그레이
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
# 생태계 카테고리
|
| 87 |
+
ECOSYSTEM_CATEGORIES = [
|
| 88 |
+
"전체",
|
| 89 |
+
"Infra",
|
| 90 |
+
"Foundation Model",
|
| 91 |
+
"Platform / Agentic AI Pipeline",
|
| 92 |
+
"Application"
|
| 93 |
+
]
|
| 94 |
+
|
| 95 |
+
# 생태계별 최적화된 컬럼 매핑
|
| 96 |
+
ECOSYSTEM_OPTIMIZED_COLUMNS = {
|
| 97 |
+
'Infra': {
|
| 98 |
+
'primary': ['기업명', '중요도', 'HQ_국가', '설립연도', 'PrimaryLLM', 'LLM_Deployment', '주요제품'],
|
| 99 |
+
'charts': ['중요도_분포', '국가별_분포', '설립연도_분포', 'LLM_배포방식'],
|
| 100 |
+
'focus': 'infrastructure and deployment capabilities'
|
| 101 |
+
},
|
| 102 |
+
'Foundation Model': {
|
| 103 |
+
'primary': ['기업명', '중요도', 'HQ_국가', 'PrimaryLLM', 'OpenSource여부', 'LLM_Deployment', '창업자'],
|
| 104 |
+
'charts': ['중요도_분포', 'OpenSource_분포', 'LLM_종류', '국가별_분포'],
|
| 105 |
+
'focus': 'model development and open-source contributions'
|
| 106 |
+
},
|
| 107 |
+
'Platform / Agentic AI Pipeline': {
|
| 108 |
+
'primary': ['기업명', '중요도', 'Application_Domain', 'PrimaryLLM', 'STT_TTS지원', 'TargetCustomer', 'PricingModel'],
|
| 109 |
+
'charts': ['중요도_분포', '응용도메인_분포', 'STT_TTS_지원', '타겟고객'],
|
| 110 |
+
'focus': 'platform integration and agentic workflows'
|
| 111 |
+
},
|
| 112 |
+
'Application': {
|
| 113 |
+
'primary': ['기업명', '중요도', 'Application_Domain', 'TargetCustomer', 'PricingModel', 'KoreaSupport', 'STT_TTS지원'],
|
| 114 |
+
'charts': ['중요도_분포', '응용도메인_분포', '가격정책', '한국지원'],
|
| 115 |
+
'focus': 'end-user applications and market solutions'
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
# 컬럼 매핑
|
| 120 |
+
EXACT_COLUMN_MAPPING = {
|
| 121 |
+
'기업명': ['기업명', 'CompanyName', 'company', 'name'],
|
| 122 |
+
'중요도': ['중요도', 'Importance', 'Priority'],
|
| 123 |
+
'Ecosystem_Category': ['Ecosystem Category', 'Ecosystem_Category'],
|
| 124 |
+
'HQ_국가': ['HQ_국가', 'HQ_Country', 'Country'],
|
| 125 |
+
'설립연도': ['설립연도', 'FoundedYear', 'Founded'],
|
| 126 |
+
'주요투자자': ['주요투자자', 'KeyInvestors', 'Investors'],
|
| 127 |
+
'기업개요': ['기업개요', 'CompanySummary', 'Summary'],
|
| 128 |
+
'웹사이트': ['웹사이트', 'Website', 'WebsiteURL'],
|
| 129 |
+
'문의URL': ['문의URL', 'ContactURL', 'Contact_URL'],
|
| 130 |
+
'이메일': ['이메일', 'Email', 'EmailAddress'],
|
| 131 |
+
'전화번호': ['전화번호', 'PhoneNumber', 'phone'],
|
| 132 |
+
'HQ_도시': ['HQ_도시', 'HQ_City', 'City'],
|
| 133 |
+
'창업자': ['창업자', 'Founders', 'founder'],
|
| 134 |
+
'Application_Domain': ['Application_Domain', 'Application Domain'],
|
| 135 |
+
'PrimaryLLM': ['PrimaryLLM', 'Primary LLM'],
|
| 136 |
+
'LLM_Deployment': ['LLM_Deployment', 'LLM Deployment'],
|
| 137 |
+
'OpenSource여부': ['OpenSource여부', 'OpenSource'],
|
| 138 |
+
'주요제품': ['주요제품', 'KeyProducts', 'Products'],
|
| 139 |
+
'STT_TTS지원': ['STT_TTS지원', 'STT_TTS'],
|
| 140 |
+
'TargetCustomer': ['TargetCustomer', 'Target Customer'],
|
| 141 |
+
'PricingModel': ['PricingModel', 'Pricing Model'],
|
| 142 |
+
'KoreaSupport': ['KoreaSupport', 'Korea Support'],
|
| 143 |
+
'PartnershipProgram': ['PartnershipProgram', 'Partnership Program']
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
# ==================== Gmail SMTP 이메일 시스템 ====================
|
| 147 |
+
|
| 148 |
+
class GmailEmailSystem:
|
| 149 |
+
def __init__(self):
|
| 150 |
+
self.email_history = []
|
| 151 |
+
self.smtp_server = "smtp.gmail.com"
|
| 152 |
+
self.port = 587
|
| 153 |
+
|
| 154 |
+
# 허깅페이스 Secrets에서 이메일 정보 가져오기
|
| 155 |
+
self.gmail_email = os.getenv("GMAIL_EMAIL", "")
|
| 156 |
+
self.gmail_password = os.getenv("GMAIL_PASSWORD", "")
|
| 157 |
+
self.recipient_email = os.getenv("RECIPIENT_EMAIL", "jeongkee10@gmission.com")
|
| 158 |
+
|
| 159 |
+
print(f"📧 Gmail 설정: {self.gmail_email[:5] if self.gmail_email else '미설정'}***@gmail.com → {self.recipient_email}")
|
| 160 |
+
|
| 161 |
+
def validate_email(self, email: str) -> bool:
|
| 162 |
+
"""이메일 주소 유효성 검증"""
|
| 163 |
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
| 164 |
+
return re.match(pattern, email) is not None
|
| 165 |
+
|
| 166 |
+
def send_email(self, subject: str, body: str, company_name: str, target_email: str) -> Tuple[bool, str]:
|
| 167 |
+
"""Gmail SMTP를 통한 실제 이메일 발송"""
|
| 168 |
+
try:
|
| 169 |
+
if not self.gmail_email or not self.gmail_password:
|
| 170 |
+
return False, "Gmail 계정 정보가 설정되지 않았습니다. 허깅페이스 Secrets를 확인하세요."
|
| 171 |
+
|
| 172 |
+
if not self.validate_email(target_email):
|
| 173 |
+
return False, f"올바르지 않은 이메일 주소: {target_email}"
|
| 174 |
+
|
| 175 |
+
# 이메일 메시지 생성
|
| 176 |
+
message = MIMEMultipart()
|
| 177 |
+
message["From"] = self.gmail_email
|
| 178 |
+
message["To"] = self.recipient_email # 항상 jeongkee10@gmission.com으로 발송
|
| 179 |
+
message["Subject"] = f"[파트너십 제안] {subject}"
|
| 180 |
+
|
| 181 |
+
# 이메일 본문 (원본 대상과 내용 포함)
|
| 182 |
+
enhanced_body = f"""
|
| 183 |
+
G-Mission 파트너십 팀에게,
|
| 184 |
+
|
| 185 |
+
다음 기업에 대한 파트너십 제안서가 대시보드에서 생성되었습니다:
|
| 186 |
+
|
| 187 |
+
원본 대상 기업: {company_name}
|
| 188 |
+
원본 이메일 주소: {target_email}
|
| 189 |
+
생성 시간: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 190 |
+
|
| 191 |
+
=== 파트너십 제안 내용 ===
|
| 192 |
+
|
| 193 |
+
{body}
|
| 194 |
+
|
| 195 |
+
=== 다음 단계 ===
|
| 196 |
+
1. 제안 내용 검토
|
| 197 |
+
2. 필요시 내용 수정
|
| 198 |
+
3. {target_email}로 직접 발송
|
| 199 |
+
4. 결과 추적 및 관리
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
이 이메일은 Agentic AI 파트너 DB 분석 대시보드에서 자동 생성되었습니다.
|
| 203 |
+
"""
|
| 204 |
+
|
| 205 |
+
message.attach(MIMEText(enhanced_body, "plain", "utf-8"))
|
| 206 |
+
|
| 207 |
+
# Gmail SMTP 서버 연결 및 발송
|
| 208 |
+
context = ssl.create_default_context()
|
| 209 |
+
with smtplib.SMTP(self.smtp_server, self.port) as server:
|
| 210 |
+
server.starttls(context=context)
|
| 211 |
+
server.login(self.gmail_email, self.gmail_password)
|
| 212 |
+
server.sendmail(self.gmail_email, self.recipient_email, message.as_string())
|
| 213 |
+
|
| 214 |
+
# 발송 기록
|
| 215 |
+
self.record_email_send(company_name, target_email, subject, body[:100])
|
| 216 |
+
|
| 217 |
+
return True, f"✅ 이메일이 {self.recipient_email}로 성공적으로 발송되었습니다!"
|
| 218 |
+
|
| 219 |
+
except smtplib.SMTPAuthenticationError:
|
| 220 |
+
return False, "❌ Gmail 인증 실패. 앱 비밀번호를 확인하세요."
|
| 221 |
+
except smtplib.SMTPException as e:
|
| 222 |
+
return False, f"❌ SMTP 오류: {str(e)}"
|
| 223 |
+
except Exception as e:
|
| 224 |
+
return False, f"❌ 이메일 발송 실패: {str(e)}"
|
| 225 |
+
|
| 226 |
+
def create_partnership_email(self, company_name: str, email: str, ecosystem_category: str,
|
| 227 |
+
importance: int = 3, country: str = "", website: str = "") -> Tuple[str, str, str]:
|
| 228 |
+
"""개선된 파트너십 이메일 생성"""
|
| 229 |
+
try:
|
| 230 |
+
if not email or '@' not in str(email) or str(email) in ['정보 없음', 'N/A']:
|
| 231 |
+
return "", "", ""
|
| 232 |
+
|
| 233 |
+
# 생태계별 맞춤 제목
|
| 234 |
+
ecosystem_subjects = {
|
| 235 |
+
"Infra": f"Infrastructure Partnership Opportunity - {company_name} & G-Mission",
|
| 236 |
+
"Foundation Model": f"AI Foundation Model Collaboration - {company_name} & G-Mission",
|
| 237 |
+
"Platform / Agentic AI Pipeline": f"Agentic AI Platform Partnership - {company_name} & G-Mission",
|
| 238 |
+
"Application": f"AI Application Partnership Proposal - {company_name} & G-Mission"
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
subject = ecosystem_subjects.get(ecosystem_category,
|
| 242 |
+
f"Strategic AI Partnership Proposal - {company_name} & G-Mission")
|
| 243 |
+
|
| 244 |
+
# 중요도별 접근 방식
|
| 245 |
+
importance_approaches = {
|
| 246 |
+
5: "We have identified your company as a top-tier strategic partner",
|
| 247 |
+
4: "We have identified your company as a key strategic partner",
|
| 248 |
+
3: "We have identified your company as a promising strategic partner",
|
| 249 |
+
2: "We are interested in exploring partnership opportunities",
|
| 250 |
+
1: "We would like to discuss potential collaboration opportunities"
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
approach = importance_approaches.get(int(importance),
|
| 254 |
+
"We are interested in exploring partnership opportunities")
|
| 255 |
+
|
| 256 |
+
# 생태계별 협력 방안
|
| 257 |
+
ecosystem_focus = {
|
| 258 |
+
"Infra": """
|
| 259 |
+
INFRASTRUCTURE COLLABORATION FOCUS:
|
| 260 |
+
• AI compute infrastructure optimization and resource sharing
|
| 261 |
+
• Cloud deployment strategies for Korean market
|
| 262 |
+
• Hardware-software integration solutions
|
| 263 |
+
• Scalable AI infrastructure development""",
|
| 264 |
+
|
| 265 |
+
"Foundation Model": """
|
| 266 |
+
FOUNDATION MODEL COLLABORATION FOCUS:
|
| 267 |
+
• Korean language model development and fine-tuning
|
| 268 |
+
• Model licensing and technology transfer opportunities
|
| 269 |
+
• Joint research in multilingual AI capabilities
|
| 270 |
+
• Open-source collaboration initiatives""",
|
| 271 |
+
|
| 272 |
+
"Platform / Agentic AI Pipeline": """
|
| 273 |
+
PLATFORM INTEGRATION FOCUS:
|
| 274 |
+
• API integration and interoperability solutions
|
| 275 |
+
• Agentic workflow automation partnerships
|
| 276 |
+
• Platform ecosystem development
|
| 277 |
+
• Enterprise AI orchestration solutions""",
|
| 278 |
+
|
| 279 |
+
"Application": """
|
| 280 |
+
APPLICATION PARTNERSHIP FOCUS:
|
| 281 |
+
• Market-specific AI application development
|
| 282 |
+
• Customer success case studies and joint marketing
|
| 283 |
+
• Industry-vertical solution partnerships
|
| 284 |
+
• Korean market entry and expansion strategies"""
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
collaboration_details = ecosystem_focus.get(ecosystem_category,
|
| 288 |
+
"• Strategic technology partnership development")
|
| 289 |
+
|
| 290 |
+
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 291 |
+
|
| 292 |
+
body = f"""Dear {company_name} Partnership Team,
|
| 293 |
+
|
| 294 |
+
Greetings from G-Mission!
|
| 295 |
+
|
| 296 |
+
{approach} in the '{ecosystem_category}' space through our comprehensive analysis of 155+ global Agentic AI companies.
|
| 297 |
+
|
| 298 |
+
{collaboration_details}
|
| 299 |
+
|
| 300 |
+
KEY PARTNERSHIP OPPORTUNITIES:
|
| 301 |
+
• Technology integration and joint product development
|
| 302 |
+
• Market expansion in Korea and APAC region
|
| 303 |
+
• Strategic alliance in enterprise AI solutions
|
| 304 |
+
• Investment and business development collaboration
|
| 305 |
+
|
| 306 |
+
ABOUT G-MISSION:
|
| 307 |
+
G-Mission is a leading AI technology company focused on developing innovative solutions for the Korean and APAC markets. We specialize in enterprise AI implementations and strategic partnerships with global technology leaders.
|
| 308 |
+
|
| 309 |
+
NEXT STEPS:
|
| 310 |
+
We would be delighted to schedule an introductory partnership discussion to explore mutual opportunities. Our team can share detailed market insights, partnership frameworks, and collaboration proposals tailored to your strategic objectives.
|
| 311 |
+
|
| 312 |
+
Would you be available for a partnership discussion in the coming weeks?
|
| 313 |
+
|
| 314 |
+
Best regards,
|
| 315 |
+
|
| 316 |
+
G-Mission Partnership Development Team
|
| 317 |
+
Email: jeongkee10@gmission.com
|
| 318 |
+
Website: https://gmission.com
|
| 319 |
+
|
| 320 |
+
---
|
| 321 |
+
This partnership proposal was generated through our Agentic AI Global Partner Database Analysis.
|
| 322 |
+
Generated on: {current_time}
|
| 323 |
+
Analysis Confidence: {"High" if importance >= 4 else "Medium" if importance >= 3 else "Standard"}"""
|
| 324 |
+
|
| 325 |
+
return subject, body, str(email)
|
| 326 |
+
|
| 327 |
+
except Exception as e:
|
| 328 |
+
print(f"이메일 생성 오류: {e}")
|
| 329 |
+
return "", "", ""
|
| 330 |
+
|
| 331 |
+
def record_email_send(self, company_name: str, email: str, subject: str, body_preview: str) -> bool:
|
| 332 |
+
"""이메일 발송 기록"""
|
| 333 |
+
try:
|
| 334 |
+
record = {
|
| 335 |
+
'timestamp': datetime.datetime.now(),
|
| 336 |
+
'company_name': str(company_name),
|
| 337 |
+
'target_email': str(email),
|
| 338 |
+
'subject': str(subject),
|
| 339 |
+
'body_preview': str(body_preview),
|
| 340 |
+
'recipient': self.recipient_email,
|
| 341 |
+
'status': '발송 완료',
|
| 342 |
+
'send_date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
self.email_history.append(record)
|
| 346 |
+
return True
|
| 347 |
+
except:
|
| 348 |
+
return False
|
| 349 |
+
|
| 350 |
+
def get_email_history_text(self) -> str:
|
| 351 |
+
"""이메일 발송 이력 반환"""
|
| 352 |
+
if not self.email_history:
|
| 353 |
+
return """📧 **이메일 발송 현황**
|
| 354 |
+
|
| 355 |
+
아직 발송된 이메일이 없습니다.
|
| 356 |
+
|
| 357 |
+
**사용 방법:**
|
| 358 |
+
1. 기업 분석 실행
|
| 359 |
+
2. 📧 이메일 탭에서 내용 확인/수정
|
| 360 |
+
3. '실제 이메일 발송' 버튼 클릭
|
| 361 |
+
4. 이메일이 jeongkee10@gmission.com으로 발송됩니다"""
|
| 362 |
+
|
| 363 |
+
history_text = f"📧 **이메일 발송 현황** (총 {len(self.email_history)}건)\n\n"
|
| 364 |
+
|
| 365 |
+
for i, record in enumerate(reversed(self.email_history), 1):
|
| 366 |
+
history_text += f"""
|
| 367 |
+
**{i}. {record['company_name']}**
|
| 368 |
+
- 발송일시: {record['send_date']}
|
| 369 |
+
- 대상 이메일: {record['target_email']}
|
| 370 |
+
- 실제 수신: {record['recipient']}
|
| 371 |
+
- 제목: {record['subject'][:50]}...
|
| 372 |
+
- 상태: ✅ {record['status']}
|
| 373 |
+
|
| 374 |
+
---
|
| 375 |
+
"""
|
| 376 |
+
|
| 377 |
+
return history_text
|
| 378 |
+
|
| 379 |
+
# ==================== 데이터 처리 시스템 ====================
|
| 380 |
+
|
| 381 |
+
class OptimizedDataProcessor:
|
| 382 |
+
@staticmethod
|
| 383 |
+
def find_column_mapping(df_columns: List[str]) -> Dict[str, str]:
|
| 384 |
+
"""컬럼 매핑 찾기"""
|
| 385 |
+
mapping = {}
|
| 386 |
+
for standard_name, possible_names in EXACT_COLUMN_MAPPING.items():
|
| 387 |
+
for possible_name in possible_names:
|
| 388 |
+
if possible_name in df_columns:
|
| 389 |
+
mapping[standard_name] = possible_name
|
| 390 |
+
break
|
| 391 |
+
return mapping
|
| 392 |
+
|
| 393 |
+
@staticmethod
|
| 394 |
+
def safe_numeric_conversion(series: pd.Series) -> pd.Series:
|
| 395 |
+
"""안전한 숫자 변환"""
|
| 396 |
+
try:
|
| 397 |
+
processed = series.astype(str).copy()
|
| 398 |
+
processed = processed.replace(['정보 없음', 'nan', 'null', 'NaN', '', 'N/A'], '0')
|
| 399 |
+
grade_mapping = {'S': '5', 'A': '4', 'B': '3', 'C': '2', 'D': '1'}
|
| 400 |
+
processed = processed.replace(grade_mapping)
|
| 401 |
+
processed = processed.str.replace(r'[^\d.]', '', regex=True).replace('', '0')
|
| 402 |
+
return pd.to_numeric(processed, errors='coerce').fillna(0)
|
| 403 |
+
except:
|
| 404 |
+
return pd.Series([0] * len(series))
|
| 405 |
+
|
| 406 |
+
@staticmethod
|
| 407 |
+
def process_data(df: pd.DataFrame) -> pd.DataFrame:
|
| 408 |
+
"""데이터 처리 메인 함수"""
|
| 409 |
+
try:
|
| 410 |
+
if df is None or df.empty:
|
| 411 |
+
return pd.DataFrame()
|
| 412 |
+
|
| 413 |
+
column_mapping = OptimizedDataProcessor.find_column_mapping(df.columns.tolist())
|
| 414 |
+
processed_df = pd.DataFrame()
|
| 415 |
+
|
| 416 |
+
for standard_name, original_name in column_mapping.items():
|
| 417 |
+
if original_name in df.columns:
|
| 418 |
+
processed_df[standard_name] = df[original_name].copy()
|
| 419 |
+
|
| 420 |
+
# 필수 컬럼 보장
|
| 421 |
+
if '기업명' not in processed_df.columns:
|
| 422 |
+
processed_df['기업명'] = df.iloc[:, 1] if len(df.columns) > 1 else [f"기업_{i+1}" for i in range(len(df))]
|
| 423 |
+
|
| 424 |
+
if 'Ecosystem_Category' not in processed_df.columns:
|
| 425 |
+
processed_df['Ecosystem_Category'] = '미분류'
|
| 426 |
+
|
| 427 |
+
# 중요도 처리
|
| 428 |
+
if '중요도' in processed_df.columns:
|
| 429 |
+
processed_df['중요도'] = OptimizedDataProcessor.safe_numeric_conversion(processed_df['중요도'])
|
| 430 |
+
processed_df['중요도등급'] = processed_df['중요도'].apply(
|
| 431 |
+
lambda x: 'S급 (최우선)' if x == 5 else
|
| 432 |
+
'A급 (핵심)' if x == 4 else
|
| 433 |
+
'B급 (유망)' if x == 3 else
|
| 434 |
+
'C급 (관심)' if x == 2 else
|
| 435 |
+
'D급 (참고)' if x == 1 else '미분류'
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
processed_df = processed_df.fillna('정보 없음')
|
| 439 |
+
|
| 440 |
+
# 메모리 최적화
|
| 441 |
+
for col in processed_df.columns:
|
| 442 |
+
if processed_df[col].dtype == 'object':
|
| 443 |
+
processed_df[col] = processed_df[col].astype('category')
|
| 444 |
+
|
| 445 |
+
return processed_df
|
| 446 |
+
|
| 447 |
+
except Exception as e:
|
| 448 |
+
print(f"데이터 처리 오류: {e}")
|
| 449 |
+
return df if df is not None else pd.DataFrame()
|
| 450 |
+
|
| 451 |
+
# ==================== 파스텔톤 차트 생성 시스템 ====================
|
| 452 |
+
|
| 453 |
+
class PastelChartGenerator:
|
| 454 |
+
@staticmethod
|
| 455 |
+
def create_ecosystem_optimized_charts(df: pd.DataFrame, ecosystem_filter: str = "전체") -> Tuple[Any, Any, Any, Any, str]:
|
| 456 |
+
"""생태계별 최적화된 파스텔톤 차트 생성"""
|
| 457 |
+
try:
|
| 458 |
+
if not PLOTLY_AVAILABLE or df is None or df.empty:
|
| 459 |
+
return None, None, None, None, "데이터가 없거나 Plotly를 사용할 수 없습니다."
|
| 460 |
+
|
| 461 |
+
# 데이터 필터링
|
| 462 |
+
if ecosystem_filter != "전체":
|
| 463 |
+
filtered_df = df[df['Ecosystem_Category'] == ecosystem_filter].copy()
|
| 464 |
+
filter_info = f"'{ecosystem_filter}' 생태계"
|
| 465 |
+
else:
|
| 466 |
+
filtered_df = df.copy()
|
| 467 |
+
filter_info = "전체 생태계"
|
| 468 |
+
|
| 469 |
+
if filtered_df.empty:
|
| 470 |
+
return None, None, None, None, f"{ecosystem_filter} 카테고리에 기업이 없습니다."
|
| 471 |
+
|
| 472 |
+
# 요약 정보 생성
|
| 473 |
+
total_companies = len(filtered_df)
|
| 474 |
+
s_grade = len(filtered_df[filtered_df['중요도'] == 5]) if '중요도' in filtered_df.columns else 0
|
| 475 |
+
a_grade = len(filtered_df[filtered_df['중요도'] == 4]) if '중요도' in filtered_df.columns else 0
|
| 476 |
+
countries = filtered_df['HQ_국가'].nunique() if 'HQ_국가' in filtered_df.columns else 0
|
| 477 |
+
|
| 478 |
+
# 생태계별 특화 분석
|
| 479 |
+
ecosystem_focus = ECOSYSTEM_OPTIMIZED_COLUMNS.get(ecosystem_filter, {}).get('focus', 'comprehensive analysis')
|
| 480 |
+
|
| 481 |
+
summary_text = f"""### 📊 {filter_info} 분석 요약
|
| 482 |
+
|
| 483 |
+
**총 기업 수:** {total_companies:,}개
|
| 484 |
+
**S급 최우선 기업:** {s_grade}개
|
| 485 |
+
**A급 핵심 기업:** {a_grade}개
|
| 486 |
+
**참여 국가:** {countries}개국
|
| 487 |
+
|
| 488 |
+
**분석 포커스:** {ecosystem_focus}
|
| 489 |
+
**전략적 기회:** {s_grade + a_grade}개 핵심 기업이 전체의 {((s_grade + a_grade) / total_companies * 100):.1f}%"""
|
| 490 |
+
|
| 491 |
+
# 1. 생태계별 파스텔톤 분포 차트
|
| 492 |
+
if ecosystem_filter == "전체":
|
| 493 |
+
ecosystem_counts = filtered_df['Ecosystem_Category'].value_counts()
|
| 494 |
+
ecosystem_colors = [PASTEL_COLORS['ecosystem_colors'].get(eco, PASTEL_COLORS['primary_palette'][i % 7])
|
| 495 |
+
for i, eco in enumerate(ecosystem_counts.index)]
|
| 496 |
+
|
| 497 |
+
fig1 = px.pie(
|
| 498 |
+
values=ecosystem_counts.values,
|
| 499 |
+
names=ecosystem_counts.index,
|
| 500 |
+
title=f"🏗️ 생태계별 기업 분포 (총 {total_companies}개)",
|
| 501 |
+
color_discrete_sequence=ecosystem_colors
|
| 502 |
+
)
|
| 503 |
+
fig1.update_traces(textposition='inside', textinfo='percent+label',
|
| 504 |
+
textfont=dict(size=12, color='white'))
|
| 505 |
+
else:
|
| 506 |
+
fig1 = go.Figure(data=[go.Pie(
|
| 507 |
+
labels=[ecosystem_filter],
|
| 508 |
+
values=[total_companies],
|
| 509 |
+
marker_colors=[PASTEL_COLORS['ecosystem_colors'].get(ecosystem_filter, '#E5BAFF')],
|
| 510 |
+
textinfo='value+label',
|
| 511 |
+
textfont=dict(size=14, color='white')
|
| 512 |
+
)])
|
| 513 |
+
fig1.update_layout(title=f"🏗️ {ecosystem_filter} 생태계 ({total_companies}개 기업)")
|
| 514 |
+
|
| 515 |
+
fig1.update_layout(
|
| 516 |
+
font=dict(size=14),
|
| 517 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 518 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 519 |
+
height=400
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
# 2. 중요도별 파스텔톤 분포 차트
|
| 523 |
+
if '중요도' in filtered_df.columns:
|
| 524 |
+
importance_counts = filtered_df['중요도'].value_counts().sort_index(ascending=False)
|
| 525 |
+
grade_names = {5: 'S급', 4: 'A급', 3: 'B급', 2: 'C급', 1: 'D급'}
|
| 526 |
+
|
| 527 |
+
if not importance_counts.empty:
|
| 528 |
+
x_labels = [grade_names.get(grade, f'{grade}급') for grade in importance_counts.index]
|
| 529 |
+
y_values = importance_counts.values
|
| 530 |
+
bar_colors = [PASTEL_COLORS['importance_colors'].get(grade, '#E5E5E5')
|
| 531 |
+
for grade in importance_counts.index]
|
| 532 |
+
|
| 533 |
+
fig2 = go.Figure(data=[
|
| 534 |
+
go.Bar(x=x_labels, y=y_values,
|
| 535 |
+
marker_color=bar_colors,
|
| 536 |
+
text=y_values, textposition='auto',
|
| 537 |
+
textfont=dict(size=12, color='black'))
|
| 538 |
+
])
|
| 539 |
+
fig2.update_layout(
|
| 540 |
+
title=f"⭐ {filter_info} 중요도별 분포",
|
| 541 |
+
xaxis_title="중요도 등급",
|
| 542 |
+
yaxis_title="기업 수",
|
| 543 |
+
font=dict(size=14),
|
| 544 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 545 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 546 |
+
height=400
|
| 547 |
+
)
|
| 548 |
+
else:
|
| 549 |
+
fig2 = go.Figure()
|
| 550 |
+
else:
|
| 551 |
+
fig2 = go.Figure()
|
| 552 |
+
|
| 553 |
+
# 3. 생태계별 최적화된 추가 차트
|
| 554 |
+
fig3 = PastelChartGenerator.create_ecosystem_specific_chart(filtered_df, ecosystem_filter)
|
| 555 |
+
|
| 556 |
+
# 4. 생태계별 상세 분석 차트
|
| 557 |
+
fig4 = PastelChartGenerator.create_detailed_analysis_chart(filtered_df, ecosystem_filter)
|
| 558 |
+
|
| 559 |
+
return fig1, fig2, fig3, fig4, summary_text
|
| 560 |
+
|
| 561 |
+
except Exception as e:
|
| 562 |
+
print(f"차트 생성 오류: {e}")
|
| 563 |
+
return None, None, None, None, f"차트 생성 중 오류: {str(e)}"
|
| 564 |
+
|
| 565 |
+
@staticmethod
|
| 566 |
+
def create_ecosystem_specific_chart(df: pd.DataFrame, ecosystem_filter: str) -> Any:
|
| 567 |
+
"""생태계별 특화 차트 생성"""
|
| 568 |
+
try:
|
| 569 |
+
if ecosystem_filter == "Infra":
|
| 570 |
+
# 인프라: LLM 배포 방식 분포
|
| 571 |
+
if 'LLM_Deployment' in df.columns:
|
| 572 |
+
deployment_counts = df['LLM_Deployment'].value_counts().head(8)
|
| 573 |
+
if not deployment_counts.empty:
|
| 574 |
+
fig = px.bar(
|
| 575 |
+
x=deployment_counts.index,
|
| 576 |
+
y=deployment_counts.values,
|
| 577 |
+
title="🚀 LLM 배포 방식 분포",
|
| 578 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 579 |
+
)
|
| 580 |
+
fig.update_layout(xaxis_title="배포 방식", yaxis_title="기업 수")
|
| 581 |
+
return fig
|
| 582 |
+
|
| 583 |
+
elif ecosystem_filter == "Foundation Model":
|
| 584 |
+
# 파운데이션 모���: 오픈소스 여부 분포
|
| 585 |
+
if 'OpenSource여부' in df.columns:
|
| 586 |
+
opensource_counts = df['OpenSource여부'].value_counts()
|
| 587 |
+
if not opensource_counts.empty:
|
| 588 |
+
fig = px.pie(
|
| 589 |
+
values=opensource_counts.values,
|
| 590 |
+
names=opensource_counts.index,
|
| 591 |
+
title="🔓 오픈소스 여부 분포",
|
| 592 |
+
color_discrete_sequence=PASTEL_COLORS['secondary_palette']
|
| 593 |
+
)
|
| 594 |
+
return fig
|
| 595 |
+
|
| 596 |
+
elif ecosystem_filter == "Platform / Agentic AI Pipeline":
|
| 597 |
+
# 플랫폼: 응용 도메인 분포
|
| 598 |
+
if 'Application_Domain' in df.columns:
|
| 599 |
+
domain_counts = df['Application_Domain'].value_counts().head(10)
|
| 600 |
+
if not domain_counts.empty:
|
| 601 |
+
fig = px.bar(
|
| 602 |
+
x=domain_counts.values,
|
| 603 |
+
y=domain_counts.index,
|
| 604 |
+
orientation='h',
|
| 605 |
+
title="🎯 응용 도메인별 분포",
|
| 606 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 607 |
+
)
|
| 608 |
+
fig.update_layout(xaxis_title="기업 수", yaxis_title="응용 도메인")
|
| 609 |
+
return fig
|
| 610 |
+
|
| 611 |
+
elif ecosystem_filter == "Application":
|
| 612 |
+
# 애플리케이션: 타겟 고객 분포
|
| 613 |
+
if 'TargetCustomer' in df.columns:
|
| 614 |
+
customer_counts = df['TargetCustomer'].value_counts().head(8)
|
| 615 |
+
if not customer_counts.empty:
|
| 616 |
+
fig = px.bar(
|
| 617 |
+
x=customer_counts.index,
|
| 618 |
+
y=customer_counts.values,
|
| 619 |
+
title="👥 타겟 고객별 분포",
|
| 620 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 621 |
+
)
|
| 622 |
+
fig.update_layout(xaxis_title="타겟 고객", yaxis_title="기업 수")
|
| 623 |
+
return fig
|
| 624 |
+
|
| 625 |
+
else:
|
| 626 |
+
# 전체: 국가별 분포
|
| 627 |
+
if 'HQ_국가' in df.columns:
|
| 628 |
+
country_counts = df['HQ_국가'].value_counts().head(10)
|
| 629 |
+
if not country_counts.empty:
|
| 630 |
+
fig = px.bar(
|
| 631 |
+
x=country_counts.values,
|
| 632 |
+
y=country_counts.index,
|
| 633 |
+
orientation='h',
|
| 634 |
+
title="🌍 주요 국가별 기업 수",
|
| 635 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 636 |
+
)
|
| 637 |
+
fig.update_layout(xaxis_title="기업 수", yaxis_title="국가")
|
| 638 |
+
return fig
|
| 639 |
+
|
| 640 |
+
return go.Figure()
|
| 641 |
+
|
| 642 |
+
except Exception as e:
|
| 643 |
+
print(f"생태계별 차트 생성 오류: {e}")
|
| 644 |
+
return go.Figure()
|
| 645 |
+
|
| 646 |
+
@staticmethod
|
| 647 |
+
def create_detailed_analysis_chart(df: pd.DataFrame, ecosystem_filter: str) -> Any:
|
| 648 |
+
"""상세 분석 차트 생성"""
|
| 649 |
+
try:
|
| 650 |
+
if ecosystem_filter == "Infra":
|
| 651 |
+
# 인프라: 설립연도별 분포
|
| 652 |
+
if '설립연도' in df.columns:
|
| 653 |
+
years = df['설립연도'][df['설립연도'] > 0]
|
| 654 |
+
if not years.empty:
|
| 655 |
+
fig = px.histogram(
|
| 656 |
+
x=years,
|
| 657 |
+
nbins=20,
|
| 658 |
+
title="📅 설립연도별 분포",
|
| 659 |
+
color_discrete_sequence=[PASTEL_COLORS['ecosystem_colors']['Infra']]
|
| 660 |
+
)
|
| 661 |
+
fig.update_layout(xaxis_title="설립연도", yaxis_title="기업 수")
|
| 662 |
+
return fig
|
| 663 |
+
|
| 664 |
+
elif ecosystem_filter == "Foundation Model":
|
| 665 |
+
# 파운데이션 모델: PrimaryLLM 분포
|
| 666 |
+
if 'PrimaryLLM' in df.columns:
|
| 667 |
+
llm_counts = df['PrimaryLLM'].value_counts().head(8)
|
| 668 |
+
if not llm_counts.empty:
|
| 669 |
+
fig = px.bar(
|
| 670 |
+
x=llm_counts.index,
|
| 671 |
+
y=llm_counts.values,
|
| 672 |
+
title="🤖 주요 LLM 모델 분포",
|
| 673 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 674 |
+
)
|
| 675 |
+
fig.update_layout(xaxis_title="LLM 모델", yaxis_title="기업 수")
|
| 676 |
+
return fig
|
| 677 |
+
|
| 678 |
+
elif ecosystem_filter == "Platform / Agentic AI Pipeline":
|
| 679 |
+
# 플랫폼: STT/TTS 지원 현황
|
| 680 |
+
if 'STT_TTS지원' in df.columns:
|
| 681 |
+
stt_counts = df['STT_TTS지원'].value_counts()
|
| 682 |
+
if not stt_counts.empty:
|
| 683 |
+
fig = px.pie(
|
| 684 |
+
values=stt_counts.values,
|
| 685 |
+
names=stt_counts.index,
|
| 686 |
+
title="🎤 STT/TTS 지원 현황",
|
| 687 |
+
color_discrete_sequence=PASTEL_COLORS['secondary_palette']
|
| 688 |
+
)
|
| 689 |
+
return fig
|
| 690 |
+
|
| 691 |
+
elif ecosystem_filter == "Application":
|
| 692 |
+
# 애플리케이션: 한국 지원 현황
|
| 693 |
+
if 'KoreaSupport' in df.columns:
|
| 694 |
+
korea_counts = df['KoreaSupport'].value_counts()
|
| 695 |
+
if not korea_counts.empty:
|
| 696 |
+
fig = px.pie(
|
| 697 |
+
values=korea_counts.values,
|
| 698 |
+
names=korea_counts.index,
|
| 699 |
+
title="🇰🇷 한국 시장 지원 현황",
|
| 700 |
+
color_discrete_sequence=PASTEL_COLORS['primary_palette']
|
| 701 |
+
)
|
| 702 |
+
return fig
|
| 703 |
+
|
| 704 |
+
else:
|
| 705 |
+
# 전체: 중요도와 설립연도 상관관계
|
| 706 |
+
if '중요도' in df.columns and '설립연도' in df.columns:
|
| 707 |
+
valid_data = df[(df['중요도'] > 0) & (df['설립연도'] > 0)]
|
| 708 |
+
if not valid_data.empty:
|
| 709 |
+
fig = px.scatter(
|
| 710 |
+
valid_data,
|
| 711 |
+
x='설립연도',
|
| 712 |
+
y='중요도',
|
| 713 |
+
size=[10] * len(valid_data),
|
| 714 |
+
title="📊 설립연도 vs 중요도 분포",
|
| 715 |
+
color_discrete_sequence=[PASTEL_COLORS['ecosystem_colors']['전체']]
|
| 716 |
+
)
|
| 717 |
+
fig.update_layout(xaxis_title="설립연도", yaxis_title="중요도")
|
| 718 |
+
return fig
|
| 719 |
+
|
| 720 |
+
return go.Figure()
|
| 721 |
+
|
| 722 |
+
except Exception as e:
|
| 723 |
+
print(f"상세 분석 차트 생성 오류: {e}")
|
| 724 |
+
return go.Figure()
|
| 725 |
+
|
| 726 |
+
# ==================== 기업 분석 시스템 ====================
|
| 727 |
+
|
| 728 |
+
def analyze_company_comprehensive(df: pd.DataFrame, company_name: str, email_system: GmailEmailSystem) -> Tuple[str, str, str, str]:
|
| 729 |
+
"""종합적인 기업 분석 (생태계별 최적화)"""
|
| 730 |
+
try:
|
| 731 |
+
if df is None or df.empty or not company_name:
|
| 732 |
+
return "기업을 선택해주세요.", "", "", ""
|
| 733 |
+
|
| 734 |
+
# 기업 데이터 찾기
|
| 735 |
+
company_data = df[df['기업명'] == company_name]
|
| 736 |
+
if company_data.empty:
|
| 737 |
+
return f"'{company_name}' 기업을 찾을 수 없습니다.", "", "", ""
|
| 738 |
+
|
| 739 |
+
data = company_data.iloc[0]
|
| 740 |
+
|
| 741 |
+
def safe_get(field, default='정보 없음'):
|
| 742 |
+
value = data.get(field, default)
|
| 743 |
+
if pd.isna(value) or str(value).strip() in ['', 'nan', 'NaN', 'N/A']:
|
| 744 |
+
return default
|
| 745 |
+
return str(value)
|
| 746 |
+
|
| 747 |
+
# 기업 정보 추출
|
| 748 |
+
company_name_clean = safe_get('기업명')
|
| 749 |
+
ecosystem_cat = safe_get('Ecosystem_Category')
|
| 750 |
+
importance = safe_get('중요도', 0)
|
| 751 |
+
importance_grade = safe_get('중요도등급', '미분류')
|
| 752 |
+
email_address = safe_get('이메일')
|
| 753 |
+
country = safe_get('HQ_국가')
|
| 754 |
+
website = safe_get('웹사이트')
|
| 755 |
+
|
| 756 |
+
# 생태계별 최적화된 분석
|
| 757 |
+
optimized_info = ECOSYSTEM_OPTIMIZED_COLUMNS.get(ecosystem_cat, {})
|
| 758 |
+
primary_columns = optimized_info.get('primary', [])
|
| 759 |
+
focus_area = optimized_info.get('focus', 'comprehensive business analysis')
|
| 760 |
+
|
| 761 |
+
# 액션 추천
|
| 762 |
+
action_recommendations = {
|
| 763 |
+
5: "🚀 **최우선 협력 추진** - S급 전략적 파트너로 즉시 파트너십 개시",
|
| 764 |
+
4: "🎯 **핵심 파트너십 개발** - A급 중요 기업으로 상세 협력 방안 수립",
|
| 765 |
+
3: "🤝 **전술적 협력 검토** - B급 유망 파트너로 단계적 접근",
|
| 766 |
+
2: "👀 **시장 동향 모니터링** - C급 관심 기업으로 지속 관찰",
|
| 767 |
+
1: "📋 **정보 수집 및 분석** - D급 참고 기업으로 기본 정보 축적"
|
| 768 |
+
}
|
| 769 |
+
|
| 770 |
+
try:
|
| 771 |
+
importance_num = float(importance)
|
| 772 |
+
action_recommendation = action_recommendations.get(int(importance_num), "📋 **기본 정보 수집**")
|
| 773 |
+
except:
|
| 774 |
+
action_recommendation = "📋 **기본 정보 수집**"
|
| 775 |
+
|
| 776 |
+
# 생태계별 상세 분석 생성
|
| 777 |
+
ecosystem_analysis = generate_ecosystem_specific_analysis(data, ecosystem_cat, primary_columns)
|
| 778 |
+
|
| 779 |
+
# 최종 분석 결과
|
| 780 |
+
analysis = f"""# 🏢 {company_name_clean} 종합 분석
|
| 781 |
+
|
| 782 |
+
## ✅ 생태계별 최적화 분석 완료!
|
| 783 |
+
|
| 784 |
+
## ⭐ 전략적 평가
|
| 785 |
+
- **전략적 중요도:** {importance}/5점 ({importance_grade})
|
| 786 |
+
- **생태계 분류:** {ecosystem_cat}
|
| 787 |
+
- **분석 포커스:** {focus_area}
|
| 788 |
+
- **권장 액션:** {action_recommendation}
|
| 789 |
+
|
| 790 |
+
---
|
| 791 |
+
|
| 792 |
+
## 📍 기업 기본 정보
|
| 793 |
+
- **본사 위치:** {country} - {safe_get('HQ_도시')}
|
| 794 |
+
- **설립 연도:** {safe_get('설립연도')}
|
| 795 |
+
- **창업자:** {safe_get('창업자')}
|
| 796 |
+
- **주요 투자자:** {safe_get('주요투자자')}
|
| 797 |
+
|
| 798 |
+
## 🎯 기업 개요
|
| 799 |
+
{safe_get('기업개요')}
|
| 800 |
+
|
| 801 |
+
{ecosystem_analysis}
|
| 802 |
+
|
| 803 |
+
## 🔗 연락처 및 웹 정보
|
| 804 |
+
- **공식 웹사이트:** {website}
|
| 805 |
+
- **문의 페이지:** {safe_get('문의URL')}
|
| 806 |
+
- **이메일:** {email_address}
|
| 807 |
+
- **전화번호:** {safe_get('전화번호')}
|
| 808 |
+
|
| 809 |
+
---
|
| 810 |
+
|
| 811 |
+
## 💡 맞춤형 파트너십 전략
|
| 812 |
+
|
| 813 |
+
{generate_partnership_strategy(ecosystem_cat, int(importance) if str(importance).isdigit() else 3)}
|
| 814 |
+
|
| 815 |
+
---
|
| 816 |
+
|
| 817 |
+
**✨ 이메일 탭으로 이동하면 자동 생성된 맞춤형 파트너십 이메일을 확인할 수 있습니다!**
|
| 818 |
+
**📧 '실제 이메일 발송' 버튼으로 jeongkee10@gmission.com에 이메일을 발송할 수 있습니다.**"""
|
| 819 |
+
|
| 820 |
+
# 이메일 자동 생성
|
| 821 |
+
if email_address != '정보 없음' and '@' in email_address:
|
| 822 |
+
subject, body, recipient = email_system.create_partnership_email(
|
| 823 |
+
company_name_clean, email_address, ecosystem_cat, int(importance) if str(importance).isdigit() else 3, country, website
|
| 824 |
+
)
|
| 825 |
+
return analysis, subject, body, recipient
|
| 826 |
+
else:
|
| 827 |
+
return analysis, "", "", ""
|
| 828 |
+
|
| 829 |
+
except Exception as e:
|
| 830 |
+
error_msg = f"기업 분석 오류: {str(e)}"
|
| 831 |
+
print(error_msg)
|
| 832 |
+
return error_msg, "", "", ""
|
| 833 |
+
|
| 834 |
+
def generate_ecosystem_specific_analysis(data, ecosystem_cat: str, primary_columns: List[str]) -> str:
|
| 835 |
+
"""생태계별 특화 분석 생성"""
|
| 836 |
+
def safe_get(field, default='정보 없음'):
|
| 837 |
+
value = data.get(field, default)
|
| 838 |
+
if pd.isna(value) or str(value).strip() in ['', 'nan', 'NaN', 'N/A']:
|
| 839 |
+
return default
|
| 840 |
+
return str(value)
|
| 841 |
+
|
| 842 |
+
if ecosystem_cat == "Infra":
|
| 843 |
+
return f"""
|
| 844 |
+
## 🏗️ 인프라 생태계 전문 분석
|
| 845 |
+
- **주요 LLM:** {safe_get('PrimaryLLM')}
|
| 846 |
+
- **배포 방식:** {safe_get('LLM_Deployment')}
|
| 847 |
+
- **주요 제품/서비스:** {safe_get('주요제품')}
|
| 848 |
+
- **파트너십 프로그램:** {safe_get('PartnershipProgram')}
|
| 849 |
+
|
| 850 |
+
**인프라 특화 협력 방안:**
|
| 851 |
+
• GPU/클라우드 리소스 공동 활용 및 최적화
|
| 852 |
+
• AI 워크로드 배포 및 스케일링 기술 협력
|
| 853 |
+
• 한국 데이터센터 및 엣지 컴퓨팅 구축
|
| 854 |
+
• 고성능 AI 인프라 공동 개발"""
|
| 855 |
+
|
| 856 |
+
elif ecosystem_cat == "Foundation Model":
|
| 857 |
+
return f"""
|
| 858 |
+
## 🤖 파운데이션 모델 생태계 전문 분석
|
| 859 |
+
- **주요 LLM:** {safe_get('PrimaryLLM')}
|
| 860 |
+
- **오픈소스 여부:** {safe_get('OpenSource여부')}
|
| 861 |
+
- **배포 방식:** {safe_get('LLM_Deployment')}
|
| 862 |
+
- **타겟 고객:** {safe_get('TargetCustomer')}
|
| 863 |
+
|
| 864 |
+
**파운데이션 모델 특화 협력 방안:**
|
| 865 |
+
• 한국어 특화 모델 공동 개발 및 Fine-tuning
|
| 866 |
+
• 모델 라이센싱 및 기술 이전 협력
|
| 867 |
+
• 멀티모달 AI 모델 공동 연구
|
| 868 |
+
• 오픈소스 생태계 기여 및 협력"""
|
| 869 |
+
|
| 870 |
+
elif ecosystem_cat == "Platform / Agentic AI Pipeline":
|
| 871 |
+
return f"""
|
| 872 |
+
## 🔗 플랫폼/에이전틱 AI 파이프라인 전문 분석
|
| 873 |
+
- **응용 도메인:** {safe_get('Application_Domain')}
|
| 874 |
+
- **STT/TTS 지원:** {safe_get('STT_TTS지원')}
|
| 875 |
+
- **타겟 고객:** {safe_get('TargetCustomer')}
|
| 876 |
+
- **가격 정책:** {safe_get('PricingModel')}
|
| 877 |
+
|
| 878 |
+
**플랫폼 특화 협력 방안:**
|
| 879 |
+
• API 연동 및 상호 운용성 확보
|
| 880 |
+
• 에이전틱 워크플로우 자동화 플랫폼 구축
|
| 881 |
+
• 엔터프라이즈 AI 오케스트레이션 솔루션
|
| 882 |
+
• 통합 플랫폼 생태계 공동 개발"""
|
| 883 |
+
|
| 884 |
+
elif ecosystem_cat == "Application":
|
| 885 |
+
return f"""
|
| 886 |
+
## 📱 애플리케이션 생태계 전문 분석
|
| 887 |
+
- **응용 도메인:** {safe_get('Application_Domain')}
|
| 888 |
+
- **타겟 고객:** {safe_get('TargetCustomer')}
|
| 889 |
+
- **가격 정책:** {safe_get('PricingModel')}
|
| 890 |
+
- **한국 지원:** {safe_get('KoreaSupport')}
|
| 891 |
+
|
| 892 |
+
**애플리케이션 특화 협력 방안:**
|
| 893 |
+
• 산업별 맞춤형 AI 솔루션 공동 개발
|
| 894 |
+
• 한국 시장 진출 및 현지화 협력
|
| 895 |
+
• B2B/B2C 고객 사례 공유 및 공동 마케팅
|
| 896 |
+
• 실제 비즈니스 적용 사례 구축"""
|
| 897 |
+
|
| 898 |
+
else:
|
| 899 |
+
return f"""
|
| 900 |
+
## 🔍 종합 기술 분석
|
| 901 |
+
- **주요 기술:** {safe_get('주요제품')}
|
| 902 |
+
- **응용 분야:** {safe_get('Application_Domain')}
|
| 903 |
+
- **시장 접근:** {safe_get('TargetCustomer')}
|
| 904 |
+
|
| 905 |
+
**종합 협력 방안:**
|
| 906 |
+
• 기술 통합 및 공동 개발
|
| 907 |
+
• 시장 확장 전략 협력
|
| 908 |
+
• 혁신 생태계 구축"""
|
| 909 |
+
|
| 910 |
+
def generate_partnership_strategy(ecosystem_cat: str, importance: int) -> str:
|
| 911 |
+
"""파트너십 전략 생성"""
|
| 912 |
+
strategies = {
|
| 913 |
+
"Infra": """
|
| 914 |
+
**인프라 파트너십 전략:**
|
| 915 |
+
1. **기술 협력**: 클라우드 인프라 공동 구축 및 최적화
|
| 916 |
+
2. **시장 진출**: 한국/APAC 데이터센터 확장 협력
|
| 917 |
+
3. **투자 협력**: 인프라 투자 펀드 공동 조성
|
| 918 |
+
4. **표준화**: AI 인프라 표준 개발 공동 참여""",
|
| 919 |
+
|
| 920 |
+
"Foundation Model": """
|
| 921 |
+
**파운데이션 모델 파트너십 전략:**
|
| 922 |
+
1. **기술 협력**: 한국어 LLM 공동 개발 및 최적화
|
| 923 |
+
2. **라이센싱**: 모델 기술 이전 및 라이센싱 협력
|
| 924 |
+
3. **연구 협력**: 공동 연구소 설립 및 인재 교류
|
| 925 |
+
4. **생태계**: 오��소스 기여 및 커뮤니티 구축""",
|
| 926 |
+
|
| 927 |
+
"Platform / Agentic AI Pipeline": """
|
| 928 |
+
**플랫폼 파트너십 전략:**
|
| 929 |
+
1. **통합 협력**: API 상호 연동 및 플랫폼 통합
|
| 930 |
+
2. **솔루션**: 엔터프라이즈 AI 솔루션 공동 개발
|
| 931 |
+
3. **마케팅**: 공동 고객 확보 및 시장 개척
|
| 932 |
+
4. **표준화**: 에이전틱 AI 표준 및 프로토콜 개발""",
|
| 933 |
+
|
| 934 |
+
"Application": """
|
| 935 |
+
**애플리케이션 파트너십 전략:**
|
| 936 |
+
1. **현지화**: 한국 시장 맞춤형 애플리케이션 개발
|
| 937 |
+
2. **고객 확보**: 공동 세일즈 및 마케팅 협력
|
| 938 |
+
3. **사례 구축**: 성공 사례 공동 개발 및 홍보
|
| 939 |
+
4. **투자**: 후속 투자 및 사업 확장 협력"""
|
| 940 |
+
}
|
| 941 |
+
|
| 942 |
+
base_strategy = strategies.get(ecosystem_cat, "**종합 파트너십 전략:** 기술, 시장, 투자 전방위 협력")
|
| 943 |
+
|
| 944 |
+
if importance >= 4:
|
| 945 |
+
priority = "\n\n**🚀 최우선 추진 권장:** 즉시 파트너십 개시 및 전담팀 구성"
|
| 946 |
+
elif importance >= 3:
|
| 947 |
+
priority = "\n\n**🎯 중점 검토 권장:** 단계별 협력 방안 수립 및 파일럿 프로젝트 진행"
|
| 948 |
+
else:
|
| 949 |
+
priority = "\n\n**👀 지속 모니터링:** 시장 동향 관찰 및 기회 발굴"
|
| 950 |
+
|
| 951 |
+
return base_strategy + priority
|
| 952 |
+
|
| 953 |
+
# ==================== 파일 처리 ====================
|
| 954 |
+
|
| 955 |
+
def process_uploaded_file(file):
|
| 956 |
+
"""파일 업로드 처리"""
|
| 957 |
+
if file is None:
|
| 958 |
+
return None, "파일을 업로드해주세요."
|
| 959 |
+
|
| 960 |
+
try:
|
| 961 |
+
file_path = file.name if hasattr(file, 'name') else str(file)
|
| 962 |
+
|
| 963 |
+
if file_path.endswith('.csv'):
|
| 964 |
+
df = pd.read_csv(file_path, encoding='utf-8-sig')
|
| 965 |
+
elif file_path.endswith(('.xlsx', '.xls')):
|
| 966 |
+
df = pd.read_excel(file_path)
|
| 967 |
+
else:
|
| 968 |
+
return None, "CSV 또는 Excel 파일을 업로드해주세요."
|
| 969 |
+
|
| 970 |
+
if df.empty:
|
| 971 |
+
return None, "빈 파일입니다."
|
| 972 |
+
|
| 973 |
+
processed_df = OptimizedDataProcessor.process_data(df)
|
| 974 |
+
success_msg = f"✅ 파일 처리 완료!\n- 총 기업 수: {len(processed_df):,}개\n- 컬럼 수: {len(processed_df.columns)}개"
|
| 975 |
+
|
| 976 |
+
# 메모리 최적화
|
| 977 |
+
del df
|
| 978 |
+
gc.collect()
|
| 979 |
+
|
| 980 |
+
return processed_df, success_msg
|
| 981 |
+
|
| 982 |
+
except Exception as e:
|
| 983 |
+
return None, f"파일 처리 오류: {str(e)}"
|
| 984 |
+
|
| 985 |
+
# ==================== 메인 대시보드 ====================
|
| 986 |
+
|
| 987 |
+
def create_huggingface_dashboard():
|
| 988 |
+
"""허깅페이스 배포용 메인 대시보드"""
|
| 989 |
+
|
| 990 |
+
# Gmail 이메일 시스템 초기화
|
| 991 |
+
email_system = GmailEmailSystem()
|
| 992 |
+
|
| 993 |
+
# 커스텀 CSS
|
| 994 |
+
custom_css = """
|
| 995 |
+
.gradio-container {
|
| 996 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
|
| 997 |
+
max-width: 1400px !important;
|
| 998 |
+
margin: 0 auto !important;
|
| 999 |
+
}
|
| 1000 |
+
.ecosystem-card {
|
| 1001 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 1002 |
+
border-radius: 10px;
|
| 1003 |
+
padding: 20px;
|
| 1004 |
+
margin: 10px;
|
| 1005 |
+
color: white;
|
| 1006 |
+
}
|
| 1007 |
+
.pastel-btn {
|
| 1008 |
+
background: linear-gradient(135deg, #FFB3BA 0%, #FFDFBA 100%);
|
| 1009 |
+
border: none;
|
| 1010 |
+
border-radius: 8px;
|
| 1011 |
+
color: #333;
|
| 1012 |
+
font-weight: bold;
|
| 1013 |
+
}
|
| 1014 |
+
"""
|
| 1015 |
+
|
| 1016 |
+
with gr.Blocks(
|
| 1017 |
+
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="cyan"),
|
| 1018 |
+
title="G-Mission Agentic AI Partner Database",
|
| 1019 |
+
css=custom_css
|
| 1020 |
+
) as demo:
|
| 1021 |
+
|
| 1022 |
+
# 헤더
|
| 1023 |
+
gr.Markdown("""
|
| 1024 |
+
# 🚀 G-Mission Agentic AI 글로벌 파트너 DB 분석 대시보드
|
| 1025 |
+
|
| 1026 |
+
### ✅ 허깅페이스 배포 완료 | ✅ Gmail SMTP 이메일 | ✅ 파스텔톤 차트 | ✅ 생태계별 최적화
|
| 1027 |
+
|
| 1028 |
+
**155개 글로벌 Agentic AI 기업 완벽 분석** | **실시간 파트너십 이메일 발송**
|
| 1029 |
+
|
| 1030 |
+
**주요 기능:**
|
| 1031 |
+
- 🎨 **파스텔톤 시각화**: 생태계별 맞춤 색상으로 직관적 분석
|
| 1032 |
+
- 🏗️ **생태계별 최적화**: Infra, Foundation Model, Platform, Application 특화 분석
|
| 1033 |
+
- 📧 **Gmail SMTP 연동**: jeongkee10@gmission.com으로 실시간 이메일 발송
|
| 1034 |
+
- 🎯 **맞춤형 파트너십**: 기업별/생태계별 최적화된 협력 제안
|
| 1035 |
+
""")
|
| 1036 |
+
|
| 1037 |
+
# 상태 변수
|
| 1038 |
+
df_state = gr.State(value=None)
|
| 1039 |
+
|
| 1040 |
+
# 파일 업로드
|
| 1041 |
+
with gr.Row():
|
| 1042 |
+
with gr.Column():
|
| 1043 |
+
file_upload = gr.File(
|
| 1044 |
+
label="📁 Agentic AI 기업 데이터베이스 업로드",
|
| 1045 |
+
file_types=['.csv', '.xlsx', '.xls'],
|
| 1046 |
+
file_count="single"
|
| 1047 |
+
)
|
| 1048 |
+
upload_status = gr.Markdown("📋 **현재 상태:** 파일을 업로드해주세요.")
|
| 1049 |
+
|
| 1050 |
+
# 메인 탭
|
| 1051 |
+
with gr.Tabs():
|
| 1052 |
+
# 탭 1: 파스텔톤 시장 분석
|
| 1053 |
+
with gr.TabItem("🎨 파스텔톤 시장 분석"):
|
| 1054 |
+
with gr.Row():
|
| 1055 |
+
with gr.Column(scale=1):
|
| 1056 |
+
market_ecosystem_filter = gr.Dropdown(
|
| 1057 |
+
choices=ECOSYSTEM_CATEGORIES,
|
| 1058 |
+
value="전체",
|
| 1059 |
+
label="🏗️ 생태계 필터",
|
| 1060 |
+
info="특정 생태계 선택 시 해당 영역에 최적화된 분석 제공"
|
| 1061 |
+
)
|
| 1062 |
+
market_analysis_btn = gr.Button(
|
| 1063 |
+
"🎨 파스텔톤 분석 실행",
|
| 1064 |
+
variant="primary",
|
| 1065 |
+
size="lg"
|
| 1066 |
+
)
|
| 1067 |
+
|
| 1068 |
+
market_summary = gr.Markdown(
|
| 1069 |
+
"생태계를 선택하고 분석을 실행하면 맞춤형 요약이 표시됩니다.",
|
| 1070 |
+
label="📊 분석 요약"
|
| 1071 |
+
)
|
| 1072 |
+
|
| 1073 |
+
with gr.Row():
|
| 1074 |
+
with gr.Column():
|
| 1075 |
+
chart1 = gr.Plot(label="🏗️ 생태계별 분포")
|
| 1076 |
+
with gr.Column():
|
| 1077 |
+
chart2 = gr.Plot(label="⭐ 중요도별 분포")
|
| 1078 |
+
|
| 1079 |
+
with gr.Row():
|
| 1080 |
+
with gr.Column():
|
| 1081 |
+
chart3 = gr.Plot(label="🎯 생태계별 특화 분석")
|
| 1082 |
+
with gr.Column():
|
| 1083 |
+
chart4 = gr.Plot(label="📊 상세 분석")
|
| 1084 |
+
|
| 1085 |
+
# 탭 2: 생태계별 최적화 기업 분석
|
| 1086 |
+
with gr.TabItem("🏢 생태계별 최적화 기업 분석"):
|
| 1087 |
+
with gr.Row():
|
| 1088 |
+
with gr.Column(scale=1):
|
| 1089 |
+
ecosystem_filter_company = gr.Dropdown(
|
| 1090 |
+
choices=ECOSYSTEM_CATEGORIES,
|
| 1091 |
+
value="전체",
|
| 1092 |
+
label="🏗️ 생태계별 필터링",
|
| 1093 |
+
info="각 생태계에 최적화된 컬럼과 분석 제공"
|
| 1094 |
+
)
|
| 1095 |
+
|
| 1096 |
+
company_dropdown = gr.Dropdown(
|
| 1097 |
+
choices=[],
|
| 1098 |
+
label="🏢 기업 선택",
|
| 1099 |
+
info="생태계별 필터링 후 기업 선택"
|
| 1100 |
+
)
|
| 1101 |
+
|
| 1102 |
+
analyze_company_btn = gr.Button(
|
| 1103 |
+
"🔍 생태계별 최적화 분석 실행",
|
| 1104 |
+
variant="primary",
|
| 1105 |
+
size="lg"
|
| 1106 |
+
)
|
| 1107 |
+
|
| 1108 |
+
# 생태계별 정보 표시
|
| 1109 |
+
ecosystem_info = gr.Markdown(
|
| 1110 |
+
"""### 🏗️ 생태계별 최적화 정보
|
| 1111 |
+
|
| 1112 |
+
**Infra**: LLM 배포, 인프라 기술, 파트너십 프로그램
|
| 1113 |
+
**Foundation Model**: LLM 모델, 오픈소스, 기술 이전
|
| 1114 |
+
**Platform**: 응용도메인, STT/TTS, 타겟고객, 가격정책
|
| 1115 |
+
**Application**: 타겟고객, 가격정책, 한국지원, 애플리케이션""",
|
| 1116 |
+
label="생태계별 특화 정보"
|
| 1117 |
+
)
|
| 1118 |
+
|
| 1119 |
+
with gr.Column(scale=2):
|
| 1120 |
+
company_analysis = gr.Markdown(
|
| 1121 |
+
"""# 🏢 생태계별 최적화 기업 분석
|
| 1122 |
+
|
| 1123 |
+
생태계를 선택하고 기업을 선택한 후 분석을 실행하면:
|
| 1124 |
+
|
| 1125 |
+
**🎯 생태계별 특화 분석:**
|
| 1126 |
+
- **Infra**: 인프라 기술, 배포 방식, 파트너십 중심 분석
|
| 1127 |
+
- **Foundation Model**: LLM 모델, 오픈소스, 기술력 중심 분석
|
| 1128 |
+
- **Platform**: 플랫폼 통합, API, 에이전틱 파이프라인 중심 분석
|
| 1129 |
+
- **Application**: 실제 응용, 시장 접근, 고객 중심 분석
|
| 1130 |
+
|
| 1131 |
+
**✨ 각 생태계에 최적화된 맞춤형 파트너십 전략이 제공됩니다!**""",
|
| 1132 |
+
label="기업 상세 분석"
|
| 1133 |
+
)
|
| 1134 |
+
|
| 1135 |
+
# 탭 3: Gmail SMTP 이메일 시스템
|
| 1136 |
+
with gr.TabItem("📧 Gmail SMTP 이메일"):
|
| 1137 |
+
with gr.Row():
|
| 1138 |
+
with gr.Column():
|
| 1139 |
+
gr.Markdown("""## 📧 Gmail SMTP 이메일 시스템
|
| 1140 |
+
|
| 1141 |
+
**설정 상태:**
|
| 1142 |
+
- 📧 Gmail 계정: 허깅페이스 Secrets에서 로드
|
| 1143 |
+
- 🎯 수신 이메일: jeongkee10@gmission.com (고정)
|
| 1144 |
+
- 🔐 보안: Gmail 앱 비밀번호 사용
|
| 1145 |
+
|
| 1146 |
+
**사용 방법:**
|
| 1147 |
+
1. 기업 분석 실행 → 이메일 자동 생성
|
| 1148 |
+
2. 아래에서 이메일 내용 확인/수정
|
| 1149 |
+
3. '실제 이메일 발송' 버튼 클릭
|
| 1150 |
+
4. jeongkee10@gmission.com으로 이메일 발송됨""")
|
| 1151 |
+
|
| 1152 |
+
# 이메일 필드들
|
| 1153 |
+
email_recipient = gr.Textbox(
|
| 1154 |
+
label="📧 대상 기업 이메일 (참조용)",
|
| 1155 |
+
placeholder="기업 분석 후 자동 입력됩니다",
|
| 1156 |
+
interactive=True
|
| 1157 |
+
)
|
| 1158 |
+
|
| 1159 |
+
email_subject = gr.Textbox(
|
| 1160 |
+
label="📧 이메일 제목 (수정 가능)",
|
| 1161 |
+
placeholder="기업 분석 후 자동 생성됩니다",
|
| 1162 |
+
interactive=True
|
| 1163 |
+
)
|
| 1164 |
+
|
| 1165 |
+
email_body = gr.Textbox(
|
| 1166 |
+
label="📧 이메일 내용 (수정 가능)",
|
| 1167 |
+
lines=15,
|
| 1168 |
+
placeholder="기업 분석 후 생태계별 맞춤형 이메일이 자동 생성됩니다",
|
| 1169 |
+
interactive=True
|
| 1170 |
+
)
|
| 1171 |
+
|
| 1172 |
+
# 실제 이메일 발송 버튼
|
| 1173 |
+
real_email_send_btn = gr.Button(
|
| 1174 |
+
"📧 실제 Gmail로 이메일 발송 (→ jeongkee10@gmission.com)",
|
| 1175 |
+
variant="primary",
|
| 1176 |
+
size="lg"
|
| 1177 |
+
)
|
| 1178 |
+
|
| 1179 |
+
email_send_status = gr.Markdown(
|
| 1180 |
+
"""### 📧 이메일 발송 안내
|
| 1181 |
+
|
| 1182 |
+
기업 분석을 실행하면 해당 기업에 맞는 맞춤형 파트너십 이메일이 자동 생성됩니다.
|
| 1183 |
+
내용을 확인/수정한 후 '실제 Gmail로 이메일 발송' 버튼을 클릭하면
|
| 1184 |
+
**jeongkee10@gmission.com**으로 이메일이 발송됩니다.
|
| 1185 |
+
|
| 1186 |
+
**발송되는 이메일에는:**
|
| 1187 |
+
- 원본 대상 기업 정보
|
| 1188 |
+
- 파트너십 제안 내용
|
| 1189 |
+
- 다음 단계 가이드
|
| 1190 |
+
- 대시보드 링크가 포함됩니다.""",
|
| 1191 |
+
label="발송 상태"
|
| 1192 |
+
)
|
| 1193 |
+
|
| 1194 |
+
# 탭 4: 이메일 발송 현황
|
| 1195 |
+
with gr.TabItem("📧 이메일 발송 현황"):
|
| 1196 |
+
with gr.Column():
|
| 1197 |
+
refresh_email_btn = gr.Button(
|
| 1198 |
+
"🔄 발송 현황 새로고침",
|
| 1199 |
+
variant="primary"
|
| 1200 |
+
)
|
| 1201 |
+
|
| 1202 |
+
email_history_display = gr.Markdown(
|
| 1203 |
+
email_system.get_email_history_text(),
|
| 1204 |
+
label="📧 이메일 발송 이력"
|
| 1205 |
+
)
|
| 1206 |
+
|
| 1207 |
+
# ==================== 이벤트 핸들러 ====================
|
| 1208 |
+
|
| 1209 |
+
# 1. 파일 업로드
|
| 1210 |
+
def handle_file_upload(file):
|
| 1211 |
+
try:
|
| 1212 |
+
df, status = process_uploaded_file(file)
|
| 1213 |
+
if df is not None:
|
| 1214 |
+
company_names = sorted(df['기업명'].unique().tolist())
|
| 1215 |
+
return (
|
| 1216 |
+
df,
|
| 1217 |
+
status,
|
| 1218 |
+
gr.update(choices=company_names, value=None)
|
| 1219 |
+
)
|
| 1220 |
+
else:
|
| 1221 |
+
return None, status, gr.update(choices=[], value=None)
|
| 1222 |
+
except Exception as e:
|
| 1223 |
+
return None, f"파일 업로드 오류: {str(e)}", gr.update(choices=[], value=None)
|
| 1224 |
+
|
| 1225 |
+
file_upload.upload(
|
| 1226 |
+
fn=handle_file_upload,
|
| 1227 |
+
inputs=[file_upload],
|
| 1228 |
+
outputs=[df_state, upload_status, company_dropdown]
|
| 1229 |
+
)
|
| 1230 |
+
|
| 1231 |
+
# 2. 파스텔톤 시장 분석
|
| 1232 |
+
def handle_market_analysis(df, ecosystem_filter):
|
| 1233 |
+
try:
|
| 1234 |
+
if df is None or df.empty:
|
| 1235 |
+
return None, None, None, None, "데이터 파일을 업로드해주세요."
|
| 1236 |
+
|
| 1237 |
+
return PastelChartGenerator.create_ecosystem_optimized_charts(df, ecosystem_filter)
|
| 1238 |
+
except Exception as e:
|
| 1239 |
+
return None, None, None, None, f"시장 분석 오류: {str(e)}"
|
| 1240 |
+
|
| 1241 |
+
market_analysis_btn.click(
|
| 1242 |
+
fn=handle_market_analysis,
|
| 1243 |
+
inputs=[df_state, market_ecosystem_filter],
|
| 1244 |
+
outputs=[chart1, chart2, chart3, chart4, market_summary]
|
| 1245 |
+
)
|
| 1246 |
+
|
| 1247 |
+
# 3. 생태계별 기업 필터링
|
| 1248 |
+
def update_company_dropdown(df, ecosystem_filter):
|
| 1249 |
+
try:
|
| 1250 |
+
if df is None or df.empty:
|
| 1251 |
+
return gr.update(choices=[])
|
| 1252 |
+
|
| 1253 |
+
if ecosystem_filter != "전체":
|
| 1254 |
+
filtered_df = df[df['Ecosystem_Category'] == ecosystem_filter]
|
| 1255 |
+
else:
|
| 1256 |
+
filtered_df = df
|
| 1257 |
+
|
| 1258 |
+
company_names = sorted(filtered_df['기업명'].unique().tolist())
|
| 1259 |
+
return gr.update(choices=company_names, value=None)
|
| 1260 |
+
except Exception as e:
|
| 1261 |
+
return gr.update(choices=[])
|
| 1262 |
+
|
| 1263 |
+
ecosystem_filter_company.change(
|
| 1264 |
+
fn=update_company_dropdown,
|
| 1265 |
+
inputs=[df_state, ecosystem_filter_company],
|
| 1266 |
+
outputs=[company_dropdown]
|
| 1267 |
+
)
|
| 1268 |
+
|
| 1269 |
+
# 4. 🔥 핵심: 생태계별 최적화 기업 분석 + 이메일 자동 생성
|
| 1270 |
+
analyze_company_btn.click(
|
| 1271 |
+
fn=lambda df, company: analyze_company_comprehensive(df, company, email_system),
|
| 1272 |
+
inputs=[df_state, company_dropdown],
|
| 1273 |
+
outputs=[
|
| 1274 |
+
company_analysis, # 기업 분석 결과
|
| 1275 |
+
email_subject, # 이메일 제목 자동 입력
|
| 1276 |
+
email_body, # 이메일 내용 자동 입력
|
| 1277 |
+
email_recipient # 받는 사람 자동 입력
|
| 1278 |
+
]
|
| 1279 |
+
)
|
| 1280 |
+
|
| 1281 |
+
# 5. 실제 Gmail 이메일 발송
|
| 1282 |
+
def handle_real_email_send(recipient, subject, body):
|
| 1283 |
+
try:
|
| 1284 |
+
if not recipient or '@' not in recipient:
|
| 1285 |
+
return "올바른 대상 기업 이메일 주소가 필요합니다."
|
| 1286 |
+
|
| 1287 |
+
if not subject or not body:
|
| 1288 |
+
return "이메일 제목과 내용을 입력해주세요."
|
| 1289 |
+
|
| 1290 |
+
# 회사명 추출
|
| 1291 |
+
company_name = recipient.split('@')[0].capitalize()
|
| 1292 |
+
|
| 1293 |
+
# 실제 Gmail 발송
|
| 1294 |
+
success, message = email_system.send_email(subject, body, company_name, recipient)
|
| 1295 |
+
|
| 1296 |
+
return message
|
| 1297 |
+
|
| 1298 |
+
except Exception as e:
|
| 1299 |
+
return f"이메일 발송 처리 오류: {str(e)}"
|
| 1300 |
+
|
| 1301 |
+
real_email_send_btn.click(
|
| 1302 |
+
fn=handle_real_email_send,
|
| 1303 |
+
inputs=[email_recipient, email_subject, email_body],
|
| 1304 |
+
outputs=[email_send_status]
|
| 1305 |
+
)
|
| 1306 |
+
|
| 1307 |
+
# 6. 이메일 현황 새로고침
|
| 1308 |
+
refresh_email_btn.click(
|
| 1309 |
+
fn=lambda: email_system.get_email_history_text(),
|
| 1310 |
+
outputs=[email_history_display]
|
| 1311 |
+
)
|
| 1312 |
+
|
| 1313 |
+
return demo
|
| 1314 |
+
|
| 1315 |
+
# ==================== 메인 실행 ====================
|
| 1316 |
+
|
| 1317 |
+
def main():
|
| 1318 |
+
"""메인 실행 함수"""
|
| 1319 |
+
print("🚀 G-Mission 허깅페이스 배포용 Agentic AI 대시보드 시작!")
|
| 1320 |
+
print("✅ Gmail SMTP 이메일 시스템")
|
| 1321 |
+
print("✅ 파스텔톤 차트 색상")
|
| 1322 |
+
print("✅ 생태계별 최적화")
|
| 1323 |
+
print("✅ jeongkee10@gmission.com 수신")
|
| 1324 |
+
|
| 1325 |
+
try:
|
| 1326 |
+
demo = create_huggingface_dashboard()
|
| 1327 |
+
|
| 1328 |
+
# 허깅페이스 배포용 launch 설정
|
| 1329 |
+
demo.launch(
|
| 1330 |
+
server_name="0.0.0.0",
|
| 1331 |
+
server_port=7860,
|
| 1332 |
+
debug=False,
|
| 1333 |
+
share=False
|
| 1334 |
+
)
|
| 1335 |
+
|
| 1336 |
+
print("✅ 허깅페이스 배포용 대시보드 실행 성공!")
|
| 1337 |
+
|
| 1338 |
+
except Exception as e:
|
| 1339 |
+
print(f"❌ 대시보드 실행 오류: {e}")
|
| 1340 |
+
traceback.print_exc()
|
| 1341 |
+
|
| 1342 |
+
if __name__ == "__main__":
|
| 1343 |
+
main()
|