diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..69d9d27 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +npm-debug.log +dist +.git +.gitignore +.DS_Store +*.log diff --git a/.github/workflows/deploy-containerapp.yml b/.github/workflows/deploy-containerapp.yml new file mode 100644 index 0000000..50b3157 --- /dev/null +++ b/.github/workflows/deploy-containerapp.yml @@ -0,0 +1,42 @@ +name: Deploy Container App + +on: + push: + branches: [main] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Azure Login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + + - name: Build and push image + run: | + ACR=${{ secrets.ACR_NAME }} + IMAGE="$ACR.azurecr.io/resume-web:${{ github.sha }}" + az acr login --name "$ACR" + docker build -f Dockerfile.chat -t "$IMAGE" . + docker push "$IMAGE" + echo "IMAGE=$IMAGE" >> $GITHUB_ENV + + - name: Update Container App + run: | + az containerapp update \ + --name ${{ secrets.CONTAINERAPP_NAME }} \ + --resource-group ${{ secrets.RESOURCE_GROUP }} \ + --image "$IMAGE" \ + --set-env-vars \ + AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPENAI_API_KEY }} \ + AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPENAI_ENDPOINT }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..61fe821 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM nginx:alpine + +COPY --from=builder /app/docs /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Dockerfile.chat b/Dockerfile.chat new file mode 100644 index 0000000..575d44c --- /dev/null +++ b/Dockerfile.chat @@ -0,0 +1,24 @@ +FROM node:20-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY tailwind.css postcss.config.js tailwind.config.js ./ +COPY docs ./docs +RUN npm run build + +FROM python:3.11-slim + +WORKDIR /app + +COPY cv_chat/requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY cv_chat ./cv_chat +COPY --from=build /app/docs ./docs + +ENV PORT=8080 + +CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:8080", "cv_chat.app:app"] diff --git a/README.md b/README.md index 113c89a..3fe7c43 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,98 @@ Only generate CSS that is used on the page, which results in a much smaller file npm run build ``` +Tutorial: What Was Done +--------- + +This project was updated and deployed as a static résumé site. Below is a concise walkthrough of the changes and deployment flow so you can repeat it. + +### 1) Update the content + +- Edit `docs/index.html` and replace the template content with your CV details. +- Keep the overall structure (sections, headers, list items) so the layout stays consistent. + +### 2) Tailwind v4 + PostCSS fixes + +Tailwind v4 moved its PostCSS plugin to a separate package. The build pipeline was adjusted accordingly: + +``` +npm install @tailwindcss/postcss +``` + +In `postcss.config.js`, swap the Tailwind plugin for the new one and pass the config path: + +``` +require("@tailwindcss/postcss")({ + config: "./tailwind.config.js" +}) +``` + +In `tailwind.css`, use the v4 import style: + +``` +@import "tailwindcss"; +``` + +### 3) Two-column layout + +To force two columns on all screen sizes, use `col-count-2` (without the `md:` prefix) on the column container in `docs/index.html`: + +``` +
+``` + +### 4) Replace deprecated CSS + +Autoprefixer warns about `color-adjust` being deprecated. Update this in `tailwind.css`: + +``` +print-color-adjust: exact !important; +``` + +### 5) Docker production container + +A production Docker image builds the CSS and serves the site via Nginx. + +``` +docker build -t universal-resume . +docker run --rm -p 8080:80 universal-resume +``` + +### 6) Azure Static Website deployment + +This setup uses Azure Storage Static Website hosting. + +Install Azure CLI (Windows): + +``` +https://aka.ms/installazurecliwindows +``` + +Login (PowerShell needs the `&` call operator): + +``` +& "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" login --use-device-code +``` + +Then create resources and upload: + +``` +$rg = "universal-resume-rg" +$loc = "germanywestcentral" +$sa = "resumestatic" + (Get-Random -Maximum 99999) + +& "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" group create --name $rg --location $loc +& "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" storage account create --name $sa --resource-group $rg --location $loc --sku Standard_LRS --kind StorageV2 --allow-blob-public-access true +& "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" storage blob service-properties update --account-name $sa --static-website --index-document index.html --404-document index.html + +$web = & "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" storage account show --name $sa --resource-group $rg --query "primaryEndpoints.web" -o tsv + +$key = & "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" storage account keys list --account-name $sa --resource-group $rg --query "[0].value" -o tsv +& "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" storage blob upload-batch --account-name $sa --account-key $key --destination '$web' --source .\docs + +$web +``` + Starting Point --------- diff --git a/cv_chat/.gitattributes b/cv_chat/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/cv_chat/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/cv_chat/.gitignore b/cv_chat/.gitignore new file mode 100644 index 0000000..930e1b2 --- /dev/null +++ b/cv_chat/.gitignore @@ -0,0 +1,17 @@ + +# dependencies +/.ipynb_checkpoints +/.pnp +.pnp.js + +# misc +.DS_Store +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/cv_chat/andrei_context.txt b/cv_chat/andrei_context.txt new file mode 100644 index 0000000..685652c --- /dev/null +++ b/cv_chat/andrei_context.txt @@ -0,0 +1,53 @@ +Name: Andrei Sirazitdinov +Location: Mannheim, Germany +Contact: dyh@list.ru | +49 176 47699707 + +About: +- Ph.D. in Computer Science +- University of Heidelberg | Data Science, ML, AI, Computer Vision +- As a researcher, I was focused on causal inference and individualized treatment effect estimation with deep learning. I also worked on explainable AI for healthcare and vision tasks. Permanent residency and full work authorization in Germany. Open to relocation. + +Research interests: +- Causal inference, individualized treatment assignment, explainable ML +- Built and evaluated deep learning models (MLPs, GANs, VAEs, GNNs) in TensorFlow, Keras, and PyTorch for healthcare decision support, privacy, and explainability. + +Experience: +- University of Heidelberg, Germany (Apr 2020 – Oct 2025) — Ph.D. Candidate + - Individualized treatment assignment with MLPs, GANs, VAEs, and GNNs in TensorFlow/Keras. + - Pain patient treatment strategies using K-NN clustering and XGBoost with RCT validation. + - Explainable pathology patch classification with prototype learning and decision trees in PyTorch. + - Digital twin models using stable diffusion for patient data privacy in PyTorch. + - Dropout prediction model for cancer patients achieving 80% precision in TensorFlow. +- National Institute of Informatics, Japan (Oct 2018 – Apr 2019) — Research Intern + - Computer vision algorithm for long-term video prediction using PyTorch. +- Irkutsk Branch of MSTUCA, Russia (Oct 2014 – Jul 2015) — Student Assistant + - Built a multi-camera system to capture helicopter panel data and populate digital tables automatically. + +Education: +- Ph.D. in Computer Science — University of Heidelberg, Germany (2020 – 2025) +- M.Sc. in Visual Computing — Saarland University, Germany (2016 – 2019) +- B.Sc. in Applied Informatics and Mathematics — Irkutsk State University, Russia (2012 – 2016) +- High School — Irkutsk, Russia (2010 – 2012) + +Projects: +- Trading Bots (Python) — WebSocket API | Docker | GitHub Actions +- Local ChatGPT Clone — Streamlit | OpenAI API | SQLite +- Databricks Integration — Centralized storage for chats and user authentication + +Skills: +- ML/AI: TensorFlow, PyTorch, Scikit-learn, Keras +- Data tools: Pandas, NumPy +- Tools and platforms: Python, SQL, R, Docker, Kubernetes, MLflow, Azure (beginner), Git, Linux, Windows + +Languages: +- English (Fluent speaking, reading, and writing) +- German (B1 certificate, completed C1 courses) +- Russian (Native) + +Selected publications: +- A. Sirazitdinov et al., "Graph Neural Networks for Individual Treatment Effect Estimation," IEEE Access, 2024. +- A. Sirazitdinov et al., "Review of Deep Learning Methods for Individual Treatment Effect Estimation with Automatic Hyperparameter Optimization," TechRxiv, 2022. + +Git Repos: +- https://github.com/dyh1265/Causal-Inference-Library — Causal inference library for GNN-TARNet and related models; includes notebooks and code for the GNN-ITE paper. +- https://github.com/dyh1265/PerPain-allocation — PerPain patient allocation system with two approaches: an R Shiny clustering app and Python ML models (TARnet, GNN-TARnet, GAT-TARnet, T-Learner) for individualized treatment recommendations. diff --git a/cv_chat/app.py b/cv_chat/app.py new file mode 100644 index 0000000..a62345e --- /dev/null +++ b/cv_chat/app.py @@ -0,0 +1,124 @@ +import os +import re +from html import unescape +from html.parser import HTMLParser +from pathlib import Path + +import dotenv +from flask import Flask, request, send_from_directory +from openai import AzureOpenAI + +dotenv.load_dotenv() + +BASE_DIR = Path(__file__).resolve().parent +DOCS_DIR = BASE_DIR.parent / "docs" + +app = Flask(__name__, static_folder=str(DOCS_DIR), static_url_path="") + +client = AzureOpenAI( + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + api_version="2024-02-15-preview", + azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT") +) + + + +context_path = BASE_DIR / "andrei_context.txt" + + +class _CVTextParser(HTMLParser): + def __init__(self): + super().__init__() + self._chunks = [] + self._skip = False + + def handle_starttag(self, tag, attrs): + if tag in {"script", "style"}: + self._skip = True + if tag in {"h1", "h2", "h3", "p", "li"}: + self._chunks.append("\n") + + def handle_endtag(self, tag): + if tag in {"script", "style"}: + self._skip = False + if tag in {"h1", "h2", "h3", "p", "li"}: + self._chunks.append("\n") + + def handle_data(self, data): + if self._skip: + return + text = data.strip() + if text: + self._chunks.append(text) + + def text(self): + raw = " ".join(self._chunks) + normalized = re.sub(r"[ \t]+", " ", raw) + normalized = re.sub(r"\n{2,}", "\n", normalized) + return normalized.strip() + + +def build_context_from_cv(cv_path: Path, extra_context: str) -> str: + if not cv_path.exists(): + return extra_context + parser = _CVTextParser() + parser.feed(cv_path.read_text(encoding="utf-8")) + cv_text = unescape(parser.text()) + cv_text = re.sub(r"[ \t]+\n", "\n", cv_text) + + git_block = "" + if "Git Repos:" in extra_context: + parts = extra_context.split("Git Repos:", 1) + git_block = "Git Repos:\n" + parts[1].strip() + + if git_block: + return f"{cv_text}\n\n{git_block}" + return cv_text + + +if context_path.exists(): + raw_context = context_path.read_text(encoding="utf-8").strip() +else: + raw_context = "" + +andrei_context = build_context_from_cv(DOCS_DIR / "index.html", raw_context) + +conversation = [ + { + "role": "system", + "content": ( + "You are a concise assistant that only answers questions about Andrei Sirazitdinov's skills, competencies, prrojects, and background. " + "Reply in short bullet points (no bold). If the question is not about Andrei's skills, competencies, projects, and background, say you can only answer " + "questions about his skills, competencies, projects, and background and ask the user to rephrase." + + (f"\n\nContext about Andrei:\n{andrei_context}" if andrei_context else "") + ), + } +] + +@app.route("/") +def index(): + return send_from_directory(DOCS_DIR, "index.html") + + +@app.route("/") +def static_files(filename): + return send_from_directory(DOCS_DIR, filename) + +@app.route("/chat", methods=["POST"]) +def chat(): + user_input = request.json.get("message") + conversation.append({"role": "user", "content": user_input}) + + response = client.chat.completions.create( + model="gpt-5.2-chat", + messages=conversation + ) + + reply = response.choices[0].message.content + conversation.append({"role": "assistant", "content": reply}) + + return reply + +if __name__ == '__main__': + port = int(os.getenv("PORT", "8080")) + app.run(host="0.0.0.0", port=port, debug=False) diff --git a/cv_chat/requirements.txt b/cv_chat/requirements.txt new file mode 100644 index 0000000..1294f24 --- /dev/null +++ b/cv_chat/requirements.txt @@ -0,0 +1,4 @@ +flask==3.0.2 +python-dotenv==1.0.1 +openai==1.59.7 +gunicorn==22.0.0 diff --git a/cv_chat/static/index.css b/cv_chat/static/index.css new file mode 100644 index 0000000..a21a891 --- /dev/null +++ b/cv_chat/static/index.css @@ -0,0 +1,150 @@ +h1 { + text-align: center; + margin: 50px; +} + +div { + position: relative; + display: block; + unicode-bidi: isolate; +} + +.rsc-container { + background: rgb(245, 248, 251); + border-radius: 10px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 12px 24px 0px; + font-family: monospace; + overflow: hidden; + position: relative; + inset: initial; + width: 350px; + height: 520px; + z-index: 999; + margin: auto; + transform: scale(1); + transform-origin: right bottom; + transition: transform 0.3s ease 0s; +} + +.rsc-header { + -webkit-box-align: center; + align-items: center; + background: rgb(110, 72, 170); + color: rgb(255, 255, 255); + display: flex; + fill: rgb(255, 255, 255); + height: 56px; + -webkit-box-pack: justify; + justify-content: space-between; + padding: 0px 10px; +} + +.rsc-header-title { + margin: 0px; + font-size: 16px; +} + +.rsc-content { + height: calc(408px); + overflow-y: scroll; + margin-top: 2px; + padding-top: 6px; +} + +.rsc-ts-bot { + align-items: flex-start; + display: flex; + -webkit-box-pack: start; + justify-content: flex-start; + height: auto; +} + +.rsc-ts-user { + align-items: flex-start; + display: flex; + -webkit-box-pack: end; + justify-content: flex-start; +} + +.rsc-ts-bot { + animation: 0.3s ease 0s 1 normal forwards running Lmuha; + background: rgb(110, 72, 170); + border-radius: 18px 18px 18px 0px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 1px 2px 0px; + color: rgb(255, 255, 255); + font-size: 14px; + max-width: 75%; + margin: 0px 0px 10px 10px; + overflow: hidden; + position: relative; + padding: 12px; + transform-origin: left bottom; +} + +.rsc-ts-user { + animation: 0.3s ease 0s 1 normal forwards running Lmuha; + background: rgb(255, 255, 255); + border-radius: 18px 18px 0px 18px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 1px 2px 0px; + color: rgb(0, 0, 0); + font-size: 14px; + width: auto; + max-width: 75%; + margin: 0px 10px 10px auto; + overflow: hidden; + position: relative; + padding: 12px; + transform-origin: right bottom; +} + +.rsc-ts-image { + border-radius: 50%; + margin-right: 10px; +} + +.rsc-footer { + position: relative; +} + +.rsc-input { + border-width: 1px 0px 0px; + border-right-style: initial; + border-bottom-style: initial; + border-left-style: initial; + border-right-color: initial; + border-bottom-color: initial; + border-left-color: initial; + border-image: initial; + border-radius: 0px 0px 10px 10px; + border-top-style: solid; + border-top-color: rgb(238, 238, 238); + box-shadow: none; + box-sizing: border-box; + font-size: 16px; + opacity: 0.5; + outline: none; + padding: 16px 52px 16px 10px; + width: 100%; + appearance: none; +} + +.rsc-submit-button { + background-color: transparent; + border: 0px; + border-bottom-right-radius: 10px; + box-shadow: none; + cursor: default; + fill: rgb(74, 74, 74); + opacity: 0.5; + outline: none; + padding: 14px 16px 12px; + position: absolute; + right: 0px; + top: -5px; +} + +.chat-response { + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; +} \ No newline at end of file diff --git a/cv_chat/templates/index.html b/cv_chat/templates/index.html new file mode 100644 index 0000000..249c022 --- /dev/null +++ b/cv_chat/templates/index.html @@ -0,0 +1,64 @@ + + + + + + + Fashion Chatbot + + + + + + + +

Fashion Chatbot

+
+
+
+

Chat

+
+
+
+ avatar +
+

Hello!! How can I help you today.

+
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index ceedba5..3338513 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,17 +1,17 @@ - + - - - + + + - Marko Marković — Resume + Andrei Sirazitdinov — Resume @@ -22,19 +22,19 @@
-
-
M
-
M
+
A
+
S

- Marko Marković + Andrei Sirazitdinov

-
+
@@ -48,16 +48,15 @@

- User Interface Designer + Ph.D. in Computer Science

- Since 2010 + University of Heidelberg | Data Science, ML, AI, Computer Vision

- Minimal and formal résumé website template for print, mo­bile, and desktop. The proportions are - the same on the screen and paper. Built with amazing Tailwind CSS °. + As a researcher, I was focused on causal inference and individualized treatment effect estimation with deep learning. I also worked on explainable AI for healthcare and vision tasks. + Permanent residency and full work authorization in Germany. Open to relocation.

@@ -66,19 +65,15 @@

- Front-End Developer + Research Interests

- Since 2013 + Causal inference, individualized treatment assignment, explainable ML

- “docs/index.html” is the main content file. By copying HTML: add pages, sec­tions, subsection, and - other parts. -

-

- Important: Too much content on one page - will break the page in the form of additional columns. + Built and evaluated deep learning models (MLPs, GANs, VAEs, GNNs) in TensorFlow, Keras, and PyTorch for + healthcare decision support, privacy, and explainability.

@@ -96,16 +91,34 @@

- WebPraktikos Inc. + University of Heidelberg, Germany

- Jun 2018 – Present | Web Developer + Apr 2020 - Oct 2025 | Ph.D. Candidate

-

- Built doner pork chop • Served salmon, cream soft cheese, and brisket • Acted 55% pork chop - • Filled burgdoggen & frankfurter strip steak with 90% burger patties and broth -

+
    +
  • + + Developed individualized treatment assignment strategies using MLPs, GANs, VAEs, and GNNs in TensorFlow/Keras. +
  • +
  • + + Designed pain patient treatment strategies using K-NN clustering and XGBoost with RCT validation. +
  • +
  • + + Built explainable pathology patch classification with prototype learning and decision trees in PyTorch. +
  • +
  • + + Applied digital twin models using stable diffusion for patient data privacy in PyTorch. +
  • +
  • + + Developed a dropout prediction model for cancer patients achieving 80% precision in TensorFlow. +
  • +

@@ -113,21 +126,16 @@

- Mammoth GmbH + National Institute of Informatics, Japan

- Feb 2017 – Apr 2018 | Android Developer + Oct 2018 - Apr 2019 | Research Intern

    - -
  • - - Cooked shrimps for 2 to 3 minutes per side, or until opaque; then, transfered to a serving dish with limon -
  • - - Roasted a pig, turning frequently, until meat reached 160°F in the thickest part of the shoulder or thigh + + Developed and evaluated a computer vision algorithm for long-term video prediction using PyTorch.
@@ -135,20 +143,16 @@

- Exquisite Systems d.o.o. + Irkutsk Branch of MSTUCA, Russia

- May 2015 – Dec 2016 | Software QA Specialist + Oct 2014 - Jul 2015 | Student Assistant

  • - - Made stockfish, which is unsalted fish, usully cod, dried by cold air and wind on wooden racks on the foreshore -
  • -
  • - - Preserved meat without salt by removing fat, cutting it into very thin strips and drying it in the sun or by a fire. + + Built a multi-camera system to capture helicopter panel data and populate digital tables automatically.
@@ -167,10 +171,10 @@

- Graz University of Technology + University of Heidelberg, Germany

- 2014 – 2015 | Master's Degree in Chemistry + 2020 - 2025 | Ph.D. in Computer Science

@@ -180,10 +184,21 @@

- Vienna University of Technology + Saarland University, Germany +

+

+ 2016 - 2019 | M.Sc. in Visual Computing +

+
+
+ +
+
+

+ Irkutsk State University, Russia

- 2010 – 2013 | Bachelor’s Degree in Biology + 2012 - 2016 | B.Sc. in Applied Informatics and Mathematics

@@ -191,10 +206,10 @@

- Vienna University of Technology + High School, Irkutsk, Russia

- 2010 – 2013| Bachelor’s Degree in Chemistry + 2010 - 2012

@@ -212,18 +227,14 @@

- - Universal Resume - - + Trading Bots (Python)

- Since 2019 | HTML CSS + WebSocket API | Docker | GitHub Actions

- Good design is as little design as possible. Less, but better — because it concentrates on the essential - aspects, and the pro­ducts are not burdened with non-essentials. + Developed and deployed trading bots with real-time market data ingestion, containerized workflows, and CI/CD.

@@ -232,49 +243,28 @@

- - tailwindcss-rich-docs - - -

-

- 2017 | JavaScript -

-
-

- Good design is long-lasting. It avoids being fashionable and therefore never appears antiquated.
- Good design is honest. It does not make a product more innovative, powerful or valuable than it really is. -

-
- -
-
-

- Third One + Local ChatGPT Clone

- 2013 – 2014 | Vue + Streamlit | OpenAI API | SQLite

- Good design is innovative. Technological development is always offering new opportunities for innovative - design. + Implemented persistent chat history and dynamic user memory management for a local assistant.

- Fantastic Project + Databricks Integration

- 2012 | JavaScript + Centralized storage for chats and user authentication

- Strip steak tail capicola alcatra ground round tenderloin ar. Venison tri-tip porchetta, brisket - tenderloin pig beef. + Extended the assistant to store chats and auth data in a Databricks-hosted database for analytics.

@@ -292,33 +282,34 @@

- JavaScript + ML/AI and Data

- Middle Level + Research and applied machine learning

- Good parts: pure function, higher-order functions, factory functions, composition. Bad parts: - inheritance, this, new. + End-to-end model development, evaluation, and explainability across healthcare and vision tasks.

    -
  • - ES6 +
  • + TensorFlow +
  • +
  • + PyTorch
  • -
  • - Vue +
  • + Scikit-learn
  • -
  • - Functional Programming +
  • + Keras
  • -
  • - Node +
  • + Pandas +
  • +
  • + NumPy
@@ -329,40 +320,40 @@

- Other + Tools and Platforms

  • - CSS + Python
  • - Rust + SQL
  • - Git + R
  • - Go + Docker
  • - Linux Server + Kubernetes
  • - UI Design + MLflow
  • - Photoshop + Azure (beginner)
  • - Illustrator + Git
  • - Figma + Linux
  • - Typography + Windows
@@ -376,31 +367,19 @@

- CONTACT + LANGUAGES

@@ -409,142 +388,28 @@

- - -
- -
- - -
- -

- ABOUT ME -

- -
-
-

- User Interface Designer -

-

- Since 2010 -

-
-

- Minimal and formal résumé website template for print, mo­bile, and desktop. The proportions are - the same on the screen and paper. Built with amazing Tailwind CSS °. -

-
- -
- -
-
-

- Front-End Developer -

-

- Since 2013 -

-
-

- “docs/index.html” is the main content file. By copying HTML: add pages, sec­tions, subsection, and - other parts. -

-

- Important: Too much content on one page - will break the page - in the form of additional columns. -

-

- If you want to change CSS in the classical way, add a class to the HTML element and write CSS inside “tailwind.css.” -

-
- -
-

- EXPERIENCE + SELECTED PUBLICATIONSd

-
-

- WebPraktikos Inc. -

-

- Jun 2018 – Present | Web Developer -

-
-

- Built doner pork chop • Served salmon, cream soft cheese, and brisket • Acted 55% pork chop - • Filled burgdoggen & frankfurter strip steak with 90% burger patties and broth -

+
    +
  • + A. Sirazitdinov et al., "Graph Neural Networks for Individual Treatment Effect Estimation," IEEE Access, 2024. +
  • +
  • + A. Sirazitdinov et al., "Review of Deep Learning Methods for Individual Treatment Effect Estimation with Automatic Hyperparameter Optimization," TechRxiv, 2022. +
  • +
-
-
-

- Mammoth GmbH -

-

- Feb 2017 – Apr 2018 | Android Developer -

-
-
    -
  • - - Cooked shrimps for 2 to 3 minutes per side, or until opaque; then, transfered to a serving dish with limon -
  • -
  • - - Roasted a pig, turning frequently, until meat reached 160°F in the thickest part of the shoulder or thigh -
  • -
-
- -
-
-

- Exquisite Systems d.o.o. -

-

- May 2015 – Dec 2016 | Software QA Specialist -

-
-
    -
  • - - Made stockfish, which is unsalted fish, usully cod, dried by cold air and wind on wooden racks on the foreshore -
  • -
  • - - Preserved meat without salt by removing fat, cutting it into very thin strips and drying it in the sun or by a fire. -
  • -
  • - - Testend shrimp, crab, lobster, scallops, clams, crawfish -
  • -
-
-
@@ -553,46 +418,25 @@

- EDUCATION + CONTACT

-
-

- Graz University of Technology -

-

- 2014 – 2015 | Master's Degree in Chemistry -

-
-

- Barbecued shrimp, broiled shrimp, shrimp kabobs -

+
    +
  • + Mannheim, Germany +
  • +
  • + dyh@list.ru +
  • +
  • + +49 176 47699707 +
  • +
-
-
-

- Vienna University of Technology -

-

- 2010 – 2013 | Bachelor’s Degree in Biology -

-
-
- -
-
-

- Vienna University of Technology -

-

- 2010 – 2013| Bachelor’s Degree in Chemistry -

-
-

@@ -601,619 +445,90 @@

- PROJECTS + CHAT

-
-
-

- - Universal Resume - - -

-

- Since 2019 | HTML CSS -

-
-

- Good design is as little design as possible. Less, but better — because it concentrates on the essential - aspects, and the pro­ducts are not burdened with non-essentials. -

-
- -
- -
-
-

- - tailwindcss-rich-docs - - -

-

- 2017 | JavaScript -

-
-

- Good design is long-lasting. It avoids being fashionable and therefore never appears antiquated. -

-

- Good design is honest. It does not make a product more innovative, powerful or valuable than it really is. -

-
+
+
+
+

Ask the CV Assistant

+
+
+
+
Hello! Ask me about Andrei’s experience or skills.
+
+
+ +
+
-
-
-

- Third One -

-

- 2013 – 2014 | Vue -

-
-

- Good design is innovative. Technological development is always offering new opportunities for innovative - design. -

-

- Good design emphasizes the usefulness of a product whilst disregarding anything that could possibly detract from it. +

+ Note: the chat requires a backend service to respond.

-
-
-
-

- Fantastic Project -

-

- 2012 | JavaScript -

-
-

- Products fulfilling a purpose are like tools. They are neither decorative objects nor works of art. Their design should therefore be both neutral and restrained, to leave room for the user’s self-expression. -

-
+

-
- - -
+
+ -

- SKILLS -

+

+ -
-
-

- JavaScript -

-

- Middle Level -

-
-

- Good parts: pure function, higher-order functions, factory functions, composition. Bad parts: - inheritance, this, new. -

-
-
    -
  • - ES6 -
  • -
  • - Vue -
  • -
  • - Functional Programming -
  • -
  • - Node -
  • -
-
-
+ -
- -
-
-

- Other -

-
-
- -
-
- - - -
- - -
- -

- CONTACT -

- -
- -
- -
- -
- - - - - - - - -
- - -
- -
- - -
- -

- ABOUT ME -

- -
-
-

- User Interface Designer -

-

- Since 2010 -

-
-

- Minimal and formal résumé website template for print, mo­bile, and desktop. The proportions are - the same on the screen and paper. Built with amazing Tailwind CSS °. -

-
- -
- -
-
-

- Front-End Developer -

-

- Since 2013 -

-
-

- “docs/index.html” is the main content file. By copying HTML: add pages, sec­tions, subsection, and - other parts. -

-

- Important: Too much content on one page - will break the page - in the form of additional columns. -

-

- If you want to change CSS in the classical way, add a class to the HTML element and write CSS inside “tailwind.css.” -

-
- -
- -
- - -
- -

- EXPERIENCE -

- -
-
-

- WebPraktikos Inc. -

-

- Jun 2018 – Present | Web Developer -

-
-

- Built doner pork chop • Served salmon, cream soft cheese, and brisket • Acted 55% pork chop - • Filled burgdoggen & frankfurter strip steak with 90% burger patties and broth -

-
- -
- -
-
-

- Mammoth GmbH -

-

- Feb 2017 – Apr 2018 | Android Developer -

-
-
    -
  • - - Cooked shrimps for 2 to 3 minutes per side, or until opaque; then, transfered to a serving dish with limon -
  • -
  • - - Roasted a pig, turning frequently, until meat reached 160°F in the thickest part of the shoulder or thigh -
  • -
-
- -
-
-

- Exquisite Systems d.o.o. -

-

- May 2015 – Dec 2016 | Software QA Specialist -

-
-
    -
  • - - Made stockfish, which is unsalted fish, usully cod, dried by cold air and wind on wooden racks on the foreshore -
  • -
  • - - Preserved meat without salt by removing fat, cutting it into very thin strips and drying it in the sun or by a fire. -
  • -
  • - - Testend shrimp, crab, lobster, scallops, clams, crawfish -
  • -
-
- -
- -
- - -
- -

- EDUCATION -

- -
-
-

- Graz University of Technology -

-

- 2014 – 2015 | Master's Degree in Chemistry -

-
-

- Barbecued shrimp, broiled shrimp, shrimp kabobs -

-
- -
- -
-
-

- Vienna University of Technology -

-

- 2010 – 2013 | Bachelor’s Degree in Biology -

-
-
- -
-
-

- Vienna University of Technology -

-

- 2010 – 2013| Bachelor’s Degree in Chemistry -

-
-
-
- -
- - -
- -

- PROJECTS -

- -
-
-

- - Universal Resume - - -

-

- Since 2019 | HTML CSS -

-
-

- Good design is as little design as possible. Less, but better — because it concentrates on the essential - aspects, and the pro­ducts are not burdened with non-essentials. -

-
- -
- -
-
-

- - tailwindcss-rich-docs - - -

-

- 2017 | JavaScript -

-
-

- Good design is long-lasting. It avoids being fashionable and therefore never appears antiquated. -

-

- Good design is honest. It does not make a product more innovative, powerful or valuable than it really is. -

-
- -
-
-

- Third One -

-

- 2013 – 2014 | Vue -

-
-

- Good design is innovative. Technological development is always offering new opportunities for innovative - design. -

-

- Good design emphasizes the usefulness of a product whilst disregarding anything that could possibly detract from it. -

-
- -
-
-

- Fantastic Project -

-

- 2012 | JavaScript -

-
-

- Products fulfilling a purpose are like tools. They are neither decorative objects nor works of art. Their design should therefore be both neutral and restrained, to leave room for the user’s self-expression. -

-
- -
- -
- - -
- -

- SKILLS -

- -
-
-

- JavaScript -

-

- Middle Level -

-
-

- Good parts: pure function, higher-order functions, factory functions, composition. Bad parts: - inheritance, this, new. -

-
-
    -
  • - ES6 -
  • -
  • - Vue -
  • -
  • - Functional Programming -
  • -
  • - Node -
  • -
-
-
- -
- -
-
-

- Other -

-
-
-
    -
  • - CSS -
  • -
  • - Rust -
  • -
  • - Git -
  • -
  • - Go -
  • -
  • - Linux Server -
  • -
  • - UI Design -
  • -
  • - Photoshop -
  • -
  • - Illustrator -
  • -
  • - Figma -
  • -
  • - Typography -
  • -
-
-
- -
- -
- - -
- -

- CONTACT -

- -
- -
- -
- -
- -
- - -
- - - + diff --git a/package.json b/package.json index e207e8c..9f10416 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,22 @@ "build": "cross-env NODE_ENV=build postcss ./tailwind.css -o ./docs/build.css" }, "dependencies": { - "@fullhuman/postcss-purgecss": "^2.0.6", - "autoprefixer": "^9.7.4", + "@fullhuman/postcss-purgecss": "^8.0.0", + "@tailwindcss/postcss": "^4.1.18", + "autoprefixer": "^10.4.24", "concurrently": "^5.1.0", "cross-env": "^7.0.0", - "cssnano": "^4.1.10", - "live-server": "^1.2.1", - "postcss-cli": "^7.0.0", - "postcss-import": "^12.0.1", - "tailwindcss": "^1.2.0" + "cssnano": "^7.1.2", + "live-server": "^1.2.0", + "postcss-cli": "^11.0.1", + "postcss-import": "^16.1.1", + "tailwindcss": "^4.1.18" + }, + "overrides": { + "braces": "^3.0.3", + "anymatch": "^3.1.3", + "chokidar": "^3.5.3", + "micromatch": "^4.0.8", + "readdirp": "^3.6.0" } } diff --git a/postcss.config.js b/postcss.config.js index c6e9067..3b97574 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -6,7 +6,9 @@ const purgecss = require("@fullhuman/postcss-purgecss")({ module.exports = { plugins: [ require("postcss-import"), - require("tailwindcss"), + require("@tailwindcss/postcss")({ + config: "./tailwind.config.js" + }), require("autoprefixer"), ...process.env.NODE_ENV === "build" ? [purgecss, require("cssnano")] : [] diff --git a/tailwind.css b/tailwind.css index 95022c5..655956f 100644 --- a/tailwind.css +++ b/tailwind.css @@ -1,7 +1,5 @@ @import "docs/fira-go.css"; -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; +@import "tailwindcss"; @screen print { @page { @@ -13,7 +11,7 @@ body { -webkit-print-color-adjust: exact !important; - color-adjust: exact !important; + print-color-adjust: exact !important; color: black !important; } @@ -83,3 +81,104 @@ body .icon-parent::before { visibility: hidden; display: inline-block; } + +/* Scoped chat widget styles (avoid global overrides) */ +.cv-chat { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.cv-chat-container { + background: rgb(245, 248, 251); + border-radius: 10px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 12px 24px 0px; + overflow: hidden; + width: 100%; + max-width: 22rem; + height: 26rem; + margin-top: 0.5rem; +} + +.cv-chat-header { + align-items: center; + background: hsl(214, 17%, 32%); + color: rgb(255, 255, 255); + display: flex; + justify-content: space-between; + padding: 0.5rem 0.75rem; +} + +.cv-chat-header-title { + margin: 0; + font-size: 0.95rem; + font-weight: 600; +} + +.cv-chat-content { + height: 18rem; + overflow-y: auto; + margin-top: 2px; + padding: 0.5rem 0.5rem 0; +} + +.cv-chat-row { + display: flex; + gap: 0.5rem; + margin-bottom: 0.6rem; +} + +.cv-chat-row.user { + justify-content: flex-end; +} + +.cv-chat-bubble { + font-size: 0.85rem; + line-height: 1.25rem; + max-width: 75%; + padding: 0.5rem 0.65rem; + border-radius: 12px; + box-shadow: rgba(0, 0, 0, 0.15) 0px 1px 2px 0px; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.cv-chat-bubble.bot { + background: hsl(214, 17%, 32%); + color: #fff; + border-bottom-left-radius: 2px; +} + +.cv-chat-bubble.user { + background: #fff; + color: #000; + border-bottom-right-radius: 2px; +} + +.cv-chat-footer { + position: relative; +} + +.cv-chat-input { + border-top: 1px solid rgb(238, 238, 238); + border-radius: 0 0 10px 10px; + box-shadow: none; + box-sizing: border-box; + font-size: 0.9rem; + outline: none; + padding: 0.75rem 3.5rem 0.75rem 0.6rem; + width: 100%; + appearance: none; +} + +.cv-chat-submit { + background: transparent; + border: 0; + cursor: pointer; + font-size: 0.9rem; + padding: 0.6rem 0.75rem; + position: absolute; + right: 0; + top: 0; + color: hsl(214, 17%, 32%); + font-weight: 600; +} diff --git a/tutorial.md b/tutorial.md new file mode 100644 index 0000000..ff7c341 --- /dev/null +++ b/tutorial.md @@ -0,0 +1,90 @@ +Universal Resume: Tutorial +========================= + +This guide covers editing the CV, building locally, and deploying to Azure (static or Container Apps), plus CI/CD. + +Prerequisites +------------- +- Node.js + npm +- Docker Desktop (only for container deployment) +- Azure account +- Azure CLI for Windows + +Project Structure +----------------- +- `docs/index.html`: main content +- `tailwind.css`, `tailwind.config.js`, `postcss.config.js`: styling pipeline +- `docs/`: build output +- `Dockerfile.chat`: combined CV + chat container (HTTPS via Container Apps) +- `cv_chat/`: chat backend + context + +1) Edit the CV +-------------- +Update `docs/index.html` with your content. + +2) Local Build +-------------- +``` +npm install +npm run build +``` + +Run dev server: +``` +npm run serve +``` + +3) Container Apps (HTTPS, Paid) +------------------------------- +Build and deploy the combined CV + chat container: +``` +$rg = "universal-resume-rg" +$loc = "germanywestcentral" +$acr = "resumereg74506" +$appEnv = "resume-ca-env" +$appName = "resume-ca" +$imgTag = "resume-web:2" +$fullImage = "$acr.azurecr.io/$imgTag" +$az = "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" + +& $az acr login --name $acr + +docker build -f Dockerfile.chat -t $fullImage . +docker push $fullImage + +$envExists = & $az containerapp env show --name $appEnv --resource-group $rg --query "name" -o tsv 2>$null +if (-not $envExists) { & $az containerapp env create --name $appEnv --resource-group $rg --location $loc } + +$appExists = & $az containerapp show --name $appName --resource-group $rg --query "name" -o tsv 2>$null +if ($appExists) { + & $az containerapp update --name $appName --resource-group $rg --image $fullImage +} else { + $acrUser = & $az acr credential show --name $acr --query "username" -o tsv + $acrPass = & $az acr credential show --name $acr --query "passwords[0].value" -o tsv + & $az containerapp create --name $appName --resource-group $rg --environment $appEnv --image $fullImage ` + --registry-server "$acr.azurecr.io" --registry-username $acrUser --registry-password $acrPass ` + --target-port 8080 --ingress external --min-replicas 1 --max-replicas 1 +} + +$fqdn = & $az containerapp show --name $appName --resource-group $rg --query "properties.configuration.ingress.fqdn" -o tsv +"https://$fqdn" +``` + +Chat env vars: +``` +& $az containerapp update --name $appName --resource-group $rg ` + --set-env-vars AZURE_OPENAI_API_KEY=YOUR_KEY AZURE_OPENAI_ENDPOINT=YOUR_ENDPOINT +``` + +4) CI/CD (Container Apps) +------------------------- + +Example workflow: `.github/workflows/deploy-containerapp.yml`. +Secrets: +- `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID`, `AZURE_CLIENT_SECRET` +- `ACR_NAME`, `RESOURCE_GROUP`, `CONTAINERAPP_NAME` +- `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT` + +Notes +----- +- Container Apps provides HTTPS by default but is not fully free.