audit-labs/tutorials

Learn how to perform data analysis, scripting, automation, and more! data-analysis learning scripting tutorials

Commit 8e4ddbe217

8e4ddbe217639aca48230d90d5d62043acec3fc1

parent: b8c7d24610

Unsigned

cmc <hello@cleberg.net> · 2025-12-24 01:14 UTC

update .gitignore

Layout: unified · split

notebooks/.ipynb_checkpoints/basic_data_analysis-checkpoint.ipynb deleted −720
@@ -1,720 +0,0 @@
1{
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "id": "2bc817b1",
6 "metadata": {},
7 "source": [
8 "\n",
9 "# Basic Data Analysis & Visualization\n",
10 "\n",
11 "This notebook demonstrates how to load a sample dataset, perform quick exploratory analysis, group and pivot the data, and create visualizations.\n",
12 "\n",
13 "We’re using a CSV file from: https://sample-files.com/downloads/data/csv/basic-data.csv\n",
14 " "
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "id": "a92fdfd1",
20 "metadata": {},
21 "source": [
22 "\n",
23 "## Install Dependencies\n",
24 "\n",
25 "If you haven't installed the required libraries, run:\n",
26 "\n",
27 "```bash\n",
28 "pip install pandas matplotlib seaborn\n",
29 "```\n",
30 " "
31 ]
32 },
33 {
34 "cell_type": "code",
35 "execution_count": 1,
36 "id": "b5d7308a",
37 "metadata": {},
38 "outputs": [
39 {
40 "name": "stdout",
41 "output_type": "stream",
42 "text": [
43 "Collecting pandas\n",
44 " Downloading pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl.metadata (91 kB)\n",
45 "Collecting matplotlib\n",
46 " Downloading matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl.metadata (52 kB)\n",
47 "Collecting seaborn\n",
48 " Downloading seaborn-0.13.2-py3-none-any.whl.metadata (5.4 kB)\n",
49 "Collecting numpy>=1.26.0 (from pandas)\n",
50 " Downloading numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl.metadata (6.6 kB)\n",
51 "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/homebrew/Cellar/jupyterlab/4.5.1/libexec/lib/python3.14/site-packages (from pandas) (2.9.0.post0)\n",
52 "Collecting pytz>=2020.1 (from pandas)\n",
53 " Using cached pytz-2025.2-py2.py3-none-any.whl.metadata (22 kB)\n",
54 "Requirement already satisfied: tzdata>=2022.7 in /opt/homebrew/Cellar/jupyterlab/4.5.1/libexec/lib/python3.14/site-packages (from pandas) (2025.3)\n",
55 "Collecting contourpy>=1.0.1 (from matplotlib)\n",
56 " Downloading contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl.metadata (5.5 kB)\n",
57 "Collecting cycler>=0.10 (from matplotlib)\n",
58 " Downloading cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)\n",
59 "Collecting fonttools>=4.22.0 (from matplotlib)\n",
60 " Downloading fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl.metadata (114 kB)\n",
61 "Collecting kiwisolver>=1.3.1 (from matplotlib)\n",
62 " Downloading kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl.metadata (6.3 kB)\n",
63 "Requirement already satisfied: packaging>=20.0 in /opt/homebrew/Cellar/jupyterlab/4.5.1/libexec/lib/python3.14/site-packages (from matplotlib) (25.0)\n",
64 "Collecting pillow>=8 (from matplotlib)\n",
65 " Downloading pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl.metadata (8.8 kB)\n",
66 "Collecting pyparsing>=3 (from matplotlib)\n",
67 " Downloading pyparsing-3.3.1-py3-none-any.whl.metadata (5.6 kB)\n",
68 "Requirement already satisfied: six>=1.5 in /opt/homebrew/Cellar/jupyterlab/4.5.1/libexec/lib/python3.14/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n",
69 "Downloading pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl (10.8 MB)\n",
70 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m10.8/10.8 MB\u001b[0m \u001b[31m5.0 MB/s\u001b[0m \u001b[33m0:00:02\u001b[0m.9 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m:02\u001b[0m\n",
71 "\u001b[?25hDownloading matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl (8.2 MB)\n",
72 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.2/8.2 MB\u001b[0m \u001b[31m6.5 MB/s\u001b[0m \u001b[33m0:00:01\u001b[0mm \u001b[31m6.6 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n",
73 "\u001b[?25hDownloading seaborn-0.13.2-py3-none-any.whl (294 kB)\n",
74 "Downloading contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl (273 kB)\n",
75 "Downloading cycler-0.12.1-py3-none-any.whl (8.3 kB)\n",
76 "Downloading fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl (2.8 MB)\n",
77 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.8/2.8 MB\u001b[0m \u001b[31m6.9 MB/s\u001b[0m \u001b[33m0:00:00\u001b[0m7.6 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n",
78 "\u001b[?25hDownloading kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl (64 kB)\n",
79 "Downloading numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl (5.2 MB)\n",
80 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.2/5.2 MB\u001b[0m \u001b[31m7.6 MB/s\u001b[0m \u001b[33m0:00:00\u001b[0m7.8 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n",
81 "\u001b[?25hDownloading pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl (4.7 MB)\n",
82 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.7/4.7 MB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m \u001b[33m0:00:00\u001b[0m7.9 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n",
83 "\u001b[?25hDownloading pyparsing-3.3.1-py3-none-any.whl (121 kB)\n",
84 "Using cached pytz-2025.2-py2.py3-none-any.whl (509 kB)\n",
85 "Installing collected packages: pytz, pyparsing, pillow, numpy, kiwisolver, fonttools, cycler, pandas, contourpy, matplotlib, seaborn\n",
86 "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m11/11\u001b[0m [seaborn]━\u001b[0m \u001b[32m10/11\u001b[0m [seaborn]atplotlib]\n",
87 "\u001b[1A\u001b[2KSuccessfully installed contourpy-1.3.3 cycler-0.12.1 fonttools-4.61.1 kiwisolver-1.4.9 matplotlib-3.10.8 numpy-2.4.0 pandas-2.3.3 pillow-12.0.0 pyparsing-3.3.1 pytz-2025.2 seaborn-0.13.2\n",
88 "Note: you may need to restart the kernel to use updated packages.\n"
89 ]
90 }
91 ],
92 "source": [
93 "pip install pandas matplotlib seaborn"
94 ]
95 },
96 {
97 "cell_type": "code",
98 "execution_count": 2,
99 "id": "0632140a",
100 "metadata": {},
101 "outputs": [
102 {
103 "name": "stderr",
104 "output_type": "stream",
105 "text": [
106 "Matplotlib is building the font cache; this may take a moment.\n"
107 ]
108 }
109 ],
110 "source": [
111 "import pandas as pd\n",
112 "import matplotlib.pyplot as plt\n",
113 "import seaborn as sns\n",
114 "\n",
115 "# Set plot style\n",
116 "sns.set(style=\"whitegrid\")"
117 ]
118 },
119 {
120 "cell_type": "markdown",
121 "id": "d326b5b6",
122 "metadata": {},
123 "source": [
124 "## Load Dataset"
125 ]
126 },
127 {
128 "cell_type": "code",
129 "execution_count": 3,
130 "id": "5d5de24b",
131 "metadata": {},
132 "outputs": [
133 {
134 "data": {
135 "text/html": [
136 "<div>\n",
137 "<style scoped>\n",
138 " .dataframe tbody tr th:only-of-type {\n",
139 " vertical-align: middle;\n",
140 " }\n",
141 "\n",
142 " .dataframe tbody tr th {\n",
143 " vertical-align: top;\n",
144 " }\n",
145 "\n",
146 " .dataframe thead th {\n",
147 " text-align: right;\n",
148 " }\n",
149 "</style>\n",
150 "<table border=\"1\" class=\"dataframe\">\n",
151 " <thead>\n",
152 " <tr style=\"text-align: right;\">\n",
153 " <th></th>\n",
154 " <th>ID</th>\n",
155 " <th>Name</th>\n",
156 " <th>Age</th>\n",
157 " <th>Country</th>\n",
158 " <th>Email</th>\n",
159 " </tr>\n",
160 " </thead>\n",
161 " <tbody>\n",
162 " <tr>\n",
163 " <th>0</th>\n",
164 " <td>1</td>\n",
165 " <td>Name_1</td>\n",
166 " <td>62</td>\n",
167 " <td>Country_1</td>\n",
168 " <td>email_1@example.com</td>\n",
169 " </tr>\n",
170 " <tr>\n",
171 " <th>1</th>\n",
172 " <td>2</td>\n",
173 " <td>Name_2</td>\n",
174 " <td>48</td>\n",
175 " <td>Country_2</td>\n",
176 " <td>email_2@example.com</td>\n",
177 " </tr>\n",
178 " <tr>\n",
179 " <th>2</th>\n",
180 " <td>3</td>\n",
181 " <td>Name_3</td>\n",
182 " <td>61</td>\n",
183 " <td>Country_3</td>\n",
184 " <td>email_3@example.com</td>\n",
185 " </tr>\n",
186 " <tr>\n",
187 " <th>3</th>\n",
188 " <td>4</td>\n",
189 " <td>Name_4</td>\n",
190 " <td>32</td>\n",
191 " <td>Country_4</td>\n",
192 " <td>email_4@example.com</td>\n",
193 " </tr>\n",
194 " <tr>\n",
195 " <th>4</th>\n",
196 " <td>5</td>\n",
197 " <td>Name_5</td>\n",
198 " <td>69</td>\n",
199 " <td>Country_5</td>\n",
200 " <td>email_5@example.com</td>\n",
201 " </tr>\n",
202 " </tbody>\n",
203 "</table>\n",
204 "</div>"
205 ],
206 "text/plain": [
207 " ID Name Age Country Email\n",
208 "0 1 Name_1 62 Country_1 email_1@example.com\n",
209 "1 2 Name_2 48 Country_2 email_2@example.com\n",
210 "2 3 Name_3 61 Country_3 email_3@example.com\n",
211 "3 4 Name_4 32 Country_4 email_4@example.com\n",
212 "4 5 Name_5 69 Country_5 email_5@example.com"
213 ]
214 },
215 "execution_count": 3,
216 "metadata": {},
217 "output_type": "execute_result"
218 }
219 ],
220 "source": [
221 "# Load the dataset from URL\n",
222 "url = \"https://sample-files.com/downloads/data/csv/basic-data.csv\"\n",
223 "df = pd.read_csv(url, skiprows=1)\n",
224 "df.columns = df.columns.str.strip()\n",
225 "\n",
226 "# Preview the data\n",
227 "df.head()"
228 ]
229 },
230 {
231 "cell_type": "markdown",
232 "id": "e7f7c2e7",
233 "metadata": {},
234 "source": [
235 "## Basic Exploration"
236 ]
237 },
238 {
239 "cell_type": "code",
240 "execution_count": 4,
241 "id": "aca7309e",
242 "metadata": {},
243 "outputs": [
244 {
245 "name": "stdout",
246 "output_type": "stream",
247 "text": [
248 "(100, 5)\n"
249 ]
250 },
251 {
252 "data": {
253 "text/html": [
254 "<div>\n",
255 "<style scoped>\n",
256 " .dataframe tbody tr th:only-of-type {\n",
257 " vertical-align: middle;\n",
258 " }\n",
259 "\n",
260 " .dataframe tbody tr th {\n",
261 " vertical-align: top;\n",
262 " }\n",
263 "\n",
264 " .dataframe thead th {\n",
265 " text-align: right;\n",
266 " }\n",
267 "</style>\n",
268 "<table border=\"1\" class=\"dataframe\">\n",
269 " <thead>\n",
270 " <tr style=\"text-align: right;\">\n",
271 " <th></th>\n",
272 " <th>ID</th>\n",
273 " <th>Name</th>\n",
274 " <th>Age</th>\n",
275 " <th>Country</th>\n",
276 " <th>Email</th>\n",
277 " </tr>\n",
278 " </thead>\n",
279 " <tbody>\n",
280 " <tr>\n",
281 " <th>count</th>\n",
282 " <td>100.000000</td>\n",
283 " <td>100</td>\n",
284 " <td>100.000000</td>\n",
285 " <td>100</td>\n",
286 " <td>100</td>\n",
287 " </tr>\n",
288 " <tr>\n",
289 " <th>unique</th>\n",
290 " <td>NaN</td>\n",
291 " <td>100</td>\n",
292 " <td>NaN</td>\n",
293 " <td>10</td>\n",
294 " <td>100</td>\n",
295 " </tr>\n",
296 " <tr>\n",
297 " <th>top</th>\n",
298 " <td>NaN</td>\n",
299 " <td>Name_1</td>\n",
300 " <td>NaN</td>\n",
301 " <td>Country_1</td>\n",
302 " <td>email_1@example.com</td>\n",
303 " </tr>\n",
304 " <tr>\n",
305 " <th>freq</th>\n",
306 " <td>NaN</td>\n",
307 " <td>1</td>\n",
308 " <td>NaN</td>\n",
309 " <td>10</td>\n",
310 " <td>1</td>\n",
311 " </tr>\n",
312 " <tr>\n",
313 " <th>mean</th>\n",
314 " <td>50.500000</td>\n",
315 " <td>NaN</td>\n",
316 " <td>44.530000</td>\n",
317 " <td>NaN</td>\n",
318 " <td>NaN</td>\n",
319 " </tr>\n",
320 " <tr>\n",
321 " <th>std</th>\n",
322 " <td>29.011492</td>\n",
323 " <td>NaN</td>\n",
324 " <td>15.190012</td>\n",
325 " <td>NaN</td>\n",
326 " <td>NaN</td>\n",
327 " </tr>\n",
328 " <tr>\n",
329 " <th>min</th>\n",
330 " <td>1.000000</td>\n",
331 " <td>NaN</td>\n",
332 " <td>18.000000</td>\n",
333 " <td>NaN</td>\n",
334 " <td>NaN</td>\n",
335 " </tr>\n",
336 " <tr>\n",
337 " <th>25%</th>\n",
338 " <td>25.750000</td>\n",
339 " <td>NaN</td>\n",
340 " <td>32.000000</td>\n",
341 " <td>NaN</td>\n",
342 " <td>NaN</td>\n",
343 " </tr>\n",
344 " <tr>\n",
345 " <th>50%</th>\n",
346 " <td>50.500000</td>\n",
347 " <td>NaN</td>\n",
348 " <td>43.500000</td>\n",
349 " <td>NaN</td>\n",
350 " <td>NaN</td>\n",
351 " </tr>\n",
352 " <tr>\n",
353 " <th>75%</th>\n",
354 " <td>75.250000</td>\n",
355 " <td>NaN</td>\n",
356 " <td>59.250000</td>\n",
357 " <td>NaN</td>\n",
358 " <td>NaN</td>\n",
359 " </tr>\n",
360 " <tr>\n",
361 " <th>max</th>\n",
362 " <td>100.000000</td>\n",
363 " <td>NaN</td>\n",
364 " <td>69.000000</td>\n",
365 " <td>NaN</td>\n",
366 " <td>NaN</td>\n",
367 " </tr>\n",
368 " </tbody>\n",
369 "</table>\n",
370 "</div>"
371 ],
372 "text/plain": [
373 " ID Name Age Country Email\n",
374 "count 100.000000 100 100.000000 100 100\n",
375 "unique NaN 100 NaN 10 100\n",
376 "top NaN Name_1 NaN Country_1 email_1@example.com\n",
377 "freq NaN 1 NaN 10 1\n",
378 "mean 50.500000 NaN 44.530000 NaN NaN\n",
379 "std 29.011492 NaN 15.190012 NaN NaN\n",
380 "min 1.000000 NaN 18.000000 NaN NaN\n",
381 "25% 25.750000 NaN 32.000000 NaN NaN\n",
382 "50% 50.500000 NaN 43.500000 NaN NaN\n",
383 "75% 75.250000 NaN 59.250000 NaN NaN\n",
384 "max 100.000000 NaN 69.000000 NaN NaN"
385 ]
386 },
387 "execution_count": 4,
388 "metadata": {},
389 "output_type": "execute_result"
390 }
391 ],
392 "source": [
393 "# Check shape and summary stats\n",
394 "print(df.shape)\n",
395 "df.describe(include=\"all\")"
396 ]
397 },
398 {
399 "cell_type": "code",
400 "execution_count": 5,
401 "id": "e775e42f",
402 "metadata": {},
403 "outputs": [
404 {
405 "name": "stdout",
406 "output_type": "stream",
407 "text": [
408 " ID Name Age Country Email\n",
409 "0 1 Name_1 62 Country_1 email_1@example.com\n",
410 "1 2 Name_2 48 Country_2 email_2@example.com\n",
411 "2 3 Name_3 61 Country_3 email_3@example.com\n",
412 "3 4 Name_4 32 Country_4 email_4@example.com\n",
413 "4 5 Name_5 69 Country_5 email_5@example.com\n",
414 ".. ... ... ... ... ...\n",
415 "95 96 Name_96 60 Country_6 email_96@example.com\n",
416 "96 97 Name_97 26 Country_7 email_97@example.com\n",
417 "97 98 Name_98 52 Country_8 email_98@example.com\n",
418 "98 99 Name_99 24 Country_9 email_99@example.com\n",
419 "99 100 Name_100 55 Country_0 email_100@example.com\n",
420 "\n",
421 "[100 rows x 5 columns]\n"
422 ]
423 }
424 ],
425 "source": [
426 "print(df)"
427 ]
428 },
429 {
430 "cell_type": "markdown",
431 "id": "dc10be72",
432 "metadata": {},
433 "source": [
434 "## Grouping and Pivoting Data"
435 ]
436 },
437 {
438 "cell_type": "code",
439 "execution_count": 6,
440 "id": "f87ea8af",
441 "metadata": {},
442 "outputs": [
443 {
444 "data": {
445 "text/html": [
446 "<div>\n",
447 "<style scoped>\n",
448 " .dataframe tbody tr th:only-of-type {\n",
449 " vertical-align: middle;\n",
450 " }\n",
451 "\n",
452 " .dataframe tbody tr th {\n",
453 " vertical-align: top;\n",
454 " }\n",
455 "\n",
456 " .dataframe thead th {\n",
457 " text-align: right;\n",
458 " }\n",
459 "</style>\n",
460 "<table border=\"1\" class=\"dataframe\">\n",
461 " <thead>\n",
462 " <tr style=\"text-align: right;\">\n",
463 " <th></th>\n",
464 " <th>Country</th>\n",
465 " <th>RecordCount</th>\n",
466 " </tr>\n",
467 " </thead>\n",
468 " <tbody>\n",
469 " <tr>\n",
470 " <th>0</th>\n",
471 " <td>Country_1</td>\n",
472 " <td>10</td>\n",
473 " </tr>\n",
474 " <tr>\n",
475 " <th>1</th>\n",
476 " <td>Country_2</td>\n",
477 " <td>10</td>\n",
478 " </tr>\n",
479 " <tr>\n",
480 " <th>2</th>\n",
481 " <td>Country_3</td>\n",
482 " <td>10</td>\n",
483 " </tr>\n",
484 " <tr>\n",
485 " <th>3</th>\n",
486 " <td>Country_4</td>\n",
487 " <td>10</td>\n",
488 " </tr>\n",
489 " <tr>\n",
490 " <th>4</th>\n",
491 " <td>Country_5</td>\n",
492 " <td>10</td>\n",
493 " </tr>\n",
494 " </tbody>\n",
495 "</table>\n",
496 "</div>"
497 ],
498 "text/plain": [
499 " Country RecordCount\n",
500 "0 Country_1 10\n",
501 "1 Country_2 10\n",
502 "2 Country_3 10\n",
503 "3 Country_4 10\n",
504 "4 Country_5 10"
505 ]
506 },
507 "execution_count": 6,
508 "metadata": {},
509 "output_type": "execute_result"
510 }
511 ],
512 "source": [
513 "# Example: Group by 'Country' and count number of records\n",
514 "country_counts = df[\"Country\"].value_counts().reset_index()\n",
515 "country_counts.columns = [\"Country\", \"RecordCount\"]\n",
516 "country_counts.head()"
517 ]
518 },
519 {
520 "cell_type": "markdown",
521 "id": "f156d6de",
522 "metadata": {},
523 "source": [
524 "## Visualizing Record Counts by Country"
525 ]
526 },
527 {
528 "cell_type": "code",
529 "execution_count": 7,
530 "id": "d644fc06",
531 "metadata": {},
532 "outputs": [
533 {
534 "name": "stderr",
535 "output_type": "stream",
536 "text": [
537 "/var/folders/z_/x3_6fwb174s0c7wcsd813s6r0000gn/T/ipykernel_10080/1795714619.py:3: FutureWarning: \n",
538 "\n",
539 "Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.\n",
540 "\n",
541 " sns.barplot(data=country_counts, x=\"Country\", y=\"RecordCount\", palette=\"viridis\")\n"
542 ]
543 },
544 {
545 "data": {
546 "image/png": "iVBORw0KGgoAAAANSUhEUgAAA00AAAJUCAYAAADJrc3LAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAASopJREFUeJzt3QeYVOXZP+CXKiCiiIrGimLD2AV7sGvsvcZeY8OgQbFhrKh8ig0/rNhb7DG2iEZjxxg1ihoU7NgoIqC0+V/P+f6z2cXlwC7L7uzOfV/Xsrszs7NnH94zc37nLadZoVAoJAAAAKrVvPqbAQAAEJoAAABmQ08TAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AUIlrvs+a2gDlSmgCKAEHHnhgWnnllat8rLLKKmmdddZJu+++e3r44YdTqfv888+z7X7ggQdm+9ivv/46XXLJJWm77bZLa665Ztpkk03SMccck4YNG5Ya0jPPPJNOPfXU3MecdtppaYsttphn2/Dzzz+nIUOGpD322COtu+66qUePHmnfffdNDz30UIOGlv/85z9pv/32a7DfD9CQWjbobwegQrdu3VK/fv0qvp8+fXoaPXp0dgDdp0+ftNBCC6WePXs2+oq98cYb6bjjjksdO3ZMBx10UOrSpUsaN25cuueee7LweNFFF6Vdd921QbYtat2Qvvvuu3TEEUekr776KqvFGmuskWbMmJGeffbZLKxFqDzvvPNSs2bN6n3bnnjiifTmm2/W++8FKAVCE0CJaN++fVprrbV+cftvfvObtOGGG2Y9OI09NEU4Oumkk9Jyyy2Xbr755tS2bduK+7bddtt01FFHpbPPPjvreVpkkUVSuYlergjKESCjRkWbbbZZ+tWvfpUuu+yytPnmm6ctt9yyQbcToNwYngdQ4uabb77UunXrKr0L0ftw3XXXpa233jr9+te/zgLHbbfd9oufjSFdu+22WzYELg68/+d//idNmTKl4v533nknHX744Wn99dfPhgLGELkYhlX06quvZkPu7r777uxgPR7z4osvZvc99dRTaeedd856Q+J3vP/++7P9W2J7vvnmm3T66adXCUyhefPm6ZRTTkkHHHBA+vHHHytuj9+3//77Z0PVYjtPPvnkrCem6Kqrrsq2cWZxW9xXeejg448/nk488cS09tprZ8Pe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oQkAAKB29DQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAAHUdmvr27Zs+++yzau/7+OOP0zHHHFObpwUAACg5Lef0gV9++WXF1w899FDaaqutUosWLX7xuOeffz699NJLdbeFAAAAjSE0/elPf8oCUdHxxx9f7eMKhULaeOON62brAAAAGktoOvfcc7MepAhFp59+evr973+flllmmSqPad68eerQoUNaf/3158W2AgAAlG5o6ty5c9ptt92yr5s1a5Z69uyZFl544Xm5bQAAAI0nNFUW4Sl6nN577700adKk7OuZde/evS62DwAAoPGFprfffjv16tUrjR49Ovu+GJqiByq+js/Dhw+v2y0FAABoLKHpoosuSi1btsw+L7744tlcJgAAgKaoVqHp3XffTZdddlm27Hh9iWXOr7vuuuz6ULEARaze99vf/rbefj8AAFCeatVF1KlTp2qv0TSvPPzww+mMM85IBxxwQHrsscfSjjvumHr37p3efPPNetsGAACgPNUqNO2///5p8ODB2SIQ81rMkbriiivSQQcdlIWm6GWK5c432mij9Nprr83z3w8AAJS3Wg3P++STT9JHH32UXcR2xRVXTG3atKlyfywEccstt9TJBo4cOTJ98cUXaaeddqpy+4033lgnzw8AADBPQtMqq6xS8f3MS45XtwT53ISmEL1ahx9+eLbM+VJLLZX1Nm2xxRa1es7Yvln1kkXga9u2bSpXkydPrvH/n5rVvGbqpq3V1/6prWlr2lr98R5aPzULjj0cr9VVWyuu/D1PQtNtt92W6suPP/6YfT711FOzxR9OOeWU9OSTT6Zjjz023XzzzWnDDTes8XNOnTp1lkuiR2Dq1q1bKlcRUqNR1YSa1bxm6qat1df+qa1pa9pa/fEeWj81C449HK/VZVtr3br1vAlN9alVq1bZ5+hliovqhlVXXTXrcaptaIrn7Nq1a7X3zUnSbMq6dOlSq56mclabmgV109a0tfrhda1+aha8rnld09bqh9e1uqvZiBEj5ujnaxWaYljc7F4Yn3nmmVQXOnfunH1eaaWVqtweoee5556r1XPGtrdr165Otq+pKeehibWlZuqmrZU2+6iaaWuly/6pbg3d1ub0ZE+tQlOPHj1+8QsmTpyY3nnnnfTzzz+ngw8+ONWV1VZbLc0///zprbfeSuutt17F7R9++GG2kh4AAMC8VKvQ1L9//1nOFYq5RrUZmzorsTLfEUccka655pqs12mNNdbIrtX04osvpiFDhtTZ7wEAAJjnc5pirlBcT6lv377ppJNOqrPnjSAWXWqXX355+vrrr9MKK6yQrrrqqrT++uvX2e8AAACol4Ugxo8fnw3Vq2uHHnpo9gEAAFDyoemhhx76xW3Tp09Po0ePTrfffnuVuUcAAABlF5pOO+20Wd639tprp7POOmtutgkAAKBxh6bqlhOP1fTat2+fOnToUBfbBQAA0HhD05JLLlnx9UcffZQmTJiQOnbsKDABAABNTq0XgvjLX/6SLr744vTdd99V3LbIIoukk08+Oe266651tX0AAACNLzQNHTo0/fGPf0wbbLBB6t27dxaWvvnmm/TII49ky40vtNBCabPNNqv7rQUAAGgMoenaa69N2223XXbdpMr22GOP9Ic//CENHjxYaAIAAJqE5rX5oQ8//DDttttu1d4Xt7///vtzu10AAACNNzTFog9xEdvqjBs3LrVu3XputwsAAKDxhqYNN9wwXX311dnFbCv76quv0jXXXJM23njjuto+AACAxjenKRZ/iPlL22yzTXYx21gIIlbRe/PNN9OCCy6YraAHAABQtj1Niy66aHrwwQfTgQcemCZPnpz+/e9/Z5/j+7i98nWcAAAAyvI6TZ06dcqWHQcAAGjKatTTNG3atHT77benp59+usrt06dPz1bNGzJkSJoxY0ZdbyMAAEDph6apU6emY489Nl1wwQXZ3KXKxowZk4Wl/v37p+OPPz4LUQAAAGUVmu655570yiuvpAEDBqQ+ffr8Yo7Tww8/nIWm559/Pt1///3zYlsBAABKNzQ98MAD6ZBDDkk77LDDLB+z6667pr322ivdd999dbV9AAAAjSM0ffLJJ2mDDTaY7eN69uyZRo0aNbfbBQAA0LhCU8uWLbN5TXPyuGbNms3tdgEAADSu0LTiiiumV199dbaPe+2119JSSy01t9sFAADQuELTLrvsku6666709ttvz/Ix7777brrjjjvSb3/727raPgAAgMZxcds999wz/eUvf0kHHnhg9vVmm22W9SjFUuNffPFFtmrevffem1ZeeeXsMQAAAGUVmmKe0uDBg9OFF16YLT9+5513VtxXKBSyuUyxcl7v3r1TmzZt5tX2AgAAlGZoChGGzj333HTSSSell19+OY0ePTq1aNEiLbnkktnKegsssMC821IAAIBSD01FCy+8cO71mgAAAMouND300EM1euK40C0AAEDZhKbTTjutyvfFazHFfKaZbwtCEwAAUFah6Zlnnqn4evjw4emPf/xjOvbYY7PlxRdbbLE0duzYNHTo0HTVVVeliy66aF5tLwAAQGmGpljsoeiEE07IAtORRx5ZcVvnzp3Tfvvtl6ZMmZIuvfTS1LNnz7rfWgAAgFK9uG1lH330UerWrVu19y2//PLp888/n9vtAgAAaLyhabnllkuPPvpotffFNZxWWmmlud0uAACAxrvk+HHHHZd69eqVRo0alTbffPPUsWPH9N1336WnnnoqjRgxIl1//fV1v6UAAACNJTRts8026ZprrkmDBg1KAwcOzFbQa968eVp77bXTkCFD0nrrrVf3WwoAANBYQtPLL7+cNtpoo7TFFlukn3/+OY0fPz4ttNBCqXXr1nW/hQAAAI1tTlOsnhdD8cJ8882XLTkuMAEAAE1RrUJThw4dUps2bep+awAAAJrC8Lyjjz46nX/++WnkyJFplVVWSe3atfvFY7p3714X2wcAAND4QlO/fv2yz5dffnn2uVmzZhX3xaIQ8f3w4cPrahsBAAAaV2i69dZb635LAAAAmkpo6tGjR91vCQAAQFMJTSHmM1155ZXptddeSz/88EN2gdu4PlNc+HaFFVao260EAABoTKFpxIgRad99900tWrTIrtW0yCKLpG+//TY9++yz6bnnnkv33Xef4AQAAJRvaBowYEBaaqml0m233ZYWWGCBitsnTJiQDj744GyBiKuvvroutxMAAKDxXKfp9ddfT8ccc0yVwBTi+6OOOiq7HwAAoGxDU8uWLdN8881X7X2tW7dOU6ZMmdvtAgAAaLyhafXVV0933nlndk2myuL7O+64I/3617+uq+0DAABoULWa09SrV6+03377pZ133jltt912adFFF80WgnjiiSeyVfVuvvnmut9SAACAxhKaoqfphhtuSP/zP/+TLfgQPUzNmjXLepiuv/761L1797rfUgAAgMZ0naYNNtgg3X333dn8pbhOU4cOHdK0adN+sTgEAABA2c1pmjp1aurXr1/ae++9U9u2bVPnzp3Tm2++mTbccMN08cUXpxkzZtT9lgIAADSW0HTVVVelRx55JO2www4Vt3Xr1i2dcsop6d57782G7gEAAJTt8LxHH300nXrqqWnfffetuG2hhRZKhxxySLYc+a233ppdrwkAAKAse5rGjh2bll566WrvW3755dPo0aPndrsAAAAab2iKYPTkk09We9/QoUPTsssuO7fbBQAA0HiH5x100EHptNNOS+PGjUtbbbVV6tSpUxozZkx69tln0+OPP54uuuiiut9SAACAxhKadt111zRx4sQ0aNCg9NRTT1Xc3rFjx3TWWWdl9wMAAJT1dZoOOOCAtP/++6eRI0dmPU5xnaYYtte8ea1G/AEAAJSkuUo4cVHbCE0ffPBBWnjhhdOoUaNSoVCou60DAABorD1N1157bRo8eHD66aefUrNmzdIaa6yRBg4cmK2sd9NNN2U9TwAAAGXZ03T77bdnF7g99NBDs4vZFnuXfve736XPPvssXXHFFXW9nQAAAI0nNN12223ZxWt79eqVVltttYrbe/bsmU466aRs2XEAAICyDU1ffvll6tGjR7X3xWIQ33333dxuFwAAQOMNTUsssUR68803q73v3//+d3Y/AABA2S4Eseeee2Zzmtq0aZM222yz7LZJkyalJ598MlscIuY6AQAAlG1oOvLII9Pnn3+eBgwYkH2Egw46KFsQYuedd05HH310XW8nAABA4wlNscT4ueeemw477LD0yiuvZBe3XWCBBVL37t1T165d01133ZVd/BYAAKCsQtPzzz+fHnzwwSw07bLLLtlqecstt1zF/cOGDUu77757drFboQkAACir0PTII4+kPn36pFatWqXWrVunxx9/PF155ZVp6623znqazj///PTYY4+lFi1amNMEAACUX2i65ZZb0pprrpluvPHGLDT17ds3XXPNNWnFFVfMQtJXX32VNt1003T66aenLl26zNutBgAAKLXQNGrUqHTeeeel9u3bZ98ff/zxafvtt0/HHntsmjJlSrriiivStttuOy+3FQAAoHRDUywpXvn6S0suuWS2Wl7Lli2zoXudOnWaV9sIAABQ+he3jYAU85WKil//4Q9/EJgAAIAma45D06wstthidbMlAAAATTE0xfLjAAAATVWNrtN0zjnnVCwEEcP1wllnnZXmn3/+XwSpWG0PAACgbEJT9+7dq4SlWd1W3fcAAABNPjTddtttqaGNHDky7b777lnvVnwGAAAo+TlN9WXq1KnplFNOyZY+BwAAqC+NJjRdddVVFfOpAAAA6kujCE2vv/56uueee1L//v0belMAAIAyU6PV8xrCDz/8kPr06ZPOPPPMtMQSS9TJc8ZCFbMa5hcr/7Vt2zaVq8mTJ9d4IQ81q3nN1E1bq6/9U1vT1rS1+uM9tH5qFhx7OF6rq7YWt83JJZRKPjTFMudrr7122mmnnep0ftTw4cOrvS8CU7du3VK5isU2olHVhJrVvGbqpq3V1/6prWlr2lr98R5aPzULjj0cr9VlW2vdunXjDk0PPfRQGjZsWHr00Ufr9HlbtWqVunbtWu195X6x3i5dutSqp6mc1aZmQd20NW2tfnhdq5+aBa9rXte0tfrhda3uajZixIg5+vmSDk33339/+v7779Nmm21W5fZ+/fqlv/71r+mGG26o1fPGi3q7du3qaCublnIemlhbaqZu2lpps4+qmbZWuuyf6tbQbW1OT/aUdGgaMGBA+umnn6rcts0226QTTzwx7bzzzg22XQAAQPko6dDUuXPnam/v1KnTLO8DAAAouyXHAQAAGkpJ9zRV54MPPmjoTQAAAMqIniYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMgh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osXBRVGEgKiTuv3SAQcckLWte++9Nzvo79evX3YiMk6mMfuz/lG32Cfj+OOss87KXtvihFCxp84++l/XXXdd9rp25513ZiNaokYxAiGGhI4dOzZ73au83/J/ol7RKxcBc7755svaW4w+iOO3r776Kh155JHzvG5OnzQh8YYYL/prrrlm9qIfwSle9Jm1OPvapUuXLDAV55nsvffe6bPPPsvO/vBLo0ePTmuttVZ2BjZeuOLNMGp11FFHZWfJYthevCFQ/dyS6J3bcssts/3ztttuS1dddVXFY+JAg/8Tb47zzz9/dvawWJf//Oc/qXfv3lmPwO67754uu+yybPhZBAFzT/4rAtO3335b0eaibnESLfbPqNvAgQPVrZITTzwxe82K/TECZQyfXXrppdP777+f3R8hnVnvp3HgH8cesR/G93HgHyEg9tMrr7xSW5tJHODHSe44yRh1K56MjF6np556Kt19993ZiUmqirZVDEvFMH7JJZekzTffPP3jH//IAnw8Zl4SmpqIOGB4++2306KLLpqdrYghLfGCLzjliwOLESNGZF8Xe0niIC1esByEVS/eECdPnpy9aMWLf7SxDTfcMG2yySZZ+7v99tuzF68YUsV/FXtCYr5cDKHdeOON09lnn52FzP79+6eDDz44Pf7440r2/0XvW5z9jzfJqNGHH35Y0Su3zz77pHXWWSd7o4yDsthfzT35r3gtK/bEfffdd9kJjdg3YxjVuuuum/7+979ngTN6CMq9btF2dt5552yYT3Eea5zYiKFm99xzT/Z91JH8k48xfDZev+LM//rrr5969uyZlltuufT0009nxyRxMFvuba3ySY0YYhbtKoYcxwnbON749a9/nbp27ZrVMV7vguOQVKUGUbvia1vxGKPY5v7yl79kQ5Hnad0KNBnvvPNOYeDAgYUZM2Zk3992222FHXfcsXDKKacURowY0dCbV1KKNXrllVcKvXv3Lnz11VcV9w0dOrSw2mqrFT755JMqPzNp0qRCOSvWbNSoUYXnnnsu+3rcuHGFu+66q/Dzzz9XPO6ss84qbLnllmVfr1mJfXHjjTcujBw5Mvv+vvvuK6yyyiqF3/zmN4XRo0fP8//HxuaKK64o7LbbbtnrWP/+/SvaYbj22msLW221VeH7779v0G0sNbE/brrppoU+ffoU3nvvvUK/fv0KU6ZMqbj/uuuuK2yxxRYVbZD/mj59evb58ccfL2y99daFYcOGZd9XbndUrdc+++xT2GSTTQrrr79+4dhjj61Snmhr8X7wxRdflH3Zim3o5ZdfLqy33nrZ61nR008/nR2vfffdd4X99tuvcPjhh5d9vWYWr/M9evQo9O3bt+K2yscehx12WGHvvfcuzEt6mpqQOEtx7LHHVpzNiUmFcUY2epxi7o6hev9VrFGcdT3nnHOyM4tF0V0ew4Iqz5E444wz0hNPPJHKWbFmyy67bHYWMcRwghjOGGfLYgJ1+P3vf58Nx4ghovxSzDOMoT/FYRkxTjvOakePQAwPMkQvVTlTGEOnovf30UcfzcarRzssDpeKnqhoa9HLzv+J2sT+GIvZvPDCC9l7QpzVjuG0xbrFZOoYjhY9nlRVfN3fYIMNsn002l3x9c9Z/6qiPUW94jUseoNj6GdxDlixFyDmCEdP1Lvvvlv2Ta34Hrrqqqtm829ibk4s1BLvoTGcMXqCYwW9mNP0+eefZ/uoNvd/og7x+v/HP/4xPfvss+nSSy/Nbi/21IWo4SeffJItTDWv6iY0NTHF1ZGKY9kjOMW44g8++CAb+xmrjfBf0c27wAILZF8XDyi+/PLL7I2gOKEwhra8+OKL2eoszPogI2pZHPIYwzJiOBDVi7AZITwOKCIQxEIasTz0DTfckB555BFl+/8HqcV9MoYv7rHHHtkqSZWHS0VgihBf+aRHuSvWJobMRjiKGsaQ2jipUbwvvo/g3rlz5wbe2tIU759xOYBTTjklGypVXIjE8LKqoj0VF3iIucGx8MM777yT3VdcOjtOBsWQsyWXXLLe/x9L+fU/XvvjJNm2226bzcmJRVviNS5EWI/jkphrrc39n2IdYj5wHNc+/PDD6aKLLqoytSKmDcT7aXFxjXnB6nlNVOUVpeJsbIzbjrk7SyyxRENvWskqHlAUJ7TG2YtYfjwWOYhx2REK4gDE+PaqYpJ5rJgUc5riRSvmOMUBhzfJXyq2n1hBL64Nts0222RhKU527LLLLlndYq4T/90n43UsAvgFF1yQ3fbyyy9nB2gxHyfmSkSv8EorraRkM4m2FAdhcfAQIw1iIYg4gRYHs7HaVLS5mBfGrE8ERX3idS0O0CKYxyJLVF+rsN5666Ubb7wxXXzxxemII47ITqDFpSji4H/llVdWukpibmb0OMVHiMWn4mRG9ND99a9/TUsttZTVaKsRoag4uiVWIYy6xXzgeK+4+eabs/00FtmYV5rFGL159uw0uMrLfBavl2Dpz+oV6xMLGcQLfRyoRXCKyYVxgBFnaou9KfxXXJcjDsiiPr/61a+ylafijTNqpq3NumZxIBZnsqu7TpO2Vr0YehErwMW1rpZZZpmsrd10003aWo4Il3H2f8CAAdkwvQhTcfIsVm0sDtlzImjWYtGM2E/jen4x9JhZi6HFsXhGvP7He2cc+McBbIR2bW3WYnGDWOQmTkDGcUcsflM8seE6V9WLhUXee++97KRjDGOM3qYITPP62ENoKgOVdzo74OzFjhhjs1dbbbVs6U+BKV8cdMWBWfRkxpCCmJ8TL1YO/PP3R4Gydm0tDvxjvlwMn419VFubM9HeYrXLOLgozg2zj855cIoeJ+Fy9qKnJA5oYw5TBCbvB3MmTgTFaqBxUiPmWkdbs3/O2etanEyLE9oxumVevx8ITY2EA6z6q1vMk4gJmnFWsRxfuGpas+qCeDm213L8m0uhrZVjT0lN61bd48utvdbm7535Z7S12fN+UDf7p7ZWmq9r5fOK2cgVG0GcKay80EOemUdeluMF+mpTt5gcffzxx1cciJXbRMya1qy6VaXKcdSvfXTe16zYQ1fualq36i7+W277aG32z3J77a+O94N5X7PiHPRy17wRvK4JTY3I0KFD029/+9uKq2vP6dme5557rizPWtSmbpV30nKum7ambqXa1oqPKef9M9hH1UxbK11e15rm65rQ1IjExOdYdSsmkef1HFVuSDEp85hjjklvvvlmKlc1qVtxJy33umlr6qatlTb7qJppa6XL/tlE6zZPL53LXF+VfGbHH398drXoWal81fK77roru+r0k08+WTb/E+qmZtpa6bJ/qpu2Vtrso2qmrc2a0FSCKgeft956qzBhwoSK7z/99NNCz549Cw8++OBsA9M666xTeOKJJwrlQt3UTFsrXfZPddPWSpt9VM20tXxCUwmf5Yngs9VWWxV22GGHwtChQ7PAFHr37l0499xzq7zIlXtgUjc109ZKl/1T3bS10mYfVTNtbfYsOV5CKi+VeOWVV6aPP/44bbrppumVV15Jzz//fOrSpUt2Rfe4unbv3r2zi5+tscYaVZ4jrit06aWXZhf82nbbbVM5UDc109ZKl/1T3bS10mYfVTNtbQ7NQbCinr366quF3XbbrTBixIiKHqQXXnihcNlllxVWX331bF7TmmuumfU2TZkypeIM0bPPPpvdXk49TJWpm5ppa6XL/qlu2lpps4+qmbaWT2hqYKNHj67SLf70008XevToUTjkkEMKEydOLEydOrXK47/44ovCFVdcUdhnn30KG2ywQeGHH36ouG/cuHGFYcOGFcqBuqmZtla67J/qpq2VNvuommlrNWfJ8QY0ZcqUNHDgwHTnnXdm37///vtZN/mKK66Y3n777fTdd9+lli1bZo8rLr34q1/9Kh133HHp1ltvTUsvvXQaNGhQdt+0adPSggsumNZdd93U1Kmbmmlrpcv+qW7aWmmzj6qZtlY7QlMDat26dRaCzj///GyuUsxF6tGjR+rVq1dacskl0+GHH55+/PHH7HERiooX7YrPcVu3bt3SuHHjstsiXJULdVMzba102T/VTVsrbfZRNdPWakdoamAnnHBCWnXVVdO//vWvtNJKK6WFFloorbPOOunMM89M8803X9pjjz2y4BShKIJT0dixY7OFIj7//PPsrFEMtSwn6qZm2lrpsn+qm7ZW2uyjaqat1ZzQ1AAqB5wxY8Zkw+z23HPPdPPNN6chQ4ZkPUkxzO7ss8/OzgjttddeacKECVV6k4o/d8YZZ2SPKV4ZuSlTNzXT1kqX/VPdtLXSZh9VM21t7nci6lHlRR+KizjECnjh8ssvL6y88sqFm2++ueIxr7/+emHTTTctnHjiib94rp9//rlQLtRNzbS10mX/VDdtrbTZR9VMW5t75TMRpkTO8hSvw3TjjTdm115q165dNp9pww03zOYyRY/RxRdfnH3ef//908SJE7PFItZcc81fPF/0MJUDdVMzba102T/VTVsrbfZRNdPW6kgdBC/mQPF6S2HgwIGFddZZp3DeeecVttlmm8IWW2xRuPfee7Oeo3hcLCkePU7du3cvHHfccRU/O23atLKrtbqpmbZWuuyf6qatlTb7qJppa3VHaKpncZ2lI488svDmm29W3Hb00UcXtt1224rgVLxe05VXXllxnabKL3zlSN3UTFsrXfZPddPWSpt9VM20tbknNNWjW265pbDZZpsVdt5558Knn35acXvMaaocnOKitpXNfIHbcqNuaqatlS77p7ppa6XNPqpm2lrdsHrePDTzMuA77bRTatOmTfrggw8qLmQbWrVqla6++uq0/PLLp0suuSS99NJLVX6unK7BFNRNzbS10mX/VDdtrbTZR9VMW5tH6ih8keO2224rPP7449nX48ePL2y11VaF7bffPlsZr/Kwu+hRuuSSS8py7lJ11E3NtLXSZf9UN22ttNlH1Uxbq1tC0zw2evTowqGHHlrYeOONC0OHDs1uGzt2bGHzzTcv7LTTTr8ITkXlHpzUTc20tdJl/1Q3ba202UfVTFure0LTPLoWQuUgFIs+9OrVq9CzZ8/CM888UxGcYtW8XXfdtfDSSy+V/UIP6qat1RdtTc20tdJl/1Q3ba20TS/j41yhaR6e5ansX//6V+GEE07IGlSxx2ncuHGFNdZYo9CnT595tRmNjrqpmbZWuuyf6qatlTb7qJppa/OO0DQPPPbYY4V11103G3pXWSTxI444ovCb3/ym8OKLL2a3TZgwoeyH4qmbtlbf7KNqpq2VLvunumlrpe2xMj3OtXrePLDQQgulNddcM51xxhlp2LBhFbevtdZaafvtt09ff/11Ouqoo9Krr76a2rdvn1q0aJGmT5+eyp26qZm2Vrrsn+qmrZU2+6iaaWvzWEOntqYytnNmb7/9duGwww7LxnNWTuJvvPFGoXfv3oX77ruvySTv2lA3NdPWSpf9U920tdJmH1Uzba3+NYt/5nUwa6riOkvNm/9fZ92jjz6aRo0alb788su08cYbp6233jp99tlnqX///unjjz9O/fr1S127dk0XXHBBWmSRRdK5556b/Vz0MEVPUzlRNzXT1kqX/VPdtLXSZh9VM22tYQhNdSAuSPvAAw+k9dZbL3300Udp8uTJacUVV0wDBw5Mn3/+ebrsssvS3//+9/SrX/0qzT///Nlj44K2kVebNWuWypW6qZm2Vrrsn+qmrZU2+6iaaWv1rAF6t5qUp59+OrtY7bvvvltx21133VXYeeeds2F4ccHaMWPGFF544YXsArfFIXlxezlTNzXT1kqX/VPdtLXSZh9VM22t/rWs75DWmH3//ffp22+/zYbTRU9S+PTTT9MSSyyRVlhhhTR16tSsB2n33XdP48ePT3fffXc2ZC+G5W2yySYVzxND8lq2LJ/Sq5uaaWuly/6pbtpaabOPqpm2VhqsnjeHTj/99NSrV6+06667Zh8333xzdnuEoghI8803XxaYpkyZklq3bp322GOPNHr06Gy43szKaQ6TuqmZtla67J/qpq2VNvuommlrpUNomgOHHnpotpjDYYcdlm699dZsrlIsHR523HHHLBgNHjw4+z4CU4ggtcoqq2TzmMqVuqmZtla67J/qpq2VNvuommlrpaV8xojV0lVXXZUt7DBo0KC08MILV1m5JhZyWGONNdKBBx6YbrrppjRx4sS0zz77pB9//DFdfvnlqU2bNmm11VZL5Ujd1ExbK132T3XT1kqbfVTNtLXSIzTNxnvvvZe22mqrisAUisuMx8p3EYzatm2bjjzyyHTjjTdmPVGdO3dOnTp1yr6Ox1ZeHrRcqJuaaWuly/6pbtpaabOPqpm2VnqEplmIoBOLPvzzn/9MBx98cMVtM4efsWPHpsceeyz17Nkzu1bTu+++mwWmbt26ZY+dNm1aWS36oG5qpq2VLvunumlrpc0+qmbaWukqr+6PGojAExehbd++fXr55Zcrbqsshud17NgxdenSJX399dfZ4yM8/frXv67oYSqnwBTUTc20tdJl/1Q3ba202UfVTFsrXULTLEQgiiXEl19++fTKK6+kkSNH/uIxxQvTxhLicdHaXxS3zIbkBXVTM22tdNk/1U1bK232UTXT1kpX+R3Vz6HifKXjjz8+G1t8ww03ZMP1ZjZmzJg0YcKEbDge6qat2UdLmdc1ddPWSpt9VM20tRLWABfUbXTuvffewmqrrVY44YQTCi+99FJ229SpUwtffPFF4eijjy7svffehWnTpjX0ZpYcdVMzba102T/VTVsrbfZRNdPWSkuz+Kehg1upixI9/fTTqV+/ftmFaWN1vDgbFPOVYgjeLbfckl3YNobpldOFa2dH3dRMWytd9k9109ZKm31UzbS10iI01cCXX36ZXnrppfT2229nC0CsvPLKadttt82CUrmtklcT6qZm2lrpsn+qm7ZW2uyjaqatlQahqQ7oYVK3+qKtqZu2Vtrso2qmrZUu+6e6zQ2hqRbd5cVV8yp/jbrVNW1N3eqLtqZu2lpps4+qmbbW8IQmAACAHJYcBwAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAGpwvRwAyk/Lht4AAJjZO++8k2699db0+uuvpzFjxqTFFlssbbjhhumoo45KSy+9dIMUbNCgQal169bpiCOOaJDfD0DD0dMEQEm544470r777pu+//77dPLJJ6frr78+C0uvvfZa2nPPPdP777/fINt1xRVXpMmTJzfI7wagYelpAqBkvPHGG+mCCy5IBxxwQDrjjDMqbl9//fXTVlttlXbdddd0+umnpwceeKBBtxOA8qKnCYCSceONN6YFFlgg9e7d+xf3Lbzwwum0005LW265ZZo0aVKaPn161iu10047pTXWWCNtttlmacCAAennn3+u+JkDDzww+6js1VdfTSuvvHL2OUQA69atW3rrrbfSPvvsk1ZfffW0+eabZ9tSFI8PV199dcXXV111Vdp6662z23r06JE22WSTdOaZZ2bbMmHChF8M7Vt33XX1VAE0UkITACWzyMI//vGPbO5S27Ztq33M9ttvn4477rjUrl27dPbZZ6eLLroo64G69tprs96p22+/PR177LE1XrBhxowZ6aSTTsqe/7rrrkvrrLNOuuSSS9ILL7yQ3X/PPfdkn2N4YPHr8OWXX6a///3v6fLLL099+/ZNhx56aBbannjiiSrP//DDD2fPPau/C4DSZngeACVh7NixWeBYaqmlZvvYESNGpD//+c/ZnKeY7xQ23njjbMGIPn36pOeffz717Nlzjn93hKwIW3vttVf2ffQKPf300+m5555Lm266aVprrbWy2xdffPGKr8O0adPSqaeemtZbb72K29Zee+0sJBWf65///GcaNWpU6t+/fw2qAUAp0dMEQElo0aJF9jmG3c1OLAoRdthhhyq3x/fxPMWhdzURYacoVsmL4YAxDHB2Vl111Srf77HHHmnYsGHpiy++yL5/8MEHU5cuXao8PwCNi9AEQElYcMEF0/zzz58NeZuVCDHjx4/PPsKiiy5a5f6WLVumjh07/mJO0Zxo06ZNle+bN28+R8P8YpsrKw7Di96m6Dl7/PHH0+67717j7QGgdAhNAJSMWEwheokqL+ZQ2b333ps22GCDiu+//fbbKvdPnTo1G+YXwalo5p6rOek9mhsRorbbbrssLMWcqPh9u+yyyzz9nQDMW0ITACXjsMMOS+PGjUsDBw78xX0RkG666abUtWvXbNW68Nhjj1V5THwfISnmJIX27dun0aNH/2JZ89qInqc5FQtGfPjhh+mWW25JG220UercuXOtficApcFCEACUjFhkoVevXllo+uijj7LrMkWv0X/+859sCfDogYr7VlhhhbTbbrulK6+8MlvGu3v37mn48OHZ8t9xTadYvCHE0uFDhw7NVtnbYostsrlGDz30UK22rUOHDtmiDq+//nqVhR+qE6Et5jHF3KtYWQ+Axk1oAqCk/P73v8+umxTXYLrwwguz+UtLLLFEdh2mY445Jvs6xEVwl1122XT//fen66+/Pls576CDDspWwSv2CsWiDJ9++mm2GMPdd9+dhasIWvvtt1+Ntyt+d1xv6cgjj0x//etfZ/v42N4xY8ZkS6ID0Lg1K9T0YhYAQK54a42V/GKO1umnn65aAI2cniYAqCM//vhjGjJkSHrnnXfSZ599lg488EC1BWgChCYAqCOxbHkMA5wxY0Y2tHDppZdWW4AmwPA8AACAHJYcBwAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAABIs/b/AF1tvKUDoPojAAAAAElFTkSuQmCC",
547 "text/plain": [
548 "<Figure size 1000x600 with 1 Axes>"
549 ]
550 },
551 "metadata": {},
552 "output_type": "display_data"
553 }
554 ],
555 "source": [
556 "# Plot bar chart\n",
557 "plt.figure(figsize=(10, 6))\n",
558 "sns.barplot(data=country_counts, x=\"Country\", y=\"RecordCount\", palette=\"viridis\")\n",
559 "plt.title(\"Record Count by Country\")\n",
560 "plt.xticks(rotation=45)\n",
561 "plt.show()"
562 ]
563 },
564 {
565 "cell_type": "markdown",
566 "id": "887b2525",
567 "metadata": {},
568 "source": [
569 "## Pivot Table Example"
570 ]
571 },
572 {
573 "cell_type": "code",
574 "execution_count": 8,
575 "id": "1e26e06b",
576 "metadata": {},
577 "outputs": [
578 {
579 "data": {
580 "text/html": [
581 "<div>\n",
582 "<style scoped>\n",
583 " .dataframe tbody tr th:only-of-type {\n",
584 " vertical-align: middle;\n",
585 " }\n",
586 "\n",
587 " .dataframe tbody tr th {\n",
588 " vertical-align: top;\n",
589 " }\n",
590 "\n",
591 " .dataframe thead th {\n",
592 " text-align: right;\n",
593 " }\n",
594 "</style>\n",
595 "<table border=\"1\" class=\"dataframe\">\n",
596 " <thead>\n",
597 " <tr style=\"text-align: right;\">\n",
598 " <th></th>\n",
599 " <th>Age</th>\n",
600 " </tr>\n",
601 " <tr>\n",
602 " <th>Country</th>\n",
603 " <th></th>\n",
604 " </tr>\n",
605 " </thead>\n",
606 " <tbody>\n",
607 " <tr>\n",
608 " <th>Country_0</th>\n",
609 " <td>43.7</td>\n",
610 " </tr>\n",
611 " <tr>\n",
612 " <th>Country_1</th>\n",
613 " <td>43.8</td>\n",
614 " </tr>\n",
615 " <tr>\n",
616 " <th>Country_2</th>\n",
617 " <td>51.0</td>\n",
618 " </tr>\n",
619 " <tr>\n",
620 " <th>Country_3</th>\n",
621 " <td>38.2</td>\n",
622 " </tr>\n",
623 " <tr>\n",
624 " <th>Country_4</th>\n",
625 " <td>44.3</td>\n",
626 " </tr>\n",
627 " <tr>\n",
628 " <th>Country_5</th>\n",
629 " <td>48.3</td>\n",
630 " </tr>\n",
631 " <tr>\n",
632 " <th>Country_6</th>\n",
633 " <td>47.3</td>\n",
634 " </tr>\n",
635 " <tr>\n",
636 " <th>Country_7</th>\n",
637 " <td>41.1</td>\n",
638 " </tr>\n",
639 " <tr>\n",
640 " <th>Country_8</th>\n",
641 " <td>40.5</td>\n",
642 " </tr>\n",
643 " <tr>\n",
644 " <th>Country_9</th>\n",
645 " <td>47.1</td>\n",
646 " </tr>\n",
647 " </tbody>\n",
648 "</table>\n",
649 "</div>"
650 ],
651 "text/plain": [
652 " Age\n",
653 "Country \n",
654 "Country_0 43.7\n",
655 "Country_1 43.8\n",
656 "Country_2 51.0\n",
657 "Country_3 38.2\n",
658 "Country_4 44.3\n",
659 "Country_5 48.3\n",
660 "Country_6 47.3\n",
661 "Country_7 41.1\n",
662 "Country_8 40.5\n",
663 "Country_9 47.1"
664 ]
665 },
666 "metadata": {},
667 "output_type": "display_data"
668 }
669 ],
670 "source": [
671 "# If 'Age' exists, average age by country (example only if dataset has relevant column)\n",
672 "if \"Age\" in df.columns:\n",
673 " age_pivot = df.pivot_table(index=\"Country\", values=\"Age\", aggfunc=\"mean\")\n",
674 " display(age_pivot)\n",
675 "else:\n",
676 " print(\"No 'Age' column in dataset to pivot on.\")"
677 ]
678 },
679 {
680 "cell_type": "markdown",
681 "id": "d9793c4e",
682 "metadata": {},
683 "source": [
684 "\n",
685 "## Summary\n",
686 "\n",
687 "In this notebook, we:\n",
688 "- Loaded a sample CSV dataset\n",
689 "- Explored its structure\n",
690 "- Grouped and counted records by country\n",
691 "- Visualized results with a bar chart\n",
692 "- Created a pivot table (if applicable)\n",
693 "\n",
694 "Try modifying this notebook for your own datasets or audit use cases!\n",
695 " "
696 ]
697 }
698 ],
699 "metadata": {
700 "kernelspec": {
701 "display_name": "Python 3 (ipykernel)",
702 "language": "python",
703 "name": "python3"
704 },
705 "language_info": {
706 "codemirror_mode": {
707 "name": "ipython",
708 "version": 3
709 },
710 "file_extension": ".py",
711 "mimetype": "text/x-python",
712 "name": "python",
713 "nbconvert_exporter": "python",
714 "pygments_lexer": "ipython3",
715 "version": "3.14.2"
716 }
717 },
718 "nbformat": 4,
719 "nbformat_minor": 5
720}
notebooks/.ipynb_checkpoints/racf_access_analysis-checkpoint.ipynb deleted −526
@@ -1,526 +0,0 @@
1{
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "id": "4c3a8609",
6 "metadata": {},
7 "source": [
8 "\n",
9 "# RACF Access Report Analysis\n",
10 "\n",
11 "This notebook demonstrates how to parse a RACF-like mainframe access report stored in a fixed-width text format, extract user access details, identify unusual access configurations, and summarize the results for follow-up.\n",
12 "\n",
13 "We'll be working with the file `sample_racf_data.txt`.\n",
14 " "
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "id": "a0b3e2b2",
20 "metadata": {},
21 "source": [
22 "\n",
23 "## Install Dependencies\n",
24 "\n",
25 "If you haven't already installed `pandas`, run:\n",
26 "\n",
27 "```bash\n",
28 "pip install pandas\n",
29 "```\n",
30 " "
31 ]
32 },
33 {
34 "cell_type": "code",
35 "execution_count": 1,
36 "id": "ca527b49",
37 "metadata": {},
38 "outputs": [],
39 "source": [
40 "import pandas as pd\n",
41 "import re"
42 ]
43 },
44 {
45 "cell_type": "markdown",
46 "id": "d1694149",
47 "metadata": {},
48 "source": [
49 "## Load RACF Report"
50 ]
51 },
52 {
53 "cell_type": "code",
54 "execution_count": 2,
55 "id": "4891d98b",
56 "metadata": {},
57 "outputs": [
58 {
59 "name": "stdout",
60 "output_type": "stream",
61 "text": [
62 "LISTGRP *\n",
63 "INFORMATION FOR GROUP PAYROLLB\n",
64 "SUPERIOR GROUP=RESEARCH OWNER=IBMUSER CREATED=06.123\n",
65 "NO INSTALLATION DATA\n",
66 "NO MODEL DATA SET\n",
67 "TERMUACC\n",
68 "NO SUBGROUPS\n",
69 "USER(S)= ACCESS= ACCESS COUNT= UNIVERSAL ACCESS=\n",
70 "IBMUSER JOIN 000000 ALTER\n",
71 "CONNECT ATTRIBUTES=NONE\n",
72 "REVOKE DATE=NONE RESUME DATE=NONE\n",
73 "DAF0 CREATE 000000 READ\n",
74 "CONNECT ATTRIBUTES=NONE\n",
75 "REVOKE DATE=NONE RESUME DATE=NONE\n",
76 "IA0 CREATE 000000 READ\n",
77 "CONNECT ATTRIBUTES=ADSP SPECIAL OPERATIONS\n",
78 "REVOKE DATE=NONE RESUME DATE=NONE\n",
79 "AEH0 CREATE 000000 READ\n",
80 "CONNECT ATTRIBUTES=NONE\n",
81 "REVOKE DATE=NONE RESUME DATE=NONE\n"
82 ]
83 }
84 ],
85 "source": [
86 "with open(\"sample_racf_data.txt\", \"r\") as file:\n",
87 " lines = file.readlines()\n",
88 "\n",
89 "# Preview first 20 lines\n",
90 "for line in lines[:20]:\n",
91 " print(line.strip())"
92 ]
93 },
94 {
95 "cell_type": "markdown",
96 "id": "9d5ee64a",
97 "metadata": {},
98 "source": [
99 "## Parse User Access Records"
100 ]
101 },
102 {
103 "cell_type": "code",
104 "execution_count": 3,
105 "id": "18f06bc9",
106 "metadata": {},
107 "outputs": [
108 {
109 "data": {
110 "text/html": [
111 "<div>\n",
112 "<style scoped>\n",
113 " .dataframe tbody tr th:only-of-type {\n",
114 " vertical-align: middle;\n",
115 " }\n",
116 "\n",
117 " .dataframe tbody tr th {\n",
118 " vertical-align: top;\n",
119 " }\n",
120 "\n",
121 " .dataframe thead th {\n",
122 " text-align: right;\n",
123 " }\n",
124 "</style>\n",
125 "<table border=\"1\" class=\"dataframe\">\n",
126 " <thead>\n",
127 " <tr style=\"text-align: right;\">\n",
128 " <th></th>\n",
129 " <th>Group</th>\n",
130 " <th>User</th>\n",
131 " <th>Access</th>\n",
132 " <th>Access Count</th>\n",
133 " <th>Universal Access</th>\n",
134 " <th>Attributes</th>\n",
135 " </tr>\n",
136 " </thead>\n",
137 " <tbody>\n",
138 " <tr>\n",
139 " <th>0</th>\n",
140 " <td>PAYROLLB</td>\n",
141 " <td>IBMUSER</td>\n",
142 " <td>JOIN</td>\n",
143 " <td>0</td>\n",
144 " <td>ALTER</td>\n",
145 " <td>NONE</td>\n",
146 " </tr>\n",
147 " <tr>\n",
148 " <th>1</th>\n",
149 " <td>PAYROLLB</td>\n",
150 " <td>DAF0</td>\n",
151 " <td>CREATE</td>\n",
152 " <td>0</td>\n",
153 " <td>READ</td>\n",
154 " <td>NONE</td>\n",
155 " </tr>\n",
156 " <tr>\n",
157 " <th>2</th>\n",
158 " <td>PAYROLLB</td>\n",
159 " <td>IA0</td>\n",
160 " <td>CREATE</td>\n",
161 " <td>0</td>\n",
162 " <td>READ</td>\n",
163 " <td>ADSP SPECIAL OPERATIONS</td>\n",
164 " </tr>\n",
165 " <tr>\n",
166 " <th>3</th>\n",
167 " <td>PAYROLLB</td>\n",
168 " <td>AEH0</td>\n",
169 " <td>CREATE</td>\n",
170 " <td>0</td>\n",
171 " <td>READ</td>\n",
172 " <td>NONE</td>\n",
173 " </tr>\n",
174 " <tr>\n",
175 " <th>4</th>\n",
176 " <td>RESEARCH</td>\n",
177 " <td>IBMUSER</td>\n",
178 " <td>JOIN</td>\n",
179 " <td>0</td>\n",
180 " <td>ALTER</td>\n",
181 " <td>NONE</td>\n",
182 " </tr>\n",
183 " </tbody>\n",
184 "</table>\n",
185 "</div>"
186 ],
187 "text/plain": [
188 " Group User Access Access Count Universal Access \\\n",
189 "0 PAYROLLB IBMUSER JOIN 0 ALTER \n",
190 "1 PAYROLLB DAF0 CREATE 0 READ \n",
191 "2 PAYROLLB IA0 CREATE 0 READ \n",
192 "3 PAYROLLB AEH0 CREATE 0 READ \n",
193 "4 RESEARCH IBMUSER JOIN 0 ALTER \n",
194 "\n",
195 " Attributes \n",
196 "0 NONE \n",
197 "1 NONE \n",
198 "2 ADSP SPECIAL OPERATIONS \n",
199 "3 NONE \n",
200 "4 NONE "
201 ]
202 },
203 "execution_count": 3,
204 "metadata": {},
205 "output_type": "execute_result"
206 }
207 ],
208 "source": [
209 "# Initialize lists to hold parsed records\n",
210 "records = []\n",
211 "current_group = \"\"\n",
212 "\n",
213 "for i, line in enumerate(lines):\n",
214 " if \"INFORMATION FOR GROUP\" in line:\n",
215 " current_group = line.strip().split()[-1]\n",
216 "\n",
217 " # Identify user lines: starts with a non-empty, non-space string followed by access keywords\n",
218 " match = re.match(r\"^\\s*(\\S+)\\s+(JOIN|CREATE|CONNECT|USE)\\s+(\\d{6})\\s+(\\S+)\", line)\n",
219 " if match:\n",
220 " user, access, access_count, universal_access = match.groups()\n",
221 "\n",
222 " # Look ahead for CONNECT ATTRIBUTES line\n",
223 " attr_line = lines[i + 1].strip() if (i + 1) < len(lines) else \"\"\n",
224 " attr_match = re.search(r\"CONNECT ATTRIBUTES=(.*)\", attr_line)\n",
225 " attributes = attr_match.group(1) if attr_match else \"NONE\"\n",
226 "\n",
227 " records.append(\n",
228 " {\n",
229 " \"Group\": current_group,\n",
230 " \"User\": user,\n",
231 " \"Access\": access,\n",
232 " \"Access Count\": int(access_count),\n",
233 " \"Universal Access\": universal_access,\n",
234 " \"Attributes\": attributes,\n",
235 " }\n",
236 " )\n",
237 "\n",
238 "# Convert to DataFrame\n",
239 "df = pd.DataFrame(records)\n",
240 "df.head()"
241 ]
242 },
243 {
244 "cell_type": "markdown",
245 "id": "ab5546a6",
246 "metadata": {},
247 "source": [
248 "## Analyze Access Data"
249 ]
250 },
251 {
252 "cell_type": "code",
253 "execution_count": 4,
254 "id": "d1b8269a",
255 "metadata": {},
256 "outputs": [
257 {
258 "name": "stdout",
259 "output_type": "stream",
260 "text": [
261 "Access\n",
262 "CREATE 5\n",
263 "JOIN 4\n",
264 "USE 3\n",
265 "CONNECT 1\n",
266 "Name: count, dtype: int64\n"
267 ]
268 },
269 {
270 "data": {
271 "text/html": [
272 "<div>\n",
273 "<style scoped>\n",
274 " .dataframe tbody tr th:only-of-type {\n",
275 " vertical-align: middle;\n",
276 " }\n",
277 "\n",
278 " .dataframe tbody tr th {\n",
279 " vertical-align: top;\n",
280 " }\n",
281 "\n",
282 " .dataframe thead th {\n",
283 " text-align: right;\n",
284 " }\n",
285 "</style>\n",
286 "<table border=\"1\" class=\"dataframe\">\n",
287 " <thead>\n",
288 " <tr style=\"text-align: right;\">\n",
289 " <th></th>\n",
290 " <th>Group</th>\n",
291 " <th>User</th>\n",
292 " <th>Access</th>\n",
293 " <th>Access Count</th>\n",
294 " <th>Universal Access</th>\n",
295 " <th>Attributes</th>\n",
296 " </tr>\n",
297 " </thead>\n",
298 " <tbody>\n",
299 " <tr>\n",
300 " <th>0</th>\n",
301 " <td>PAYROLLB</td>\n",
302 " <td>IBMUSER</td>\n",
303 " <td>JOIN</td>\n",
304 " <td>0</td>\n",
305 " <td>ALTER</td>\n",
306 " <td>NONE</td>\n",
307 " </tr>\n",
308 " <tr>\n",
309 " <th>2</th>\n",
310 " <td>PAYROLLB</td>\n",
311 " <td>IA0</td>\n",
312 " <td>CREATE</td>\n",
313 " <td>0</td>\n",
314 " <td>READ</td>\n",
315 " <td>ADSP SPECIAL OPERATIONS</td>\n",
316 " </tr>\n",
317 " <tr>\n",
318 " <th>4</th>\n",
319 " <td>RESEARCH</td>\n",
320 " <td>IBMUSER</td>\n",
321 " <td>JOIN</td>\n",
322 " <td>0</td>\n",
323 " <td>ALTER</td>\n",
324 " <td>NONE</td>\n",
325 " </tr>\n",
326 " <tr>\n",
327 " <th>6</th>\n",
328 " <td>RESEARCH</td>\n",
329 " <td>IA0</td>\n",
330 " <td>CONNECT</td>\n",
331 " <td>4</td>\n",
332 " <td>READ</td>\n",
333 " <td>ADSP SPECIAL OPERATIONS</td>\n",
334 " </tr>\n",
335 " </tbody>\n",
336 "</table>\n",
337 "</div>"
338 ],
339 "text/plain": [
340 " Group User Access Access Count Universal Access \\\n",
341 "0 PAYROLLB IBMUSER JOIN 0 ALTER \n",
342 "2 PAYROLLB IA0 CREATE 0 READ \n",
343 "4 RESEARCH IBMUSER JOIN 0 ALTER \n",
344 "6 RESEARCH IA0 CONNECT 4 READ \n",
345 "\n",
346 " Attributes \n",
347 "0 NONE \n",
348 "2 ADSP SPECIAL OPERATIONS \n",
349 "4 NONE \n",
350 "6 ADSP SPECIAL OPERATIONS "
351 ]
352 },
353 "execution_count": 4,
354 "metadata": {},
355 "output_type": "execute_result"
356 }
357 ],
358 "source": [
359 "# Count users by Access type\n",
360 "access_summary = df[\"Access\"].value_counts()\n",
361 "print(access_summary)\n",
362 "\n",
363 "# Identify users with ALTER access or SPECIAL OPERATIONS attribute\n",
364 "anomalies = df[\n",
365 " (df[\"Universal Access\"] == \"ALTER\")\n",
366 " | (df[\"Attributes\"].str.contains(\"SPECIAL OPERATIONS\"))\n",
367 "]\n",
368 "\n",
369 "anomalies"
370 ]
371 },
372 {
373 "cell_type": "markdown",
374 "id": "38201382",
375 "metadata": {},
376 "source": [
377 "## Prepare Follow-Up Report"
378 ]
379 },
380 {
381 "cell_type": "code",
382 "execution_count": 5,
383 "id": "a885710c",
384 "metadata": {},
385 "outputs": [
386 {
387 "data": {
388 "text/html": [
389 "<div>\n",
390 "<style scoped>\n",
391 " .dataframe tbody tr th:only-of-type {\n",
392 " vertical-align: middle;\n",
393 " }\n",
394 "\n",
395 " .dataframe tbody tr th {\n",
396 " vertical-align: top;\n",
397 " }\n",
398 "\n",
399 " .dataframe thead th {\n",
400 " text-align: right;\n",
401 " }\n",
402 "</style>\n",
403 "<table border=\"1\" class=\"dataframe\">\n",
404 " <thead>\n",
405 " <tr style=\"text-align: right;\">\n",
406 " <th></th>\n",
407 " <th>Group</th>\n",
408 " <th>User</th>\n",
409 " <th>Access</th>\n",
410 " <th>Universal Access</th>\n",
411 " <th>Attributes</th>\n",
412 " <th>Notes</th>\n",
413 " </tr>\n",
414 " </thead>\n",
415 " <tbody>\n",
416 " <tr>\n",
417 " <th>0</th>\n",
418 " <td>PAYROLLB</td>\n",
419 " <td>IBMUSER</td>\n",
420 " <td>JOIN</td>\n",
421 " <td>ALTER</td>\n",
422 " <td>NONE</td>\n",
423 " <td>Review access appropriateness with system owner</td>\n",
424 " </tr>\n",
425 " <tr>\n",
426 " <th>1</th>\n",
427 " <td>PAYROLLB</td>\n",
428 " <td>IA0</td>\n",
429 " <td>CREATE</td>\n",
430 " <td>READ</td>\n",
431 " <td>ADSP SPECIAL OPERATIONS</td>\n",
432 " <td>Review access appropriateness with system owner</td>\n",
433 " </tr>\n",
434 " <tr>\n",
435 " <th>2</th>\n",
436 " <td>RESEARCH</td>\n",
437 " <td>IBMUSER</td>\n",
438 " <td>JOIN</td>\n",
439 " <td>ALTER</td>\n",
440 " <td>NONE</td>\n",
441 " <td>Review access appropriateness with system owner</td>\n",
442 " </tr>\n",
443 " <tr>\n",
444 " <th>3</th>\n",
445 " <td>RESEARCH</td>\n",
446 " <td>IA0</td>\n",
447 " <td>CONNECT</td>\n",
448 " <td>READ</td>\n",
449 " <td>ADSP SPECIAL OPERATIONS</td>\n",
450 " <td>Review access appropriateness with system owner</td>\n",
451 " </tr>\n",
452 " </tbody>\n",
453 "</table>\n",
454 "</div>"
455 ],
456 "text/plain": [
457 " Group User Access Universal Access Attributes \\\n",
458 "0 PAYROLLB IBMUSER JOIN ALTER NONE \n",
459 "1 PAYROLLB IA0 CREATE READ ADSP SPECIAL OPERATIONS \n",
460 "2 RESEARCH IBMUSER JOIN ALTER NONE \n",
461 "3 RESEARCH IA0 CONNECT READ ADSP SPECIAL OPERATIONS \n",
462 "\n",
463 " Notes \n",
464 "0 Review access appropriateness with system owner \n",
465 "1 Review access appropriateness with system owner \n",
466 "2 Review access appropriateness with system owner \n",
467 "3 Review access appropriateness with system owner "
468 ]
469 },
470 "execution_count": 5,
471 "metadata": {},
472 "output_type": "execute_result"
473 }
474 ],
475 "source": [
476 "# Create a concise follow-up report\n",
477 "follow_up = anomalies[\n",
478 " [\"Group\", \"User\", \"Access\", \"Universal Access\", \"Attributes\"]\n",
479 "].copy()\n",
480 "follow_up[\"Notes\"] = \"Review access appropriateness with system owner\"\n",
481 "\n",
482 "follow_up.reset_index(drop=True, inplace=True)\n",
483 "follow_up"
484 ]
485 },
486 {
487 "cell_type": "markdown",
488 "id": "0158f787",
489 "metadata": {},
490 "source": [
491 "\n",
492 "## Summary\n",
493 "\n",
494 "In this notebook, we:\n",
495 "- Parsed a RACF-like access report from a fixed-width text file\n",
496 "- Extracted key fields into a structured DataFrame\n",
497 "- Analyzed access configurations for high-risk permissions\n",
498 "- Summarized anomalies requiring follow-up with system owners\n",
499 "\n",
500 "Use this as a starting point for mainframe audit automation projects!\n",
501 " "
502 ]
503 }
504 ],
505 "metadata": {
506 "kernelspec": {
507 "display_name": "Python 3 (ipykernel)",
508 "language": "python",
509 "name": "python3"
510 },
511 "language_info": {
512 "codemirror_mode": {
513 "name": "ipython",
514 "version": 3
515 },
516 "file_extension": ".py",
517 "mimetype": "text/x-python",
518 "name": "python",
519 "nbconvert_exporter": "python",
520 "pygments_lexer": "ipython3",
521 "version": "3.14.2"
522 }
523 },
524 "nbformat": 4,
525 "nbformat_minor": 5
526}