gosgia 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gosgia-0.1.0.dist-info/METADATA +15 -0
- gosgia-0.1.0.dist-info/RECORD +15 -0
- gosgia-0.1.0.dist-info/WHEEL +5 -0
- gosgia-0.1.0.dist-info/entry_points.txt +2 -0
- gosgia-0.1.0.dist-info/top_level.txt +1 -0
- solutions/__init__.py +25 -0
- solutions/zadanie_1.py +46 -0
- solutions/zadanie_17.py +41 -0
- solutions/zadanie_18.py +179 -0
- solutions/zadanie_21.py +176 -0
- solutions/zadanie_24.py +43 -0
- solutions/zadanie_3.py +57 -0
- solutions/zadanie_5.py +76 -0
- solutions/zadanie_8.py +56 -0
- solutions/zadanie_9.py +53 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gosgia
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Коллекция решений учебных заданий по программированию
|
|
5
|
+
Author-email: Student <student@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/yourusername/gosgia
|
|
8
|
+
Keywords: решения,учебные,примеры,код
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Topic :: Education
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
solutions/__init__.py,sha256=dhnuaitRxLtCgtioMxZnWai97hBYVnsn_OTAJTpLfEA,539
|
|
2
|
+
solutions/zadanie_1.py,sha256=CWqfrfA_jcDfns0JLXK8Zw3UrsEm9aZC2Bp206B3KaE,3122
|
|
3
|
+
solutions/zadanie_17.py,sha256=QBmR63yUtLi4vTu8Qj62yGR11RiAN9Y8SZfldCMdIzE,2441
|
|
4
|
+
solutions/zadanie_18.py,sha256=Rf-TNc6jtOT9cpUucelrfH5bvuyoYTCZ2BrzZVSvP4U,8895
|
|
5
|
+
solutions/zadanie_21.py,sha256=vbwt-3mS7jx3UI6kA2PUDJ3hHRO8NfZEOnOE0h4BWoE,9777
|
|
6
|
+
solutions/zadanie_24.py,sha256=ze_xeU0V2llAxQWPMe4Uhtt2t9HWJzARmeZfeAZBqJ4,3293
|
|
7
|
+
solutions/zadanie_3.py,sha256=fE96dmLgkjNgztEHMxYoaPvCA7VUASvOn5Lfqh_rbmw,3861
|
|
8
|
+
solutions/zadanie_5.py,sha256=znuWyR85_NiYe4ga38dvGs6XpRXueU5egGGjK6_gwb8,3364
|
|
9
|
+
solutions/zadanie_8.py,sha256=OEFIOwnWU7S83TcoL9sVZOEC_VGMWfYyj2BwuDjnT3A,2943
|
|
10
|
+
solutions/zadanie_9.py,sha256=r-v9U3XvaUrB1JvPPzbsrevQe78Og49-uNER6S6XzqI,3702
|
|
11
|
+
gosgia-0.1.0.dist-info/METADATA,sha256=JbUoCvSQARFzcLTFRlzZLlrs303CcWpSC_B6RyJi1G4,647
|
|
12
|
+
gosgia-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
gosgia-0.1.0.dist-info/entry_points.txt,sha256=DeTyY4FlNspkXnc-WDTzu3BmxhZ5uQ3iXqt2ty52SV8,44
|
|
14
|
+
gosgia-0.1.0.dist-info/top_level.txt,sha256=uAC7WbgG54gX48Rq6nnJu4C_HO9xypSO737MtlZ3Thk,10
|
|
15
|
+
gosgia-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
solutions
|
solutions/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Решения практических заданий
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .zadanie_1 import zadanie_1
|
|
6
|
+
from .zadanie_3 import zadanie_3
|
|
7
|
+
from .zadanie_5 import zadanie_5
|
|
8
|
+
from .zadanie_8 import zadanie_8
|
|
9
|
+
from .zadanie_9 import zadanie_9
|
|
10
|
+
from .zadanie_17 import zadanie_17
|
|
11
|
+
from .zadanie_18 import zadanie_18
|
|
12
|
+
from .zadanie_21 import zadanie_21
|
|
13
|
+
from .zadanie_24 import zadanie_24
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'zadanie_1',
|
|
17
|
+
'zadanie_3',
|
|
18
|
+
'zadanie_5',
|
|
19
|
+
'zadanie_8',
|
|
20
|
+
'zadanie_9',
|
|
21
|
+
'zadanie_17',
|
|
22
|
+
'zadanie_18',
|
|
23
|
+
'zadanie_21',
|
|
24
|
+
'zadanie_24'
|
|
25
|
+
]
|
solutions/zadanie_1.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Исходный список словарей с информацией о студентах
|
|
2
|
+
def zadanie_1():
|
|
3
|
+
students = [
|
|
4
|
+
{"name": "Анна", "age": 20, "grade": 4.5, "group": "ИВТ-101"},
|
|
5
|
+
{"name": "Иван", "age": 22, "grade": 3.8, "group": "ИВТ-102"},
|
|
6
|
+
{"name": "Мария", "age": 21, "grade": 4.9, "group": "ИВТ-101"},
|
|
7
|
+
{"name": "Петр", "age": 23, "grade": 3.5, "group": "ИВТ-103"},
|
|
8
|
+
{"name": "Ольга", "age": 20, "grade": 4.2, "group": "ИВТ-102"}
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# 1. Сортировка студентов по убыванию среднего балла
|
|
13
|
+
# Используем функцию sorted() с параметром key, который задаёт критерий сортировки
|
|
14
|
+
# lambda x: x['grade'] — извлекает значение поля 'grade' из каждого словаря (элемента списка)
|
|
15
|
+
# reverse=True — сортировка в порядке убывания (от большего к меньшему)
|
|
16
|
+
sorted_students = sorted(students, key=lambda x: x['grade'], reverse=True)
|
|
17
|
+
|
|
18
|
+
# Выводим отсортированный список для проверки
|
|
19
|
+
print("Студенты, отсортированные по убыванию среднего балла:")
|
|
20
|
+
for student in sorted_students:
|
|
21
|
+
print(f"Имя: {student['name']}, Средний балл: {student['grade']}, Группа: {student['group']}")
|
|
22
|
+
|
|
23
|
+
# 2. Вычисление среднего балла по каждой группе
|
|
24
|
+
# Создаём пустой словарь для хранения сумм баллов и количества студентов в каждой группе
|
|
25
|
+
group_stats = {}
|
|
26
|
+
|
|
27
|
+
# Проходим по каждому студенту в исходном списке
|
|
28
|
+
for student in students:
|
|
29
|
+
group = student['group'] # Получаем название группы текущего студента
|
|
30
|
+
grade = student['grade'] # Получаем средний балл текущего студента
|
|
31
|
+
|
|
32
|
+
# Если группа ещё не добавлена в словарь, инициализируем её
|
|
33
|
+
if group not in group_stats:
|
|
34
|
+
group_stats[group] = {'total_grade': 0, 'count': 0}
|
|
35
|
+
|
|
36
|
+
# Добавляем балл студента к общей сумме для его группы
|
|
37
|
+
group_stats[group]['total_grade'] += grade
|
|
38
|
+
# Увеличиваем счётчик студентов в группе на 1
|
|
39
|
+
group_stats[group]['count'] += 1
|
|
40
|
+
|
|
41
|
+
# Теперь вычисляем средний балл для каждой группы
|
|
42
|
+
print("\nСредний балл по группам:")
|
|
43
|
+
for group, stats in group_stats.items():
|
|
44
|
+
# Средний балл = сумма баллов / количество студентов
|
|
45
|
+
average_grade = stats['total_grade'] / stats['count']
|
|
46
|
+
print(f"Группа {group}: средний балл {average_grade:.2f}")
|
solutions/zadanie_17.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
def zadanie_17():
|
|
2
|
+
'''SELECT
|
|
3
|
+
Преподаватели.Табельный_номер,
|
|
4
|
+
Преподаватели.ФИО,
|
|
5
|
+
Должности.Название AS Должность,
|
|
6
|
+
Nz(Учёные_степени.Название, "Нет") AS Учёная_степень,
|
|
7
|
+
Nz(Учёные_звания.Название, "Нет") AS Учёное_звание,
|
|
8
|
+
Преподаватели.Ставка,
|
|
9
|
+
|
|
10
|
+
-- Базовый оклад: 27000 * коэффициент нагрузки
|
|
11
|
+
27000 * Должности.Коэффициент_нагрузки AS Базовый_оклад,
|
|
12
|
+
|
|
13
|
+
-- Надбавка за степень (если есть)
|
|
14
|
+
Nz(Учёные_степени.Надбавка, 0) AS Надбавка_за_степень,
|
|
15
|
+
|
|
16
|
+
-- Надбавка за звание (если есть)
|
|
17
|
+
Nz(Учёные_звания.Надбавка, 0) AS Надбавка_за_звание,
|
|
18
|
+
|
|
19
|
+
-- Сумма до применения ставки и коэффициентов
|
|
20
|
+
(27000 * Должности.Коэффициент_нагрузки +
|
|
21
|
+
Nz(Учёные_степени.Надбавка, 0) +
|
|
22
|
+
Nz(Учёные_звания.Надбавка, 0)) AS Сумма_до_коэффициентов,
|
|
23
|
+
|
|
24
|
+
-- Итоговая зарплата:
|
|
25
|
+
-- (Сумма_до_коэффициентов * Ставка) * Уральский_коэфф (1.15) * (1 - НДФЛ 0.13)
|
|
26
|
+
ROUND(
|
|
27
|
+
(27000 * Должности.Коэффициент_нагрузки +
|
|
28
|
+
Nz(Учёные_степени.Надбавка, 0) +
|
|
29
|
+
Nz(Учёные_звания.Надбавка, 0)) *
|
|
30
|
+
Преподаватели.Ставка * 1.15 * 0.87,
|
|
31
|
+
2
|
|
32
|
+
) AS Итоговая_зарплата_руб
|
|
33
|
+
|
|
34
|
+
FROM
|
|
35
|
+
(Преподаватели
|
|
36
|
+
INNER JOIN Должности ON Преподаватели.ID_должности = Должности.ID_должности)
|
|
37
|
+
LEFT JOIN Учёные_степени ON Преподаватели.ID_степени = Учёные_степени.ID_степени
|
|
38
|
+
LEFT JOIN Учёные_звания ON Преподаватели.ID_звания = Учёные_звания.ID_звания
|
|
39
|
+
|
|
40
|
+
ORDER BY
|
|
41
|
+
Преподаватели.Табельный_номер;'''
|
solutions/zadanie_18.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
def zadanie_18():
|
|
2
|
+
import sqlite3
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Задание 18: расчёт заработной платы преподавателей в SQLite
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Алгоритм:
|
|
10
|
+
1. Создаём БД и таблицы для хранения данных о преподавателях, должностях и надбавках.
|
|
11
|
+
2. Заполняем справочники (должности, степени, звания, коэффициенты).
|
|
12
|
+
3. Добавляем данные о преподавателях.
|
|
13
|
+
4. Пишем SQL‑запрос для расчёта зарплаты по заданной формуле.
|
|
14
|
+
5. Выполняем запрос и выводим результаты.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Удаляем базу, если уже существует (для чистоты эксперимента)
|
|
18
|
+
if os.path.exists("university_payroll.db"):
|
|
19
|
+
os.remove("university_payroll.db")
|
|
20
|
+
|
|
21
|
+
# Создаём подключение к БД
|
|
22
|
+
conn = sqlite3.connect("university_payroll.db")
|
|
23
|
+
cursor = conn.cursor()
|
|
24
|
+
|
|
25
|
+
print("1. Создание таблиц базы данных...\n")
|
|
26
|
+
|
|
27
|
+
# Таблица должностей с коэффициентами часовой нагрузки
|
|
28
|
+
cursor.execute("""")
|
|
29
|
+
CREATE TABLE IF NOT EXISTS positions (
|
|
30
|
+
position_id INTEGER PRIMARY KEY,
|
|
31
|
+
position_name TEXT NOT NULL UNIQUE, -- название должности
|
|
32
|
+
load_coefficient REAL NOT NULL -- коэффициент нагрузки (относительно доцента)
|
|
33
|
+
);
|
|
34
|
+
""")
|
|
35
|
+
|
|
36
|
+
# Таблица учёных степеней и их надбавок
|
|
37
|
+
cursor.execute("""")
|
|
38
|
+
CREATE TABLE IF NOT EXISTS degrees (
|
|
39
|
+
degree_id INTEGER PRIMARY KEY,
|
|
40
|
+
degree_name TEXT NOT NULL UNIQUE, -- название степени
|
|
41
|
+
bonus_amount REAL NOT NULL -- надбавка за степень (руб. на ставку)
|
|
42
|
+
);
|
|
43
|
+
""")
|
|
44
|
+
|
|
45
|
+
# Таблица учёных званий и их надбавок
|
|
46
|
+
cursor.execute("""")
|
|
47
|
+
CREATE TABLE IF NOT EXISTS titles (
|
|
48
|
+
title_id INTEGER PRIMARY KEY,
|
|
49
|
+
title_name TEXT NOT NULL UNIQUE, -- название звания
|
|
50
|
+
bonus_amount REAL NOT NULL -- надбавка за звание (руб. на ставку)
|
|
51
|
+
);
|
|
52
|
+
""")
|
|
53
|
+
|
|
54
|
+
# Основная таблица преподавателей
|
|
55
|
+
cursor.execute("""")
|
|
56
|
+
CREATE TABLE IF NOT EXISTS teachers (
|
|
57
|
+
teacher_id INTEGER PRIMARY KEY, -- табельный номер
|
|
58
|
+
full_name TEXT NOT NULL, -- ФИО
|
|
59
|
+
position_id INTEGER NOT NULL, -- ссылка на должность
|
|
60
|
+
degree_id INTEGER, -- ссылка на учёную степень (может быть NULL)
|
|
61
|
+
title_id INTEGER, -- ссылка на учёное звание (может быть NULL)
|
|
62
|
+
work_rate REAL NOT NULL, -- ставка (0.5, 1.0, 1.5)
|
|
63
|
+
FOREIGN KEY (position_id) REFERENCES positions (position_id),
|
|
64
|
+
FOREIGN KEY (degree_id) REFERENCES degrees (degree_id),
|
|
65
|
+
FOREIGN KEY (title_id) REFERENCES titles (title_id)
|
|
66
|
+
);
|
|
67
|
+
""")
|
|
68
|
+
|
|
69
|
+
print("Таблицы созданы.\n")
|
|
70
|
+
|
|
71
|
+
print("2. Заполнение справочников...\n")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Заполняем должности (коэффициенты относительно доцента)
|
|
75
|
+
cursor.executemany("INSERT OR REPLACE INTO positions (position_id, position_name, load_coefficient) VALUES (?, ?, ?)", [
|
|
76
|
+
(1, "Старший преподаватель", 0.9),
|
|
77
|
+
(2, "Доцент", 1.0),
|
|
78
|
+
(3, "Профессор", 1.1)
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
# Заполняем учёные степени и надбавки
|
|
82
|
+
cursor.executemany("INSERT OR REPLACE INTO degrees (degree_id, degree_name, bonus_amount) VALUES (?, ?, ?)", [
|
|
83
|
+
(1, "Кандидат наук", 3000.0),
|
|
84
|
+
(2, "Доктор наук", 6000.0)
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
# Заполняем учёные звания и надбавки
|
|
88
|
+
cursor.executemany("INSERT OR REPLACE INTO titles (title_id, title_name, bonus_amount) VALUES (?, ?, ?)", [
|
|
89
|
+
(1, "Доцент", 1000.0),
|
|
90
|
+
(2, "Профессор", 2500.0)
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
print("Справочники заполнены.\n")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
print("3. Добавление данных о преподавателях...\n")
|
|
97
|
+
|
|
98
|
+
# Добавляем нескольких преподавателей для примера
|
|
99
|
+
cursor.executemany("INSERT INTO teachers (teacher_id, full_name, position_id, degree_id, title_id, work_rate) VALUES (?, ?, ?, ?, ?, ?)", [
|
|
100
|
+
(101, "Иванов А.С.", 2, 1, 1, 1.0), # Доцент, канд. наук, доцент, 1 ставка
|
|
101
|
+
(102, "Петрова М.И.", 3, 2, 2, 1.5), # Профессор, д-р наук, профессор, 1.5 ставки
|
|
102
|
+
(103, "Сидоров В.П.", 1, None, None, 0.5), # Ст. преподаватель, без степени и звания, 0.5 ставки
|
|
103
|
+
(104, "Кузнецова Е.А.", 2, None, 1, 1.0) # Доцент, без степени, доцент, 1 ставка
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
print("Данные о преподавателях добавлены.\n")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
print("4. Расчёт заработной платы...\n")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Формула расчёта:
|
|
113
|
+
# З/П = (Оклад_доцента * Коэфф_нагрузки + Надбавка_за_степень + Надбавка_за_звание) *
|
|
114
|
+
# * Количество_ставок * Уральский_коэфф * (1 - НДФЛ)
|
|
115
|
+
#
|
|
116
|
+
# Принимаем:
|
|
117
|
+
# - Оклад доцента на 1 ставку = 27 000 руб.
|
|
118
|
+
# - Уральский коэффициент = 1.15 (15 %)
|
|
119
|
+
# - НДФЛ = 13 % → множитель (1 – 0.13) = 0.87
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
query = """
|
|
123
|
+
SELECT
|
|
124
|
+
t.teacher_id AS "Табельный номер",
|
|
125
|
+
t.full_name AS "ФИО",
|
|
126
|
+
p.position_name AS "Должность",
|
|
127
|
+
COALESCE(d.degree_name, 'Нет') AS "Учёная степень",
|
|
128
|
+
COALESCE(tl.title_name, 'Нет') AS "Учёное звание",
|
|
129
|
+
t.work_rate AS "Ставка",
|
|
130
|
+
-- Расчёт базовой части (оклад доцента × коэффициент нагрузки)
|
|
131
|
+
ROUND(27000 * p.load_coefficient, 2) AS "Базовый оклад (руб.)",
|
|
132
|
+
-- Надбавка за степень (если есть)
|
|
133
|
+
COALESCE(d.bonus_amount, 0) AS "Надбавка за степень (руб.)",
|
|
134
|
+
-- Надбавка за звание (если есть)
|
|
135
|
+
COALESCE(tl.bonus_amount, 0) AS "Надбавка за звание (руб.)",
|
|
136
|
+
-- Сумма до применения ставок и коэффициентов
|
|
137
|
+
ROUND(
|
|
138
|
+
27000 * p.load_coefficient +
|
|
139
|
+
COALESCE(d.bonus_amount, 0) +
|
|
140
|
+
COALESCE(tl.bonus_amount, 0),
|
|
141
|
+
2
|
|
142
|
+
) AS "Сумма до ставок (руб.)",
|
|
143
|
+
-- Итоговая зарплата с учётом ставки, уральского коэфф. и НДФЛ
|
|
144
|
+
ROUND(
|
|
145
|
+
(27000 * p.load_coefficient +
|
|
146
|
+
COALESCE(d.bonus_amount, 0) +
|
|
147
|
+
COALESCE(tl.bonus_amount, 0)) *
|
|
148
|
+
t.work_rate * 1.15 * 0.87,
|
|
149
|
+
2
|
|
150
|
+
) AS "Итоговая зарплата (руб.)"
|
|
151
|
+
FROM teachers t
|
|
152
|
+
JOIN positions p ON t.position_id = p.position_id
|
|
153
|
+
LEFT JOIN degrees d ON t.degree_id = d.degree_id
|
|
154
|
+
LEFT JOIN titles tl ON t.title_id = tl.title_id
|
|
155
|
+
ORDER BY t.teacher_id;
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# Выполняем запрос
|
|
159
|
+
cursor.execute(query)
|
|
160
|
+
results = cursor.fetchall()
|
|
161
|
+
|
|
162
|
+
# Выводим результаты
|
|
163
|
+
print("Результаты расчёта заработной платы:\n")
|
|
164
|
+
for row in results:
|
|
165
|
+
print(f"Табельный номер: {row[0]}")
|
|
166
|
+
print(f"ФИО: {row[1]}")
|
|
167
|
+
print(f"Должность: {row[2]}")
|
|
168
|
+
print(f"Учёная степень: {row[3]}")
|
|
169
|
+
print(f"Учёное звание: {row[4]}")
|
|
170
|
+
print(f"Ставка: {row[5]}")
|
|
171
|
+
print(f"Базовый оклад: {row[6]} руб.")
|
|
172
|
+
print(f"Надбавка за степень: {row[7]} руб.")
|
|
173
|
+
print(f"Надбавка за звание: {row[8]} руб.")
|
|
174
|
+
print(f"Сумма до ставок: {row[9]} руб.")
|
|
175
|
+
print(f"Итоговая зарплата: {row[10]} руб.")
|
|
176
|
+
|
|
177
|
+
print("\n5. Закрытие соединения с БД...")
|
|
178
|
+
conn.close()
|
|
179
|
+
print("Работа завершена.")
|
solutions/zadanie_21.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
def zadanie_21():
|
|
2
|
+
"""
|
|
3
|
+
Инфологическая модель БД для учёта дополнительной работы преподавателей
|
|
4
|
+
(для расчёта надбавок к окладу)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Цель: спроектировать структуру данных, позволяющую:
|
|
8
|
+
- фиксировать виды дополнительной работы;
|
|
9
|
+
- учитывать объёмы выполненной работы по каждому преподавателю;
|
|
10
|
+
- хранить нормативы оплаты;
|
|
11
|
+
- готовить данные для расчёта надбавок.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Импорт необходимых модулей (для демонстрации структуры)
|
|
15
|
+
from datetime import date
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
1. Сущность «Преподаватель» (Teacher)
|
|
21
|
+
Хранит основную информацию о сотруднике.
|
|
22
|
+
"""
|
|
23
|
+
class Teacher:
|
|
24
|
+
def __init__(self, teacher_id: int, full_name: str, position: str, base_salary: float):
|
|
25
|
+
self.teacher_id = teacher_id # PK: уникальный идентификатор преподавателя
|
|
26
|
+
self.full_name = full_name # ФИО
|
|
27
|
+
self.position = position # Должность (профессор, доцент, ст. преподаватель и т.п.)
|
|
28
|
+
self.base_salary = base_salary # Основной оклад (руб.)
|
|
29
|
+
# Дополнительные атрибуты по необходимости (кафедра, учёная степень и др.)
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
2. Сущность «Вид дополнительной работы» (ExtraWorkType)
|
|
33
|
+
Описывает типы работ, за которые положена надбавка.
|
|
34
|
+
"""
|
|
35
|
+
class ExtraWorkType:
|
|
36
|
+
def __init__(self, work_type_id: int, name: str, unit: str, rate_per_unit: float, is_active: bool = True):
|
|
37
|
+
self.work_type_id = work_type_id # PK: идентификатор типа работы
|
|
38
|
+
self.name = name # Название работы (например, «Руководство ВКР», «Организация олимпиады»)
|
|
39
|
+
self.unit = unit # Единица измерения объёма (часы, штуки, мероприятия)
|
|
40
|
+
self.rate_per_unit = rate_per_unit # Ставка за единицу (руб.)
|
|
41
|
+
self.is_active = is_active # Активен ли тип работы (для архивирования устаревших)
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
3. Сущность «Период учёта» (ReportingPeriod)
|
|
45
|
+
Определяет временной интервал, за который фиксируется работа.
|
|
46
|
+
"""
|
|
47
|
+
class ReportingPeriod:
|
|
48
|
+
def __init__(self, period_id: int, start_date: date, end_date: date, name: str):
|
|
49
|
+
self.period_id = period_id # PK: идентификатор периода
|
|
50
|
+
self.start_date = start_date # Начало периода
|
|
51
|
+
self.end_date = end_date # Конец периода
|
|
52
|
+
self.name = name # Наименование (например, «Семестр 1 2025/2026»)
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
4. Сущность «Запись о выполненной работе» (WorkRecord)
|
|
56
|
+
Фиксирует факт выполнения работы преподавателем.
|
|
57
|
+
"""
|
|
58
|
+
class WorkRecord:
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
record_id: int,
|
|
62
|
+
teacher_id: int, # FK: ссылка на преподавателя
|
|
63
|
+
work_type_id: int, # FK: ссылка на тип работы
|
|
64
|
+
period_id: int, # FK: ссылка на период учёта
|
|
65
|
+
volume: float, # Объём выполненной работы (в единицах из ExtraWorkType)
|
|
66
|
+
confirmation_doc: str, # Номер и дата подтверждающего документа
|
|
67
|
+
confirmation_date: date, # Дата подтверждения объёма работы
|
|
68
|
+
approved: bool = False # Утверждена ли запись (для согласования)
|
|
69
|
+
):
|
|
70
|
+
self.record_id = record_id
|
|
71
|
+
self.teacher_id = teacher_id
|
|
72
|
+
self.work_type_id = work_type_id
|
|
73
|
+
self.period_id = period_id
|
|
74
|
+
self.volume = volume
|
|
75
|
+
self.confirmation_doc = confirmation_doc
|
|
76
|
+
self.confirmation_date = confirmation_date
|
|
77
|
+
self.approved = approved
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
5. Сущность «Расчёт надбавки» (BonusCalculation)
|
|
81
|
+
Хранит итоговые данные по надбавкам за период.
|
|
82
|
+
(Может формироваться автоматически на основе WorkRecord.)
|
|
83
|
+
"""
|
|
84
|
+
class BonusCalculation:
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
calculation_id: int,
|
|
88
|
+
teacher_id: int, # FK: преподаватель
|
|
89
|
+
period_id: int, # FK: период учёта
|
|
90
|
+
total_bonus: float, # Итоговая сумма надбавки (руб.)
|
|
91
|
+
calculation_date: date, # Дата расчёта
|
|
92
|
+
notes: Optional[str] = None # Примечания (например, «учтены премии»)
|
|
93
|
+
):
|
|
94
|
+
self.calculation_id = calculation_id
|
|
95
|
+
self.teacher_id = teacher_id
|
|
96
|
+
self.period_id = period_id
|
|
97
|
+
self.total_bonus = total_bonus
|
|
98
|
+
self.calculation_date = calculation_date
|
|
99
|
+
self.notes = notes
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
Связи между сущностями:
|
|
103
|
+
|
|
104
|
+
1. Teacher ↔ WorkRecord: один-ко-многим
|
|
105
|
+
- Один преподаватель может иметь много записей о выполненной работе.
|
|
106
|
+
- Каждая запись относится к одному преподавателю.
|
|
107
|
+
|
|
108
|
+
2. ExtraWorkType ↔ WorkRecord: один-ко-многим
|
|
109
|
+
- Один тип работы может встречаться во многих записях.
|
|
110
|
+
- Каждая запись относится к одному типу работы.
|
|
111
|
+
|
|
112
|
+
3. ReportingPeriod ↔ WorkRecord: один-ко-многим
|
|
113
|
+
- В одном периоде может быть много записей о работе.
|
|
114
|
+
- Каждая запись относится к одному периоду учёта.
|
|
115
|
+
4. Teacher + ReportingPeriod ↔ BonusCalculation: один-ко-одному (на пару)
|
|
116
|
+
- Для каждого преподавателя и периода формируется один расчёт надбавки.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
Нормализация:
|
|
120
|
+
- Все сущности приведены к 3НФ: нет транзитивных зависимостей, каждый атрибут зависит только от PK.
|
|
121
|
+
- Внешние ключи (FK) обеспечивают ссылочную целостность.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
Как использовать модель:
|
|
125
|
+
1. Заполнить справочники: Teacher, ExtraWorkType, ReportingPeriod.
|
|
126
|
+
2. Вносить записи о выполненной работе в WorkRecord (с подтверждением документов).
|
|
127
|
+
3. После закрытия периода — сформировать BonusCalculation для каждого преподавателя.
|
|
128
|
+
4. Использовать total_bonus из BonusCalculation для начисления выплат.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# Пример создания объектов (для иллюстрации)
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
# Создаём преподавателя
|
|
134
|
+
teacher = Teacher(teacher_id=1, full_name="Иванов А.С.", position="Доцент", base_salary=60000.00)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Создаём тип работы
|
|
138
|
+
work_type = ExtraWorkType(
|
|
139
|
+
work_type_id=101,
|
|
140
|
+
name="Руководство ВКР",
|
|
141
|
+
unit="штука",
|
|
142
|
+
rate_per_unit=5000.00
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Создаём период учёта
|
|
146
|
+
period = ReportingPeriod(
|
|
147
|
+
period_id=20251,
|
|
148
|
+
start_date=date(2025, 9, 1),
|
|
149
|
+
end_date=date(2025, 12, 31),
|
|
150
|
+
name="Семестр 1 2025/2026"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Фиксируем выполненную работу
|
|
154
|
+
record = WorkRecord(
|
|
155
|
+
record_id=1,
|
|
156
|
+
teacher_id=1,
|
|
157
|
+
work_type_id=101,
|
|
158
|
+
period_id=20251,
|
|
159
|
+
volume=3.0,
|
|
160
|
+
confirmation_doc="Акт №123 от 15.11.2025",
|
|
161
|
+
confirmation_date=date(2025, 11, 15),
|
|
162
|
+
approved=True
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Рассчитываем надбавку (упрощённо: volume × rate_per_unit)
|
|
166
|
+
total_bonus = record.volume * work_type.rate_per_unit
|
|
167
|
+
calculation = BonusCalculation(
|
|
168
|
+
calculation_id=1,
|
|
169
|
+
teacher_id=1,
|
|
170
|
+
period_id=20251,
|
|
171
|
+
total_bonus=total_bonus,
|
|
172
|
+
calculation_date=date(2025, 12, 20),
|
|
173
|
+
notes="Рассчитано по 3 ВКР"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
print(f"Надбавка для {teacher.full_name}: {calculation.total_bonus} руб.")
|
solutions/zadanie_24.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
def zadanie_24():
|
|
2
|
+
'''
|
|
3
|
+
-- SQL‑запрос для выбора трёх лучших по выручке товаров
|
|
4
|
+
-- Поясним каждый этап построения запроса
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
SELECT
|
|
8
|
+
product_id, -- Идентификатор товара (выводим для идентификации)
|
|
9
|
+
product_name, -- Название товара (удобно для анализа)
|
|
10
|
+
total_revenue -- Рассчитанная выручка по товару
|
|
11
|
+
FROM (
|
|
12
|
+
-- Вложенный запрос (подзапрос): считаем выручку по каждому товару
|
|
13
|
+
SELECT
|
|
14
|
+
p.product_id, -- Берём ID товара
|
|
15
|
+
p.product_name, -- Берём название товара
|
|
16
|
+
SUM(s.quantity * p.price) AS total_revenue -- Выручка = сумма(количество × цена)
|
|
17
|
+
FROM
|
|
18
|
+
products p -- Основная таблица товаров
|
|
19
|
+
JOIN sales s ON p.product_id = s.product_id -- Соединяем с таблицей продаж
|
|
20
|
+
GROUP BY
|
|
21
|
+
p.product_id, p.product_name -- Группируем по товару, чтобы посчитать выручку
|
|
22
|
+
) AS revenue_calc
|
|
23
|
+
-- Конец вложенного запроса: теперь у нас есть таблица с выручкой по каждому товару
|
|
24
|
+
|
|
25
|
+
-- Основной запрос: сортируем и ограничиваем результат
|
|
26
|
+
ORDER BY
|
|
27
|
+
total_revenue DESC -- Сортируем по выручке по убыванию (самые прибыльные — вверху)
|
|
28
|
+
LIMIT 3; -- Оставляем только 3 записи (три лучших товара)
|
|
29
|
+
|
|
30
|
+
-- Пояснение логики:
|
|
31
|
+
-- 1. Мы соединяем таблицу товаров (products) с таблицей продаж (sales) по полю product_id.
|
|
32
|
+
-- 2. Для каждого товара считаем общую выручку как сумму произведений количества проданного (quantity)
|
|
33
|
+
-- на цену товара (price) из таблицы products.
|
|
34
|
+
-- 3. Группируем результаты по товару (product_id и product_name), чтобы получить итоговую выручку
|
|
35
|
+
-- для каждого отдельного товара.
|
|
36
|
+
-- 4. Во внешнем запросе сортируем товары по убыванию выручки и ограничиваем вывод тремя записями.
|
|
37
|
+
--
|
|
38
|
+
-- Предполагаемые структуры таблиц:
|
|
39
|
+
-- products: product_id (PK), product_name, price
|
|
40
|
+
-- sales: sale_id (PK), product_id (FK), quantity, sale_date
|
|
41
|
+
--
|
|
42
|
+
-- Запрос универсален и будет работать в большинстве реляционных СУБД (PostgreSQL, MySQL, SQLite, SQL Server, Oracle),
|
|
43
|
+
-- при условии соответствия имён таблиц и полей.'''
|
solutions/zadanie_3.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
def zadanie_3():
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Устанавливаем случайное зерно для воспроизводимости результатов
|
|
6
|
+
np.random.seed(42)
|
|
7
|
+
|
|
8
|
+
# 1. Создаём первый DataFrame (df_a)
|
|
9
|
+
# Предположим, что индекс — это названия городов (для наглядности возьмём несколько городов)
|
|
10
|
+
cities_a = ['Москва', 'Санкт‑Петербург', 'Казань', 'Нижний Новгород', 'Екатеринбург']
|
|
11
|
+
df_a = pd.DataFrame({
|
|
12
|
+
'report': np.random.randint(1, 11, size=len(cities_a)), # случайные целые от 1 до 10
|
|
13
|
+
'sales': np.random.randint(100, 1001, size=len(cities_a)) # случайные целые от 100 до 1000
|
|
14
|
+
}, index=cities_a)
|
|
15
|
+
|
|
16
|
+
# Назначаем имя индексу (для ясности)
|
|
17
|
+
df_a.index.name = 'city'
|
|
18
|
+
|
|
19
|
+
print("DataFrame df_a:")
|
|
20
|
+
print(df_a)
|
|
21
|
+
print()
|
|
22
|
+
|
|
23
|
+
# 2. Создаём второй DataFrame (df_b)
|
|
24
|
+
# Возьмём частично пересекающийся набор городов, чтобы продемонстрировать объединение
|
|
25
|
+
cities_b = ['Москва', 'Самара', 'Казань', 'Ростов‑на‑Дону', 'Новосибирск']
|
|
26
|
+
df_b = pd.DataFrame({
|
|
27
|
+
'report': np.random.randint(1, 11, size=len(cities_b)),
|
|
28
|
+
'sales': np.random.randint(100, 1001, size=len(cities_b))
|
|
29
|
+
}, index=cities_b)
|
|
30
|
+
|
|
31
|
+
df_b.index.name = 'city'
|
|
32
|
+
|
|
33
|
+
print("DataFrame df_b:")
|
|
34
|
+
print(df_b)
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
# 3. Объединяем два DataFrame по индексу (городу)
|
|
38
|
+
# Используем pd.concat для вертикального объединения, затем группируем по индексу
|
|
39
|
+
df_combined = pd.concat([df_a, df_b])
|
|
40
|
+
|
|
41
|
+
# 4. Группируем по городу (index) и суммируем значения в колонках 'report' и 'sales'
|
|
42
|
+
df_total = df_combined.groupby('city').sum()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 5. Выводим итоговый DataFrame
|
|
46
|
+
print("Итоговый DataFrame df_total (суммарные значения по городам):")
|
|
47
|
+
print(df_total)
|
|
48
|
+
|
|
49
|
+
# Пояснение алгоритма:
|
|
50
|
+
# - Шаг 1–2: создаём два DataFrame с индексами-городами и случайными данными в колонках 'report' (1..10) и 'sales' (100..1000).
|
|
51
|
+
# np.random.randint(a, b) генерирует случайные целые числа в диапазоне [a, b-1], поэтому для 1..10 указываем (1, 11).
|
|
52
|
+
# - Шаг 3: объединяем df_a и df_b с помощью pd.concat(). Это создаёт новый DataFrame, где строки из обоих исходных DataFrame идут подряд.
|
|
53
|
+
# Индексы (города) сохраняются, поэтому города, присутствующие в обоих DataFrame, окажутся в объединённой таблице дважды.
|
|
54
|
+
# - Шаг 4: группируем объединённый DataFrame по индексу 'city' с помощью groupby('city').
|
|
55
|
+
# Затем применяем .sum(), чтобы для каждого уникального города сложить значения в колонках 'report' и 'sales'.
|
|
56
|
+
# Таким образом, если город есть и в df_a, и в df_b, его показатели будут суммированы.
|
|
57
|
+
# - Шаг 5: выводим результат — df_total содержит все уникальные города из обоих DataFrame и их суммарные 'report' и 'sales'.
|
solutions/zadanie_5.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
def zadanie_5():
|
|
2
|
+
def ip_to_binary(ip):
|
|
3
|
+
"""
|
|
4
|
+
Преобразует IP‑адрес из десятичного формата (строка) в 32‑битную двоичную строку.
|
|
5
|
+
|
|
6
|
+
Пример: '172.16.17.30' → '10101100000100000001000100011110'
|
|
7
|
+
"""
|
|
8
|
+
parts = ip.split('.')
|
|
9
|
+
binary = ''
|
|
10
|
+
for part in parts:
|
|
11
|
+
# Преобразуем каждую часть в 8‑битное двоичное число (с ведущими нулями)
|
|
12
|
+
binary += format(int(part), '08b')
|
|
13
|
+
return binary
|
|
14
|
+
|
|
15
|
+
def apply_mask(binary_ip, mask_length):
|
|
16
|
+
"""
|
|
17
|
+
Применяет маску подсети к двоичному IP‑адресу.
|
|
18
|
+
|
|
19
|
+
Параметры:
|
|
20
|
+
binary_ip — двоичная строка (32 бита)
|
|
21
|
+
mask_length — длина маски (например, 20)
|
|
22
|
+
Возвращает:
|
|
23
|
+
адрес сети в двоичном виде (32 бита, где последние (32 − mask_length) бит обнулены)
|
|
24
|
+
"""
|
|
25
|
+
# Оставляем первые mask_length бит, остальные обнуляем
|
|
26
|
+
network = binary_ip[:mask_length] + '0' * (32 - mask_length)
|
|
27
|
+
return network
|
|
28
|
+
|
|
29
|
+
def binary_to_ip(binary):
|
|
30
|
+
"""
|
|
31
|
+
Преобразует 32‑битную двоичную строку в десятичный IP‑адрес (строка).
|
|
32
|
+
|
|
33
|
+
Пример: '10101100000100000001000000000000' → '172.16.16.0'
|
|
34
|
+
"""
|
|
35
|
+
ip_parts = []
|
|
36
|
+
for i in range(0, 32, 8):
|
|
37
|
+
# Разбиваем на 4 части по 8 бит, преобразуем в десятичное число
|
|
38
|
+
part = binary[i:i+8]
|
|
39
|
+
ip_parts.append(str(int(part, 2)))
|
|
40
|
+
return '.'.join(ip_parts)
|
|
41
|
+
|
|
42
|
+
# Исходные данные
|
|
43
|
+
pc1_ip = '172.16.17.30'
|
|
44
|
+
pc2_ip = '172.16.28.15'
|
|
45
|
+
mask_length = 20 # Маска /2 Newton
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
print(f"PC1: {pc1_ip}/{mask_length}")
|
|
49
|
+
print(f"PC2: {pc2_ip}/{mask_length}")
|
|
50
|
+
|
|
51
|
+
# Шаг 1. Переводим IP‑адреса в двоичный формат
|
|
52
|
+
pc1_binary = ip_to_binary(pc1_ip)
|
|
53
|
+
pc2_binary = ip_to_binary(pc2_ip)
|
|
54
|
+
|
|
55
|
+
print(f"\nДвоичный вид:")
|
|
56
|
+
print(f"PC1: {pc1_binary}")
|
|
57
|
+
print(f"PC2: {pc2_binary}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Шаг 2. Применяем маску к каждому IP
|
|
61
|
+
pc1_network = apply_mask(pc1_binary, mask_length)
|
|
62
|
+
pc2_network = apply_mask(pc2_binary, mask_length)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
print(f"\nАдрес сети после применения маски /{mask_length}:")
|
|
66
|
+
print(f"PC1: {pc1_network} ({binary_to_ip(pc1_network)})")
|
|
67
|
+
print(f"PC2: {pc2_network} ({binary_to_ip(pc2_network)})")
|
|
68
|
+
|
|
69
|
+
# Шаг 3. Сравниваем адреса сетей
|
|
70
|
+
if pc1_network == pc2_network:
|
|
71
|
+
print("\nВывод: Устройства находятся в одной подсети.")
|
|
72
|
+
print(f"Общая сеть: {binary_to_ip(pc1_network)}/{mask_length}")
|
|
73
|
+
else:
|
|
74
|
+
print("\nВывод: Устройства находятся в разных подсетях.")
|
|
75
|
+
print(f"Сеть PC1: {binary_to_ip(pc1_network)}/{mask_length}")
|
|
76
|
+
print(f"Сеть PC2: {binary_to_ip(pc2_network)}/{mask_length}")
|
solutions/zadanie_8.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
def zadanie_8():
|
|
2
|
+
# Импорт библиотеки для работы с числами
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
# Функция для расчёта NPV
|
|
6
|
+
def calculate_npv(initial_investment, cash_flows, discount_rate):
|
|
7
|
+
"""
|
|
8
|
+
Рассчитывает чистый приведённый доход (NPV).
|
|
9
|
+
|
|
10
|
+
Параметры:
|
|
11
|
+
- initial_investment: начальные инвестиции (отрицательное значение, так как это затраты).
|
|
12
|
+
- cash_flows: список денежных потоков по годам.
|
|
13
|
+
- discount_rate: ставка дисконтирования (в десятичном виде, например, 0.1 для 10%).
|
|
14
|
+
|
|
15
|
+
Возвращает:
|
|
16
|
+
- NPV проекта.
|
|
17
|
+
"""
|
|
18
|
+
npv = initial_investment # Начинаем с начальных инвестиций
|
|
19
|
+
for t, cf in enumerate(cash_flows, start=1):
|
|
20
|
+
# Дисконтирование денежного потока за период t
|
|
21
|
+
discounted_cf = cf / (1 + discount_rate) ** t
|
|
22
|
+
npv += discounted_cf
|
|
23
|
+
return npv
|
|
24
|
+
|
|
25
|
+
# Данные для Проекта А
|
|
26
|
+
project_a = {
|
|
27
|
+
"initial_investment": -2_000_000, # Инвестиции в млн руб. (знак минус, так как это затраты)
|
|
28
|
+
"cash_flows": [1_000_000, 1_000_000, 1_000_000], # Денежные потоки по годам в млн руб.
|
|
29
|
+
"name": "Проект А (мобильное приложение)"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Данные для Проекта Б
|
|
33
|
+
project_b = {
|
|
34
|
+
"initial_investment": -1_500_000, # Инвестиции в млн руб.
|
|
35
|
+
"cash_flows": [700_000, 700_000, 700_000], # Денежные потоки по годам в млн руб.
|
|
36
|
+
"name": "Проект Б (апгрейд серверов)"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Ставка дисконтирования (10% в десятичном виде)
|
|
40
|
+
discount_rate = 0.1
|
|
41
|
+
|
|
42
|
+
# Расчёт NPV для каждого проекта
|
|
43
|
+
npv_a = calculate_npv(project_a["initial_investment"], project_a["cash_flows"], discount_rate)
|
|
44
|
+
npv_b = calculate_npv(project_b["initial_investment"], project_b["cash_flows"], discount_rate)
|
|
45
|
+
|
|
46
|
+
# Вывод результатов
|
|
47
|
+
print(f"NPV {project_a['name']}: {npv_a:.2f} млн руб.")
|
|
48
|
+
print(f"NPV {project_b['name']}: {npv_b:.2f} млн руб.")
|
|
49
|
+
|
|
50
|
+
# Сравнение проектов
|
|
51
|
+
if npv_a > npv_b:
|
|
52
|
+
print(f"{project_a['name']} экономически выгоднее, так как имеет больший NPV.")
|
|
53
|
+
elif npv_b > npv_a:
|
|
54
|
+
print(f"{project_b['name']} экономически выгоднее, так как имеет больший NPV.")
|
|
55
|
+
else:
|
|
56
|
+
print("Оба проекта имеют одинаковый NPV.")
|
solutions/zadanie_9.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
def zadanie_9():
|
|
2
|
+
'''CREATE TABLE customers (
|
|
3
|
+
id INT AUTO_INCREMENT PRIMARY KEY, -- Уникальный ID заказчика (автозаполнение)
|
|
4
|
+
name VARCHAR(255) NOT NULL, -- Название заказчика (обязательно)
|
|
5
|
+
phone VARCHAR(20) -- Телефон заказчика (может быть пустым)
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE contracts (
|
|
9
|
+
id INT AUTO_INCREMENT PRIMARY KEY, -- Уникальный ID договора (автозаполнение)
|
|
10
|
+
contract_date DATE NOT NULL, -- Дата договора (обязательно)
|
|
11
|
+
amount DECIMAL(10, 2) NOT NULL, -- Сумма договора (обязательно, до 2 знаков после запятой)
|
|
12
|
+
customer_id INT NOT NULL, -- ID заказчика (внешний ключ)
|
|
13
|
+
|
|
14
|
+
-- Внешний ключ: связывает customer_id с id из таблицы customers
|
|
15
|
+
FOREIGN KEY (customer_id) REFERENCES customers(id)
|
|
16
|
+
ON DELETE RESTRICT -- Запретить удаление заказчика, если у него есть договоры
|
|
17
|
+
ON UPDATE CASCADE -- При обновлении id заказчика автоматически обновить customer_id в договорах
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
SELECT
|
|
21
|
+
c.id AS customer_id, -- ID заказчика
|
|
22
|
+
c.name AS customer_name, -- Название заказчика
|
|
23
|
+
IFNULL(SUM(co.amount), 0.00) AS total_amount -- Общая сумма договоров за 2025 год (0.00, если договоров нет)
|
|
24
|
+
FROM
|
|
25
|
+
customers c
|
|
26
|
+
LEFT JOIN -- LEFT JOIN: включаем всех заказчиков, даже если у них нет договоров
|
|
27
|
+
contracts co
|
|
28
|
+
ON c.id = co.customer_id
|
|
29
|
+
AND YEAR(co.contract_date) = 2025 -- Фильтрация по году прямо в JOIN (эффективнее, чем в WHERE)
|
|
30
|
+
GROUP BY
|
|
31
|
+
c.id, c.name -- Группируем по заказчику
|
|
32
|
+
ORDER BY
|
|
33
|
+
total_amount DESC; -- Сортируем по сумме (убывание)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
-- Пояснения к ключевым моментам:
|
|
37
|
+
-- LEFT JOIN vs INNER JOIN:
|
|
38
|
+
-- - LEFT JOIN гарантирует, что в результате будут все заказчики, даже если у них нет договоров за 2025 год.
|
|
39
|
+
-- - INNER JOIN исключил бы таких заказчиков.
|
|
40
|
+
--
|
|
41
|
+
-- Фильтрация по году:
|
|
42
|
+
-- - YEAR(co.contract_date) = 2025 — извлекает год из даты и сравнивает с 2025.
|
|
43
|
+
-- - Условие добавлено в ON LEFT JOIN, чтобы не отфильтровать заказчиков без договоров.
|
|
44
|
+
--
|
|
45
|
+
-- IFNULL(SUM(...), 0.00):
|
|
46
|
+
-- - Если у заказчика нет договоров, SUM(amount) вернёт NULL.
|
|
47
|
+
-- - IFNULL заменяет NULL на 0.00 для наглядности.
|
|
48
|
+
--
|
|
49
|
+
-- GROUP BY c.id, c.name:
|
|
50
|
+
-- - Группируем по уникальному ID и имени заказчика, чтобы агрегировать суммы.
|
|
51
|
+
--
|
|
52
|
+
-- ORDER BY total_amount DESC:
|
|
53
|
+
-- - Сортируем результат по убыванию суммы, чтобы сначала шли заказчики с наибольшими оборотами.'''
|