audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit. audit automation compliance evidence scripts

Commit 212dc41655

212dc416554a66690b9a902ed22cfec0fdbbf09b

parent: d7a0551830

Unsigned

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

move notebooks to new tutorials repo

Layout: unified Β· split

notebooks/basic_data_analysis.ipynb deleted βˆ’688
@@ -1,688 +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": 15,
36 "id": "b5d7308a",
37 "metadata": {},
38 "outputs": [
39 {
40 "name": "stdout",
41 "output_type": "stream",
42 "text": [
43 "Defaulting to user installation because normal site-packages is not writeable\n",
44 "Requirement already satisfied: pandas in /Users/cmc/Library/Python/3.9/lib/python/site-packages (2.2.3)\n",
45 "Requirement already satisfied: matplotlib in /Users/cmc/Library/Python/3.9/lib/python/site-packages (3.9.4)\n",
46 "Requirement already satisfied: seaborn in /Users/cmc/Library/Python/3.9/lib/python/site-packages (0.13.2)\n",
47 "Requirement already satisfied: numpy>=1.22.4 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from pandas) (1.26.4)\n",
48 "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from pandas) (2.9.0.post0)\n",
49 "Requirement already satisfied: pytz>=2020.1 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from pandas) (2025.2)\n",
50 "Requirement already satisfied: tzdata>=2022.7 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from pandas) (2025.2)\n",
51 "Requirement already satisfied: contourpy>=1.0.1 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (1.3.0)\n",
52 "Requirement already satisfied: cycler>=0.10 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (0.12.1)\n",
53 "Requirement already satisfied: fonttools>=4.22.0 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (4.58.1)\n",
54 "Requirement already satisfied: kiwisolver>=1.3.1 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (1.4.7)\n",
55 "Requirement already satisfied: packaging>=20.0 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (24.2)\n",
56 "Requirement already satisfied: pillow>=8 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (11.2.1)\n",
57 "Requirement already satisfied: pyparsing>=2.3.1 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (3.2.3)\n",
58 "Requirement already satisfied: importlib-resources>=3.2.0 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from matplotlib) (6.5.2)\n",
59 "Requirement already satisfied: zipp>=3.1.0 in /Users/cmc/Library/Python/3.9/lib/python/site-packages (from importlib-resources>=3.2.0->matplotlib) (3.21.0)\n",
60 "Requirement already satisfied: six>=1.5 in /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/site-packages (from python-dateutil>=2.8.2->pandas) (1.15.0)\n",
61 "\n",
62 "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.1.1\u001b[0m\n",
63 "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49m/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip\u001b[0m\n",
64 "Note: you may need to restart the kernel to use updated packages.\n"
65 ]
66 }
67 ],
68 "source": [
69 "pip install pandas matplotlib seaborn"
70 ]
71 },
72 {
73 "cell_type": "code",
74 "execution_count": 16,
75 "id": "0632140a",
76 "metadata": {},
77 "outputs": [],
78 "source": [
79 "import pandas as pd\n",
80 "import matplotlib.pyplot as plt\n",
81 "import seaborn as sns\n",
82 "\n",
83 "# Set plot style\n",
84 "sns.set(style=\"whitegrid\")"
85 ]
86 },
87 {
88 "cell_type": "markdown",
89 "id": "d326b5b6",
90 "metadata": {},
91 "source": [
92 "## πŸ“‚ Load Dataset"
93 ]
94 },
95 {
96 "cell_type": "code",
97 "execution_count": 21,
98 "id": "5d5de24b",
99 "metadata": {},
100 "outputs": [
101 {
102 "data": {
103 "text/html": [
104 "<div>\n",
105 "<style scoped>\n",
106 " .dataframe tbody tr th:only-of-type {\n",
107 " vertical-align: middle;\n",
108 " }\n",
109 "\n",
110 " .dataframe tbody tr th {\n",
111 " vertical-align: top;\n",
112 " }\n",
113 "\n",
114 " .dataframe thead th {\n",
115 " text-align: right;\n",
116 " }\n",
117 "</style>\n",
118 "<table border=\"1\" class=\"dataframe\">\n",
119 " <thead>\n",
120 " <tr style=\"text-align: right;\">\n",
121 " <th></th>\n",
122 " <th>ID</th>\n",
123 " <th>Name</th>\n",
124 " <th>Age</th>\n",
125 " <th>Country</th>\n",
126 " <th>Email</th>\n",
127 " </tr>\n",
128 " </thead>\n",
129 " <tbody>\n",
130 " <tr>\n",
131 " <th>0</th>\n",
132 " <td>1</td>\n",
133 " <td>Name_1</td>\n",
134 " <td>62</td>\n",
135 " <td>Country_1</td>\n",
136 " <td>email_1@example.com</td>\n",
137 " </tr>\n",
138 " <tr>\n",
139 " <th>1</th>\n",
140 " <td>2</td>\n",
141 " <td>Name_2</td>\n",
142 " <td>48</td>\n",
143 " <td>Country_2</td>\n",
144 " <td>email_2@example.com</td>\n",
145 " </tr>\n",
146 " <tr>\n",
147 " <th>2</th>\n",
148 " <td>3</td>\n",
149 " <td>Name_3</td>\n",
150 " <td>61</td>\n",
151 " <td>Country_3</td>\n",
152 " <td>email_3@example.com</td>\n",
153 " </tr>\n",
154 " <tr>\n",
155 " <th>3</th>\n",
156 " <td>4</td>\n",
157 " <td>Name_4</td>\n",
158 " <td>32</td>\n",
159 " <td>Country_4</td>\n",
160 " <td>email_4@example.com</td>\n",
161 " </tr>\n",
162 " <tr>\n",
163 " <th>4</th>\n",
164 " <td>5</td>\n",
165 " <td>Name_5</td>\n",
166 " <td>69</td>\n",
167 " <td>Country_5</td>\n",
168 " <td>email_5@example.com</td>\n",
169 " </tr>\n",
170 " </tbody>\n",
171 "</table>\n",
172 "</div>"
173 ],
174 "text/plain": [
175 " ID Name Age Country Email\n",
176 "0 1 Name_1 62 Country_1 email_1@example.com\n",
177 "1 2 Name_2 48 Country_2 email_2@example.com\n",
178 "2 3 Name_3 61 Country_3 email_3@example.com\n",
179 "3 4 Name_4 32 Country_4 email_4@example.com\n",
180 "4 5 Name_5 69 Country_5 email_5@example.com"
181 ]
182 },
183 "execution_count": 21,
184 "metadata": {},
185 "output_type": "execute_result"
186 }
187 ],
188 "source": [
189 "# Load the dataset from URL\n",
190 "url = \"https://sample-files.com/downloads/data/csv/basic-data.csv\"\n",
191 "df = pd.read_csv(url, skiprows=1)\n",
192 "df.columns = df.columns.str.strip()\n",
193 "\n",
194 "# Preview the data\n",
195 "df.head()"
196 ]
197 },
198 {
199 "cell_type": "markdown",
200 "id": "e7f7c2e7",
201 "metadata": {},
202 "source": [
203 "## πŸ“Š Basic Exploration"
204 ]
205 },
206 {
207 "cell_type": "code",
208 "execution_count": 22,
209 "id": "aca7309e",
210 "metadata": {},
211 "outputs": [
212 {
213 "name": "stdout",
214 "output_type": "stream",
215 "text": [
216 "(100, 5)\n"
217 ]
218 },
219 {
220 "data": {
221 "text/html": [
222 "<div>\n",
223 "<style scoped>\n",
224 " .dataframe tbody tr th:only-of-type {\n",
225 " vertical-align: middle;\n",
226 " }\n",
227 "\n",
228 " .dataframe tbody tr th {\n",
229 " vertical-align: top;\n",
230 " }\n",
231 "\n",
232 " .dataframe thead th {\n",
233 " text-align: right;\n",
234 " }\n",
235 "</style>\n",
236 "<table border=\"1\" class=\"dataframe\">\n",
237 " <thead>\n",
238 " <tr style=\"text-align: right;\">\n",
239 " <th></th>\n",
240 " <th>ID</th>\n",
241 " <th>Name</th>\n",
242 " <th>Age</th>\n",
243 " <th>Country</th>\n",
244 " <th>Email</th>\n",
245 " </tr>\n",
246 " </thead>\n",
247 " <tbody>\n",
248 " <tr>\n",
249 " <th>count</th>\n",
250 " <td>100.000000</td>\n",
251 " <td>100</td>\n",
252 " <td>100.000000</td>\n",
253 " <td>100</td>\n",
254 " <td>100</td>\n",
255 " </tr>\n",
256 " <tr>\n",
257 " <th>unique</th>\n",
258 " <td>NaN</td>\n",
259 " <td>100</td>\n",
260 " <td>NaN</td>\n",
261 " <td>10</td>\n",
262 " <td>100</td>\n",
263 " </tr>\n",
264 " <tr>\n",
265 " <th>top</th>\n",
266 " <td>NaN</td>\n",
267 " <td>Name_1</td>\n",
268 " <td>NaN</td>\n",
269 " <td>Country_1</td>\n",
270 " <td>email_1@example.com</td>\n",
271 " </tr>\n",
272 " <tr>\n",
273 " <th>freq</th>\n",
274 " <td>NaN</td>\n",
275 " <td>1</td>\n",
276 " <td>NaN</td>\n",
277 " <td>10</td>\n",
278 " <td>1</td>\n",
279 " </tr>\n",
280 " <tr>\n",
281 " <th>mean</th>\n",
282 " <td>50.500000</td>\n",
283 " <td>NaN</td>\n",
284 " <td>44.530000</td>\n",
285 " <td>NaN</td>\n",
286 " <td>NaN</td>\n",
287 " </tr>\n",
288 " <tr>\n",
289 " <th>std</th>\n",
290 " <td>29.011492</td>\n",
291 " <td>NaN</td>\n",
292 " <td>15.190012</td>\n",
293 " <td>NaN</td>\n",
294 " <td>NaN</td>\n",
295 " </tr>\n",
296 " <tr>\n",
297 " <th>min</th>\n",
298 " <td>1.000000</td>\n",
299 " <td>NaN</td>\n",
300 " <td>18.000000</td>\n",
301 " <td>NaN</td>\n",
302 " <td>NaN</td>\n",
303 " </tr>\n",
304 " <tr>\n",
305 " <th>25%</th>\n",
306 " <td>25.750000</td>\n",
307 " <td>NaN</td>\n",
308 " <td>32.000000</td>\n",
309 " <td>NaN</td>\n",
310 " <td>NaN</td>\n",
311 " </tr>\n",
312 " <tr>\n",
313 " <th>50%</th>\n",
314 " <td>50.500000</td>\n",
315 " <td>NaN</td>\n",
316 " <td>43.500000</td>\n",
317 " <td>NaN</td>\n",
318 " <td>NaN</td>\n",
319 " </tr>\n",
320 " <tr>\n",
321 " <th>75%</th>\n",
322 " <td>75.250000</td>\n",
323 " <td>NaN</td>\n",
324 " <td>59.250000</td>\n",
325 " <td>NaN</td>\n",
326 " <td>NaN</td>\n",
327 " </tr>\n",
328 " <tr>\n",
329 " <th>max</th>\n",
330 " <td>100.000000</td>\n",
331 " <td>NaN</td>\n",
332 " <td>69.000000</td>\n",
333 " <td>NaN</td>\n",
334 " <td>NaN</td>\n",
335 " </tr>\n",
336 " </tbody>\n",
337 "</table>\n",
338 "</div>"
339 ],
340 "text/plain": [
341 " ID Name Age Country Email\n",
342 "count 100.000000 100 100.000000 100 100\n",
343 "unique NaN 100 NaN 10 100\n",
344 "top NaN Name_1 NaN Country_1 email_1@example.com\n",
345 "freq NaN 1 NaN 10 1\n",
346 "mean 50.500000 NaN 44.530000 NaN NaN\n",
347 "std 29.011492 NaN 15.190012 NaN NaN\n",
348 "min 1.000000 NaN 18.000000 NaN NaN\n",
349 "25% 25.750000 NaN 32.000000 NaN NaN\n",
350 "50% 50.500000 NaN 43.500000 NaN NaN\n",
351 "75% 75.250000 NaN 59.250000 NaN NaN\n",
352 "max 100.000000 NaN 69.000000 NaN NaN"
353 ]
354 },
355 "execution_count": 22,
356 "metadata": {},
357 "output_type": "execute_result"
358 }
359 ],
360 "source": [
361 "# Check shape and summary stats\n",
362 "print(df.shape)\n",
363 "df.describe(include=\"all\")"
364 ]
365 },
366 {
367 "cell_type": "code",
368 "execution_count": 23,
369 "id": "e775e42f",
370 "metadata": {},
371 "outputs": [
372 {
373 "name": "stdout",
374 "output_type": "stream",
375 "text": [
376 " ID Name Age Country Email\n",
377 "0 1 Name_1 62 Country_1 email_1@example.com\n",
378 "1 2 Name_2 48 Country_2 email_2@example.com\n",
379 "2 3 Name_3 61 Country_3 email_3@example.com\n",
380 "3 4 Name_4 32 Country_4 email_4@example.com\n",
381 "4 5 Name_5 69 Country_5 email_5@example.com\n",
382 ".. ... ... ... ... ...\n",
383 "95 96 Name_96 60 Country_6 email_96@example.com\n",
384 "96 97 Name_97 26 Country_7 email_97@example.com\n",
385 "97 98 Name_98 52 Country_8 email_98@example.com\n",
386 "98 99 Name_99 24 Country_9 email_99@example.com\n",
387 "99 100 Name_100 55 Country_0 email_100@example.com\n",
388 "\n",
389 "[100 rows x 5 columns]\n"
390 ]
391 }
392 ],
393 "source": [
394 "print(df)"
395 ]
396 },
397 {
398 "cell_type": "markdown",
399 "id": "dc10be72",
400 "metadata": {},
401 "source": [
402 "## πŸ“ˆ Grouping and Pivoting Data"
403 ]
404 },
405 {
406 "cell_type": "code",
407 "execution_count": 24,
408 "id": "f87ea8af",
409 "metadata": {},
410 "outputs": [
411 {
412 "data": {
413 "text/html": [
414 "<div>\n",
415 "<style scoped>\n",
416 " .dataframe tbody tr th:only-of-type {\n",
417 " vertical-align: middle;\n",
418 " }\n",
419 "\n",
420 " .dataframe tbody tr th {\n",
421 " vertical-align: top;\n",
422 " }\n",
423 "\n",
424 " .dataframe thead th {\n",
425 " text-align: right;\n",
426 " }\n",
427 "</style>\n",
428 "<table border=\"1\" class=\"dataframe\">\n",
429 " <thead>\n",
430 " <tr style=\"text-align: right;\">\n",
431 " <th></th>\n",
432 " <th>Country</th>\n",
433 " <th>RecordCount</th>\n",
434 " </tr>\n",
435 " </thead>\n",
436 " <tbody>\n",
437 " <tr>\n",
438 " <th>0</th>\n",
439 " <td>Country_1</td>\n",
440 " <td>10</td>\n",
441 " </tr>\n",
442 " <tr>\n",
443 " <th>1</th>\n",
444 " <td>Country_2</td>\n",
445 " <td>10</td>\n",
446 " </tr>\n",
447 " <tr>\n",
448 " <th>2</th>\n",
449 " <td>Country_3</td>\n",
450 " <td>10</td>\n",
451 " </tr>\n",
452 " <tr>\n",
453 " <th>3</th>\n",
454 " <td>Country_4</td>\n",
455 " <td>10</td>\n",
456 " </tr>\n",
457 " <tr>\n",
458 " <th>4</th>\n",
459 " <td>Country_5</td>\n",
460 " <td>10</td>\n",
461 " </tr>\n",
462 " </tbody>\n",
463 "</table>\n",
464 "</div>"
465 ],
466 "text/plain": [
467 " Country RecordCount\n",
468 "0 Country_1 10\n",
469 "1 Country_2 10\n",
470 "2 Country_3 10\n",
471 "3 Country_4 10\n",
472 "4 Country_5 10"
473 ]
474 },
475 "execution_count": 24,
476 "metadata": {},
477 "output_type": "execute_result"
478 }
479 ],
480 "source": [
481 "# Example: Group by 'Country' and count number of records\n",
482 "country_counts = df[\"Country\"].value_counts().reset_index()\n",
483 "country_counts.columns = [\"Country\", \"RecordCount\"]\n",
484 "country_counts.head()"
485 ]
486 },
487 {
488 "cell_type": "markdown",
489 "id": "f156d6de",
490 "metadata": {},
491 "source": [
492 "## πŸ“Š Visualizing Record Counts by Country"
493 ]
494 },
495 {
496 "cell_type": "code",
497 "execution_count": 25,
498 "id": "d644fc06",
499 "metadata": {},
500 "outputs": [
501 {
502 "name": "stderr",
503 "output_type": "stream",
504 "text": [
505 "/var/folders/pf/tt0qdz214wn0q90g989_0r0h0000gn/T/ipykernel_79108/1394705902.py:3: FutureWarning: \n",
506 "\n",
507 "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",
508 "\n",
509 " sns.barplot(data=country_counts, x='Country', y='RecordCount', palette='viridis')\n"
510 ]
511 },
512 {
513 "data": {
514 "image/png": "iVBORw0KGgoAAAANSUhEUgAAA00AAAJUCAYAAADJrc3LAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAABLd0lEQVR4nO3dB5hU5dk/4BcpAnZR0VhRbBi72A12jb3X2GtsGDR2JbFi+RT7Z+899hhbRKOxa4waRQ0KdqygKCjF+V/P+f6z7uJy5BwWt8x9X9e6uzPD7DuP786e33nLaVepVCoJAACARk3T+M0AAAAEoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwDU45rvk6Y2QK0SmgBagF122SUtuuiiDT4WW2yxtNxyy6Wtttoq3X333aml++CDD7J233HHHT/72E8++SSdccYZacMNN0xLL710Wn311dP++++fXnjhhdScHnnkkXTkkUfmPuaoo45Ka6+99lRrw/fff5+uvvrqtPXWW6fll18+rbjiimmHHXZId911V7OGlv/+979pxx13bLafD9CcOjTrTwegTq9evVL//v3rvp8wYUIaPnx4dgB9xBFHpJlnnjn16dMntXYvvvhiOvDAA9Mss8ySdt1119SjR480cuTIdMstt2Th8bTTTktbbLFFs7Qtat2cPv/887T33nunjz/+OKvFUkstlX744Yf06KOPZmEtQuVJJ52U2rVr94u37YEHHkgvvfTSL/5zAVoCoQmghZh++unTMsss85Pbf/Ob36RVVlklG8Fp7aEpwtGhhx6aFlhggXTVVVelLl261N23wQYbpH333TedcMIJ2cjTbLPNlmpNjHJFUI4AGTWqWnPNNdOvfvWrdPbZZ6e11lorrbPOOs3aToBaY3oeQAs37bTTpk6dOjUYXYjRh0svvTStt9566de//nUWOK677rqf/NuY0rXllltmU+DiwPt//ud/0tixY+vuf/XVV9Nee+2VVlpppWwqYEyRi2lYVc8++2w25e7mm2/ODtbjMU8++WR230MPPZQ222yzbDQkfsYbb7zxs68l2vPpp5+mY445pkFgCtNMM006/PDD084775y++eabutvj5+20007ZVLVo52GHHZaNxFSdf/75WRsnFrfFffWnDt5///3pkEMOScsuu2w27e24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fs6f//znrEbbbbddtqtfvJ7YeS5ef2zKEDsbVte3Re1jDVtcIDj+38X/w7gwbgSuqFuIvhKbL8SmFfE80d54zMRT8Sa3NiFCbGOhbmJFXztASyY0AbRwMWoU21m/+eabWTgKMVIToSGunxQHwbHtc0yBuvLKK+sW80c4ioASoWS//fbLprzts88+dTvOxUhArJuKEZ84yI4QFmElduaLTSjyxGhXjIjEGps4eI/wE9uWT46YhnbbbbdlI10xehJtit3xYjpiPE+s1aqKEYoIYxFkIsjF64lRrpiGGKNQIUakImjF1tsRAGN0I4Li5KyxmljULAJqtCnC4KTEtuQR+iKcRT1jc4obb7yxwfS8aqCLKXKTO9ISa76iBhGaIpzEa47txGOdU9Qofl79kan4mbGWKrZij9AcI3/x+mMzhhB9IeoXtY3/xzHNM+q7/vrrF65N/JsYwTrqqKOynQt/TtHXDtCStauUWQELAPys2Ho7QlWMkk1qU462qpZfO9D2uE4TADSxmAYXm13ESGCMtNRSaKjl1w60XabnAUATi6mCsc4rpiDGtLlaUsuvHWi7TM8DAADIYaQJAAAgh9AEAACQQ2gCAADIUXO758UV22MZV1yHAwAAqF3jxo3LrikX1wDMU3OhKQKTvS8AAIDKZOaCmgtN1RGmuKo5AABQu1599dXJepw1TQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAAC0ltB0ySWXpF122aXBbYMHD06/+93v0jLLLJPWXnvtdO211zZb+wAAgNrTYkLTDTfckAYOHNjgthEjRqQ99tgjzTfffOn2229PBx54YDrrrLOyrwEAAH4JHVIz++STT1L//v3Ts88+mxZYYIEG9916662pY8eO6cQTT0wdOnRICy20UHr33XfTpZdemrbeeutmazMAAFA7mn2k6bXXXsuC0T333JOWXnrpBve98MILacUVV8wCU9XKK6+chg0blj7//PNmaC0AAFBrmn2kKdYpxUdjhg8fnhZZZJEGt80xxxzZ548//jjNNttspX5mpVJJo0ePnuT97dq1S7UqalOGmpWjbsWpWTnqVpyalaNuxalZOepWnJo1fvvk1KXZQ1Oe7777LnXq1KnBbdNOO232+fvvvy/9vOPGjcs2mGhMjHr16tWrwehWrRg/fnx6/fXXs/oUoWbFa/Zj3ZZIHTq0T7Vm/PgJ6fXXXyvZ19SsKHUr2deW6JU6tK/B97UJ49Prr03B+5q6Ffp3ala+ry2xRK/UvgbrNmHC+PRayb6mZuMavX/ivNGYFl21zp07p7Fjxza4rRqWunbtWvp5o9P07Nmz0fsiacbB/xlHXZnef2d4qhXzLjhnOmLAnmnhhRcufPaiWrMzT78tvf/+Z6lWzDvv7OmPR25bqmY/1q19OvXCO9N7H9XOdNP5fjVbOubALaegr7VPf7r6zjRseO3UbIE5Z0t/2r1czerX7bhb70hDP6uduvWYfbZ08nZble9r7Tuko+/7S3rni9qp2YLdZkunbbzNlPW19h3Snx+/Nb07snb+Hsw/8+yp/2+2m6K+dsnL16ePv/kk1Yq5pu+e9lv6d1PU1+Lg/943L0pfjP4o1YpuXX+VNl30gNJ9LWr2ryF/Tt+MeTfVium7zJ+W69l/kjUbMmTIZD1Piw5Nc845Z/r0008b3Fb9vnv37qWfNzrNz4WuCExvD34/1ZouXbqU/rcRmN4e8nGqNVNSsxCBaciw2gnoTVG3CExvfaBmRUVgeuMjdSsiAtMbn3pfKyoC01tf1s6BbFPULQLTu19/mGrNlPa1CEyffDss1ZopqVsEpq9Gv5VqTZdJ1Gxypyw2+0YQeXr37p1efPHFNGHChLrbnnnmmdSjR4/UrVu3Zm0bAABQG1p0aIptxb/55pt07LHHZkNnd9xxR7r66qvTfvvt19xNAwAAakSLDk0xmnT55ZenoUOHpi233DJdcMEF6Ygjjsi+BgAA+CW0qDVNAwYM+MltSy21VLrllluapT0AAAAteqQJAACguQlNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABAaw9N48ePT+eee25aa6210rLLLpt23nnn9O9//7u5mwUAANSAVhGaLr744nTbbbelk046Kd11112pR48eae+9906ffvppczcNAABo41pFaPr73/+eNtlkk7T66qun+eefPx111FFp1KhRRpsAAICprlWEpm7duqVHH300ffDBB2nChAnplltuSZ06dUqLLbZYczcNAABo4zqkVuDYY49Nffv2Teuss05q3759mmaaadL555+f5ptvvlLPV6lU0ujRoxu9r127dqlLly6pVo0ZMyarTxFqVrxmQd30taL0tXL0teL0tXL0teL0tXL0taarWdwWtWkToWnIkCFphhlmSBdeeGHq3r17tr7p8MMPT9dff31afPHFCz/fuHHj0uDBgxu9LzpTr169Uq0aOnRo1qmKULPiNQvqpq8Vpa+Vo68Vp6+Vo68Vp6+Vo681bc1iBlurD00ff/xxOuyww9LVV1+dVlhhhey2JZdcMgtSMdp00UUXFX7Ojh07pp49ezZ63+QkzbYsNtkoc+ailpWpWVA3fa0ofa0cfa04fa0cfa04fa0cfa3pahaZYnK0+ND08ssvZyNDEZTqW3rppdPjjz9e6jmj03Tt2rWJWti21PKwbVlqVo66Fadm5ahbcWpWjroVp2blqFvT1Wxyw2SL3whizjnnzD6/+eabDW5/66230gILLNBMrQIAAGpFiw9NSy21VFp++eXTkUcemZ555pk0bNiwNHDgwPT000+nfffdt7mbBwAAtHEtfnpe7JQXF7eNoHT00Uenr776Ki2yyCLZGqeYogcAAFDToSnMNNNMqX///tkHAADAL6nFT88DAABoTkITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAA0NSh6eijj07vv/9+o/e98847af/99y/ztAAAAC1Oh8l94EcffVT39V133ZXWXXfd1L59+5887vHHH09PPfVU07UQAACgNYSmP//5z1kgqjrooIMafVylUkmrrbZa07QOAACgtYSmE088MRtBilB0zDHHpN///vdpvvnma/CYaaaZJs0444xppZVWmhptBQAAaLmhqXv37mnLLbfMvm7Xrl3q06dPmnXWWadm2wAAAFpPaKovwlOMOL3++utp9OjR2dcT6927d1O0DwAAoPWFpldeeSX17ds3DR8+PPu+GppiBCq+js+DBw9u2pYCAAC0ltB02mmnpQ4dOmSf55xzzmwtEwAAQFtUKjS99tpr6eyzz862Hf+lxDbnl156aXZ9qNiAInbv++1vf/uL/XwAAKA2lRoi6tatW6PXaJpa7r777nTsscemnXfeOd13331pk002Sf369UsvvfTSL9YGAACgNpUKTTvttFO65JJLsk0gprZYI3XuueemXXfdNQtNMcoU252vuuqq6bnnnpvqPx8AAKhtpabnvfvuu+ntt9/OLmK78MILp86dOze4PzaCuOaaa5qkgUOHDk0ffvhh2nTTTRvcfsUVVzTJ8wMAAEyV0LTYYovVfT/xluONbUE+JaEpxKjWXnvtlW1zPs8882SjTWuvvXap54z2TWqULAJfly5dUq0aM2ZM4f9/ala8ZkHd9LWi9LVy9LXi9LVy9LXi9LVy9LWmq1l15++pEpquu+669Ev55ptvss9HHnlktvnD4Ycfnh588MF0wAEHpKuuuiqtssoqhZ9z3Lhxk9wSPTpTr169Uq2KkBqdqgg1K16zoG76WlH6Wjn6WnH6Wjn6WnH6Wjn6WtPWrFOnTlMnNP2SOnbsmH2OUaa4qG5YfPHFsxGnsqEpnrNnz56N3jc5SbMt69GjR6kzF7WsTM2CuulrRelr5ehrxelr5ehrxelr5ehrTVezIUOGTNa/LxWaYlrczxX+kUceSU2he/fu2edFFlmkwe0Reh577LFSzxlt79q1a5O0r62p5WHbstSsHHUrTs3KUbfi1KwcdStOzcpRt6ar2eSGyVKhacUVV/zJD/j222/Tq6++mr7//vu02267paayxBJLpOmmmy69/PLLaYUVVqi7/a233sp20gMAAJiaSoWmAQMGTHKtUKw1KjM3dVJiZ7699947XXjhhdmo01JLLZVdq+nJJ59MV199dZP9HAAAgKm+pinWCsX1lI4++uh06KGHNtnzRhCLIbVzzjknffLJJ2mhhRZK559/flpppZWa7GcAAAD8IhtBfPXVV9lUvaa2xx57ZB8AAAAtPjTdddddP7ltwoQJafjw4en6669vsPYIAACg5kLTUUcdNcn7ll122XT88cdPSZsAAABad2hqbDvx2E1v+umnTzPOOGNTtAsAAKD1hqa555677uu33347jRo1Ks0yyywCEwAA0OaU3gjir3/9azr99NPT559/XnfbbLPNlg477LC0xRZbNFX7AAAAWl9oGjRoUPrjH/+YVl555dSvX78sLH366afpnnvuybYbn3nmmdOaa67Z9K0FAABoDaHp4osvThtuuGF23aT6tt566/SHP/whXXLJJUITAADQJkxT5h+99dZbacstt2z0vrj9jTfemNJ2AQAAtN7QFJs+xEVsGzNy5MjUqVOnKW0XAABA6w1Nq6yySrrggguyi9nW9/HHH6cLL7wwrbbaak3VPgAAgNa3pik2f4j1S+uvv352MdvYCCJ20XvppZfSTDPNlO2gBwAAULMjTbPPPnu688470y677JLGjBmT/vOf/2Sf4/u4vf51nAAAAGryOk3dunXLth0HAABoywqNNI0fPz5df/316eGHH25w+4QJE7Jd866++ur0ww8/NHUbAQAAWn5oGjduXDrggAPSKaeckq1dqu/LL7/MwtKAAQPSQQcdlIUoAACAmgpNt9xyS3rmmWfSWWedlY444oifrHG6++67s9D0+OOPp9tvv31qtBUAAKDlhqY77rgj7b777mnjjTee5GO22GKLtO2226bbbrutqdoHAADQOkLTu+++m1ZeeeWffVyfPn3SsGHDprRdAAAArSs0dejQIVvXNDmPa9eu3ZS2CwAAoHWFpoUXXjg9++yzP/u45557Ls0zzzxT2i4AAIDWFZo233zzdNNNN6VXXnllko957bXX0g033JB++9vfNlX7AAAAWsfFbbfZZpv017/+Ne2yyy7Z12uuuWY2ohRbjX/44YfZrnm33nprWnTRRbPHAAAA1FRoinVKl1xySTr11FOz7cdvvPHGuvsqlUq2lil2zuvXr1/q3Lnz1GovAABAywxNIcLQiSeemA499ND09NNPp+HDh6f27dunueeeO9tZb4YZZph6LQUAAGjpoalq1llnzb1eEwAAQM2FprvuuqvQE8eFbgEAAGomNB111FENvq9eiynWM018WxCaAACAmgpNjzzySN3XgwcPTn/84x/TAQcckG0vPsccc6QRI0akQYMGpfPPPz+ddtppU6u9AAAALTM0xWYPVQcffHAWmPbZZ5+627p375523HHHNHbs2HTmmWemPn36NH1rAQAAWurFbet7++23U69evRq9b8EFF0wffPDBlLYLAACg9YamBRZYIN17772N3hfXcFpkkUWmtF0AAACtd8vxAw88MPXt2zcNGzYsrbXWWmmWWWZJn3/+eXrooYfSkCFD0mWXXdb0LQUAAGgtoWn99ddPF154YbrooovSwIEDsx30pplmmrTsssumq6++Oq2wwgpN31IAAIDWEpqefvrptOqqq6a11147ff/99+mrr75KM888c+rUqVPTtxAAAKC1rWmK3fNiKl6Ydtppsy3HBSYAAKAtKhWaZpxxxtS5c+embw0AAEBbmJ633377pZNPPjkNHTo0LbbYYqlr164/eUzv3r2bon0AAACtLzT1798/+3zOOedkn9u1a1d3X2wKEd8PHjy4qdoIAADQukLTtdde2/QtAQAAaCuhacUVV2z6lgAAALSV0BRiPdN5552XnnvuufT1119nF7iN6zPFhW8XWmihpm0lAABAawpNQ4YMSTvssENq3759dq2m2WabLX322Wfp0UcfTY899li67bbbBCcAAKB2Q9NZZ52V5plnnnTdddelGWaYoe72UaNGpd122y3bIOKCCy5oynYCAAC0nus0Pf/882n//fdvEJhCfL/vvvtm9wMAANRsaOrQoUOadtppG72vU6dOaezYsVPaLgAAgNYbmpZccsl04403Ztdkqi++v+GGG9Kvf/3rpmofAABAsyq1pqlv375pxx13TJtttlnacMMN0+yzz55tBPHAAw9ku+pdddVVTd9SAACA1hKaYqTp8ssvT//zP/+TbfgQI0zt2rXLRpguu+yy1Lt376ZvKQAAQGu6TtPKK6+cbr755mz9UlynacYZZ0zjx4//yeYQAAAANbemady4cal///5pu+22S126dEndu3dPL730UlpllVXS6aefnn744YembykAAEBrCU3nn39+uueee9LGG29cd1uvXr3S4Ycfnm699dZs6h4AAEDNTs+7995705FHHpl22GGHuttmnnnmtPvuu2fbkV977bXZ9ZoAAABqcqRpxIgRad555230vgUXXDANHz58StsFAADQekNTBKMHH3yw0fsGDRqU5p9//iltFwAAQOudnrfrrrumo446Ko0cOTKtu+66qVu3bunLL79Mjz76aLr//vvTaaed1vQtBQAAaC2haYsttkjffvttuuiii9JDDz1Ud/sss8ySjj/++Ox+AACAmr5O084775x22mmnNHTo0GzEKa7TFNP2ppmm1Iw/AACAFmmKEk5c1DZC05tvvplmnXXWNGzYsFSpVJqudQAAAK11pOniiy9Ol1xySfruu+9Su3bt0lJLLZUGDhyY7ax35ZVXZiNPAAAANTnSdP3112cXuN1jjz2yi9lWR5d+97vfpffffz+de+65Td1OAACA1hOarrvuuuzitX379k1LLLFE3e19+vRJhx56aLbtOAAAQM2Gpo8++iituOKKjd4Xm0F8/vnnU9ouAACA1hua5pprrvTSSy81et9//vOf7H4AAICa3Qhim222ydY0de7cOa255prZbaNHj04PPvhgtjlErHUCAACo2dC0zz77pA8++CCdddZZ2UfYddddsw0hNttss7Tffvs1dTsBAABaT2iKLcZPPPHEtOeee6Znnnkmu7jtDDPMkHr37p169uyZbrrppuzitwAAADUVmh5//PF05513ZqFp8803z3bLW2CBBeruf+GFF9JWW22VXexWaAIAAGoqNN1zzz3piCOOSB07dkydOnVK999/fzrvvPPSeuutl400nXzyyem+++5L7du3t6YJAACovdB0zTXXpKWXXjpdccUVWWg6+uij04UXXpgWXnjhLCR9/PHHaY011kjHHHNM6tGjx9RtNQAAQEsLTcOGDUsnnXRSmn766bPvDzrooLTRRhulAw44II0dOzade+65aYMNNpiabQUAAGi5oSm2FK9//aW555472y2vQ4cO2dS9bt26Ta02AgAAtPyL20ZAivVKVdWv//CHPwhMAABAmzXZoWlS5phjjqZpCQAAQFsMTbH9OAAAQFtV6DpNf/rTn+o2gojpeuH4449P00033U+CVOy2BwAAUDOhqXfv3g3C0qRua+x7AACANh+arrvuutTchg4dmrbaaqtsdCs+AwAAtPg1Tb+UcePGpcMPPzzb+hwAAOCX0mpC0/nnn1+3ngoAAOCX0ipC0/PPP59uueWWNGDAgOZuCgAAUGMK7Z7XHL7++ut0xBFHpOOOOy7NNddcTfKcsVHFpKb5xc5/Xbp0SbVqzJgxhTfyULPiNQvqpq8Vpa+Vo68Vp6+Vo68Vp6+Vo681Xc3itsm5hFKLD02xzfmyyy6bNt100yZdHzV48OBG74vO1KtXr1SrYrON6FRFqFnxmgV109eK0tfK0deK09fK0deK09fK0deatmadOnVq3aHprrvuSi+88EK69957m/R5O3bsmHr27NnofbV+sd4ePXqUOnNRy8rULKibvlaUvlaOvlacvlaOvlacvlaOvtZ0NRsyZMhk/fsWHZpuv/329MUXX6Q111yzwe39+/dPf/vb39Lll19e6nmj03Tt2rWJWtm21PKwbVlqVo66Fadm5ahbcWpWjroVp2blqFvT1Wxyw2SLDk1nnXVW+u677xrctv7666dDDjkkbbbZZs3WLgAAoHa06NDUvXv3Rm/v1q3bJO8DAACouS3HAQAAmkuLHmlqzJtvvtncTQAAAGqIkSYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAANDaQ9PIkSPTCSeckH7zm9+k5ZZbLu24447phRdeaO5mAQAANaBVhKZ+/fqll156KZ199tnp9ttvT4svvnjaa6+90jvvvNPcTQMAANq4Fh+a3n333fTkk0+mP/3pT2mFFVZIPXr0SMcff3yaY4450r333tvczQMAANq4Fh+aZplllnTppZemJZdcsu62du3aZR9ff/11s7YNAABo+zqkFm7GGWdMffr0aXDbgw8+mI1AHXPMMaWes1KppNGjRzd6X4SxLl26pFo1ZsyYrD5FqFnxmgV109eK0tfK0deK09fK0deK09fK0dearmZxW9Sm1Yemif3rX/9KRx99dFp//fXTmmuuWeo5xo0blwYPHtzofdGZevXqlWrV0KFDs05VhJoVr1lQN32tKH2tHH2tOH2tHH2tOH2tHH2taWvWqVOnthWa/v73v6fDDz8820HvrLPOKv08HTt2TD179mz0vslJmm1ZrBkrc+ailpWpWVA3fa0ofa0cfa04fa0cfa04fa0cfa3pajZkyJDJ+vetJjRdf/316ZRTTkkbbrhhOv300ycrEeZ1mq5duzZp+9qKWh62LUvNylG34tSsHHUrTs3KUbfi1KwcdStuUjWb3DDZ4jeCCDfeeGM66aST0s4775xtOz4lgQkAAKCIDq1h/uGpp56a1ltvvbTffvulzz//vO6+zp07pxlmmKFZ2wcAALRtLT40xU55sXHDww8/nH3Ut+WWW6YBAwY0W9sAAIC2r8WHpv333z/7AAAAaA6tYk0TAABAcxGaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACAHEITAABADqEJAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAEAAOQQmgAAAHIITQAAADmEJgAAgBxCEwAAQA6hCQAAIIfQBAAAkENoAgAAyCE0AQAA5BCaAAAAcghNAAAAOYQmAACA1h6afvjhh3TeeeelNdZYIy2zzDJpn332Se+//35zNwsAAKgBrSI0XXTRRenGG29MJ510Urr55puzELX33nunsWPHNnfTAACANq7Fh6YIRldeeWU65JBD0pprrpkWW2yxdM4556Thw4enhx56qLmbBwAAtHEtPjS98cYb6dtvv02rrLJK3W0zzjhj6tWrV3r++eebtW0AAEDb165SqVRSCxajSQcffHB6+eWXU+fOnetu79u3b/ruu+/SJZdcUuj5/vWvf6V4yR07dpzkY9q1a5e++nJUGj9+QqoVHTq0TzPNOkNWmzKymo38No2fUEM1a98+zTTzdKVrVq3byK9H11zdZp6x6xT1tRHfjK65389Zpi9fs7q6ffttGjfhh1QrOrafJs0yXfnf0ajZl6O/TeN/qKG+Nk37NGvXKX9fG/Fd7dVtls5T1tdGjf2m5mo2Q6fpp7ivjR73dfqhUjt1m6Zd+9S144xT1NfGjhuRfqiMT7VimnYdUqeOs0yyZuPGjcvqstxyy+U+T4fUwo0ZMyb73KlTpwa3TzvttOmrr74q/HxRlPqfJyUCRC36ubrkiQBRi6akZiECRC2akrpFgKhFU9rXIkDUoimpWwSIWjTFfa2zuhUVAaIWTWlfiwBRi6akbhEgalG7SdQsbp+cerb40FQdXYq1TfVHmr7//vvUpUuXws+37LLLNmn7AACAtq3Fr2maa665ss+ffvppg9vj++7duzdTqwAAgFrR4kNT7JY3/fTTp2effbbutq+//jq9/vrrqXfv3s3aNgAAoO1r8dPzYi3T7373u3TWWWelWWedNc0999zpzDPPTHPOOWdaf/31m7t5AABAG9fiQ1OIazSNHz8+HXfccdmOeTHCdMUVV+TugAcAAFATW44DAAA0pxa/pgkAAKA5CU0AAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAmIgrsgD1CU0AtAoOYvmlfPnll6ldu3bN3YxWye8pbZXQBPAL++6775q7Ca3OySefnIYOHdrczWh1fvjhh+ZuQqvzhz/8IZ1zzjnZ1wJAccImbVWH5m4ATevaa69NK664YlpsscWauymtyoUXXpg+/fTT9PXXX6cddtghLbnkkqlr167N3awW39e++uqr9P3332cHGe3bt88OMPzBzNe3b98022yzpYMPPjjNPPPMzd2cVmHHHXdMX3zxRTriiCOauymtyqmnnpo++eST9M0336Rdd901rbrqqqljx47N3awWbZdddknPP/986t27d/a997PJc/HFF2e/o3FC6KCDDkpzzjmnvwc/46677kqjRo3Kfj+32GKL1K1bt9SpU6fmblaLd8MNN2R1mzBhQtp2223TLLPM8ou9rwlNbcjbb7+drr766vSf//wn7b///mnBBRds7ia1CnvuuWc2FaNXr17ZmezDDjssHX300WnjjTdu7qa16AOLCEzxh/Hll19O77zzTrrooov8gZwMo0ePzt70Z5111qyOM844Y3M3qcUHprFjx2YHGHFA4UBs8uyxxx7ZQezaa6+dnnvuueykkMCUL06YjR8/Pv35z39Of/nLX7ITQlGzaaYxKSfP7rvvnkaOHJkWXnjh9OKLL6bBgwen22+/Pfs99fs66d/POPCfaaaZ0nvvvZduu+229Lvf/S5ts802/ibkiL+Z8b4Wfz8//PDDdOedd2YnhDbddNMsPE1tQlMbMsccc2TJ+4UXXkj/+7//m/bbb7+00EILNXezWrRLLrkke+O66aabUpcuXbLbdtppp3TNNdcITZNw7rnnpm+//TY7qIg/iPHHMT7iDGPnzp2bu3ktXowCR9C84IILspHNGHGafvrpm7tZLfbAIk5oPPjgg3W3jRs3Lnuf69ChQza6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+NlSSy3V4DniukJnnnlmdsGvDTbYINUCdStOzcpRt+LUrBx1K07NylG34tSsRuv2i0Y0Jsuzzz5b2XLLLStDhgypS9ZPPPFE5eyzz64sueSS2XzPpZdeOkvhY8eOrUvtjz76aHZ7LZ2xqE/dilOzctStODUrR92KU7Ny1K04NautuglNzWz48OENhioffvjhyoorrljZfffdK99++21l3LhxDR7/4YcfVs4999zK9ttvX1l55ZUrX3/9dd19I0eOrLzwwguVWqBuxalZOepWnJqVo27FqVk56lacmpXTlupmy/FmNHbs2DRw4MB04403Zt+/8cYb2dDlwgsvnF555ZX0+eefpw4dOmSPq269+Ktf/SodeOCB6dprr03zzjtvuuiii7L7xo8fn2aaaaa0/PLLp7ZO3YpTs3LUrTg1K0fdilOzctStODUrp63VTWhqRp06dco6x8knn5zN4Yw5miuuuGLq27dvmnvuudNee+2Vvvnmm+xx0VmqF+2Kz3Fbr1690siRI7PbotPVCnUrTs3KUbfi1KwcdStOzcpRt+LUrJy2VjehqZkdfPDBafHFF0///ve/0yKLLJJmnnnmtNxyy6XjjjsuTTvttGnrrbfOOlR0luhQVSNGjMgW0H3wwQdZQq+1/TzUrTg1K0fdilOzctStODUrR92KU7Ny2lLdhKZmUP9//JdffpkNP26zzTbpqquuSldffXWWsGP48YQTTsiS9rbbbptGjRrVIGVX/92xxx6bPaZ6ZeS2TN2KU7Ny1K04NStH3YpTs3LUrTg1K6fSVuvWbKupalT9xXDVxW2xM0g455xzKosuumjlqquuqnvM888/X1ljjTUqhxxyyE+e6/vvv6/UCnUrTs3KUbfi1KwcdStOzcpRt+LUrJwJbbhuzT9BsIZE8q7uT3/FFVdke9J37do1m+e5yiqrZHM8I0mffvrp2eeddtopffvtt9kiuqWXXvonzxfJuxaoW3FqVo66Fadm5ahbcWpWjroVp2blVNp63Zo7tdWK+lcyHjhwYHYl45NOOqmy/vrrV9Zee+3KrbfemiXqeFxstRhJvHfv3pUDDzyw7t+OHz++UmvUrTg1K0fdilOzctStODUrR92KU7NyfqiBuglNv7DYf36fffapvPTSS3W37bfffpUNNtigrkNV97E/77zz6vavr98Za5G6Fadm5ahbcWpWjroVp2blqFtxalbOh224bkLTL+iaa66prLnmmpXNNtus8t5779XdHnM963eouNhXfRNf+KvWqFtxalaOuhWnZuWoW3FqVo66Fadm5VzTxutm97ypaOLtETfddNPUuXPn9Oabb9Zd4Ct07NgxXXDBBWnBBRdMZ5xxRnrqqaca/LuWsDf9L0ndilOzctStODUrR92KU7Ny1K04NSunUmt1a+7UVguuu+66yv333599/dVXX1XWXXfdykYbbZTtGFJ/ODKS9hlnnNHi53T+UtStODUrR92KU7Ny1K04NStH3YpTs3Kuq5G6CU1T2fDhwyt77LFHZbXVVqsMGjQou23EiBGVtdZaq7Lpppv+pENVtdYO1VTUrTg1K0fdilOzctStODUrR92KU7NyhtdQ3YSmqbQ/ff0OEovh+vbtW+nTp0/lkUceqetQsZvIFltsUXnqqadaxQK4qUndilOzctStODUrR92KU7Ny1K04NStnQg3XTWiaism7vn//+9+Vgw8+OOtQ1SQ+cuTIylJLLVU54ogjmqmVLY+6Fadm5ahbcWpWjroVp2blqFtxalbO8Bqsm9A0Fdx3332V5ZdfPhuSrC+S+N577135zW9+U3nyySez20aNGtUqhyinBnUrTs3KUbfi1KwcdStOzcpRt+LUrJz7arRuds+bCmaeeebsysbHHntseuGFF+puX2aZZdJGG22UPvnkk7TvvvumZ599Nk0//fSpffv2acKECanWqVtxalaOuhWnZuWoW3FqVo66Fadm5cxcq3Vr7tTWVuZ2TuyVV16p7Lnnntl8zvpJ/MUXX6z069evctttt7WZ5F2GuhWnZuWoW3FqVo66Fadm5ahbcWpWjrr9qF38p7mDW2sV+89PM83/Ddbde++9adiwYemjjz5Kq622WlpvvfXS+++/nwYMGJDeeeed1L9//9SzZ890yimnpNlmmy2deOKJ2b+L5B0JvJaoW3FqVo66Fadm5ahbcWpWjroVp2blqFtDQlMTiAt13XHHHWmFFVZIb7/9dhozZkxaeOGF08CBA9MHH3yQzj777PSPf/wj/epXv0rTTTdd9ti40FeUvl27dqlWqVtxalaOuhWnZuWoW3FqVo66Fadm5ajb/1dv1IkSHn744ewiXq+99lrdbTfddFNls802y4Yn40JeX375ZeWJJ57ILvxVHaqM22uZuhWnZuWoW3FqVo66Fadm5ahbcWpWjrr9qEM1PPHzvvjii/TZZ59lw4yRsMN7772X5pprrrTQQgulcePGZcl6q622Sl999VW6+eabs6HMGK5cffXV654nhio7dKid0qtbcWpWjroVp2blqFtxalaOuhWnZuWoWz67502mY445JvXt2zdtscUW2cdVV12V3R6dJTrOtNNOm3WksWPHpk6dOqWtt946DR8+PBvGnFhbmds5OdStODUrR92KU7Ny1K04NStH3YpTs3LU7ecJTZNhjz32yBa57bnnnunaa6/N5nDGlophk002yTrMJZdckn0fHSlEB1tsscWy+Z21St2KU7Ny1K04NStH3YpTs3LUrTg1K0fdJk/bGztrYueff3624O2iiy5Ks846a4PdRGKB21JLLZV22WWXdOWVV6Zvv/02bb/99umbb75J55xzTurcuXNaYoklUi1St+LUrBx1K07NylG34tSsHHUrTs3KUbfJJzT9jNdffz2tu+66dR0pVLdfjB1BosN06dIl7bPPPumKK67IEnr37t1Tt27dsq/jsfW3bKwV6lacmpWjbsWpWTnqVpyalaNuxalZOeo2+YSmSYgOEIvh/vWvf6Xddtut7raJO8WIESPSfffdl/r06ZPtYf/aa69lHalXr17ZY8ePH98mF8NNiroVp2blqFtxalaOuhWnZuWoW3FqVo66Fdf2Y2FJ0RHi4lzTTz99evrpp+tuqy+GLWeZZZbUo0eP9Mknn2SPj07161//ui5510pHqlK34tSsHHUrTs3KUbfi1KwcdStOzcpRt+KEpkmIjhJbKy644ILpmWeeSUOHDv3JY6oX7IqtFeNiXhOrhaHKialbcWpWjroVp2blqFtxalaOuhWnZuWoW3G19WoLqM7jPOigg7L5npdffnk2jDmxL7/8Mo0aNSobpkTdylCzctStODUrR92KU7Ny1K04NStH3Uqod6FbJuHWW2+tLLHEEpWDDz648tRTT9Vd6fjDDz+s7LfffpXtttuu7grI/EjdilOzctStODUrR92KU7Ny1K04NStH3SZPu/hPmbBVS6JEDz/8cOrfv392wa7YNSQSeszjjKHJa665JrvgVwxfttULepWhbsWpWTnqVpyalaNuxalZOepWnJqVo26TR2gq4KOPPkpPPfVUeuWVV7KFcYsuumjaYIMNsg5US7uHFKVuxalZOepWnJqVo27FqVk56lacmpWjbvmEpiZQ68m7LHUrTs3KUbfi1KwcdStOzcpRt+LUrBx1+z9CU0FRrupuIvW/Jp+6Fadm5ahbcWpWjroVp2blqFtxalaOuk2a0AQAAJDDluMAAAA5hCYAAIAcQhMAAEAOoQkAACCH0AQAAJBDaAIAAMghNAHAZHKVDoDa1KG5GwAAE3v11VfTtddem55//vn05ZdfpjnmmCOtssoqad99903zzjtvs7TpoosuSp06dUp77713s/x8AJqPkSYAWpQbbrgh7bDDDumLL75Ihx12WLrsssuysPTcc8+lbbbZJr3xxhvN0q5zzz03jRkzpll+NgDNy0gTAC3Giy++mE455ZS08847p2OPPbbu9pVWWimtu+66aYsttkjHHHNMuuOOO5q1nQDUFiNNALQYV1xxRZphhhlSv379fnLfrLPOmo466qi0zjrrpNGjR6cJEyZko1KbbrppWmqppdKaa66ZzjrrrPT999/X/Ztddtkl+6jv2WefTYsuumj2OUQA69WrV3r55ZfT9ttvn5Zccsm01lprZW2piseHCy64oO7r888/P6233nrZbSuuuGJaffXV03HHHZe1ZdSoUT+Z2rf88ssbqQJopYQmAFrMJgv//Oc/s7VLXbp0afQxG220UTrwwANT165d0wknnJBOO+20bATq4osvzkanrr/++nTAAQcU3rDhhx9+SIceemj2/Jdeemlabrnl0hlnnJGeeOKJ7P5bbrkl+xzTA6tfh48++ij94x//SOecc046+uij0x577JGFtgceeKDB8999993Zc0/qdQHQspmeB0CLMGLEiCxwzDPPPD/72CFDhqS//OUv2ZqnWO8UVltttWzDiCOOOCI9/vjjqU+fPpP9syNkRdjadttts+9jVOjhhx9Ojz32WFpjjTXSMsssk90+55xz1n0dxo8fn4488si0wgor1N227LLLZiGp+lz/+te/0rBhw9KAAQMKVAOAlsRIEwAtQvv27bPPMe3u58SmEGHjjTducHt8H89TnXpXRISdqtglL6YDxjTAn7P44os3+H7rrbdOL7zwQvrwww+z7++8887Uo0ePBs8PQOsiNAHQIsw000xpuummy6a8TUqEmK+++ir7CLPPPnuD+zt06JBmmWWWn6wpmhydO3du8P0000wzWdP8os31VafhxWhTjJzdf//9aauttircHgBaDqEJgBYjNlOIUaL6mznUd+utt6aVV1657vvPPvuswf3jxo3LpvlFcKqaeORqckaPpkSEqA033DALS7EmKn7e5ptvPlV/JgBTl9AEQIux5557ppEjR6aBAwf+5L4ISFdeeWXq2bNntmtduO+++xo8Jr6PkBRrksL000+fhg8f/pNtzcuIkafJFRtGvPXWW+maa65Jq666aurevXupnwlAy2AjCABajNhkoW/fvlloevvtt7PrMsWo0X//+99sC/AYgYr7FlpoobTlllum8847L9vGu3fv3mnw4MHZ9t9xTafYvCHE1uGDBg3Kdtlbe+21s7VGd911V6m2zTjjjNmmDs8//3yDjR8aE6Et1jHF2qvYWQ+A1k1oAqBF+f3vf59dNymuwXTqqadm65fmmmuu7DpM+++/f/Z1iIvgzj///On2229Pl112WbZz3q677prtglcdFYpNGd57771sM4abb745C1cRtHbcccfC7YqfHddb2meffdLf/va3n318tPfLL7/MtkQHoHVrVyl6MQsAIFf8aY2d/GKN1jHHHNPczQFgChlpAoAm8s0336Srr746vfrqq+n9999Pu+yyS3M3CYAmIDQBQBOJbctjGuAPP/yQTS2cd955m7tJADQB0/MAAABy2HIcAAAgh9AEAACQQ2gCAADIITQBAADkEJoAAAByCE0AAAA5hCYAAIAcQhMAAEAOoQkAACBN2v8DXW28pR6Q0MgAAAAASUVORK5CYII=",
515 "text/plain": [
516 "<Figure size 1000x600 with 1 Axes>"
517 ]
518 },
519 "metadata": {},
520 "output_type": "display_data"
521 }
522 ],
523 "source": [
524 "# Plot bar chart\n",
525 "plt.figure(figsize=(10, 6))\n",
526 "sns.barplot(data=country_counts, x=\"Country\", y=\"RecordCount\", palette=\"viridis\")\n",
527 "plt.title(\"Record Count by Country\")\n",
528 "plt.xticks(rotation=45)\n",
529 "plt.show()"
530 ]
531 },
532 {
533 "cell_type": "markdown",
534 "id": "887b2525",
535 "metadata": {},
536 "source": [
537 "## πŸ“ˆ Pivot Table Example"
538 ]
539 },
540 {
541 "cell_type": "code",
542 "execution_count": 26,
543 "id": "1e26e06b",
544 "metadata": {},
545 "outputs": [
546 {
547 "data": {
548 "text/html": [
549 "<div>\n",
550 "<style scoped>\n",
551 " .dataframe tbody tr th:only-of-type {\n",
552 " vertical-align: middle;\n",
553 " }\n",
554 "\n",
555 " .dataframe tbody tr th {\n",
556 " vertical-align: top;\n",
557 " }\n",
558 "\n",
559 " .dataframe thead th {\n",
560 " text-align: right;\n",
561 " }\n",
562 "</style>\n",
563 "<table border=\"1\" class=\"dataframe\">\n",
564 " <thead>\n",
565 " <tr style=\"text-align: right;\">\n",
566 " <th></th>\n",
567 " <th>Age</th>\n",
568 " </tr>\n",
569 " <tr>\n",
570 " <th>Country</th>\n",
571 " <th></th>\n",
572 " </tr>\n",
573 " </thead>\n",
574 " <tbody>\n",
575 " <tr>\n",
576 " <th>Country_0</th>\n",
577 " <td>43.7</td>\n",
578 " </tr>\n",
579 " <tr>\n",
580 " <th>Country_1</th>\n",
581 " <td>43.8</td>\n",
582 " </tr>\n",
583 " <tr>\n",
584 " <th>Country_2</th>\n",
585 " <td>51.0</td>\n",
586 " </tr>\n",
587 " <tr>\n",
588 " <th>Country_3</th>\n",
589 " <td>38.2</td>\n",
590 " </tr>\n",
591 " <tr>\n",
592 " <th>Country_4</th>\n",
593 " <td>44.3</td>\n",
594 " </tr>\n",
595 " <tr>\n",
596 " <th>Country_5</th>\n",
597 " <td>48.3</td>\n",
598 " </tr>\n",
599 " <tr>\n",
600 " <th>Country_6</th>\n",
601 " <td>47.3</td>\n",
602 " </tr>\n",
603 " <tr>\n",
604 " <th>Country_7</th>\n",
605 " <td>41.1</td>\n",
606 " </tr>\n",
607 " <tr>\n",
608 " <th>Country_8</th>\n",
609 " <td>40.5</td>\n",
610 " </tr>\n",
611 " <tr>\n",
612 " <th>Country_9</th>\n",
613 " <td>47.1</td>\n",
614 " </tr>\n",
615 " </tbody>\n",
616 "</table>\n",
617 "</div>"
618 ],
619 "text/plain": [
620 " Age\n",
621 "Country \n",
622 "Country_0 43.7\n",
623 "Country_1 43.8\n",
624 "Country_2 51.0\n",
625 "Country_3 38.2\n",
626 "Country_4 44.3\n",
627 "Country_5 48.3\n",
628 "Country_6 47.3\n",
629 "Country_7 41.1\n",
630 "Country_8 40.5\n",
631 "Country_9 47.1"
632 ]
633 },
634 "metadata": {},
635 "output_type": "display_data"
636 }
637 ],
638 "source": [
639 "# If 'Age' exists, average age by country (example only if dataset has relevant column)\n",
640 "if \"Age\" in df.columns:\n",
641 " age_pivot = df.pivot_table(index=\"Country\", values=\"Age\", aggfunc=\"mean\")\n",
642 " display(age_pivot)\n",
643 "else:\n",
644 " print(\"No 'Age' column in dataset to pivot on.\")"
645 ]
646 },
647 {
648 "cell_type": "markdown",
649 "id": "d9793c4e",
650 "metadata": {},
651 "source": [
652 "\n",
653 "## πŸŽ‰ Summary\n",
654 "\n",
655 "In this notebook, we:\n",
656 "- Loaded a sample CSV dataset\n",
657 "- Explored its structure\n",
658 "- Grouped and counted records by country\n",
659 "- Visualized results with a bar chart\n",
660 "- Created a pivot table (if applicable)\n",
661 "\n",
662 "πŸ–₯️ Try modifying this notebook for your own datasets or audit use cases!\n",
663 " "
664 ]
665 }
666 ],
667 "metadata": {
668 "kernelspec": {
669 "display_name": "Python 3",
670 "language": "python",
671 "name": "python3"
672 },
673 "language_info": {
674 "codemirror_mode": {
675 "name": "ipython",
676 "version": 3
677 },
678 "file_extension": ".py",
679 "mimetype": "text/x-python",
680 "name": "python",
681 "nbconvert_exporter": "python",
682 "pygments_lexer": "ipython3",
683 "version": "3.9.6"
684 }
685 },
686 "nbformat": 4,
687 "nbformat_minor": 5
688}
notebooks/racf_access_analysis.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": 3,
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": 4,
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": 4,
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": 5,
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": 5,
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": 6,
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": 6,
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",
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.9.6"
522 }
523 },
524 "nbformat": 4,
525 "nbformat_minor": 5
526}
notebooks/sample_racf_data.txt deleted βˆ’54
@@ -1,54 +0,0 @@
1LISTGRP *
2INFORMATION FOR GROUP PAYROLLB
3 SUPERIOR GROUP=RESEARCH OWNER=IBMUSER CREATED=06.123
4 NO INSTALLATION DATA
5 NO MODEL DATA SET
6 TERMUACC
7 NO SUBGROUPS
8 USER(S)= ACCESS= ACCESS COUNT= UNIVERSAL ACCESS=
9 IBMUSER JOIN 000000 ALTER
10 CONNECT ATTRIBUTES=NONE
11 REVOKE DATE=NONE RESUME DATE=NONE
12 DAF0 CREATE 000000 READ
13 CONNECT ATTRIBUTES=NONE
14 REVOKE DATE=NONE RESUME DATE=NONE
15 IA0 CREATE 000000 READ
16 CONNECT ATTRIBUTES=ADSP SPECIAL OPERATIONS
17 REVOKE DATE=NONE RESUME DATE=NONE
18 AEH0 CREATE 000000 READ
19 CONNECT ATTRIBUTES=NONE
20 REVOKE DATE=NONE RESUME DATE=NONE
21INFORMATION FOR GROUP RESEARCH
22 SUPERIOR GROUP=SYS1 OWNER=IBMUSER CREATED=06.123
23 NO INSTALLATION DATA
24 NO MODEL DATA SET
25 TERMUACC
26 SUBGROUP(S)= PAYROLLB
27 USER(S)= ACCESS= ACCESS COUNT= UNIVERSAL ACCESS=
28 IBMUSER JOIN 000000 ALTER
29 CONNECT ATTRIBUTES=NONE
30 REVOKE DATE=NONE RESUME DATE=NONE
31 DAF0 JOIN 000002 READ
32 CONNECT ATTRIBUTES=NONE
33 REVOKE DATE=NONE RESUME DATE=NONE
34 IA0 CONNECT 000004 READ
35 CONNECT ATTRIBUTES=ADSP SPECIAL OPERATIONS
36 REVOKE DATE=NONE RESUME DATE=NONE
37 ESH25 USE 000000 READ
38 CONNECT ATTRIBUTES=NONE
39 REVOKE DATE=NONE RESUME DATE=NONE
40 PROJECTB USE 000000 READ
41 CONNECT ATTRIBUTES=NONE
42 REVOKE DATE=NONE RESUME DATE=NONE
43 RV2 CREATE 000002 READ
44 CONNECT ATTRIBUTES=NONE
45 REVOKE DATE=NONE RESUME DATE=NONE
46 RV3 CREATE 000000 READ
47 CONNECT ATTRIBUTES=NONE
48 REVOKE DATE=NONE RESUME DATE=NONE
49 ADM1 JOIN 000001 READ
50 CONNECT ATTRIBUTES=OPERATIONS
51 REVOKE DATE=NONE RESUME DATE=NONE
52 AEH0 USE 000000 READ
53 CONNECT ATTRIBUTES=NONE
54 REVOKE DATE=NONE RESUME DATE=NONE