Blame
|
1 | # GC Jobs |
||||||
| 2 | ||||||||
| 3 | ← [[Home]] |
|||||||
| 4 | ||||||||
| 5 | Two services on PCT 107 (debian, 192.168.2.81) for Government of Canada job hunting. |
|||||||
| 6 | ||||||||
| 7 | --- |
|||||||
| 8 | ||||||||
| 9 | ## gcjobs-qa (stack: `gcjobs-qa`) |
|||||||
| 10 | ||||||||
| 11 | Streamlit STAR interview prep assistant at `gcjobs.carr-family.org` (Authentik protected). |
|||||||
| 12 | ||||||||
| 13 | - **Port:** 8501 (host-mode on 192.168.2.81) |
|||||||
| 14 | - **Image:** `gcjobs-qa:latest` — built locally on PCT 107 |
|||||||
| 15 | - **Data volume:** `gcjobs-qa_gcjobs_data` → `/app/data/` (resume.json, star_answers.csv, etc.) |
|||||||
| 16 | - **Env:** `OPENAI_API_KEY` in compose |
|||||||
| 17 | ||||||||
| 18 | --- |
|||||||
| 19 | ||||||||
| 20 | ## gcjobs-filler (stack: `gcjobs-filler`) |
|||||||
| 21 | ||||||||
| 22 | Selenium bot that pre-fills GC Jobs (PSC) applications section-by-section and emails a summary for manual review before submitting. |
|||||||
| 23 | ||||||||
| 24 | - **URL:** `gcjobs-filler.carr-family.org` (no Authentik — static route in `routes.yml`) |
|||||||
| 25 | - **Port:** 8000 (FastAPI, host-mode on 192.168.2.81) |
|||||||
| 26 | - **Image:** `gcjobs-filler:latest` — built locally on PCT 107 from `/tank/appdata/gcjobs-filler/app/` |
|||||||
| 27 | - **Compose:** `/tank/appdata/gcjobs-filler/docker-compose.yml` — `traefik.enable=false`, routing via `routes.yml` |
|||||||
| 28 | - **Data:** `/tank/appdata/gcjobs-filler/data/` on ZFS — mounted into PCT 107 via `mp3`, then Docker bind-mounts to `/app/data` |
|||||||
| 29 | - **STAR data:** gcjobs-qa answer library mounted read-only at `/app/star_data` from `gcjobs-qa_gcjobs_data` volume on PCT 107 |
|||||||
| 30 | - **Permissions:** data dir `chmod 777`, files `666`, screenshots dir `777` (ZFS owned root:root, container writes as unprivileged user) |
|||||||
| 31 | - **Notifications:** email via Proton Bridge SMTP when pre-fill completes |
|||||||
| 32 | ||||||||
| 33 | --- |
|||||||
| 34 | ||||||||
| 35 | ## Bot Flow |
|||||||
| 36 | ||||||||
| 37 | Navigate to `page1710?careerChoiceId=<id>&psrsMode=11` → PSC login (UserNumber/Password) → OTP from Proton Bridge IMAP (`Folders/GC Jobs`) → optional `page1570` (Notice page — bot clicks Continue) → optional `page1960` (Confirmation/consent — bot checks checkbox then clicks Continue) → `/applicant/<appid>/page1600` → fill 8 sections → stop before Submit → email notification. |
|||||||
| 38 | ||||||||
| 39 | **8 sections filled:** Employee info, Résumé, Screening Questions, Work locations, Classification, Education, Languages, Employment Equity. |
|||||||
| 40 | ||||||||
| 41 | **On unexpected pages:** bot saves a screenshot to `/app/data/screenshots/` for debugging. |
|||||||
| 42 | ||||||||
| 43 | --- |
|||||||
| 44 | ||||||||
| 45 | ## Supported Job URL Formats |
|||||||
| 46 | ||||||||
| 47 | | Format | Example | |
|||||||
| 48 | |--------|---------| |
|||||||
| 49 | | Standard posting | `page1800?poster=2424673` | |
|||||||
| 50 | | Direct apply link | `page1710?careerChoiceId=2424673` | |
|||||||
| 51 | | Newer PSC format | `/applicant/1156524/page1820?careerChoice=2424673` | |
|||||||
| 52 | ||||||||
| 53 | Career ID is extracted automatically from any of these formats. |
|||||||
| 54 | ||||||||
| 55 | --- |
|||||||
| 56 | ||||||||
| 57 | ## Config Files |
|||||||
| 58 | ||||||||
| 59 | | File | Purpose | |
|||||||
| 60 | |------|---------| |
|||||||
| 61 | | `/tank/appdata/gcjobs-filler/data/config.yml` | GCKey creds (`[email protected]`), IMAP config, email, monitor settings | |
|||||||
| 62 | | `/tank/appdata/gcjobs-filler/data/profile.yml` | Employment info, language prefs, screening Q answers + narratives | |
|||||||
| 63 | ||||||||
| 64 | ### Key profile.yml Settings |
|||||||
| 65 | ||||||||
| 66 | - `employment.classification` — must match visible text in the PSC dropdown (e.g. `"EC-05"`) |
|||||||
| 67 | - `screening_questions` — map question-text substrings to `'yes'`/`'no'`; bot fuzzy-matches (≥85%) dynamically per job |
|||||||
| 68 | - `screening_narratives` — free-text responses for Yes answers; same substring keys |
|||||||
| 69 | ||||||||
| 70 | --- |
|||||||
| 71 | ||||||||
| 72 | ## Screening Question Resolution Pipeline (`app/ai_helper.py`) |
|||||||
| 73 | ||||||||
| 74 | 1. Fuzzy match (≥85% via `rapidfuzz`) against `profile.yml` `screening_questions` → use profiled answer |
|||||||
| 75 | 2. If answer is `yes` and no narrative in `screening_narratives`, fuzzy-match question against STAR CSV → AI adapts that answer as narrative |
|||||||
| 76 | 3. No profile match → fuzzy-match (≥70%) against STAR CSV → answer `yes` + AI-adapt STAR narrative |
|||||||
| 77 | 4. No STAR match → ask OpenAI (`gpt-4o-mini`) yes/no from resume.json → if yes, generate narrative via `gpt-4o` |
|||||||
| 78 | ||||||||
| 79 | **"Section link not found for screeningQuestions"** — normal; the bot skips jobs with no screening questions. |
|||||||
| 80 | ||||||||
| 81 | --- |
|||||||
| 82 | ||||||||
| 83 | ## Web UI Tabs |
|||||||
| 84 | ||||||||
| 85 | | Tab | Purpose | |
|||||||
| 86 | |-----|---------| |
|||||||
| 87 | | Apply | Submit a job URL | |
|||||||
| 88 | | Monitor | Watch the bot's progress | |
|||||||
| 89 | | Jobs | Browse scraped job listings | |
|||||||
| 90 | | History | Past applications | |
|||||||
| 91 | | Profile | Employment, languages, education, EE consent, screening Qs + narratives | |
|||||||
| 92 | | STAR Answers | Searchable answer library (from gcjobs-qa) | |
|||||||
| 93 | | Settings | GCKey creds, IMAP, SMTP, monitor config | |
|||||||
| 94 | ||||||||
| 95 | **Settings password gotcha** — browsers block pre-filling `type="password"` fields, so opening Settings shows blank boxes. The backend preserves existing passwords when an empty string is submitted — safe to save without touching password fields. |
|||||||
| 96 | ||||||||
| 97 | --- |
|||||||
| 98 | ||||||||
| 99 | ## Monitor Filters (Settings tab) |
|||||||
| 100 | ||||||||
| 101 | - `keywords` — title/classification substrings |
|||||||
| 102 | - `classifications` — e.g. `EC-05` |
|||||||
| 103 | - `language_requirements` — e.g. `bilingual`, `english essential`; blank = all |
|||||||
| 104 | ||||||||
| 105 | **Jobs table** shows extracted language requirement per posting (colour-coded: purple = bilingual, blue = English essential). |
|||||||
| 106 | ||||||||
| 107 | --- |
|||||||
| 108 | ||||||||
| 109 | ## Rebuild & Redeploy |
|||||||
| 110 | ||||||||
| 111 | ```bash |
|||||||
| 112 | # Build (context is parent dir — Dockerfile does COPY app/ .) |
|||||||
| 113 | tar -cf - -C /tank/appdata/gcjobs-filler --exclude='./data' . | \ |
|||||||
| 114 | pct exec 107 -- bash -c 'rm -rf /tmp/gcjobs-build && mkdir -p /tmp/gcjobs-build && tar -xf - -C /tmp/gcjobs-build' |
|||||||
| 115 | pct exec 107 -- docker build -t gcjobs-filler:latest /tmp/gcjobs-build/ |
|||||||
| 116 | ||||||||
| 117 | # Image-only change (no compose changes): |
|||||||
| 118 | pct exec 108 -- docker service update --force gcjobs-filler_gcjobs-filler |
|||||||
| 119 | ||||||||
| 120 | # Compose changes (volumes, ports, etc.) — must pipe file in; PCT 108 can't read /tank/appdata/gcjobs-filler/ directly: |
|||||||
| 121 | cat /tank/appdata/gcjobs-filler/docker-compose.yml | \ |
|||||||
| 122 | pct exec 108 -- bash -c 'cat > /tmp/gcjobs-filler-compose.yml' |
|||||||
| 123 | pct exec 108 -- docker stack deploy -c /tmp/gcjobs-filler-compose.yml gcjobs-filler |
|||||||
| 124 | ``` |
|||||||
| 125 | ||||||||
| 126 | **Deploy gotcha:** PCT 108 cannot access `/tank/appdata/gcjobs-filler/` (not in its LXC mounts). Always pipe the compose file in via stdin. `cp /tank/... /tmp/...` on the Proxmox host writes to the host's `/tmp/`, not PCT 108's. |
|||||||
| 127 | ||||||||
| 128 | --- |
|||||||
| 129 | ||||||||
| 130 | ## Known Issue |
|||||||
| 131 | ||||||||
| 132 | `Client.__init__() got an unexpected keyword argument 'proxies'` — AI narrative generation is broken in gcjobs-filler. Leaving screening question narratives unfilled. Cause: OpenAI client version incompatibility. |
|||||||
