{"id":1034,"date":"2025-08-19T18:46:38","date_gmt":"2025-08-19T18:46:38","guid":{"rendered":"https:\/\/yairmartinezcybersecurityportfolio.com\/?p=1034"},"modified":"2025-12-26T22:59:13","modified_gmt":"2025-12-26T22:59:13","slug":"log-parser-weather-enricher","status":"publish","type":"post","link":"https:\/\/yairmartinezcybersecurityportfolio.com\/?p=1034","title":{"rendered":"Log Parser + Weather Enricher"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What this is<\/h2>\n\n\n\n<p>A <strong>Python-based log parser and weather enricher<\/strong>. It demonstrates how to combine multiple Python libraries in a single, cohesive workflow: <strong>CLI argument parsing, logging, CSV processing with pandas, live API integration, defensive error handling, and automated exporting\/archiving<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Purpose: <strong>skills practice and demonstration<\/strong>. This is a conceptual exercise to learn, integrate, and ship a clean, original script<\/p>\n\n\n\n<p>Below is a demonstration of the script in action.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div class=\"wp-block-stackable-image-box stk-block-image-box stk-hover-parent stk-block stk-3737523 is-style-default\" data-block-id=\"3737523\"><style>.stk-3737523 {margin-bottom:0px !important;}<\/style><div class=\"stk-block-content stk-inner-blocks has-text-align-center stk-row stk-block-image-box__content\">\n<div class=\"wp-block-stackable-image stk-block-image stk-block stk-d8785e3\" data-block-id=\"d8785e3\"><style>.stk-d8785e3 .stk-img-wrapper{width:100% !important;height:453px !important;}:where(.stk-hover-parent:hover,  .stk-hover-parent.stk--is-hovered) .stk-d8785e3 .stk-img-wrapper::after{background-color:#000000B3 !important;}<\/style><figure><span class=\"stk-img-wrapper stk-image--shape-stretch\"><img loading=\"lazy\" decoding=\"async\" class=\"stk-img wp-image-1329\" src=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/LogParserWeatherEnricher.gif\" width=\"1281\" height=\"688\"\/><\/span><\/figure><\/div>\n\n\n\n<div class=\"wp-block-stackable-column stk-block-column stk-column stk-block stk-d2f7a08\" data-v=\"4\" data-block-id=\"d2f7a08\"><style>.stk-d2f7a08 {align-items:center !important;display:flex !important;}<\/style><div class=\"stk-column-wrapper stk-block-column__content stk-container stk-d2f7a08-container stk--no-background stk--no-padding\"><div class=\"stk-block-content stk-inner-blocks stk-d2f7a08-inner-blocks\"><\/div><\/div><\/div>\n<\/div><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Tech stack<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Python<\/strong> (3.x)<\/li>\n\n\n\n<li><strong>pandas<\/strong> \u2013 CSV parsing &amp; data enrichment<\/li>\n\n\n\n<li><strong>requests<\/strong> \u2013 Weather API calls<\/li>\n\n\n\n<li><strong>python-dotenv<\/strong> \u2013 load <code>WEATHER_API_KEY<\/code> from <code>.env<\/code><\/li>\n\n\n\n<li><strong>argparse<\/strong> \u2013 CLI interface<\/li>\n\n\n\n<li><strong>logging<\/strong> \u2013 structured logs: <code>main.log<\/code>, <code>errors.log<\/code>, <code>export.log<\/code><\/li>\n\n\n\n<li><strong>zipfile<\/strong> \u2013 package outputs<\/li>\n\n\n\n<li>Standard libs: <code>os<\/code>, <code>sys<\/code>, <code>json<\/code>, <code>time<\/code>, <code>datetime<\/code>, <code>pathlib<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What the script does<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Accepts CLI input<\/strong><code>python3 main.py logs.csv \"Tokyo\" --units C<\/code>\n<ul class=\"wp-block-list\">\n<li><code>csv<\/code>: path to input log file<\/li>\n\n\n\n<li><code>city<\/code>: city for live weather lookup<\/li>\n\n\n\n<li><code>--units<\/code>: <code>C<\/code> or <code>F<\/code> (default <code>F<\/code>)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Reads logs &amp; filters only <code>CRITICAL<\/code> rows<\/strong> with pandas.<\/li>\n\n\n\n<li><strong>Pulls live weather<\/strong> (current temp + condition) from a public API using an API key loaded from <code>.env<\/code>.<\/li>\n\n\n\n<li><strong>Enriches<\/strong> each <code>CRITICAL<\/code> entry with the weather fields.<\/li>\n\n\n\n<li><strong>Exports<\/strong> the result as timestamped <code>CSV<\/code> and <code>JSON<\/code>, then <strong>zips<\/strong> both into a single archive.<br>Example outputs:\n<ul class=\"wp-block-list\">\n<li><code>critical_with_weather_YYYYMMDD_HHMM.csv<\/code><\/li>\n\n\n\n<li><code>critical_with_weather_YYYYMMDD_HHMM.json<\/code><\/li>\n\n\n\n<li><code>critical_with_weather_YYYYMMDD_HHMM.zip<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Logs the lifecycle<\/strong> (start\/end, API attempts, errors, export\/zip steps) to dedicated log files.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Development log (Parts 1\u20133)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Part 1 \u2014 Argument parsing &amp; logging foundation<\/h3>\n\n\n\n<p><strong>Goal:<\/strong> My goal for part 1 was to establish a solid base so the script accepts user input and reports progress\/errors consistently. Here&#8217;s what i did <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implemented <strong>positional CLI args<\/strong> (<code>csv<\/code>, <code>city<\/code>) and optional <code>--units<\/code>.<\/li>\n\n\n\n<li><strong>Validated input file<\/strong> exists; fail fast with a clear message.<\/li>\n\n\n\n<li>Built a <strong>split logging system<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>main.log<\/code> \u2013 info\/progress<\/li>\n\n\n\n<li><code>errors.log<\/code> \u2013 warnings\/errors<\/li>\n\n\n\n<li><code>export.log<\/code> \u2013 export\/packaging events (set up here for later phases)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Logged <strong>Script start\/end<\/strong> for clear lifecycle markers.<\/li>\n\n\n\n<li>Note: I implemented this on my own first, then used AI for <strong>polish and troubleshooting<\/strong> (e.g., avoiding multiple <code>basicConfig()<\/code> pitfalls and separating concerns with dedicated loggers).<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Part 2 \u2014 Resilience + enrichment<\/h3>\n\n\n\n<p><strong>Focus:<\/strong> Make the script resilient and enrich the filtered data with live weather info.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Constructed the Weather API URL<\/strong> using the CLI city and <code>WEATHER_API_KEY<\/code> from <code>.env<\/code>.<\/li>\n\n\n\n<li>Added <strong>retry logic<\/strong> with a short delay for transient request failures.<\/li>\n\n\n\n<li>Robust <strong>exception handling<\/strong> for:\n<ul class=\"wp-block-list\">\n<li>missing\/invalid CSV<\/li>\n\n\n\n<li>empty CSV or bad format<\/li>\n\n\n\n<li>request failures\/timeouts<\/li>\n\n\n\n<li>invalid JSON or missing keys<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Filtered<\/strong> <code>CRITICAL<\/code> rows via pandas; handled \u201cno CRITICAL rows\u201d gracefully.<\/li>\n\n\n\n<li><strong>Enriched<\/strong> the filtered DF with <code>weather_location<\/code>, <code>weather_temp<\/code>, <code>weather_unit<\/code>, <code>weather_condition<\/code>.\n<ul class=\"wp-block-list\">\n<li>Used <code>.copy()<\/code> before enrichment to avoid <code>SettingWithCopyWarning<\/code>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Exported<\/strong> to CSV and JSON (initially with static filenames).<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Parts 3 Export polish &amp; packaging<\/h3>\n\n\n\n<p><strong>Focus:<\/strong> Professionalize the export flow and user experience.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Timestamped filenames<\/strong> (<code>YYYYMMDD_HHMM<\/code>) for CSV\/JSON to avoid overwrites and aid traceability.<\/li>\n\n\n\n<li><strong>Zipped exports<\/strong> into a single archive for clean delivery.<\/li>\n\n\n\n<li>Used <code>arcname=os.path.basename(...)<\/code> in <code>zipf.write()<\/code> so the ZIP contains <strong>just file names<\/strong>, not host paths.<\/li>\n\n\n\n<li><strong>Prompted the user<\/strong> (yes\/no\/quit) whether to delete the original CSV\/JSON after zipping.<\/li>\n\n\n\n<li>Expanded <strong>export lifecycle logging<\/strong> (file creation, zip success, cleanup).<\/li>\n\n\n\n<li>Ensured <strong>clean terminal output<\/strong> and avoided pandas warnings by working on a copied DF.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting &amp; decisions <\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1) pandas filter\/enrich bug<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Original attempt<\/strong> (buggy): <code>critical_rows = df[df.get('level') == 'CRITICAL']<\/code>\n<ul class=\"wp-block-list\">\n<li><code>df.get('level')<\/code> here is wrong usage; and modifying a filtered view later can trigger <code>SettingWithCopyWarning<\/code>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Final fix<\/strong>: <code>critical_rows = df[df['level'] == 'CRITICAL'].copy()<\/code>\n<ul class=\"wp-block-list\">\n<li>Use column selection via <code>df['level']<\/code> and <strong>create a copy<\/strong> before mutation.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2) ZIP contents &amp; \u201czip slip\u201d learning<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Original zipping<\/strong>: \n<ul class=\"wp-block-list\">\n<li>This can store full\/relative paths as-is.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><img decoding=\"async\" src=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-22-03-07.png\" alt=\"\"><\/li>\n\n\n\n<li><strong>Reviewed and improved<\/strong>:<\/li>\n\n\n\n<li><img decoding=\"async\" src=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-22-04-03.png\" alt=\"\">\n<ul class=\"wp-block-list\">\n<li>I reviewed this choice and learned about <strong>zip-slip<\/strong> (a path traversal vulnerability during extraction).<\/li>\n\n\n\n<li><strong>My script only creates archives<\/strong>, it doesn\u2019t extract them, so it\u2019s <em>not<\/em> directly vulnerable.<\/li>\n\n\n\n<li>Still, using <code>arcname=os.path.basename(...)<\/code> is a <strong>no-downside best practice<\/strong> that keeps archives clean and predictable.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Code walkthroughs (with line-by-line explanations)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Walkthrough A \u2014 CLI parsing &amp; early validation<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"940\" height=\"357\" src=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-19-20-27-05.png\" alt=\"\" class=\"wp-image-1046\" srcset=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-19-20-27-05.png 940w, https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-19-20-27-05-300x114.png 300w, https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-19-20-27-05-768x292.png 768w\" sizes=\"auto, (max-width: 940px) 100vw, 940px\" \/><\/figure>\n\n\n\n<p><strong>Line by line<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>ArgumentParser(...)<\/code>: creates a CLI interface with a helpful description.<\/li>\n\n\n\n<li><code>add_argument(\"csv\")<\/code>: required positional argument for the input CSV path.<\/li>\n\n\n\n<li><code>add_argument(\"city\")<\/code>: required positional argument for the target city.<\/li>\n\n\n\n<li><code>add_argument(\"--units\", choices=[\"C\",\"F\"], default=\"F\")<\/code>: optional flag constrained to <code>C<\/code>\/<code>F<\/code>.<\/li>\n\n\n\n<li><code>args = parser.parse_args()<\/code>: parses actual CLI values into <code>args<\/code>.<\/li>\n\n\n\n<li><code>if not os.path.isfile(args.csv)<\/code>: fail fast if the file doesn\u2019t exist.<\/li>\n\n\n\n<li><code>errors_logger.error(...)<\/code>: record the problem in <code>errors.log<\/code>.<\/li>\n\n\n\n<li><code>print(...)<\/code> + <code>sys.exit(1)<\/code>: inform user and exit non-zero.<\/li>\n\n\n\n<li><code>logger.info(...)<\/code>: confirm the CSV is found in <code>main.log<\/code>.<\/li>\n<\/ul>\n\n\n\n<p><strong>Why this matters<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Clear contracts for how to run the script.<\/li>\n\n\n\n<li>Early, explicit validation and structured logging improve UX and debuggability.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Walkthrough B \u2014 Weather API request with retry + parsing<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>url = f\"https:\/\/api.weatherapi.com\/v1\/current.json?q={args.city}&amp;key={API_KEY}\"\nfor attempt in range(3):\n    try:\n        response = requests.get(url, timeout=5)\n        if response.status_code == 200:\n            break\n        else:\n            logging.warning(f\"Attempt {attempt+1}: API returned {response.status_code}\")\n    except requests.exceptions.RequestException as e:\n        logging.warning(f\"Attempt {attempt+1}: {e}\")\n    time.sleep(2)\nelse:\n    print(\"All attempts failed.\")\n    errors_logger.error(\"All API attempts failed.\")\n    sys.exit(1)\n\ndata = response.json()\nlocation = f\"{data&#91;'location']&#91;'name']}, {data&#91;'location']&#91;'country']}\"\ntemp, unit_symbol = (\n    (data&#91;\"current\"]&#91;\"temp_f\"], \"\u00b0F\") if args.units == \"F\" else (data&#91;\"current\"]&#91;\"temp_c\"], \"\u00b0C\")\n)\ncondition = data&#91;\"current\"]&#91;\"condition\"]&#91;\"text\"]\n<\/code><\/pre>\n\n\n\n<p><strong>Line by line<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Build <code>url<\/code> using user-provided <code>city<\/code> and secret <code>API_KEY<\/code>.<\/li>\n\n\n\n<li><code>for attempt in range(3)<\/code>: bounded retry loop.<\/li>\n\n\n\n<li><code>requests.get(..., timeout=5)<\/code>: prevent hanging; fail fast.<\/li>\n\n\n\n<li><code>if response.status_code == 200: break<\/code>: success path exits the loop.<\/li>\n\n\n\n<li><code>logging.warning(...)<\/code>: log non-200s and network exceptions.<\/li>\n\n\n\n<li><code>time.sleep(2)<\/code>: small backoff between attempts.<\/li>\n\n\n\n<li><code>else:<\/code> on the <code>for<\/code>: runs only if loop never <code>break<\/code>s; we bail with log + exit.<\/li>\n\n\n\n<li><code>response.json()<\/code>: parse JSON payload (exception-handled in the full script).<\/li>\n\n\n\n<li>Extract <code>location<\/code>, choose temp by unit, and get <code>condition<\/code>.<\/li>\n<\/ul>\n\n\n\n<p><strong>Why this matters<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Solid network hygiene (timeouts, bounded retries).<\/li>\n\n\n\n<li>Clean separation of <strong>transport errors<\/strong> vs <strong>data parsing<\/strong>.<\/li>\n\n\n\n<li>Reliable behavior under flaky networks.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"779\" height=\"337\" src=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-15-38-49.png\" alt=\"\" class=\"wp-image-1043\" srcset=\"https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-15-38-49.png 779w, https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-15-38-49-300x130.png 300w, https:\/\/yairmartinezcybersecurityportfolio.com\/wp-content\/uploads\/2025\/08\/Screenshot-from-2025-08-14-15-38-49-768x332.png 768w\" sizes=\"auto, (max-width: 779px) 100vw, 779px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Walkthrough C \u2014 Filter &amp; enrich safely with pandas<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>df = pd.read_csv(args.csv)\ncritical_rows = df&#91;df&#91;'level'] == 'CRITICAL'].copy()\n\ncritical_rows.loc&#91;:, \"weather_location\"] = location\ncritical_rows.loc&#91;:, \"weather_temp\"] = temp\ncritical_rows.loc&#91;:, \"weather_unit\"] = unit_symbol\ncritical_rows.loc&#91;:, \"weather_condition\"] = condition\n<\/code><\/pre>\n\n\n\n<p><strong>Line by line<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>pd.read_csv(...)<\/code>: load the input logs into a DataFrame.<\/li>\n\n\n\n<li><code>df['level'] == 'CRITICAL'<\/code>: boolean mask for the target rows.<\/li>\n\n\n\n<li><code>.copy()<\/code>: create a new, writable DataFrame to avoid view\/assignment issues.<\/li>\n\n\n\n<li><code>.loc[:, \"weather_location\"] = ...<\/code>: column-wise assignment (explicit, clear).<\/li>\n\n\n\n<li>Repeat for <code>weather_temp<\/code>, <code>weather_unit<\/code>, <code>weather_condition<\/code>.<\/li>\n<\/ul>\n\n\n\n<p><strong>Why this matters<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Demonstrates correct pandas filtering and mutation without <code>SettingWithCopyWarning<\/code>.<\/li>\n\n\n\n<li>Keeps the original <code>df<\/code> untouched while producing a clean enriched subset.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Reflection: <\/h2>\n\n\n\n<p>Compared to my <strong>Nmap Dashboard<\/strong> project, I used AI <strong>less<\/strong> here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>I wrote most of the code myself, then used AI for <strong>polish, debugging, and second opinions<\/strong>.<\/li>\n\n\n\n<li>When AI suggested alternatives, I <strong>researched and decided<\/strong> deliberately (e.g., <code>.copy()<\/code> to avoid chained assignment issues, <code>arcname<\/code> for ZIP hygiene).<\/li>\n\n\n\n<li>The process helped me go a layer deeper into Python and pandas; I can now <strong>read and explain<\/strong> everything I wrote and most AI suggestions I kept.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">How to run<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 -m venv venv\nsource venv\/bin\/activate\npip install -r requirements.txt\n\n# create .env with your API key\necho 'WEATHER_API_KEY=your_api_key_here' &gt; .env\n\n# run the script\npython3 main.py logs.csv \"Tokyo\" --units C\n<\/code><\/pre>\n\n\n\n<p><strong>Outputs<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>critical_with_weather_YYYYMMDD_HHMM.csv<\/code><\/li>\n\n\n\n<li><code>critical_with_weather_YYYYMMDD_HHMM.json<\/code> <\/li>\n\n\n\n<li><code>critical_with_weather_YYYYMMDD_HHMM.zip<\/code><\/li>\n<\/ul>\n\n\n\n<p>TRY IT OUT AT <a href=\"https:\/\/github.com\/yairemartinez\/Log-Parser-Weather-Enricher\">https:\/\/github.com\/yairemartinez\/Log-Parser-Weather-Enricher<\/a> <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Closing Summary<\/h2>\n\n\n\n<p>Working through this capstone gave me confidence in handling <strong>end-to-end Python scripting projects<\/strong> from parsing input to enriching data, handling errors, exporting results, and securing the workflow. While the script itself is conceptual, the process taught me transferable skills: how to think about data pipelines, build resilient code, and apply security-minded decision making.<\/p>\n\n\n\n<p>THANKS FOR READING. \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What this is A Python-based log parser and weather enricher. It demonstrates how to combine multiple Python libraries in a single, cohesive workflow: CLI argument parsing, logging, CSV processing with pandas, live API integration, defensive error handling, and automated exporting\/archiving. Purpose: skills practice and demonstration. This is a conceptual exercise to learn, integrate, and ship [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1329,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,37],"tags":[56,42,57,51,44],"class_list":["post-1034","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-projects","category-python","tag-api","tag-linux","tag-log-enrichment","tag-logging","tag-python"],"_links":{"self":[{"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/posts\/1034","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1034"}],"version-history":[{"count":12,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/posts\/1034\/revisions"}],"predecessor-version":[{"id":1330,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/posts\/1034\/revisions\/1330"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=\/wp\/v2\/media\/1329"}],"wp:attachment":[{"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1034"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1034"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yairmartinezcybersecurityportfolio.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1034"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}