7 Commits

Author SHA1 Message Date
dependabot[bot]
d544deb70f Bump requests from 2.32.5 to 2.33.0 in /src
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 16:40:34 +00:00
6fb6fbd828 try to be less embarassing in the future. 2026-03-22 12:43:35 -05:00
c21f8089d4 fun 2026-03-03 13:37:32 -06:00
a478f708a2 certs update 2026-03-03 12:45:22 -06:00
d21a8ec278 Update requirements.txt 2026-02-21 00:36:50 -06:00
7232d1f8de formalizing 2026-02-20 22:23:25 -06:00
3bd27f59d7 feat: implement SPA loading bar, harden status page logic, and optimize Dockerfile 2026-02-20 22:15:21 -06:00
12 changed files with 477 additions and 199 deletions

View File

@@ -1,10 +1,34 @@
FROM python:3.10-bullseye
# Use a slimmer base image to reduce image size and pull times
FROM python:3.10-slim-bullseye
LABEL maintainer="Andrew Simonson <asimonson1125@gmail.com>"
# Set environment variables for better Python performance in Docker
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
# Create a non-root user for security
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Copy only the requirements file first to leverage Docker layer caching
COPY src/requirements.txt .
# Install dependencies as root, but then switch to the non-root user
RUN pip install -r requirements.txt
# Copy the rest of the source code
COPY src/ .
RUN pip install --no-cache-dir -r requirements.txt
# Ensure the appuser owns the app directory
RUN chown -R appuser:appuser /app
CMD [ "gunicorn", "--bind", "0.0.0.0:8080", "app:app"]
# Switch to the non-root user for better security
USER appuser
# Expose the port (Gunicorn's default or specified in CMD)
EXPOSE 8080
# Start Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]

View File

@@ -1,11 +1,92 @@
# I made a uhh website
So people can see how excellent my coding standards are.
# Personal Portfolio & Service Monitor
* Style: 5/10
* Originality: 3/10
* Security: Yes*
* Viruses: not included
A Flask-based website for my personal portfolio and a service monitoring dashboard. This project handles dynamic project showcases, automated service health tracking, and production-ready optimizations.
You gotta uhh `pip3 install -r requirements.txt` and `python3 app.py` that thing
## Features
Docker compose configured to expose at `localhost:8080`
- **Content Management**: Pages like projects, books, and skills are managed via JSON files in the `static` directory.
- **Service Monitoring**: Background health checks for external services with uptime statistics stored in PostgreSQL.
- **Optimizations**:
- HTML, CSS, and JS minification via `Flask-Minify`.
- MD5-based cache busting for static assets.
- Configurable cache-control headers.
- **Security**: Pre-configured headers for XSS protection and frame security.
- **Deployment**: Ready for containerized deployment with Docker and Gunicorn.
## Tech Stack
- **Backend**: Python 3.12, Flask
- **Frontend**: Vanilla CSS/JS, Jinja2
- **Database**: PostgreSQL (optional, for monitoring history)
- **Infrastructure**: Docker, docker-compose
## Project Structure
```text
.
├── src/
│ ├── app.py # Application entry point
│ ├── monitor.py # Service monitoring logic
│ ├── config.py # Environment configuration
│ ├── templates/ # HTML templates
│ ├── static/ # CSS, JS, and JSON data
│ └── requirements.txt # Python dependencies
├── Dockerfile # Container definition
├── docker-compose.yml # Local stack orchestration
└── STATUS_MONITOR_README.md # Monitoring system documentation
```
## Getting Started
### Using Docker
To run the full stack (App + PostgreSQL):
1. **Clone the repository**:
```bash
git clone https://github.com/asimonson1125/asimonson1125.github.io.git
cd asimonson1125.github.io
```
2. **Start services**:
```bash
docker-compose up --build
```
3. **Access the site**:
Visit [http://localhost:8080](http://localhost:8080).
### Local Development
To run the Flask app without Docker:
1. **Set up a virtual environment**:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
2. **Install dependencies**:
```bash
pip install -r src/requirements.txt
```
3. **Run the application**:
```bash
cd src
python3 app.py
```
*Note: status monitor is by default disabled outside of its container cluster*
## Service Monitoring
The monitoring system in `src/monitor.py` tracks service availability. It:
- Runs concurrent health checks every hour.
- Calculates uptime for various windows (24h, 7d, 30d).
- Provides a status UI at `/status` and a JSON API at `/api/status`.
See [STATUS_MONITOR_README.md](./STATUS_MONITOR_README.md) for more details.
## License
This project is personal property. All rights reserved.

View File

@@ -13,8 +13,10 @@ import requests
SERVICES = [
{'id': 'main', 'name': 'asimonson.com', 'url': 'https://asimonson.com', 'timeout': 10},
# {'id': 'EternalRelays', 'name': 'eternalrelays.com', 'url': 'https://eternalrelays.com', 'timeout': 10},
{'id': 'files', 'name': 'files.asimonson.com', 'url': 'https://files.asimonson.com', 'timeout': 10},
{'id': 'git', 'name': 'git.asimonson.com', 'url': 'https://git.asimonson.com', 'timeout': 10},
{'id': 'cascadalyst', 'name': 'cascadalyst.com', 'url': 'https://cascadalyst.com', 'timeout': 10},
]
CHECK_INTERVAL = 60 # seconds between checks
@@ -248,7 +250,7 @@ class ServiceMonitor:
if not cur.fetchone()[0]:
return None
return round((online_count / total_count) * 100, 2)
return round((online_count / total_count) * 100, 3)
finally:
conn.close()

36
src/requirements.txt Executable file → Normal file
View File

@@ -1,23 +1,23 @@
blinker==1.8.2
certifi==2024.7.4
charset-normalizer==3.3.2
click==8.1.7
Flask==3.0.3
Flask-Minify==0.48
gunicorn==22.0.0
blinker==1.9.0
certifi==2026.1.4
charset-normalizer==3.4.4
click==8.3.1
Flask==3.1.3
Flask-Minify==0.50
gunicorn==25.1.0
htmlminf==0.1.13
idna==3.7
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.4
Jinja2==3.1.6
jsmin==3.0.1
lesscpy==0.15.1
MarkupSafe==2.1.5
packaging==24.1
MarkupSafe==3.0.3
packaging==26.0
ply==3.11
rcssmin==1.1.2
requests==2.32.3
six==1.16.0
urllib3==2.2.2
Werkzeug==3.0.3
xxhash==3.4.1
psycopg2-binary==2.9.9
psycopg2-binary==2.9.11
rcssmin==1.2.2
requests==2.33.0
six==1.17.0
urllib3==2.6.3
Werkzeug==3.1.6
xxhash==3.6.0

View File

@@ -219,10 +219,10 @@ tr {
margin: auto;
display: block;
/* width: 5em; */
width: 10em;
width: 8em;
/* height: 30em; */
/* max-height: 70vh; */
max-width: 90vw;
max-width: 70vw;
}
#homeName {
@@ -341,8 +341,8 @@ tr {
position: fixed;
top: 0;
left: 0;
min-width: 100vw;
min-height: 100vh;
min-width: 200vw;
min-height: 200vh;
overflow: hidden;
background-color: rgba(var(--bg-card-rgb), 0.85);
z-index: -1;
@@ -778,7 +778,6 @@ tr {
text-transform: uppercase;
border: 1px solid currentColor;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(6px);
}
.proj-status-badge.complete { color: var(--status-online); }
@@ -1512,6 +1511,10 @@ tr {
grid-template-columns: 1fr;
}
.cert-grid {
grid-template-columns: 1fr;
}
.legend-items {
flex-direction: column;
gap: 0.5em;
@@ -1840,3 +1843,133 @@ tr {
.text-muted {
color: #888 !important;
}
/* Certifications */
.cert-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5em;
margin-top: 1.5em;
}
.cert-group {
margin-bottom: 1.5em;
min-width: 0;
}
.cert-group-provider {
font-size: 0.75rem;
color: var(--text-secondary);
margin: 0 0 0.25em 0;
letter-spacing: 0.08em;
}
.cert-program-badge {
display: inline-flex;
align-items: center;
max-width: 100%;
padding: 0.35em 0.9em;
margin-bottom: 1em;
border: 1px solid rgba(var(--accent-rgb), 0.75);
background: rgba(var(--accent-rgb), 0.18);
color: var(--text-heading) !important;
font-size: 0.78rem;
letter-spacing: 0.09em;
text-transform: uppercase;
font-family: 'Courier New', Courier, monospace;
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
opacity: 1 !important;
}
.cert-program-badge:hover {
background: rgba(var(--accent-rgb), 0.32);
border-color: rgb(var(--accent-rgb));
box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.35);
opacity: 1 !important;
}
.cert-list {
list-style: none;
padding: 0;
margin: 0.5em 0 0 0;
display: flex;
flex-direction: column;
gap: 0.35em;
}
.cert-list li {
margin: 0;
}
.cert-item {
display: flex;
align-items: center;
gap: 0.6em;
padding: 0.5em 0.75em;
border-left: 3px solid rgba(var(--accent-rgb), 0.65);
background: rgba(0, 0, 0, 0.25);
color: var(--text-secondary) !important;
font-size: 0.88rem;
text-decoration: underline;
text-decoration-color: rgba(var(--accent-rgb), 0.35);
text-underline-offset: 3px;
transition: border-color 0.2s ease, background 0.2s ease, padding-left 0.2s ease, color 0.2s ease;
opacity: 1 !important;
}
.cert-item::before {
content: '';
color: rgb(var(--accent-rgb));
font-size: 1.25em;
line-height: 1;
flex-shrink: 0;
transition: transform 0.2s ease;
}
.cert-item:hover {
border-left-color: rgb(var(--accent-rgb));
background: rgba(var(--accent-rgb), 0.12);
padding-left: 1em;
color: var(--text-heading) !important;
text-decoration-color: rgba(var(--accent-rgb), 0.6);
opacity: 1 !important;
}
.cert-item:hover::before {
transform: translateX(3px);
}
/* SPA Loading Bar */
#loading-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent) 0%, #fff 50%, var(--accent) 100%);
background-size: 200% 100%;
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.6);
z-index: 2000;
width: 0%;
opacity: 0;
transition: width 0.4s cubic-bezier(0.1, 0.05, 0.1, 1), opacity 0.3s ease;
pointer-events: none;
}
#loading-bar.visible {
opacity: 1;
}
#loading-bar.active {
width: 70%;
animation: loading-shimmer 2s infinite linear;
}
#loading-bar.finish {
opacity: 0;
width: 100%;
transition: width 0.2s ease, opacity 0.4s ease 0.1s;
}
@keyframes loading-shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}

View File

@@ -14,50 +14,78 @@ function toggleMenu(collapse) {
}
async function goto(location, { push = true } = {}) {
let response;
const loadingBar = document.getElementById('loading-bar');
if (loadingBar) {
loadingBar.style.width = ''; // Clear inline style from previous run
}
let loadingTimeout = setTimeout(() => {
if (loadingBar) {
loadingBar.classList.remove('finish');
loadingBar.classList.add('active');
loadingBar.classList.add('visible');
}
}, 150);
try {
response = await fetch("/api/goto/" + location, {
const response = await fetch("/api/goto/" + location, {
credentials: "include",
method: "GET",
mode: "cors",
});
if (!response.ok) {
console.error(`Navigation failed: HTTP ${response.status}`);
return;
throw new Error(`HTTP ${response.status}`);
}
} catch (err) {
console.error("Navigation fetch failed:", err);
return;
}
document.dispatchEvent(new Event('beforenavigate'));
// Wait for the full body to download - this is usually the slow part
const [metadata, content] = await response.json();
const [metadata, content] = await response.json();
const root = document.getElementById("root");
root.innerHTML = content;
document.dispatchEvent(new Event('beforenavigate'));
// Re-execute scripts injected via innerHTML (browser ignores them otherwise)
root.querySelectorAll("script").forEach(function(oldScript) {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach(function(attr) {
newScript.setAttribute(attr.name, attr.value);
const root = document.getElementById("root");
root.innerHTML = content;
// Re-execute scripts
root.querySelectorAll("script").forEach(function(oldScript) {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach(function(attr) {
newScript.setAttribute(attr.name, attr.value);
});
newScript.textContent = oldScript.textContent;
oldScript.parentNode.replaceChild(newScript, oldScript);
});
newScript.textContent = oldScript.textContent;
oldScript.parentNode.replaceChild(newScript, oldScript);
});
if (window.location.href.includes("#")) {
const id = decodeURIComponent(window.location.hash.substring(1));
const el = document.getElementById(id);
if (el) el.scrollIntoView();
} else {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
}
if (window.location.href.includes("#")) {
const id = decodeURIComponent(window.location.hash.substring(1));
const el = document.getElementById(id);
if (el) el.scrollIntoView();
} else {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
}
toggleMenu(true);
document.querySelector("title").textContent = metadata["title"];
if (push) {
history.pushState(null, null, metadata["canonical"]);
toggleMenu(true);
document.querySelector("title").textContent = metadata["title"];
if (push) {
history.pushState(null, null, metadata["canonical"]);
}
} catch (err) {
console.error("Navigation failed:", err);
} finally {
clearTimeout(loadingTimeout);
if (loadingBar && loadingBar.classList.contains('active')) {
loadingBar.classList.add('finish');
loadingBar.classList.remove('active');
setTimeout(() => {
if (!loadingBar.classList.contains('active')) {
loadingBar.style.width = '0%';
loadingBar.classList.remove('finish');
loadingBar.classList.remove('visible');
}
}, 500);
}
}
}

View File

@@ -1,4 +1,8 @@
let statusIntervalId = null;
// Use a global to track the interval and ensure we don't stack listeners
if (window.statusIntervalId) {
clearInterval(window.statusIntervalId);
window.statusIntervalId = null;
}
async function fetchStatus() {
try {
@@ -17,7 +21,8 @@ async function fetchStatus() {
function updateStatusDisplay(data) {
if (data.last_check) {
const lastCheck = new Date(data.last_check);
document.getElementById('lastUpdate').textContent = `Last checked: ${lastCheck.toLocaleString()}`;
const lastUpdateEl = document.getElementById('lastUpdate');
if (lastUpdateEl) lastUpdateEl.textContent = `Last checked: ${lastCheck.toLocaleString()}`;
}
if (data.next_check) {
@@ -28,11 +33,12 @@ function updateStatusDisplay(data) {
}
}
data.services.forEach(function(service) {
updateServiceCard(service);
});
updateOverallStatus(data.services);
if (data.services) {
data.services.forEach(function(service) {
updateServiceCard(service);
});
updateOverallStatus(data.services);
}
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn) {
@@ -65,36 +71,38 @@ function updateServiceCard(service) {
const uptimeDisplay = document.getElementById(`uptime-${service.id}`);
const checksDisplay = document.getElementById(`checks-${service.id}`);
timeDisplay.textContent = service.response_time !== null ? `${service.response_time}ms` : '--';
if (timeDisplay) timeDisplay.textContent = service.response_time !== null ? `${service.response_time}ms` : '--';
if (service.status_code !== null) {
codeDisplay.textContent = service.status_code;
} else {
codeDisplay.textContent = service.status === 'unknown' ? 'Unknown' : 'Error';
if (codeDisplay) {
if (service.status_code !== null) {
codeDisplay.textContent = service.status_code;
} else {
codeDisplay.textContent = service.status === 'unknown' ? 'Unknown' : 'Error';
}
}
card.classList.remove('online', 'degraded', 'offline', 'unknown');
switch (service.status) {
case 'online':
stateDot.className = 'state-dot online';
stateText.textContent = 'Operational';
if (stateDot) stateDot.className = 'state-dot online';
if (stateText) stateText.textContent = 'Operational';
card.classList.add('online');
break;
case 'degraded':
case 'timeout':
stateDot.className = 'state-dot degraded';
stateText.textContent = service.status === 'timeout' ? 'Timeout' : 'Degraded';
if (stateDot) stateDot.className = 'state-dot degraded';
if (stateText) stateText.textContent = service.status === 'timeout' ? 'Timeout' : 'Degraded';
card.classList.add('degraded');
break;
case 'offline':
stateDot.className = 'state-dot offline';
stateText.textContent = 'Offline';
if (stateDot) stateDot.className = 'state-dot offline';
if (stateText) stateText.textContent = 'Offline';
card.classList.add('offline');
break;
default:
stateDot.className = 'state-dot loading';
stateText.textContent = 'Unknown';
if (stateDot) stateDot.className = 'state-dot loading';
if (stateText) stateText.textContent = 'Unknown';
card.classList.add('unknown');
}
@@ -114,6 +122,8 @@ function updateServiceCard(service) {
function updateOverallStatus(services) {
const overallBar = document.getElementById('overallStatus');
if (!overallBar) return;
const icon = overallBar.querySelector('.summary-icon');
const title = overallBar.querySelector('.summary-title');
const subtitle = document.getElementById('summary-subtitle');
@@ -125,46 +135,52 @@ function updateOverallStatus(services) {
const degraded = services.filter(function(s) { return s.status === 'degraded' || s.status === 'timeout'; }).length;
const offline = services.filter(function(s) { return s.status === 'offline'; }).length;
onlineCount.textContent = online;
totalCount.textContent = total;
if (onlineCount) onlineCount.textContent = online;
if (totalCount) totalCount.textContent = total;
overallBar.classList.remove('online', 'degraded', 'offline');
icon.classList.remove('operational', 'partial', 'major', 'loading');
if (icon) icon.classList.remove('operational', 'partial', 'major', 'loading');
// Determine overall status
if (online === total) {
// All systems operational
overallBar.classList.add('online');
icon.classList.add('operational');
icon.textContent = '\u2713';
title.textContent = 'All Systems Operational';
subtitle.textContent = `All ${total} services are running normally`;
if (icon) {
icon.classList.add('operational');
icon.textContent = '\u2713';
}
if (title) title.textContent = 'All Systems Operational';
if (subtitle) subtitle.textContent = `All ${total} services are running normally`;
} else if (offline >= Math.ceil(total / 2)) {
// Major outage (50% or more offline)
overallBar.classList.add('offline');
icon.classList.add('major');
icon.textContent = '\u2715';
title.textContent = 'Major Outage';
subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline, ${degraded} degraded`;
if (icon) {
icon.classList.add('major');
icon.textContent = '\u2715';
}
if (title) title.textContent = 'Major Outage';
if (subtitle) subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline, ${degraded} degraded`;
} else if (offline > 0 || degraded > 0) {
// Partial outage
overallBar.classList.add('degraded');
icon.classList.add('partial');
icon.textContent = '\u26A0';
title.textContent = 'Partial Outage';
if (offline > 0 && degraded > 0) {
subtitle.textContent = `${offline} offline, ${degraded} degraded`;
} else if (offline > 0) {
subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline`;
} else {
subtitle.textContent = `${degraded} service${degraded !== 1 ? 's' : ''} degraded`;
if (icon) {
icon.classList.add('partial');
icon.textContent = '\u26A0';
}
if (title) title.textContent = 'Partial Outage';
if (subtitle) {
if (offline > 0 && degraded > 0) {
subtitle.textContent = `${offline} offline, ${degraded} degraded`;
} else if (offline > 0) {
subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline`;
} else {
subtitle.textContent = `${degraded} service${degraded !== 1 ? 's' : ''} degraded`;
}
}
} else {
// Unknown state
icon.classList.add('loading');
icon.textContent = '\u25D0';
title.textContent = 'Status Unknown';
subtitle.textContent = 'Waiting for service data';
if (icon) {
icon.classList.add('loading');
icon.textContent = '\u25D0';
}
if (title) title.textContent = 'Status Unknown';
if (subtitle) subtitle.textContent = 'Waiting for service data';
}
}
@@ -191,23 +207,23 @@ function refreshStatus() {
}
function initStatusPage() {
if (statusIntervalId !== null) {
clearInterval(statusIntervalId);
if (window.statusIntervalId) {
clearInterval(window.statusIntervalId);
}
fetchStatus();
statusIntervalId = setInterval(fetchStatus, 60000);
window.statusIntervalId = setInterval(fetchStatus, 60000);
}
// Clean up polling interval when navigating away via SPA
document.addEventListener('beforenavigate', function() {
if (statusIntervalId !== null) {
clearInterval(statusIntervalId);
statusIntervalId = null;
function cleanupStatusPage() {
if (window.statusIntervalId) {
clearInterval(window.statusIntervalId);
window.statusIntervalId = null;
}
});
document.removeEventListener('beforenavigate', cleanupStatusPage);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initStatusPage);
} else {
document.addEventListener('beforenavigate', cleanupStatusPage);
if (document.getElementById('overallStatus')) {
initStatusPage();
}

View File

@@ -1,4 +1,9 @@
{
"PsyCom - Physical Combinatorics": {
"status": "WIP",
"classes": "programming",
"content": "Experimental innovation engine operating on physical attributes and limitations of proven existing technologies with further AI review."
},
"Antietam-Conococheague Watershed Monitoring": {
"status": "complete",
"classes": "geospacial",
@@ -50,7 +55,7 @@
]
},
"Portfolio Website": {
"status": "WIP",
"status": "complete",
"classes": "programming",
"content": "This website is my personal sandbox where I've integrated some of my data projects via docker cluster. It is self hosted and zero-trust secure while remaining dynamic and free of the tech debt that comes with pre-designed sites and excessive framework application. Yeah, I can do E2E.",
"links": [

View File

@@ -1,75 +1,49 @@
{% block content %}
<div class="foreground"></div>
<div class="foregroundContent">
<h1>Andrew's Secret Certification Page</h1>
<h1>Certifications</h1>
<p class="page-subtitle">
Comprehensive list verifiable on
<a href="https://www.linkedin.com/in/simonsonandrew/details/certifications/">LinkedIn</a>
</p>
<p>Computer Science BS and Data Science MS from Rochester Institute of Technology</p>
<strong
>See
<a
href="https://www.linkedin.com/in/simonsonandrew/details/certifications/"
>here</a
>
for a comprehensive list of certifications that can be confirmed by
LinkedIn</strong
>
<p>Some highlights:</p>
<strong><a href="http://credentials.edx.org/credentials/4b7e78dca8154c0d88ca9abc5aedb4ac">UCSanDiegoX Data Science MicroMasters Program</a></strong>
<ol>
<li><a href="https://courses.edx.org/certificates/b6deccc56e5344ae84cb55f9ad81fd79">DSE200x Python for Data Science</a></li>
<li><a href="https://courses.edx.org/certificates/f29d0e65fc024c6e95121619e329a286">DSE210x Probability and Statistics in Data Science using Python</a></li>
<li><a href="https://courses.edx.org/certificates/cccc2bd2ed61470e8492d6da1be530c5">DSE220x Machine Learning Fundamentals</a></li>
<li><a href="https://courses.edx.org/certificates/4dfd6563a1f84caaa8922a02a5125f29">DSE230x Big Data Analytics Using Spark</a></li>
</ol>
<strong>One-Off Courses</strong>
<ul>
<li>
<a
href="https://files.asimonson.com/u/2403_3_1303226_1765822061_Databricks%20-%20Generic.pdf"
>Machine Learning Operations by Databricks</a
>
</li>
<li>
<a
href="https://www.linkedin.com/learning/certificates/2cb69378c606fec5a6f3a107b99a896862db392b7a3692f71a6b53af5d5545c5"
>Career Essentials in Data Analysis by Microsoft</a
>
</li>
<li>
<a
href="https://www.linkedin.com/learning/certificates/7facc28a13405134b3b7fa785303e9b1cf697f32d67f759e89960fbdc8a044d9"
>Career Essentials in GitHub Professional Certificate</a
>
</li>
<li>
<a
href="https://www.linkedin.com/learning/certificates/7b952323152e258ca468c33ddc9ebcf3c55036f58a5cfb3fb9c1410da655aaa5"
>Docker Foundations Professional Certificate</a
>
</li>
<li>
<a
href="https://www.linkedin.com/learning/certificates/7017147ac73af5bc26fdab9b3c43671fb8105a0de59d4689d5f0f71c549c150f"
>Data Science Foundations: Fundamentals</a
>
</li>
</ul>
<strong>RIT Entrepreneurial Certifications</strong>
<ul>
<li>
<a href="https://files.asimonson.com/u/designThinkingCert.pdf"
>Design Thinking Certification</a
>
</li>
<li>
<a href="https://files.asimonson.com/u/ideationCert.pdf"
>Ideation Certification</a
>
</li>
<li>
<a href="https://files.asimonson.com/u/toolsForInnovatorsCert.pdf"
>Tools for Innovators Certification</a
>
</li>
</ul>
<div class="boxed cert-group">
<p class="cert-group-provider">UCSanDiegoX &middot; edX</p>
<h2 class="concentratedHead">Data Science MicroMasters Program</h2>
<a href="http://credentials.edx.org/credentials/4b7e78dca8154c0d88ca9abc5aedb4ac" class="cert-program-badge">
View Program Certificate
</a>
<ul class="cert-list">
<li><a href="https://courses.edx.org/certificates/b6deccc56e5344ae84cb55f9ad81fd79" class="cert-item">DSE200x &mdash; Python for Data Science</a></li>
<li><a href="https://courses.edx.org/certificates/f29d0e65fc024c6e95121619e329a286" class="cert-item">DSE210x &mdash; Probability and Statistics in Data Science using Python</a></li>
<li><a href="https://courses.edx.org/certificates/cccc2bd2ed61470e8492d6da1be530c5" class="cert-item">DSE220x &mdash; Machine Learning Fundamentals</a></li>
<li><a href="https://courses.edx.org/certificates/4dfd6563a1f84caaa8922a02a5125f29" class="cert-item">DSE230x &mdash; Big Data Analytics Using Spark</a></li>
</ul>
</div>
<div class="cert-grid">
<div class="boxed cert-group">
<h3 class="concentratedHead">One-Off Courses</h3>
<ul class="cert-list">
<li><a href="https://files.asimonson.com/u/2662_3_1303226_1772561098_Databricks%20-%20Generic.pdf" class="cert-item">Building Retrieval Agents On Databricks</a></li>
<li><a href="https://files.asimonson.com/u/2403_3_1303226_1765822061_Databricks%20-%20Generic.pdf" class="cert-item">Machine Learning Operations by Databricks</a></li>
<li><a href="https://www.linkedin.com/learning/certificates/2cb69378c606fec5a6f3a107b99a896862db392b7a3692f71a6b53af5d5545c5" class="cert-item">Career Essentials in Data Analysis by Microsoft</a></li>
<li><a href="https://www.linkedin.com/learning/certificates/7facc28a13405134b3b7fa785303e9b1cf697f32d67f759e89960fbdc8a044d9" class="cert-item">Career Essentials in GitHub Professional Certificate</a></li>
<li><a href="https://www.linkedin.com/learning/certificates/7b952323152e258ca468c33ddc9ebcf3c55036f58a5cfb3fb9c1410da655aaa5" class="cert-item">Docker Foundations Professional Certificate</a></li>
<li><a href="https://www.linkedin.com/learning/certificates/7017147ac73af5bc26fdab9b3c43671fb8105a0de59d4689d5f0f71c549c150f" class="cert-item">Data Science Foundations: Fundamentals</a></li>
</ul>
</div>
<div class="boxed cert-group">
<p class="cert-group-provider">Rochester Institute of Technology</p>
<h3 class="concentratedHead">Entrepreneurial Certifications</h3>
<ul class="cert-list">
<li><a href="https://files.asimonson.com/u/designThinkingCert.pdf" class="cert-item">Design Thinking Certification</a></li>
<li><a href="https://files.asimonson.com/u/ideationCert.pdf" class="cert-item">Ideation Certification</a></li>
<li><a href="https://files.asimonson.com/u/toolsForInnovatorsCert.pdf" class="cert-item">Tools for Innovators Certification</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}

View File

@@ -59,6 +59,7 @@
</head>
{% block header %}
<body onpopstate="backButton()">
<div id="loading-bar"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<main id='map'></main>
<div id="contentStuffer">

View File

@@ -1,6 +1,10 @@
{% block content %} {% macro nameplate() %}
<div>
<h1 id="homeName" class="textGrad">Andrew's Definitely Active Website</h1>
<h1 id="homeName" class="textGrad">oh no.</h1>
<h2>I did not plan for visitors.</h2>
<br /> <hr> <br />
<div class="flex vertOnMobile">
<div>
<img

View File

@@ -22,7 +22,15 @@
industry obsessed with implicit rules and exclusive empiricism.
As the analysis grew more sophisticated, so too did the tech
stack - to the point that I now manage most services, like this
website, end to end, container image to insight visual. -->
website, end to end, container image to insight visual.
With substantial development and systems operation expertise, I
am capable of working with small teams that require many critical
functions, from devops to analytical platforms, to artificial
intelligence implementations, to all be handled by the same person.
Looking for an analyst who can create and operate the full digital
stack on your crack team of scientists? You've come to the right place.
-->
<br />
<br />
I get bored and throw random stuff on this website.<br/>
@@ -33,6 +41,8 @@
<br/>
<h4 class='concentratedHead'>
I also have a
<a href="certs">certifications page</a>
and a
<a href="Resume_Simonson_Andrew.pdf" target="_blank">resume</a>
for unexplained reasons.
</h4>