sista-tacrpy 1.0.12__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.
- sista_tacrpy-1.0.12.dist-info/METADATA +18 -0
- sista_tacrpy-1.0.12.dist-info/RECORD +17 -0
- sista_tacrpy-1.0.12.dist-info/WHEEL +5 -0
- sista_tacrpy-1.0.12.dist-info/top_level.txt +1 -0
- tacrpy/__init__.py +3 -0
- tacrpy/analytics/__init__.py +0 -0
- tacrpy/data_fetcher/__init__.py +3 -0
- tacrpy/data_fetcher/googlesheets.py +237 -0
- tacrpy/data_fetcher/isvavai.py +162 -0
- tacrpy/data_fetcher/ssot.py +1006 -0
- tacrpy/data_operations.py +70 -0
- tacrpy/datahub/__init__.py +5 -0
- tacrpy/datahub/data_lineage.py +86 -0
- tacrpy/datahub/datasets.py +421 -0
- tacrpy/datahub/glossary.py +378 -0
- tacrpy/datahub/import_checks.py +123 -0
- tacrpy/datahub/openapi.py +78 -0
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import re
|
|
3
|
+
from google.cloud import bigquery
|
|
4
|
+
import google.auth.impersonated_credentials
|
|
5
|
+
|
|
6
|
+
project_id = 'sista-data-stream'
|
|
7
|
+
creds, pid = google.auth.default()
|
|
8
|
+
target_scopes = [
|
|
9
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
10
|
+
"https://www.googleapis.com/auth/drive"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
print(f"Obtained default credentials for the project {pid}")
|
|
16
|
+
tcreds = google.auth.impersonated_credentials.Credentials(
|
|
17
|
+
source_credentials=creds,
|
|
18
|
+
# target_principal="big-query-user@sista-data-stream.iam.gserviceaccount.com",
|
|
19
|
+
|
|
20
|
+
# target_principal="bq-reader@sista-data-stream.iam.gserviceaccount.com", # 1.0.6
|
|
21
|
+
|
|
22
|
+
target_principal="data-stream-bq@sista-data-stream.iam.gserviceaccount.com", # 1.0.7
|
|
23
|
+
# RefreshError: ('Unable to acquire impersonated credentials', '{\n "error": {\n "code": 403,\n "message": "Permission \'iam.serviceAccounts.getAccessToken\' denied on resource (or it may not exist).",\n "status": "PERMISSION_DENIED",\n "details": [\n {\n "@type": "type.googleapis.com/google.rpc.ErrorInfo",\n "reason": "IAM_PERMISSION_DENIED",\n "domain": "iam.googleapis.com",\n "metadata": {\n "permission": "iam.serviceAccounts.getAccessToken"\n }\n }\n ]\n }\n}\n')
|
|
24
|
+
|
|
25
|
+
target_scopes=target_scopes
|
|
26
|
+
)
|
|
27
|
+
client = bigquery.Client(credentials=tcreds, project=project_id)
|
|
28
|
+
#? credentials a vsechno funguji
|
|
29
|
+
#! uz ne????
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Projects:
|
|
33
|
+
""" Třída, která načítá a reprezentuje tabulku projektů.
|
|
34
|
+
Funguje pouze v rámci Google Colab prostředí.
|
|
35
|
+
|
|
36
|
+
:param projects: DataFrame načtených dat ze zdroje pravdy nebo z nově vytvořené (vyfiltrované) instance
|
|
37
|
+
:type projects: DataFrame
|
|
38
|
+
:param summary_cfp: DataFrame s agregovanými údaji na úrovni veřejných soutěží
|
|
39
|
+
:type summary_cfp: DataFrame
|
|
40
|
+
:param summary_prog: DataFrame s agregovanými údaji na úrovni programů
|
|
41
|
+
:type summary_prog: DataFrame
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, df: object = None):
|
|
45
|
+
""" Konstruktor, který načte data do DataFrame a vytvoří agregovanou tabulku.
|
|
46
|
+
"""
|
|
47
|
+
if df is None:
|
|
48
|
+
self.projects = self._get_projects()
|
|
49
|
+
else:
|
|
50
|
+
self.projects = df
|
|
51
|
+
# self.summary_cfp = self.create_summary() #! az dopisu create_summary() tak odkomentovat !!
|
|
52
|
+
# self.summary_prog = self.create_summary('prog') #! az dopisu create_summary() tak odkomentovat !!
|
|
53
|
+
|
|
54
|
+
#? DONE, TESTED, vola se stejne, funguje beze zmeny
|
|
55
|
+
def _get_projects(self) -> pd.DataFrame:
|
|
56
|
+
""" Načte data o projektech z databáze zdroje pravdy v Bigquery.
|
|
57
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
:return: DataFrame načtených dat
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# data-poc-424211.ssot.Projekty_ssot
|
|
64
|
+
# sista-data-stream.ssot.projects
|
|
65
|
+
query = """
|
|
66
|
+
SELECT *
|
|
67
|
+
FROM `sista-data-stream.ssot.projects`
|
|
68
|
+
"""
|
|
69
|
+
df = client.query(query).to_dataframe()
|
|
70
|
+
return df
|
|
71
|
+
|
|
72
|
+
#? DONE, TESTED helper function, melo by fungovat by default, porad ty stejne datove typy atd
|
|
73
|
+
def _check_missing_items(self, provided_items: tuple, existing_items: list, item_name: str):
|
|
74
|
+
"""Ověří, zda se všechny zadané položky nacházejí v existujícím seznamu.
|
|
75
|
+
|
|
76
|
+
Pokud jsou nalezeny chybějící položky, vyvolá ValueError s chybovou zprávou.
|
|
77
|
+
|
|
78
|
+
:param provided_items: Tuple položek zadaných pro filtrování.
|
|
79
|
+
:param existing_items: Seznam všech unikátních položek nacházejících se v datové sadě.
|
|
80
|
+
:param item_name: Popisný název položky, který se použije v chabové hlášce.
|
|
81
|
+
|
|
82
|
+
:raises ValueError: Pokud alespoň jedna zadaná položka neexistuje v datové sadě.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
missing_items = [item for item in provided_items if item not in existing_items]
|
|
86
|
+
if missing_items:
|
|
87
|
+
raise ValueError(f'{item_name} {missing_items} neexistuje/neexistují.')
|
|
88
|
+
|
|
89
|
+
# TODO: tohle bude oser (mozna)
|
|
90
|
+
def create_summary(self, level: str = 'cfp') -> pd.DataFrame:
|
|
91
|
+
""" Vytvoří agregovaný souhrn buď na úrovni veřejných soutěží (defaultní) nebo na úrovni programů.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
:param level: určuje, na jaké úrovni se provede agregace
|
|
95
|
+
* 'cfp' (defaultní) - na úrovni veřejných soutěží
|
|
96
|
+
* 'prog' - na úrovni programů
|
|
97
|
+
:return: agregovaný DataFrame, který obsahuje:
|
|
98
|
+
* Počet podaných projektů
|
|
99
|
+
* Počet podpořených projektů
|
|
100
|
+
* Náklady podpořených projektů
|
|
101
|
+
* Podpora podpořených projektů
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
if level not in ['cfp', 'prog']:
|
|
105
|
+
raise ValueError('Neexistující forma agregace.')
|
|
106
|
+
|
|
107
|
+
temp_df = self.projects.copy()
|
|
108
|
+
temp_df['Podpořené'] = temp_df.apply(
|
|
109
|
+
lambda x: 'Ano' if x['faze_projektu'] in ['Realizace', 'Implementace', 'Ukončené'] else 'Ne', axis=1)
|
|
110
|
+
submitted = temp_df.groupby(['kod_programu', 'kod_VS']).agg(
|
|
111
|
+
{'kod_projektu': 'count', 'naklady_celkem': 'sum', 'podpora_celkem': 'sum'}).reset_index()
|
|
112
|
+
funded = temp_df[temp_df['Podpořené'] == 'Ano'].groupby(['kod_programu', 'kod_VS']).agg(
|
|
113
|
+
{'kod_projektu': 'count', 'naklady_celkem': 'sum', 'podpora_celkem': 'sum'}).reset_index()
|
|
114
|
+
|
|
115
|
+
summary_df = pd.merge(submitted[['kod_programu', 'kod_VS', 'kod_projektu']], funded, how='inner',
|
|
116
|
+
on=['kod_programu', 'kod_VS'])
|
|
117
|
+
summary_df.columns = ['kod_programu', 'kod_VS', 'Podané', 'Podpořené', 'Náklady', 'Podpora']
|
|
118
|
+
|
|
119
|
+
if level == 'cfp':
|
|
120
|
+
pass
|
|
121
|
+
elif level == 'prog':
|
|
122
|
+
summary_df = summary_df.groupby('kod_programu').agg('sum', numeric_only=True).reset_index()
|
|
123
|
+
|
|
124
|
+
return summary_df
|
|
125
|
+
|
|
126
|
+
#? DONE, TESTED , vola se stejne, funguje beze zmeny
|
|
127
|
+
def select_programme(self, *args: str) -> 'Projects':
|
|
128
|
+
""" Vyfiltruje dataframe projektů tak, aby obsahovala pouze projekty vybraných programů.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
:param args: kódy programů (dvoumístné - například 'FW'), které se mají vyfiltrovat
|
|
132
|
+
:return: nová instance třídy Projects s vyfiltrovanými údaji
|
|
133
|
+
:raise: ValueError
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
# existing_programmes = self.projects['kod_programu'].unique()
|
|
137
|
+
existing_programmes = self.projects['programme_code'].unique()
|
|
138
|
+
self._check_missing_items(args, existing_programmes, 'Programy')
|
|
139
|
+
|
|
140
|
+
programme_list = [prog for prog in args]
|
|
141
|
+
# select_df = self.projects[self.projects['kod_programu'].isin(programme_list)].reset_index(drop=True)
|
|
142
|
+
select_df = self.projects[self.projects['programme_code'].isin(programme_list)].reset_index(drop=True)
|
|
143
|
+
return Projects(select_df)
|
|
144
|
+
|
|
145
|
+
#? DONE, TESTED, vola se stejne, funguje beze zmeny
|
|
146
|
+
def select_cfp(self, *args: str) -> 'Projects':
|
|
147
|
+
""" Vyfiltruje dataframe projektů tak, aby obsahovala pouze projekty vybraných veřejných soutěží.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
:param args: kódy veřejných soutěží (čtyřmístné - například 'FW01'), které se mají vyfiltrovat
|
|
151
|
+
:return: nová instance třídy Projects s vyfiltrovanými údaji
|
|
152
|
+
:raise: ValueError
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
# existing_cfp = self.projects['kod_VS'].unique()
|
|
156
|
+
existing_cfp = self.projects['cfp_code'].unique()
|
|
157
|
+
self._check_missing_items(args, existing_cfp, 'Veřejné soutěže')
|
|
158
|
+
|
|
159
|
+
cfp_list = [cfp for cfp in args]
|
|
160
|
+
select_df = self.projects[self.projects['cfp_code'].isin(cfp_list)].reset_index(drop=True)
|
|
161
|
+
return Projects(select_df)
|
|
162
|
+
|
|
163
|
+
#? DONE, , rename to `select_financed()`
|
|
164
|
+
def select_funded(self) -> 'Projects':
|
|
165
|
+
""" Vyfiltruje dataframe projektů tak, aby obsahoval pouze podpořené projekty.
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
:return: nová instance třídy Projects s vyfiltrovanými údaji
|
|
169
|
+
"""
|
|
170
|
+
financed_states = ['400','500', '600']
|
|
171
|
+
# funded_states = ['Realizace', 'Implementace', 'Ukončené']
|
|
172
|
+
# select_df = self.projects[self.projects['faze_projektu'].isin(funded_states)].reset_index(drop=True)
|
|
173
|
+
select_df = self.projects[self.projects['project_state'].isin(financed_states)].reset_index(drop=True)
|
|
174
|
+
return Projects(select_df)
|
|
175
|
+
|
|
176
|
+
#TODO: jak fungujou cepy, @Vojta
|
|
177
|
+
def select_cep(self, level: int, *args: str) -> 'Projects':
|
|
178
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze projekty vybraných oborů nebo skupin oborů klasifikace CEP
|
|
179
|
+
|
|
180
|
+
:param level: úroveň - 1 = skupiny oborů CEP, 2 = obory CEP
|
|
181
|
+
:param args: kódy skupin oborů CEP (1 písmeno) nebo oborů CEP (2 písmena), které se mají vyfiltrovat
|
|
182
|
+
úroveň 1 - skupiny oborů:
|
|
183
|
+
* A = A - Společenské vědy
|
|
184
|
+
* B = B - Fyzika a matematika
|
|
185
|
+
* C = C - Chemie
|
|
186
|
+
* D = D - Vědy o zemi
|
|
187
|
+
* E = E - Biovědy
|
|
188
|
+
* F = F - Lékařské vědy
|
|
189
|
+
* G = G - Zemědělství
|
|
190
|
+
* I = I - Informatika
|
|
191
|
+
* J = J - Průmysl
|
|
192
|
+
* K = K - Vojenství a politika
|
|
193
|
+
úroveň 2 - obory CEP dostupná zde https://docs.google.com/spreadsheets/d/1VknMmHAjKspJmyYlCeJCVEOn01xFGETbTNKi4dMvqf8/edit#gid=0
|
|
194
|
+
|
|
195
|
+
:return: nová instance třídy Projects s vyfiltrovanými údaji
|
|
196
|
+
:raise: ValueError
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
if level == 1:
|
|
200
|
+
incorrect_level = [lvl for lvl in args if len(lvl) != 1]
|
|
201
|
+
if incorrect_level:
|
|
202
|
+
raise ValueError("Pro úroveň 1 (Skupina oborů CEP) je nutné zvolit pouze jednopísmenný kód!")
|
|
203
|
+
else:
|
|
204
|
+
cep1_enum = ["A", "B", "C", "D", "E", "F", "G", "I", "J", "K"]
|
|
205
|
+
incorrect_cep1 = [cep1 for cep1 in args if cep1 not in cep1_enum]
|
|
206
|
+
if incorrect_cep1:
|
|
207
|
+
raise ValueError(f"Skupina oborů CEP {incorrect_cep1} není v číselníku skupin oborů CEP!")
|
|
208
|
+
else:
|
|
209
|
+
existing_cep1 = self.projects['cep1'].str[:1].unique()
|
|
210
|
+
missing_cep1 = [cep1 for cep1 in args if cep1 not in existing_cep1]
|
|
211
|
+
if missing_cep1:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Žádný projekt ze skupiny oborů CEP {missing_cep1} není v zadaném výběru nebo v této skupině oborů vůbec nebyl v TA ČR žádný projekt podpořen.")
|
|
214
|
+
else:
|
|
215
|
+
cep1_list = [cep1 for cep1 in args]
|
|
216
|
+
select_df = self.projects[self.projects['cep1'].str[:1].isin(cep1_list)].reset_index(drop=True)
|
|
217
|
+
return Projects(select_df)
|
|
218
|
+
elif level == 2:
|
|
219
|
+
incorrect_level = [lvl for lvl in args if len(lvl) != 2]
|
|
220
|
+
if incorrect_level:
|
|
221
|
+
raise ValueError("Pro úroveň 2 (Obory CEP) je nutné zvolit pouze dvoupísmenný kód!")
|
|
222
|
+
else:
|
|
223
|
+
cep2_enum = [
|
|
224
|
+
'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ'
|
|
225
|
+
, 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BK', 'BL', 'BM', 'BN', 'BO'
|
|
226
|
+
, 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', 'CG', 'CH', 'CI'
|
|
227
|
+
, 'DA', 'DB', 'DC', 'DD', 'DE', 'DF', 'DG', 'DH', 'DI', 'DJ', 'DK', 'DL', 'DM', 'DN', 'DO'
|
|
228
|
+
, 'EA', 'EB', 'EC', 'ED', 'EE', 'EF', 'EG', 'EH', 'EI'
|
|
229
|
+
, 'FA', 'FB', 'FC', 'FD', 'FE', 'FF', 'FG', 'FH', 'FI', 'FJ', 'FK', 'FL', 'FM', 'FN', 'FO', 'FP',
|
|
230
|
+
'FQ', 'FR', 'FS'
|
|
231
|
+
, 'GA', 'GB', 'GC', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GJ', 'GK', 'GL', 'GM'
|
|
232
|
+
, 'IN'
|
|
233
|
+
, 'JA', 'JB', 'JC', 'JD', 'JE', 'JF', 'JG', 'JH', 'JI', 'JJ', 'JK', 'JL', 'JM', 'JN', 'JO', 'JP',
|
|
234
|
+
'JQ', 'JR', 'JS', 'JT', 'JU', 'JV', 'JW', 'JY'
|
|
235
|
+
, 'KA']
|
|
236
|
+
incorrect_cep2 = [cep2 for cep2 in args if cep2 not in cep2_enum]
|
|
237
|
+
if incorrect_cep2:
|
|
238
|
+
raise ValueError(f"Obor {incorrect_cep2} není v číselníku oborů CEP!")
|
|
239
|
+
else:
|
|
240
|
+
existing_cep2 = self.projects['cep1'].str[:2].unique()
|
|
241
|
+
missing_cep2 = [cep2 for cep2 in args if cep2 not in existing_cep2]
|
|
242
|
+
if missing_cep2:
|
|
243
|
+
raise ValueError(
|
|
244
|
+
f"Žádný projekt s oborem CEP {missing_cep2} není v zadaném výběru nebo s tímto oborem vůbec nebyl v TA ČR žádný projekt podpořen.")
|
|
245
|
+
else:
|
|
246
|
+
cep2_list = [cep2 for cep2 in args]
|
|
247
|
+
select_df = self.projects[self.projects['cep1'].str[:2].isin(cep2_list)].reset_index(drop=True)
|
|
248
|
+
return Projects(select_df)
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError("Lze zvolit pouze úroveň 1 nebo 2")
|
|
251
|
+
|
|
252
|
+
#TODO @Vojta, jak fungujou fordy
|
|
253
|
+
def select_ford(self, level, *args: str) -> 'Projects':
|
|
254
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze projekty vybraných oborů nebo
|
|
255
|
+
skupin oborů klasifikace FORD
|
|
256
|
+
|
|
257
|
+
:param level: úroveň - 1 = Vědní oblast, 2 = FIELDS OF RESEARCH AND DEVELOPMENT (FORD), 3 = DETAILED FORD
|
|
258
|
+
:param args: kódy úrovní oborů FORD (1,2 nebo 3-písmenný), které se mají vyfiltrovat
|
|
259
|
+
úroveň 1 - skupiny oborů
|
|
260
|
+
* 1 = 1. Natural Sciences
|
|
261
|
+
* 2 = 2. Engineering and Technology
|
|
262
|
+
* 3 = 3. Medical and Health Sciences
|
|
263
|
+
* 4 = 4. Agricultural and veterinary sciences
|
|
264
|
+
* 5 = 5. Social Sciences
|
|
265
|
+
* 6 = 6. Humanities and the Arts
|
|
266
|
+
podrobně rozepsaná úroveň 2 (FIELDS OF RESEARCH AND DEVELOPMENT (FORD))
|
|
267
|
+
a úroveň 3 (DETAILED FORD) jsou dostupnézde https://docs.google.com/spreadsheets/d/1J5OChOGxdTZGXOAMeU0icp5Kiz88BhSndRbxJ94A0nc/edit#gid=0
|
|
268
|
+
:return: nová instance třídy Projects s vyfiltrovanými údaji
|
|
269
|
+
:raise: ValueError
|
|
270
|
+
"""
|
|
271
|
+
self.projects["ford_code"] = self.projects["ford1"].apply(
|
|
272
|
+
lambda text: re.search(r'\b(\d{5})\b', text).group(1) if isinstance(text, str) and text and re.search(
|
|
273
|
+
r'\b(\d{5})\b', text) else None)
|
|
274
|
+
|
|
275
|
+
if level == 1:
|
|
276
|
+
incorrect_level = [lvl for lvl in args if len(lvl) != 1]
|
|
277
|
+
if incorrect_level:
|
|
278
|
+
raise ValueError("Pro úroveň 1 (Vědní oblast) je nutné zvolit pouze jednočíselný kód!")
|
|
279
|
+
else:
|
|
280
|
+
ford1_enum = ['1', '2', '3', '4', '5', '6']
|
|
281
|
+
incorrect_ford1 = [ford1 for ford1 in args if ford1 not in ford1_enum]
|
|
282
|
+
if incorrect_ford1:
|
|
283
|
+
raise ValueError(f"Vědní oblast FORD {incorrect_ford1} není v číselníku FORD!")
|
|
284
|
+
else:
|
|
285
|
+
existing_ford1 = self.projects['ford_code'].str[:1].unique()
|
|
286
|
+
missing_ford1 = [ford1 for ford1 in args if ford1 not in existing_ford1]
|
|
287
|
+
if missing_ford1:
|
|
288
|
+
raise ValueError(
|
|
289
|
+
f"Žádný projekt z Vědního oboru FORD {missing_ford1} není v zadaném výběru nebo vůbec nebyl v TA ČR žádný takový projekt podpořen.")
|
|
290
|
+
else:
|
|
291
|
+
ford1_list = [ford1 for ford1 in args]
|
|
292
|
+
select_df = self.projects[self.projects['ford_code'].str[:1].isin(ford1_list)].reset_index(
|
|
293
|
+
drop=True)
|
|
294
|
+
return Projects(select_df)
|
|
295
|
+
|
|
296
|
+
elif level == 2:
|
|
297
|
+
incorrect_level = [lvl for lvl in args if len(lvl) != 3]
|
|
298
|
+
if incorrect_level:
|
|
299
|
+
raise ValueError(
|
|
300
|
+
"Pro úroveň 2 (FIELDS OF RESEARCH AND DEVELOPMENT (FORD)) je nutné zvolit pouze trojčíselný kód!")
|
|
301
|
+
else:
|
|
302
|
+
ford2_enum = ['101', '102', '103', '104', '105', '106', '107'
|
|
303
|
+
, '201', '202', '203', '204', '205', '206', '207', '208', '209', '210', '211'
|
|
304
|
+
, '301', '302', '303', '304', '305'
|
|
305
|
+
, '401', '402', '403', '404', '405'
|
|
306
|
+
, '501', '502', '503', '504', '505', '506', '507', '508', '509'
|
|
307
|
+
, '601', '602', '603', '604', '605']
|
|
308
|
+
incorrect_ford2 = [ford2 for ford2 in args if ford2 not in ford2_enum]
|
|
309
|
+
if incorrect_ford2:
|
|
310
|
+
raise ValueError(f"Obor {incorrect_ford2} není v číselníku oborů FORD!")
|
|
311
|
+
else:
|
|
312
|
+
existing_ford2 = self.projects['ford_code'].str[:3].unique()
|
|
313
|
+
missing_ford2 = [ford2 for ford2 in args if ford2 not in existing_ford2]
|
|
314
|
+
if missing_ford2:
|
|
315
|
+
raise ValueError(
|
|
316
|
+
f"Žádný projekt s oborem FORD {missing_ford2} není v zadaném výběru nebo s tímto oborem vůbec nebyl v TA ČR žádný projekt podpořen.")
|
|
317
|
+
else:
|
|
318
|
+
ford2_list = [ford2 for ford2 in args]
|
|
319
|
+
select_df = self.projects[self.projects['ford_code'].str[:3].isin(ford2_list)].reset_index(
|
|
320
|
+
drop=True)
|
|
321
|
+
return Projects(select_df)
|
|
322
|
+
|
|
323
|
+
elif level == 3:
|
|
324
|
+
incorrect_level = [lvl for lvl in args if len(lvl) != 5]
|
|
325
|
+
if incorrect_level:
|
|
326
|
+
raise ValueError("Pro úroveň 3 (DETAILED FORD) je nutné zvolit pouze pětičíselný kód!")
|
|
327
|
+
else:
|
|
328
|
+
ford3_enum = ['10101', '10102', '10103', '10201', '10301', '10302', '10303', '10304', '10305', '10306',
|
|
329
|
+
'10307', '10308', '10401', '10402', '10403', '10404', '10405', '10406', '10501', '10502',
|
|
330
|
+
'10503', '10504', '10505', '10506', '10507', '10508', '10509', '10510', '10511', '10601',
|
|
331
|
+
'10602', '10603', '10604', '10605', '10606', '10607', '10608', '10609', '10610', '10611',
|
|
332
|
+
'10612', '10613', '10614', '10615', '10616', '10617', '10618', '10619', '10620'
|
|
333
|
+
, '20101', '20102', '20103', '20104', '20201', '20202', '20203', '20204', '20205', '20206', '20301',
|
|
334
|
+
'20302', '20303', '20304', '20305', '20306', '20401', '20402', '20501', '20502', '20503',
|
|
335
|
+
'20504', '20505', '20506', '20601', '20602', '20701', '20702', '20703', '20704', '20705',
|
|
336
|
+
'20706', '20707', '20801', '20802', '20803', '20901', '20902', '20903', '21001', '21002',
|
|
337
|
+
'21101'
|
|
338
|
+
, '30101', '30102', '30103', '30104', '30105', '30106', '30107', '30108', '30109', '30201', '30202',
|
|
339
|
+
'30203', '30204', '30205', '30206', '30207', '30208', '30209', '30210', '30211', '30212',
|
|
340
|
+
'30213', '30214', '30215', '30216', '30217', '30218', '30219', '30220', '30221', '30223',
|
|
341
|
+
'30224', '30225', '30226', '30227', '30229', '30230', '30301', '30302', '30303', '30304',
|
|
342
|
+
'30305', '30306', '30307', '30308', '30309', '30310', '30311', '30312', '30401', '30402',
|
|
343
|
+
'30403', '30404', '30405', '30501', '30502'
|
|
344
|
+
, '40101', '40102', '40103', '40104', '40105', '40106', '40201', '40202', '40203', '40301', '40401',
|
|
345
|
+
'40402', '40403'
|
|
346
|
+
, '50101', '50102', '50103', '50201', '50202', '50203', '50204', '50205', '50206', '50301', '50302',
|
|
347
|
+
'50401', '50402', '50403', '50404', '50501', '50502', '50601', '50602', '50603', '50701',
|
|
348
|
+
'50702', '50703', '50704', '50801', '50802', '50803', '50804', '50901', '50902'
|
|
349
|
+
, '60101', '60102', '60201', '60202', '60203', '60204', '60205', '60206', '60301', '60302', '60303',
|
|
350
|
+
'60304', '60401', '60402', '60403', '60404', '60405', '60501']
|
|
351
|
+
incorrect_ford3 = [ford3 for ford3 in args if ford3 not in ford3_enum]
|
|
352
|
+
if incorrect_ford3:
|
|
353
|
+
raise ValueError(f"Obor {incorrect_ford3} není v číselníku oborů CEP!")
|
|
354
|
+
else:
|
|
355
|
+
existing_ford3 = self.projects['ford_code'].str[:5].unique()
|
|
356
|
+
missing_ford3 = [ford3 for ford3 in args if ford3 not in existing_ford3]
|
|
357
|
+
if missing_ford3:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
f"Žádný projekt s oborem FORD {missing_ford3} není v zadaném výběru nebo s tímto oborem vůbec nebyl v TA ČR žádný projekt podpořen.")
|
|
360
|
+
else:
|
|
361
|
+
ford3_list = [ford3 for ford3 in args]
|
|
362
|
+
select_df = self.projects[self.projects['ford_code'].str[:5].isin(ford3_list)].reset_index(
|
|
363
|
+
drop=True)
|
|
364
|
+
return Projects(select_df)
|
|
365
|
+
|
|
366
|
+
else:
|
|
367
|
+
raise ValueError("Lze zvolit pouze úroveň 1,2 nebo 3")
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
#TODO: kde se to vubec bere??? `applicants`
|
|
371
|
+
class Organizations: #! JE TO `Applicants` v ssot
|
|
372
|
+
""" Třída, která načítá a reprezentuje tabulku organizací.
|
|
373
|
+
|
|
374
|
+
Funguje pouze v rámci Google Colab prostředí.
|
|
375
|
+
|
|
376
|
+
:param organizations: DataFrame načtených dat ze zdroje nebo z nově vytvořené (vyfiltrované) instance
|
|
377
|
+
:type organizations: DataFrame
|
|
378
|
+
:param summary_cfp: DataFrame s agregovanými údaji na úrovni veřejných soutěží
|
|
379
|
+
:type summary_cfp: DataFrame
|
|
380
|
+
:param summary_prog: DataFrame s agregovanými údaji na úrovni programů
|
|
381
|
+
:type summary_prog: DataFrame
|
|
382
|
+
:param summary_ico: DataFrame s agregovanými údaji na úrovni organizací
|
|
383
|
+
:type summary_ico: DataFrame
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def __init__(self, df: object = None):
|
|
387
|
+
""" Kontstruktor, který načte data do DataFrame, očistí finanční hodnoty a vytvoří agregovanou tabulku.
|
|
388
|
+
|
|
389
|
+
:param df: dataframe instance Project
|
|
390
|
+
"""
|
|
391
|
+
if df is None:
|
|
392
|
+
self.organizations = self._get_organizations()
|
|
393
|
+
self._data_preparing()
|
|
394
|
+
else:
|
|
395
|
+
self.organizations = df
|
|
396
|
+
|
|
397
|
+
self.summary_cfp = self.create_summary()
|
|
398
|
+
self.summary_prog = self.create_summary('prog')
|
|
399
|
+
self.summary_ico = self.create_summary('ico')
|
|
400
|
+
|
|
401
|
+
#? DONE, , nebudu prejmenovavat, uzivatelsky se to nevola, bordel to tedy nepacha
|
|
402
|
+
def _get_organizations(self) -> pd.DataFrame:
|
|
403
|
+
""" Načte data o organizacích ze "zdroje pravdy" z databázového úložiště BigQuery.
|
|
404
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
405
|
+
|
|
406
|
+
:return: DataFrame načtených dat ze zdroje
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
query = """
|
|
410
|
+
SELECT *
|
|
411
|
+
FROM `sista-data-stream.ssot.applicants`
|
|
412
|
+
"""
|
|
413
|
+
df = client.query(query).to_dataframe()
|
|
414
|
+
return df
|
|
415
|
+
|
|
416
|
+
#? DONE, , watafak (......#TODO: tohle je potreba, tohle se vola hned z initu)
|
|
417
|
+
def _data_preparing(self):
|
|
418
|
+
""" Interní funkce, která doplní potřebná data, které nejsou ve zdroji pravdy v tabulce organizací.
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
self.organizations["kod_program"] = self.organizations["project_code"].str[:2]
|
|
422
|
+
|
|
423
|
+
if ["kod_program"] == 'TG': #! GAMMA ma vyjimku, posrali kody - elegantni reseni: vycist rovnou z ssot.projects
|
|
424
|
+
self.organizations["kod_vs"] = self.organizations["project_code"].str[:6]
|
|
425
|
+
else:
|
|
426
|
+
self.organizations["kod_vs"] = self.organizations["project_code"].str[:4]
|
|
427
|
+
# self.organizations["typeorg_kod"] = self.organizations["typ_organizace"].str[:2]
|
|
428
|
+
self.organizations["typeorg_kod"] = self.organizations["type"] # uz tam davame do nazvu jen ty dvoupismenne kody
|
|
429
|
+
|
|
430
|
+
# map_roles = {"Příjemce": "HP", "Hlavní příjemce": "HP", "Zahraniční partner": "ZU", "Další účastník": "DU"}
|
|
431
|
+
map_roles = {"main": "HP", "foreign-partner": "ZU", "additional": "DU"}
|
|
432
|
+
self.organizations["role_kod"] = self.organizations["role"].map(map_roles) # role v novem: mame foreign-partner, main, additional
|
|
433
|
+
|
|
434
|
+
map_kraj = {
|
|
435
|
+
"Hlavní město Praha": "PH",
|
|
436
|
+
"Středočeský kraj": "ST",
|
|
437
|
+
"Ústecký kraj": "US",
|
|
438
|
+
"Liberecký kraj": "LI",
|
|
439
|
+
"Pardubický kraj": "PA",
|
|
440
|
+
"Královéhradecký kraj": "KR",
|
|
441
|
+
"Karlovarský kraj": "KA",
|
|
442
|
+
"Plzeňský kraj": "PL",
|
|
443
|
+
"Jihočeský kraj": "JC",
|
|
444
|
+
"Kraj Vysočina": "VY",
|
|
445
|
+
"Jihomoravský kraj": "JM",
|
|
446
|
+
"Zlínský kraj": "ZL",
|
|
447
|
+
"Olomoucký kraj": "OL",
|
|
448
|
+
"Moravskoslezský kraj": "MO",
|
|
449
|
+
# "ZAH": "ZP"
|
|
450
|
+
} #TO DO: if zahranicni tak `null`
|
|
451
|
+
# self.organizations["kraj_kod"] = self.organizations["kraj"].map(map_kraj)
|
|
452
|
+
self.organizations["kraj_kod"] = self.organizations["kraj"].map(map_kraj).fillna("ZP")
|
|
453
|
+
|
|
454
|
+
query = """
|
|
455
|
+
SELECT *
|
|
456
|
+
FROM `sista-data-stream.ssot.projects`
|
|
457
|
+
"""
|
|
458
|
+
projekty_df = client.query(query).to_dataframe()
|
|
459
|
+
self.organizations = pd.merge(self.organizations,
|
|
460
|
+
# projekty_df[["kod_projektu", "faze_projektu", "stav_projektu"]], on='kod_projektu',
|
|
461
|
+
projekty_df[["project_code", "project_state", "project_substate"]], on='project_code',
|
|
462
|
+
how="left")
|
|
463
|
+
|
|
464
|
+
#? DONE, , melo by byt beze zmen.
|
|
465
|
+
def _check_missing_items(self, provided_items: tuple, existing_items: list, item_name: str):
|
|
466
|
+
"""Ověří, zda se všechny zadané položky nacházejí v existujícím seznamu.
|
|
467
|
+
|
|
468
|
+
Pokud jsou nalezeny chybějící položky, vyvolá ValueError s chybovou zprávou.
|
|
469
|
+
|
|
470
|
+
:param provided_items: Tuple položek zadaných pro filtrování.
|
|
471
|
+
:param existing_items: Seznam všech unikátních položek nacházejících se v datové sadě.
|
|
472
|
+
:param item_name: Popisný název položky, který se použije v chabové hlášce.
|
|
473
|
+
|
|
474
|
+
:raises ValueError: Pokud alespoň jedna zadaná položka neexistuje v datové sadě.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
missing_items = [item for item in provided_items if item not in existing_items]
|
|
478
|
+
if missing_items:
|
|
479
|
+
raise ValueError(f'{item_name} {missing_items} neexistuje/neexistují.')
|
|
480
|
+
|
|
481
|
+
#TODO: not mentally ready for this yet
|
|
482
|
+
def create_summary(self, level: str = 'cfp') -> pd.DataFrame:
|
|
483
|
+
""" Vytvoří agregovaný souhrn buď na úrovni veřejných soutěží (defaultní),na úrovni programů
|
|
484
|
+
nebo organizací.
|
|
485
|
+
|
|
486
|
+
:param level: určuje, na jaké úrovni se provede agregace
|
|
487
|
+
* 'cfp' (defaultní) - na úrovni veřejných soutěží
|
|
488
|
+
* 'prog' - na úrovni programů
|
|
489
|
+
# 'ico' - na úrovni jednotlivých organizací
|
|
490
|
+
:return: agregovaný DataFrame, který obsahuje:
|
|
491
|
+
* Počet žádostí o podporu
|
|
492
|
+
* Počet účastí v podpořených projektech
|
|
493
|
+
* Náklady organizace/organizací v podpořených projektech
|
|
494
|
+
* Podpora organizace/organizací v podpořených projektech
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
if level not in ['cfp', 'prog', 'ico']:
|
|
498
|
+
raise ValueError('Neexistující forma agregace.')
|
|
499
|
+
|
|
500
|
+
temp_df = self.organizations.copy()
|
|
501
|
+
temp_df['Podpořené'] = temp_df.apply(
|
|
502
|
+
lambda x: 'Ano' if x['faze_projektu'] in ['Realizace', 'Implementace', 'Ukončené'] else 'Ne', axis=1)
|
|
503
|
+
|
|
504
|
+
if level == 'ico':
|
|
505
|
+
submitted = temp_df.groupby('ICO_organizace').agg({'kod_projektu': 'nunique'}).reset_index()
|
|
506
|
+
funded = temp_df[temp_df['Podpořené'] == 'Ano'].groupby('ICO_organizace').agg(
|
|
507
|
+
{'kod_projektu': 'nunique', 'naklady_celkem': 'sum', 'podpora_celkem': 'sum'}).reset_index()
|
|
508
|
+
|
|
509
|
+
summary_df = pd.merge(submitted, funded, how='left', on='ICO_organizace',
|
|
510
|
+
suffixes=('_podane', '_podporene'))
|
|
511
|
+
summary_df.rename(columns={'kod_projektu_podane': 'Podané', 'kod_projektu_podporene': 'Podpořené'},
|
|
512
|
+
inplace=True)
|
|
513
|
+
summary_df.fillna(0, inplace=True)
|
|
514
|
+
summary_df['Podpořené'] = summary_df['Podpořené'].astype(int)
|
|
515
|
+
summary_df = summary_df.sort_values('podpora_celkem', ascending=False).reset_index(drop=True)
|
|
516
|
+
|
|
517
|
+
return summary_df
|
|
518
|
+
|
|
519
|
+
else:
|
|
520
|
+
submitted = temp_df.groupby(['kod_program', 'kod_vs']).agg(
|
|
521
|
+
{'ICO_organizace': 'count', 'naklady_celkem': 'sum', 'podpora_celkem': 'sum'}).reset_index()
|
|
522
|
+
funded = temp_df[temp_df['Podpořené'] == 'Ano'].groupby(['kod_program', 'kod_vs']).agg(
|
|
523
|
+
{'ICO_organizace': 'count', 'naklady_celkem': 'sum', 'podpora_celkem': 'sum'}).reset_index()
|
|
524
|
+
|
|
525
|
+
summary_df = pd.merge(submitted[['kod_program', 'kod_vs', 'ICO_organizace']], funded, how='inner',
|
|
526
|
+
on=['kod_program', 'kod_vs'])
|
|
527
|
+
summary_df.columns = ['kod_program', 'kod_vs', 'Podané', 'Podpořené', 'Náklady', 'Podpora']
|
|
528
|
+
|
|
529
|
+
if level == 'prog':
|
|
530
|
+
summary_df = summary_df.groupby('kod_program').agg('sum', numeric_only=True).reset_index()
|
|
531
|
+
|
|
532
|
+
return summary_df
|
|
533
|
+
|
|
534
|
+
#? DONE, ,
|
|
535
|
+
def select_ico(self, *args: str) -> 'Organizations':
|
|
536
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze konkrétní vybrané organizace na základě zadaného IČ.
|
|
537
|
+
|
|
538
|
+
:param args: IČ organizace/organizací, které se mají vyfiltrovat
|
|
539
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
540
|
+
:raise: ValueError
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
# existing_ico = self.organizations['ICO_organizace'].unique()
|
|
544
|
+
existing_ico = self.organizations['id_number'].unique()
|
|
545
|
+
self._check_missing_items(args, existing_ico, 'Organizace')
|
|
546
|
+
|
|
547
|
+
ico_list = [ico for ico in args]
|
|
548
|
+
select_df = self.organizations[self.organizations['id_number'].isin(ico_list)].reset_index(drop=True)
|
|
549
|
+
return Organizations(select_df)
|
|
550
|
+
|
|
551
|
+
#? DONE, , tohle by nemelo byt potreba delat, tohle je osetrene v `_data_preparing()`
|
|
552
|
+
def select_programme(self, *args: str) -> 'Organizations':
|
|
553
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organizace z vybraných programů.
|
|
554
|
+
|
|
555
|
+
:param args: kódy programů, které se mají vyfiltrovat
|
|
556
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
557
|
+
:raise: ValueError
|
|
558
|
+
"""
|
|
559
|
+
|
|
560
|
+
existing_programmes = self.organizations['kod_program'].unique()
|
|
561
|
+
self._check_missing_items(args, existing_programmes, 'Programy')
|
|
562
|
+
|
|
563
|
+
programme_list = [prog for prog in args]
|
|
564
|
+
select_df = self.organizations[self.organizations['kod_program'].isin(programme_list)].reset_index(drop=True)
|
|
565
|
+
return Organizations(select_df) # todo maybe another class Programms?
|
|
566
|
+
|
|
567
|
+
#? DONE, , again osetreno v `_data_preparing()`
|
|
568
|
+
def select_cfp(self, *args: str) -> 'Organizations':
|
|
569
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organizace z vybraných veřejných soutěží.
|
|
570
|
+
|
|
571
|
+
:param args: kódy veřejných soutěží, které se mají vyfiltrovat
|
|
572
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
573
|
+
:raise: ValueError
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
existing_cfp = self.organizations['kod_vs'].unique()
|
|
577
|
+
self._check_missing_items(args, existing_cfp, 'Veřejné soutěže')
|
|
578
|
+
|
|
579
|
+
cfp_list = [cfp for cfp in args]
|
|
580
|
+
select_df = self.organizations[self.organizations['kod_vs'].isin(cfp_list)].reset_index(drop=True)
|
|
581
|
+
return Organizations(select_df)
|
|
582
|
+
|
|
583
|
+
#? DONE, , project_state to bere diky _data_preparing()
|
|
584
|
+
def select_funded(self) -> 'Organizations':
|
|
585
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organizace v podpořených projektech.
|
|
586
|
+
|
|
587
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
financed_states = ['400', '500', '600']
|
|
591
|
+
# funded_states = ['Realizace', 'Implementace', 'Ukončené'] # realizace: 500, imple+ukoncene: 600
|
|
592
|
+
# select_df = self.organizations[self.organizations['faze_projektu'].isin(financed_states)].reset_index(drop=True)
|
|
593
|
+
select_df = self.organizations[self.organizations['project_state'].isin(financed_states)].reset_index(drop=True)
|
|
594
|
+
return Organizations(select_df)
|
|
595
|
+
|
|
596
|
+
#? DONE, , melo by byt osetrene z `_data_preparing()`
|
|
597
|
+
def select_type(self, *args: str) -> 'Organizations':
|
|
598
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organizace podle vybraného typu organizace.
|
|
599
|
+
|
|
600
|
+
:param args: kódy typu organizací, které se mají vyfiltrovat
|
|
601
|
+
* UP = mikro podnik
|
|
602
|
+
* MP = malý podnik
|
|
603
|
+
* SP = střední podnik
|
|
604
|
+
* VP = velký podnik
|
|
605
|
+
* VO = výzkumná organizace
|
|
606
|
+
* DPO = další právnické osoby veřejného i soukromého práva
|
|
607
|
+
* O = ostatní uchazeči povolení ZD
|
|
608
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
609
|
+
:raise: ValueError
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
# co je typeorg: self.organizations["typeorg_kod"] = self.organizations["type"] # uz tam davame do nazvu jen ty dvoupismenne kody
|
|
613
|
+
existing_orgtype = self.organizations['typeorg_kod'].unique()
|
|
614
|
+
self._check_missing_items(args, existing_orgtype, 'Typy organizace')
|
|
615
|
+
|
|
616
|
+
orgtype_list = [orgtype for orgtype in args]
|
|
617
|
+
select_df = self.organizations[self.organizations['typeorg_kod'].isin(orgtype_list)].reset_index(drop=True)
|
|
618
|
+
return Organizations(select_df)
|
|
619
|
+
|
|
620
|
+
#? DONE, , again `_data_preparing()`
|
|
621
|
+
def select_role(self, *args: str) -> 'Organizations':
|
|
622
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organizace podle vybraného typu role.
|
|
623
|
+
|
|
624
|
+
:param args: kódy rolí, které se mají vyfiltrovat
|
|
625
|
+
* HP = hlavní příjemce
|
|
626
|
+
* DU = další účastník
|
|
627
|
+
* ZU = zahraniční účastník
|
|
628
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
629
|
+
:raise: ValueError
|
|
630
|
+
"""
|
|
631
|
+
# jak to funguje:
|
|
632
|
+
# map_roles = {"main": "HP", "foreign-partner": "ZU", "additional": "DU"}
|
|
633
|
+
# self.organizations["role_kod"] = self.organizations["role"].map(map_roles) # role v novem: mame foreign-partner, main, additional
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
existing_roles = self.organizations['role_kod'].unique()
|
|
637
|
+
self._check_missing_items(args, existing_roles, 'Role')
|
|
638
|
+
|
|
639
|
+
roles_list = [role for role in args]
|
|
640
|
+
select_df = self.organizations[self.organizations['role_kod'].isin(roles_list)].reset_index(drop=True)
|
|
641
|
+
return Organizations(select_df)
|
|
642
|
+
|
|
643
|
+
#? DONE, , again `_data_preparing()`
|
|
644
|
+
def select_region(self, *args: str) -> 'Organizations':
|
|
645
|
+
""" Vyfiltruje tabulku tak, aby obsahovala pouze organiazce podle vybraného kraje.
|
|
646
|
+
|
|
647
|
+
:param args: kódy krajů, které se mají vyfiltrovat
|
|
648
|
+
* PH = Hlavní město Praha
|
|
649
|
+
* ST = Středočeský kraj
|
|
650
|
+
* US = Ústecký kraj
|
|
651
|
+
* LI = Liberecký kraj
|
|
652
|
+
* PA = Pardubický kraj
|
|
653
|
+
* KR = Královéhradecký kraj
|
|
654
|
+
* KA = Karlovarský kraj
|
|
655
|
+
* PL = Plzeňský kraj
|
|
656
|
+
* JC = Jihočeský kraj
|
|
657
|
+
* VY = Kraj Vysočina
|
|
658
|
+
* JM = Jihomoravský kraj
|
|
659
|
+
* ZL = Zlínský kraj
|
|
660
|
+
* OL = Olomoucký kraj
|
|
661
|
+
* MO = Moravskoslezský kraj
|
|
662
|
+
* ZP = ZAH
|
|
663
|
+
:return: nová instance třídy Organizations s vyfiltrovanými údaji
|
|
664
|
+
:raise: ValueError
|
|
665
|
+
"""
|
|
666
|
+
|
|
667
|
+
existing_kraje = self.organizations['kraj_kod'].unique()
|
|
668
|
+
self._check_missing_items(args, existing_kraje, 'Kraje')
|
|
669
|
+
|
|
670
|
+
kraje_list = [kraj for kraj in args]
|
|
671
|
+
select_df = self.organizations[self.organizations['kraj_kod'].isin(kraje_list)].reset_index(drop=True)
|
|
672
|
+
return Organizations(select_df)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
#? DONE, , #TODO: tohle ma vlastni polozku v ssot
|
|
676
|
+
def projects_finance() -> pd.DataFrame:
|
|
677
|
+
""" Načte data o financích projektů z databáze zdroje pravdy v Bigquery.
|
|
678
|
+
Finance jsou v rozdělení po jednotlivých letech.
|
|
679
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
680
|
+
|
|
681
|
+
:return: DataFrame načtených dat ze zdroje
|
|
682
|
+
"""
|
|
683
|
+
# FROM `data-poc-424211.ssot.Projekty_finance_ssot`
|
|
684
|
+
|
|
685
|
+
query = """
|
|
686
|
+
SELECT *
|
|
687
|
+
FROM `sista-data-stream.ssot.projects_finances`
|
|
688
|
+
"""
|
|
689
|
+
df = client.query(query).to_dataframe()
|
|
690
|
+
return df
|
|
691
|
+
|
|
692
|
+
#? DONE, ,
|
|
693
|
+
def organizations_finance() -> pd.DataFrame:
|
|
694
|
+
""" Načte data o financích organizací z databáze zdroje pravdy v Bigquery.
|
|
695
|
+
Finance jsou v rozdělení po jednotlivých letech.
|
|
696
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
697
|
+
|
|
698
|
+
:return: DataFrame načtených dat ze zdroje
|
|
699
|
+
"""
|
|
700
|
+
# FROM `data-poc-424211.ssot.Organizace_finance_ssot`
|
|
701
|
+
|
|
702
|
+
query = """
|
|
703
|
+
SELECT *
|
|
704
|
+
FROM `sista-data-stream.ssot.applicants_finances`
|
|
705
|
+
"""
|
|
706
|
+
df = client.query(query).to_dataframe()
|
|
707
|
+
return df
|
|
708
|
+
|
|
709
|
+
#? DONE, ,
|
|
710
|
+
def results() -> pd.DataFrame:
|
|
711
|
+
""" Načte data o výsledcích projektů z databáze zdroje pravdy v Bigquery.
|
|
712
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
713
|
+
|
|
714
|
+
:return: DataFrame načtených dat ze zdroje
|
|
715
|
+
"""
|
|
716
|
+
# FROM `data-poc-424211.ssot.Vysledky_ssot`
|
|
717
|
+
|
|
718
|
+
query = """
|
|
719
|
+
SELECT *
|
|
720
|
+
FROM `sista-data-stream.ssot.results`
|
|
721
|
+
"""
|
|
722
|
+
df = client.query(query).to_dataframe()
|
|
723
|
+
return df
|
|
724
|
+
|
|
725
|
+
#? DONE, ,
|
|
726
|
+
def cfp() -> pd.DataFrame:
|
|
727
|
+
""" Načte data o veřejných soutěží z databáze zdroje pravdy v Bigquery.
|
|
728
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
729
|
+
|
|
730
|
+
:return: DataFrame načtených dat ze zdroje
|
|
731
|
+
"""
|
|
732
|
+
# FROM `data-poc-424211.ssot.Verejne_souteze_ssot`
|
|
733
|
+
|
|
734
|
+
query = """
|
|
735
|
+
SELECT *
|
|
736
|
+
FROM `sista-data-stream.ssot.cfp`
|
|
737
|
+
"""
|
|
738
|
+
df = client.query(query).to_dataframe()
|
|
739
|
+
return df
|
|
740
|
+
|
|
741
|
+
#? DONE, ,
|
|
742
|
+
def programmes() -> pd.DataFrame:
|
|
743
|
+
""" Načte data o programech z databáze zdroje pravdy v Bigquery.
|
|
744
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
745
|
+
|
|
746
|
+
:return: DataFrame načtených dat ze zdroje
|
|
747
|
+
"""
|
|
748
|
+
# FROM `data-poc-424211.ssot.Programy_ssot`
|
|
749
|
+
|
|
750
|
+
query = """
|
|
751
|
+
SELECT *
|
|
752
|
+
FROM `sista-data-stream.ssot.programmes`
|
|
753
|
+
"""
|
|
754
|
+
df = client.query(query).to_dataframe()
|
|
755
|
+
return df
|
|
756
|
+
|
|
757
|
+
#?, DONE, , solved: internal_data_ISTA (ask Vojta, protoze nic jako `export_projekty` neni nikde)
|
|
758
|
+
def projects_raw_data() -> pd.DataFrame:
|
|
759
|
+
""" Načte kompletní zdrojová data o projektech z databáze zdroje pravdy v Bigquery.
|
|
760
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
761
|
+
|
|
762
|
+
:return: DataFrame načtených dat ze zdroje
|
|
763
|
+
"""
|
|
764
|
+
|
|
765
|
+
# query = """
|
|
766
|
+
# SELECT *
|
|
767
|
+
# FROM `data-poc-424211.ssot_source_tables.Export_projekty`
|
|
768
|
+
# """
|
|
769
|
+
query = """
|
|
770
|
+
SELECT *
|
|
771
|
+
FROM `sista-data-stream.internal_data_ISTA.raw_projects`
|
|
772
|
+
"""
|
|
773
|
+
df = client.query(query).to_dataframe()
|
|
774
|
+
return df
|
|
775
|
+
|
|
776
|
+
#? DONE, , same
|
|
777
|
+
def organizations_raw_data() -> pd.DataFrame:
|
|
778
|
+
""" Načte kompletní zdrojová data o organizacích z databáze zdroje pravdy v Bigquery.
|
|
779
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
780
|
+
|
|
781
|
+
:return: DataFrame načtených dat ze zdroje
|
|
782
|
+
"""
|
|
783
|
+
|
|
784
|
+
# query = """
|
|
785
|
+
# SELECT *
|
|
786
|
+
# FROM `data-poc-424211.ssot_source_tables.Export_ucastnici`
|
|
787
|
+
# """
|
|
788
|
+
query = """
|
|
789
|
+
SELECT *
|
|
790
|
+
FROM `sista-data-stream.internal_data_ISTA.raw_applicants`
|
|
791
|
+
"""
|
|
792
|
+
df = client.query(query).to_dataframe()
|
|
793
|
+
return df
|
|
794
|
+
|
|
795
|
+
#TODO: zbyva prepsat `intersects()`
|
|
796
|
+
class Administrovane_projekty:
|
|
797
|
+
""" Třída, která vypočítává počet administrovaných projektů v určitému časovému úseku.
|
|
798
|
+
|
|
799
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
800
|
+
|
|
801
|
+
:param df_hodnoceno_final: DataFrame počtu hodnocených projektů ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
802
|
+
:type df_hodnoceno_final: DataFrame
|
|
803
|
+
:param df_realizace_final: DataFrame počtu realizovaných projektů ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
804
|
+
:type df_realizace_final: DataFrame
|
|
805
|
+
:param df_implementace_final: DataFrame počtu implementovaných projektů ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
806
|
+
:type df_implementace_final: DataFrame
|
|
807
|
+
:param df_administrovan_vse_final: DataFrame počtu administrovaných projektů ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
808
|
+
:type df_administrovan_vse_final: DataFrame
|
|
809
|
+
:param df_administrovan_bez_impl_final: DataFrame počtu administrovaných projektů (bez implementovaných) ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
810
|
+
:type df_administrovan_bez_impl_final: DataFrame
|
|
811
|
+
:param df_pouze_implementace_final: DataFrame počtu pouze implementovaných projektů (vyloučení projektů, které byly ve stejném období v realizaci nebo hodnocené) ve zvoleném časovém úseku, agregace podle programu nebo VS
|
|
812
|
+
:type df_pouze_implementace_final: DataFrame
|
|
813
|
+
|
|
814
|
+
"""
|
|
815
|
+
|
|
816
|
+
pd.set_option("mode.chained_assignment", None)
|
|
817
|
+
|
|
818
|
+
def __init__(self, agg_col: str, start_period: str, end_period: str):
|
|
819
|
+
"""Konstruktor, který vrací potřebné výstupy ve formě dataframe
|
|
820
|
+
"""
|
|
821
|
+
|
|
822
|
+
self.df = self.intersects(start_period, end_period)
|
|
823
|
+
self.create_output(self.df, agg_col)
|
|
824
|
+
|
|
825
|
+
@staticmethod
|
|
826
|
+
def intersects(start_period, end_period):
|
|
827
|
+
"""připraví data ze ssot načte projekty a přidá termíny ze souboru VS, upraví formáty dat
|
|
828
|
+
vyhodnotí, zda byl projekt hodnocen, realizován nebo implementován
|
|
829
|
+
v případě administrovaných nebo pouze implementovaných projektů započítá každý projekt pouze jednou
|
|
830
|
+
|
|
831
|
+
Lze použít pouze v rámci Google Colab prostředí.
|
|
832
|
+
|
|
833
|
+
:param start_period: začátek intervalu pro výpočet ve formátu 'YYYY-MM-DD'
|
|
834
|
+
:param end_period: konec intervalu pro výpočet ve formátu 'YYYY-MM-DD'
|
|
835
|
+
:return: dataframe s novým sloupcem/sloupci s označením fáze ve kterém se projekt během zadaného intervalu nacházel
|
|
836
|
+
"""
|
|
837
|
+
#! bere uz z nove prepsanych Projects() -> check!!
|
|
838
|
+
vs_data = cfp()
|
|
839
|
+
proj_data = Projects().projects
|
|
840
|
+
proj_data.loc[proj_data["kod_programu"]=="TG", "kod_VS"] = proj_data["kod_projektu"].str[:6]
|
|
841
|
+
df = pd.merge(proj_data, vs_data[["kod_VS","termin_vyhlaseni_vysledku","ukonceni_soutezni_lhuty"]],on="kod_VS",how="left")
|
|
842
|
+
date_cols = ["zacatek_reseni", "konec_reseni", "ukonceni_soutezni_lhuty", "termin_vyhlaseni_vysledku"]
|
|
843
|
+
for col in date_cols:
|
|
844
|
+
df[col]=pd.to_datetime(df[col], format="%d.%m.%Y", errors="coerce")
|
|
845
|
+
|
|
846
|
+
if start_period is None or end_period is None:
|
|
847
|
+
raise ValueError("Musíte zadat oba parametry 'start_period' a 'end_period', pokud vybíráte typ 'period'.")
|
|
848
|
+
try:
|
|
849
|
+
first_day = pd.to_datetime(start_period)
|
|
850
|
+
last_day = pd.to_datetime(end_period)
|
|
851
|
+
first_day_stamp = pd.Timestamp(first_day)
|
|
852
|
+
impl_start = first_day_stamp - pd.DateOffset(months=48) # bereme fixně, že impelemntace trvá 4 roky po skončení projektu
|
|
853
|
+
|
|
854
|
+
df['hodnocen'] = (df["ukonceni_soutezni_lhuty"]<=last_day) & (df["termin_vyhlaseni_vysledku"]>=first_day) | df["termin_vyhlaseni_vysledku"].isna() # upravoval sem na interval
|
|
855
|
+
df['realizace'] = (df["zacatek_reseni"]<=last_day) & (df["konec_reseni"]>=first_day) & ~(df["faze_projektu"] =="Nepodpořené")
|
|
856
|
+
df['implementace'] = (df["konec_reseni"]<last_day) & (df["konec_reseni"] >= impl_start) & ~(df["faze_projektu"] =="Nepodpořené")
|
|
857
|
+
df['administrovan_vse'] = (df["hodnocen"] == True) | (df["realizace"] == True) | (df["implementace"] == True)
|
|
858
|
+
df['administrovan_bez_impl'] = (df["hodnocen"] == True) | (df["realizace"] == True)
|
|
859
|
+
df['pouze_implementace'] = (df["hodnocen"] == False) & (df["realizace"] == False) & (df["implementace"] == True)
|
|
860
|
+
|
|
861
|
+
except ValueError:
|
|
862
|
+
raise ValueError("Parametry 'start_period' a 'end_period' musí být ve správném formátu 'YYYY-MM-DD'.")
|
|
863
|
+
|
|
864
|
+
if last_day < first_day:
|
|
865
|
+
raise ValueError("'end_period' nemůže být dříve než 'start_period'.")
|
|
866
|
+
|
|
867
|
+
return df
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def create_output(self, df:pd.DataFrame, agg_col: str):
|
|
871
|
+
"""vytoří agregovaný souhrn počtu projektů v zadané fázi
|
|
872
|
+
|
|
873
|
+
:param df: určuje fázi, pro kterou chci provést výpočet
|
|
874
|
+
* hodnoceno - počet hodnocených projektů
|
|
875
|
+
* realizace - počet realizovancých projektů
|
|
876
|
+
* implementace - počet implementovaných projektů
|
|
877
|
+
* administrovan_vse - počet administrovaných projektů
|
|
878
|
+
* administrovan_bez_impl - počet administroavných projektů bez implementovaných
|
|
879
|
+
* pouze_implementace - počet implementovaných projektů s vyloučením projektů které byly realizovány nebo hodnoceny
|
|
880
|
+
:param agg_col: určuje agregaci výpočtu
|
|
881
|
+
* kod_programu - na úrovni programů
|
|
882
|
+
* kod_VS - na úrovni VS
|
|
883
|
+
:return: dataframe s počty projektů v zadané fázi
|
|
884
|
+
"""
|
|
885
|
+
|
|
886
|
+
agg_enable = ['kod_programu', 'kod_VS']
|
|
887
|
+
if agg_col not in agg_enable:
|
|
888
|
+
raise ValueError(f"Chyba: Sloupec '{agg_col}' není povolený pro agregaci! Zadejte jeden z těchto sloupců: {agg_enable}")
|
|
889
|
+
|
|
890
|
+
cols_to_process = [
|
|
891
|
+
'hodnocen',
|
|
892
|
+
'realizace',
|
|
893
|
+
'implementace',
|
|
894
|
+
'administrovan_vse',
|
|
895
|
+
'administrovan_bez_impl',
|
|
896
|
+
'pouze_implementace'
|
|
897
|
+
]
|
|
898
|
+
|
|
899
|
+
results = {}
|
|
900
|
+
|
|
901
|
+
for col in cols_to_process:
|
|
902
|
+
df_grouped = df.groupby(agg_col)[col].sum().reset_index()
|
|
903
|
+
total_count = df_grouped[col].sum()
|
|
904
|
+
df_final = pd.concat([df_grouped, pd.DataFrame({agg_col: ['Součet'], col: [total_count]})], ignore_index=True)
|
|
905
|
+
results[col] = df_final
|
|
906
|
+
|
|
907
|
+
self.hodnoceno = results['hodnocen']
|
|
908
|
+
self.realizace = results['realizace']
|
|
909
|
+
self.implementace = results['implementace']
|
|
910
|
+
self.administrovan_vse = results['administrovan_vse']
|
|
911
|
+
self.administrovan_bez_impl = results['administrovan_bez_impl']
|
|
912
|
+
self.pouze_implementace = results['pouze_implementace']
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _get_VOPO_kod(typ, list_item):
|
|
916
|
+
"""Pomocná funkce pro získání typu organizace. Respektive se jedná o agregaci původní typologie do nové"""
|
|
917
|
+
|
|
918
|
+
if typ in list_item:
|
|
919
|
+
return 'PO'
|
|
920
|
+
elif typ == 'VO':
|
|
921
|
+
return 'VO'
|
|
922
|
+
else:
|
|
923
|
+
return 'O'
|
|
924
|
+
|
|
925
|
+
#? DONE, ,
|
|
926
|
+
def podporene_VOPO(from_date, to_date, typ_agg, show_projects=False):
|
|
927
|
+
"""Funkce, která spočítá počet podpořených organizací (unikátně podle IČO) po programech v zadaném roce.
|
|
928
|
+
Organizace jsou dělené na VO - výzkumné organizace a PO - podniky, příp. O - ostatní.
|
|
929
|
+
Používá se do tabulek ve VZ a zprávě pro KR.
|
|
930
|
+
|
|
931
|
+
Použití v Google prostředí.
|
|
932
|
+
|
|
933
|
+
:param from_date: datum začátku intervalu, za který chceme podpořené organizace spočítat, ve formátu 'YYYY-MM-DD'
|
|
934
|
+
:param to_date: datum konce intervalu, za který chceme podpořené organizace spočítat, ve formátu 'YYYY-MM-DD'
|
|
935
|
+
:param typ_agg: typ agregace, 'celkem' počítá účasti nebo 'unikatni' počítá unikátní IČA
|
|
936
|
+
:param show_projects: volitelný parametr, pokud je True, vrací seznam projektů za dané období
|
|
937
|
+
:return: dataframe s počtem podpořených organizací po porogramech rozděleno na VO, PO a ostatní
|
|
938
|
+
"""
|
|
939
|
+
|
|
940
|
+
try:
|
|
941
|
+
from_date = pd.to_datetime(from_date)
|
|
942
|
+
to_date = pd.to_datetime(to_date)
|
|
943
|
+
except ValueError:
|
|
944
|
+
raise ValueError("Parametry 'from_date' a 'to_date' musí být ve správném formátu 'YYYY-MM-DD'.")
|
|
945
|
+
|
|
946
|
+
if from_date > to_date:
|
|
947
|
+
raise ValueError("Začátek intervalu (from_date) nesmí být později než konec intervalu (to_date).")
|
|
948
|
+
|
|
949
|
+
org_podp = Organizations().select_funded().organizations
|
|
950
|
+
projects_podp=Projects().select_funded().projects
|
|
951
|
+
|
|
952
|
+
# org_podp_terminy=pd.merge(org_podp, projects_podp, left_on='kod_projektu', right_on='kod_projektu')
|
|
953
|
+
org_podp_terminy=pd.merge(org_podp, projects_podp, left_on='project_code', right_on='project_code')
|
|
954
|
+
|
|
955
|
+
PO_list = ['MP', 'SP', 'VP', 'UP']
|
|
956
|
+
org_podp_terminy['VOPO_kod'] = org_podp_terminy['typeorg_kod'].apply(lambda typ: _get_VOPO_kod(typ, PO_list)) #? `typeorg` by mel byt zdefinovany z `_data_preparing()`
|
|
957
|
+
# v `VOPO_KOD` je tedy ulozeno 'PO', 'VO', 'O'
|
|
958
|
+
|
|
959
|
+
# filtered_df = org_podp_terminy[(org_podp_terminy['konec_reseni'] >= from_date) & (org_podp_terminy['zacatek_reseni'] <= to_date)]
|
|
960
|
+
filtered_df = org_podp_terminy[(org_podp_terminy['implementation_end'] >= from_date) & (org_podp_terminy['implementation_start'] <= to_date)]
|
|
961
|
+
|
|
962
|
+
if typ_agg == 'celkem':
|
|
963
|
+
# org_prog_typ = filtered_df.groupby(['kod_program', 'VOPO_kod']).agg({'ICO_organizace': 'count'}).reset_index()
|
|
964
|
+
org_prog_typ = filtered_df.groupby(['programme_code', 'VOPO_kod']).agg({'id_number': 'count'}).reset_index()
|
|
965
|
+
elif typ_agg == 'unikatni':
|
|
966
|
+
# org_prog_typ = filtered_df.groupby(['kod_program', 'VOPO_kod']).agg({'ICO_organizace': 'nunique'}).reset_index()
|
|
967
|
+
org_prog_typ = filtered_df.groupby(['programme_code', 'VOPO_kod']).agg({'id_number': 'nunique'}).reset_index()
|
|
968
|
+
else:
|
|
969
|
+
raise ValueError(f'Neznámý typ agregace {typ_agg}, platné typy jsou \'celkem\' a \'unikatni\'')
|
|
970
|
+
|
|
971
|
+
org_prog_typ_pivot = org_prog_typ.pivot(
|
|
972
|
+
# index='kod_program', columns='VOPO_kod', values="ICO_organizace"
|
|
973
|
+
index='programme_code', columns='VOPO_kod', values="id_number"
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
org_prog_typ_pivot = org_prog_typ_pivot.fillna(0)
|
|
977
|
+
org_prog_typ_pivot = org_prog_typ_pivot.astype(int)
|
|
978
|
+
org_prog_typ_pivot = org_prog_typ_pivot[['VO', 'PO','O']]
|
|
979
|
+
|
|
980
|
+
period_str = f"{from_date.strftime('%Y-%m-%d')} - {to_date.strftime('%Y-%m-%d')}"
|
|
981
|
+
new_names = {'PO': f"Počet podpořených podniků v období {period_str}",'VO': f"Počet podpořených VO v období {period_str}: ",'O': f"Počet podpořených ostatních v období {period_str}: "}
|
|
982
|
+
|
|
983
|
+
org_prog_typ_pivot.rename(columns=new_names, inplace=True)
|
|
984
|
+
org_prog_typ_pivot.loc['Celkem'] = org_prog_typ_pivot.sum(numeric_only=True)
|
|
985
|
+
|
|
986
|
+
if show_projects:
|
|
987
|
+
### kdybychom to chteli tridit dle programu, coz ale Michal tvrdi, ze spis nechceme: ###
|
|
988
|
+
# projects_list = filtered_df.groupby('kod_program')['kod_projektu'].apply(lambda x: x.unique().tolist()).reset_index()
|
|
989
|
+
# projects_list.rename(columns={'kod_projektu': 'list_of_projects'}, inplace=True)
|
|
990
|
+
# print(projects_list)
|
|
991
|
+
# projects_list.to_excel('projects_list.xlsx', index=False)
|
|
992
|
+
# print(f"\nSeznam projektu ke kazdemu programu byl ulozen do souboru 'projects_list.xlsx'.\n")
|
|
993
|
+
# unique_projects_df = filtered_df[['kod_projektu']].drop_duplicates().reset_index(drop=True)
|
|
994
|
+
unique_projects_df = filtered_df[['project_code']].drop_duplicates().reset_index(drop=True)
|
|
995
|
+
# unique_projects_df.rename(columns={'kod_projektu': 'Seznam unikátních projektů'}, inplace=True)
|
|
996
|
+
unique_projects_df.rename(columns={'project_code': 'Seznam unikátních projektů'}, inplace=True)
|
|
997
|
+
print(unique_projects_df)
|
|
998
|
+
unique_projects_df.to_excel('unique_projects.xlsx', index=False)
|
|
999
|
+
print(f"\nSeznam unikátních projektů byl uložen do souboru 'unique_projects.xlsx'.\n")
|
|
1000
|
+
|
|
1001
|
+
return org_prog_typ_pivot
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
#! project_state = faze_projektu
|
|
1006
|
+
#! project_substate = stav_projektu
|