DEWgor commited on
Commit
90756b2
·
verified ·
1 Parent(s): a940c29

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +180 -163
app.py CHANGED
@@ -1,163 +1,180 @@
1
- import os
2
- import logging
3
- from flask import Flask, render_template
4
- from config import Config
5
- from extensions import db, login_manager, csrf
6
- from models import User, KpiDefinition
7
-
8
- logging.basicConfig(level=logging.INFO)
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- def create_app():
13
- app = Flask(__name__)
14
- app.config.from_object(Config)
15
-
16
- os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
17
-
18
- db.init_app(app)
19
- login_manager.init_app(app)
20
- csrf.init_app(app)
21
-
22
- login_manager.login_view = 'auth.login'
23
- login_manager.login_message = 'Пожалуйста, войдите в систему.'
24
- login_manager.login_message_category = 'info'
25
-
26
- from routes.auth import auth_bp
27
- from routes.main import main_bp
28
- from routes.admin import admin_bp
29
- from routes.api import api_bp
30
-
31
- app.register_blueprint(auth_bp)
32
- app.register_blueprint(main_bp)
33
- app.register_blueprint(admin_bp)
34
- app.register_blueprint(api_bp)
35
-
36
- @login_manager.user_loader
37
- def load_user(user_id):
38
- return User.query.get(int(user_id))
39
-
40
- @app.errorhandler(404)
41
- def not_found(e):
42
- return render_template('errors/404.html'), 404
43
-
44
- @app.errorhandler(403)
45
- def forbidden(e):
46
- return render_template('errors/403.html'), 403
47
-
48
- @app.errorhandler(500)
49
- def server_error(e):
50
- return render_template('errors/500.html'), 500
51
-
52
- @app.context_processor
53
- def utility_processor():
54
- from models import Notification
55
- unread_count = 0
56
- try:
57
- from flask_login import current_user
58
- if current_user.is_authenticated:
59
- unread_count = Notification.query.filter_by(
60
- user_id=current_user.id, is_read=False
61
- ).count()
62
- except Exception:
63
- pass
64
- return dict(unread_notifications_count=unread_count)
65
-
66
- with app.app_context():
67
- init_database()
68
-
69
- from services.scheduler import init_scheduler
70
- init_scheduler(app)
71
-
72
- @app.cli.command('sync-facts')
73
- def sync_facts_command():
74
- from services.platform_fetcher import fetch_all_facts
75
- result = fetch_all_facts()
76
- saved = result.get('values_saved', 0)
77
- errors = result.get('errors', [])
78
- print(f'Sync complete: {saved} values saved, {len(errors)} errors')
79
- for err in errors:
80
- print(f' ERROR: {err}')
81
-
82
- return app
83
-
84
-
85
- def init_database():
86
- db.create_all()
87
-
88
- # Ensure egor (SM) exists
89
- sm = User.query.filter_by(username='egor').first()
90
- if not sm:
91
- sm = User(
92
- username='egor', full_name='Егор', role='sm',
93
- grade='SM', position='руководитель', is_active=True
94
- )
95
- sm.set_password('egor')
96
- db.session.add(sm)
97
- db.session.commit()
98
- logger.info('SM created: egor / egor')
99
-
100
- # Ensure polina (guest viewer) exists
101
- polina = User.query.filter_by(username='polina').first()
102
- if not polina:
103
- polina = User(
104
- username='polina', full_name='Полина', role='guest',
105
- is_active=True, email='polina@kpi.local'
106
- )
107
- polina.set_password('polina')
108
- db.session.add(polina)
109
- db.session.commit()
110
- logger.info('Guest viewer created: polina / polina')
111
-
112
- # Migration: add training file columns for existing databases
113
- try:
114
- import sqlalchemy as sa
115
- insp = sa.inspect(db.engine)
116
- cols = [c['name'] for c in insp.get_columns('trainings')]
117
- if 'original_name' not in cols:
118
- db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN original_name VARCHAR(300)'))
119
- if 'file_size' not in cols:
120
- db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN file_size INTEGER DEFAULT 0'))
121
- if 'uploaded_by' not in cols:
122
- db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN uploaded_by INTEGER REFERENCES users(id)'))
123
- if 'upload_date' not in cols:
124
- db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN upload_date DATETIME'))
125
- db.session.commit()
126
- except Exception:
127
- pass
128
-
129
- db.session.commit()
130
-
131
- demo_kpis = [
132
- ('Объем продаж', 'Сумма продаж за период', 'руб.', 'Продажи', 1000000),
133
- ('Выполнение плана', 'Процент выполнения плана продаж', '%', 'Продажи', 100),
134
- ('Средний чек', 'Средняя сумма покупки', 'руб.', 'Продажи', 5000),
135
- ('Кол-во клиентов', 'Количество обслуженных клиентов', 'шт.', 'Сервис', 100),
136
- ('Индекс лояльности NPS', 'Готовность рекомендовать бутик', '%', 'Сервис', 80),
137
- ('Возвраты', 'Процент возвратов товара', '%', 'Сервис', 5),
138
- ('Обучение', 'Пройдено обучений за период', 'шт.', 'Развитие', 3),
139
- ('Скорость обслуживания', 'Среднее время на клиента', 'мин', 'Сервис', 15),
140
- ('Доп. продажи', 'Количество дополнительных продаж', 'шт.', 'Продажи', 30),
141
- ('Выполнение KPI', 'Общий процент выполнения KPI', '%', 'Итог', 90),
142
- ('Работа с возражениями', 'Успешное закрытие возражений', '%', 'Сервис', 70),
143
- ('Знание продукта', 'Оценка знания ассортимента', 'балл', 'Развитие', 85),
144
- ]
145
-
146
- for name, desc, unit, category, target in demo_kpis:
147
- existing = KpiDefinition.query.filter_by(name=name).first()
148
- if not existing:
149
- kpi = KpiDefinition(
150
- name=name, description=desc, unit=unit,
151
- category=category, target_value=target, is_active=True
152
- )
153
- db.session.add(kpi)
154
-
155
- db.session.commit()
156
-
157
-
158
- app = create_app()
159
-
160
- if __name__ == '__main__':
161
- port = int(os.environ.get('PORT', 5000))
162
- debug = os.environ.get('FLASK_ENV', 'production') == 'development'
163
- app.run(host='0.0.0.0', port=port, debug=debug)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from flask import Flask, render_template
4
+ from config import Config
5
+ from extensions import db, login_manager, csrf
6
+ from models import User, KpiDefinition
7
+
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def create_app():
13
+ app = Flask(__name__)
14
+ app.config.from_object(Config)
15
+
16
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
17
+
18
+ db.init_app(app)
19
+ login_manager.init_app(app)
20
+ csrf.init_app(app)
21
+
22
+ login_manager.login_view = 'auth.login'
23
+ login_manager.login_message = 'Пожалуйста, войдите в систему.'
24
+ login_manager.login_message_category = 'info'
25
+
26
+ from routes.auth import auth_bp
27
+ from routes.main import main_bp
28
+ from routes.admin import admin_bp
29
+ from routes.api import api_bp
30
+
31
+ app.register_blueprint(auth_bp)
32
+ app.register_blueprint(main_bp)
33
+ app.register_blueprint(admin_bp)
34
+ app.register_blueprint(api_bp)
35
+
36
+ @login_manager.user_loader
37
+ def load_user(user_id):
38
+ return User.query.get(int(user_id))
39
+
40
+ @app.errorhandler(404)
41
+ def not_found(e):
42
+ return render_template('errors/404.html'), 404
43
+
44
+ @app.errorhandler(403)
45
+ def forbidden(e):
46
+ return render_template('errors/403.html'), 403
47
+
48
+ @app.errorhandler(500)
49
+ def server_error(e):
50
+ return render_template('errors/500.html'), 500
51
+
52
+ @app.context_processor
53
+ def utility_processor():
54
+ from models import Notification
55
+ unread_count = 0
56
+ try:
57
+ from flask_login import current_user
58
+ if current_user.is_authenticated:
59
+ unread_count = Notification.query.filter_by(
60
+ user_id=current_user.id, is_read=False
61
+ ).count()
62
+ except Exception:
63
+ pass
64
+ return dict(unread_notifications_count=unread_count)
65
+
66
+ with app.app_context():
67
+ init_database()
68
+
69
+ from services.scheduler import init_scheduler
70
+ init_scheduler(app)
71
+
72
+ @app.cli.command('sync-facts')
73
+ def sync_facts_command():
74
+ from services.platform_fetcher import fetch_all_facts
75
+ result = fetch_all_facts()
76
+ saved = result.get('values_saved', 0)
77
+ errors = result.get('errors', [])
78
+ print(f'Sync complete: {saved} values saved, {len(errors)} errors')
79
+ for err in errors:
80
+ print(f' ERROR: {err}')
81
+
82
+ return app
83
+
84
+
85
+ def init_database():
86
+ db.create_all()
87
+
88
+ # Restore from seed if running in cloud and /data/ is fresh
89
+ seed_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'seed', 'kpi_dashboard.db')
90
+ if Config._cloud and os.path.exists(seed_path):
91
+ db_uri = Config.SQLALCHEMY_DATABASE_URI
92
+ if db_uri.startswith('sqlite:///'):
93
+ db_file = db_uri.replace('sqlite:///', '', 1)
94
+ if not os.path.exists(db_file):
95
+ os.makedirs(os.path.dirname(db_file), exist_ok=True)
96
+ import shutil
97
+ shutil.copy2(seed_path, db_file)
98
+ logger.info('Restored seed database to %s', db_file)
99
+ # Reconnect with new DB
100
+ db.session.remove()
101
+ db.engine.dispose()
102
+
103
+ db.create_all()
104
+
105
+ # Ensure egor (SM) exists
106
+ sm = User.query.filter_by(username='egor').first()
107
+ if not sm:
108
+ sm = User(
109
+ username='egor', full_name='Егор', role='sm',
110
+ grade='SM', position='руководитель', is_active=True
111
+ )
112
+ sm.set_password('egor')
113
+ db.session.add(sm)
114
+ db.session.commit()
115
+ logger.info('SM created: egor / egor')
116
+
117
+ # Ensure polina (guest viewer) exists
118
+ polina = User.query.filter_by(username='polina').first()
119
+ if not polina:
120
+ polina = User(
121
+ username='polina', full_name='Полина', role='guest',
122
+ is_active=True, email='polina@kpi.local'
123
+ )
124
+ polina.set_password('polina')
125
+ db.session.add(polina)
126
+ db.session.commit()
127
+ logger.info('Guest viewer created: polina / polina')
128
+
129
+ # Migration: add training file columns for existing databases
130
+ try:
131
+ import sqlalchemy as sa
132
+ insp = sa.inspect(db.engine)
133
+ cols = [c['name'] for c in insp.get_columns('trainings')]
134
+ if 'original_name' not in cols:
135
+ db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN original_name VARCHAR(300)'))
136
+ if 'file_size' not in cols:
137
+ db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN file_size INTEGER DEFAULT 0'))
138
+ if 'uploaded_by' not in cols:
139
+ db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN uploaded_by INTEGER REFERENCES users(id)'))
140
+ if 'upload_date' not in cols:
141
+ db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN upload_date DATETIME'))
142
+ db.session.commit()
143
+ except Exception:
144
+ pass
145
+
146
+ db.session.commit()
147
+
148
+ demo_kpis = [
149
+ ('Объем продаж', 'Сумма продаж за период', 'руб.', 'Продажи', 1000000),
150
+ ('Выполнение плана', 'Процент выполнения плана продаж', '%', 'Продажи', 100),
151
+ ('Средний чек', 'Средняя сумма покупки', 'руб.', 'Продажи', 5000),
152
+ ('Кол-во клиентов', 'Количество обслуженных клиентов', 'шт.', 'Сервис', 100),
153
+ ('Индекс лояльности NPS', 'Готовность рекомендовать бутик', '%', 'Сервис', 80),
154
+ ('Возвраты', 'Процент возвратов товара', '%', 'Сервис', 5),
155
+ ('Обучение', 'Пройдено обучений за период', 'шт.', 'Развитие', 3),
156
+ ('Скорость обслуживания', 'Среднее время на клиента', 'мин', 'Сервис', 15),
157
+ ('Доп. продажи', 'Количество дополнительных продаж', 'шт.', 'Продажи', 30),
158
+ ('Выполнение KPI', 'Общий процент выполнения KPI', '%', 'Итог', 90),
159
+ ('Работа с возражениями', 'Успешное закрытие возражений', '%', 'Сервис', 70),
160
+ ('Знание продукта', 'Оценка знания ассортимента', 'балл', 'Развитие', 85),
161
+ ]
162
+
163
+ for name, desc, unit, category, target in demo_kpis:
164
+ existing = KpiDefinition.query.filter_by(name=name).first()
165
+ if not existing:
166
+ kpi = KpiDefinition(
167
+ name=name, description=desc, unit=unit,
168
+ category=category, target_value=target, is_active=True
169
+ )
170
+ db.session.add(kpi)
171
+
172
+ db.session.commit()
173
+
174
+
175
+ app = create_app()
176
+
177
+ if __name__ == '__main__':
178
+ port = int(os.environ.get('PORT', 5000))
179
+ debug = os.environ.get('FLASK_ENV', 'production') == 'development'
180
+ app.run(host='0.0.0.0', port=port, debug=debug)